// admin.jsx — admin panel: products CRUD, orders, stats, customers function AdminLayout({ route, setRoute, lang, children }) { const t = (ar, en) => lang === 'ar' ? ar : en; const items = [ { id: 'admin-products', label: t('المنتجات', 'Products'), icon: }, { id: 'admin-orders', label: t('الطلبات', 'Orders'), icon: }, { id: 'admin-stats', label: t('الإحصائيات', 'Analytics'), icon: }, { id: 'admin-customers', label: t('العملاء', 'Customers'), icon: }, { id: 'admin-settings', label: t('الإعدادات', 'Settings'), icon: }, ]; return (
{children}
); } // ── Products ─────────────────────────────────────────────── function AdminProducts({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [products, setProducts] = React.useState([]); const [loading, setLoading] = React.useState(true); const [q, setQ] = React.useState(''); const [familyFilter, setFamilyFilter] = React.useState('all'); const [edit, setEdit] = React.useState(null); // product being edited (modal) const [creating, setCreating] = React.useState(false); const load = React.useCallback(() => { setLoading(true); apiGet('products').then(d => { setProducts(Array.isArray(d) ? d : []); setLoading(false); }); }, []); React.useEffect(() => { load(); }, [load]); const filtered = products.filter(p => { if (familyFilter !== 'all' && p.family !== familyFilter) return false; if (q && !p.name_ar.includes(q) && !p.name_en.toLowerCase().includes(q.toLowerCase())) return false; return true; }); const save = async (p) => { if (creating) await apiPost('products', p); else await apiPut('products', p.id, p); setEdit(null); setCreating(false); load(); }; const del = async (id) => { if (!confirm(t('حذف المنتج؟', 'Delete this product?'))) return; await apiDel('products', id); load(); }; return ( <>
{t('الإدارة · 01', 'Manage · 01')}

{t('المنتجات', 'Products')}

