// admin-finance.jsx — real financial system driven by the API (no mock data) const fmtAED = (n) => (n < 0 ? '−' : '') + 'AED ' + Math.abs(Math.round(n)).toLocaleString(); const EXP_LABELS = { inventory: ['مخزون وعطور خام', 'Inventory & raw materials'], shipping: ['شحن وتوصيل', 'Shipping & logistics'], salaries: ['رواتب', 'Salaries'], rent: ['إيجار وفواتير', 'Rent & utilities'], marketing: ['تسويق', 'Marketing'], misc: ['متفرقات', 'Other'], }; const PAY_LABELS = { card: ['بطاقة ائتمان', 'Credit card'], apple: ['Apple Pay', 'Apple Pay'], mada: ['مدى', 'Mada'], bank: ['حوالة بنكية', 'Bank transfer'], cod: ['عند الاستلام', 'Cash on delivery'], cash: ['نقداً', 'Cash'], }; function FinLoading() { return
; } function FinEmpty({ lang, label }) { return
{lang === 'ar' ? 'لا توجد بيانات بعد' : 'No data yet'}
{label}
; } // ── reusable: add a ledger entry (expense / manual) ── function EntryModal({ lang, onClose, onSaved, defaultType }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [f, setF] = React.useState({ type: defaultType || 'expense', desc_ar: '', amount: '', cat: 'inventory', method: 'bank', date: new Date().toISOString().slice(0, 10) }); const upd = (k, v) => setF({ ...f, [k]: v }); const [busy, setBusy] = React.useState(false); const save = async () => { if (!f.desc_ar || !f.amount) return; setBusy(true); await apiPost('transactions', { ...f, desc_en: f.desc_ar, amount: Number(f.amount) }); onSaved(); }; return (
e.stopPropagation()}>
{t('قيد جديد', 'New entry')}

{t('إضافة معاملة', 'Add transaction')}

upd('amount', e.target.value)} />
upd('desc_ar', e.target.value)} />
{f.type === 'expense' && (
)}
upd('date', e.target.value)} />
); } // ── reusable: create invoice ── function InvoiceModal({ lang, onClose, onSaved }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [f, setF] = React.useState({ client: '', items: 1, subtotal: '', due: new Date(Date.now() + 30 * 864e5).toISOString().slice(0, 10), date: new Date().toISOString().slice(0, 10), status: 'sent' }); const upd = (k, v) => setF({ ...f, [k]: v }); const [busy, setBusy] = React.useState(false); const vat = Math.round((Number(f.subtotal) || 0) * 0.05); const save = async () => { if (!f.client || !f.subtotal) return; setBusy(true); await apiPost('invoices', { ...f, client_en: f.client, subtotal: Number(f.subtotal), vat }); onSaved(); }; return (
e.stopPropagation()}>
{t('فاتورة جديدة', 'New invoice')}

{t('إصدار فاتورة', 'Issue invoice')}

