/* Bonyan ERP — مكوّن رسم خطي مشترك (Line Chart) مع Tooltip وعلامات قيم. يُستخدم في لوحة التحكم والتقارير. props: labels : ["نيسان","أيار",...] أسماء المحور الأفقي series : [{ name, color, values:[...] }] خط واحد أو أكثر (مقارنة) height : ارتفاع المخطط (px) fmt : دالة تنسيق القيمة (افتراضي: فواصل إنجليزية) unit : وحدة تُلحق بالقيمة في الـ tooltip (مثل "مليون د.ع") */ function BnyLineChart({ labels, series, height, isMobile, fmt, unit }) { const [active, setActive] = React.useState(null); labels = labels || []; series = series || []; fmt = fmt || ((v) => Number(v).toLocaleString("en-US")); const single = series.length === 1; const H = height || (isMobile ? 180 : 240); const padT = 26, padB = 30, chartH = H - padT - padB; const n = labels.length; if (!n || !series.length) { return
لا توجد بيانات كافية لعرض الاتجاه
; } const allVals = series.reduce((a, s) => a.concat(s.values), []); const maxV = Math.max(...allVals, 1); const minV = Math.min(...allVals, 0); const span = (maxV - minV) || 1; const xOf = (i) => (n > 1 ? (i / (n - 1)) * 100 : 50); // % const yOf = (v) => padT + (1 - (v - minV) / span) * chartH; // px return (
setActive(null)}> {/* شبكة أفقية خفيفة */} {[0, 0.5, 1].map((t) => ( ))} {/* تعبئة أسفل الخط الأول */} {single && ( xOf(i) + "," + yOf(v)).join(" ") + " 100," + (padT + chartH)} fill={series[0].color || "var(--brand)"} opacity="0.08" stroke="none" /> )} {/* الخطوط */} {series.map((s, si) => ( xOf(i) + "," + yOf(v)).join(" ")} fill="none" stroke={s.color || "var(--brand)"} strokeWidth="2.5" strokeLinejoin="round" strokeLinecap="round" vectorEffect="non-scaling-stroke" /> ))} {/* خط عمودي للنقطة النشطة */} {active != null && ( )} {/* النقاط + علامات القيمة (للخط المفرد) عبر overlay لتفادي تشوّه الدوائر */} {series.map((s, si) => s.values.map((v, i) => { const on = active === i; return ( ); }))} {single && series[0].values.map((v, i) => ( {fmt(v)} ))} {/* أسماء الأشهر */} {labels.map((m, i) => ( {m} ))} {/* أعمدة لمس/تمرير شفافة لالتقاط النقطة النشطة (نقر أو تمرير) */} {labels.map((m, i) => { const gap = n > 1 ? 100 / (n - 1) : 100; return (
setActive(i)} onClick={() => setActive(active === i ? null : i)} style={{ position: "absolute", left: (n > 1 ? (i - 0.5) * gap : 0) + "%", top: 0, height: H, width: gap + "%", cursor: "pointer" }} /> ); })} {/* Tooltip */} {active != null && (
1 ? 6 : 2 }}>{labels[active]}
{series.map((s, si) => (
{series.length > 1 && } {series.length > 1 && {s.name}:} {fmt(s.values[active])}{unit ? " " + unit : ""}
))}
)}
); } window.BnyLineChart = BnyLineChart; /* أداة ترتيب + تصفية مشتركة (زر بجانب البحث → قائمة منسدلة). props: sort, onSort, sortOptions[{key,label}], filterVal, onFilter, filterOptions[{value,label}], filterLabel */ function BnyListFilter({ sort, onSort, sortOptions, filterVal, onFilter, filterOptions, filterLabel }) { const I = window.BnyIcon; const [open, setOpen] = React.useState(false); const defaultSort = sortOptions && sortOptions[0] && sortOptions[0].key; const active = (sort && sort !== defaultSort) || !!filterVal; return (
{open && ( <>
setOpen(false)} style={{ position: "fixed", inset: 0, zIndex: 19 }} />
الترتيب
{(sortOptions || []).map((o) => ( ))}
{filterOptions && filterOptions.length > 0 && ( <>
{filterLabel || "التصفية"}
)} {active && ( )}
)}
); } window.BnyListFilter = BnyListFilter; /* جدول كشف الديون الإجمالي (عملاء/موردون). rows: [{ id, name, total, paid, remaining, extra }] props: totalLabel, nameLabel, extraLabel, onRow(row), onPdf(row), onPrint(row) */ function BnyDebtSummary({ title, nameLabel, totalLabel, extraLabel, rows, onRow, onPdf, onPrint }) { const I = window.BnyIcon; const { Card, Money } = window.BonyanDesignSystem_ad3ca0; const fmt = (n) => Number(n || 0).toLocaleString("en-US"); const th = { fontWeight: 600, padding: "0 10px 12px", whiteSpace: "nowrap" }; const td = { padding: "13px 10px", whiteSpace: "nowrap" }; const pdfBtn = { display: "inline-flex", alignItems: "center", gap: 4, padding: "5px 10px", border: "1px solid var(--border-strong)", background: "var(--surface-card)", color: "var(--brand-strong)", borderRadius: "var(--radius-md)", cursor: "pointer", fontSize: 12, fontWeight: 600, fontFamily: "var(--font-body)" }; const cols = extraLabel ? 7 : 6; return (
{extraLabel && } {rows.map((r) => ( onRow && onRow(r)} onMouseEnter={(e) => (e.currentTarget.style.background = "var(--surface-hover)")} onMouseLeave={(e) => (e.currentTarget.style.background = "transparent")} style={{ borderTop: "1px solid var(--border-subtle)", cursor: onRow ? "pointer" : "default", transition: "background var(--dur-fast,160ms) ease" }}> {extraLabel && } ))} {rows.length === 0 && }
# {nameLabel} {totalLabel} المدفوع المتبقي / الدين{extraLabel}كشف
{r.id} {r.name} {fmt(r.total)} د.ع {fmt(r.paid)} د.ع {r.remaining > 0 ? : مسدّد}{r.extra || "—"} e.stopPropagation()}>
{onPrint && }
لا بيانات
); } window.BnyDebtSummary = BnyDebtSummary; /* ورقة كشف حساب A4 للطباعة/PDF — ألوان ورقية ثابتة. props: shop, title, entityName, entityMeta, cols[{label,align,mono,render}], rows, totals[{label,value,strong}] */ function BnyStatementSheet({ shop, title, entityName, entityMeta, cols, rows, totals }) { shop = shop || {}; return (
{shop.logo ? : }
{shop.name || "بُنيان"}
{shop.phone ?
هاتف: {shop.phone}
: null}
{title}
التاريخ: {window.BnyAr ? window.BnyAr(new Date().toLocaleDateString("en-GB")) : new Date().toLocaleDateString("en-GB")}
{entityName}{entityMeta ? · {entityMeta} : null}
{cols.map((c, i) => )} {rows.map((r, i) => {cols.map((c, j) => )})} {rows.length === 0 && }
{c.label}
{c.render(r)}
لا حركات
{totals.map((t, i) =>
{t.label}{t.value}
)}
{shop.footer ?
{shop.footer}
: null}
); } window.BnyStatementSheet = BnyStatementSheet; /* مخطط أعمدة بسيط ونظيف (مجموعات شهرية × سلاسل) — بديل أبسط للمنحنى. groups: [{ label, values: { : number } }]، series: [{ key, name, color }]. valueLabel(g): نص مختصر يظهر أعلى كل مجموعة (اختياري). */ function BnyBars({ groups, series, isMobile, height, valueLabel }) { const H = height || (isMobile ? 150 : 210); let max = 1; groups.forEach((g) => series.forEach((s) => { max = Math.max(max, Number(g.values[s.key]) || 0); })); return (
{groups.map((g, i) => (
{valueLabel && {valueLabel(g)}}
{series.map((s) => { const v = Number(g.values[s.key]) || 0; const h = v > 0 ? Math.max(3, Math.round(v / max * 100)) : 0; return
0 ? 3 : 0, background: s.color, borderRadius: "5px 5px 0 0", transition: "height .45s cubic-bezier(.4,0,.2,1)" }} />; })}
{g.label}
))}
{series.map((s) => {s.name})}
); } window.BnyBars = BnyBars; /* نافذة تسديد دفعة (عميل أو مورد) — مبلغ + عملة، مع عرض المستحق الحالي */ function BnyPayModal({ title, entityName, dueIqd, dueUsd, onSave, onClose }) { const I = window.BnyIcon; const { Button } = window.BonyanDesignSystem_ad3ca0; const [cur, setCur] = React.useState(dueIqd > 0 || dueUsd <= 0 ? "iqd" : "usd"); const [amount, setAmount] = React.useState(""); const [busy, setBusy] = React.useState(false); const isUsd = cur === "usd"; const due = isUsd ? (dueUsd || 0) : (dueIqd || 0); const val = Number(String(amount).replace(/[^\d.]/g, "")) || 0; const fmt = (n) => Number(n || 0).toLocaleString("en-US"); const save = async () => { if (val <= 0 || busy) return; setBusy(true); try { await onSave(Math.min(val, due || val), cur); } finally { setBusy(false); } }; return (
e.stopPropagation()} className="bny-card bny-card--pad" style={{ width: "100%", maxWidth: 380 }}>

{title}

{entityName}
العملة
المستحق الحالي 0 ? "var(--danger-text)" : "var(--success)" }}>{fmt(due)} {isUsd ? "$" : "د.ع"}
setAmount(e.target.value.replace(/[^\d.]/g, ""))} placeholder="مبلغ الدفعة" aria-label="مبلغ الدفعة" style={{ width: "100%", height: 44, fontSize: 16, fontWeight: 700, textAlign: "center", marginBottom: 8 }} /> {due > 0 && ( )}
); } window.BnyPayModal = BnyPayModal; /* نافذة تأكيد بصلاحية المدير: - المدير/المالك الحالي: تأكيد بسيط (نعم/لا). - الموظف العادي: يطلب اسم مستخدم + كلمة مرور مدير ويتحقق منها فعليًا. */ function BnyManagerConfirm({ title, message, confirmLabel, onConfirm, onClose }) { const I = window.BnyIcon; const { Button, Input } = window.BonyanDesignSystem_ad3ca0; const user = window.BnyCurrentUser || {}; const isManager = user.role === "manager" || user.isOwner || user.owner; const [u, setU] = React.useState(""); const [p, setP] = React.useState(""); const [busy, setBusy] = React.useState(false); const [err, setErr] = React.useState(""); const run = async () => { if (busy) return; setBusy(true); setErr(""); try { // المدير/المالك: بلا بيانات. الموظف: يمرّر بيانات مدير. const creds = isManager ? null : { user: u.trim(), pass: p }; // في الوضع التجريبي فقط نتحقق محليًا؛ في المباشر تتحقق دالة القاعدة (وفيها قفل تخمين). if (!isManager && !window.BnyConfigured) { const ok = await window.BnyVerifyManager(creds.user, creds.pass); if (!ok) { setErr("بيانات المدير غير صحيحة أو لا يملك الصلاحية"); setBusy(false); return; } } await onConfirm(creds); } catch (e) { setErr(String((e && e.message) || e)); setBusy(false); } }; return (
e.stopPropagation()} className="bny-card bny-card--pad" style={{ width: "100%", maxWidth: 400 }}>

{title || "تأكيد العملية"}

{message}

{!isManager && (
هذه العملية تتطلب تخويل مدير:
setU(e.target.value)} placeholder="اسم مستخدم المدير" icon={} /> setP(e.target.value)} type="password" placeholder="كلمة مرور المدير" />
)} {err &&
{err}
}
); } window.BnyManagerConfirm = BnyManagerConfirm;