{products.length} {t('منتج', 'products')} · {products.filter(p => p.stock < 10).length} {t('مخزون منخفض', 'low stock')}
setQ(e.target.value)} />
{filtered.map(p => { const fam = SCENT_FAMILIES.find(f => f.id === p.family); const stockClass = p.stock < 8 ? 'crit' : p.stock < 15 ? 'low' : ''; return ( ); })}
{t('المنتج', 'Product')} {t('العائلة', 'Family')} {t('التركيز', 'Concentration')} {t('المخزون', 'Stock')} {t('السعر', 'Price')} {t('التقييم', 'Rating')}
N°{String(p.id).padStart(3, '0')}
{p.image ? :
}
{p.name_ar}
{p.name_en}
{lang === 'ar' ? fam.ar : fam.en} {p.concentration}
{p.stock}
{p.price} AED {p.rating}
{edit && { setEdit(null); setCreating(false); }} lang={lang} creating={creating} />} ); } function ProductEditor({ product, onSave, onClose, lang, creating }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [p, setP] = React.useState(product); const [uploading, setUploading] = React.useState(false); const fileRef = React.useRef(); const upd = (k, v) => setP(prev => ({ ...prev, [k]: v })); const updSize = (i, k, v) => { setP(prev => { const sizes = [...prev.sizes]; sizes[i] = { ...sizes[i], [k]: Number(v) }; return { ...prev, sizes }; }); }; const onFile = async (e) => { const f = e.target.files && e.target.files[0]; if (!f) return; setUploading(true); const r = await apiUpload(f); setUploading(false); if (r && r.url) upd('image', r.url); else alert((r && r.error) || (lang === 'ar' ? 'فشل رفع الصورة' : 'Upload failed')); e.target.value = ''; }; return (
e.stopPropagation()}>
{t('معاينة', 'Preview')}
{p.image ? :
ISTABRAQ
{p.name_en || 'NAME'}
}
{p.image && }
{SCENT_FAMILIES.map(f => (
{creating ? t('منتج جديد', 'New product') : t('تعديل منتج', 'Edit product')}

{p.name_ar || t('بلا اسم', 'Untitled')}

upd('name_ar', e.target.value)} />
upd('name_en', e.target.value)} />
upd('origin', e.target.value)} placeholder="e.g. Mysore" />
upd('year', +e.target.value)} />
upd('top_ar', e.target.value)} />
upd('heart_ar', e.target.value)} />
upd('base_ar', e.target.value)} />
upd('price', +e.target.value)} />
upd('stock', +e.target.value)} />
{t('الأحجام والأسعار', 'Sizes & prices')}
{p.sizes.map((s, i) => (
updSize(i, 'price', e.target.value)} style={{ fontSize: 18, fontFamily: 'var(--f-en-head)', fontStyle: 'italic' }} />
))}
); } // ── Orders ───────────────────────────────────────────────── function AdminOrders({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [orders, setOrders] = React.useState([]); const [statusFilter, setStatusFilter] = React.useState('all'); const [view, setView] = React.useState(null); const [invoice, setInvoice] = React.useState(null); const load = () => apiGet('orders').then(d => setOrders(Array.isArray(d) ? d : [])); React.useEffect(() => { load(); }, []); const filtered = orders.filter(o => statusFilter === 'all' || o.status === statusFilter); const setStatus = (id, status) => { setOrders(prev => prev.map(o => o.id === id ? { ...o, status } : o)); setView(v => v && v.id === id ? { ...v, status } : v); apiPut('orders', id, { status }); }; const del = async (id) => { if (!confirm(t('حذف هذا الطلب نهائيًا؟', 'Delete this order permanently?'))) return; await apiDel('orders', id); setView(null); load(); }; const totalSales = orders.reduce((s, o) => s + (o.total || 0), 0); const tabs = [ { id: 'all', label: t('الكل', 'All'), count: orders.length }, { id: 'pending', label: t('قيد الانتظار', 'Pending'), count: orders.filter(o => o.status === 'pending').length }, { id: 'shipped', label: t('تم الشحن', 'Shipped'), count: orders.filter(o => o.status === 'shipped').length }, { id: 'delivered', label: t('تم التسليم', 'Delivered'), count: orders.filter(o => o.status === 'delivered').length }, { id: 'cancelled', label: t('ملغية', 'Cancelled'), count: orders.filter(o => o.status === 'cancelled').length }, ]; return ( <>
{t('الإدارة · 02', 'Manage · 02')}

{t('الطلبات', 'Orders')}