upd('client', e.target.value)} />
upd('items', e.target.value)} />
upd('subtotal', e.target.value)} />
upd('due', e.target.value)} />
{t('الضريبة (٥٪): ', 'VAT (5%): ')}AED {vat.toLocaleString()} · {t('الإجمالي: ', 'Total: ')}AED {((Number(f.subtotal) || 0) + vat).toLocaleString()}
); } function exportCSV(rows, filename) { if (!rows.length) return; const keys = Object.keys(rows[0]); const csv = [keys.join(','), ...rows.map(r => keys.map(k => `"${String(r[k] ?? '').replace(/"/g, '""')}"`).join(','))].join('\n'); const blob = new Blob(['' + csv], { type: 'text/csv;charset=utf-8' }); const a = document.createElement('a'); a.href = URL.createObjectURL(blob); a.download = filename; a.click(); } // ─── Finance Dashboard ────────────────────────────────────── function FinanceDashboard({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [fin, setFin] = React.useState(null); const [txs, setTxs] = React.useState([]); React.useEffect(() => { apiGet('finance').then(setFin); apiGet('transactions').then(d => setTxs(Array.isArray(d) ? d : [])); }, []); if (!fin) return ; const k = fin.kpis, cash = fin.cash; const expSum = fin.kpis.expenses; const kpis = [ { lbl: t('الإيرادات · 30 يوم', 'Revenue · 30d'), val: fmtAED(k.revenue), tone: '#6B7050', icon: }, { lbl: t('المصاريف · 30 يوم', 'Expenses · 30d'), val: fmtAED(k.expenses), tone: '#C97B5E', icon: }, { lbl: t('صافي الربح', 'Net profit'), val: fmtAED(k.net), tone: '#2A1F18', icon: }, { lbl: t('هامش الربح', 'Profit margin'), val: k.margin + '%', tone: '#8C4A2B', icon: }, ]; const W = 600, H = 200; const rev = fin.series.map(s => s.rev), exp = fin.series.map(s => s.exp); const xs = fin.series.map((_, i) => (i / Math.max(1, fin.series.length - 1)) * W); const maxY = Math.max(1, ...rev, ...exp) * 1.1; const yOf = (v) => H - (v / maxY) * H; const buildArea = (arr) => 'M0 ' + H + ' ' + arr.map((v, i) => `L${xs[i]} ${yOf(v)}`).join(' ') + ` L${W} ${H} Z`; const buildLine = (arr) => arr.map((v, i) => `${i ? 'L' : 'M'}${xs[i]} ${yOf(v)}`).join(' '); return ( <>
{t('المالية · 01', 'Finance · 01')}

{t('لوحة الإيرادات', 'Financial Overview')}

{t('آخر ٣٠ يوماً · جميع المبالغ بالدرهم الإماراتي', 'Last 30 days · all amounts in AED')}
{kpis.map((c, i) => (
{c.icon}
{c.lbl}
{c.val}
))}
{t('السيولة (صافي)', 'Cash position')}
{fmtAED(cash.on_hand)}
{t('إيرادات − مصاريف', 'income − expenses')}
{t('ذمم مدينة', 'Receivable')}
{fmtAED(cash.receivable)}
{t('فواتير غير مدفوعة', 'unpaid invoices')}
{t('مدفوعات معلّقة', 'Pending')}
{fmtAED(cash.pending)}
{t('قيود معلّقة', 'pending entries')}
{t('ضريبة محصّلة', 'VAT collected')}
{fmtAED(cash.vat_payable)}
{t('من الطلبات', 'from orders')}

{t('الإيرادات مقابل المصاريف · آخر ٣٠ يوماً', 'Revenue vs Expenses · 30 days')}

{t('إيرادات', 'Revenue')} {t('مصاريف', 'Expenses')}
{[0, 0.25, 0.5, 0.75, 1].map((g, i) => )}

{t('توزيع المصاريف', 'Expense breakdown')}

{expSum === 0 ? : (
{fin.expense_categories.filter(c => c.amount > 0).map(c => (
{lang === 'ar' ? EXP_LABELS[c.id][0] : EXP_LABELS[c.id][1]} {fmtAED(c.amount)}
{c.pct}% {t('من الإجمالي', 'of total')}
))}
)}

{t('طرق الدفع', 'Payment methods')}

{fin.payment_split.length === 0 ? : (
{fin.payment_split.map(p => (
{p.id === 'cash' || p.id === 'cod' ? : p.id === 'bank' ? : }
{PAY_LABELS[p.id] ? (lang === 'ar' ? PAY_LABELS[p.id][0] : PAY_LABELS[p.id][1]) : p.id}
{p.pct}%
))}
)}

{t('أحدث المعاملات', 'Recent transactions')}

