// admin-finance.jsx — Full financial system: dashboard, transactions, expenses, invoices, P&L, tax
const fmtAED = (n) => (n < 0 ? '−' : '') + 'AED ' + Math.abs(Math.round(n)).toLocaleString();
const fmtCompact = (n) => {
const a = Math.abs(n);
if (a >= 1000) return (n / 1000).toFixed(1) + 'K';
return String(n);
};
// ─── Finance Dashboard ──────────────────────────────────────
function FinanceDashboard({ lang }) {
const t = (ar, en) => lang === 'ar' ? ar : en;
const revSum = FINANCE_30D.revenue.reduce((a, b) => a + b, 0);
const expSum = FINANCE_30D.expenses.reduce((a, b) => a + b, 0);
const netProfit = revSum - expSum;
const margin = (netProfit / revSum * 100).toFixed(1);
const revMax = Math.max(...FINANCE_30D.revenue);
const cashOnHand = 184320;
const acctsRecv = INVOICES.filter(i => i.status !== 'paid').reduce((s, i) => s + i.total, 0);
const pendingPay = TRANSACTIONS.filter(t => t.status === 'pending').reduce((s, t) => s + Math.abs(t.amount), 0);
const kpis = [
{ lbl: t('الإيرادات · 30 يوم', 'Revenue · 30d'), val: fmtAED(revSum), delta: '+18.2%', up: true, icon: , tone: '#6B7050' },
{ lbl: t('المصاريف · 30 يوم', 'Expenses · 30d'), val: fmtAED(expSum), delta: '+8.4%', up: false, icon: , tone: '#C97B5E' },
{ lbl: t('صافي الربح', 'Net profit'), val: fmtAED(netProfit), delta: '+24.6%', up: true, icon: , tone: '#2A1F18' },
{ lbl: t('هامش الربح', 'Profit margin'), val: margin + '%', delta: '+2.1pt', up: true, icon: , tone: '#8C4A2B' },
];
// Combined area chart paths
const W = 600, H = 200;
const xs = FINANCE_30D.revenue.map((_, i) => (i / 29) * W);
const maxY = revMax * 1.1;
const yRev = FINANCE_30D.revenue.map(v => H - (v / maxY) * H);
const yExp = FINANCE_30D.expenses.map(v => H - (v / maxY) * H);
const buildArea = (ys) => {
let d = `M0 ${H} `;
ys.forEach((y, i) => d += `L${xs[i]} ${y} `);
d += `L${W} ${H} Z`;
return d;
};
const buildLine = (ys) => ys.map((y, i) => `${i ? 'L' : 'M'}${xs[i]} ${y}`).join(' ');
return (
<>
{t('المالية · 01', 'Finance · 01')}
{t('لوحة الإيرادات', 'Financial Overview')}
{t('آخر ٣٠ يوماً · جميع المبالغ بالدرهم الإماراتي', 'Last 30 days · all amounts in AED')}
{t('آخر ٣٠ يوم', 'Last 30 days')}
{t('هذا الشهر', 'This month')}
{t('هذا الربع', 'This quarter')}
{t('هذه السنة', 'This year')}
{t('تصدير', 'Export')}
{kpis.map((k, i) => (
{k.icon}
{k.lbl}
{k.val}
{k.up ? : } {k.delta} {t('vs الفترة السابقة', 'vs prev')}
))}
{/* Cash position strip */}
{t('السيولة المتاحة', 'Cash on hand')}
{fmtAED(cashOnHand)}
{t('عبر ٣ حسابات بنكية', 'across 3 bank accounts')}
{t('ذمم مدينة', 'Accounts receivable')}
{fmtAED(acctsRecv)}
{INVOICES.filter(i => i.status !== 'paid').length} {t('فاتورة معلّقة', 'unpaid invoices')}
{t('مدفوعات معلّقة', 'Pending payments')}
{fmtAED(pendingPay)}
{t('للمورّدين', 'to suppliers')}
{t('ضريبة القيمة المضافة', 'VAT payable')}
{fmtAED(Math.round(revSum * 0.05))}
{t('Q2 · يستحق ٣٠ يونيو', 'Q2 · due Jun 30')}
{/* Main chart: revenue vs expenses */}
{t('الإيرادات مقابل المصاريف · آخر ٣٠ يوماً', 'Revenue vs Expenses · 30 days')}
{t('إيرادات', 'Revenue')}
{t('مصاريف', 'Expenses')}
{/* gridlines */}
{[0, 0.25, 0.5, 0.75, 1].map((g, i) => (
))}
{/* dots on last point */}
{/* x labels */}
Apr 19
May 4
May 18
{/* Two-up: expense breakdown + payment methods */}
{t('توزيع المصاريف', 'Expense breakdown')}
{EXPENSE_CATEGORIES.map(c => (
{lang === 'ar' ? c.ar : c.en}
{fmtAED(Math.round(expSum * c.pct / 100))}
{c.pct}% {t('من الإجمالي', 'of total')}
))}
{t('طرق الدفع', 'Payment methods')}
{PAYMENT_SPLIT.map(p => (
{p.id === 'cash' ? : p.id === 'bank' ? : }
{lang === 'ar' ? p.ar : p.en}
{p.pct}%
))}
{/* Recent transactions teaser */}
{t('أحدث المعاملات', 'Recent transactions')}
{t('انتقل إلى المعاملات لعرض الكل', 'Go to Transactions to see all')}
{TRANSACTIONS.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 [filter, setFilter] = React.useState('all');
const [q, setQ] = React.useState('');
const filtered = TRANSACTIONS.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: TRANSACTIONS.length },
{ id: 'sale', label: t('مبيعات', 'Sales'), count: TRANSACTIONS.filter(t => t.type === 'sale').length },
{ id: 'expense', label: t('مصاريف', 'Expenses'), count: TRANSACTIONS.filter(t => t.type === 'expense').length },
{ id: 'refund', label: t('استرداد', 'Refunds'), count: TRANSACTIONS.filter(t => t.type === 'refund').length },
];
const typeLabel = (type) => ({
sale: t('بيع', 'Sale'),
expense: t('مصروف', 'Expense'),
refund: t('استرداد', 'Refund'),
})[type];
const methodLabel = (m) => ({
card: t('بطاقة', 'Card'),
apple: 'Apple Pay',
mada: 'Mada',
bank: t('حوالة', 'Bank'),
cash: t('نقد', 'Cash'),
})[m];
return (
<>
{t('المالية · 02', 'Finance · 02')}
{t('المعاملات', 'Transactions')}
{TRANSACTIONS.length} {t('معاملة · يُحدّث آنياً', 'transactions · live ledger')}
{tabs.map(tab => (
setFilter(tab.id)}>
{tab.label} {tab.count}
))}
{t('المعرّف', 'ID')}
{t('التاريخ', 'Date')}
{t('النوع', 'Type')}
{t('الوصف', 'Description')}
{t('طريقة الدفع', 'Method')}
{t('المرجع', 'Ref')}
{t('المبلغ', 'Amount')}
{t('الحالة', 'Status')}
{filtered.map(tx => (
{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')}
))}
{/* footer totals */}
{t('إجمالي المبيعات', 'Sales total')}
{fmtAED(filtered.filter(t => t.type === 'sale').reduce((s, t) => s + t.amount, 0))}
{t('إجمالي المصاريف', 'Expenses total')}
{fmtAED(filtered.filter(t => t.type === 'expense').reduce((s, t) => s + t.amount, 0))}
{t('استردادات', 'Refunds')}
{fmtAED(filtered.filter(t => t.type === 'refund').reduce((s, t) => s + t.amount, 0))}
{t('الصافي', 'Net')}
{fmtAED(filtered.reduce((s, t) => s + t.amount, 0))}
>
);
}
// ─── Expenses ──────────────────────────────────────────────
function FinanceExpenses({ lang }) {
const t = (ar, en) => lang === 'ar' ? ar : en;
const [adding, setAdding] = React.useState(false);
const expenses = TRANSACTIONS.filter(t => t.type === 'expense');
const expSum = expenses.reduce((s, t) => s + Math.abs(t.amount), 0);
return (
<>
{t('المالية · 03', 'Finance · 03')}
{t('المصاريف', 'Expenses')}
{expenses.length} {t('مصروف هذا الشهر · إجمالي ', 'this month · total ')} {fmtAED(expSum)}
{t('مصروف جديد', 'New expense')}
{/* Category cards */}
{EXPENSE_CATEGORIES.map(c => (
{lang === 'ar' ? c.ar : c.en}
{fmtAED(Math.round(expSum * c.pct / 100))}
{c.pct}%
{Math.round(expenses.length * c.pct / 100)} {t('قيد', 'entries')}
))}
{t('التاريخ', 'Date')}
{t('الوصف', 'Description')}
{t('الفئة', 'Category')}
{t('طريقة الدفع', 'Method')}
{t('المرجع', 'Reference')}
{t('المبلغ', 'Amount')}
{t('الحالة', 'Status')}
{expenses.map(e => {
const cat = EXPENSE_CATEGORIES.find(c => c.id === e.cat);
return (
{e.date}
{lang === 'ar' ? e.desc_ar : e.desc_en}
{cat && (
{lang === 'ar' ? cat.ar : cat.en}
)}
{e.method.toUpperCase()}
{e.ref}
{fmtAED(e.amount)}
{e.status === 'completed' ? t('مدفوع', 'Paid') : t('معلّق', 'Pending')}
);
})}
>
);
}
// ─── Invoices ──────────────────────────────────────────────
function FinanceInvoices({ lang }) {
const t = (ar, en) => lang === 'ar' ? ar : en;
const [viewing, setViewing] = React.useState(null);
const totalPaid = INVOICES.filter(i => i.status === 'paid').reduce((s, i) => s + i.total, 0);
const totalUnpaid = INVOICES.filter(i => i.status !== 'paid').reduce((s, i) => s + i.total, 0);
const overdue = INVOICES.filter(i => i.status === 'overdue').length;
const statusLabel = (s) => ({
paid: t('مدفوعة', 'Paid'),
sent: t('مرسلة', 'Sent'),
overdue: t('متأخرة', 'Overdue'),
draft: t('مسودة', 'Draft'),
})[s];
return (
<>
{t('المالية · 04', 'Finance · 04')}
{t('الفواتير', 'Invoices')}
{INVOICES.length} {t('فاتورة', 'invoices')} · {overdue} {t('متأخرة', 'overdue')}
{t('فاتورة جديدة', 'New invoice')}
{t('مدفوعة هذا الشهر', 'Paid this month')}
{fmtAED(totalPaid)}
{INVOICES.filter(i => i.status === 'paid').length} {t('فواتير', 'invoices')}
{t('غير مدفوعة', 'Outstanding')}
{fmtAED(totalUnpaid)}
{INVOICES.filter(i => i.status === 'sent').length} {t('مرسلة', 'sent')} · {overdue} {t('متأخرة', 'overdue')}
{t('متوسط وقت الدفع', 'Avg payment time')}
18 {t('يوم', 'days')}
{t('أسرع بـ ٤ أيام', '4 days faster')}
{t('الفاتورة', 'Invoice')}
{t('العميل', 'Client')}
{t('تاريخ الإصدار', 'Issue date')}
{t('الاستحقاق', 'Due')}
{t('العناصر', 'Items')}
{t('المجموع الفرعي', 'Subtotal')}
{t('ضريبة', 'VAT')}
{t('الإجمالي', 'Total')}
{t('الحالة', 'Status')}
{INVOICES.map(inv => (
setViewing(inv)}>
{inv.id}
{lang === 'ar' ? inv.client : inv.client_en}
{inv.date}
{inv.due}
{inv.items}
{inv.subtotal.toLocaleString()}
{inv.vat.toLocaleString()}
AED {inv.total.toLocaleString()}
{statusLabel(inv.status)}
{ e.stopPropagation(); setViewing(inv); }}>
))}
{viewing && setViewing(null)} lang={lang} />}
>
);
}
function InvoiceViewer({ invoice, onClose, 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.ae
{t('فاتورة', 'INVOICE')}
{invoice.id}
{t('تاريخ:', 'Issued:')} {invoice.date}
{t('استحقاق:', 'Due:')} {invoice.due}
{invoice.status === 'paid' ? t('مدفوعة', 'Paid') : invoice.status === 'overdue' ? t('متأخرة', 'Overdue') : t('مرسلة', 'Sent')}
{t('من', 'From')}
{t('استبرق للعطور ذ.م.م', 'Istabraq Perfumery LLC')}
Dubai · UAE
{t('إلى', 'Bill to')}
{lang === 'ar' ? invoice.client : invoice.client_en}
{t('عميل تجزئة بالجملة', 'Wholesale account')}
{t('الوصف', 'Description')}
{t('الكمية', 'Qty')}
{t('السعر', 'Unit')}
{t('المجموع', 'Amount')}
{[0, 6, 4].map((i, idx) => {
const p = PRODUCTS[i];
const qty = [6, 4, 8][idx];
return (
{lang === 'ar' ? p.name_ar : p.name_en}
{p.concentration} · 50ml · {p.origin}
{qty}
{p.price.toLocaleString()}
AED {(p.price * qty).toLocaleString()}
);
})}
{t('المجموع الفرعي', 'Subtotal')} AED {invoice.subtotal.toLocaleString()}
{t('ضريبة القيمة المضافة (٥٪)', 'VAT (5%)')} AED {invoice.vat.toLocaleString()}
{t('الإجمالي المستحق', 'Total due')} AED {invoice.total.toLocaleString()}
{t('شروط الدفع', 'Payment terms')}
{t('الدفع خلال ٣٠ يوماً من تاريخ الإصدار. تحويل بنكي إلى Emirates NBD · AE12 0260 0010 1234 5678 901.',
'Payment due within 30 days. Bank transfer to Emirates NBD · AE12 0260 0010 1234 5678 901.')}
{t('طباعة', 'Print')}
{t('PDF', 'PDF')}
{invoice.status !== 'paid' && {t('تسجيل الدفع', 'Mark paid')} }
);
}
// ─── P&L ───────────────────────────────────────────────────
function FinancePnL({ lang }) {
const t = (ar, en) => lang === 'ar' ? ar : en;
const max = Math.max(...PNL_MONTHLY.map(m => m.rev));
const totRev = PNL_MONTHLY.reduce((s, m) => s + m.rev, 0);
const totExp = PNL_MONTHLY.reduce((s, m) => s + m.exp, 0);
const totNet = totRev - totExp;
// P&L line items
const breakdown = [
{ label_ar: 'مبيعات تجزئة', label_en: 'Retail sales', amount: 892400, kind: 'rev' },
{ label_ar: 'مبيعات الجملة (B2B)', label_en: 'Wholesale (B2B)', amount: 274200, kind: 'rev' },
{ label_ar: 'العينات', label_en: 'Sample sales', amount: 18400, kind: 'rev' },
{ label_ar: 'أخرى', label_en: 'Other revenue', amount: 6800, kind: 'rev' },
{ label_ar: 'تكلفة البضاعة المباعة', label_en: 'Cost of goods sold', amount: -284600,kind: 'cogs' },
{ label_ar: 'الرواتب', label_en: 'Salaries & wages', amount: -92400, kind: 'opex' },
{ label_ar: 'الإيجار والمرافق', label_en: 'Rent & utilities', amount: -48200, kind: 'opex' },
{ label_ar: 'التسويق والإعلان', label_en: 'Marketing & ads', amount: -38400, kind: 'opex' },
{ label_ar: 'الشحن والخدمات', label_en: 'Shipping & fulfilment', amount: -52800, kind: 'opex' },
{ label_ar: 'متفرقات', label_en: 'Misc & admin', amount: -19600, kind: 'opex' },
];
const totalRev = breakdown.filter(b => b.kind === 'rev').reduce((s, b) => s + b.amount, 0);
const totalCOGS = Math.abs(breakdown.filter(b => b.kind === 'cogs').reduce((s, b) => s + b.amount, 0));
const totalOpex = Math.abs(breakdown.filter(b => b.kind === 'opex').reduce((s, b) => s + b.amount, 0));
const grossProfit = totalRev - totalCOGS;
const netIncome = grossProfit - totalOpex;
return (
<>
{t('المالية · 05', 'Finance · 05')}
{t('الأرباح والخسائر', 'Profit & Loss')}
{t('آخر ٦ أشهر · ديسمبر إلى مايو', 'Last 6 months · Dec → May')}
{t('طباعة', 'Print')}
{t('PDF', 'PDF')}
{/* 6-month chart */}
{t('الإيرادات والمصاريف وصافي الربح', 'Revenue, expenses, net profit')}
{PNL_MONTHLY.map((m, i) => (
{lang === 'ar' ? m.m_ar : m.m_en}
))}
{t('إيرادات', 'Revenue')}
{t('مصاريف', 'Expenses')}
{t('صافي الربح', 'Net profit')}
{t('الإجمالي:', 'Total:')} {fmtAED(totRev)} · {fmtAED(totExp)} · {fmtAED(totNet)}
{/* P&L statement */}
{t('بيان الدخل · مايو ٢٠٢٦', 'Income statement · May 2026')}
{t('الإيرادات', 'Revenue')}
{breakdown.filter(b => b.kind === 'rev').map((b, i) => (
{lang === 'ar' ? b.label_ar : b.label_en}
AED {b.amount.toLocaleString()}
))}
{t('إجمالي الإيرادات', 'Total revenue')}
AED {totalRev.toLocaleString()}
{t('تكلفة البضائع', 'Cost of goods sold')}
{breakdown.filter(b => b.kind === 'cogs').map((b, i) => (
{lang === 'ar' ? b.label_ar : b.label_en}
−AED {Math.abs(b.amount).toLocaleString()}
))}
{t('إجمالي الربح', 'Gross profit')}
AED {grossProfit.toLocaleString()} ({(grossProfit / totalRev * 100).toFixed(1)}%)
{t('المصاريف التشغيلية', 'Operating expenses')}
{breakdown.filter(b => b.kind === 'opex').map((b, i) => (
{lang === 'ar' ? b.label_ar : b.label_en}
−AED {Math.abs(b.amount).toLocaleString()}
))}
{t('إجمالي المصاريف التشغيلية', 'Total operating')}
−AED {totalOpex.toLocaleString()}
{t('صافي الدخل', 'Net income')}
AED {netIncome.toLocaleString()} ({(netIncome / totalRev * 100).toFixed(1)}%)
>
);
}
// ─── Tax / VAT ─────────────────────────────────────────────
function FinanceTax({ lang }) {
const t = (ar, en) => lang === 'ar' ? ar : en;
const Q2_revenue = 671200;
const Q2_vat_output = Q2_revenue * 0.05;
const Q2_purchases = 312400;
const Q2_vat_input = Q2_purchases * 0.05;
const Q2_payable = Q2_vat_output - Q2_vat_input;
const quarters = [
{ id: 'Q1-2026', period: t('يناير - مارس ٢٠٢٦', 'Jan - Mar 2026'), rev: 531400, out: 26570, inp: 13420, paid: true },
{ id: 'Q2-2026', period: t('أبريل - يونيو ٢٠٢٦', 'Apr - Jun 2026'), rev: Q2_revenue, out: Q2_vat_output, inp: Q2_vat_input, paid: false },
{ id: 'Q4-2025', period: t('أكتوبر - ديسمبر ٢٠٢٥', 'Oct - Dec 2025'), rev: 482600, out: 24130, inp: 11890, paid: true },
{ id: 'Q3-2025', period: t('يوليو - سبتمبر ٢٠٢٥', 'Jul - Sep 2025'), rev: 418200, out: 20910, inp: 10240, paid: true },
];
return (
<>
{t('المالية · 06', 'Finance · 06')}
{t('ضريبة القيمة المضافة', 'VAT & Tax')}
{t('TRN: 100123456700003 · معدل الضريبة ٥٪', 'TRN: 100123456700003 · 5% rate')}
{t('تقرير FTA', 'FTA report')}
{/* current quarter — featured card */}
{t('الربع الحالي · Q2 ٢٠٢٦', 'Current quarter · Q2 2026')}
{fmtAED(Q2_payable)}
{t('ضريبة مستحقة', 'VAT payable')}
{t('تاريخ الاستحقاق: ٣٠ يونيو ٢٠٢٦', 'Due: 30 June 2026')}
{t('ضريبة المدخلات (قابل للخصم)', 'Input VAT (deductible)')}: {fmtAED(Q2_vat_input)}
{t('ضريبة المخرجات', 'Output VAT')}: {fmtAED(Q2_vat_output)}
{t('المبيعات الخاضعة للضريبة', 'Taxable sales')}
{fmtAED(Q2_revenue)}
{t('المشتريات الخاضعة للضريبة', 'Taxable purchases')}
{fmtAED(Q2_purchases)}
{t('ضريبة المخرجات', 'Output VAT (5%)')}
+{fmtAED(Q2_vat_output)}
{t('ضريبة المدخلات', 'Input VAT (5%)')}
−{fmtAED(Q2_vat_input)}
{t('صافي الضريبة المستحقة', 'Net VAT due')}
{fmtAED(Q2_payable)}
{t('تقديم الإقرار الضريبي', 'File return now')}
{/* quarters history */}
{t('سجل الإقرارات الضريبية', 'Tax return history')}
{t('الفترة', 'Period')}
{t('المبيعات', 'Sales')}
{t('ضريبة المخرجات', 'Output VAT')}
{t('ضريبة المدخلات', 'Input VAT')}
{t('الصافي', 'Net VAT')}
{t('الحالة', 'Status')}
{quarters.map(q => (
{q.id} {q.period}
AED {q.rev.toLocaleString()}
AED {Math.round(q.out).toLocaleString()}
−AED {Math.round(q.inp).toLocaleString()}
AED {Math.round(q.out - q.inp).toLocaleString()}
{q.paid ? : null} {q.paid ? t('مقدّم ومدفوع', 'Filed & paid') : t('قيد التحضير', 'Due')}
))}
>
);
}
window.FinanceDashboard = FinanceDashboard;
window.FinanceTransactions = FinanceTransactions;
window.FinanceExpenses = FinanceExpenses;
window.FinanceInvoices = FinanceInvoices;
window.FinancePnL = FinancePnL;
window.FinanceTax = FinanceTax;