{orders.length} {t('طلب · إجمالي المبيعات', 'orders · total sales')} AED {totalSales.toLocaleString()}
{tabs.map(tab => ( ))}
{filtered.length === 0 ? (
{t('لا طلبات بعد', 'No orders yet')}
{t('ستظهر طلبات الزبائن هنا تلقائيًّا', 'Customer orders will appear here automatically')}
) : (
{filtered.map(o => ( setView(o)}> ))}
{t('رقم الطلب', 'Order')}{t('العميل', 'Customer')}{t('المدينة', 'City')} {t('التاريخ', 'Date')}{t('العناصر', 'Items')}{t('المجموع', 'Total')} {t('الحالة', 'Status')}
{o.id}
{o.customer}
{o.phone || o.email}
{o.city} {o.date} {o.items} AED {o.total.toLocaleString()} e.stopPropagation()}> e.stopPropagation()}>
)} {view && setView(null)} onStatus={setStatus} onDelete={del} onInvoice={() => setInvoice(view)} />} {invoice && setInvoice(null)} />} ); } const PAY_M = { card: ['بطاقة', 'Card'], apple: ['Apple Pay', 'Apple Pay'], cod: ['الدفع عند الاستلام', 'Cash on delivery'], mada: ['مدى', 'Mada'], bank: ['حوالة', 'Bank'] }; function OrderDetail({ order, lang, onClose, onStatus, onDelete, onInvoice }) { const t = (ar, en) => lang === 'ar' ? ar : en; const items = order.line_items || []; return (
e.stopPropagation()}>
{t('تفاصيل الطلب', 'Order details')}

{order.id}

{t('العميل', 'Customer')}
{order.customer}
{t('التاريخ', 'Date')}
{order.date}
{t('الجوّال', 'Phone')}
{order.phone || '—'}
{t('البريد', 'Email')}
{order.email || '—'}
{t('العنوان', 'Address')}
{[order.address, order.city, order.emirate].filter(Boolean).join('، ')}
{t('طريقة الدفع', 'Payment')}
{PAY_M[order.pay_method] ? (lang === 'ar' ? PAY_M[order.pay_method][0] : PAY_M[order.pay_method][1]) : order.pay_method}
{t('المنتجات المطلوبة', 'Items ordered')}
{items.map((it, i) => ( ))}
{t('المنتج', 'Product')}{t('الحجم', 'Size')}{t('الكمية', 'Qty')}{t('المجموع', 'Total')}
{lang === 'ar' ? it.name_ar : it.name_en} {it.size}ml {it.qty} AED {(it.price * it.qty).toLocaleString()}
{t('المجموع الفرعي', 'Subtotal')}{(order.subtotal || 0).toLocaleString()} AED
{t('الشحن', 'Shipping')}{order.shipping ? order.shipping + ' AED' : t('مجاناً', 'Free')}
{t('الضريبة', 'VAT')}{(order.vat || 0).toLocaleString()} AED
{t('الإجمالي', 'Total')}AED {order.total.toLocaleString()}
); } function OrderInvoice({ order, lang, onClose }) { const t = (ar, en) => lang === 'ar' ? ar : en; const items = order.line_items || []; return (
e.stopPropagation()}>
{t('استبرق', 'ISTABRAQ')}
{t('عطور هندية · دبي', 'Indian perfumery · Dubai')}
VAT TRN: 100123456700003
hello@istabraq.store
{t('فاتورة', 'INVOICE')}
{order.id}
{t('التاريخ:', 'Date:')} {order.date}

{t('من', 'From')}
{t('استبرق للعطور', 'Istabraq Perfumery')}
Dubai · UAE
{t('إلى', 'Bill to')}
{order.customer}
{order.phone}
{order.email}
{[order.address, order.city, order.emirate].filter(Boolean).join('، ')}
{items.map((it, i) => ( ))}
{t('الوصف', 'Description')}{t('الكمية', 'Qty')}{t('السعر', 'Unit')}{t('المجموع', 'Amount')}
{lang === 'ar' ? it.name_ar : it.name_en}
{it.concentration} · {it.size}ml
{it.qty} {it.price.toLocaleString()} AED {(it.price * it.qty).toLocaleString()}
{t('المجموع الفرعي', 'Subtotal')}AED {(order.subtotal || 0).toLocaleString()}
{t('الشحن', 'Shipping')}{order.shipping ? 'AED ' + order.shipping : t('مجاناً', 'Free')}
{t('ضريبة القيمة المضافة (٥٪)', 'VAT (5%)')}AED {(order.vat || 0).toLocaleString()}
{t('الإجمالي', 'Total')}AED {order.total.toLocaleString()}
{t('طريقة الدفع', 'Payment')}
{PAY_M[order.pay_method] ? (lang === 'ar' ? PAY_M[order.pay_method][0] : PAY_M[order.pay_method][1]) : order.pay_method} · {t('شكراً لطلبك من استبرق', 'Thank you for your order')}
); } // ── Stats / Analytics ────────────────────────────────────── function AdminStats({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [stats, setStats] = React.useState(null); const [fin, setFin] = React.useState(null); const [products, setProducts] = React.useState([]); React.useEffect(() => { apiGet('stats').then(setStats); apiGet('finance').then(setFin); apiGet('products').then(d => setProducts(Array.isArray(d) ? d : [])); }, []); if (!stats || !fin) return
; const series = fin.series.slice(-14).map(s => s.rev); const max = Math.max(1, ...series); const peak = Math.max(...series); const peakIdx = series.indexOf(peak); const cards = [ { lbl: t('الإيرادات', 'Revenue'), val: `AED ${(stats.revenue / 1000).toFixed(1)}K` }, { lbl: t('الطلبات', 'Orders'), val: String(stats.orders) }, { lbl: t('العملاء', 'Customers'), val: String(stats.customers) }, { lbl: t('متوسط قيمة الطلب', 'Avg order value'), val: `AED ${stats.aov}` }, ]; const fsplit = (stats.family_split || []).filter(x => x.value > 0); const total = fsplit.reduce((s, x) => s + x.value, 0) || 1; let acc = 0; const r = 70, circ = 2 * Math.PI * r; const lowStock = [...products].sort((a, b) => a.stock - b.stock).slice(0, 6); return ( <>
{t('الإدارة · 03', 'Manage · 03')}

{t('الإحصائيات', 'Analytics')}

{t('من بياناتك الفعلية', 'From your real data')}
{cards.map((c, i) => (
{c.lbl}
{c.val}
))}

{t('الإيرادات اليومية · AED', 'Daily revenue · AED')}

peak: {peak.toLocaleString()} AED
{series.map((v, i) => (
0 ? ' peak' : '')} style={{ height: `${(v / max) * 100}%` }} title={`AED ${v}`} /> ))}
{t('آخر ١٤ يوماً', 'Last 14 days')}