{txs.length === 0 ? : (
{txs.slice(0, 6).map(tx => (
{lang === 'ar' ? tx.desc_ar : tx.desc_en}
{tx.date} · {tx.id}
0 ? 'pos' : 'neg')}>{fmtAED(tx.amount)}
))}
)}
); } // ─── Transactions ────────────────────────────────────────── function FinanceTransactions({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [rows, setRows] = React.useState([]); const [filter, setFilter] = React.useState('all'); const [q, setQ] = React.useState(''); const [adding, setAdding] = React.useState(false); const load = () => apiGet('transactions').then(d => setRows(Array.isArray(d) ? d : [])); React.useEffect(() => { load(); }, []); const filtered = rows.filter(tx => { if (filter !== 'all' && tx.type !== filter) return false; if (q && !(tx.desc_ar || '').includes(q) && !(tx.desc_en || '').toLowerCase().includes(q.toLowerCase()) && !tx.id.includes(q)) return false; return true; }); const tabs = [ { id: 'all', label: t('الكل', 'All'), count: rows.length }, { id: 'sale', label: t('مبيعات', 'Sales'), count: rows.filter(r => r.type === 'sale').length }, { id: 'expense', label: t('مصاريف', 'Expenses'), count: rows.filter(r => r.type === 'expense').length }, { id: 'refund', label: t('استرداد', 'Refunds'), count: rows.filter(r => r.type === 'refund').length }, ]; const typeLabel = (x) => ({ sale: t('بيع', 'Sale'), expense: t('مصروف', 'Expense'), refund: t('استرداد', 'Refund') })[x]; const methodLabel = (m) => ({ card: t('بطاقة', 'Card'), apple: 'Apple Pay', mada: 'Mada', bank: t('حوالة', 'Bank'), cash: t('نقد', 'Cash'), cod: t('عند الاستلام', 'COD') })[m] || m; return ( <>
{t('المالية · 02', 'Finance · 02')}

{t('المعاملات', 'Transactions')}

{rows.length} {t('معاملة', 'transactions')}
setQ(e.target.value)} />
{tabs.map(tab => ( ))}
{filtered.length === 0 ? : (
{filtered.map(tx => ( ))}
{t('المعرّف', 'ID')}{t('التاريخ', 'Date')}{t('النوع', 'Type')}{t('الوصف', 'Description')} {t('طريقة الدفع', 'Method')}{t('المرجع', 'Ref')}{t('المبلغ', 'Amount')}{t('الحالة', 'Status')}
{tx.id} {tx.date} {typeLabel(tx.type)} {lang === 'ar' ? tx.desc_ar : tx.desc_en} {methodLabel(tx.method)} {tx.ref} 0 ? 'tx-amount pos' : 'tx-amount neg'}>{fmtAED(tx.amount)} {tx.status === 'completed' ? t('مكتمل', 'Completed') : t('معلّق', 'Pending')}
)}
{t('إجمالي المبيعات', 'Sales total')}
{fmtAED(filtered.filter(r => r.type === 'sale').reduce((s, r) => s + r.amount, 0))}
{t('إجمالي المصاريف', 'Expenses total')}
{fmtAED(filtered.filter(r => r.type === 'expense').reduce((s, r) => s + r.amount, 0))}
{t('استردادات', 'Refunds')}
{fmtAED(filtered.filter(r => r.type === 'refund').reduce((s, r) => s + r.amount, 0))}
{t('الصافي', 'Net')}
{fmtAED(filtered.reduce((s, r) => s + r.amount, 0))}
{adding && setAdding(false)} onSaved={() => { setAdding(false); load(); }} />} ); } // ─── Expenses ────────────────────────────────────────────── function FinanceExpenses({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [rows, setRows] = React.useState([]); const [fin, setFin] = React.useState(null); const [adding, setAdding] = React.useState(false); const load = () => { apiGet('transactions').then(d => setRows((d || []).filter(x => x.type === 'expense'))); apiGet('finance').then(setFin); }; React.useEffect(() => { load(); }, []); const expSum = rows.reduce((s, r) => s + Math.abs(r.amount), 0); return ( <>
{t('المالية · 03', 'Finance · 03')}

{t('المصاريف', 'Expenses')}

