/* eslint-disable */ // JBREWS DMS — Vendor Accounts (Accounting). // Master–detail vendor book: searchable list on the left; on the right the // vendor's contact card, credit limit & utilization, open invoices (account // history), and full payment/check history. Pay and edit inline. Backed by // DMS.vendors / vendorHistory / upsertVendor; checks come from Check Writing. const va$ = (n) => "$" + (Number(n) || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); const VA_TYPES = ["Trade", "OEM / Parts", "Floorplan", "Advertising", "Utilities", "Sublet", "Tax / Gov", "Other"]; const VA_TYPE_TONE = { "OEM / Parts": "var(--jb-blue, #1B66A8)", "Floorplan": "var(--jb-navy, #11337A)", "Tax / Gov": "#9A6700", "Sublet": "#0E7A37" }; const VendorAccounts = ({ onClose, go }) => { const D = window.DMS; const [, force] = React.useReducer(n => n + 1, 0); React.useEffect(() => (D && D.subscribe) ? D.subscribe(force) : undefined, []); const act = (r) => Promise.resolve(r).then(x => { window.k2bToast && window.k2bToast(x.msg, x.ok ? "ok" : "warn", 3600); return x; }); // RC33 (mpx): bill payments are SERVER commands (promises) const vendors = (D && D.vendors) ? D.vendors() : []; const [q, setQ] = React.useState(""); const [type, setType] = React.useState("All"); const [selId, setSelId] = React.useState(null); const [modal, setModal] = React.useState(null); // {type:"vendor",vendor} | {type:"write",vendor} const needle = q.trim().toLowerCase(); const filtered = vendors .filter(v => type === "All" || v.type === type) .filter(v => !needle || [v.name, v.contact, v.phone, v.email, v.acctNo, v.type].some(x => String(x || "").toLowerCase().includes(needle))) .sort((a, b) => a.name.localeCompare(b.name)); React.useEffect(() => { if (selId && vendors.some(v => v.id === selId)) return; if (filtered[0]) setSelId(filtered[0].id); }, [vendors.length, filtered.length]); const hist = selId && D && D.vendorHistory ? D.vendorHistory(selId) : null; const totalOpen = vendors.reduce((s, v) => s + (D.vendorBalance ? D.vendorBalance(v.id) : 0), 0); const overLimit = vendors.filter(v => v.creditLimit && D.vendorBalance && D.vendorBalance(v.id) > v.creditLimit); return (
{}} onMax={() => {}} /> {/* KPI strip */}
{[["Vendors", vendors.length, "var(--jb-navy, #11337A)"], ["Open A/P", va$(totalOpen), "var(--jb-blue, #1B66A8)"], ["Over Credit Limit", overLimit.length, overLimit.length ? "var(--jb-red, #C03546)" : "var(--jb-green, #16A34A)"]].map(([lab, val, c]) => (
{lab}
{val}
))}
{/* ── left: searchable vendor list ── */}
setQ(e.target.value)} placeholder="Search name, contact, phone, account #…" style={{ fontSize: 13, padding: "7px 10px", border: "1px solid #C8CCD2", borderRadius: 6, background: "#FFFEF4" }} />
{["All", ...VA_TYPES].map(t => ( ))}
{filtered.map(v => { const bal = D.vendorBalance ? D.vendorBalance(v.id) : 0; const over = v.creditLimit && bal > v.creditLimit; const on = v.id === selId; return (
setSelId(v.id)} style={{ padding: "9px 12px", borderBottom: "1px solid var(--jb-border)", cursor: "pointer", background: on ? "var(--jb-blue-tint, #E5EFFA)" : "transparent", borderLeft: on ? "3px solid var(--jb-blue)" : "3px solid transparent", opacity: v.active === false ? 0.5 : 1 }}>
{v.name} {va$(bal)}
{v.type} {v.terms}{v.phone ? " · " + v.phone : ""} {over && · over limit}
); })} {!filtered.length &&
No vendors match{q ? " '" + q + "'" : ""}.{q ? <> · : null}
}
{/* ── right: vendor detail ── */}
{hist ? setModal({ type: "vendor", vendor: hist.vendor })} onPay={() => setModal({ type: "write", vendor: hist.vendor })} go={go} /> :
Select a vendor — or add your first one.
}
ESC-Close go && go("checkwriting")}>Check Writing go && go("acctoffice")}>Acct Office (A/P) go && go("accounting")}>General Ledger
{modal && modal.type === "vendor" && setModal(null)} onSaved={(id) => id && setSelId(id)} />} {modal && modal.type === "write" && window.WriteCheckModal && ( setModal(null)} /> )}
); }; /* ── detail pane ── */ const VaDetail = ({ hist, act, onEdit, onPay, go }) => { const v = hist.vendor; const [tab, setTab] = React.useState("open"); // open | payments const limitPct = v.creditLimit ? Math.min(100, Math.round((hist.openBal / v.creditLimit) * 100)) : 0; const Card = ({ label, value, tone }) => (
{label}
{value}
); return (
{/* header */}
{v.name} {v.type} {v.active === false && Inactive} {hist.overLimit && ⚠ Over credit limit}
{v.contact && {v.contact} · }{v.phone && {v.phone} · }{v.email && {v.email} · } {v.address && {v.address} · } terms {v.terms} · GL {v.defaultAcct}{v.acctNo ? " · our acct # " + v.acctNo : ""}
{v.notes &&
{v.notes}
}
{/* totals + credit limit */}
Credit Limit
{v.creditLimit ? ( <>
{va$(hist.openBal)} used of {va$(v.creditLimit)}
80 ? "#C8862A" : "var(--jb-green, #16A34A)" }} />
{hist.overLimit ? va$(-hist.creditRemaining) + " over limit" : va$(hist.creditRemaining) + " available"}
) :
No limit set — add one in ✎ Edit.
}
{/* history tabs */}
{[["open", "Account History — Open (" + hist.open.length + ")"], ["payments", "Payment History (" + hist.checks.length + ")"]].map(([id, lab]) => ( ))}
{tab === "open" && hist.open.length > 0 && pay from Check Writing or “🧾 Write Check”}
{tab === "open" ? ( {["Invoice", "Description", "GL Acct", "Due", "Amount", ""].map((h, i) => )} {hist.open.map((p, i) => ( ))} {hist.paid.slice(0, 6).map((p, i) => ( ))} {!hist.open.length && !hist.paid.length && }
{h}
{p.inv} {p.desc || "—"} {p.acct} {p.due || "—"} {va$(p.amount)}
{p.inv} {p.desc || "—"} {p.acct} paid {va$(p.amount)}
No invoices on account.
) : ( {["Check #", "Date", "Memo", "GL Acct", "Amount", "Status"].map((h, i) => )} {hist.checks.concat(hist.voided).sort((a, b) => (b.num || 0) - (a.num || 0)).map((c, i) => ( ))} {!hist.checks.length && !hist.voided.length && }
{h}
{c.num} {c.date} {c.memo || "—"} {c.acct} {va$(c.amount)} {c.status === "Void" ? "VOID" : c.cleared ? "Cleared" : "Outstanding"}
No checks written to this vendor yet.
)}
); }; /* ── add / edit vendor (with credit limit) ── */ // Hoisted — defined inside the modal it would remount its input every // keystroke (the one-character cursor-drop bug). Per project rule. const VaField = ({ f, set, label, k, ph, mono, span }) => ( ); const VaVendorModal = ({ rec, act, onClose, onSaved }) => { const [f, setF] = React.useState(rec ? { ...rec } : { name: "", type: "Trade", terms: "Net 30", defaultAcct: "300", contact: "", phone: "", email: "", address: "", acctNo: "", creditLimit: "", active: true, notes: "" }); const set = (k, v) => setF(s => ({ ...s, [k]: v })); const save = () => { const r = window.DMS.upsertVendor(f); act(r); if (r.ok) { onSaved && onSaved(r.id); onClose(); } }; return ( {rec && { if (window.confirm("Remove " + f.name + "?")) { act(window.DMS.removeVendor(rec.id)); onClose(); } }}>Delete} Cancel Save Vendor }>
); }; Object.assign(window, { VendorAccounts, VaVendorModal });