@voyantjs/finance-ui 0.30.6 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -0
- package/dist/components/invoice-detail-page.d.ts +113 -0
- package/dist/components/invoice-detail-page.d.ts.map +1 -0
- package/dist/components/invoice-detail-page.js +142 -0
- package/dist/components/invoices-page-skeleton.d.ts +5 -0
- package/dist/components/invoices-page-skeleton.d.ts.map +1 -0
- package/dist/components/invoices-page-skeleton.js +13 -0
- package/dist/components/invoices-page.d.ts +6 -0
- package/dist/components/invoices-page.d.ts.map +1 -0
- package/dist/components/invoices-page.js +109 -0
- package/dist/components/payment-detail-page.d.ts +41 -0
- package/dist/components/payment-detail-page.d.ts.map +1 -0
- package/dist/components/payment-detail-page.js +79 -0
- package/dist/components/payments-page-skeleton.d.ts +5 -0
- package/dist/components/payments-page-skeleton.d.ts.map +1 -0
- package/dist/components/payments-page-skeleton.js +13 -0
- package/dist/components/payments-page.d.ts +20 -0
- package/dist/components/payments-page.d.ts.map +1 -0
- package/dist/components/payments-page.js +151 -0
- package/dist/components/taxes-page.d.ts +11 -0
- package/dist/components/taxes-page.d.ts.map +1 -0
- package/dist/components/taxes-page.js +718 -0
- package/dist/i18n/en.d.ts +308 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +308 -0
- package/dist/i18n/index.d.ts +1 -1
- package/dist/i18n/index.d.ts.map +1 -1
- package/dist/i18n/messages.d.ts +193 -1
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/messages.js +11 -0
- package/dist/i18n/provider.d.ts +616 -0
- package/dist/i18n/provider.d.ts.map +1 -1
- package/dist/i18n/ro.d.ts +308 -0
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +308 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -0
- package/package.json +9 -9
package/README.md
CHANGED
|
@@ -12,6 +12,24 @@ pnpm add @voyantjs/finance-ui @voyantjs/finance-react @voyantjs/ui @tanstack/rea
|
|
|
12
12
|
|
|
13
13
|
All components accept a `className` prop and merge it with `cn()`. Wrap or compose to extend; use the registry copy-paste path (`npx shadcn add @voyant/...`) for components you want to fork outright.
|
|
14
14
|
|
|
15
|
+
## Components
|
|
16
|
+
|
|
17
|
+
- `InvoicesPage`, `InvoiceDetailPage`, and `PaymentsPage` publish
|
|
18
|
+
list/detail-management compositions.
|
|
19
|
+
- `TaxesPage` publishes the finance-owned tax class, tax regime, and tax policy
|
|
20
|
+
profile settings composition. It reads `baseUrl` and `fetcher` from
|
|
21
|
+
`VoyantFinanceProvider`; pass the `api` prop only when an app needs a custom
|
|
22
|
+
transport adapter.
|
|
23
|
+
- `InvoiceDetailHeader`, `InvoiceSummaryCard`, `InvoiceLinksCard`,
|
|
24
|
+
`InvoiceLineItemsCard`, `InvoicePaymentsCard`, `InvoiceCreditNotesCard`, and
|
|
25
|
+
`InvoiceNotesCard` remain exported for consumers that need to compose the
|
|
26
|
+
invoice detail page manually.
|
|
27
|
+
- `PaymentDetailPage` publishes the customer/supplier payment detail
|
|
28
|
+
workspace with summary, related record, and metadata cards.
|
|
29
|
+
- `PaymentDetailHeader`, `PaymentSummaryCard`, `PaymentLinksCard`, and
|
|
30
|
+
`PaymentMetadataCard` remain exported for consumers that need to compose the
|
|
31
|
+
detail page manually.
|
|
32
|
+
|
|
15
33
|
## I18n
|
|
16
34
|
|
|
17
35
|
Components render English by default. To localize them, wrap your UI in
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import type { CreditNoteRecord, FinanceNoteRecord, InvoiceRecord, LineItemRecord, PaymentRecord } from "@voyantjs/finance-react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { useFinanceUiMessagesOrDefault } from "../i18n/index.js";
|
|
4
|
+
export interface InvoiceDetailPageSlots {
|
|
5
|
+
afterHeader?: ReactNode;
|
|
6
|
+
afterSummary?: ReactNode;
|
|
7
|
+
afterLineItems?: ReactNode;
|
|
8
|
+
afterPayments?: ReactNode;
|
|
9
|
+
afterCreditNotes?: ReactNode;
|
|
10
|
+
afterNotes?: ReactNode;
|
|
11
|
+
dialogs?: ReactNode;
|
|
12
|
+
}
|
|
13
|
+
export interface InvoiceDetailPageProps {
|
|
14
|
+
id: string;
|
|
15
|
+
className?: string;
|
|
16
|
+
onBack?: () => void;
|
|
17
|
+
onDeleted?: () => void;
|
|
18
|
+
onBookingOpen?: (bookingId: string, invoice: InvoiceRecord) => void;
|
|
19
|
+
onPersonOpen?: (personId: string, invoice: InvoiceRecord) => void;
|
|
20
|
+
onOrganizationOpen?: (organizationId: string, invoice: InvoiceRecord) => void;
|
|
21
|
+
onLineItemCreate?: (invoice: InvoiceRecord) => void;
|
|
22
|
+
onLineItemEdit?: (lineItem: LineItemRecord, invoice: InvoiceRecord) => void;
|
|
23
|
+
onPaymentCreate?: (invoice: InvoiceRecord) => void;
|
|
24
|
+
onCreditNoteCreate?: (invoice: InvoiceRecord) => void;
|
|
25
|
+
slots?: InvoiceDetailPageSlots;
|
|
26
|
+
}
|
|
27
|
+
export declare function InvoiceDetailPage({ id, className, onBack, onDeleted, onBookingOpen, onPersonOpen, onOrganizationOpen, onLineItemCreate, onLineItemEdit, onPaymentCreate, onCreditNoteCreate, slots, }: InvoiceDetailPageProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export interface InvoiceDetailHeaderProps {
|
|
29
|
+
invoice: InvoiceRecord;
|
|
30
|
+
onBack?: () => void;
|
|
31
|
+
onEdit: () => void;
|
|
32
|
+
onDelete: () => Promise<void>;
|
|
33
|
+
deletePending?: boolean;
|
|
34
|
+
className?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare function InvoiceDetailHeader({ invoice, onBack, onEdit, onDelete, deletePending, className, }: InvoiceDetailHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
export interface InvoiceDetailCardProps {
|
|
38
|
+
invoice: InvoiceRecord;
|
|
39
|
+
className?: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function InvoiceSummaryCard({ invoice, className }: InvoiceDetailCardProps): import("react/jsx-runtime").JSX.Element;
|
|
42
|
+
export interface InvoiceLinksCardProps extends InvoiceDetailCardProps {
|
|
43
|
+
onBookingOpen?: (bookingId: string, invoice: InvoiceRecord) => void;
|
|
44
|
+
onPersonOpen?: (personId: string, invoice: InvoiceRecord) => void;
|
|
45
|
+
onOrganizationOpen?: (organizationId: string, invoice: InvoiceRecord) => void;
|
|
46
|
+
}
|
|
47
|
+
export declare function InvoiceLinksCard({ invoice, className, onBookingOpen, onPersonOpen, onOrganizationOpen, }: InvoiceLinksCardProps): import("react/jsx-runtime").JSX.Element;
|
|
48
|
+
export interface InvoiceLineItemsCardProps extends InvoiceDetailCardProps {
|
|
49
|
+
lineItems: LineItemRecord[];
|
|
50
|
+
pending?: boolean;
|
|
51
|
+
deletePending?: boolean;
|
|
52
|
+
onCreate?: (invoice: InvoiceRecord) => void;
|
|
53
|
+
onEdit?: (lineItem: LineItemRecord, invoice: InvoiceRecord) => void;
|
|
54
|
+
onDelete?: (lineItemId: string) => Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
export declare function InvoiceLineItemsCard({ invoice, lineItems, pending, deletePending, onCreate, onEdit, onDelete, className, }: InvoiceLineItemsCardProps): import("react/jsx-runtime").JSX.Element;
|
|
57
|
+
export interface InvoicePaymentsCardProps extends InvoiceDetailCardProps {
|
|
58
|
+
payments: PaymentRecord[];
|
|
59
|
+
pending?: boolean;
|
|
60
|
+
onCreate?: (invoice: InvoiceRecord) => void;
|
|
61
|
+
}
|
|
62
|
+
export declare function InvoicePaymentsCard({ invoice, payments, pending, onCreate, className, }: InvoicePaymentsCardProps): import("react/jsx-runtime").JSX.Element;
|
|
63
|
+
export interface InvoiceCreditNotesCardProps extends InvoiceDetailCardProps {
|
|
64
|
+
creditNotes: CreditNoteRecord[];
|
|
65
|
+
pending?: boolean;
|
|
66
|
+
onCreate?: (invoice: InvoiceRecord) => void;
|
|
67
|
+
}
|
|
68
|
+
export declare function InvoiceCreditNotesCard({ invoice, creditNotes, pending, onCreate, className, }: InvoiceCreditNotesCardProps): import("react/jsx-runtime").JSX.Element;
|
|
69
|
+
export interface InvoiceNotesCardProps {
|
|
70
|
+
notes: FinanceNoteRecord[];
|
|
71
|
+
noteContent: string;
|
|
72
|
+
pending?: boolean;
|
|
73
|
+
addPending?: boolean;
|
|
74
|
+
className?: string;
|
|
75
|
+
onNoteChange: (value: string) => void;
|
|
76
|
+
onAddNote: () => Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
export declare function InvoiceNotesCard({ notes, noteContent, pending, addPending, className, onNoteChange, onAddNote, }: InvoiceNotesCardProps): import("react/jsx-runtime").JSX.Element;
|
|
79
|
+
export interface MoneyProps {
|
|
80
|
+
cents: number;
|
|
81
|
+
currency: string;
|
|
82
|
+
}
|
|
83
|
+
export declare function Money({ cents, currency }: MoneyProps): import("react/jsx-runtime").JSX.Element;
|
|
84
|
+
export interface DetailRowProps {
|
|
85
|
+
label: string;
|
|
86
|
+
children: ReactNode;
|
|
87
|
+
}
|
|
88
|
+
export declare function DetailRow({ label, children }: DetailRowProps): import("react/jsx-runtime").JSX.Element;
|
|
89
|
+
export interface DetailLinkProps {
|
|
90
|
+
label: string;
|
|
91
|
+
actionLabel: string;
|
|
92
|
+
onClick: () => void;
|
|
93
|
+
disabled?: boolean;
|
|
94
|
+
}
|
|
95
|
+
export declare function DetailLink({ label, actionLabel, onClick, disabled }: DetailLinkProps): string | import("react/jsx-runtime").JSX.Element;
|
|
96
|
+
export interface EmptyRowProps {
|
|
97
|
+
children: ReactNode;
|
|
98
|
+
}
|
|
99
|
+
export declare function EmptyRow({ children }: EmptyRowProps): import("react/jsx-runtime").JSX.Element;
|
|
100
|
+
export declare function LoadingRow(): import("react/jsx-runtime").JSX.Element;
|
|
101
|
+
export declare function InvoiceDetailLoading({ className }: {
|
|
102
|
+
className?: string;
|
|
103
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
104
|
+
export declare function InvoiceDetailState({ className, message, onBack, }: {
|
|
105
|
+
className?: string;
|
|
106
|
+
message: string;
|
|
107
|
+
onBack?: () => void;
|
|
108
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
109
|
+
export declare function formatPaymentMethod(method: string, messages: ReturnType<typeof useFinanceUiMessagesOrDefault>): string;
|
|
110
|
+
export declare const invoiceStatusVariant: Record<string, "default" | "secondary" | "outline" | "destructive">;
|
|
111
|
+
export declare const paymentStatusVariant: Record<string, "default" | "secondary" | "outline" | "destructive">;
|
|
112
|
+
export declare const creditNoteStatusVariant: Record<string, "default" | "secondary" | "outline" | "destructive">;
|
|
113
|
+
//# sourceMappingURL=invoice-detail-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoice-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/invoice-detail-page.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useInvoice, useInvoiceCreditNotes, useInvoiceLineItemMutation, useInvoiceLineItems, useInvoiceMutation, useInvoiceNoteMutation, useInvoiceNotes, useInvoicePayments, } from "@voyantjs/finance-react";
|
|
4
|
+
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, ConfirmActionButton, Textarea, } from "@voyantjs/ui/components";
|
|
5
|
+
import { cn } from "@voyantjs/ui/lib/utils";
|
|
6
|
+
import { ArrowLeft, ExternalLink, Loader2, Pencil, Plus } from "lucide-react";
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
import { useFinanceUiI18nOrDefault, useFinanceUiMessagesOrDefault } from "../i18n/index.js";
|
|
9
|
+
import { InvoiceDialog } from "./invoice-dialog.js";
|
|
10
|
+
export function InvoiceDetailPage({ id, className, onBack, onDeleted, onBookingOpen, onPersonOpen, onOrganizationOpen, onLineItemCreate, onLineItemEdit, onPaymentCreate, onCreditNoteCreate, slots, }) {
|
|
11
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
12
|
+
const [editOpen, setEditOpen] = useState(false);
|
|
13
|
+
const [noteContent, setNoteContent] = useState("");
|
|
14
|
+
const invoiceQuery = useInvoice(id);
|
|
15
|
+
const invoice = invoiceQuery.data?.data;
|
|
16
|
+
const lineItemsQuery = useInvoiceLineItems(id, { enabled: Boolean(invoice) });
|
|
17
|
+
const paymentsQuery = useInvoicePayments(id, { enabled: Boolean(invoice) });
|
|
18
|
+
const creditNotesQuery = useInvoiceCreditNotes(id, { enabled: Boolean(invoice) });
|
|
19
|
+
const notesQuery = useInvoiceNotes(id, { enabled: Boolean(invoice) });
|
|
20
|
+
const { remove: removeInvoice } = useInvoiceMutation();
|
|
21
|
+
const { remove: removeLineItem } = useInvoiceLineItemMutation(id);
|
|
22
|
+
const addNote = useInvoiceNoteMutation(id);
|
|
23
|
+
if (invoiceQuery.isPending) {
|
|
24
|
+
return _jsx(InvoiceDetailLoading, { className: className });
|
|
25
|
+
}
|
|
26
|
+
if (invoiceQuery.isError || !invoice) {
|
|
27
|
+
return (_jsx(InvoiceDetailState, { className: className, message: invoiceQuery.isError
|
|
28
|
+
? invoiceQuery.error instanceof Error
|
|
29
|
+
? invoiceQuery.error.message
|
|
30
|
+
: messages.invoiceDetailPage.states.loadFailed
|
|
31
|
+
: messages.invoiceDetailPage.states.notFound, onBack: onBack }));
|
|
32
|
+
}
|
|
33
|
+
const lineItems = lineItemsQuery.data?.data ?? [];
|
|
34
|
+
const payments = paymentsQuery.data?.data ?? [];
|
|
35
|
+
const creditNotes = creditNotesQuery.data?.data ?? [];
|
|
36
|
+
const notes = notesQuery.data?.data ?? [];
|
|
37
|
+
return (_jsxs("div", { "data-slot": "invoice-detail-page", className: cn("flex flex-col gap-6 p-6", className), children: [_jsx(InvoiceDetailHeader, { invoice: invoice, onBack: onBack, onEdit: () => setEditOpen(true), deletePending: removeInvoice.isPending, onDelete: async () => {
|
|
38
|
+
await removeInvoice.mutateAsync(id);
|
|
39
|
+
onDeleted?.();
|
|
40
|
+
onBack?.();
|
|
41
|
+
} }), slots?.afterHeader, _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsx(InvoiceSummaryCard, { invoice: invoice }), _jsx(InvoiceLinksCard, { invoice: invoice, onBookingOpen: onBookingOpen, onPersonOpen: onPersonOpen, onOrganizationOpen: onOrganizationOpen })] }), slots?.afterSummary, _jsx(InvoiceLineItemsCard, { invoice: invoice, lineItems: lineItems, pending: lineItemsQuery.isPending, deletePending: removeLineItem.isPending, onCreate: onLineItemCreate, onEdit: onLineItemEdit, onDelete: async (lineItemId) => {
|
|
42
|
+
await removeLineItem.mutateAsync(lineItemId);
|
|
43
|
+
} }), slots?.afterLineItems, _jsx(InvoicePaymentsCard, { invoice: invoice, payments: payments, pending: paymentsQuery.isPending, onCreate: onPaymentCreate }), slots?.afterPayments, _jsx(InvoiceCreditNotesCard, { invoice: invoice, creditNotes: creditNotes, pending: creditNotesQuery.isPending, onCreate: onCreditNoteCreate }), slots?.afterCreditNotes, _jsx(InvoiceNotesCard, { notes: notes, noteContent: noteContent, pending: notesQuery.isPending, addPending: addNote.isPending, onNoteChange: setNoteContent, onAddNote: async () => {
|
|
44
|
+
const content = noteContent.trim();
|
|
45
|
+
if (!content)
|
|
46
|
+
return;
|
|
47
|
+
await addNote.mutateAsync({ content });
|
|
48
|
+
setNoteContent("");
|
|
49
|
+
} }), slots?.afterNotes, _jsx(InvoiceDialog, { open: editOpen, onOpenChange: setEditOpen, invoice: invoice }), slots?.dialogs] }));
|
|
50
|
+
}
|
|
51
|
+
export function InvoiceDetailHeader({ invoice, onBack, onEdit, onDelete, deletePending, className, }) {
|
|
52
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
53
|
+
const detail = messages.invoiceDetailPage;
|
|
54
|
+
const canDelete = invoice.status === "draft";
|
|
55
|
+
return (_jsxs("div", { "data-slot": "invoice-detail-header", className: cn("flex flex-col gap-4 md:flex-row md:items-start", className), children: [onBack ? (_jsxs(Button, { type: "button", variant: "ghost", size: "icon", onClick: onBack, children: [_jsx(ArrowLeft, { className: "size-4", "aria-hidden": "true" }), _jsx("span", { className: "sr-only", children: detail.actions.back })] })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("h1", { className: "truncate text-2xl font-bold tracking-tight", children: invoice.invoiceNumber }), _jsx(Badge, { variant: "outline", children: invoice.invoiceType ?? "invoice" })] }), _jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: invoice.bookingId })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2 md:justify-end", children: [_jsx(Badge, { variant: invoiceStatusVariant[invoice.status] ?? "secondary", children: messages.common.invoiceStatusLabels[invoice.status] }), _jsxs(Button, { type: "button", variant: "outline", onClick: onEdit, children: [_jsx(Pencil, { className: "size-4", "aria-hidden": "true" }), detail.actions.edit] }), _jsx(ConfirmActionButton, { buttonLabel: detail.actions.delete, confirmLabel: detail.actions.delete, cancelLabel: messages.common.cancel, title: detail.actions.deleteTitle, description: canDelete ? detail.actions.deleteDescription : detail.actions.deleteOnlyDraft, variant: "destructive", confirmVariant: "destructive", disabled: !canDelete || deletePending, onConfirm: onDelete })] })] }));
|
|
56
|
+
}
|
|
57
|
+
export function InvoiceSummaryCard({ invoice, className }) {
|
|
58
|
+
const { formatCurrency } = useFinanceUiI18nOrDefault();
|
|
59
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
60
|
+
const detail = messages.invoiceDetailPage;
|
|
61
|
+
return (_jsxs(Card, { "data-slot": "invoice-summary-card", className: className, children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: detail.titles.summary }) }), _jsx(CardContent, { children: _jsxs("dl", { className: "grid gap-3 text-sm", children: [_jsx(DetailRow, { label: detail.fields.currency, children: invoice.currency }), _jsx(DetailRow, { label: detail.fields.subtotal, children: _jsx(Money, { cents: invoice.subtotalCents, currency: invoice.currency }) }), _jsx(DetailRow, { label: detail.fields.tax, children: _jsx(Money, { cents: invoice.taxCents, currency: invoice.currency }) }), _jsx(DetailRow, { label: detail.fields.total, children: _jsx("span", { className: "font-semibold", children: formatCurrency(invoice.totalCents / 100, invoice.currency) }) }), _jsx(DetailRow, { label: detail.fields.paid, children: _jsx(Money, { cents: invoice.paidCents, currency: invoice.currency }) }), _jsx(DetailRow, { label: detail.fields.balanceDue, children: _jsx("span", { className: "font-semibold", children: formatCurrency(invoice.balanceDueCents / 100, invoice.currency) }) }), _jsx(DetailRow, { label: detail.fields.status, children: _jsx(Badge, { variant: invoiceStatusVariant[invoice.status] ?? "secondary", children: messages.common.invoiceStatusLabels[invoice.status] }) })] }) })] }));
|
|
62
|
+
}
|
|
63
|
+
export function InvoiceLinksCard({ invoice, className, onBookingOpen, onPersonOpen, onOrganizationOpen, }) {
|
|
64
|
+
const { formatDate, formatDateTime } = useFinanceUiI18nOrDefault();
|
|
65
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
66
|
+
const detail = messages.invoiceDetailPage;
|
|
67
|
+
return (_jsxs(Card, { "data-slot": "invoice-links-card", className: className, children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: detail.titles.links }) }), _jsx(CardContent, { children: _jsxs("dl", { className: "grid gap-3 text-sm", children: [_jsx(DetailRow, { label: detail.fields.issueDate, children: formatDate(invoice.issueDate) }), _jsx(DetailRow, { label: detail.fields.dueDate, children: formatDate(invoice.dueDate) }), _jsx(DetailRow, { label: detail.fields.booking, children: _jsx(DetailLink, { label: invoice.bookingId, actionLabel: detail.actions.viewBooking, onClick: () => onBookingOpen?.(invoice.bookingId, invoice), disabled: !onBookingOpen }) }), invoice.personId ? (_jsx(DetailRow, { label: detail.fields.person, children: _jsx(DetailLink, { label: invoice.personId, actionLabel: detail.actions.viewPerson, onClick: () => onPersonOpen?.(invoice.personId, invoice), disabled: !onPersonOpen }) })) : null, invoice.organizationId ? (_jsx(DetailRow, { label: detail.fields.organization, children: _jsx(DetailLink, { label: invoice.organizationId, actionLabel: detail.actions.viewOrganization, onClick: () => onOrganizationOpen?.(invoice.organizationId, invoice), disabled: !onOrganizationOpen }) })) : null, invoice.notes ? (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx("dt", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: detail.fields.notes }), _jsx("dd", { className: "whitespace-pre-wrap text-sm", children: invoice.notes })] })) : null, _jsx(DetailRow, { label: detail.fields.createdAt, children: formatDateTime(invoice.createdAt) }), _jsx(DetailRow, { label: detail.fields.updatedAt, children: formatDateTime(invoice.updatedAt) })] }) })] }));
|
|
68
|
+
}
|
|
69
|
+
export function InvoiceLineItemsCard({ invoice, lineItems, pending, deletePending, onCreate, onEdit, onDelete, className, }) {
|
|
70
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
71
|
+
const detail = messages.invoiceDetailPage;
|
|
72
|
+
return (_jsxs(Card, { "data-slot": "invoice-line-items-card", className: className, children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(CardTitle, { children: detail.titles.lineItems }), onCreate ? (_jsxs(Button, { size: "sm", onClick: () => onCreate(invoice), children: [_jsx(Plus, { className: "size-4", "aria-hidden": "true" }), detail.actions.addLineItem] })) : null] }), _jsx(CardContent, { children: pending ? (_jsx(LoadingRow, {})) : lineItems.length === 0 ? (_jsx(EmptyRow, { children: detail.states.noLineItems })) : (_jsx("div", { className: "overflow-hidden rounded border bg-background", children: _jsxs("table", { className: "w-full text-sm", children: [_jsx("thead", { children: _jsxs("tr", { className: "border-b text-muted-foreground", children: [_jsx("th", { className: "p-2 text-left font-medium", children: detail.columns.description }), _jsx("th", { className: "p-2 text-right font-medium", children: detail.columns.quantity }), _jsx("th", { className: "p-2 text-right font-medium", children: detail.columns.unitPrice }), _jsx("th", { className: "p-2 text-right font-medium", children: detail.columns.total }), _jsx("th", { className: "p-2 text-right font-medium", children: detail.columns.taxRate }), _jsx("th", { className: "w-20 p-2" })] }) }), _jsx("tbody", { children: lineItems.map((lineItem) => (_jsxs("tr", { className: "border-b last:border-b-0", children: [_jsx("td", { className: "p-2", children: lineItem.description }), _jsx("td", { className: "p-2 text-right", children: lineItem.quantity }), _jsx("td", { className: "p-2 text-right", children: _jsx(Money, { cents: lineItem.unitPriceCents, currency: invoice.currency }) }), _jsx("td", { className: "p-2 text-right", children: _jsx(Money, { cents: lineItem.totalCents, currency: invoice.currency }) }), _jsx("td", { className: "p-2 text-right", children: lineItem.taxRate === null ? detail.states.noValue : `${lineItem.taxRate}%` }), _jsx("td", { className: "p-2", children: _jsxs("div", { className: "flex justify-end gap-1", children: [onEdit ? (_jsxs(Button, { type: "button", variant: "ghost", size: "icon-sm", onClick: () => onEdit(lineItem, invoice), children: [_jsx(Pencil, { className: "size-4", "aria-hidden": "true" }), _jsx("span", { className: "sr-only", children: detail.actions.editLineItem })] })) : null, onDelete ? (_jsx(ConfirmActionButton, { buttonLabel: detail.actions.deleteLineItemShort, confirmLabel: detail.actions.delete, cancelLabel: messages.common.cancel, title: detail.actions.deleteLineItemTitle, description: detail.actions.deleteLineItemDescription, disabled: deletePending, variant: "ghost", confirmVariant: "destructive", onConfirm: () => onDelete(lineItem.id) })) : null] }) })] }, lineItem.id))) })] }) })) })] }));
|
|
73
|
+
}
|
|
74
|
+
export function InvoicePaymentsCard({ invoice, payments, pending, onCreate, className, }) {
|
|
75
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
76
|
+
const detail = messages.invoiceDetailPage;
|
|
77
|
+
return (_jsxs(Card, { "data-slot": "invoice-payments-card", className: className, children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(CardTitle, { children: detail.titles.payments }), onCreate ? (_jsxs(Button, { size: "sm", onClick: () => onCreate(invoice), children: [_jsx(Plus, { className: "size-4", "aria-hidden": "true" }), detail.actions.recordPayment] })) : null] }), _jsx(CardContent, { children: pending ? (_jsx(LoadingRow, {})) : payments.length === 0 ? (_jsx(EmptyRow, { children: detail.states.noPayments })) : (_jsx("ul", { className: "divide-y", children: payments.map((payment) => (_jsxs("li", { className: "flex items-center justify-between gap-3 py-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm font-medium", children: formatPaymentMethod(payment.paymentMethod, messages) }), _jsx("p", { className: "text-xs text-muted-foreground", children: payment.paymentDate })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Money, { cents: payment.amountCents, currency: payment.currency }), _jsx(Badge, { variant: paymentStatusVariant[payment.status] ?? "secondary", children: messages.common.supplierPaymentStatusLabels[payment.status] })] })] }, payment.id))) })) })] }));
|
|
78
|
+
}
|
|
79
|
+
export function InvoiceCreditNotesCard({ invoice, creditNotes, pending, onCreate, className, }) {
|
|
80
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
81
|
+
const detail = messages.invoiceDetailPage;
|
|
82
|
+
return (_jsxs(Card, { "data-slot": "invoice-credit-notes-card", className: className, children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(CardTitle, { children: detail.titles.creditNotes }), onCreate ? (_jsxs(Button, { size: "sm", onClick: () => onCreate(invoice), children: [_jsx(Plus, { className: "size-4", "aria-hidden": "true" }), detail.actions.addCreditNote] })) : null] }), _jsx(CardContent, { children: pending ? (_jsx(LoadingRow, {})) : creditNotes.length === 0 ? (_jsx(EmptyRow, { children: detail.states.noCreditNotes })) : (_jsx("ul", { className: "divide-y", children: creditNotes.map((creditNote) => (_jsxs("li", { className: "flex items-center justify-between gap-3 py-3", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate text-sm font-medium", children: creditNote.creditNoteNumber }), _jsx("p", { className: "line-clamp-2 text-xs text-muted-foreground", children: creditNote.reason })] }), _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Money, { cents: creditNote.amountCents, currency: creditNote.currency }), _jsx(Badge, { variant: creditNoteStatusVariant[creditNote.status] ?? "secondary", children: detail.creditNoteStatusLabels[creditNote.status] })] })] }, creditNote.id))) })) })] }));
|
|
83
|
+
}
|
|
84
|
+
export function InvoiceNotesCard({ notes, noteContent, pending, addPending, className, onNoteChange, onAddNote, }) {
|
|
85
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
86
|
+
const detail = messages.invoiceDetailPage;
|
|
87
|
+
return (_jsxs(Card, { "data-slot": "invoice-notes-card", className: className, children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: detail.titles.notes }) }), _jsxs(CardContent, { className: "flex flex-col gap-4", children: [pending ? (_jsx(LoadingRow, {})) : notes.length === 0 ? (_jsx(EmptyRow, { children: detail.states.noNotes })) : (_jsx("ul", { className: "divide-y", children: notes.map((note) => (_jsxs("li", { className: "py-3", children: [_jsx("p", { className: "whitespace-pre-wrap text-sm", children: note.content }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: note.createdAt })] }, note.id))) })), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Textarea, { value: noteContent, onChange: (event) => onNoteChange(event.target.value), placeholder: detail.placeholders.note, rows: 3 }), _jsxs(Button, { type: "button", className: "self-end", disabled: addPending || noteContent.trim().length === 0, onClick: () => void onAddNote(), children: [addPending ? _jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }) : null, detail.actions.addNote] })] })] })] }));
|
|
88
|
+
}
|
|
89
|
+
export function Money({ cents, currency }) {
|
|
90
|
+
const { formatCurrency } = useFinanceUiI18nOrDefault();
|
|
91
|
+
return _jsx("span", { className: "font-mono", children: formatCurrency(cents / 100, currency) });
|
|
92
|
+
}
|
|
93
|
+
export function DetailRow({ label, children }) {
|
|
94
|
+
return (_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("dt", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }), _jsx("dd", { className: "text-right", children: children })] }));
|
|
95
|
+
}
|
|
96
|
+
export function DetailLink({ label, actionLabel, onClick, disabled }) {
|
|
97
|
+
if (disabled)
|
|
98
|
+
return label;
|
|
99
|
+
return (_jsxs(Button, { type: "button", variant: "link", className: "h-auto p-0", onClick: onClick, children: [label, _jsx(ExternalLink, { className: "ml-1 size-3", "aria-label": actionLabel })] }));
|
|
100
|
+
}
|
|
101
|
+
export function EmptyRow({ children }) {
|
|
102
|
+
return _jsx("p", { className: "py-6 text-center text-sm text-muted-foreground", children: children });
|
|
103
|
+
}
|
|
104
|
+
export function LoadingRow() {
|
|
105
|
+
return (_jsx("div", { className: "flex justify-center py-6", children: _jsx(Loader2, { className: "size-5 animate-spin text-muted-foreground" }) }));
|
|
106
|
+
}
|
|
107
|
+
export function InvoiceDetailLoading({ className }) {
|
|
108
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
109
|
+
return (_jsx("div", { className: cn("flex min-h-48 items-center justify-center", className), children: _jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }), messages.invoiceDetailPage.states.loading] }) }));
|
|
110
|
+
}
|
|
111
|
+
export function InvoiceDetailState({ className, message, onBack, }) {
|
|
112
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
113
|
+
return (_jsxs("div", { className: cn("flex flex-col items-center justify-center gap-4 py-12", className), children: [_jsx("p", { className: "text-muted-foreground", children: message }), onBack ? (_jsx(Button, { type: "button", variant: "outline", onClick: onBack, children: messages.invoiceDetailPage.actions.back })) : null] }));
|
|
114
|
+
}
|
|
115
|
+
export function formatPaymentMethod(method, messages) {
|
|
116
|
+
if (method in messages.common.paymentMethodLabels) {
|
|
117
|
+
return messages.common.paymentMethodLabels[method];
|
|
118
|
+
}
|
|
119
|
+
if (method in messages.common.supplierPaymentMethodLabels) {
|
|
120
|
+
return messages.common.supplierPaymentMethodLabels[method];
|
|
121
|
+
}
|
|
122
|
+
return method.replace(/_/g, " ");
|
|
123
|
+
}
|
|
124
|
+
export const invoiceStatusVariant = {
|
|
125
|
+
draft: "outline",
|
|
126
|
+
sent: "secondary",
|
|
127
|
+
partially_paid: "secondary",
|
|
128
|
+
paid: "default",
|
|
129
|
+
overdue: "destructive",
|
|
130
|
+
void: "secondary",
|
|
131
|
+
};
|
|
132
|
+
export const paymentStatusVariant = {
|
|
133
|
+
pending: "outline",
|
|
134
|
+
completed: "default",
|
|
135
|
+
failed: "destructive",
|
|
136
|
+
refunded: "secondary",
|
|
137
|
+
};
|
|
138
|
+
export const creditNoteStatusVariant = {
|
|
139
|
+
draft: "outline",
|
|
140
|
+
issued: "secondary",
|
|
141
|
+
applied: "default",
|
|
142
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoices-page-skeleton.d.ts","sourceRoot":"","sources":["../../src/components/invoices-page-skeleton.tsx"],"names":[],"mappings":"AAcA,wBAAgB,qBAAqB,CAAC,EAAE,IAAQ,EAAE,EAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,2CAiCpE;AAED,wBAAgB,oBAAoB,4CAgBnC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Skeleton } from "@voyantjs/ui/components/skeleton";
|
|
3
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
|
|
4
|
+
import { useFinanceUiMessagesOrDefault } from "../i18n/index.js";
|
|
5
|
+
const INVOICE_WIDTHS = ["w-28", "w-16", "w-20", "w-20", "w-20", "w-24"];
|
|
6
|
+
export function InvoicesTableSkeleton({ rows = 8 }) {
|
|
7
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
8
|
+
const columns = messages.invoicesPage.columns;
|
|
9
|
+
return (_jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: columns.invoiceNumber }), _jsx(TableHead, { children: columns.status }), _jsx(TableHead, { children: columns.total }), _jsx(TableHead, { children: columns.paid }), _jsx(TableHead, { children: columns.balanceDue }), _jsx(TableHead, { children: columns.dueDate })] }) }), _jsx(TableBody, { children: Array.from({ length: rows }).map((_, rowIndex) => (_jsx(TableRow, { children: Array.from({ length: 6 }).map((__, columnIndex) => (_jsx(TableCell, { children: _jsx(Skeleton, { className: `h-4 ${INVOICE_WIDTHS[columnIndex] ?? "w-1/2"}` }) }, columnIndex))) }, rowIndex))) })] }) }));
|
|
10
|
+
}
|
|
11
|
+
export function InvoicesPageSkeleton() {
|
|
12
|
+
return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Skeleton, { className: "h-7 w-32" }), _jsx(Skeleton, { className: "h-4 w-80" })] }), _jsx(Skeleton, { className: "h-9 w-36" })] }), _jsx(Skeleton, { className: "h-9 w-full max-w-sm" }), _jsx(InvoicesTableSkeleton, { rows: 8 })] }));
|
|
13
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export interface InvoicesPageProps {
|
|
2
|
+
className?: string;
|
|
3
|
+
onOpenInvoice?: (invoiceId: string) => void;
|
|
4
|
+
}
|
|
5
|
+
export declare function InvoicesPage({ className, onOpenInvoice }?: InvoicesPageProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
//# sourceMappingURL=invoices-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"invoices-page.d.ts","sourceRoot":"","sources":["../../src/components/invoices-page.tsx"],"names":[],"mappings":"AA0CA,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;CAC5C;AAeD,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,aAAa,EAAE,GAAE,iBAAsB,2CAgThF"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useInvoices, } from "@voyantjs/finance-react";
|
|
3
|
+
import { formatMessage } from "@voyantjs/i18n";
|
|
4
|
+
import { Badge, Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
|
|
5
|
+
import { CurrencyCombobox } from "@voyantjs/ui/components/currency-combobox";
|
|
6
|
+
import { DateRangePicker } from "@voyantjs/ui/components/date-picker";
|
|
7
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@voyantjs/ui/components/popover";
|
|
8
|
+
import { Skeleton } from "@voyantjs/ui/components/skeleton";
|
|
9
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
|
|
10
|
+
import { cn } from "@voyantjs/ui/lib/utils";
|
|
11
|
+
import { ArrowDown, ArrowUp, ArrowUpDown, ListFilter, Plus, Search, X } from "lucide-react";
|
|
12
|
+
import { useState } from "react";
|
|
13
|
+
import { useFinanceUiMessagesOrDefault } from "../i18n/index.js";
|
|
14
|
+
import { invoiceStatuses } from "../i18n/messages.js";
|
|
15
|
+
import { InvoiceDialog } from "./invoice-dialog.js";
|
|
16
|
+
const PAGE_SIZE = 25;
|
|
17
|
+
const STATUS_ALL = "__all__";
|
|
18
|
+
const invoiceStatusVariant = {
|
|
19
|
+
draft: "outline",
|
|
20
|
+
sent: "secondary",
|
|
21
|
+
partially_paid: "secondary",
|
|
22
|
+
paid: "default",
|
|
23
|
+
overdue: "destructive",
|
|
24
|
+
void: "destructive",
|
|
25
|
+
};
|
|
26
|
+
function formatAmount(cents, currency) {
|
|
27
|
+
return `${(cents / 100).toFixed(2)} ${currency}`;
|
|
28
|
+
}
|
|
29
|
+
export function InvoicesPage({ className, onOpenInvoice } = {}) {
|
|
30
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
31
|
+
const [invoiceDialogOpen, setInvoiceDialogOpen] = useState(false);
|
|
32
|
+
const [search, setSearch] = useState("");
|
|
33
|
+
const [status, setStatus] = useState(STATUS_ALL);
|
|
34
|
+
const [currency, setCurrency] = useState(null);
|
|
35
|
+
const [dueDateRange, setDueDateRange] = useState(null);
|
|
36
|
+
const [sortBy, setSortBy] = useState("createdAt");
|
|
37
|
+
const [sortDir, setSortDir] = useState("desc");
|
|
38
|
+
const [pageIndex, setPageIndex] = useState(0);
|
|
39
|
+
const [filterPopoverOpen, setFilterPopoverOpen] = useState(false);
|
|
40
|
+
const { data, isPending, isFetching, isError } = useInvoices({
|
|
41
|
+
search: search || undefined,
|
|
42
|
+
status: status === STATUS_ALL ? undefined : status,
|
|
43
|
+
currency: currency ?? undefined,
|
|
44
|
+
dueDateFrom: dueDateRange?.from ?? undefined,
|
|
45
|
+
dueDateTo: dueDateRange?.to ?? undefined,
|
|
46
|
+
sortBy,
|
|
47
|
+
sortDir,
|
|
48
|
+
limit: PAGE_SIZE,
|
|
49
|
+
offset: pageIndex * PAGE_SIZE,
|
|
50
|
+
});
|
|
51
|
+
const invoices = data?.data ?? [];
|
|
52
|
+
const total = data?.total ?? 0;
|
|
53
|
+
const page = pageIndex + 1;
|
|
54
|
+
const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
|
55
|
+
const showSkeleton = isPending || (isFetching && invoices.length === 0);
|
|
56
|
+
const resetPage = () => setPageIndex(0);
|
|
57
|
+
const handleSort = (field) => {
|
|
58
|
+
resetPage();
|
|
59
|
+
if (sortBy !== field) {
|
|
60
|
+
setSortBy(field);
|
|
61
|
+
setSortDir("asc");
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (sortDir === "asc") {
|
|
65
|
+
setSortDir("desc");
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
setSortBy("createdAt");
|
|
69
|
+
setSortDir("desc");
|
|
70
|
+
};
|
|
71
|
+
const activeFilterCount = (status !== STATUS_ALL ? 1 : 0) +
|
|
72
|
+
(currency !== null ? 1 : 0) +
|
|
73
|
+
(dueDateRange?.from || dueDateRange?.to ? 1 : 0);
|
|
74
|
+
const hasActiveFilters = activeFilterCount > 0 || search !== "";
|
|
75
|
+
const clearFilters = () => {
|
|
76
|
+
setSearch("");
|
|
77
|
+
setStatus(STATUS_ALL);
|
|
78
|
+
setCurrency(null);
|
|
79
|
+
setDueDateRange(null);
|
|
80
|
+
resetPage();
|
|
81
|
+
};
|
|
82
|
+
const f = messages.invoicesPage;
|
|
83
|
+
return (_jsxs("div", { className: cn("flex flex-col gap-6 p-6", className), children: [_jsx("div", { className: "flex items-center justify-between", children: _jsxs("div", { children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: f.title }), _jsx("p", { className: "text-sm text-muted-foreground", children: f.description })] }) }), _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "relative min-w-[14rem] flex-1", children: [_jsx(Label, { htmlFor: "invoices-search", className: "sr-only", children: f.searchPlaceholder }), _jsx(Search, { className: "absolute left-3 top-1/2 size-4 -translate-y-1/2 text-muted-foreground", "aria-hidden": "true" }), _jsx(Input, { id: "invoices-search", placeholder: f.searchPlaceholder, value: search, onChange: (event) => {
|
|
84
|
+
setSearch(event.target.value);
|
|
85
|
+
resetPage();
|
|
86
|
+
}, className: "pl-9" })] }), _jsxs(Popover, { open: filterPopoverOpen, onOpenChange: setFilterPopoverOpen, children: [_jsx(PopoverTrigger, { render: _jsxs(Button, { variant: "outline", size: "default", children: [_jsx(ListFilter, { className: "mr-2 size-4", "aria-hidden": "true" }), f.filters.button, activeFilterCount > 0 && (_jsx(Badge, { variant: "secondary", className: "ml-2 px-1.5", children: activeFilterCount }))] }) }), _jsx(PopoverContent, { align: "start", className: "w-[24rem] p-4", children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "invoices-filter-status", children: f.filters.statusLabel }), _jsxs(Select, { value: status, onValueChange: (value) => {
|
|
87
|
+
setStatus(value ?? STATUS_ALL);
|
|
88
|
+
resetPage();
|
|
89
|
+
}, children: [_jsx(SelectTrigger, { id: "invoices-filter-status", className: "w-full", children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: STATUS_ALL, children: f.filters.statusAll }), invoiceStatuses.map((value) => (_jsx(SelectItem, { value: value, children: messages.common.invoiceStatusLabels[value] }, value)))] })] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { children: f.filters.currencyLabel }), _jsx(CurrencyCombobox, { value: currency, onChange: (value) => {
|
|
90
|
+
setCurrency(value);
|
|
91
|
+
resetPage();
|
|
92
|
+
}, placeholder: f.filters.currencyAny })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { children: f.filters.dueDateLabel }), _jsx(DateRangePicker, { value: dueDateRange, onChange: (value) => {
|
|
93
|
+
setDueDateRange(value);
|
|
94
|
+
resetPage();
|
|
95
|
+
}, placeholder: f.filters.dateAny, clearable: true, className: "w-full" })] })] }) })] }), hasActiveFilters && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: clearFilters, children: [_jsx(X, { className: "mr-1 size-4", "aria-hidden": "true" }), f.filters.clear] })), _jsx("div", { className: "ml-auto", children: _jsxs(Button, { onClick: () => setInvoiceDialogOpen(true), children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), f.actions.newInvoice] }) })] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: _jsx(SortHeader, { label: f.columns.invoiceNumber, field: "invoiceNumber", sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) }), _jsx(TableHead, { children: _jsx(SortHeader, { label: f.columns.status, field: "status", sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) }), _jsx(TableHead, { children: _jsx(SortHeader, { label: f.columns.total, field: "totalCents", sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) }), _jsx(TableHead, { children: _jsx(SortHeader, { label: f.columns.paid, field: "paidCents", sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) }), _jsx(TableHead, { children: _jsx(SortHeader, { label: f.columns.balanceDue, field: "balanceDueCents", sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) }), _jsx(TableHead, { children: _jsx(SortHeader, { label: f.columns.dueDate, field: "dueDate", sortBy: sortBy, sortDir: sortDir, onSort: handleSort }) })] }) }), _jsx(TableBody, { children: showSkeleton ? (_jsx(InvoiceRowSkeleton, { rows: 6 })) : isError ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "h-24 text-center text-sm text-destructive", children: f.loadFailed }) })) : invoices.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "h-24 text-center text-sm text-muted-foreground", children: f.empty }) })) : (invoices.map((row) => (_jsxs(TableRow, { onClick: () => onOpenInvoice?.(row.id), className: cn(onOpenInvoice && "cursor-pointer"), children: [_jsx(TableCell, { className: "font-medium", children: row.invoiceNumber }), _jsx(TableCell, { children: _jsx(Badge, { variant: invoiceStatusVariant[row.status] ?? "secondary", className: "capitalize", children: messages.common.invoiceStatusLabels[row.status] ?? row.status }) }), _jsx(TableCell, { className: "font-mono", children: formatAmount(row.totalCents, row.currency) }), _jsx(TableCell, { className: "font-mono", children: formatAmount(row.paidCents, row.currency) }), _jsx(TableCell, { className: "font-mono", children: formatAmount(row.balanceDueCents, row.currency) }), _jsx(TableCell, { children: row.dueDate })] }, row.id)))) })] }) }), _jsx(PaginationBar, { shown: invoices.length, total: total, page: page, pageCount: pageCount, onPrevious: () => setPageIndex((prev) => Math.max(0, prev - 1)), onNext: () => setPageIndex((prev) => prev + 1), canGoBack: pageIndex > 0, canGoForward: (pageIndex + 1) * PAGE_SIZE < total })] }), _jsx(InvoiceDialog, { open: invoiceDialogOpen, onOpenChange: setInvoiceDialogOpen })] }));
|
|
96
|
+
}
|
|
97
|
+
function SortHeader({ label, field, sortBy, sortDir, onSort, }) {
|
|
98
|
+
const active = sortBy === field;
|
|
99
|
+
const Icon = active ? (sortDir === "asc" ? ArrowUp : ArrowDown) : ArrowUpDown;
|
|
100
|
+
return (_jsxs("button", { type: "button", onClick: () => onSort(field), className: "-ml-2 inline-flex h-8 items-center gap-1 rounded-sm px-2 hover:bg-muted/60 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", children: [_jsx("span", { children: label }), _jsx(Icon, { className: `size-3.5 ${active ? "text-foreground" : "text-muted-foreground/60"}`, "aria-hidden": true })] }));
|
|
101
|
+
}
|
|
102
|
+
function PaginationBar({ shown, total, page, pageCount, onPrevious, onNext, canGoBack, canGoForward, }) {
|
|
103
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
104
|
+
const f = messages.invoicesPage.pagination;
|
|
105
|
+
return (_jsxs("div", { className: "flex items-center justify-between text-sm text-muted-foreground", children: [_jsx("span", { children: formatMessage(f.showing, { count: shown, total }) }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { variant: "outline", size: "sm", disabled: !canGoBack, onClick: onPrevious, children: f.previous }), _jsx("span", { children: formatMessage(f.page, { page, pageCount }) }), _jsx(Button, { variant: "outline", size: "sm", disabled: !canGoForward, onClick: onNext, children: f.next })] })] }));
|
|
106
|
+
}
|
|
107
|
+
function InvoiceRowSkeleton({ rows }) {
|
|
108
|
+
return (_jsx(_Fragment, { children: Array.from({ length: rows }).map((_, idx) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-32" }) }), _jsx(TableCell, { children: _jsx(Skeleton, { className: "h-5 w-20 rounded-full" }) }), _jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-24" }) }), _jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-24" }) }), _jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-24" }) }), _jsx(TableCell, { children: _jsx(Skeleton, { className: "h-4 w-24" }) })] }, `skeleton-${idx}`))) }));
|
|
109
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { type UnifiedPaymentRecord } from "@voyantjs/finance-react";
|
|
2
|
+
import type * as React from "react";
|
|
3
|
+
export interface PaymentDetailPageSlots {
|
|
4
|
+
afterHeader?: React.ReactNode;
|
|
5
|
+
afterSummary?: React.ReactNode;
|
|
6
|
+
afterLinks?: React.ReactNode;
|
|
7
|
+
afterMetadata?: React.ReactNode;
|
|
8
|
+
}
|
|
9
|
+
export interface PaymentDetailPageProps {
|
|
10
|
+
id: string;
|
|
11
|
+
className?: string;
|
|
12
|
+
onBack?: () => void;
|
|
13
|
+
onInvoiceOpen?: (invoiceId: string, payment: UnifiedPaymentRecord) => void;
|
|
14
|
+
onBookingOpen?: (bookingId: string, payment: UnifiedPaymentRecord) => void;
|
|
15
|
+
onPersonOpen?: (personId: string, payment: UnifiedPaymentRecord) => void;
|
|
16
|
+
onOrganizationOpen?: (organizationId: string, payment: UnifiedPaymentRecord) => void;
|
|
17
|
+
onSupplierOpen?: (supplierId: string, payment: UnifiedPaymentRecord) => void;
|
|
18
|
+
slots?: PaymentDetailPageSlots;
|
|
19
|
+
}
|
|
20
|
+
export declare function PaymentDetailPage({ id, className, onBack, onInvoiceOpen, onBookingOpen, onPersonOpen, onOrganizationOpen, onSupplierOpen, slots, }: PaymentDetailPageProps): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export interface PaymentDetailHeaderProps {
|
|
22
|
+
payment: UnifiedPaymentRecord;
|
|
23
|
+
onBack?: () => void;
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
export declare function PaymentDetailHeader({ payment, onBack, className }: PaymentDetailHeaderProps): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
export interface PaymentDetailCardProps {
|
|
28
|
+
payment: UnifiedPaymentRecord;
|
|
29
|
+
className?: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function PaymentSummaryCard({ payment, className }: PaymentDetailCardProps): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
export interface PaymentLinksCardProps extends PaymentDetailCardProps {
|
|
33
|
+
onInvoiceOpen?: (invoiceId: string, payment: UnifiedPaymentRecord) => void;
|
|
34
|
+
onBookingOpen?: (bookingId: string, payment: UnifiedPaymentRecord) => void;
|
|
35
|
+
onPersonOpen?: (personId: string, payment: UnifiedPaymentRecord) => void;
|
|
36
|
+
onOrganizationOpen?: (organizationId: string, payment: UnifiedPaymentRecord) => void;
|
|
37
|
+
onSupplierOpen?: (supplierId: string, payment: UnifiedPaymentRecord) => void;
|
|
38
|
+
}
|
|
39
|
+
export declare function PaymentLinksCard({ payment, className, onInvoiceOpen, onBookingOpen, onPersonOpen, onOrganizationOpen, onSupplierOpen, }: PaymentLinksCardProps): import("react/jsx-runtime").JSX.Element;
|
|
40
|
+
export declare function PaymentMetadataCard({ payment, className }: PaymentDetailCardProps): import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
//# sourceMappingURL=payment-detail-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/payment-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,oBAAoB,EAAc,MAAM,yBAAyB,CAAA;AAI/E,OAAO,KAAK,KAAK,KAAK,MAAM,OAAO,CAAA;AAInC,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC7B,YAAY,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC9B,UAAU,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;IAC5B,aAAa,CAAC,EAAE,KAAK,CAAC,SAAS,CAAA;CAChC;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC1E,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC1E,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IACxE,kBAAkB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IACpF,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC5E,KAAK,CAAC,EAAE,sBAAsB,CAAA;CAC/B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,EAAE,EACF,SAAS,EACT,MAAM,EACN,aAAa,EACb,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,cAAc,EACd,KAAK,GACN,EAAE,sBAAsB,2CAiDxB;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,oBAAoB,CAAA;IAC7B,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,wBAAwB,2CA0B3F;AAED,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,oBAAoB,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,sBAAsB,2CAgDhF;AAED,MAAM,WAAW,qBAAsB,SAAQ,sBAAsB;IACnE,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC1E,aAAa,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAC1E,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IACxE,kBAAkB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;IACpF,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAA;CAC7E;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,OAAO,EACP,SAAS,EACT,aAAa,EACb,aAAa,EACb,YAAY,EACZ,kBAAkB,EAClB,cAAc,GACf,EAAE,qBAAqB,2CA0FvB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,sBAAsB,2CAqBjF"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { usePayment } from "@voyantjs/finance-react";
|
|
4
|
+
import { Badge, Button, Card, CardContent, CardHeader, CardTitle } from "@voyantjs/ui/components";
|
|
5
|
+
import { cn } from "@voyantjs/ui/lib/utils";
|
|
6
|
+
import { ArrowLeft, ExternalLink, Loader2 } from "lucide-react";
|
|
7
|
+
import { useFinanceUiI18nOrDefault, useFinanceUiMessagesOrDefault } from "../i18n/index.js";
|
|
8
|
+
export function PaymentDetailPage({ id, className, onBack, onInvoiceOpen, onBookingOpen, onPersonOpen, onOrganizationOpen, onSupplierOpen, slots, }) {
|
|
9
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
10
|
+
const paymentQuery = usePayment(id);
|
|
11
|
+
if (paymentQuery.isPending) {
|
|
12
|
+
return _jsx(PaymentDetailLoading, { className: className });
|
|
13
|
+
}
|
|
14
|
+
if (paymentQuery.isError || !paymentQuery.data?.data) {
|
|
15
|
+
return (_jsx(PaymentDetailState, { className: className, message: paymentQuery.isError
|
|
16
|
+
? paymentQuery.error instanceof Error
|
|
17
|
+
? paymentQuery.error.message
|
|
18
|
+
: messages.paymentDetailPage.states.loadFailed
|
|
19
|
+
: messages.paymentDetailPage.states.notFound, onBack: onBack }));
|
|
20
|
+
}
|
|
21
|
+
const payment = paymentQuery.data.data;
|
|
22
|
+
return (_jsxs("div", { "data-slot": "payment-detail-page", className: cn("flex flex-col gap-6 p-6", className), children: [_jsx(PaymentDetailHeader, { payment: payment, onBack: onBack }), slots?.afterHeader, _jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsx(PaymentSummaryCard, { payment: payment }), _jsx(PaymentLinksCard, { payment: payment, onInvoiceOpen: onInvoiceOpen, onBookingOpen: onBookingOpen, onPersonOpen: onPersonOpen, onOrganizationOpen: onOrganizationOpen, onSupplierOpen: onSupplierOpen })] }), slots?.afterSummary, slots?.afterLinks, _jsx(PaymentMetadataCard, { payment: payment }), slots?.afterMetadata] }));
|
|
23
|
+
}
|
|
24
|
+
export function PaymentDetailHeader({ payment, onBack, className }) {
|
|
25
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
26
|
+
const relatedNumber = payment.kind === "customer" ? payment.invoiceNumber : payment.bookingNumber;
|
|
27
|
+
return (_jsxs("div", { "data-slot": "payment-detail-header", className: cn("flex items-start gap-4", className), children: [onBack ? (_jsxs(Button, { type: "button", variant: "ghost", size: "icon", onClick: onBack, children: [_jsx(ArrowLeft, { className: "size-4", "aria-hidden": "true" }), _jsx("span", { className: "sr-only", children: messages.paymentDetailPage.actions.back })] })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("h1", { className: "truncate font-mono text-lg font-semibold tracking-tight", children: payment.id }), _jsx(Badge, { variant: "outline", children: messages.paymentsPage.kindLabels[payment.kind] })] }), relatedNumber ? (_jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: relatedNumber })) : null] }), _jsx(Badge, { variant: paymentStatusVariant[payment.status] ?? "secondary", children: messages.common.supplierPaymentStatusLabels[payment.status] })] }));
|
|
28
|
+
}
|
|
29
|
+
export function PaymentSummaryCard({ payment, className }) {
|
|
30
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
31
|
+
const { formatCurrency } = useFinanceUiI18nOrDefault();
|
|
32
|
+
const detail = messages.paymentDetailPage;
|
|
33
|
+
return (_jsxs(Card, { "data-slot": "payment-summary-card", className: className, children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: detail.titles.summary }) }), _jsx(CardContent, { children: _jsxs("dl", { className: "grid gap-3 text-sm", children: [_jsx(DetailRow, { label: detail.fields.amount, children: _jsx("span", { className: "font-mono", children: formatCurrency(payment.amountCents / 100, payment.currency) }) }), payment.baseAmountCents !== null && payment.baseCurrency ? (_jsx(DetailRow, { label: detail.fields.baseAmount, children: _jsx("span", { className: "font-mono", children: formatCurrency(payment.baseAmountCents / 100, payment.baseCurrency) }) })) : null, _jsx(DetailRow, { label: detail.fields.status, children: _jsx(Badge, { variant: paymentStatusVariant[payment.status] ?? "secondary", children: messages.common.supplierPaymentStatusLabels[payment.status] }) }), _jsx(DetailRow, { label: detail.fields.method, children: formatPaymentMethod(payment.paymentMethod, messages) }), _jsx(DetailRow, { label: detail.fields.date, children: payment.paymentDate }), _jsx(DetailRow, { label: detail.fields.reference, children: payment.referenceNumber ?? detail.states.noValue }), payment.notes ? (_jsxs("div", { className: "mt-2 flex flex-col gap-1", children: [_jsx("dt", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: detail.fields.notes }), _jsx("dd", { className: "whitespace-pre-wrap text-sm", children: payment.notes })] })) : null] }) })] }));
|
|
34
|
+
}
|
|
35
|
+
export function PaymentLinksCard({ payment, className, onInvoiceOpen, onBookingOpen, onPersonOpen, onOrganizationOpen, onSupplierOpen, }) {
|
|
36
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
37
|
+
const detail = messages.paymentDetailPage;
|
|
38
|
+
return (_jsxs(Card, { "data-slot": "payment-links-card", className: className, children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: detail.titles.links }) }), _jsx(CardContent, { children: _jsx("dl", { className: "grid gap-3 text-sm", children: payment.kind === "customer" ? (_jsxs(_Fragment, { children: [_jsx(DetailRow, { label: detail.fields.paidBy, children: payment.personId && payment.personName ? (_jsx(DetailLink, { label: payment.personName, actionLabel: detail.actions.viewPerson, onClick: () => onPersonOpen?.(payment.personId, payment), disabled: !onPersonOpen })) : payment.organizationId && payment.organizationName ? (_jsx(DetailLink, { label: payment.organizationName, actionLabel: detail.actions.viewOrganization, onClick: () => onOrganizationOpen?.(payment.organizationId, payment), disabled: !onOrganizationOpen })) : (detail.states.noValue) }), payment.personId &&
|
|
39
|
+
payment.personName &&
|
|
40
|
+
payment.organizationId &&
|
|
41
|
+
payment.organizationName ? (_jsx(DetailRow, { label: detail.fields.organization, children: _jsx(DetailLink, { label: payment.organizationName, actionLabel: detail.actions.viewOrganization, onClick: () => onOrganizationOpen?.(payment.organizationId, payment), disabled: !onOrganizationOpen }) })) : null, _jsx(DetailRow, { label: detail.fields.invoice, children: payment.invoiceId ? (_jsx(DetailLink, { label: payment.invoiceNumber ?? detail.actions.viewInvoice, actionLabel: detail.actions.viewInvoice, onClick: () => onInvoiceOpen?.(payment.invoiceId, payment), disabled: !onInvoiceOpen })) : (detail.states.noValue) })] })) : (_jsxs(_Fragment, { children: [_jsx(DetailRow, { label: detail.fields.paidTo, children: payment.supplierId && payment.supplierName ? (_jsx(DetailLink, { label: payment.supplierName, actionLabel: detail.actions.viewSupplier, onClick: () => onSupplierOpen?.(payment.supplierId, payment), disabled: !onSupplierOpen })) : ((payment.supplierName ?? detail.states.noValue)) }), _jsx(DetailRow, { label: detail.fields.booking, children: payment.bookingId ? (_jsx(DetailLink, { label: payment.bookingNumber ?? detail.actions.viewBooking, actionLabel: detail.actions.viewBooking, onClick: () => onBookingOpen?.(payment.bookingId, payment), disabled: !onBookingOpen })) : (detail.states.noValue) })] })) }) })] }));
|
|
42
|
+
}
|
|
43
|
+
export function PaymentMetadataCard({ payment, className }) {
|
|
44
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
45
|
+
const { formatDateTime } = useFinanceUiI18nOrDefault();
|
|
46
|
+
const detail = messages.paymentDetailPage;
|
|
47
|
+
return (_jsxs(Card, { "data-slot": "payment-metadata-card", className: className, children: [_jsx(CardHeader, { children: _jsx(CardTitle, { children: detail.titles.metadata }) }), _jsx(CardContent, { children: _jsxs("dl", { className: "grid gap-3 text-sm", children: [_jsx(DetailRow, { label: detail.fields.kind, children: messages.paymentsPage.kindLabels[payment.kind] }), _jsx(DetailRow, { label: detail.fields.createdAt, children: formatDateTime(payment.createdAt) }), _jsx(DetailRow, { label: detail.fields.updatedAt, children: formatDateTime(payment.updatedAt) })] }) })] }));
|
|
48
|
+
}
|
|
49
|
+
function PaymentDetailLoading({ className }) {
|
|
50
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
51
|
+
return (_jsx("div", { className: cn("flex min-h-48 items-center justify-center", className), children: _jsxs("div", { className: "flex items-center gap-2 text-sm text-muted-foreground", children: [_jsx(Loader2, { className: "size-4 animate-spin", "aria-hidden": "true" }), messages.paymentDetailPage.states.loading] }) }));
|
|
52
|
+
}
|
|
53
|
+
function PaymentDetailState({ className, message, onBack, }) {
|
|
54
|
+
const messages = useFinanceUiMessagesOrDefault();
|
|
55
|
+
return (_jsxs("div", { className: cn("flex flex-col items-center justify-center gap-4 py-12", className), children: [_jsx("p", { className: "text-muted-foreground", children: message }), onBack ? (_jsx(Button, { type: "button", variant: "outline", onClick: onBack, children: messages.paymentDetailPage.actions.back })) : null] }));
|
|
56
|
+
}
|
|
57
|
+
function DetailRow({ label, children }) {
|
|
58
|
+
return (_jsxs("div", { className: "flex items-center justify-between gap-4", children: [_jsx("dt", { className: "text-xs uppercase tracking-wide text-muted-foreground", children: label }), _jsx("dd", { className: "text-right", children: children })] }));
|
|
59
|
+
}
|
|
60
|
+
function DetailLink({ label, actionLabel, onClick, disabled, }) {
|
|
61
|
+
if (disabled)
|
|
62
|
+
return label;
|
|
63
|
+
return (_jsxs(Button, { type: "button", variant: "link", className: "h-auto p-0", onClick: onClick, children: [label, _jsx(ExternalLink, { className: "ml-1 size-3", "aria-label": actionLabel })] }));
|
|
64
|
+
}
|
|
65
|
+
const paymentStatusVariant = {
|
|
66
|
+
pending: "outline",
|
|
67
|
+
completed: "default",
|
|
68
|
+
failed: "destructive",
|
|
69
|
+
refunded: "secondary",
|
|
70
|
+
};
|
|
71
|
+
function formatPaymentMethod(method, messages) {
|
|
72
|
+
if (method in messages.common.paymentMethodLabels) {
|
|
73
|
+
return messages.common.paymentMethodLabels[method];
|
|
74
|
+
}
|
|
75
|
+
if (method in messages.common.supplierPaymentMethodLabels) {
|
|
76
|
+
return messages.common.supplierPaymentMethodLabels[method];
|
|
77
|
+
}
|
|
78
|
+
return method.replace(/_/g, " ");
|
|
79
|
+
}
|