{rows.length} {t('مصروف · إجمالي ', 'expenses · total ')}{fmtAED(expSum)}
{fin && expSum > 0 && (
{fin.expense_categories.filter(c => c.amount > 0).map(c => (
{lang === 'ar' ? EXP_LABELS[c.id][0] : EXP_LABELS[c.id][1]}
{fmtAED(c.amount)}
{c.pct}%
))}
)} {rows.length === 0 ? : (
{rows.map(e => ( ))}
{t('التاريخ', 'Date')}{t('الوصف', 'Description')}{t('الفئة', 'Category')}{t('طريقة الدفع', 'Method')}{t('المرجع', 'Ref')}{t('المبلغ', 'Amount')}{t('الحالة', 'Status')}
{e.date} {lang === 'ar' ? e.desc_ar : e.desc_en} {e.cat && EXP_LABELS[e.cat] ? (lang === 'ar' ? EXP_LABELS[e.cat][0] : EXP_LABELS[e.cat][1]) : '—'} {(e.method || '').toUpperCase()} {e.ref} {fmtAED(e.amount)} {e.status === 'completed' ? t('مدفوع', 'Paid') : t('معلّق', 'Pending')}
)} {adding && setAdding(false)} onSaved={() => { setAdding(false); load(); }} />} ); } // ─── Invoices ────────────────────────────────────────────── function FinanceInvoices({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [rows, setRows] = React.useState([]); const [viewing, setViewing] = React.useState(null); const [adding, setAdding] = React.useState(false); const load = () => apiGet('invoices').then(d => setRows(Array.isArray(d) ? d : [])); React.useEffect(() => { load(); }, []); const totalPaid = rows.filter(i => i.status === 'paid').reduce((s, i) => s + i.total, 0); const totalUnpaid = rows.filter(i => i.status !== 'paid').reduce((s, i) => s + i.total, 0); const overdue = rows.filter(i => i.status === 'overdue').length; const statusLabel = (s) => ({ paid: t('مدفوعة', 'Paid'), sent: t('مرسلة', 'Sent'), overdue: t('متأخرة', 'Overdue'), draft: t('مسودة', 'Draft') })[s]; const markPaid = async (code) => { await apiPut('invoices', code, { status: 'paid' }); setViewing(null); load(); }; return ( <>
{t('المالية · 04', 'Finance · 04')}

{t('الفواتير', 'Invoices')}

{rows.length} {t('فاتورة', 'invoices')} · {overdue} {t('متأخرة', 'overdue')}
{t('مدفوعة', 'Paid')}
{fmtAED(totalPaid)}
{rows.filter(i => i.status === 'paid').length} {t('فواتير', 'invoices')}
{t('غير مدفوعة', 'Outstanding')}
{fmtAED(totalUnpaid)}
{rows.filter(i => i.status === 'sent').length} {t('مرسلة', 'sent')} · {overdue} {t('متأخرة', 'overdue')}
{t('عدد الفواتير', 'Total invoices')}
{rows.length}
{t('هذا الشهر', 'this period')}
{rows.length === 0 ? : (
{rows.map(inv => ( setViewing(inv)}> ))}
{t('الفاتورة', 'Invoice')}{t('العميل', 'Client')}{t('الإصدار', 'Issued')}{t('الاستحقاق', 'Due')}{t('العناصر', 'Items')}{t('الإجمالي', 'Total')}{t('الحالة', 'Status')}
{inv.id}
{lang === 'ar' ? inv.client : inv.client_en}
{inv.date} {inv.due} {inv.items} AED {inv.total.toLocaleString()} {statusLabel(inv.status)}
)} {viewing && setViewing(null)} onMarkPaid={markPaid} lang={lang} />} {adding && setAdding(false)} onSaved={() => { setAdding(false); load(); }} />} ); } function InvoiceViewer({ invoice, onClose, onMarkPaid, lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; return (
e.stopPropagation()}>
{t('استبرق', 'ISTABRAQ')}
{t('Indian perfumery · since 2018', 'Indian perfumery · since 2018')}
{t('شارع جميرا، دبي، الإمارات', 'Jumeirah St, Dubai, UAE')}
VAT TRN: 100123456700003
hello@istabraq.store
{t('فاتورة', 'INVOICE')}
{invoice.id}
{t('تاريخ:', 'Issued:')} {invoice.date}
{t('استحقاق:', 'Due:')} {invoice.due}

{t('من', 'From')}
{t('استبرق للعطور ذ.م.م', 'Istabraq Perfumery LLC')}
Dubai · UAE
{t('إلى', 'Bill to')}
{lang === 'ar' ? invoice.client : invoice.client_en}
{t('المجموع الفرعي', 'Subtotal')}AED {invoice.subtotal.toLocaleString()}
{t('عدد العناصر', 'Items')}{invoice.items}
{t('ضريبة القيمة المضافة (٥٪)', 'VAT (5%)')}AED {invoice.vat.toLocaleString()}
{t('الإجمالي المستحق', 'Total due')}AED {invoice.total.toLocaleString()}
{invoice.status !== 'paid' && }
); } // ─── P&L ─────────────────────────────────────────────────── function FinancePnL({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [fin, setFin] = React.useState(null); React.useEffect(() => { apiGet('finance').then(setFin); }, []); if (!fin) return ; const months = fin.pnl_months; const max = Math.max(1, ...months.map(m => m.rev)); const totRev = months.reduce((s, m) => s + m.rev, 0); const totExp = months.reduce((s, m) => s + m.exp, 0); const totNet = totRev - totExp; const monthName = (ym) => { const d = new Date(ym + '-01'); return d.toLocaleString(lang === 'ar' ? 'ar' : 'en', { month: 'short' }); }; return ( <>
{t('المالية · 05', 'Finance · 05')}

