93 lines
3.1 KiB
TypeScript
93 lines
3.1 KiB
TypeScript
import { useTranslations, useFormatter } from "next-intl";
|
|
import { Link } from "@/i18n/navigation";
|
|
import { Card } from "@/components/ui/card";
|
|
import type { Invoice } from "@/types";
|
|
|
|
interface Props {
|
|
invoices: Invoice[];
|
|
}
|
|
|
|
const statusColors: Record<string, string> = {
|
|
open: "text-text-secondary bg-surface-3",
|
|
paid: "text-success bg-success/10",
|
|
overdue: "text-error bg-error/10",
|
|
void: "text-text-muted bg-surface-3 line-through",
|
|
};
|
|
|
|
/**
|
|
* Customer's invoice history table. Server component — gets a
|
|
* pre-fetched Invoice[] from /billing/page.tsx. Each row links
|
|
* to /billing/<invoice-number> for the full detail view.
|
|
*
|
|
* Columns: number, period, due date, total, status. Status is
|
|
* displayed with a colored badge so the customer can scan for
|
|
* outstanding ones at a glance.
|
|
*/
|
|
export function CustomerInvoiceList({ invoices }: Props) {
|
|
const t = useTranslations("customerBilling");
|
|
const fmt = useFormatter();
|
|
|
|
if (invoices.length === 0) {
|
|
return (
|
|
<Card>
|
|
<p className="text-sm text-text-muted italic text-center py-8">
|
|
{t("emptyHistory")}
|
|
</p>
|
|
</Card>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Card>
|
|
<table className="w-full text-sm">
|
|
<thead className="text-xs text-text-muted text-left">
|
|
<tr>
|
|
<th className="pb-2">{t("numberCol")}</th>
|
|
<th className="pb-2">{t("periodCol")}</th>
|
|
<th className="pb-2">{t("dueCol")}</th>
|
|
<th className="pb-2 text-right">{t("totalCol")}</th>
|
|
<th className="pb-2 text-right">{t("statusCol")}</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{invoices.map((inv) => (
|
|
<tr
|
|
key={inv.id}
|
|
className="border-t border-border hover:bg-surface-2 transition-colors"
|
|
>
|
|
<td className="py-2">
|
|
<Link
|
|
href={`/billing/${inv.invoiceNumber}`}
|
|
className="font-mono text-xs text-accent hover:underline"
|
|
>
|
|
{inv.invoiceNumber}
|
|
</Link>
|
|
</td>
|
|
<td className="py-2 text-xs text-text-secondary">
|
|
{fmt.dateTime(new Date(inv.periodStart), { dateStyle: "medium" })}
|
|
<span className="text-text-muted mx-1">→</span>
|
|
{fmt.dateTime(new Date(inv.periodEnd), { dateStyle: "medium" })}
|
|
</td>
|
|
<td className="py-2 text-xs text-text-secondary">
|
|
{fmt.dateTime(new Date(inv.dueAt), { dateStyle: "medium" })}
|
|
</td>
|
|
<td className="py-2 text-right font-mono">
|
|
CHF {inv.totalChf.toFixed(2)}
|
|
</td>
|
|
<td className="py-2 text-right">
|
|
<span
|
|
className={`text-[10px] uppercase tracking-wider px-2 py-1 rounded-md font-semibold ${
|
|
statusColors[inv.status] ?? "text-text-muted bg-surface-3"
|
|
}`}
|
|
>
|
|
{t(`status.${inv.status}` as any)}
|
|
</span>
|
|
</td>
|
|
</tr>
|
|
))}
|
|
</tbody>
|
|
</table>
|
|
</Card>
|
|
);
|
|
}
|