@voyantjs/legal-ui 0.52.1 → 0.52.3

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.
@@ -7,6 +7,12 @@ export interface ContractDetailPageProps {
7
7
  renderReference?: (props: ContractReferenceRenderProps) => ReactNode;
8
8
  getAttachmentDownloadHref?: (attachment: LegalContractAttachmentRecord) => string;
9
9
  slots?: ContractDetailPageSlots;
10
+ /**
11
+ * Resolve the recipient email for the Send-contract dialog. Operator
12
+ * templates wire this to the linked CRM person's primary email; if
13
+ * unset the dialog displays a "no recipient" warning and disables Send.
14
+ */
15
+ resolveSendRecipientEmail?: (contract: LegalContractRecord) => string | null | undefined;
10
16
  }
11
17
  export interface ContractDetailPageSlots {
12
18
  detailsContent?: ReactNode;
@@ -14,7 +20,7 @@ export interface ContractDetailPageSlots {
14
20
  signaturesContent?: ReactNode;
15
21
  documentsContent?: ReactNode;
16
22
  }
17
- export type ContractReferenceKind = "person" | "organization" | "supplier" | "channel" | "booking" | "order";
23
+ export type ContractReferenceKind = "person" | "organization" | "supplier" | "channel" | "booking" | "order" | "templateVersion" | "series";
18
24
  export interface ContractReferenceRenderProps {
19
25
  kind: ContractReferenceKind;
20
26
  id: string;
@@ -26,5 +32,5 @@ export interface ContractDetailDialogRenderProps {
26
32
  contract: LegalContractRecord;
27
33
  onSuccess: () => void;
28
34
  }
29
- export declare function ContractDetailPage({ id, onBackToContracts, renderContractDialog, renderReference, getAttachmentDownloadHref, slots, }: ContractDetailPageProps): import("react/jsx-runtime").JSX.Element;
35
+ export declare function ContractDetailPage({ id, onBackToContracts, renderContractDialog, renderReference, getAttachmentDownloadHref, slots, resolveSendRecipientEmail, }: ContractDetailPageProps): import("react/jsx-runtime").JSX.Element;
30
36
  //# sourceMappingURL=contract-detail-page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"contract-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/contract-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EAMzB,MAAM,uBAAuB,CAAA;AAqB9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAqBtC,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAA;IACV,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC9B,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,+BAA+B,KAAK,SAAS,CAAA;IAC5E,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,4BAA4B,KAAK,SAAS,CAAA;IACpE,yBAAyB,CAAC,EAAE,CAAC,UAAU,EAAE,6BAA6B,KAAK,MAAM,CAAA;IACjF,KAAK,CAAC,EAAE,uBAAuB,CAAA;CAChC;AAED,MAAM,WAAW,uBAAuB;IACtC,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,iBAAiB,CAAC,EAAE,SAAS,CAAA;IAC7B,gBAAgB,CAAC,EAAE,SAAS,CAAA;CAC7B;AAED,MAAM,MAAM,qBAAqB,GAC7B,QAAQ,GACR,cAAc,GACd,UAAU,GACV,SAAS,GACT,SAAS,GACT,OAAO,CAAA;AAEX,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,qBAAqB,CAAA;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,mBAAmB,CAAA;CAC9B;AAED,MAAM,WAAW,+BAA+B;IAC9C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EACjC,EAAE,EACF,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,yBAAyB,EACzB,KAAK,GACN,EAAE,uBAAuB,2CAoXzB"}
1
+ {"version":3,"file":"contract-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/contract-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EAMzB,MAAM,uBAAuB,CAAA;AAoB9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAsBtC,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAA;IACV,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC9B,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,+BAA+B,KAAK,SAAS,CAAA;IAC5E,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,4BAA4B,KAAK,SAAS,CAAA;IACpE,yBAAyB,CAAC,EAAE,CAAC,UAAU,EAAE,6BAA6B,KAAK,MAAM,CAAA;IACjF,KAAK,CAAC,EAAE,uBAAuB,CAAA;IAC/B;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CACzF;AAED,MAAM,WAAW,uBAAuB;IACtC,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,iBAAiB,CAAC,EAAE,SAAS,CAAA;IAC7B,gBAAgB,CAAC,EAAE,SAAS,CAAA;CAC7B;AAED,MAAM,MAAM,qBAAqB,GAC7B,QAAQ,GACR,cAAc,GACd,UAAU,GACV,SAAS,GACT,SAAS,GACT,OAAO,GACP,iBAAiB,GACjB,QAAQ,CAAA;AAEZ,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,qBAAqB,CAAA;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,mBAAmB,CAAA;CAC9B;AAED,MAAM,WAAW,+BAA+B;IAC9C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EACjC,EAAE,EACF,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,yBAAyB,EACzB,KAAK,EACL,yBAAyB,GAC1B,EAAE,uBAAuB,2CAyXzB"}
@@ -4,11 +4,11 @@ import { formatMessage } from "@voyantjs/i18n";
4
4
  import { useLegalContract, useLegalContractAttachmentMutation, useLegalContractAttachments, useLegalContractMutation, useLegalContractSignatures, } from "@voyantjs/legal-react";
5
5
  import { Badge, Button } from "@voyantjs/ui/components";
6
6
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
7
- import { Tabs, TabsContent, TabsList, TabsTrigger } from "@voyantjs/ui/components/tabs";
8
7
  import { ArrowLeft, CheckCircle2, ExternalLink, FileText, Pencil, Plus, Send, Trash2, } from "lucide-react";
9
8
  import { useState } from "react";
10
9
  import { useLegalUiI18nOrDefault, useLegalUiMessagesOrDefault } from "../i18n/index.js";
11
10
  import { AttachmentDialog } from "./attachment-dialog.js";
11
+ import { ContractSendDialog } from "./contract-send-dialog.js";
12
12
  import { SignatureDialog } from "./signature-dialog.js";
13
13
  const statusVariant = {
14
14
  draft: "outline",
@@ -19,14 +19,15 @@ const statusVariant = {
19
19
  expired: "destructive",
20
20
  void: "destructive",
21
21
  };
22
- export function ContractDetailPage({ id, onBackToContracts, renderContractDialog, renderReference, getAttachmentDownloadHref, slots, }) {
22
+ export function ContractDetailPage({ id, onBackToContracts, renderContractDialog, renderReference, getAttachmentDownloadHref, slots, resolveSendRecipientEmail, }) {
23
23
  const queryClient = useQueryClient();
24
24
  const i18n = useLegalUiI18nOrDefault();
25
25
  const messages = useLegalUiMessagesOrDefault();
26
26
  const f = messages.contractDetailPage;
27
- const { remove, issue, send, execute, voidContract } = useLegalContractMutation();
27
+ const { remove, issue, execute, voidContract } = useLegalContractMutation();
28
28
  const { remove: removeAttachment } = useLegalContractAttachmentMutation();
29
29
  const [editOpen, setEditOpen] = useState(false);
30
+ const [sendOpen, setSendOpen] = useState(false);
30
31
  const [signOpen, setSignOpen] = useState(false);
31
32
  const [attachOpen, setAttachOpen] = useState(false);
32
33
  const [editingAttachment, setEditingAttachment] = useState();
@@ -46,7 +47,7 @@ export function ContractDetailPage({ id, onBackToContracts, renderContractDialog
46
47
  const status = contract.status;
47
48
  const canAddSignature = status === "sent" || status === "signed";
48
49
  const renderReferenceValue = (kind, referenceId) => renderReference?.({ kind, id: referenceId, contract }) ?? (_jsx("span", { className: "font-mono text-xs", children: referenceId }));
49
- return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-4", children: [onBackToContracts ? (_jsx(Button, { variant: "ghost", size: "icon", onClick: onBackToContracts, children: _jsx(ArrowLeft, { className: "size-4", "aria-hidden": "true" }) })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: f.title }), _jsx(Badge, { variant: statusVariant[status] ?? "secondary", children: messages.common.contractStatusLabels[status] ?? status }), _jsx(Badge, { variant: "outline", children: messages.common.contractScopeLabels[contract.scope] ?? contract.scope })] }), _jsx("p", { className: "mt-1 truncate font-mono text-muted-foreground text-sm", children: contract.contractNumber ?? contract.title }), contract.contractNumber ? (_jsx("p", { className: "mt-1 truncate text-muted-foreground text-xs", children: contract.title })) : null] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [status === "draft" ? (_jsx(Button, { size: "sm", onClick: () => issue.mutate(id), disabled: issue.isPending, children: f.actions.issue })) : null, status === "issued" ? (_jsxs(Button, { size: "sm", onClick: () => send.mutate(id), disabled: send.isPending, children: [_jsx(Send, { className: "mr-2 size-4", "aria-hidden": "true" }), f.actions.send] })) : null, status === "signed" ? (_jsxs(Button, { size: "sm", onClick: () => execute.mutate(id), disabled: execute.isPending, children: [_jsx(CheckCircle2, { className: "mr-2 size-4", "aria-hidden": "true" }), f.actions.execute] })) : null, status !== "void" ? (_jsx(Button, { size: "sm", variant: "destructive", onClick: () => {
50
+ return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex items-center gap-4", children: [onBackToContracts ? (_jsx(Button, { variant: "ghost", size: "icon", onClick: onBackToContracts, children: _jsx(ArrowLeft, { className: "size-4", "aria-hidden": "true" }) })) : null, _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsx("h1", { className: "text-2xl font-bold tracking-tight", children: f.title }), _jsx(Badge, { variant: statusVariant[status] ?? "secondary", children: messages.common.contractStatusLabels[status] ?? status }), _jsx(Badge, { variant: "outline", children: messages.common.contractScopeLabels[contract.scope] ?? contract.scope })] }), _jsx("p", { className: "mt-1 truncate font-mono text-muted-foreground text-sm", children: contract.contractNumber ?? contract.title }), contract.contractNumber ? (_jsx("p", { className: "mt-1 truncate text-muted-foreground text-xs", children: contract.title })) : null] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [status === "draft" ? (_jsx(Button, { size: "sm", onClick: () => issue.mutate(id), disabled: issue.isPending, children: f.actions.issue })) : null, status === "issued" || status === "sent" ? (_jsxs(Button, { size: "sm", onClick: () => setSendOpen(true), children: [_jsx(Send, { className: "mr-2 size-4", "aria-hidden": "true" }), f.actions.send] })) : null, status === "signed" ? (_jsxs(Button, { size: "sm", onClick: () => execute.mutate(id), disabled: execute.isPending, children: [_jsx(CheckCircle2, { className: "mr-2 size-4", "aria-hidden": "true" }), f.actions.execute] })) : null, status !== "void" ? (_jsx(Button, { size: "sm", variant: "destructive", onClick: () => {
50
51
  if (confirm(f.voidConfirm)) {
51
52
  voidContract.mutate(id);
52
53
  }
@@ -54,22 +55,22 @@ export function ContractDetailPage({ id, onBackToContracts, renderContractDialog
54
55
  if (confirm(formatMessage(f.deleteConfirm, { title: contract.title }))) {
55
56
  remove.mutate(id, { onSuccess: () => onBackToContracts?.() });
56
57
  }
57
- }, disabled: remove.isPending, children: [_jsx(Trash2, { className: "mr-2 size-4", "aria-hidden": "true" }), messages.common.delete] })) : null] })] }), _jsxs(Tabs, { defaultValue: "details", children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "details", children: f.sections.details }), _jsx(TabsTrigger, { value: "parties", children: f.sections.parties }), _jsx(TabsTrigger, { value: "signatures", children: f.sections.signatures }), _jsx(TabsTrigger, { value: "documents", children: f.sections.documents })] }), _jsx(TabsContent, { value: "details", className: "mt-4", children: slots?.detailsContent !== undefined ? (slots.detailsContent) : (_jsx(ContractSection, { title: f.sections.details, children: _jsxs("div", { className: "grid gap-3 text-sm", children: [_jsx(DetailRow, { label: f.fields.language, children: contract.language }), contract.templateVersionId ? (_jsx(DetailRow, { label: f.fields.templateVersion, children: _jsx("span", { className: "font-mono text-xs", children: contract.templateVersionId }) })) : null, contract.seriesId ? (_jsx(DetailRow, { label: f.fields.series, children: _jsx("span", { className: "font-mono text-xs", children: contract.seriesId }) })) : null, contract.expiresAt ? (_jsx(DetailRow, { label: f.fields.expires, children: i18n.formatDate(contract.expiresAt) })) : null, _jsxs("div", { className: "mt-2 border-t pt-3", children: [_jsx(DetailRow, { label: f.fields.created, children: i18n.formatDate(contract.createdAt) }), _jsx(DetailRow, { label: f.fields.updated, children: i18n.formatDate(contract.updatedAt) })] })] }) })) }), _jsx(TabsContent, { value: "parties", className: "mt-4", children: slots?.partiesContent !== undefined ? (slots.partiesContent) : (_jsx(ContractSection, { title: f.sections.parties, children: _jsxs("div", { className: "grid gap-3 text-sm", children: [contract.personId ? (_jsx(DetailRow, { label: f.fields.person, children: renderReferenceValue("person", contract.personId) })) : null, contract.organizationId ? (_jsx(DetailRow, { label: f.fields.organization, children: renderReferenceValue("organization", contract.organizationId) })) : null, contract.supplierId ? (_jsx(DetailRow, { label: f.fields.supplier, children: renderReferenceValue("supplier", contract.supplierId) })) : null, contract.channelId ? (_jsx(DetailRow, { label: f.fields.channel, children: renderReferenceValue("channel", contract.channelId) })) : null, contract.bookingId ? (_jsx(DetailRow, { label: f.fields.booking, children: renderReferenceValue("booking", contract.bookingId) })) : null, contract.orderId ? (_jsx(DetailRow, { label: f.fields.order, children: renderReferenceValue("order", contract.orderId) })) : null, !contract.personId &&
58
- !contract.organizationId &&
59
- !contract.supplierId &&
60
- !contract.channelId &&
61
- !contract.bookingId &&
62
- !contract.orderId ? (_jsx("p", { className: "text-muted-foreground", children: f.empty.noParties })) : null] }) })) }), _jsx(TabsContent, { value: "signatures", className: "mt-4", children: slots?.signaturesContent !== undefined ? (slots.signaturesContent) : (_jsx(ContractSection, { title: f.sections.signatures, action: canAddSignature ? (_jsxs(Button, { size: "sm", onClick: () => setSignOpen(true), children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), f.actions.addSignature] })) : null, children: !signatures || signatures.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: f.empty.noSignatures })) : (_jsx("div", { className: "rounded border bg-background", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: f.fields.name }), _jsx(TableHead, { children: f.fields.email }), _jsx(TableHead, { children: f.fields.role }), _jsx(TableHead, { children: f.fields.method }), _jsx(TableHead, { children: f.fields.signedAt })] }) }), _jsx(TableBody, { children: signatures.map((signature) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: signature.signerName }), _jsx(TableCell, { children: signature.signerEmail ?? messages.common.noResultsDash }), _jsx(TableCell, { children: signature.signerRole ?? messages.common.noResultsDash }), _jsx(TableCell, { children: signature.method }), _jsx(TableCell, { children: i18n.formatDateTime(signature.signedAt) })] }, signature.id))) })] }) })) })) }), _jsx(TabsContent, { value: "documents", className: "mt-4", children: slots?.documentsContent !== undefined ? (slots.documentsContent) : (_jsx(ContractSection, { title: f.sections.documents, action: _jsxs(Button, { size: "sm", onClick: () => {
63
- setEditingAttachment(undefined);
64
- setAttachOpen(true);
65
- }, children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), f.actions.addDocument] }), children: !attachments || attachments.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: f.empty.noAttachments })) : (_jsx("div", { className: "rounded border bg-background", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: f.fields.name }), _jsx(TableHead, { children: f.fields.kind }), _jsx(TableHead, { children: f.fields.mimeType }), _jsx(TableHead, { children: f.fields.size }), _jsx(TableHead, { className: "w-16" })] }) }), _jsx(TableBody, { children: attachments.map((attachment) => (_jsx(AttachmentRow, { attachment: attachment, downloadHref: getAttachmentDownloadHref?.(attachment), onEdit: () => {
66
- setEditingAttachment(attachment);
67
- setAttachOpen(true);
68
- }, onDelete: () => {
69
- if (confirm(f.deleteAttachmentConfirm)) {
70
- removeAttachment.mutate({ contractId: id, id: attachment.id });
71
- }
72
- } }, attachment.id))) })] }) })) })) })] }), renderContractDialog?.({
58
+ }, disabled: remove.isPending, children: [_jsx(Trash2, { className: "mr-2 size-4", "aria-hidden": "true" }), messages.common.delete] })) : null] })] }), _jsxs("div", { className: "flex flex-col gap-4", children: [slots?.detailsContent !== undefined ? (slots.detailsContent) : (_jsx(ContractSection, { title: f.sections.details, children: _jsxs("div", { className: "grid gap-3 text-sm", children: [_jsx(DetailRow, { label: f.fields.language, children: contract.language }), contract.templateVersionId ? (_jsx(DetailRow, { label: f.fields.templateVersion, children: renderReferenceValue("templateVersion", contract.templateVersionId) })) : null, contract.seriesId ? (_jsx(DetailRow, { label: f.fields.series, children: renderReferenceValue("series", contract.seriesId) })) : null, contract.expiresAt ? (_jsx(DetailRow, { label: f.fields.expires, children: i18n.formatDate(contract.expiresAt) })) : null, _jsxs("div", { className: "mt-2 border-t pt-3", children: [_jsx(DetailRow, { label: f.fields.created, children: i18n.formatDate(contract.createdAt) }), _jsx(DetailRow, { label: f.fields.updated, children: i18n.formatDate(contract.updatedAt) })] })] }) })), slots?.partiesContent !== undefined ? (slots.partiesContent) : (_jsx(ContractSection, { title: f.sections.parties, children: _jsxs("div", { className: "grid gap-3 text-sm", children: [contract.personId ? (_jsx(DetailRow, { label: f.fields.person, children: renderReferenceValue("person", contract.personId) })) : null, contract.organizationId ? (_jsx(DetailRow, { label: f.fields.organization, children: renderReferenceValue("organization", contract.organizationId) })) : null, contract.supplierId ? (_jsx(DetailRow, { label: f.fields.supplier, children: renderReferenceValue("supplier", contract.supplierId) })) : null, contract.channelId ? (_jsx(DetailRow, { label: f.fields.channel, children: renderReferenceValue("channel", contract.channelId) })) : null, contract.bookingId ? (_jsx(DetailRow, { label: f.fields.booking, children: renderReferenceValue("booking", contract.bookingId) })) : null, contract.orderId ? (_jsx(DetailRow, { label: f.fields.order, children: renderReferenceValue("order", contract.orderId) })) : null, !contract.personId &&
59
+ !contract.organizationId &&
60
+ !contract.supplierId &&
61
+ !contract.channelId &&
62
+ !contract.bookingId &&
63
+ !contract.orderId ? (_jsx("p", { className: "text-muted-foreground", children: f.empty.noParties })) : null] }) })), slots?.signaturesContent !== undefined ? (slots.signaturesContent) : (_jsx(ContractSection, { title: f.sections.signatures, action: canAddSignature ? (_jsxs(Button, { size: "sm", onClick: () => setSignOpen(true), children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), f.actions.addSignature] })) : null, children: !signatures || signatures.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: f.empty.noSignatures })) : (_jsx("div", { className: "rounded border bg-background", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: f.fields.name }), _jsx(TableHead, { children: f.fields.email }), _jsx(TableHead, { children: f.fields.role }), _jsx(TableHead, { children: f.fields.method }), _jsx(TableHead, { children: f.fields.signedAt })] }) }), _jsx(TableBody, { children: signatures.map((signature) => (_jsxs(TableRow, { children: [_jsx(TableCell, { children: signature.signerName }), _jsx(TableCell, { children: signature.signerEmail ?? messages.common.noResultsDash }), _jsx(TableCell, { children: signature.signerRole ?? messages.common.noResultsDash }), _jsx(TableCell, { children: signature.method }), _jsx(TableCell, { children: i18n.formatDateTime(signature.signedAt) })] }, signature.id))) })] }) })) })), slots?.documentsContent !== undefined ? (slots.documentsContent) : (_jsx(ContractSection, { title: f.sections.documents, action: _jsxs(Button, { size: "sm", onClick: () => {
64
+ setEditingAttachment(undefined);
65
+ setAttachOpen(true);
66
+ }, children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), f.actions.addDocument] }), children: !attachments || attachments.length === 0 ? (_jsx("p", { className: "py-4 text-center text-sm text-muted-foreground", children: f.empty.noAttachments })) : (_jsx("div", { className: "rounded border bg-background", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: f.fields.name }), _jsx(TableHead, { children: f.fields.kind }), _jsx(TableHead, { className: "w-16" })] }) }), _jsx(TableBody, { children: attachments.map((attachment) => (_jsx(AttachmentRow, { attachment: attachment, downloadHref: getAttachmentDownloadHref?.(attachment), onEdit: () => {
67
+ setEditingAttachment(attachment);
68
+ setAttachOpen(true);
69
+ }, onDelete: () => {
70
+ if (confirm(f.deleteAttachmentConfirm)) {
71
+ removeAttachment.mutate({ contractId: id, id: attachment.id });
72
+ }
73
+ } }, attachment.id))) })] }) })) }))] }), renderContractDialog?.({
73
74
  open: editOpen,
74
75
  onOpenChange: setEditOpen,
75
76
  contract,
@@ -85,6 +86,10 @@ export function ContractDetailPage({ id, onBackToContracts, renderContractDialog
85
86
  setAttachOpen(false);
86
87
  setEditingAttachment(undefined);
87
88
  void refetchAttachments();
89
+ } }), _jsx(ContractSendDialog, { open: sendOpen, onOpenChange: setSendOpen, contract: contract, defaultRecipientEmail: resolveSendRecipientEmail?.(contract) ?? null, attachments: (attachments ?? [])
90
+ .filter((a) => a.kind === "document")
91
+ .map((a) => ({ id: a.id, name: a.name })), onSent: () => {
92
+ void queryClient.invalidateQueries();
88
93
  } })] }));
