@voyant-travel/legal-react 0.119.2
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/LICENSE +201 -0
- package/README.md +66 -0
- package/dist/admin/contract-detail-host.d.ts +21 -0
- package/dist/admin/contract-detail-host.d.ts.map +1 -0
- package/dist/admin/contract-detail-host.js +118 -0
- package/dist/admin/contract-dialog-fields.d.ts +79 -0
- package/dist/admin/contract-dialog-fields.d.ts.map +1 -0
- package/dist/admin/contract-dialog-fields.js +178 -0
- package/dist/admin/contract-dialog.d.ts +4 -0
- package/dist/admin/contract-dialog.d.ts.map +1 -0
- package/dist/admin/contract-dialog.js +479 -0
- package/dist/admin/contract-upload-field.d.ts +12 -0
- package/dist/admin/contract-upload-field.d.ts.map +1 -0
- package/dist/admin/contract-upload-field.js +31 -0
- package/dist/admin/contracts-host.d.ts +10 -0
- package/dist/admin/contracts-host.d.ts.map +1 -0
- package/dist/admin/contracts-host.js +32 -0
- package/dist/admin/index.d.ts +78 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +193 -0
- package/dist/admin/legal-admin-shared.d.ts +31 -0
- package/dist/admin/legal-admin-shared.d.ts.map +1 -0
- package/dist/admin/legal-admin-shared.js +48 -0
- package/dist/admin/number-series-dialog.d.ts +11 -0
- package/dist/admin/number-series-dialog.d.ts.map +1 -0
- package/dist/admin/number-series-dialog.js +110 -0
- package/dist/admin/number-series-host.d.ts +7 -0
- package/dist/admin/number-series-host.d.ts.map +1 -0
- package/dist/admin/number-series-host.js +12 -0
- package/dist/admin/pages/contract-detail-page.d.ts +7 -0
- package/dist/admin/pages/contract-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/contract-detail-page.js +9 -0
- package/dist/admin/pages/policy-detail-page.d.ts +7 -0
- package/dist/admin/pages/policy-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/policy-detail-page.js +9 -0
- package/dist/admin/pages/template-detail-page.d.ts +7 -0
- package/dist/admin/pages/template-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/template-detail-page.js +9 -0
- package/dist/admin/policies-host.d.ts +9 -0
- package/dist/admin/policies-host.d.ts.map +1 -0
- package/dist/admin/policies-host.js +16 -0
- package/dist/admin/policy-assignment-dialog.d.ts +11 -0
- package/dist/admin/policy-assignment-dialog.d.ts.map +1 -0
- package/dist/admin/policy-assignment-dialog.js +234 -0
- package/dist/admin/policy-detail-host.d.ts +12 -0
- package/dist/admin/policy-detail-host.d.ts.map +1 -0
- package/dist/admin/policy-detail-host.js +17 -0
- package/dist/admin/policy-dialog.d.ts +10 -0
- package/dist/admin/policy-dialog.d.ts.map +1 -0
- package/dist/admin/policy-dialog.js +81 -0
- package/dist/admin/template-detail-host.d.ts +11 -0
- package/dist/admin/template-detail-host.d.ts.map +1 -0
- package/dist/admin/template-detail-host.js +20 -0
- package/dist/admin/template-dialog.d.ts +10 -0
- package/dist/admin/template-dialog.d.ts.map +1 -0
- package/dist/admin/template-dialog.js +117 -0
- package/dist/admin/template-version-dialog.d.ts +9 -0
- package/dist/admin/template-version-dialog.d.ts.map +1 -0
- package/dist/admin/template-version-dialog.js +67 -0
- package/dist/admin/templates-host.d.ts +9 -0
- package/dist/admin/templates-host.d.ts.map +1 -0
- package/dist/admin/templates-host.js +20 -0
- package/dist/client.d.ts +14 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +69 -0
- package/dist/components/attachment-dialog.d.ts +11 -0
- package/dist/components/attachment-dialog.d.ts.map +1 -0
- package/dist/components/attachment-dialog.js +154 -0
- package/dist/components/booking-contract-card.d.ts +37 -0
- package/dist/components/booking-contract-card.d.ts.map +1 -0
- package/dist/components/booking-contract-card.js +101 -0
- package/dist/components/contract-detail-page.d.ts +36 -0
- package/dist/components/contract-detail-page.d.ts.map +1 -0
- package/dist/components/contract-detail-page.js +156 -0
- package/dist/components/contract-dialog-fields.d.ts +114 -0
- package/dist/components/contract-dialog-fields.d.ts.map +1 -0
- package/dist/components/contract-dialog-fields.js +233 -0
- package/dist/components/contract-dialog.d.ts +5 -0
- package/dist/components/contract-dialog.d.ts.map +1 -0
- package/dist/components/contract-dialog.js +345 -0
- package/dist/components/contract-send-dialog.d.ts +37 -0
- package/dist/components/contract-send-dialog.d.ts.map +1 -0
- package/dist/components/contract-send-dialog.js +56 -0
- package/dist/components/contracts-page.d.ts +30 -0
- package/dist/components/contracts-page.d.ts.map +1 -0
- package/dist/components/contracts-page.js +192 -0
- package/dist/components/number-series-page.d.ts +13 -0
- package/dist/components/number-series-page.d.ts.map +1 -0
- package/dist/components/number-series-page.js +37 -0
- package/dist/components/policies-page.d.ts +13 -0
- package/dist/components/policies-page.d.ts.map +1 -0
- package/dist/components/policies-page.js +70 -0
- package/dist/components/policy-detail-page.d.ts +23 -0
- package/dist/components/policy-detail-page.d.ts.map +1 -0
- package/dist/components/policy-detail-page.js +171 -0
- package/dist/components/policy-rule-dialog.d.ts +12 -0
- package/dist/components/policy-rule-dialog.d.ts.map +1 -0
- package/dist/components/policy-rule-dialog.js +101 -0
- package/dist/components/policy-version-dialog.d.ts +11 -0
- package/dist/components/policy-version-dialog.d.ts.map +1 -0
- package/dist/components/policy-version-dialog.js +62 -0
- package/dist/components/signature-dialog.d.ts +9 -0
- package/dist/components/signature-dialog.d.ts.map +1 -0
- package/dist/components/signature-dialog.js +64 -0
- package/dist/components/template-detail-page.d.ts +10 -0
- package/dist/components/template-detail-page.d.ts.map +1 -0
- package/dist/components/template-detail-page.js +84 -0
- package/dist/components/templates-page.d.ts +27 -0
- package/dist/components/templates-page.d.ts.map +1 -0
- package/dist/components/templates-page.js +65 -0
- package/dist/hooks/index.d.ts +31 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +30 -0
- package/dist/hooks/use-contract-attachment-mutation.d.ts +119 -0
- package/dist/hooks/use-contract-attachment-mutation.d.ts.map +1 -0
- package/dist/hooks/use-contract-attachment-mutation.js +72 -0
- package/dist/hooks/use-contract-attachments.d.ts +23 -0
- package/dist/hooks/use-contract-attachments.d.ts.map +1 -0
- package/dist/hooks/use-contract-attachments.js +12 -0
- package/dist/hooks/use-contract-mutation.d.ts +427 -0
- package/dist/hooks/use-contract-mutation.d.ts.map +1 -0
- package/dist/hooks/use-contract-mutation.js +151 -0
- package/dist/hooks/use-contract-signature-mutation.d.ts +35 -0
- package/dist/hooks/use-contract-signature-mutation.d.ts.map +1 -0
- package/dist/hooks/use-contract-signature-mutation.js +23 -0
- package/dist/hooks/use-contract-signatures.d.ts +28 -0
- package/dist/hooks/use-contract-signatures.d.ts.map +1 -0
- package/dist/hooks/use-contract-signatures.js +12 -0
- package/dist/hooks/use-contract-template-authoring.d.ts +5 -0
- package/dist/hooks/use-contract-template-authoring.d.ts.map +1 -0
- package/dist/hooks/use-contract-template-authoring.js +7 -0
- package/dist/hooks/use-contract-template-mutation.d.ts +56 -0
- package/dist/hooks/use-contract-template-mutation.d.ts.map +1 -0
- package/dist/hooks/use-contract-template-mutation.js +39 -0
- package/dist/hooks/use-contract-template-version-mutation.d.ts +22 -0
- package/dist/hooks/use-contract-template-version-mutation.d.ts.map +1 -0
- package/dist/hooks/use-contract-template-version-mutation.js +22 -0
- package/dist/hooks/use-contract-template-versions.d.ts +15 -0
- package/dist/hooks/use-contract-template-versions.d.ts.map +1 -0
- package/dist/hooks/use-contract-template-versions.js +12 -0
- package/dist/hooks/use-contract-template.d.ts +20 -0
- package/dist/hooks/use-contract-template.d.ts.map +1 -0
- package/dist/hooks/use-contract-template.js +12 -0
- package/dist/hooks/use-contract-templates.d.ts +26 -0
- package/dist/hooks/use-contract-templates.d.ts.map +1 -0
- package/dist/hooks/use-contract-templates.js +12 -0
- package/dist/hooks/use-contract.d.ts +47 -0
- package/dist/hooks/use-contract.d.ts.map +1 -0
- package/dist/hooks/use-contract.js +12 -0
- package/dist/hooks/use-contracts.d.ts +53 -0
- package/dist/hooks/use-contracts.d.ts.map +1 -0
- package/dist/hooks/use-contracts.js +12 -0
- package/dist/hooks/use-default-contract-template.d.ts +21 -0
- package/dist/hooks/use-default-contract-template.d.ts.map +1 -0
- package/dist/hooks/use-default-contract-template.js +12 -0
- package/dist/hooks/use-evaluate-cancellation.d.ts +46 -0
- package/dist/hooks/use-evaluate-cancellation.d.ts.map +1 -0
- package/dist/hooks/use-evaluate-cancellation.js +43 -0
- package/dist/hooks/use-number-series-mutation.d.ts +58 -0
- package/dist/hooks/use-number-series-mutation.d.ts.map +1 -0
- package/dist/hooks/use-number-series-mutation.js +38 -0
- package/dist/hooks/use-number-series.d.ts +24 -0
- package/dist/hooks/use-number-series.d.ts.map +1 -0
- package/dist/hooks/use-number-series.js +12 -0
- package/dist/hooks/use-policies.d.ts +22 -0
- package/dist/hooks/use-policies.d.ts.map +1 -0
- package/dist/hooks/use-policies.js +12 -0
- package/dist/hooks/use-policy-acceptances.d.ts +26 -0
- package/dist/hooks/use-policy-acceptances.d.ts.map +1 -0
- package/dist/hooks/use-policy-acceptances.js +12 -0
- package/dist/hooks/use-policy-assignment-mutation.d.ts +63 -0
- package/dist/hooks/use-policy-assignment-mutation.d.ts.map +1 -0
- package/dist/hooks/use-policy-assignment-mutation.js +41 -0
- package/dist/hooks/use-policy-assignments.d.ts +23 -0
- package/dist/hooks/use-policy-assignments.d.ts.map +1 -0
- package/dist/hooks/use-policy-assignments.js +12 -0
- package/dist/hooks/use-policy-mutation.d.ts +44 -0
- package/dist/hooks/use-policy-mutation.d.ts.map +1 -0
- package/dist/hooks/use-policy-mutation.js +40 -0
- package/dist/hooks/use-policy-rule-mutation.d.ts +56 -0
- package/dist/hooks/use-policy-rule-mutation.d.ts.map +1 -0
- package/dist/hooks/use-policy-rule-mutation.js +39 -0
- package/dist/hooks/use-policy-rules.d.ts +22 -0
- package/dist/hooks/use-policy-rules.d.ts.map +1 -0
- package/dist/hooks/use-policy-rules.js +12 -0
- package/dist/hooks/use-policy-version-mutation.d.ts +69 -0
- package/dist/hooks/use-policy-version-mutation.d.ts.map +1 -0
- package/dist/hooks/use-policy-version-mutation.js +50 -0
- package/dist/hooks/use-policy-versions.d.ts +19 -0
- package/dist/hooks/use-policy-versions.d.ts.map +1 -0
- package/dist/hooks/use-policy-versions.js +12 -0
- package/dist/hooks/use-policy.d.ts +16 -0
- package/dist/hooks/use-policy.d.ts.map +1 -0
- package/dist/hooks/use-policy.js +12 -0
- package/dist/hooks/use-resolve-policy.d.ts +72 -0
- package/dist/hooks/use-resolve-policy.d.ts.map +1 -0
- package/dist/hooks/use-resolve-policy.js +16 -0
- package/dist/hooks/use-term.d.ts +27 -0
- package/dist/hooks/use-term.d.ts.map +1 -0
- package/dist/hooks/use-term.js +12 -0
- package/dist/hooks/use-terms.d.ts +33 -0
- package/dist/hooks/use-terms.d.ts.map +1 -0
- package/dist/hooks/use-terms.js +12 -0
- package/dist/i18n/en.d.ts +548 -0
- package/dist/i18n/en.d.ts.map +1 -0
- package/dist/i18n/en.js +547 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +4 -0
- package/dist/i18n/messages.d.ts +504 -0
- package/dist/i18n/messages.d.ts.map +1 -0
- package/dist/i18n/messages.js +29 -0
- package/dist/i18n/provider.d.ts +1118 -0
- package/dist/i18n/provider.d.ts.map +1 -0
- package/dist/i18n/provider.js +44 -0
- package/dist/i18n/ro.d.ts +548 -0
- package/dist/i18n/ro.d.ts.map +1 -0
- package/dist/i18n/ro.js +547 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/provider.d.ts +2 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +1 -0
- package/dist/query-keys.d.ts +120 -0
- package/dist/query-keys.d.ts.map +1 -0
- package/dist/query-keys.js +27 -0
- package/dist/query-options.d.ts +1929 -0
- package/dist/query-options.d.ts.map +1 -0
- package/dist/query-options.js +183 -0
- package/dist/schemas.d.ts +1547 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +269 -0
- package/dist/ui.d.ts +16 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +15 -0
- package/package.json +159 -0
- package/src/styles.css +11 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// agent-quality: file-size exception -- owner: legal-react; existing UI surface stays co-located until a dedicated split preserves behavior and tests.
|
|
3
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { formatMessage } from "@voyant-travel/i18n";
|
|
5
|
+
import { Badge, Button } from "@voyant-travel/ui/components";
|
|
6
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyant-travel/ui/components/table";
|
|
7
|
+
import { ArrowLeft, CheckCircle2, ExternalLink, FileText, Pencil, Plus, Send, Trash2, } from "lucide-react";
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
import { useLegalUiI18nOrDefault, useLegalUiMessagesOrDefault } from "../i18n/index.js";
|
|
10
|
+
import { useLegalContract, useLegalContractAttachmentMutation, useLegalContractAttachments, useLegalContractMutation, useLegalContractSignatures, useVoyantLegalContext, } from "../index.js";
|
|
11
|
+
import { AttachmentDialog } from "./attachment-dialog.js";
|
|
12
|
+
import { ContractSendDialog } from "./contract-send-dialog.js";
|
|
13
|
+
import { SignatureDialog } from "./signature-dialog.js";
|
|
14
|
+
const statusVariant = {
|
|
15
|
+
draft: "outline",
|
|
16
|
+
issued: "secondary",
|
|
17
|
+
sent: "secondary",
|
|
18
|
+
signed: "default",
|
|
19
|
+
executed: "default",
|
|
20
|
+
expired: "destructive",
|
|
21
|
+
void: "destructive",
|
|
22
|
+
};
|
|
23
|
+
const generationFailureLabelKey = {
|
|
24
|
+
render_unavailable: "templateError",
|
|
25
|
+
generator_failed: "generatorFailed",
|
|
26
|
+
};
|
|
27
|
+
function resolveContractGenerationFailure(contract) {
|
|
28
|
+
const metadata = contract.metadata;
|
|
29
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
const status = metadata.lastGenerationStatus;
|
|
33
|
+
if (typeof status !== "string" || status === "generated") {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
status,
|
|
38
|
+
error: typeof metadata.lastGenerationError === "string" && metadata.lastGenerationError.trim()
|
|
39
|
+
? metadata.lastGenerationError
|
|
40
|
+
: null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function withApiBaseUrl(baseUrl, path) {
|
|
44
|
+
const trimmedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
45
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
46
|
+
return `${trimmedBase}${normalizedPath}`;
|
|
47
|
+
}
|
|
48
|
+
function getDefaultLegalContractAttachmentDownloadHref(baseUrl, attachment) {
|
|
49
|
+
return withApiBaseUrl(baseUrl, `/v1/admin/legal/contracts/attachments/${attachment.id}/download`);
|
|
50
|
+
}
|
|
51
|
+
export function ContractDetailPage({ id, onBackToContracts, renderContractDialog, renderReference, getAttachmentDownloadHref, slots, resolveSendRecipientEmail, }) {
|
|
52
|
+
const queryClient = useQueryClient();
|
|
53
|
+
const { baseUrl } = useVoyantLegalContext();
|
|
54
|
+
const i18n = useLegalUiI18nOrDefault();
|
|
55
|
+
const messages = useLegalUiMessagesOrDefault();
|
|
56
|
+
const f = messages.contractDetailPage;
|
|
57
|
+
const { remove, issue, execute, voidContract } = useLegalContractMutation();
|
|
58
|
+
const { remove: removeAttachment } = useLegalContractAttachmentMutation();
|
|
59
|
+
const [editOpen, setEditOpen] = useState(false);
|
|
60
|
+
const [sendOpen, setSendOpen] = useState(false);
|
|
61
|
+
const [signOpen, setSignOpen] = useState(false);
|
|
62
|
+
const [attachOpen, setAttachOpen] = useState(false);
|
|
63
|
+
const [editingAttachment, setEditingAttachment] = useState();
|
|
64
|
+
const { data: contract, isPending } = useLegalContract(id);
|
|
65
|
+
const { data: signatures, refetch: refetchSignatures } = useLegalContractSignatures({
|
|
66
|
+
contractId: id,
|
|
67
|
+
});
|
|
68
|
+
const { data: attachments, refetch: refetchAttachments } = useLegalContractAttachments({
|
|
69
|
+
contractId: id,
|
|
70
|
+
});
|
|
71
|
+
if (isPending) {
|
|
72
|
+
return (_jsx("div", { className: "flex flex-col gap-6 p-6", children: _jsx("div", { className: "rounded-md border border-dashed p-8 text-center", children: _jsx("p", { className: "text-sm text-muted-foreground", children: messages.common.loading }) }) }));
|
|
73
|
+
}
|
|
74
|
+
if (!contract) {
|
|
75
|
+
return (_jsxs("div", { className: "flex flex-col items-center justify-center gap-4 py-12", children: [_jsx("p", { className: "text-muted-foreground", children: f.notFound }), onBackToContracts ? (_jsx(Button, { variant: "outline", onClick: onBackToContracts, children: f.backToContracts })) : null] }));
|
|
76
|
+
}
|
|
77
|
+
const status = contract.status;
|
|
78
|
+
const generationFailure = resolveContractGenerationFailure(contract);
|
|
79
|
+
const generationFailureMessages = messages.contractDetailPage.generationFailure;
|
|
80
|
+
const failureLabelKey = generationFailure
|
|
81
|
+
? generationFailureLabelKey[generationFailure.status]
|
|
82
|
+
: null;
|
|
83
|
+
const failureLabel = failureLabelKey
|
|
84
|
+
? generationFailureMessages[failureLabelKey]
|
|
85
|
+
: generationFailureMessages.defaultLabel;
|
|
86
|
+
const canAddSignature = status === "sent" || status === "signed";
|
|
87
|
+
const renderReferenceValue = (kind, referenceId) => renderReference?.({ kind, id: referenceId, contract }) ?? (_jsx("span", { className: "font-mono text-xs", children: referenceId }));
|
|
88
|
+
const targetValue = getContractTargetValue(contract);
|
|
89
|
+
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, generationFailure ? (_jsxs("div", { className: "mt-3 max-w-2xl space-y-1", children: [_jsx(Badge, { variant: "destructive", children: failureLabel }), _jsx("p", { className: "text-muted-foreground text-sm", children: generationFailure.error ?? generationFailureMessages.fallbackReason })] })) : 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: () => {
|
|
90
|
+
if (confirm(f.voidConfirm)) {
|
|
91
|
+
voidContract.mutate(id);
|
|
92
|
+
}
|
|
93
|
+
}, disabled: voidContract.isPending, children: f.actions.void })) : null, renderContractDialog ? (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setEditOpen(true), children: [_jsx(Pencil, { className: "mr-2 size-4", "aria-hidden": "true" }), messages.common.edit] })) : null, status === "draft" || status === "void" ? (_jsxs(Button, { variant: "destructive", size: "sm", onClick: () => {
|
|
94
|
+
if (confirm(formatMessage(f.deleteConfirm, { title: contract.title }))) {
|
|
95
|
+
remove.mutate(id, { onSuccess: () => onBackToContracts?.() });
|
|
96
|
+
}
|
|
97
|
+
}, 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, targetValue ? (_jsx(DetailRow, { label: f.fields.target, children: renderReferenceValue("target", targetValue) })) : null, contract.legacyTransactionOfferId ? (_jsx(DetailRow, { label: f.fields.legacyTransactionOffer, children: renderReferenceValue("legacyTransactionOffer", contract.legacyTransactionOfferId) })) : null, contract.legacyTransactionOrderId ? (_jsx(DetailRow, { label: f.fields.legacyTransactionOrder, children: renderReferenceValue("legacyTransactionOrder", contract.legacyTransactionOrderId) })) : null, !contract.personId &&
|
|
98
|
+
!contract.organizationId &&
|
|
99
|
+
!contract.supplierId &&
|
|
100
|
+
!contract.channelId &&
|
|
101
|
+
!contract.bookingId &&
|
|
102
|
+
!targetValue &&
|
|
103
|
+
!contract.legacyTransactionOfferId &&
|
|
104
|
+
!contract.legacyTransactionOrderId ? (_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: () => {
|
|
105
|
+
setEditingAttachment(undefined);
|
|
106
|
+
setAttachOpen(true);
|
|
107
|
+
}, 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) ??
|
|
108
|
+
getDefaultLegalContractAttachmentDownloadHref(baseUrl, attachment), onEdit: () => {
|
|
109
|
+
setEditingAttachment(attachment);
|
|
110
|
+
setAttachOpen(true);
|
|
111
|
+
}, onDelete: () => {
|
|
112
|
+
if (confirm(f.deleteAttachmentConfirm)) {
|
|
113
|
+
removeAttachment.mutate({ contractId: id, id: attachment.id });
|
|
114
|
+
}
|
|
115
|
+
} }, attachment.id))) })] }) })) }))] }), renderContractDialog?.({
|
|
116
|
+
open: editOpen,
|
|
117
|
+
onOpenChange: setEditOpen,
|
|
118
|
+
contract,
|
|
119
|
+
onSuccess: () => {
|
|
120
|
+
setEditOpen(false);
|
|
121
|
+
void queryClient.invalidateQueries();
|
|
122
|
+
},
|
|
123
|
+
}), _jsx(SignatureDialog, { open: signOpen, onOpenChange: setSignOpen, contractId: id, onSuccess: () => {
|
|
124
|
+
setSignOpen(false);
|
|
125
|
+
void refetchSignatures();
|
|
126
|
+
void queryClient.invalidateQueries();
|
|
127
|
+
} }), _jsx(AttachmentDialog, { open: attachOpen, onOpenChange: setAttachOpen, contractId: id, attachment: editingAttachment, onSuccess: () => {
|
|
128
|
+
setAttachOpen(false);
|
|
129
|
+
setEditingAttachment(undefined);
|
|
130
|
+
void refetchAttachments();
|
|
131
|
+
} }), _jsx(ContractSendDialog, { open: sendOpen, onOpenChange: setSendOpen, contract: contract, defaultRecipientEmail: resolveSendRecipientEmail?.(contract) ?? null, attachments: (attachments ?? [])
|
|
132
|
+
.filter((a) => a.kind === "document")
|
|
133
|
+
.map((a) => ({ id: a.id, name: a.name })), onSent: () => {
|
|
134
|
+
void queryClient.invalidateQueries();
|
|
135
|
+
} })] }));
|
|
136
|
+
}
|
|
137
|
+
function getContractTargetValue(contract) {
|
|
138
|
+
if (contract.targetKind === "provider_source_ref") {
|
|
139
|
+
if (!contract.targetProvider && !contract.targetSourceRef)
|
|
140
|
+
return null;
|
|
141
|
+
return `${contract.targetProvider ?? "provider"}:${contract.targetSourceRef ?? ""}`;
|
|
142
|
+
}
|
|
143
|
+
if (contract.targetKind && contract.targetId) {
|
|
144
|
+
return `${contract.targetKind}:${contract.targetId}`;
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
function DetailRow({ label, children }) {
|
|
149
|
+
return (_jsxs("div", { children: [_jsxs("span", { className: "text-muted-foreground", children: [label, ":"] }), " ", _jsx("span", { children: children })] }));
|
|
150
|
+
}
|
|
151
|
+
function ContractSection({ title, action, children, }) {
|
|
152
|
+
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 })] }));
|
|
153
|
+
}
|
|
154
|
+
function AttachmentRow({ attachment, downloadHref, onEdit, onDelete, }) {
|
|
155
|
+
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" }) })] }) })] }));
|
|
156
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { UseFormSetValue, UseFormWatch } from "react-hook-form";
|
|
3
|
+
import { z } from "zod/v4";
|
|
4
|
+
import type { useLegalUiMessagesOrDefault } from "../i18n/index.js";
|
|
5
|
+
import { type LegalContractScope } from "../i18n/messages.js";
|
|
6
|
+
import type { LegalContractRecord } from "../index.js";
|
|
7
|
+
type ContractDialogMessages = ReturnType<typeof useLegalUiMessagesOrDefault>["contractDialog"];
|
|
8
|
+
export declare const contractFormSchema: z.ZodObject<{
|
|
9
|
+
scope: z.ZodEnum<{
|
|
10
|
+
customer: "customer";
|
|
11
|
+
partner: "partner";
|
|
12
|
+
supplier: "supplier";
|
|
13
|
+
other: "other";
|
|
14
|
+
channel: "channel";
|
|
15
|
+
}>;
|
|
16
|
+
title: z.ZodString;
|
|
17
|
+
contractNumber: z.ZodOptional<z.ZodString>;
|
|
18
|
+
language: z.ZodOptional<z.ZodString>;
|
|
19
|
+
templateVersionId: z.ZodOptional<z.ZodString>;
|
|
20
|
+
seriesId: z.ZodOptional<z.ZodString>;
|
|
21
|
+
personId: z.ZodOptional<z.ZodString>;
|
|
22
|
+
organizationId: z.ZodOptional<z.ZodString>;
|
|
23
|
+
supplierId: z.ZodOptional<z.ZodString>;
|
|
24
|
+
channelId: z.ZodOptional<z.ZodString>;
|
|
25
|
+
expiresAt: z.ZodOptional<z.ZodString>;
|
|
26
|
+
templateVariables: z.ZodArray<z.ZodObject<{
|
|
27
|
+
key: z.ZodString;
|
|
28
|
+
label: z.ZodString;
|
|
29
|
+
type: z.ZodString;
|
|
30
|
+
description: z.ZodOptional<z.ZodString>;
|
|
31
|
+
example: z.ZodOptional<z.ZodString>;
|
|
32
|
+
required: z.ZodDefault<z.ZodBoolean>;
|
|
33
|
+
value: z.ZodOptional<z.ZodString>;
|
|
34
|
+
booleanValue: z.ZodDefault<z.ZodBoolean>;
|
|
35
|
+
includeBooleanValue: z.ZodDefault<z.ZodBoolean>;
|
|
36
|
+
}, z.core.$strip>>;
|
|
37
|
+
additionalVariables: z.ZodArray<z.ZodObject<{
|
|
38
|
+
key: z.ZodOptional<z.ZodString>;
|
|
39
|
+
value: z.ZodOptional<z.ZodString>;
|
|
40
|
+
}, z.core.$strip>>;
|
|
41
|
+
metadataEntries: z.ZodArray<z.ZodObject<{
|
|
42
|
+
key: z.ZodOptional<z.ZodString>;
|
|
43
|
+
value: z.ZodOptional<z.ZodString>;
|
|
44
|
+
}, z.core.$strip>>;
|
|
45
|
+
}, z.core.$strip>;
|
|
46
|
+
export type FormValues = z.input<typeof contractFormSchema>;
|
|
47
|
+
export type FormOutput = z.output<typeof contractFormSchema>;
|
|
48
|
+
export type TemplateVariableRow = FormValues["templateVariables"][number];
|
|
49
|
+
export interface LinkedRecordPickerProps {
|
|
50
|
+
value: string | undefined;
|
|
51
|
+
onChange: (id: string | undefined) => void;
|
|
52
|
+
scope: LegalContractScope;
|
|
53
|
+
}
|
|
54
|
+
export interface ContractDialogProps {
|
|
55
|
+
open: boolean;
|
|
56
|
+
onOpenChange: (open: boolean) => void;
|
|
57
|
+
contract?: LegalContractRecord;
|
|
58
|
+
onSuccess: () => void;
|
|
59
|
+
renderPersonPicker?: (props: LinkedRecordPickerProps) => ReactNode;
|
|
60
|
+
renderOrganizationPicker?: (props: LinkedRecordPickerProps) => ReactNode;
|
|
61
|
+
renderSupplierPicker?: (props: LinkedRecordPickerProps) => ReactNode;
|
|
62
|
+
renderChannelPicker?: (props: LinkedRecordPickerProps) => ReactNode;
|
|
63
|
+
}
|
|
64
|
+
export type ComboboxOption = {
|
|
65
|
+
value: string;
|
|
66
|
+
label: string;
|
|
67
|
+
description?: string;
|
|
68
|
+
};
|
|
69
|
+
export declare const languageOptions: ComboboxOption[];
|
|
70
|
+
export declare function mergeUniqueOptions(...groups: Array<ComboboxOption[] | undefined>): ComboboxOption[];
|
|
71
|
+
export declare function objectToEntries(record: Record<string, unknown> | null | undefined): {
|
|
72
|
+
key: string;
|
|
73
|
+
value: string;
|
|
74
|
+
}[];
|
|
75
|
+
export declare function parseLooseValue(value: string): any;
|
|
76
|
+
export declare function buildRecordFromPairs(entries: Array<{
|
|
77
|
+
key?: string;
|
|
78
|
+
value?: string;
|
|
79
|
+
}>): Record<string, unknown> | undefined;
|
|
80
|
+
export declare function buildVariablesPayload(rows: TemplateVariableRow[], additional: Array<{
|
|
81
|
+
key?: string;
|
|
82
|
+
value?: string;
|
|
83
|
+
}>): Record<string, unknown> | undefined;
|
|
84
|
+
export declare function clearedOptionalValue(value: string | undefined, isEditing: boolean): string | null | undefined;
|
|
85
|
+
export declare function parseBooleanFormValue(value: string | undefined): boolean | undefined;
|
|
86
|
+
export declare function inferTemplateVariableKeys(body: string | null | undefined, requiredKeys: string[]): Set<string>;
|
|
87
|
+
export declare function SearchableSelect({ value, onChange, options, placeholder, searchPlaceholder, emptyLabel, loadingLabel, loading, disabled, onSearchChange, }: {
|
|
88
|
+
value: string | null | undefined;
|
|
89
|
+
onChange: (value: string | null) => void;
|
|
90
|
+
options: ComboboxOption[];
|
|
91
|
+
placeholder: string;
|
|
92
|
+
searchPlaceholder?: string;
|
|
93
|
+
emptyLabel: string;
|
|
94
|
+
loadingLabel: string;
|
|
95
|
+
loading?: boolean;
|
|
96
|
+
disabled?: boolean;
|
|
97
|
+
onSearchChange?: (value: string) => void;
|
|
98
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
99
|
+
export declare function VariableValueField({ row, index, setValue, watch, messages, }: {
|
|
100
|
+
row: TemplateVariableRow;
|
|
101
|
+
index: number;
|
|
102
|
+
setValue: UseFormSetValue<FormValues>;
|
|
103
|
+
watch: UseFormWatch<FormValues>;
|
|
104
|
+
messages: ContractDialogMessages;
|
|
105
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
106
|
+
export declare function LinkedRecordField({ label, value, scope, onChange, renderPicker, }: {
|
|
107
|
+
label: string;
|
|
108
|
+
value: string | null;
|
|
109
|
+
scope: LegalContractScope;
|
|
110
|
+
onChange: (id: string | undefined) => void;
|
|
111
|
+
renderPicker?: (props: LinkedRecordPickerProps) => ReactNode;
|
|
112
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
113
|
+
export {};
|
|
114
|
+
//# sourceMappingURL=contract-dialog-fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-dialog-fields.d.ts","sourceRoot":"","sources":["../../src/components/contract-dialog-fields.tsx"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AACpE,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAA;AAC1B,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAA;AACnE,OAAO,EAAE,KAAK,kBAAkB,EAAuB,MAAM,qBAAqB,CAAA;AAElF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAA;AAEtD,KAAK,sBAAsB,GAAG,UAAU,CAAC,OAAO,2BAA2B,CAAC,CAAC,gBAAgB,CAAC,CAAA;AAE9F,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAqC7B,CAAA;AAEF,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC3D,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,kBAAkB,CAAC,CAAA;AAC5D,MAAM,MAAM,mBAAmB,GAAG,UAAU,CAAC,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEzE,MAAM,WAAW,uBAAuB;IACtC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;IACzB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAA;IAC1C,KAAK,EAAE,kBAAkB,CAAA;CAC1B;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,CAAC,EAAE,mBAAmB,CAAA;IAC9B,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,kBAAkB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAA;IAClE,wBAAwB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAA;IACxE,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAA;IACpE,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAA;CACpE;AAED,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,CAAA;AAID,eAAO,MAAM,eAAe,EAAE,cAAc,EAmBvC,CAAA;AAEL,wBAAgB,kBAAkB,CAChC,GAAG,MAAM,EAAE,KAAK,CAAC,cAAc,EAAE,GAAG,SAAS,CAAC,GAC7C,cAAc,EAAE,CAQlB;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,SAAS;;;IAUjF;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,OAiB5C;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,KAAK,CAAC;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,uCAUpF;AAED,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,mBAAmB,EAAE,EAC3B,UAAU,EAAE,KAAK,CAAC;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,uCAwBpD;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,EAAE,SAAS,EAAE,OAAO,6BAIjF;AAED,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,uBAG9D;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,eAUhG;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,QAAQ,EACR,OAAO,EACP,WAAW,EACX,iBAAiB,EACjB,UAAU,EACV,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,cAAc,GACf,EAAE;IACD,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;IACxC,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,WAAW,EAAE,MAAM,CAAA;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CACzC,2CA8DA;AAED,wBAAgB,kBAAkB,CAAC,EACjC,GAAG,EACH,KAAK,EACL,QAAQ,EACR,KAAK,EACL,QAAQ,GACT,EAAE;IACD,GAAG,EAAE,mBAAmB,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,eAAe,CAAC,UAAU,CAAC,CAAA;IACrC,KAAK,EAAE,YAAY,CAAC,UAAU,CAAC,CAAA;IAC/B,QAAQ,EAAE,sBAAsB,CAAA;CACjC,2CAsFA;AAED,wBAAgB,iBAAiB,CAAC,EAChC,KAAK,EACL,KAAK,EACL,KAAK,EACL,QAAQ,EACR,YAAY,GACb,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,KAAK,EAAE,kBAAkB,CAAA;IACzB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAA;IAC1C,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,SAAS,CAAA;CAC7D,kDAaA"}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Input, Label, Switch } from "@voyant-travel/ui/components";
|
|
4
|
+
import { Combobox, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, } from "@voyant-travel/ui/components/combobox";
|
|
5
|
+
import { DatePicker } from "@voyant-travel/ui/components/date-picker";
|
|
6
|
+
import { DateTimePicker } from "@voyant-travel/ui/components/date-time-picker";
|
|
7
|
+
import { languages } from "@voyant-travel/utils/languages";
|
|
8
|
+
import { useEffect, useMemo, useState } from "react";
|
|
9
|
+
import { z } from "zod/v4";
|
|
10
|
+
import { legalContractScopes } from "../i18n/messages.js";
|
|
11
|
+
export const contractFormSchema = z.object({
|
|
12
|
+
scope: z.enum(legalContractScopes),
|
|
13
|
+
title: z.string().min(1, "titleRequired"),
|
|
14
|
+
contractNumber: z.string().optional(),
|
|
15
|
+
language: z.string().min(2).max(10).optional(),
|
|
16
|
+
templateVersionId: z.string().optional(),
|
|
17
|
+
seriesId: z.string().optional(),
|
|
18
|
+
personId: z.string().optional(),
|
|
19
|
+
organizationId: z.string().optional(),
|
|
20
|
+
supplierId: z.string().optional(),
|
|
21
|
+
channelId: z.string().optional(),
|
|
22
|
+
expiresAt: z.string().optional(),
|
|
23
|
+
templateVariables: z.array(z.object({
|
|
24
|
+
key: z.string(),
|
|
25
|
+
label: z.string(),
|
|
26
|
+
type: z.string(),
|
|
27
|
+
description: z.string().optional(),
|
|
28
|
+
example: z.string().optional(),
|
|
29
|
+
required: z.boolean().default(false),
|
|
30
|
+
value: z.string().optional(),
|
|
31
|
+
booleanValue: z.boolean().default(false),
|
|
32
|
+
includeBooleanValue: z.boolean().default(false),
|
|
33
|
+
})),
|
|
34
|
+
additionalVariables: z.array(z.object({
|
|
35
|
+
key: z.string().optional(),
|
|
36
|
+
value: z.string().optional(),
|
|
37
|
+
})),
|
|
38
|
+
metadataEntries: z.array(z.object({
|
|
39
|
+
key: z.string().optional(),
|
|
40
|
+
value: z.string().optional(),
|
|
41
|
+
})),
|
|
42
|
+
});
|
|
43
|
+
const PREFERRED_LANGUAGE_ORDER = ["en", "ro", "fr", "de", "es", "it"];
|
|
44
|
+
export const languageOptions = Object.entries(languages)
|
|
45
|
+
.sort(([codeA, nameA], [codeB, nameB]) => {
|
|
46
|
+
const preferredA = PREFERRED_LANGUAGE_ORDER.indexOf(codeA);
|
|
47
|
+
const preferredB = PREFERRED_LANGUAGE_ORDER.indexOf(codeB);
|
|
48
|
+
if (preferredA !== -1 || preferredB !== -1) {
|
|
49
|
+
if (preferredA === -1)
|
|
50
|
+
return 1;
|
|
51
|
+
if (preferredB === -1)
|
|
52
|
+
return -1;
|
|
53
|
+
return preferredA - preferredB;
|
|
54
|
+
}
|
|
55
|
+
return nameA.localeCompare(nameB);
|
|
56
|
+
})
|
|
57
|
+
.map(([code, name]) => ({
|
|
58
|
+
value: code,
|
|
59
|
+
label: `${name} (${code})`,
|
|
60
|
+
description: code,
|
|
61
|
+
}));
|
|
62
|
+
export function mergeUniqueOptions(...groups) {
|
|
63
|
+
const map = new Map();
|
|
64
|
+
for (const group of groups) {
|
|
65
|
+
for (const option of group ?? []) {
|
|
66
|
+
map.set(option.value, option);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return Array.from(map.values());
|
|
70
|
+
}
|
|
71
|
+
export function objectToEntries(record) {
|
|
72
|
+
return Object.entries(record ?? {}).map(([key, value]) => ({
|
|
73
|
+
key,
|
|
74
|
+
value: typeof value === "string"
|
|
75
|
+
? value
|
|
76
|
+
: typeof value === "number" || typeof value === "boolean"
|
|
77
|
+
? String(value)
|
|
78
|
+
: JSON.stringify(value),
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
export function parseLooseValue(value) {
|
|
82
|
+
const trimmed = value.trim();
|
|
83
|
+
if (!trimmed)
|
|
84
|
+
return undefined;
|
|
85
|
+
if (trimmed === "true")
|
|
86
|
+
return true;
|
|
87
|
+
if (trimmed === "false")
|
|
88
|
+
return false;
|
|
89
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed))
|
|
90
|
+
return Number(trimmed);
|
|
91
|
+
if ((trimmed.startsWith("{") && trimmed.endsWith("}")) ||
|
|
92
|
+
(trimmed.startsWith("[") && trimmed.endsWith("]"))) {
|
|
93
|
+
try {
|
|
94
|
+
return JSON.parse(trimmed);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
return trimmed;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return trimmed;
|
|
101
|
+
}
|
|
102
|
+
export function buildRecordFromPairs(entries) {
|
|
103
|
+
const record = {};
|
|
104
|
+
for (const entry of entries) {
|
|
105
|
+
const key = entry.key?.trim();
|
|
106
|
+
if (!key)
|
|
107
|
+
continue;
|
|
108
|
+
const parsed = parseLooseValue(entry.value ?? "");
|
|
109
|
+
if (parsed === undefined)
|
|
110
|
+
continue;
|
|
111
|
+
record[key] = parsed;
|
|
112
|
+
}
|
|
113
|
+
return Object.keys(record).length ? record : undefined;
|
|
114
|
+
}
|
|
115
|
+
export function buildVariablesPayload(rows, additional) {
|
|
116
|
+
const variables = {};
|
|
117
|
+
for (const row of rows) {
|
|
118
|
+
if (row.type === "boolean") {
|
|
119
|
+
if (row.booleanValue || row.required || row.includeBooleanValue) {
|
|
120
|
+
variables[row.key] = row.booleanValue;
|
|
121
|
+
}
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const parsed = parseLooseValue(row.value ?? "");
|
|
125
|
+
if (parsed !== undefined)
|
|
126
|
+
variables[row.key] = parsed;
|
|
127
|
+
}
|
|
128
|
+
for (const entry of additional) {
|
|
129
|
+
const key = entry.key?.trim();
|
|
130
|
+
if (!key)
|
|
131
|
+
continue;
|
|
132
|
+
const parsed = parseLooseValue(entry.value ?? "");
|
|
133
|
+
if (parsed !== undefined)
|
|
134
|
+
variables[key] = parsed;
|
|
135
|
+
}
|
|
136
|
+
return Object.keys(variables).length ? variables : undefined;
|
|
137
|
+
}
|
|
138
|
+
export function clearedOptionalValue(value, isEditing) {
|
|
139
|
+
const trimmed = value?.trim();
|
|
140
|
+
if (trimmed)
|
|
141
|
+
return trimmed;
|
|
142
|
+
return isEditing ? null : undefined;
|
|
143
|
+
}
|
|
144
|
+
export function parseBooleanFormValue(value) {
|
|
145
|
+
const parsed = parseLooseValue(value ?? "");
|
|
146
|
+
return typeof parsed === "boolean" ? parsed : undefined;
|
|
147
|
+
}
|
|
148
|
+
export function inferTemplateVariableKeys(body, requiredKeys) {
|
|
149
|
+
if (!body)
|
|
150
|
+
return new Set(requiredKeys);
|
|
151
|
+
const detected = new Set(requiredKeys);
|
|
152
|
+
const variablePattern = /\{\{\s*([a-zA-Z0-9_.[\]]+)\s*\}\}/g;
|
|
153
|
+
for (const match of body.matchAll(variablePattern)) {
|
|
154
|
+
if (match[1])
|
|
155
|
+
detected.add(match[1]);
|
|
156
|
+
}
|
|
157
|
+
return detected;
|
|
158
|
+
}
|
|
159
|
+
export function SearchableSelect({ value, onChange, options, placeholder, searchPlaceholder, emptyLabel, loadingLabel, loading, disabled, onSearchChange, }) {
|
|
160
|
+
const optionMap = useMemo(() => new Map(options.map((option) => [option.value, option])), [options]);
|
|
161
|
+
const selected = value ? optionMap.get(value) : undefined;
|
|
162
|
+
const selectedLabel = selected?.label ?? "";
|
|
163
|
+
const [inputValue, setInputValue] = useState(selectedLabel);
|
|
164
|
+
useEffect(() => {
|
|
165
|
+
setInputValue(selectedLabel);
|
|
166
|
+
}, [selectedLabel]);
|
|
167
|
+
return (_jsxs(Combobox, { items: options.map((option) => option.value), value: value ?? null, inputValue: inputValue, autoHighlight: true, disabled: disabled, itemToStringValue: (id) => optionMap.get(id)?.label ?? "", onInputValueChange: (next) => {
|
|
168
|
+
setInputValue(next);
|
|
169
|
+
onSearchChange?.(next);
|
|
170
|
+
if (!next)
|
|
171
|
+
onChange(null);
|
|
172
|
+
}, onValueChange: (next) => {
|
|
173
|
+
const resolved = next ?? null;
|
|
174
|
+
onChange(resolved);
|
|
175
|
+
setInputValue(resolved ? (optionMap.get(resolved)?.label ?? "") : "");
|
|
176
|
+
}, children: [_jsx(ComboboxInput, { placeholder: searchPlaceholder ?? placeholder, showClear: !!value, disabled: disabled }), _jsxs(ComboboxContent, { children: [_jsx(ComboboxEmpty, { children: loading ? loadingLabel : emptyLabel }), _jsx(ComboboxList, { children: _jsx(ComboboxCollection, { children: (id) => {
|
|
177
|
+
const option = optionMap.get(id);
|
|
178
|
+
if (!option)
|
|
179
|
+
return null;
|
|
180
|
+
return (_jsx(ComboboxItem, { value: option.value, children: _jsxs("div", { className: "flex min-w-0 flex-col", children: [_jsx("span", { className: "truncate font-medium", children: option.label }), option.description ? (_jsx("span", { className: "truncate text-xs text-muted-foreground", children: option.description })) : null] }) }, option.value));
|
|
181
|
+
} }) })] })] }));
|
|
182
|
+
}
|
|
183
|
+
export function VariableValueField({ row, index, setValue, watch, messages, }) {
|
|
184
|
+
const valuePath = `templateVariables.${index}.value`;
|
|
185
|
+
const booleanPath = `templateVariables.${index}.booleanValue`;
|
|
186
|
+
const includeBooleanPath = `templateVariables.${index}.includeBooleanValue`;
|
|
187
|
+
if (row.type === "boolean") {
|
|
188
|
+
return (_jsx("div", { className: "flex min-h-10 items-center rounded-md border border-input px-3 text-sm", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { "aria-label": row.label, checked: watch(booleanPath), onCheckedChange: (checked) => {
|
|
189
|
+
setValue(booleanPath, checked, {
|
|
190
|
+
shouldDirty: true,
|
|
191
|
+
shouldValidate: true,
|
|
192
|
+
});
|
|
193
|
+
setValue(includeBooleanPath, true, {
|
|
194
|
+
shouldDirty: true,
|
|
195
|
+
shouldValidate: true,
|
|
196
|
+
});
|
|
197
|
+
} }), _jsx("span", { children: watch(booleanPath) ? messages.booleanYes : messages.booleanNo })] }) }));
|
|
198
|
+
}
|
|
199
|
+
if (row.type === "datetime") {
|
|
200
|
+
return (_jsx(DateTimePicker, { value: watch(valuePath) || null, onChange: (next) => setValue(valuePath, next ?? "", {
|
|
201
|
+
shouldDirty: true,
|
|
202
|
+
shouldValidate: true,
|
|
203
|
+
}), placeholder: row.example || messages.datetimeFallbackPlaceholder, className: "w-full" }));
|
|
204
|
+
}
|
|
205
|
+
if (row.type === "date") {
|
|
206
|
+
return (_jsx(DatePicker, { value: watch(valuePath) || null, onChange: (next) => setValue(valuePath, next ?? "", {
|
|
207
|
+
shouldDirty: true,
|
|
208
|
+
shouldValidate: true,
|
|
209
|
+
}), placeholder: row.example || messages.dateFallbackPlaceholder, className: "w-full" }));
|
|
210
|
+
}
|
|
211
|
+
const inputType = row.type === "email"
|
|
212
|
+
? "email"
|
|
213
|
+
: row.type === "url"
|
|
214
|
+
? "url"
|
|
215
|
+
: row.type === "phone"
|
|
216
|
+
? "tel"
|
|
217
|
+
: row.type === "number"
|
|
218
|
+
? "number"
|
|
219
|
+
: "text";
|
|
220
|
+
return (_jsx(Input, { type: inputType, step: row.type === "number" ? "1" : undefined, value: watch(valuePath) || "", onChange: (event) => setValue(valuePath, event.target.value, {
|
|
221
|
+
shouldDirty: true,
|
|
222
|
+
shouldValidate: true,
|
|
223
|
+
}), placeholder: row.example || messages.valueFallbackPlaceholder }));
|
|
224
|
+
}
|
|
225
|
+
export function LinkedRecordField({ label, value, scope, onChange, renderPicker, }) {
|
|
226
|
+
if (!renderPicker)
|
|
227
|
+
return null;
|
|
228
|
+
return (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: label }), renderPicker({
|
|
229
|
+
value: value ?? undefined,
|
|
230
|
+
onChange,
|
|
231
|
+
scope,
|
|
232
|
+
})] }));
|
|
233
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ContractDialogProps } from "./contract-dialog-fields.js";
|
|
2
|
+
export type { ContractDialogProps, LinkedRecordPickerProps } from "./contract-dialog-fields.js";
|
|
3
|
+
export { buildRecordFromPairs, buildVariablesPayload, clearedOptionalValue, } from "./contract-dialog-fields.js";
|
|
4
|
+
export declare function ContractDialog({ open, onOpenChange, contract, onSuccess, renderPersonPicker, renderOrganizationPicker, renderSupplierPicker, renderChannelPicker, }: ContractDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
5
|
+
//# sourceMappingURL=contract-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-dialog.d.ts","sourceRoot":"","sources":["../../src/components/contract-dialog.tsx"],"names":[],"mappings":"AAyCA,OAAO,EAGL,KAAK,mBAAmB,EAczB,MAAM,6BAA6B,CAAA;AAEpC,YAAY,EAAE,mBAAmB,EAAE,uBAAuB,EAAE,MAAM,6BAA6B,CAAA;AAC/F,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,6BAA6B,CAAA;AAEpC,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,SAAS,EACT,kBAAkB,EAClB,wBAAwB,EACxB,oBAAoB,EACpB,mBAAmB,GACpB,EAAE,mBAAmB,2CA2sBrB"}
|