{t('المبيعات حسب العائلة', 'Sales by family')}

{fsplit.length === 0 ? (
{t('تظهر بعد أول عملية بيع', 'appears after the first sale')}
) : (
{fsplit.map((s) => { const fam = SCENT_FAMILIES.find(f => f.id === s.id) || { tone: '#8C4A2B' }; const len = (s.value / total) * circ; const seg = ( ); acc += len + 2; return seg; })}
100%
{t('المبيعات', 'sales')}
{fsplit.map(s => { const fam = SCENT_FAMILIES.find(f => f.id === s.id) || { tone: '#8C4A2B', ar: s.id, en: s.id }; return (
{lang === 'ar' ? fam.ar : fam.en} {Math.round(s.value / total * 100)}%
); })}
)}

{t('المخزون · الأقل توفّراً', 'Inventory · lowest stock')}

{products.length === 0 ? (
{t('أضف منتجات من قسم المنتجات', 'Add products in the Products section')}
) : ( {lowStock.map(p => { const fam = SCENT_FAMILIES.find(f => f.id === p.family); return ( ); })}
{t('المنتج', 'Product')}{t('العائلة', 'Family')}{t('السعر', 'Price')}{t('المخزون', 'Stock')}
{lang === 'ar' ? p.name_ar : p.name_en}
{lang === 'ar' ? p.name_en : p.name_ar}
{fam ? (lang === 'ar' ? fam.ar : fam.en) : '—'} AED {p.price.toLocaleString()}
{p.stock}
)}
); } // ── Customers ────────────────────────────────────────────── function AdminCustomers({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [CUSTOMERS, setCustomers] = React.useState([]); React.useEffect(() => { apiGet('customers').then(d => setCustomers(Array.isArray(d) ? d : [])); }, []); const vip = CUSTOMERS.filter(c => c.tier === 'VIP').length; return ( <>
{t('الإدارة · 04', 'Manage · 04')}

{t('العملاء', 'Customers')}

