Suppliers: dark-mode contrast for monthly figures and coloured text; heading drawn in the view (React key warning)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
56305fc599
commit
4248f3d37f
2 changed files with 24 additions and 29 deletions
|
|
@ -1,4 +1,3 @@
|
|||
import { Store } from "lucide-react";
|
||||
import { serverAuth } from "@/lib/pb";
|
||||
import { buildSupplierSpend } from "@/lib/supplier-spend";
|
||||
import { Suppliers2View } from "@/components/Suppliers2View";
|
||||
|
|
@ -48,15 +47,6 @@ export default async function Suppliers2Page() {
|
|||
rows={rows}
|
||||
year={now.getFullYear()}
|
||||
month={now.getMonth() + 1}
|
||||
title={
|
||||
<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]"><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>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { useState, useMemo } from "react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { ChevronRight, Plus, Trash2, X, Loader2, Pencil } from "lucide-react";
|
||||
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";
|
||||
|
|
@ -18,12 +18,11 @@ const monthLabel = (ym: string) => {
|
|||
};
|
||||
|
||||
export function Suppliers2View({
|
||||
rows, year, month, title,
|
||||
rows, year, month,
|
||||
}: {
|
||||
rows: SupplierSpendRow[];
|
||||
year: number;
|
||||
month: number;
|
||||
title: React.ReactNode;
|
||||
}) {
|
||||
// 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");
|
||||
|
|
@ -56,7 +55,13 @@ export function Suppliers2View({
|
|||
return (
|
||||
<>
|
||||
<div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-4 mb-6">
|
||||
{title}
|
||||
<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>
|
||||
|
|
@ -79,7 +84,7 @@ export function Suppliers2View({
|
|||
|
||||
<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] font-medium">{visible.length} supplier{visible.length !== 1 && "s"}</span>
|
||||
<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>
|
||||
)}
|
||||
|
|
@ -144,7 +149,7 @@ export function Suppliers2View({
|
|||
<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] font-semibold shrink-0">{r.code}</span>
|
||||
<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
|
||||
|
|
@ -168,8 +173,8 @@ export function Suppliers2View({
|
|||
</div>
|
||||
</div>
|
||||
<div className="text-right shrink-0 w-32 hidden md:block">
|
||||
<div className="text-xs uppercase tracking-wider text-[#00798C]">Paid</div>
|
||||
<div className={`text-sm font-semibold tabular-nums ${r.paidNow > 0 ? "text-[#00798C]" : "text-gray-300 dark:text-text-soft"}`}>
|
||||
<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>
|
||||
|
|
@ -190,7 +195,7 @@ export function Suppliers2View({
|
|||
<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] tabular-nums">paid {ZAR(r.paidNow)}</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>
|
||||
|
|
@ -205,7 +210,7 @@ export function Suppliers2View({
|
|||
{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]/15" : "text-gray-300 dark:text-text-soft"}`}>
|
||||
<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} />
|
||||
|
|
@ -217,7 +222,7 @@ export function Suppliers2View({
|
|||
<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]" : "text-[#00798C]"}`}>
|
||||
<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>
|
||||
|
|
@ -243,7 +248,7 @@ export function Suppliers2View({
|
|||
<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]">{ZAR2(p.invoiced)} invoiced — {ZAR2(p.invoiced - p.total)} OVER the PO</span>
|
||||
? <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>
|
||||
|
|
@ -272,11 +277,11 @@ function MonthPayLine({ due, paid, past }: { due: number; paid: number; past: bo
|
|||
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] font-medium" title="Paid this month">paid {ZAR(paid)}</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] font-semibold">+{ZAR(diff)} surplus</div>
|
||||
: <div className="text-[#D1495B] font-semibold">{ZAR(-diff)} short</div>
|
||||
? <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>
|
||||
);
|
||||
|
|
@ -298,7 +303,7 @@ function PaymentRow({ p, editMode }: { p: { id: string; paid_on: string; descrip
|
|||
<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] shrink-0">{ZAR2(p.amount)}</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">
|
||||
|
|
@ -374,11 +379,11 @@ function AddPaymentModal({ rows, month, onClose }: { rows: SupplierSpendRow[]; m
|
|||
{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] font-medium">{ZAR2(sup.paidNow)} paid</span>
|
||||
{" · "}<span className="text-[#00798C] dark:text-[#00A6BE] font-medium">{ZAR2(sup.paidNow)} paid</span>
|
||||
{" · "}
|
||||
{sup.owingToDate > 0.005
|
||||
? <span className="text-[#D1495B] font-medium">{ZAR2(sup.owingToDate)} owing to date</span>
|
||||
: <span className="text-[#00798C] font-medium">settled to date</span>}
|
||||
? <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">
|
||||
|
|
|
|||
Loading…
Reference in a new issue