mega-plant/components/Suppliers2View.tsx
2026-10-29 21:16:54 +02:00

473 lines
27 KiB
TypeScript

"use client";
import { useState, useMemo } from "react";
import { useRouter } from "next/navigation";
import { Store, ChevronRight, Plus, Trash2, X, Loader2, Pencil } from "lucide-react";
import { Sparkline } from "@/components/Sparkline";
import { MONTH_NAMES } from "@/lib/data";
import { ZAR, ZAR2 } from "@/lib/utils";
import { termsLabel, isAccount, TERMS } from "@/lib/supplier";
import { STATUS_STYLE } from "@/lib/purchase-invoice-status";
import type { SupplierSpendRow } from "@/lib/supplier-spend";
const SPARK = "#30638E";
const monthLabel = (ym: string) => {
const m = /^(\d{4})-(\d{2})$/.exec(ym);
return m ? `${MONTH_NAMES[Number(m[2]) - 1]} ${m[1]}` : "—";
};
export function Suppliers2View({
rows, year, month,
}: {
rows: SupplierSpendRow[];
year: number;
month: number;
}) {
// Cash suppliers are paid on the spot — nothing to chase — so they stay out of the way until asked for.
const [show, setShow] = useState<"account" | "all" | "cash">("account");
const [expanded, setExpanded] = useState<string | null>(null);
const [paying, setPaying] = useState(false);
const [termsFor, setTermsFor] = useState<SupplierSpendRow | null>(null);
// Deleting a payment is hidden until this is switched on, and it resets on reload.
const [editMode, setEditMode] = useState(false);
const cashN = rows.filter((r) => r.isCash).length;
const visible = useMemo(
() => rows.filter((r) => (show === "all" ? true : show === "cash" ? r.isCash : !r.isCash))
.sort((a, b) => b.thisMonth - a.thisMonth || b.ytd - a.ytd),
[rows, show],
);
// Summed over what is VISIBLE, so the headline never disagrees with the list under it.
const spendNow = visible.reduce((s, r) => s + r.thisMonth, 0);
const payableNow = visible.reduce((s, r) => s + r.payableNow, 0);
// Remaining = what is still owed this month; surplus = paid above what fell due. Per supplier,
// so one supplier's overpayment never quietly hides another's shortfall.
const remainingNow = visible.reduce((s, r) => s + r.payableNet, 0);
const surplusNow = visible.reduce((s, r) => s + r.surplus, 0);
const broughtFwd = visible.reduce((s, r) => s + r.broughtForward, 0);
const paidNowTotal = visible.reduce((s, r) => s + r.paidNow, 0);
const payableNext = visible.reduce((s, r) => s + r.payableNext, 0);
const payableLater = visible.reduce((s, r) => s + r.payableLater, 0);
const unsetInvoiced = visible.reduce((s, r) => s + r.termsUnsetInvoiced, 0);
return (
<>
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-4 mb-6">
<div className="flex items-center gap-3">
<div className="w-10 h-10 shrink-0 rounded-xl bg-gradient-to-br from-[#30638E]/15 to-[#00798C]/15 grid place-items-center text-[#30638E] dark:text-[#8DB8E0]"><Store className="w-5 h-5" /></div>
<div>
<h1 className="text-2xl font-semibold text-[#003D5B] dark:text-white">Suppliers</h1>
<p className="text-gray-500 dark:text-gray-400 text-sm">Spend and payables per supplier</p>
</div>
</div>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
<div className="bg-gradient-to-br from-[#30638E] to-[#003D5B] rounded-2xl px-5 py-3 text-white shadow-lg shadow-blue-900/20">
<div className="text-xs text-white/80">Spend in {MONTH_NAMES[month - 1]} {year}</div>
<div className="text-2xl font-semibold tabular-nums">{ZAR(spendNow)}</div>
</div>
<div className="bg-gradient-to-br from-[#EDAE49] to-[#D1495B] rounded-2xl px-5 py-3 text-white shadow-lg">
<div className="text-xs text-white/80">Payable in {MONTH_NAMES[month - 1]}</div>
<div className="text-2xl font-semibold tabular-nums">{ZAR(remainingNow)}</div>
<div className="text-xs text-white/80">
{ZAR(paidNowTotal)} paid this month{broughtFwd > 0 && ` · ${ZAR(broughtFwd)} brought forward`}{surplusNow > 0 && ` · ${ZAR(surplusNow)} surplus`}
</div>
</div>
<div className="bg-gradient-to-br from-[#00798C] to-[#30638E] rounded-2xl px-5 py-3 text-white shadow-lg">
<div className="text-xs text-white/80">Payable in {MONTH_NAMES[month % 12]}{month === 12 ? ` ${year + 1}` : ""}</div>
<div className="text-2xl font-semibold tabular-nums">{ZAR(payableNext)}</div>
<div className="text-xs text-white/80">{ZAR(payableLater)} after that</div>
</div>
</div>
</div>
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3 mb-5">
<div className="flex items-center gap-2 text-xs text-gray-500 dark:text-gray-400 flex-wrap">
<span className="px-2.5 py-1 rounded-md bg-[#30638E]/10 text-[#30638E] dark:text-[#8DB8E0] font-medium">{visible.length} supplier{visible.length !== 1 && "s"}</span>
{show === "account" && cashN > 0 && (
<span className="px-2.5 py-1 rounded-md bg-gray-100 dark:bg-white/5 font-medium">{cashN} cash supplier{cashN !== 1 && "s"} hidden</span>
)}
{unsetInvoiced > 0 && (
<span className="px-2.5 py-1 rounded-md bg-[#EDAE49]/15 text-[#8a6420] dark:text-[#EDAE49] font-medium">
{ZAR(unsetInvoiced)} invoiced with terms not set — not counted as payable
</span>
)}
</div>
<div className="flex items-center gap-2 flex-wrap">
<button onClick={() => setEditMode(!editMode)} aria-pressed={editMode}
className={`min-h-[44px] px-3 rounded-lg text-xs font-medium inline-flex items-center gap-1.5 border transition ${
editMode ? "bg-[#EDAE49] border-[#EDAE49] text-white" : "border-gray-200 dark:border-border text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-white/5"
}`}>
<Pencil className="w-4 h-4" /> {editMode ? "Editing — delete enabled" : "Edit mode"}
</button>
<button onClick={() => setPaying(true)}
className="min-h-[44px] px-4 rounded-lg text-xs font-medium text-white inline-flex items-center gap-2 bg-[#00798C]">
<Plus className="w-4 h-4" /> Add payment
</button>
<div className="flex items-center gap-1 p-0.5 rounded-lg bg-white dark:bg-white/5 border border-gray-200 dark:border-white/5 self-start">
{([
{ id: "account", label: "Accounts", n: rows.length - cashN },
{ id: "all", label: "All", n: rows.length },
{ id: "cash", label: "Cash", n: cashN },
] as const).map((o) => (
<button
key={o.id}
onClick={() => { setShow(o.id); setExpanded(null); }}
aria-pressed={show === o.id}
className={`min-h-[44px] px-3 rounded-md text-xs font-medium transition ${
show === o.id ? "bg-[#003D5B] text-white dark:bg-[#00798C]" : "text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-white/5"
}`}
>
{o.label} <span className="opacity-60">({o.n})</span>
</button>
))}
</div>
</div>
</div>
{termsFor && <TermsModal row={termsFor} onClose={() => setTermsFor(null)} />}
{paying && <AddPaymentModal rows={rows} month={month} onClose={() => setPaying(false)} />}
<div className="flex items-center gap-3 flex-wrap text-xs text-gray-500 dark:text-gray-400 mb-3">
{(["none", "partial", "complete", "over"] as const).map((k) => (
<span key={k} className="inline-flex items-center gap-1.5">
<span className="w-3 h-1.5 rounded" style={{ background: STATUS_STYLE[k].color }} /> {STATUS_STYLE[k].label}
</span>
))}
</div>
{visible.length === 0 ? (
<div className="bg-white dark:bg-surface rounded-2xl border border-gray-100 dark:border-border p-12 text-center text-sm text-gray-500">No suppliers in this view.</div>
) : (
<div className="space-y-2">
{visible.map((r) => {
const isOpen = expanded === r.id;
const stripe = r.status === "n/a" ? "#d1d5db" : STATUS_STYLE[r.status].color;
return (
<div key={r.id} className="relative bg-white dark:bg-surface border border-gray-100 dark:border-border rounded-xl overflow-hidden shadow-sm hover:shadow-md transition">
<span className="absolute left-0 inset-y-0 w-1.5" style={{ background: stripe }} aria-hidden />
<button onClick={() => setExpanded(isOpen ? null : r.id)} aria-expanded={isOpen} className="w-full text-left p-4 pl-5">
<div className="flex items-center gap-3 sm:gap-4 min-h-[44px]">
{r.code && (
<span className="hidden sm:inline font-mono text-xs px-2 py-1 rounded-md bg-[#00798C]/10 text-[#00798C] dark:text-[#00A6BE] font-semibold shrink-0">{r.code}</span>
)}
<div className="flex-1 min-w-0">
<div
className="text-sm font-medium text-[#003D5B] dark:text-white truncate"
onDoubleClick={(e) => { e.stopPropagation(); setTermsFor(r); }}
title="Double-click to set the payment terms"
>{r.name}</div>
<div className="text-xs text-gray-500 dark:text-gray-400">
{r.terms ? termsLabel(r.terms) : <span className="text-[#EDAE49]">Terms not set</span>}
</div>
</div>
<div className="hidden sm:block shrink-0"><Sparkline data={r.months} color={SPARK} /></div>
<div className="text-right shrink-0 w-32 hidden md:block">
<div className="text-xs text-gray-400 dark:text-text-soft uppercase tracking-wider">Spend this month</div>
<div className="text-sm font-semibold tabular-nums text-[#003D5B] dark:text-white">{ZAR2(r.thisMonth)}</div>
</div>
<div className="text-right shrink-0 w-32 hidden md:block">
<div className="text-xs uppercase tracking-wider text-[#EDAE49]">Payable now</div>
<div className={`text-sm font-semibold tabular-nums ${r.payableNet > 0.005 ? "text-[#EDAE49]" : "text-gray-300 dark:text-text-soft"}`}>
{r.payableNet > 0.005 ? ZAR2(r.payableNet) : "—"}
</div>
</div>
<div className="text-right shrink-0 w-32 hidden md:block">
<div className="text-xs uppercase tracking-wider text-[#00798C] dark:text-[#00A6BE]">Paid</div>
<div className={`text-sm font-semibold tabular-nums ${r.paidNow > 0 ? "text-[#00798C] dark:text-[#00A6BE]" : "text-gray-300 dark:text-text-soft"}`}>
{r.paidNow > 0 ? ZAR2(r.paidNow) : "—"}
</div>
</div>
<div className="text-right shrink-0 w-32 hidden md:block">
<div className="text-xs text-gray-400 dark:text-text-soft uppercase tracking-wider">YTD spend</div>
<div className="text-sm font-semibold tabular-nums text-[#003D5B] dark:text-white">{ZAR2(r.ytd)}</div>
</div>
<ChevronRight className={`w-4 h-4 text-gray-400 shrink-0 transition-transform ${isOpen ? "rotate-90" : ""}`} />
</div>
<div className="flex md:hidden items-center justify-between gap-3 pt-2 text-xs">
<Sparkline data={r.months} color={SPARK} width={72} height={28} />
<div className="text-right min-w-0">
<div className="text-xs text-gray-400 uppercase tracking-wider">Spend this month</div>
<div className="text-sm font-semibold tabular-nums text-[#003D5B] dark:text-white">{ZAR2(r.thisMonth)}</div>
</div>
<div className="text-right min-w-0">
<div className="text-xs uppercase tracking-wider text-[#EDAE49]">Payable</div>
<div className={`text-sm font-semibold tabular-nums ${r.payableNet > 0.005 ? "text-[#EDAE49]" : "text-gray-300 dark:text-text-soft"}`}>
{r.payableNet > 0.005 ? ZAR2(r.payableNet) : "—"}
</div>
{r.paidNow > 0 && <div className="text-xs text-[#00798C] dark:text-[#00A6BE] tabular-nums">paid {ZAR(r.paidNow)}</div>}
</div>
<div className="text-right min-w-0">
<div className="text-xs text-gray-400 uppercase tracking-wider">YTD</div>
<div className="text-sm font-semibold tabular-nums text-[#003D5B] dark:text-white">{ZAR2(r.ytd)}</div>
</div>
</div>
</button>
{isOpen && (
<div className="border-t border-gray-100 dark:border-border px-4 py-4 pl-5 bg-gray-50/50 dark:bg-white/[0.04]">
<div className="grid grid-cols-3 md:grid-cols-6 lg:grid-cols-12 gap-2 text-xs">
{r.months.map((v, i) => (
<div key={i} className="text-center">
<div className="text-gray-400 dark:text-text-soft text-xs mb-1">{MONTH_NAMES[i]}</div>
<div className={`py-1.5 rounded tabular-nums ${v ? "bg-[#30638E]/5 text-[#30638E] dark:bg-[#30638E]/25 dark:text-gray-100" : "text-gray-300 dark:text-text-soft"}`}>
{v ? ZAR2(v) : "—"}
</div>
<MonthPayLine due={r.dueByMonth[i]} paid={r.paidByMonth[i]} past={i < month} />
</div>
))}
</div>
{isAccount(r.terms) && (
<div className="mt-4 pt-3 border-t border-gray-100 dark:border-border">
<div className="flex items-baseline justify-between gap-3 mb-2">
<div className="text-xs uppercase tracking-wider text-gray-400">Payments</div>
<div className={`text-xs font-semibold tabular-nums ${r.owingToDate > 0.005 ? "text-[#D1495B] dark:text-[#D94F60]" : "text-[#00798C] dark:text-[#00A6BE]"}`}>
{r.owingToDate > 0.005 ? `${ZAR2(r.owingToDate)} still owing to date` : r.owingToDate < -0.005 ? `${ZAR2(-r.owingToDate)} paid ahead` : "Settled to date"}
</div>
</div>
{r.payments.length === 0 ? (
<div className="text-xs text-gray-400">No payments entered yet.</div>
) : (
<ul className="divide-y divide-gray-100 dark:divide-border">
{r.payments.map((p) => <PaymentRow key={p.id} p={p} editMode={editMode} />)}
</ul>
)}
</div>
)}
<div className="mt-4 pt-3 border-t border-gray-100 dark:border-border">
<div className="text-xs uppercase tracking-wider text-gray-400 mb-2">Purchase orders</div>
{r.pos.length === 0 ? (
<div className="text-xs text-gray-400">No purchase orders — only till slips{r.isCash ? "" : " so far"}.</div>
) : (
<ul className="space-y-1">
{r.pos.map((p) => (
<li key={p.id} className="relative flex items-center gap-3 rounded-lg bg-white dark:bg-surface border border-gray-100 dark:border-border pl-4 pr-3 py-2 overflow-hidden text-xs">
<span className="absolute left-0 inset-y-0 w-1.5" style={{ background: STATUS_STYLE[p.status].color }} title={STATUS_STYLE[p.status].label} />
<span className="font-mono font-semibold text-[#003D5B] dark:text-white">{p.po_no}</span>
<span className="flex-1 min-w-0 truncate text-gray-500 dark:text-gray-400">
{p.invoiceCount === 0 ? "no invoice yet" : p.status === "over"
? <span className="font-semibold text-[#8B5CF6] dark:text-[#A78BFA]">{ZAR2(p.invoiced)} invoiced — {ZAR2(p.invoiced - p.total)} OVER the PO</span>
: `${ZAR2(p.invoiced)} of ${ZAR2(p.total)} invoiced`}
{p.dueMonth && ` · pay ${monthLabel(p.dueMonth)}`}
</span>
<span className="tabular-nums font-semibold shrink-0">{ZAR2(p.total)}</span>
</li>
))}
</ul>
)}
</div>
</div>
)}
</div>
);
})}
</div>
)}
</>
);
}
/** Under a month's spend: what fell due, what was paid (green) and the surplus / shortfall. */
function MonthPayLine({ due, paid, past }: { due: number; paid: number; past: boolean }) {
if (!due && !paid) return null;
const diff = paid - due;
return (
<div className="mt-1 space-y-0.5 text-xs tabular-nums">
{due > 0 && <div className="text-[#EDAE49]" title="Fell due this month">due {ZAR(due)}</div>}
{paid > 0 && <div className="text-[#00798C] dark:text-[#00A6BE] font-medium" title="Paid this month">paid {ZAR(paid)}</div>}
{past && Math.abs(diff) > 0.005 && (
diff > 0
? <div className="text-[#00798C] dark:text-[#00A6BE] font-semibold">+{ZAR(diff)} surplus</div>
: <div className="text-[#D1495B] dark:text-[#D94F60] font-semibold">{ZAR(-diff)} short</div>
)}
</div>
);
}
function PaymentRow({ p, editMode }: { p: { id: string; paid_on: string; description: string; amount: number }; editMode: boolean }) {
const router = useRouter();
const [busy, setBusy] = useState(false);
async function remove() {
if (!confirm(`Remove the ${ZAR2(p.amount)} payment of ${p.paid_on}?`)) return;
setBusy(true);
const res = await fetch(`/api/supplier-payments/${p.id}`, { method: "DELETE" });
const body = await res.json().catch(() => ({ ok: false, error: "Could not remove" }));
setBusy(false);
if (!body.ok) alert(body.error);
else router.refresh();
}
return (
<li className="flex items-center gap-3 py-1 text-xs">
<span className="tabular-nums text-gray-500 dark:text-gray-400 shrink-0">{p.paid_on}</span>
<span className="flex-1 min-w-0 truncate text-gray-600 dark:text-gray-300">{p.description || "—"}</span>
<span className="font-semibold tabular-nums text-[#00798C] dark:text-[#00A6BE] shrink-0">{ZAR2(p.amount)}</span>
{editMode && (
<button onClick={remove} disabled={busy} aria-label="Remove payment"
className="min-h-[44px] min-w-[44px] grid place-items-center rounded-lg text-gray-400 hover:text-[#D1495B] hover:bg-gray-50 dark:hover:bg-white/5 disabled:opacity-50">
{busy ? <Loader2 className="w-4 h-4 animate-spin" /> : <Trash2 className="w-4 h-4" />}
</button>
)}
</li>
);
}
const inputCls =
"min-h-[44px] w-full rounded-lg border border-gray-200 dark:border-border bg-white dark:bg-surface px-3 text-sm text-[#003D5B] dark:text-white";
/** Global "Add payment": pick an ACCOUNT supplier, then date, description and amount. */
function AddPaymentModal({ rows, month, onClose }: { rows: SupplierSpendRow[]; month: number; onClose: () => void }) {
const router = useRouter();
const accounts = useMemo(() => rows.filter((r) => isAccount(r.terms)).sort((a, b) => a.name.localeCompare(b.name)), [rows]);
const [supplierId, setSupplierId] = useState("");
const [paidOn, setPaidOn] = useState(() => new Date().toISOString().slice(0, 10));
const [description, setDescription] = useState("");
const [amount, setAmount] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const sup = accounts.find((a) => a.id === supplierId) || null;
async function submit(e: React.FormEvent) {
e.preventDefault();
setBusy(true);
setError(null);
try {
const res = await fetch("/api/supplier-payments", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ supplier_id: supplierId, paid_on: paidOn, description, amount }),
});
const body = await res.json();
if (!body.ok) { setError(body.error); return; }
router.refresh();
onClose();
} catch {
setError("Could not save — check your connection");
} finally {
setBusy(false);
}
}
return (
<div className="fixed inset-0 z-50 bg-black/40 grid place-items-center p-4 overflow-y-auto" onClick={onClose}>
<form onSubmit={submit} onClick={(e) => e.stopPropagation()} role="dialog" aria-label="Add payment"
className="w-full max-w-md bg-white dark:bg-surface rounded-2xl p-5 shadow-xl space-y-3">
<div className="flex items-center justify-between">
<h2 className="text-base font-semibold text-[#003D5B] dark:text-white">Add payment</h2>
<button type="button" onClick={onClose} aria-label="Close"
className="min-h-[44px] min-w-[44px] grid place-items-center rounded-lg hover:bg-gray-50 dark:hover:bg-white/5"><X className="w-4 h-4" /></button>
</div>
<label className="block text-[12px] text-gray-500 dark:text-gray-400">
Supplier (accounts only)
<select value={supplierId} onChange={(e) => setSupplierId(e.target.value)} required className={inputCls}>
<option value="">Choose a supplier…</option>
{accounts.map((a) => <option key={a.id} value={a.id}>{a.name} · {termsLabel(a.terms)}</option>)}
</select>
</label>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
<label className="block text-[12px] text-gray-500 dark:text-gray-400">
Date paid
<input type="date" value={paidOn} onChange={(e) => setPaidOn(e.target.value)} required className={inputCls} />
</label>
<label className="block text-[12px] text-gray-500 dark:text-gray-400">
Amount
<input value={amount} onChange={(e) => setAmount(e.target.value)} inputMode="decimal" placeholder="0.00" required className={inputCls} />
</label>
</div>
{sup && (
<div className="text-xs text-gray-500 dark:text-gray-400">
{MONTH_NAMES[month - 1]}: <span className="text-[#EDAE49] font-medium">{ZAR2(sup.payableNow)} due</span>
{" · "}<span className="text-[#00798C] dark:text-[#00A6BE] font-medium">{ZAR2(sup.paidNow)} paid</span>
{" · "}
{sup.owingToDate > 0.005
? <span className="text-[#D1495B] dark:text-[#D94F60] font-medium">{ZAR2(sup.owingToDate)} owing to date</span>
: <span className="text-[#00798C] dark:text-[#00A6BE] font-medium">settled to date</span>}
</div>
)}
<label className="block text-[12px] text-gray-500 dark:text-gray-400">
Description
<input value={description} onChange={(e) => setDescription(e.target.value)} placeholder="e.g. EFT — September invoices" className={inputCls} />
</label>
{accounts.length === 0 && <div className="text-xs text-[#EDAE49]">No supplier is on 30 or 60 day terms yet — set terms on the Suppliers page first.</div>}
{error && <div className="text-xs text-[#D1495B] dark:text-[#D94F60]">{error}</div>}
<div className="flex gap-2 justify-end">
<button type="button" onClick={onClose} className="min-h-[44px] px-4 rounded-lg text-xs font-medium text-gray-500 hover:bg-gray-50 dark:hover:bg-white/5">Cancel</button>
<button type="submit" disabled={busy || !supplierId}
className="min-h-[44px] px-4 rounded-lg text-xs font-medium text-white inline-flex items-center gap-2 bg-[#00798C] disabled:opacity-50">
{busy && <Loader2 className="w-4 h-4 animate-spin" />} Save payment
</button>
</div>
</form>
</div>
);
}
/** Double-click a supplier name: set how the supplier is paid. Cash suppliers owe nothing; 30 / 60 are accounts. */
function TermsModal({ row, onClose }: { row: SupplierSpendRow; onClose: () => void }) {
const router = useRouter();
const [terms, setTerms] = useState(row.terms);
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
async function save() {
setBusy(true);
setError(null);
try {
const res = await fetch(`/api/suppliers/${row.id}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ terms }),
});
const body = await res.json();
if (!body.ok) { setError(body.error); return; }
router.refresh();
onClose();
} catch {
setError("Could not save — check your connection");
} finally {
setBusy(false);
}
}
return (
<div className="fixed inset-0 z-50 bg-black/40 grid place-items-center p-4" onClick={onClose}>
<div onClick={(e) => e.stopPropagation()} role="dialog" aria-label="Payment terms"
className="w-full max-w-sm bg-white dark:bg-surface rounded-2xl p-5 shadow-xl space-y-3">
<div className="flex items-start justify-between gap-3">
<div>
<h2 className="text-base font-semibold text-[#003D5B] dark:text-white">Payment terms</h2>
<div className="text-xs text-gray-500 dark:text-gray-400">{row.name}</div>
</div>
<button onClick={onClose} aria-label="Close"
className="min-h-[44px] min-w-[44px] grid place-items-center rounded-lg hover:bg-gray-50 dark:hover:bg-white/5"><X className="w-4 h-4" /></button>
</div>
<div className="grid grid-cols-3 gap-2">
{TERMS.map((t) => (
<button key={t.id} onClick={() => setTerms(t.id)} aria-pressed={terms === t.id}
className={`min-h-[44px] rounded-lg text-xs font-medium border transition ${
terms === t.id ? "bg-[#003D5B] border-[#003D5B] text-white dark:bg-[#00798C] dark:border-[#00798C]" : "border-gray-200 dark:border-border text-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-white/5"
}`}>
{t.label}
</button>
))}
</div>
<p className="text-xs text-gray-500 dark:text-gray-400">
{terms === "cash" ? "Paid on the spot — nothing is ever payable, and payments are not tracked."
: terms ? `Invoices are paid ${Number(terms) / 30} month${Number(terms) > 30 ? "s" : ""} after the invoice month.`
: "Not set yet — invoices from this supplier are not counted as payable."}
</p>
{error && <div className="text-xs text-[#D1495B] dark:text-[#D94F60]">{error}</div>}
<div className="flex gap-2 justify-end">
<button onClick={onClose} className="min-h-[44px] px-4 rounded-lg text-xs font-medium text-gray-500 hover:bg-gray-50 dark:hover:bg-white/5">Cancel</button>
<button onClick={save} disabled={busy || terms === row.terms}
className="min-h-[44px] px-4 rounded-lg text-xs font-medium text-white inline-flex items-center gap-2 bg-[#00798C] disabled:opacity-50">
{busy && <Loader2 className="w-4 h-4 animate-spin" />} Save terms
</button>
</div>
</div>
</div>
);
}