{CUSTOMERS.length} {t('عميل', 'customers')} · {vip} VIP
{CUSTOMERS.map(c => ( ))}
{t('الاسم', 'Name')} {t('البريد', 'Email')} {t('الطلبات', 'Orders')} {t('الإنفاق', 'Spent')} {t('المستوى', 'Tier')}
{c.name[0]}
{c.name}
{c.email} {c.orders} AED {c.spent.toLocaleString()} {c.tier}
); } function AdminSettings({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [s, setS] = React.useState(null); const [saved, setSaved] = React.useState(false); const [busy, setBusy] = React.useState(false); React.useEffect(() => { apiGet('settings').then(d => setS(d || {})); }, []); const upd = (k, v) => { setS({ ...s, [k]: v }); setSaved(false); }; const save = async () => { setBusy(true); await apiPut('settings', null, s); setBusy(false); setSaved(true); }; if (!s) return
; return ( <>
{t('الإدارة · 05', 'Manage · 05')}

{t('الإعدادات', 'Settings')}

{t('بيانات المتجر، الشحن، الضريبة وبوابة الدفع', 'Store, shipping, tax & payment')}
{saved && {t('تم الحفظ ✓', 'Saved ✓')}}

{t('بيانات المتجر', 'Store info')}

upd('store_name_ar', e.target.value)} />
upd('store_name_en', e.target.value)} />
upd('contact_email', e.target.value)} />
upd('contact_phone', e.target.value)} />
upd('address', e.target.value)} />
upd('trn', e.target.value)} />

{t('الشحن والضريبة', 'Shipping & tax')}

upd('vat_rate', e.target.value)} />
upd('free_ship_over', e.target.value)} />
upd('flat_shipping', e.target.value)} />

{t('بوابة الدفع', 'Payment gateway')}

upd('pay_publishable', e.target.value)} placeholder="pk_..." />
upd('pay_secret', e.target.value)} placeholder="sk_..." />
{t('أدخل مفاتيح حسابك من المزوّد لتفعيل الدفع بالبطاقة عند الشراء.', 'Enter your provider keys to enable card payment at checkout.')}
); } // ── Site appearance / content editor ─────────────────────── function AdminSite({ lang }) { const t = (ar, en) => lang === 'ar' ? ar : en; const [s, setS] = React.useState(null); const [saved, setSaved] = React.useState(false); const [busy, setBusy] = React.useState(false); const [uploading, setUploading] = React.useState(false); const fileRef = React.useRef(); React.useEffect(() => { apiGet('settings').then(d => setS(d || {})); }, []); const upd = (k, v) => { setS(prev => ({ ...prev, [k]: v })); setSaved(false); }; const save = async () => { setBusy(true); await apiPut('settings', null, s); setBusy(false); setSaved(true); }; const onFile = async (e) => { const f = e.target.files && e.target.files[0]; if (!f) return; setUploading(true); const r = await apiUpload(f); setUploading(false); if (r && r.url) upd('hero_image', r.url); else alert((r && r.error) || 'upload failed'); e.target.value = ''; }; if (!s) return
; return ( <>
{t('الإدارة · 06', 'Manage · 06')}

{t('الموقع والمظهر', 'Site & appearance')}

{t('عدّل صورة الواجهة والنصوص واللون — تظهر مباشرة في المتجر', 'Edit hero image, text and color — reflects on the storefront')}
{saved && {t('تم الحفظ ✓', 'Saved ✓')}}

{t('صورة الواجهة (Hero)', 'Hero image')}

{s.hero_image ? : {t('لا صورة (عرض افتراضي)', 'No image (default render)')}}
{s.hero_image && }

{t('نصوص الواجهة', 'Hero text')}

upd('hero_title_ar', e.target.value)} />
upd('hero_title_en', e.target.value)} />
upd('hero_sub_ar', e.target.value)} />
upd('hero_sub_en', e.target.value)} />

{t('اللون والمظهر', 'Color & layout')}

upd('accent', e.target.value)} style={{ width: 46, height: 34, padding: 0, border: '1px solid var(--line)', borderRadius: 8, background: 'none' }} /> upd('accent', e.target.value)} style={{ flex: 1 }} />
{['#B97A5E','#7E8B5E','#C08274','#9A8C72','#C46A4B','#8A8B6C'].map(c => (
); } window.AdminLayout = AdminLayout; window.AdminProducts = AdminProducts; window.AdminOrders = AdminOrders; window.AdminStats = AdminStats; window.AdminCustomers = AdminCustomers; window.AdminSettings = AdminSettings; window.AdminSite = AdminSite;