Files
crm/apps/web/src/components/person-assets/PersonAssetsPanel.vue
T

882 lines
27 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { computed, onMounted, ref, watch } from 'vue';
import axios from 'axios';
import { api } from '@/lib/api';
import { formatMoney } from '@/lib/money';
import MoneyInput from '@/components/MoneyInput.vue';
import type {
PersonAccountingLinkDto,
PersonAssetsDto,
TahesabConnectionDto,
TahesabSanadType,
} from '@mento/shared';
import { TAHSAB_SANAD_LABELS } from '@/lib/tahesab';
const props = defineProps<{
personId: string;
}>();
const loading = ref(false);
const error = ref('');
const ok = ref('');
const connections = ref<TahesabConnectionDto[]>([]);
const links = ref<PersonAccountingLinkDto[]>([]);
const connectionId = ref('');
const assets = ref<PersonAssetsDto | null>(null);
const activeTab = ref<'مانده حساب' | 'جزئیات حساب' | 'ثبت سند'>('مانده حساب');
const asnadLoading = ref(false);
const asnadItems = ref<Record<string, unknown>[]>([]);
const fromDate = ref('');
const toDate = ref('');
const manualCode = ref('');
const matchCandidates = ref<
Array<{ moshtariCode: number; name: string; tel: string; matchBy: string }>
>([]);
const sanadBusy = ref(false);
const sanad = ref({
docType: 'buy_sale_gold' as TahesabSanadType,
year: 1405,
month: 1,
day: 1,
sharh: '',
vazn: 0,
ayar: 750,
buyOrSale: 0 as 0 | 1,
mazaneh: 0,
mazanehIsGeram: 0 as 0 | 1,
isAbshode: 1 as 0 | 1,
mablaghKol: 0,
count: 1,
nameSekeh: '',
isBedehiOrTalab: 0 as 0 | 1,
mablagh: 0,
baabat: 'CRM',
isVoroodOrKhorooj: 0 as 0 | 1,
angNumber: '',
nameAz: '',
isDaryaftOrPardakht: 1 as 0 | 1,
nameBank: '',
codeRahgiri: '',
});
const currentLink = computed(() =>
links.value.find((l) => l.connectionId === connectionId.value) ?? null,
);
const hasMultipleConnections = computed(() => connections.value.length > 1);
const goldBalance = computed(() => assets.value?.goldBalance ?? null);
const rialBalance = computed(() => assets.value?.rialBalance ?? null);
const mandehGroups = computed(() => {
const items = assets.value?.mandehDetail ?? [];
const groups: Record<string, typeof items> = {
مالی: [],
'فلزات/وزنی': [],
سکه: [],
کالا: [],
ارز: [],
سایر: [],
};
for (const item of items) {
const name = item.Name || '';
if (/ریال|تومان|مالی|پول|وجه/i.test(name)) groups['مالی']!.push(item);
else if (/سکه/i.test(name)) groups['سکه']!.push(item);
else if (/ارز|دلار|یورو/i.test(name)) groups['ارز']!.push(item);
else if (/طلا|نقره|پلاتین|وزن|آبشده|شمش/i.test(name))
groups['فلزات/وزنی']!.push(item);
else if (/کار|اتیکت|کالا/i.test(name)) groups['کالا']!.push(item);
else groups['سایر']!.push(item);
}
return Object.entries(groups)
.filter(([, list]) => list.length > 0)
.map(([label, list]) => ({ label, items: list }));
});
function balanceClass(v: number | null) {
if (v == null || v === 0) return '';
return v > 0 ? 'credit' : 'debt';
}
function balanceLabel(v: number | null) {
if (v == null || v === 0) return '';
return v > 0 ? 'بستانکار' : 'بدهکار';
}
function formatAmt(v: number | null, digits = 3) {
if (v == null) return '—';
return Math.abs(v).toLocaleString('fa-IR', {
maximumFractionDigits: digits,
minimumFractionDigits: 0,
});
}
function formatMandehAmount(amount: string | number, unit: string) {
const n = Number(amount);
if (!Number.isFinite(n)) return String(amount);
const abs = Math.abs(n);
if (unit === 'ریال') return formatMoney(abs, 'IRR');
return abs.toLocaleString('fa-IR', { maximumFractionDigits: 3 });
}
function defaultJalaliRange() {
// تقریبی: کاربر می‌تواند عوض کند؛ پیش‌فرض بازه یک‌ماهه متنی
const now = new Date();
// بدون وابستگی سنگین: مقادیر نمونه قابل ویرایش
toDate.value = `${now.getFullYear() - 621}-01-01`;
// بهتر: از API تاریخ نخواهیم؛ فیلدها را خالی‌تر با placeholder بگذاریم
const y = 1405;
fromDate.value = `${y}-01-01`;
toDate.value = `${y}-12-29`;
sanad.value.year = y;
sanad.value.month = 1;
sanad.value.day = 1;
}
async function loadConnectionsAndLinks() {
loading.value = true;
error.value = '';
try {
const [connRes, linkRes] = await Promise.all([
api.get<TahesabConnectionDto[]>('/integrations/tahesab/connections/public'),
api.get<PersonAccountingLinkDto[]>(
`/integrations/tahesab/people/${props.personId}/links`,
),
]);
connections.value = connRes.data.filter((c) => c.enabled);
links.value = linkRes.data;
const def =
connections.value.find((c) => c.isDefault) ?? connections.value[0];
if (!connectionId.value && def) connectionId.value = def.id;
else if (
connectionId.value &&
!connections.value.some((c) => c.id === connectionId.value)
) {
connectionId.value = def?.id ?? '';
}
} catch (e) {
error.value = axios.isAxiosError(e)
? String(
(e.response?.data as { message?: string })?.message ??
'بارگذاری اتصال ته‌حساب ناموفق بود.',
)
: 'بارگذاری اتصال ته‌حساب ناموفق بود.';
} finally {
loading.value = false;
}
}
async function refreshAssets() {
if (!connectionId.value || !currentLink.value) {
assets.value = null;
return;
}
loading.value = true;
error.value = '';
try {
const { data } = await api.get<PersonAssetsDto>(
`/integrations/tahesab/people/${props.personId}/assets`,
{ params: { connectionId: connectionId.value, refresh: '1' } },
);
assets.value = data;
} catch (e) {
assets.value = null;
error.value = axios.isAxiosError(e)
? String(
(e.response?.data as { message?: string })?.message ??
'دریافت مانده ناموفق بود.',
)
: 'دریافت مانده ناموفق بود.';
} finally {
loading.value = false;
}
}
async function loadAsnad() {
if (!connectionId.value || !currentLink.value) return;
if (!fromDate.value || !toDate.value) {
error.value = 'تاریخ از و تا را وارد کنید (شمسی YYYY-MM-DD).';
return;
}
asnadLoading.value = true;
error.value = '';
try {
const { data } = await api.get<{ items: Record<string, unknown>[] }>(
`/integrations/tahesab/people/${props.personId}/asnad`,
{
params: {
connectionId: connectionId.value,
from: fromDate.value,
to: toDate.value,
},
},
);
asnadItems.value = data.items ?? [];
} catch (e) {
error.value = axios.isAxiosError(e)
? String(
(e.response?.data as { message?: string })?.message ??
'دریافت اسناد ناموفق بود.',
)
: 'دریافت اسناد ناموفق بود.';
} finally {
asnadLoading.value = false;
}
}
async function runMatch() {
if (!connectionId.value) return;
error.value = '';
ok.value = '';
try {
const { data } = await api.post<{
status: string;
candidates: typeof matchCandidates.value;
link: PersonAccountingLinkDto | null;
}>(`/integrations/tahesab/people/${props.personId}/match`, {
connectionId: connectionId.value,
autoLinkIfUnique: true,
});
matchCandidates.value = data.candidates ?? [];
if (data.status === 'linked') {
ok.value = 'مشتری با موفقیت لینک شد.';
await loadConnectionsAndLinks();
await refreshAssets();
} else if (data.status === 'none') {
error.value =
'مشتری در ته‌حساب پیدا نشد. کد را دستی وارد کنید یا «ثبت در ته‌حساب» را بزنید.';
} else if (data.status === 'ambiguous') {
error.value = 'چند مشتری پیدا شد — یکی را انتخاب کنید.';
} else if (data.status === 'unique') {
ok.value = 'یک مشتری پیدا شد — برای لینک تأیید کنید.';
}
} catch (e) {
error.value = axios.isAxiosError(e)
? String((e.response?.data as { message?: string })?.message ?? 'تطبیق ناموفق')
: 'تطبیق ناموفق';
}
}
async function linkCode(code: number) {
error.value = '';
ok.value = '';
try {
await api.post(`/integrations/tahesab/people/${props.personId}/links`, {
connectionId: connectionId.value,
moshtariCode: code,
});
ok.value = `لینک به کد ${code} انجام شد.`;
matchCandidates.value = [];
await loadConnectionsAndLinks();
await refreshAssets();
} catch (e) {
error.value = axios.isAxiosError(e)
? String((e.response?.data as { message?: string })?.message ?? 'لینک ناموفق')
: 'لینک ناموفق';
}
}
async function manualLink() {
const code = Number(manualCode.value);
if (!Number.isFinite(code) || code <= 0) {
error.value = 'کد مشتری معتبر وارد کنید.';
return;
}
await linkCode(code);
}
async function createInTahesab() {
if (!connectionId.value) return;
if (!confirm('این شخص به‌عنوان مشتری جدید در ته‌حساب ثبت شود؟')) return;
error.value = '';
ok.value = '';
try {
await api.post(
`/integrations/tahesab/people/${props.personId}/create-in-tahesab`,
{ connectionId: connectionId.value },
);
ok.value = 'مشتری در ته‌حساب ثبت و لینک شد.';
await loadConnectionsAndLinks();
await refreshAssets();
} catch (e) {
error.value = axios.isAxiosError(e)
? String(
(e.response?.data as { message?: string })?.message ?? 'ثبت ناموفق',
)
: 'ثبت ناموفق';
}
}
async function unlink() {
if (!connectionId.value || !currentLink.value) return;
if (!confirm('لینک این شخص با ته‌حساب قطع شود؟')) return;
await api.delete(
`/integrations/tahesab/people/${props.personId}/links/${connectionId.value}`,
);
assets.value = null;
await loadConnectionsAndLinks();
}
async function pushSanad() {
if (!connectionId.value || !currentLink.value) return;
if (!confirm('سند در ته‌حساب ثبت شود؟ این عمل مالی است و قابل بازگشت از CRM نیست.')) {
return;
}
sanadBusy.value = true;
error.value = '';
ok.value = '';
try {
const body = {
connectionId: connectionId.value,
...sanad.value,
};
const { data } = await api.post<{
idempotent?: boolean;
log: { factorCode: string; status: string; tahesabOk: string | null };
}>(`/integrations/tahesab/people/${props.personId}/sanad`, body);
ok.value = data.idempotent
? `سند تکراری نبود — همان Factor_Code قبلی: ${data.log.factorCode}`
: `سند ثبت شد. کد: ${data.log.tahesabOk ?? data.log.factorCode}`;
await refreshAssets();
if (activeTab.value === 'جزئیات حساب') await loadAsnad();
} catch (e) {
error.value = axios.isAxiosError(e)
? String(
(e.response?.data as { message?: string | string[] })?.message ??
'ثبت سند ناموفق',
)
: 'ثبت سند ناموفق';
} finally {
sanadBusy.value = false;
}
}
watch(connectionId, async () => {
matchCandidates.value = [];
asnadItems.value = [];
if (currentLink.value) await refreshAssets();
else assets.value = null;
});
onMounted(async () => {
defaultJalaliRange();
await loadConnectionsAndLinks();
if (currentLink.value) await refreshAssets();
});
</script>
<template>
<div class="assets">
<p v-if="!connections.length && !loading" class="empty">
اتصال ته‌حساب تعریف نشده است.
<RouterLink to="/settings/tahesab">تنظیمات ته‌حساب</RouterLink>
</p>
<template v-else>
<div class="toolbar">
<label v-if="hasMultipleConnections" class="conn-pick">
<span>اتصال</span>
<select v-model="connectionId">
<option v-for="c in connections" :key="c.id" :value="c.id">
{{ c.label }} ({{ c.dbName }})
</option>
</select>
</label>
<div v-else-if="connections[0]" class="muted">
اتصال: {{ connections[0].label }} · DB {{ connections[0].dbName }}
</div>
</div>
<div class="link-bar card">
<template v-if="currentLink">
<div>
<strong>لینک‌شده</strong>
<span class="muted">
کد ته‌حساب: {{ currentLink.moshtariCode }} · روش:
{{ currentLink.matchMethod }}
</span>
</div>
<div class="actions">
<button type="button" class="btn ghost" @click="refreshAssets">بروزرسانی مانده</button>
<button type="button" class="btn ghost" @click="unlink">قطع لینک</button>
</div>
</template>
<template v-else>
<p class="muted">این مشتری هنوز به ته‌حساب لینک نشده است.</p>
<div class="actions wrap">
<button type="button" class="btn" @click="runMatch">تطبیق خودکار</button>
<button type="button" class="btn ghost" @click="createInTahesab">ثبت در ته‌حساب</button>
<div class="manual">
<input
v-model="manualCode"
type="number"
min="1"
placeholder="کد مشتری"
dir="ltr"
/>
<button type="button" class="btn ghost" @click="manualLink">لینک دستی</button>
</div>
</div>
<ul v-if="matchCandidates.length" class="candidates">
<li v-for="c in matchCandidates" :key="c.moshtariCode">
<button type="button" class="btn ghost" @click="linkCode(c.moshtariCode)">
{{ c.moshtariCode }} — {{ c.name || 'بدون نام' }}
<span class="muted">({{ c.tel || c.matchBy }})</span>
</button>
</li>
</ul>
</template>
</div>
<p v-if="error" class="error">{{ error }}</p>
<p v-if="ok" class="ok">{{ ok }}</p>
<p v-if="loading" class="muted">در حال دریافت اطلاعات…</p>
<template v-if="currentLink && assets">
<section class="summary card">
<h3>دارایی</h3>
<div class="balance-row">
<span>دارایی طلایی</span>
<div class="val" :class="balanceClass(goldBalance)" dir="ltr">
<small v-if="balanceLabel(goldBalance)">{{ balanceLabel(goldBalance) }}</small>
{{ formatAmt(goldBalance) }}
<em>(گرم)</em>
</div>
</div>
<div class="balance-row">
<span>دارایی ریالی</span>
<div class="val" :class="balanceClass(rialBalance)" dir="ltr">
<small v-if="balanceLabel(rialBalance)">{{ balanceLabel(rialBalance) }}</small>
{{ rialBalance != null ? formatMoney(Math.abs(rialBalance), 'IRR') : '—' }}
</div>
</div>
<div v-if="assets.totalGoldEquiv != null" class="balance-row">
<span>دارایی کل به طلا</span>
<div class="val" :class="balanceClass(assets.totalGoldEquiv)" dir="ltr">
{{ formatAmt(assets.totalGoldEquiv) }}
<em>(گرم)</em>
</div>
</div>
<p class="muted tiny">آخرین دریافت: {{ assets.fetchedAt }}</p>
</section>
<div class="tabs">
<button
v-for="t in ['مانده حساب', 'جزئیات حساب', 'ثبت سند'] as const"
:key="t"
type="button"
class="tab"
:class="{ on: activeTab === t }"
@click="activeTab = t"
>
{{ t }}
</button>
</div>
<div v-show="activeTab === 'مانده حساب'" class="detail">
<template v-for="group in mandehGroups" :key="group.label">
<div class="group-title">
<span class="line" />
<strong>{{ group.label }}</strong>
<span class="line" />
</div>
<div
v-for="(item, idx) in group.items"
:key="idx"
class="mandeh-card card"
>
<div class="name">{{ item.Name }}:</div>
<div class="vals">
<template v-for="(v, vi) in item.values" :key="vi">
<span :class="Number(v.amount) >= 0 ? 'credit' : 'debt'">
{{ formatMandehAmount(v.amount, v.unit) }}
</span>
<span class="unit">{{ v.unit }}</span>
<span class="st">{{ v.status === 'بس' ? 'بد' : 'بس' }}</span>
<span v-if="vi < item.values.length - 1" class="sep">|</span>
</template>
</div>
</div>
</template>
<p v-if="!mandehGroups.length" class="muted">جزئیات مانده‌ای نیست.</p>
</div>
<div v-show="activeTab === 'جزئیات حساب'" class="asnad">
<div class="date-row">
<label>
از
<input v-model="fromDate" dir="ltr" placeholder="1405-01-01" />
</label>
<label>
تا
<input v-model="toDate" dir="ltr" placeholder="1405-12-29" />
</label>
<button type="button" class="btn" :disabled="asnadLoading" @click="loadAsnad">
{{ asnadLoading ? '…' : 'نمایش اسناد' }}
</button>
</div>
<div class="asnad-grid">
<article
v-for="(item, idx) in asnadItems"
:key="String(item.Factor_Code ?? item.ID ?? idx)"
class="sanad-card card"
>
<div class="sc-head">
<span>شماره سند: {{ item.Sh_Factor ?? item.Sh_factor ?? '—' }}</span>
<span>تاریخ: {{ item.Tarikh ?? '—' }}</span>
</div>
<div class="sc-row gold">
<span>نوع</span>
<strong>{{ item.NO ?? '—' }}</strong>
</div>
<div class="sc-row gold">
<span>وزن / عیار</span>
<strong dir="ltr">{{ item.Vazn ?? '—' }} / {{ item.Ayar ?? '—' }}</strong>
</div>
<div class="sc-row cash">
<span>مالی</span>
<strong dir="ltr">{{ item.Mali ?? '—' }}</strong>
</div>
<div class="sc-row">
<span>شرح</span>
<span>{{ item.Sharh1 ?? item.Sharh ?? '—' }}</span>
</div>
</article>
</div>
<p v-if="!asnadLoading && !asnadItems.length" class="muted">سندی نیست.</p>
</div>
<form v-show="activeTab === 'ثبت سند'" class="sanad-form card" @submit.prevent="pushSanad">
<p class="warn">
ثبت سند مستقیماً در ته‌حساب انجام می‌شود. قبل از تأیید مقادیر را دوباره بررسی کنید.
</p>
<label>
نوع سند
<select v-model="sanad.docType">
<option v-for="(label, key) in TAHSAB_SANAD_LABELS" :key="key" :value="key">
{{ label }}
</option>
</select>
</label>
<div class="triple">
<label>سال<input v-model.number="sanad.year" type="number" required /></label>
<label>ماه<input v-model.number="sanad.month" type="number" min="1" max="12" required /></label>
<label>روز<input v-model.number="sanad.day" type="number" min="1" max="31" required /></label>
</div>
<label>شرح<input v-model="sanad.sharh" /></label>
<template v-if="sanad.docType === 'buy_sale_gold' || sanad.docType === 'buy_sale_sekeh'">
<div class="triple">
<label>وزن<input v-model.number="sanad.vazn" type="number" step="any" required /></label>
<label>عیار<input v-model.number="sanad.ayar" type="number" step="any" required /></label>
<label>
خرید/فروش
<select v-model.number="sanad.buyOrSale">
<option :value="1">خرید</option>
<option :value="0">فروش</option>
</select>
</label>
</div>
<div class="triple">
<label>
مظنه
<MoneyInput v-model="sanad.mazaneh" as-number :decimals="2" required />
</label>
<label>
مبلغ کل
<MoneyInput v-model="sanad.mablaghKol" as-number :decimals="0" required />
</label>
<label v-if="sanad.docType === 'buy_sale_gold'">
آبشده؟
<select v-model.number="sanad.isAbshode">
<option :value="1">آبشده</option>
<option :value="0">متفرقه</option>
</select>
</label>
</div>
<template v-if="sanad.docType === 'buy_sale_sekeh'">
<label>تعداد<input v-model.number="sanad.count" type="number" step="any" required /></label>
<label>نام سکه<input v-model="sanad.nameSekeh" required /></label>
</template>
</template>
<template v-else-if="sanad.docType === 'talab_bedehi'">
<label>
نوع
<select v-model.number="sanad.isBedehiOrTalab">
<option :value="0">طلب</option>
<option :value="1">بدهی</option>
</select>
</label>
<label>
مبلغ
<MoneyInput v-model="sanad.mablagh" as-number :decimals="0" required />
</label>
<label>وزن (اختیاری)<input v-model.number="sanad.vazn" type="number" step="any" /></label>
<label>بابت<input v-model="sanad.baabat" /></label>
</template>
<template v-else-if="sanad.docType === 'vkh_gold'">
<div class="triple">
<label>وزن<input v-model.number="sanad.vazn" type="number" step="any" required /></label>
<label>عیار<input v-model.number="sanad.ayar" type="number" step="any" required /></label>
<label>
ورود/خروج
<select v-model.number="sanad.isVoroodOrKhorooj">
<option :value="0">ورود</option>
<option :value="1">خروج</option>
</select>
</label>
</div>
<label>شماره انگ<input v-model="sanad.angNumber" /></label>
<label>آزمایشگاه<input v-model="sanad.nameAz" /></label>
</template>
<template v-else-if="sanad.docType === 'vkh_bank'">
<label>
دریافت/پرداخت
<select v-model.number="sanad.isDaryaftOrPardakht">
<option :value="1">دریافت از مشتری</option>
<option :value="0">پرداخت به مشتری</option>
</select>
</label>
<label>
مبلغ
<MoneyInput v-model="sanad.mablagh" as-number :decimals="0" required />
</label>
<label>نام بانک<input v-model="sanad.nameBank" required /></label>
<label>کد رهگیری<input v-model="sanad.codeRahgiri" /></label>
</template>
<button class="btn" type="submit" :disabled="sanadBusy">
{{ sanadBusy ? 'در حال ثبت…' : 'ثبت سند در ته‌حساب' }}
</button>
</form>
</template>
</template>
</div>
</template>
<style scoped>
.assets {
display: grid;
gap: 0.9rem;
}
.card {
background: var(--surface);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 0.9rem;
}
.toolbar,
.link-bar,
.actions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
justify-content: space-between;
}
.actions.wrap {
justify-content: flex-start;
}
.conn-pick {
display: flex;
gap: 0.5rem;
align-items: center;
}
.manual {
display: flex;
gap: 0.35rem;
}
.candidates {
list-style: none;
margin: 0.5rem 0 0;
padding: 0;
display: grid;
gap: 0.35rem;
width: 100%;
}
.summary h3 {
margin: 0 0 0.75rem;
}
.balance-row {
display: flex;
justify-content: space-between;
align-items: center;
gap: 0.75rem;
padding: 0.45rem 0;
border-bottom: 1px dashed var(--line);
}
.balance-row:last-of-type {
border-bottom: 0;
}
.val {
display: flex;
align-items: baseline;
gap: 0.35rem;
font-weight: 700;
}
.val em {
font-style: normal;
font-weight: 500;
font-size: 0.75rem;
color: var(--muted);
}
.val small {
font-size: 0.7rem;
font-weight: 600;
}
.credit {
color: #15803d;
}
.debt {
color: #b91c1c;
}
.tabs {
display: flex;
gap: 0.4rem;
flex-wrap: wrap;
}
.tab {
border: 1px solid var(--line);
background: var(--bg);
color: var(--text);
border-radius: 10px;
padding: 0.45rem 0.75rem;
cursor: pointer;
}
.tab.on {
background: var(--accent);
color: #fff;
border-color: transparent;
}
.group-title {
display: flex;
align-items: center;
gap: 0.5rem;
margin: 0.75rem 0 0.4rem;
}
.group-title .line {
flex: 1;
border-top: 1px dashed var(--line);
}
.mandeh-card {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: center;
margin-bottom: 0.45rem;
}
.vals {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
align-items: center;
}
.unit,
.st,
.sep,
.muted,
.tiny {
color: var(--muted);
font-size: 0.85rem;
}
.date-row {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
align-items: end;
margin-bottom: 0.75rem;
}
.date-row label,
.sanad-form label {
display: grid;
gap: 0.25rem;
font-size: 0.85rem;
color: var(--muted);
}
.asnad-grid {
display: grid;
gap: 0.65rem;
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));
}
.sc-head {
display: flex;
justify-content: space-between;
gap: 0.5rem;
font-size: 0.85rem;
margin-bottom: 0.4rem;
}
.sc-row {
display: flex;
justify-content: space-between;
gap: 0.5rem;
padding: 0.3rem 0.4rem;
border-radius: 8px;
margin-top: 0.25rem;
font-size: 0.9rem;
}
.sc-row.gold {
background: color-mix(in srgb, #ca8a04 12%, transparent);
}
.sc-row.cash {
background: color-mix(in srgb, #16a34a 12%, transparent);
}
.sanad-form {
display: grid;
gap: 0.65rem;
}
.triple {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 0.5rem;
}
.warn {
margin: 0;
color: #92400e;
background: #fffbeb;
border: 1px solid #fde68a;
border-radius: 8px;
padding: 0.55rem 0.7rem;
font-size: 0.88rem;
}
input,
select {
border: 1px solid var(--line);
border-radius: 10px;
padding: 0.45rem 0.6rem;
background: var(--bg);
color: var(--text);
}
.btn {
border: 0;
border-radius: 10px;
padding: 0.45rem 0.8rem;
background: var(--accent);
color: #fff;
font-weight: 600;
cursor: pointer;
}
.btn.ghost {
background: transparent;
color: var(--text);
border: 1px solid var(--line);
}
.error {
color: #b91c1c;
}
.ok {
color: #15803d;
}
.empty a {
color: var(--accent);
}
@media (max-width: 640px) {
.triple {
grid-template-columns: 1fr;
}
}
</style>