89
94
  }
90
95
  function DetailRow({ label, children }) {
@@ -94,20 +99,5 @@ function ContractSection({ title, action, children, }) {
94
99
  return (_jsxs("section", { className: "rounded-md border bg-background", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 border-b px-4 py-3", children: [_jsx("h2", { className: "font-semibold text-sm", children: title }), action] }), _jsx("div", { className: "p-4", children: children })] }));
95
100
  }
96
101
  function AttachmentRow({ attachment, downloadHref, onEdit, onDelete, }) {
97
- const messages = useLegalUiMessagesOrDefault();
98
- const f = messages.contractDetailPage;
99
- return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: downloadHref ? (_jsxs("a", { href: downloadHref, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1.5 hover:underline", children: [_jsx(FileText, { className: "size-3.5 shrink-0 opacity-60", "aria-hidden": "true" }), _jsx("span", { className: "truncate", children: attachment.name }), _jsx(ExternalLink, { className: "size-3 opacity-60", "aria-hidden": "true" })] })) : (_jsx("span", { children: attachment.name })) }), _jsx(TableCell, { children: attachment.kind }), _jsx(TableCell, { children: attachment.mimeType ?? messages.common.noResultsDash }), _jsx(TableCell, { children: attachment.fileSize != null
100
- ? formatBytes(attachment.fileSize, {
101
- bytes: f.units.bytes,
102
- kilobytes: f.units.kilobytes,
103
- megabytes: f.units.megabytes,
104
- })
105
- : messages.common.noResultsDash }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: onEdit, className: "text-muted-foreground hover:text-foreground", children: _jsx(Pencil, { className: "size-3", "aria-hidden": "true" }) }), _jsx("button", { type: "button", onClick: onDelete, className: "text-muted-foreground hover:text-destructive", children: _jsx(Trash2, { className: "size-3", "aria-hidden": "true" }) })] }) })] }));
106
- }
107
- function formatBytes(bytes, units) {
108
- if (bytes < 1024)
109
- return `${bytes} ${units.bytes}`;
110
- if (bytes < 1024 * 1024)
111
- return `${Math.round(bytes / 1024)} ${units.kilobytes}`;
112
- return `${(bytes / (1024 * 1024)).toFixed(1)} ${units.megabytes}`;
102
+ return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: downloadHref ? (_jsxs("a", { href: downloadHref, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1.5 hover:underline", children: [_jsx(FileText, { className: "size-3.5 shrink-0 opacity-60", "aria-hidden": "true" }), _jsx("span", { className: "truncate", children: attachment.name }), _jsx(ExternalLink, { className: "size-3 opacity-60", "aria-hidden": "true" })] })) : (_jsx("span", { children: attachment.name })) }), _jsx(TableCell, { children: attachment.kind }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("button", { type: "button", onClick: onEdit, className: "text-muted-foreground hover:text-foreground", children: _jsx(Pencil, { className: "size-3", "aria-hidden": "true" }) }), _jsx("button", { type: "button", onClick: onDelete, className: "text-muted-foreground hover:text-destructive", children: _jsx(Trash2, { className: "size-3", "aria-hidden": "true" }) })] }) })] }));
113
103
  }
@@ -0,0 +1,37 @@
1
+ import { type LegalContractRecord } from "@voyantjs/legal-react";
2
+ export interface ContractSendDialogProps {
3
+ open: boolean;
4
+ onOpenChange: (open: boolean) => void;
5
+ contract: LegalContractRecord;
6
+ /**
7
+ * Recipient email — typically the linked person's primary email, resolved
8
+ * by the caller (the operator template knows how to look it up via CRM).
9
+ * The dialog uses this as the read-only "To" line; if absent the dialog
10
+ * shows a warning and disables Send.
11
+ */
12
+ defaultRecipientEmail?: string | null;
13
+ /** Optional override for the prefilled subject. */
14
+ defaultSubject?: string;
15
+ /** Optional override for the prefilled message body. */
16
+ defaultMessage?: string;
17
+ /**
18
+ * Optional list of attachment names that will accompany the email. The
19
+ * dialog renders them as a paperclip-prefixed list so the operator sees
20
+ * what's bundled (the contract PDF + any extras).
21
+ */
22
+ attachments?: ReadonlyArray<{
23
+ id: string;
24
+ name: string;
25
+ }>;
26
+ onSent?: () => void;
27
+ }
28
+ /**
29
+ * Send-contract preview dialog. Renders an editable subject + message
30
+ * over a read-only recipient line, so the operator can tweak the
31
+ * customer-facing copy before flipping the contract to `sent`. The
32
+ * actual `POST /:id/send` accepts the subject + message + recipient
33
+ * overrides and forwards them on the lifecycle event for whichever
34
+ * notification subscriber wires up email delivery downstream.
35
+ */
36
+ export declare function ContractSendDialog({ open, onOpenChange, contract, defaultRecipientEmail, defaultSubject, defaultMessage, attachments, onSent, }: ContractSendDialogProps): import("react/jsx-runtime").JSX.Element;
37
+ //# sourceMappingURL=contract-send-dialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract-send-dialog.d.ts","sourceRoot":"","sources":["../../src/components/contract-send-dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAA4B,MAAM,uBAAuB,CAAA;AAkB1F,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,mBAAmB,CAAA;IAC7B;;;;;OAKG;IACH,qBAAqB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;OAIG;IACH,WAAW,CAAC,EAAE,aAAa,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IACzD,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;CACpB;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,qBAAqB,EACrB,cAAc,EACd,cAAc,EACd,WAAW,EACX,MAAM,GACP,EAAE,uBAAuB,2CAwIzB"}
@@ -0,0 +1,56 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useLegalContractMutation } from "@voyantjs/legal-react";
3
+ import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Textarea, } from "@voyantjs/ui/components";
4
+ import { Loader2, Mail, Paperclip } from "lucide-react";
5
+ import { useEffect, useState } from "react";
6
+ import { useLegalUiMessagesOrDefault } from "../i18n/index.js";
7
+ /**
8
+ * Send-contract preview dialog. Renders an editable subject + message
9
+ * over a read-only recipient line, so the operator can tweak the
10
+ * customer-facing copy before flipping the contract to `sent`. The
11
+ * actual `POST /:id/send` accepts the subject + message + recipient
12
+ * overrides and forwards them on the lifecycle event for whichever
13
+ * notification subscriber wires up email delivery downstream.
14
+ */
15
+ export function ContractSendDialog({ open, onOpenChange, contract, defaultRecipientEmail, defaultSubject, defaultMessage, attachments, onSent, }) {
16
+ const { send } = useLegalContractMutation();
17
+ const messages = useLegalUiMessagesOrDefault().contractSendDialog;
18
+ const fallbackSubject = defaultSubject ??
19
+ messages.fallbackSubject.replace("{number}", contract.contractNumber ?? messages.title);
20
+ const fallbackMessage = defaultMessage ??
21
+ [
22
+ "Hi,",
23
+ "",
24
+ `Please find attached the contract${contract.contractNumber ? ` ${contract.contractNumber}` : ""} for your booking.`,
25
+ "",
26
+ "Reply to this email if you have any questions before signing.",
27
+ "",
28
+ "Thanks,",
29
+ ].join("\n");
30
+ const [subject, setSubject] = useState(fallbackSubject);
31
+ const [message, setMessage] = useState(fallbackMessage);
32
+ // Reset the form when the dialog re-opens or the underlying contract /
33
+ // defaults change — guarantees the operator never sees stale copy from
34
+ // a previous Send attempt on a different contract.
35
+ useEffect(() => {
36
+ if (open) {
37
+ setSubject(fallbackSubject);
38
+ setMessage(fallbackMessage);
39
+ }
40
+ }, [open, fallbackSubject, fallbackMessage]);
41
+ const canSend = Boolean(defaultRecipientEmail) && !send.isPending;
42
+ const isAlreadySent = contract.status === "sent" || contract.status === "signed";
43
+ const handleSend = async () => {
44
+ await send.mutateAsync({
45
+ id: contract.id,
46
+ input: {
47
+ recipientEmail: defaultRecipientEmail ?? null,
48
+ subject: subject.trim() || null,
49
+ message: message.trim() || null,
50
+ },
51
+ });
52
+ onOpenChange(false);
53
+ onSent?.();
54
+ };
55
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: messages.title }) }), _jsxs(DialogBody, { className: "grid gap-4", children: [!defaultRecipientEmail ? (_jsx("div", { role: "alert", className: "rounded-md border border-destructive/50 bg-destructive/10 px-3 py-2 text-xs text-destructive", children: messages.missingRecipient })) : null, isAlreadySent ? (_jsx("div", { role: "status", className: "rounded-md border border-amber-500/50 bg-amber-500/10 px-3 py-2 text-xs text-amber-700 dark:text-amber-300", children: messages.alreadySentWarning })) : null, _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.fields.to }), _jsxs("div", { className: "flex items-center gap-2 rounded-md border bg-muted/40 px-3 py-2 text-sm", children: [_jsx(Mail, { className: "h-3.5 w-3.5 text-muted-foreground", "aria-hidden": "true" }), _jsx("span", { className: "font-mono", children: defaultRecipientEmail ?? messages.recipientPlaceholder })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "contract-send-subject", children: messages.fields.subject }), _jsx(Input, { id: "contract-send-subject", value: subject, onChange: (event) => setSubject(event.target.value) })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { htmlFor: "contract-send-message", children: messages.fields.message }), _jsx(Textarea, { id: "contract-send-message", rows: 8, value: message, onChange: (event) => setMessage(event.target.value) }), _jsx("p", { className: "text-xs text-muted-foreground", children: messages.messageHint })] }), attachments && attachments.length > 0 ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.fields.attachments }), _jsx("ul", { className: "flex flex-col gap-1 rounded-md border bg-muted/40 px-3 py-2 text-xs", children: attachments.map((attachment) => (_jsxs("li", { className: "flex items-center gap-2", children: [_jsx(Paperclip, { className: "h-3 w-3 text-muted-foreground", "aria-hidden": "true" }), _jsx("span", { className: "truncate", children: attachment.name })] }, attachment.id))) })] })) : null] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", onClick: () => onOpenChange(false), disabled: send.isPending, children: messages.actions.cancel }), _jsxs(Button, { type: "button", size: "sm", onClick: handleSend, disabled: !canSend, children: [send.isPending ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, messages.actions.send] })] })] }) }));
56
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"contracts-page.d.ts","sourceRoot":"","sources":["../../src/components/contracts-page.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAoBtC,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,cAAc,EAAE,qBAAqB,GAAG,IAAI,CAAA;IAC5C,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,KAAK,IAAI,CAAA;CACzF;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACpD,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,SAAS,CAAA;IACtE,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,qBAAqB,GAAG,IAAI,KAAK,SAAS,CAAA;IAC/F,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,+BAA+B,KAAK,SAAS,CAAA;CAC3E;AAED,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,eAAe,EACf,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,GACnB,GAAE,kBAAuB,2CA6NzB"}
1
+ {"version":3,"file":"contracts-page.d.ts","sourceRoot":"","sources":["../../src/components/contracts-page.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAoBtC,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACrB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,cAAc,EAAE,qBAAqB,GAAG,IAAI,CAAA;IAC5C,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,qBAAqB,GAAG,IAAI,KAAK,IAAI,CAAA;CACzF;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACpD,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,SAAS,CAAA;IACtE,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,EAAE,qBAAqB,GAAG,IAAI,KAAK,SAAS,CAAA;IAC/F,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,+BAA+B,KAAK,SAAS,CAAA;CAC3E;AAED,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,eAAe,EACf,QAAQ,EACR,gBAAgB,EAChB,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,kBAAkB,GACnB,GAAE,kBAAuB,2CAgSzB"}
@@ -8,7 +8,7 @@ import { Popover, PopoverContent, PopoverTrigger } from "@voyantjs/ui/components
8
8
  import { Skeleton } from "@voyantjs/ui/components/skeleton";
9
9
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
10
10
  import { cn } from "@voyantjs/ui/lib/utils";
11
- import { ChevronDown, Plus, Search, User, X } from "lucide-react";
11
+ import { ChevronDown, ListFilter, Plus, Search, User, X } from "lucide-react";
12
12
  import { useMemo, useState } from "react";
13
13
  import { useLegalUiI18nOrDefault, useLegalUiMessagesOrDefault } from "../i18n/index.js";
14
14
  import { legalContractScopes, legalContractStatuses } from "../i18n/messages.js";
@@ -34,8 +34,11 @@ export function ContractsPage({ className, defaultPersonId, personId, onPersonId
34
34
  const [internalPersonId, setInternalPersonId] = useState(() => getInitialPersonId(defaultPersonId));
35
35
  const [selectedPerson, setSelectedPerson] = useState(null);
36
36
  const [dialogOpen, setDialogOpen] = useState(false);
37
+ const [filtersOpen, setFiltersOpen] = useState(false);
37
38
  const [pageIndex, setPageIndex] = useState(0);
38
39
  const resolvedPersonId = personId ?? internalPersonId;
40
+ const activeFilterCount = (scope !== SCOPE_ALL ? 1 : 0) + (status !== STATUS_ALL ? 1 : 0) + (resolvedPersonId ? 1 : 0);
41
+ const hasActiveFilters = activeFilterCount > 0 || search !== "";
39
42
  const { data, isPending, isFetching, isError, refetch } = useLegalContracts({
40
43
  search,
41
44
  scope: scope === SCOPE_ALL ? "all" : scope,
@@ -69,20 +72,31 @@ export function ContractsPage({ className, defaultPersonId, personId, onPersonId
69
72
  onPersonIdChange?.(nextPersonId);
70
73
  resetPage();
71
74
  };
72
- return (_jsxs("div", { className: cn("flex flex-col gap-6 p-6", className), children: [_jsxs("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 })] }), renderContractDialog ? (_jsxs(Button, { onClick: () => setDialogOpen(true), children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), f.create] })) : null] }), _jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsxs("div", { className: "relative min-w-[14rem] max-w-sm flex-1", children: [_jsx(Label, { htmlFor: "contracts-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: "contracts-search", placeholder: f.searchPlaceholder, value: search, onChange: (event) => {
75
+ const clearFilters = () => {
76
+ setSearch("");
77
+ setScope(SCOPE_ALL);
78
+ setStatus(STATUS_ALL);
79
+ handlePersonChange(null, null);
80
+ resetPage();
81
+ };
82
+ return (_jsxs("div", { className: cn("flex flex-col gap-6 p-6", className), 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-wrap items-center gap-2", children: [_jsxs("div", { className: "relative min-w-[14rem] flex-1", children: [_jsx(Label, { htmlFor: "contracts-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: "contracts-search", placeholder: f.searchPlaceholder, value: search, onChange: (event) => {
73
83
  setSearch(event.target.value);
74
84
  resetPage();
75
- }, className: "pl-9" })] }), _jsxs(Select, { value: scope, onValueChange: (value) => {
76
- setScope(value ?? SCOPE_ALL);
77
- resetPage();
78
- }, children: [_jsx(SelectTrigger, { className: "w-[12.5rem]", children: _jsx(SelectValue, { placeholder: f.filters.scope }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: SCOPE_ALL, children: f.filters.allScopes }), legalContractScopes.map((value) => (_jsx(SelectItem, { value: value, children: messages.common.contractScopeLabels[value] }, value)))] })] }), _jsxs(Select, { value: status, onValueChange: (value) => {
79
- setStatus(value ?? STATUS_ALL);
80
- resetPage();
81
- }, children: [_jsx(SelectTrigger, { className: "w-[12.5rem]", children: _jsx(SelectValue, { placeholder: f.filters.status }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: STATUS_ALL, children: f.filters.allStatuses }), legalContractStatuses.map((value) => (_jsx(SelectItem, { value: value, children: messages.common.contractStatusLabels[value] }, value)))] })] }), renderPersonFilter ? (renderPersonFilter({
82
- personId: resolvedPersonId || null,
83
- selectedPerson: contractPersonSummary,
84
- onPersonChange: handlePersonChange,
85
- })) : (_jsx(ContractsPersonFilter, { value: resolvedPersonId || null, selectedPerson: contractPersonSummary, onChange: handlePersonChange }))] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: f.columns.number }), _jsx(TableHead, { children: f.columns.title }), _jsx(TableHead, { children: f.columns.scope }), _jsx(TableHead, { children: f.columns.status }), _jsx(TableHead, { children: f.columns.person }), _jsx(TableHead, { children: f.columns.created })] }) }), _jsx(TableBody, { children: showSkeleton ? (_jsx(ContractRowSkeleton, { rows: 6 })) : isError ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "h-24 text-center text-sm text-destructive", children: f.loadFailed }) })) : contracts.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "h-24 text-center text-sm text-muted-foreground", children: f.empty }) })) : (contracts.map((contract) => (_jsxs(TableRow, { onClick: () => onOpenContract?.(contract.id), className: cn(onOpenContract && "cursor-pointer"), children: [_jsx(TableCell, { className: "font-mono text-xs", children: contract.contractNumber ?? messages.common.noResultsDash }), _jsx(TableCell, { className: "font-medium", children: contract.title }), _jsx(TableCell, { children: _jsx(Badge, { variant: "outline", children: messages.common.contractScopeLabels[contract.scope] ?? contract.scope }) }), _jsx(TableCell, { children: _jsx(Badge, { variant: statusVariant[contract.status] ?? "secondary", children: messages.common.contractStatusLabels[contract.status] }) }), _jsx(TableCell, { children: _jsx(ContractPersonCell, { contract: contract, renderPersonCell: renderPersonCell }) }), _jsx(TableCell, { children: i18n.formatDate(contract.createdAt) })] }, contract.id)))) })] }) }), _jsx(PaginationBar, { shown: contracts.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 }), renderContractDialog?.({
85
+ }, className: "pl-9" })] }), _jsxs(Popover, { open: filtersOpen, onOpenChange: setFiltersOpen, children: [_jsx(PopoverTrigger, { render: _jsxs(Button, { variant: "outline", size: "default", children: [_jsx(ListFilter, { className: "mr-2 size-4" }), f.filters.button, activeFilterCount > 0 && (_jsx(Badge, { variant: "secondary", className: "ml-2 px-1.5", children: activeFilterCount }))] }) }), _jsx(PopoverContent, { align: "start", className: "w-[22rem] 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: "contracts-filter-scope", children: f.filters.scope }), _jsxs(Select, { value: scope, onValueChange: (value) => {
86
+ setScope(value ?? SCOPE_ALL);
87
+ resetPage();
88
+ }, children: [_jsx(SelectTrigger, { id: "contracts-filter-scope", className: "w-full", children: _jsx(SelectValue, { children: (value) => value === SCOPE_ALL
89
+ ? f.filters.allScopes
90
+ : (messages.common.contractScopeLabels[value] ?? value) }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: SCOPE_ALL, children: f.filters.allScopes }), legalContractScopes.map((value) => (_jsx(SelectItem, { value: value, children: messages.common.contractScopeLabels[value] }, value)))] })] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "contracts-filter-status", children: f.filters.status }), _jsxs(Select, { value: status, onValueChange: (value) => {
91
+ setStatus(value ?? STATUS_ALL);
92
+ resetPage();
93
+ }, children: [_jsx(SelectTrigger, { id: "contracts-filter-status", className: "w-full", children: _jsx(SelectValue, { children: (value) => value === STATUS_ALL
94
+ ? f.filters.allStatuses
95
+ : (messages.common.contractStatusLabels[value] ?? value) }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: STATUS_ALL, children: f.filters.allStatuses }), legalContractStatuses.map((value) => (_jsx(SelectItem, { value: value, children: messages.common.contractStatusLabels[value] }, value)))] })] })] }), _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { children: f.filters.person }), renderPersonFilter ? (renderPersonFilter({
96
+ personId: resolvedPersonId || null,
97
+ selectedPerson: contractPersonSummary,
98
+ onPersonChange: handlePersonChange,
99
+ })) : (_jsx(ContractsPersonFilter, { value: resolvedPersonId || null, selectedPerson: contractPersonSummary, onChange: handlePersonChange }))] })] }) })] }), hasActiveFilters && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: clearFilters, children: [_jsx(X, { className: "mr-1 size-4" }), f.filters.clear] })), renderContractDialog ? (_jsx("div", { className: "ml-auto", children: _jsxs(Button, { onClick: () => setDialogOpen(true), children: [_jsx(Plus, { className: "mr-1 size-4", "aria-hidden": "true" }), f.create] }) })) : null] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: f.columns.number }), _jsx(TableHead, { children: f.columns.title }), _jsx(TableHead, { children: f.columns.scope }), _jsx(TableHead, { children: f.columns.status }), _jsx(TableHead, { children: f.columns.person }), _jsx(TableHead, { children: f.columns.created })] }) }), _jsx(TableBody, { children: showSkeleton ? (_jsx(ContractRowSkeleton, { rows: 6 })) : isError ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "h-24 text-center text-sm text-destructive", children: f.loadFailed }) })) : contracts.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 6, className: "h-24 text-center text-sm text-muted-foreground", children: f.empty }) })) : (contracts.map((contract) => (_jsxs(TableRow, { onClick: () => onOpenContract?.(contract.id), className: cn(onOpenContract && "cursor-pointer"), children: [_jsx(TableCell, { className: "font-mono text-xs", children: contract.contractNumber ?? messages.common.noResultsDash }), _jsx(TableCell, { className: "font-medium", children: contract.title }), _jsx(TableCell, { children: _jsx(Badge, { variant: "outline", children: messages.common.contractScopeLabels[contract.scope] ?? contract.scope }) }), _jsx(TableCell, { children: _jsx(Badge, { variant: statusVariant[contract.status] ?? "secondary", children: messages.common.contractStatusLabels[contract.status] }) }), _jsx(TableCell, { children: _jsx(ContractPersonCell, { contract: contract, renderPersonCell: renderPersonCell }) }), _jsx(TableCell, { children: i18n.formatDate(contract.createdAt) })] }, contract.id)))) })] }) }), _jsx(PaginationBar, { shown: contracts.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 }), renderContractDialog?.({
86
100
  open: dialogOpen,
87
101
  onOpenChange: setDialogOpen,
88
102
  onSuccess: () => {
@@ -139,7 +153,7 @@ function ContractsPersonFilter({ value, selectedPerson, onChange, }) {
139
153
  });
140
154
  const people = peopleQuery.data?.data ?? [];
141
155
  const label = formatPersonLabel(selectedPerson, value) ?? messages.allPeople;
142
- return (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { type: "button", variant: "outline", className: "w-[14rem] justify-between gap-2 px-3" }), children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx(User, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }), _jsx("span", { className: "truncate", children: label })] }), _jsx(ChevronDown, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" })] }), _jsx(PopoverContent, { className: "w-[20rem] p-0", align: "start", children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { value: search, onValueChange: setSearch, placeholder: messages.personSearchPlaceholder }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: peopleQuery.isLoading ? messages.personSearching : messages.personEmpty }), _jsx(CommandGroup, { children: people.map((person) => {
156
+ return (_jsxs("div", { className: "flex items-center gap-1.5", children: [_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsxs(PopoverTrigger, { render: _jsx(Button, { type: "button", variant: "outline", className: "w-full flex-1 justify-between gap-2 px-3" }), children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx(User, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" }), _jsx("span", { className: "truncate", children: label })] }), _jsx(ChevronDown, { className: "size-4 shrink-0 text-muted-foreground", "aria-hidden": "true" })] }), _jsx(PopoverContent, { className: "w-[20rem] p-0", align: "start", children: _jsxs(Command, { shouldFilter: false, children: [_jsx(CommandInput, { value: search, onValueChange: setSearch, placeholder: messages.personSearchPlaceholder }), _jsxs(CommandList, { children: [_jsx(CommandEmpty, { children: peopleQuery.isLoading ? messages.personSearching : messages.personEmpty }), _jsx(CommandGroup, { children: people.map((person) => {
143
157
  const summary = personSummaryFromRecord(person);
144
158
  const name = formatPersonName(summary);
145
159
  return (_jsx(CommandItem, { value: `${name ?? ""} ${person.email ?? ""} ${person.phone ?? ""}`, onSelect: () => {
@@ -1 +1 @@
1
- {"version":3,"file":"policies-page.d.ts","sourceRoot":"","sources":["../../src/components/policies-page.tsx"],"names":[],"mappings":"AAwBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAStC,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAA;CACnE;AAED,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,YAAY,EACZ,kBAAkB,GACnB,GAAE,iBAAsB,2CAmJxB"}
1
+ {"version":3,"file":"policies-page.d.ts","sourceRoot":"","sources":["../../src/components/policies-page.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAStC,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAA;CACnE;AAED,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,YAAY,EACZ,kBAAkB,GACnB,GAAE,iBAAsB,2CAkMxB"}
@@ -1,11 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { formatMessage } from "@voyantjs/i18n";
3
3
  import { useLegalPolicies } from "@voyantjs/legal-react";
4
- import { Badge, Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
4
+ import { Badge, Button, Input, Label, Popover, PopoverContent, PopoverTrigger, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
5
5
  import { Skeleton } from "@voyantjs/ui/components/skeleton";
6
6
  import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
7
7
  import { cn } from "@voyantjs/ui/lib/utils";
8
- import { Plus, Search } from "lucide-react";
8
+ import { ListFilter, Plus, Search, X } from "lucide-react";
9
9
  import { useState } from "react";
10
10
  import { useLegalUiI18nOrDefault, useLegalUiMessagesOrDefault } from "../i18n/index.js";
11
11
  import { legalPolicyKinds } from "../i18n/messages.js";
@@ -19,6 +19,14 @@ export function PoliciesPage({ className, onOpenPolicy, renderPolicyDialog, } =
19
19
  const [kind, setKind] = useState(KIND_ALL);
20
20
  const [dialogOpen, setDialogOpen] = useState(false);
21
21
  const [pageIndex, setPageIndex] = useState(0);
22
+ const [filtersOpen, setFiltersOpen] = useState(false);
23
+ const activeFilterCount = kind !== KIND_ALL ? 1 : 0;
24
+ const hasActiveFilters = activeFilterCount > 0 || search !== "";
25
+ const clearFilters = () => {
26
+ setSearch("");
27
+ setKind(KIND_ALL);
28
+ setPageIndex(0);
29
+ };
22
30
  const { data, isPending, isFetching, isError, refetch } = useLegalPolicies({
23
31
  search,
24
32
  kind: kind === KIND_ALL ? "all" : kind,
@@ -31,13 +39,15 @@ export function PoliciesPage({ className, onOpenPolicy, renderPolicyDialog, } =
31
39
  const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE));
32
40
  const showSkeleton = isPending || (isFetching && policies.length === 0);
33
41
  const resetPage = () => setPageIndex(0);
34
- return (_jsxs("div", { className: cn("flex flex-col gap-6 p-6", className), children: [_jsxs("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 })] }), renderPolicyDialog ? (_jsxs(Button, { onClick: () => setDialogOpen(true), children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), f.create] })) : null] }), _jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsxs("div", { className: "relative min-w-[14rem] max-w-sm flex-1", children: [_jsx(Label, { htmlFor: "policies-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: "policies-search", placeholder: f.searchPlaceholder, value: search, onChange: (event) => {
42
+ return (_jsxs("div", { className: cn("flex flex-col gap-6 p-6", className), children: [_jsx("div", { children: _jsx("h1", { className: "text-2xl font-bold tracking-tight", children: f.title }) }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [_jsxs("div", { className: "relative min-w-[14rem] flex-1", children: [_jsx(Label, { htmlFor: "policies-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: "policies-search", placeholder: f.searchPlaceholder, value: search, onChange: (event) => {
35
43
  setSearch(event.target.value);
36
44
  resetPage();
37
- }, className: "pl-9" })] }), _jsxs(Select, { value: kind, onValueChange: (value) => {
38
- setKind(value ?? KIND_ALL);
39
- resetPage();
40
- }, children: [_jsx(SelectTrigger, { className: "w-[12.5rem]", children: _jsx(SelectValue, { placeholder: f.columns.kind }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: KIND_ALL, children: f.allKinds }), legalPolicyKinds.map((value) => (_jsx(SelectItem, { value: value, children: messages.common.policyKindLabels[value] }, value)))] })] })] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: f.columns.name }), _jsx(TableHead, { children: f.columns.slug }), _jsx(TableHead, { children: f.columns.kind }), _jsx(TableHead, { children: f.columns.language }), _jsx(TableHead, { children: f.columns.created })] }) }), _jsx(TableBody, { children: showSkeleton ? (_jsx(PolicyRowSkeleton, { rows: 6 })) : isError ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "h-24 text-center text-sm text-destructive", children: f.loadFailed }) })) : policies.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "h-24 text-center text-sm text-muted-foreground", children: f.empty }) })) : (policies.map((policy) => (_jsx(PolicyRow, { policy: policy, created: i18n.formatDate(policy.createdAt), kindLabel: messages.common.policyKindLabels[policy.kind] ?? policy.kind, onOpenPolicy: onOpenPolicy }, policy.id)))) })] }) }), _jsx(PaginationBar, { shown: policies.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 }), renderPolicyDialog?.({
45
+ }, className: "pl-9" })] }), _jsxs(Popover, { open: filtersOpen, onOpenChange: setFiltersOpen, children: [_jsx(PopoverTrigger, { render: _jsxs(Button, { variant: "outline", size: "default", children: [_jsx(ListFilter, { className: "mr-2 size-4" }), f.filters.button, activeFilterCount > 0 && (_jsx(Badge, { variant: "secondary", className: "ml-2 px-1.5", children: activeFilterCount }))] }) }), _jsx(PopoverContent, { align: "start", className: "w-[22rem] p-4", children: _jsx("div", { className: "flex flex-col gap-4", children: _jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Label, { htmlFor: "policies-filter-kind", children: f.filters.kind }), _jsxs(Select, { value: kind, onValueChange: (value) => {
46
+ setKind(value ?? KIND_ALL);
47
+ resetPage();
48
+ }, children: [_jsx(SelectTrigger, { id: "policies-filter-kind", className: "w-full", children: _jsx(SelectValue, { children: (value) => value === KIND_ALL
49
+ ? f.allKinds
50
+ : (messages.common.policyKindLabels[value] ?? value) }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: KIND_ALL, children: f.allKinds }), legalPolicyKinds.map((value) => (_jsx(SelectItem, { value: value, children: messages.common.policyKindLabels[value] }, value)))] })] })] }) }) })] }), hasActiveFilters && (_jsxs(Button, { variant: "ghost", size: "sm", onClick: clearFilters, children: [_jsx(X, { className: "mr-1 size-4" }), f.filters.clear] })), renderPolicyDialog ? (_jsx("div", { className: "ml-auto", children: _jsxs(Button, { onClick: () => setDialogOpen(true), children: [_jsx(Plus, { className: "mr-1 size-4", "aria-hidden": "true" }), f.create] }) })) : null] }), _jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: f.columns.name }), _jsx(TableHead, { children: f.columns.slug }), _jsx(TableHead, { children: f.columns.kind }), _jsx(TableHead, { children: f.columns.language }), _jsx(TableHead, { children: f.columns.created })] }) }), _jsx(TableBody, { children: showSkeleton ? (_jsx(PolicyRowSkeleton, { rows: 6 })) : isError ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "h-24 text-center text-sm text-destructive", children: f.loadFailed }) })) : policies.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "h-24 text-center text-sm text-muted-foreground", children: f.empty }) })) : (policies.map((policy) => (_jsx(PolicyRow, { policy: policy, created: i18n.formatDate(policy.createdAt), kindLabel: messages.common.policyKindLabels[policy.kind] ?? policy.kind, onOpenPolicy: onOpenPolicy }, policy.id)))) })] }) }), _jsx(PaginationBar, { shown: policies.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 }), renderPolicyDialog?.({
41
51
  open: dialogOpen,
42
52
  onOpenChange: setDialogOpen,
43
53
  onSuccess: () => {
@@ -6,6 +6,12 @@ export interface TemplateDialogRenderProps {
6
6
  template?: LegalContractTemplateRecord;
7
7
  onSuccess: () => void;
8
8
  }
9
+ /**
10
+ * Render-prop shape for the template version dialog. Templates' index
11
+ * page no longer surfaces an "Add version" action (versions are managed
12
+ * on the template detail page), but the type is still exported for the
13
+ * detail page to reuse.
14
+ */
9
15
  export interface TemplateVersionDialogRenderProps {
10
16
  open: boolean;
11
17
  onOpenChange: (open: boolean) => void;
@@ -16,7 +22,6 @@ export interface TemplatesPageProps {
16
22
  className?: string;
17
23
  onOpenTemplate?: (templateId: string) => void;
18
24
  renderTemplateDialog?: (props: TemplateDialogRenderProps) => ReactNode;
19
- renderTemplateVersionDialog?: (props: TemplateVersionDialogRenderProps) => ReactNode;
20
25
  }
21
- export declare function TemplatesPage({ className, onOpenTemplate, renderTemplateDialog, renderTemplateVersionDialog, }?: TemplatesPageProps): import("react/jsx-runtime").JSX.Element;
26
+ export declare function TemplatesPage({ className, onOpenTemplate, renderTemplateDialog, }?: TemplatesPageProps): import("react/jsx-runtime").JSX.Element;
22
27
  //# sourceMappingURL=templates-page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"templates-page.d.ts","sourceRoot":"","sources":["../../src/components/templates-page.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,2BAA2B,EAIjC,MAAM,uBAAuB,CAAA;AAuB9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQtC,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,CAAC,EAAE,2BAA2B,CAAA;IACtC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,gCAAgC;IAC/C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,SAAS,CAAA;IACtE,2BAA2B,CAAC,EAAE,CAAC,KAAK,EAAE,gCAAgC,KAAK,SAAS,CAAA;CACrF;AAED,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,cAAc,EACd,oBAAoB,EACpB,2BAA2B,GAC5B,GAAE,kBAAuB,2CAqJzB"}
1
+ {"version":3,"file":"templates-page.d.ts","sourceRoot":"","sources":["../../src/components/templates-page.tsx"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,2BAA2B,EAGjC,MAAM,uBAAuB,CAAA;AA0B9B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAQtC,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,CAAC,EAAE,2BAA2B,CAAA;IACtC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED;;;;;GAKG;AACH,MAAM,WAAW,gCAAgC;IAC/C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,cAAc,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,yBAAyB,KAAK,SAAS,CAAA;CACvE;AAED,wBAAgB,aAAa,CAAC,EAC5B,SAAS,EACT,cAAc,EACd,oBAAoB,GACrB,GAAE,kBAAuB,2CAyNzB"}