{t('الأرباح والخسائر', 'Profit & Loss')}

{t('محسوب من معاملاتك الفعلية', 'Computed from your real ledger')}
{months.length === 0 ? : (

{t('الإيرادات والمصاريف وصافي الربح', 'Revenue, expenses, net profit')}

{months.map((m, i) => (
{monthName(m.ym)}
))}
{t('إيرادات', 'Revenue')} {t('مصاريف', 'Expenses')} {t('صافي', 'Net')}
)}

{t('بيان الدخل', 'Income statement')}

{fin.expense_categories.filter(c => c.amount > 0).map(c => ( ))}
{t('الإيرادات', 'Revenue')}
{t('إجمالي المبيعات', 'Total sales')}AED {Math.round(totRev).toLocaleString()}
{t('المصاريف', 'Expenses')}
{lang === 'ar' ? EXP_LABELS[c.id][0] : EXP_LABELS[c.id][1]}−AED {Math.round(c.amount).toLocaleString()}
{t('إجمالي المصاريف', 'Total expenses')}−AED {Math.round(totExp).toLocaleString()}
{t('صافي الدخل', 'Net income')}AED {Math.round(totNet).toLocaleString()} {totRev > 0 && ({(totNet / totRev * 100).toFixed(1)}%)}
); } // ─── Tax / VAT ───────────────────────────────────────────── function FinanceTax({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [fin, setFin] = React.useState(null); React.useEffect(() => { apiGet('finance').then(setFin); }, []); if (!fin) return ; const outputVat = fin.cash.vat_payable; const inputVat = Math.round(fin.kpis.expenses * 0.05); const payable = outputVat - inputVat; return ( <>
{t('المالية · 06', 'Finance · 06')}

{t('ضريبة القيمة المضافة', 'VAT & Tax')}

{t('TRN: 100123456700003 · معدل ٥٪ · من بياناتك الفعلية', 'TRN: 100123456700003 · 5% · from your real data')}
{t('الفترة الحالية', 'Current period')}
{fmtAED(payable)}
{t('ضريبة مستحقة', 'VAT payable')}
{t('ضريبة المدخلات', 'Input VAT')}: {fmtAED(inputVat)} {t('ضريبة المخرجات', 'Output VAT')}: {fmtAED(outputVat)}
{t('المبيعات الخاضعة', 'Taxable sales')}{fmtAED(fin.kpis.revenue)}
{t('المصاريف الخاضعة', 'Taxable purchases')}{fmtAED(fin.kpis.expenses)}
{t('ضريبة المخرجات', 'Output VAT')}+{fmtAED(outputVat)}
{t('ضريبة المدخلات', 'Input VAT')}−{fmtAED(inputVat)}
{t('صافي الضريبة المستحقة', 'Net VAT due')}{fmtAED(payable)}
); } window.FinanceDashboard = FinanceDashboard; window.FinanceTransactions = FinanceTransactions; window.FinanceExpenses = FinanceExpenses; window.FinanceInvoices = FinanceInvoices; window.FinancePnL = FinancePnL; window.FinanceTax = FinanceTax; window.EntryModal = EntryModal; window.InvoiceModal = InvoiceModal;