JEI_Import_Documents-App/app/stock-codes/[code]/page.tsx

134 lines
5.3 KiB
TypeScript
Raw Normal View History

import Link from "next/link";
import { notFound } from "next/navigation";
import { getPb, fmtGBP, fmtNum, fmtDate } from "../../lib/pb";
export const dynamic = "force-dynamic";
export default async function StockCodeDetail({ params }: { params: { code: string } }) {
const pb = getPb();
const code = decodeURIComponent(params.code);
let sc: any = null;
let lines: any[] = [];
try {
// Find stock code by its `code` field
const found = await pb.collection("JEI_stock_codes").getList(1, 1, {
filter: `code="${code.replace(/"/g, '\\"')}"`,
});
sc = found.items[0];
if (!sc) return notFound();
// All lines for this stock code, with their invoice (expand)
const lr = await pb.collection("JEI_invoice_lines").getList(1, 500, {
filter: `stock_code="${sc.id}"`,
expand: "invoice",
});
sc = found.items[0];
if (!sc) return notFound();
// All lines for this stock code, with their invoice (expand)
const lr = await pb.collection("JEI_invoice_lines").getList(1, 500, {
filter: `stock_code="${sc.id}"`,
expand: "invoice",
});
lines = lr.items;
} catch (e: any) {
return (
<div className="space-y-4">
<h1 className="text-2xl font-bold text-brand-dark">Stock code {code}</h1>
<div className="bg-red-50 border border-red-200 text-red-800 rounded p-4">
{String(e?.message || e)}
</div>
</div>
);
}
if (!sc) return notFound();
// Stats
const totalQty = lines.reduce((s, l) => s + (l.quantity || 0), 0);
const totalValue = lines.reduce((s, l) => s + (l.net_amount || 0), 0);
const unitPrices = lines.map((l) => l.unit_price).filter((p) => p != null && p > 0);
const avgUnitPrice = unitPrices.length ? unitPrices.reduce((a, b) => a + b, 0) / unitPrices.length : 0;
const minUnitPrice = unitPrices.length ? Math.min(...unitPrices) : 0;
const maxUnitPrice = unitPrices.length ? Math.max(...unitPrices) : 0;
const invoiceIds = new Set(lines.map((l) => l.invoice));
return (
<div className="space-y-6">
<div>
<Link href="/stock-codes" className="text-sm text-slate-500 hover:underline">← All stock codes</Link>
<h1 className="text-2xl font-bold text-brand-dark mt-1">{sc.code}</h1>
<p className="text-slate-700 mt-1">{sc.description_goods}</p>
</div>
<div className="grid grid-cols-2 md:grid-cols-6 gap-3">
<Stat label="Total qty" value={fmtNum(totalQty)} />
<Stat label="Total £" value={fmtGBP(totalValue)} />
<Stat label="Invoices" value={String(invoiceIds.size)} />
<Stat label="Avg £/unit" value={fmtGBP(avgUnitPrice)} />
<Stat label="Min £/unit" value={fmtGBP(minUnitPrice)} />
<Stat label="Max £/unit" value={fmtGBP(maxUnitPrice)} />
</div>
<div className="bg-white rounded-lg shadow">
<div className="px-6 py-3 border-b font-semibold">
Purchase history ({lines.length} line(s) across {invoiceIds.size} invoice(s))
</div>
<table className="w-full text-sm">
<thead className="bg-slate-50 text-left">
<tr>
<th className="px-3 py-2">Date</th>
<th className="px-3 py-2">Invoice</th>
<th className="px-3 py-2 text-right">Qty</th>
<th className="px-3 py-2 text-right">Unit price (£)</th>
<th className="px-3 py-2 text-right">Net (£)</th>
<th className="px-3 py-2">Commodity</th>
<th className="px-3 py-2">Origin</th>
</tr>
</thead>
<tbody>
{lines.length === 0 && (
<tr>
<td colSpan={7} className="px-3 py-6 text-center text-slate-500">
No purchases recorded for this stock code yet.
</td>
</tr>
)}
{lines.map((l) => {
const inv = l.expand?.invoice;
return (
<tr key={l.id} className="border-t">
<td className="px-3 py-2 text-slate-600">
{inv ? fmtDate(inv.invoice_date) : "—"}
</td>
<td className="px-3 py-2">
{inv ? (
<Link href={`/invoices/${inv.id}`} className="text-brand hover:underline">
{inv.invoice_no}
</Link>
) : (
"—"
)}
</td>
<td className="px-3 py-2 text-right tabular-nums">{fmtNum(l.quantity)}</td>
<td className="px-3 py-2 text-right tabular-nums">{fmtGBP(l.unit_price)}</td>
<td className="px-3 py-2 text-right tabular-nums">{fmtGBP(l.net_amount)}</td>
<td className="px-3 py-2 text-slate-600">{l.commodity_code || "—"}</td>
<td className="px-3 py-2 text-slate-600">{l.country_of_origin || "—"}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
);
}
function Stat({ label, value }: { label: string; value: string }) {
return (
<div className="bg-white rounded-lg shadow p-4">
<div className="text-xs text-slate-500">{label}</div>
<div className="text-xl font-semibold mt-1 tabular-nums">{value}</div>
</div>
);
}