Files
pieced-portal/src/components/billing/customer-credit-note-list.tsx
admin bff3aad1ca
All checks were successful
Build and Push / build (push) Successful in 1m49s
add error/loading/404 boundaries, responsive tables, Metadata API
2026-05-29 22:32:08 +02:00

104 lines
3.6 KiB
TypeScript

import { useTranslations, useFormatter } from "next-intl";
import { Card } from "@/components/ui/card";
import type { CreditNote } from "@/types";
interface Props {
creditNotes: CreditNote[];
}
const kindColors: Record<string, string> = {
// Voids = the invoice was cancelled; gentle red.
void: "text-error bg-error/10",
// Refunds = money returned; also red but slightly differentiated.
refund: "text-error bg-error/10",
};
/**
* Phase 7 — customer's credit-note history below the invoice list.
*
* Hidden entirely when the org has zero credit notes (most orgs in
* normal operation). When present, each row shows the credit-note
* number, the invoice it relates to, kind (void / refund), amount,
* and a download link to the PDF.
*
* No detail page — clicking the PDF link opens the document inline
* (browser PDF viewer), which IS the credit-note detail view. A
* separate per-credit-note web page would duplicate what's in the
* PDF and add no value.
*/
export function CustomerCreditNoteList({ creditNotes }: Props) {
const t = useTranslations("customerBilling");
const fmt = useFormatter();
if (creditNotes.length === 0) {
return null;
}
return (
<Card>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead className="text-xs text-text-muted text-left">
<tr>
<th className="pb-2 pr-4">{t("creditNoteNumberCol")}</th>
<th className="pb-2 pr-4">{t("creditNoteInvoiceCol")}</th>
<th className="pb-2 pr-4">{t("creditNoteIssuedCol")}</th>
<th className="pb-2 pr-4 text-right">{t("creditNoteAmountCol")}</th>
<th className="pb-2 pr-4 text-right">{t("creditNoteKindCol")}</th>
<th className="pb-2 text-right">{t("creditNotePdfCol")}</th>
</tr>
</thead>
<tbody>
{creditNotes.map((cn) => (
<tr
key={cn.id}
className="border-t border-border align-middle"
>
<td className="py-2 pr-4 font-mono text-xs">
{cn.creditNoteNumber}
</td>
<td className="py-2 pr-4 font-mono text-xs text-text-secondary">
{cn.invoiceNumber}
</td>
<td className="py-2 pr-4 text-text-secondary whitespace-nowrap">
{fmt.dateTime(new Date(cn.issuedAt), { dateStyle: "medium" })}
</td>
<td className="py-2 pr-4 text-right font-mono whitespace-nowrap">
CHF {cn.amountChf.toFixed(2)}
</td>
<td className="py-2 pr-4 text-right">
<span
className={`px-2 py-0.5 rounded text-xs ${
kindColors[cn.kind] ?? ""
}`}
>
{t(`creditNoteKind_${cn.kind}` as any)}
</span>
</td>
<td className="py-2 text-right">
{cn.hasPdf ? (
<a
href={`/api/credit-notes/${encodeURIComponent(
cn.creditNoteNumber
)}/pdf`}
className="text-accent hover:underline text-xs"
target="_blank"
rel="noopener noreferrer"
>
{t("downloadPdf")}
</a>
) : (
<span className="text-text-muted text-xs italic">
{t("creditNoteNoPdf")}
</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</Card>
);
}