@voyantjs/legal-ui 0.32.3 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/attachment-dialog.d.ts.map +1 -1
- package/dist/components/attachment-dialog.js +103 -20
- package/dist/components/contract-detail-page.d.ts +8 -1
- package/dist/components/contract-detail-page.d.ts.map +1 -1
- package/dist/components/contract-detail-page.js +20 -13
- package/dist/components/contracts-page.d.ts +18 -2
- package/dist/components/contracts-page.d.ts.map +1 -1
- package/dist/components/contracts-page.js +109 -4
- package/dist/components/policy-rule-dialog.d.ts.map +1 -1
- package/dist/components/policy-rule-dialog.js +5 -1
- package/dist/i18n/en.d.ts +19 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +28 -9
- package/dist/i18n/messages.d.ts +19 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/provider.d.ts +38 -0
- package/dist/i18n/provider.d.ts.map +1 -1
- package/dist/i18n/ro.d.ts +19 -0
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +28 -9
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/package.json +9 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"attachment-dialog.d.ts","sourceRoot":"","sources":["../../src/components/attachment-dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,6BAA6B,EAEnC,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"attachment-dialog.d.ts","sourceRoot":"","sources":["../../src/components/attachment-dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,6BAA6B,EAEnC,MAAM,uBAAuB,CAAA;AA6C9B,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,UAAU,CAAC,EAAE,6BAA6B,CAAA;IAC1C,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,UAAU,EACV,SAAS,GACV,EAAE,qBAAqB,2CA8OvB"}
|
|
@@ -1,32 +1,44 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useLegalContractAttachmentMutation, } from "@voyantjs/legal-react";
|
|
3
|
-
import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, } from "@voyantjs/ui/components";
|
|
3
|
+
import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
|
|
4
4
|
import { zodResolver } from "@voyantjs/ui/lib/zod-resolver";
|
|
5
|
-
import { Loader2 } from "lucide-react";
|
|
6
|
-
import { useEffect } from "react";
|
|
5
|
+
import { FileText, Loader2, Upload } from "lucide-react";
|
|
6
|
+
import { useEffect, useRef, useState } from "react";
|
|
7
7
|
import { useForm } from "react-hook-form";
|
|
8
8
|
import { z } from "zod/v4";
|
|
9
9
|
import { useLegalUiMessagesOrDefault } from "../i18n/index.js";
|
|
10
|
+
const attachmentKindValues = ["document", "appendix", "scan"];
|
|
10
11
|
function createAttachmentFormSchema(messages) {
|
|
11
12
|
return z.object({
|
|
12
13
|
name: z.string().min(1, messages.attachmentDialog.validation.nameRequired),
|
|
13
14
|
kind: z.string().min(1).optional(),
|
|
14
15
|
mimeType: z.string().optional(),
|
|
15
|
-
fileSize: z.coerce.number().int().optional(),
|
|
16
|
+
fileSize: z.preprocess((value) => (value === "" || value == null ? undefined : value), z.coerce.number().int().optional()),
|
|
16
17
|
storageKey: z.string().optional(),
|
|
17
18
|
checksum: z.string().optional(),
|
|
18
19
|
});
|
|
19
20
|
}
|
|
20
21
|
export function AttachmentDialog({ open, onOpenChange, contractId, attachment, onSuccess, }) {
|
|
21
22
|
const isEditing = !!attachment;
|
|
22
|
-
const {
|
|
23
|
+
const { update, upload, replaceFile } = useLegalContractAttachmentMutation();
|
|
23
24
|
const messages = useLegalUiMessagesOrDefault();
|
|
24
25
|
const attachmentFormSchema = createAttachmentFormSchema(messages);
|
|
26
|
+
const fileInputRef = useRef(null);
|
|
27
|
+
const [selectedFile, setSelectedFile] = useState(null);
|
|
28
|
+
const [fileError, setFileError] = useState(null);
|
|
29
|
+
const [isDragging, setIsDragging] = useState(false);
|
|
30
|
+
const kindItems = attachmentKindValues.map((value) => ({
|
|
31
|
+
value,
|
|
32
|
+
label: messages.attachmentDialog.kindLabels[value],
|
|
33
|
+
}));
|
|
34
|
+
if (attachment?.kind && !attachmentKindValues.includes(attachment.kind)) {
|
|
35
|
+
kindItems.push({ value: attachment.kind, label: attachment.kind });
|
|
36
|
+
}
|
|
25
37
|
const form = useForm({
|
|
26
38
|
resolver: zodResolver(attachmentFormSchema),
|
|
27
39
|
defaultValues: {
|
|
28
40
|
name: "",
|
|
29
|
-
kind: "
|
|
41
|
+
kind: "document", // i18n-literal-ok domain attachment kind
|
|
30
42
|
mimeType: "",
|
|
31
43
|
fileSize: undefined,
|
|
32
44
|
storageKey: "",
|
|
@@ -43,29 +55,100 @@ export function AttachmentDialog({ open, onOpenChange, contractId, attachment, o
|
|
|
43
55
|
storageKey: attachment.storageKey ?? "",
|
|
44
56
|
checksum: attachment.checksum ?? "",
|
|
45
57
|
});
|
|
58
|
+
setSelectedFile(null);
|
|
59
|
+
setFileError(null);
|
|
46
60
|
}
|
|
47
61
|
else if (open) {
|
|
48
62
|
form.reset();
|
|
63
|
+
setSelectedFile(null);
|
|
64
|
+
setFileError(null);
|
|
49
65
|
}
|
|
66
|
+
setIsDragging(false);
|
|
50
67
|
}, [open, attachment, form]);
|
|
68
|
+
const applySelectedFile = (file) => {
|
|
69
|
+
setSelectedFile(file);
|
|
70
|
+
setFileError(null);
|
|
71
|
+
setIsDragging(false);
|
|
72
|
+
const currentName = form.getValues("name");
|
|
73
|
+
if (!currentName || currentName === attachment?.name) {
|
|
74
|
+
form.setValue("name", file.name, { shouldDirty: true, shouldValidate: true });
|
|
75
|
+
}
|
|
76
|
+
form.setValue("mimeType", file.type || "", { shouldDirty: true, shouldValidate: true });
|
|
77
|
+
form.setValue("fileSize", file.size, { shouldDirty: true, shouldValidate: true });
|
|
78
|
+
};
|
|
79
|
+
const onFileChange = (event) => {
|
|
80
|
+
const file = event.currentTarget.files?.[0];
|
|
81
|
+
if (!file)
|
|
82
|
+
return;
|
|
83
|
+
applySelectedFile(file);
|
|
84
|
+
event.currentTarget.value = "";
|
|
85
|
+
};
|
|
86
|
+
const onFileDrop = (event) => {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
event.stopPropagation();
|
|
89
|
+
const file = event.dataTransfer.files?.[0];
|
|
90
|
+
if (file)
|
|
91
|
+
applySelectedFile(file);
|
|
92
|
+
};
|
|
93
|
+
const onFileDragOver = (event) => {
|
|
94
|
+
event.preventDefault();
|
|
95
|
+
event.stopPropagation();
|
|
96
|
+
setIsDragging(true);
|
|
97
|
+
};
|
|
98
|
+
const onFileDragLeave = (event) => {
|
|
99
|
+
event.preventDefault();
|
|
100
|
+
event.stopPropagation();
|
|
101
|
+
setIsDragging(false);
|
|
102
|
+
};
|
|
51
103
|
const onSubmit = async (values) => {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
104
|
+
const uploadInput = selectedFile
|
|
105
|
+
? {
|
|
106
|
+
file: selectedFile,
|
|
107
|
+
name: values.name,
|
|
108
|
+
kind: values.kind || "document", // i18n-literal-ok domain attachment kind
|
|
109
|
+
}
|
|
110
|
+
: null;
|
|
111
|
+
if (uploadInput && isEditing && attachment) {
|
|
112
|
+
await replaceFile.mutateAsync({ contractId, id: attachment.id, input: uploadInput });
|
|
113
|
+
onSuccess();
|
|
114
|
+
return;
|
|
62
115
|
}
|
|
63
|
-
|
|
64
|
-
await
|
|
116
|
+
if (uploadInput) {
|
|
117
|
+
await upload.mutateAsync({ contractId, input: uploadInput });
|
|
118
|
+
onSuccess();
|
|
119
|
+
return;
|
|
65
120
|
}
|
|
121
|
+
if (!isEditing) {
|
|
122
|
+
setFileError(messages.attachmentDialog.validation.fileRequired);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await update.mutateAsync({
|
|
126
|
+
contractId,
|
|
127
|
+
id: attachment.id,
|
|
128
|
+
input: {
|
|
129
|
+
name: values.name,
|
|
130
|
+
kind: values.kind || "document", // i18n-literal-ok domain attachment kind
|
|
131
|
+
mimeType: values.mimeType || undefined,
|
|
132
|
+
fileSize: values.fileSize,
|
|
133
|
+
storageKey: values.storageKey || undefined,
|
|
134
|
+
checksum: values.checksum || undefined,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
66
137
|
onSuccess();
|
|
67
138
|
};
|
|
139
|
+
const isSubmitting = form.formState.isSubmitting || update.isPending || upload.isPending || replaceFile.isPending;
|
|
140
|
+
const submitError = update.error ?? upload.error ?? replaceFile.error ?? null;
|
|
68
141
|
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing
|
|
69
142
|
? messages.attachmentDialog.titles.edit
|
|
70
|
-
: messages.attachmentDialog.titles.create }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [
|
|
143
|
+
: messages.attachmentDialog.titles.create }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsx("input", { type: "hidden", ...form.register("mimeType") }), _jsx("input", { type: "hidden", ...form.register("fileSize") }), _jsx("input", { type: "hidden", ...form.register("storageKey") }), _jsx("input", { type: "hidden", ...form.register("checksum") }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.attachmentDialog.fields.file }), _jsx("button", { type: "button", onClick: () => fileInputRef.current?.click(), onDrop: onFileDrop, onDragOver: onFileDragOver, onDragLeave: onFileDragLeave, "data-dragging": isDragging, className: "flex min-h-32 flex-col items-center justify-center gap-2 rounded-md border border-dashed px-4 py-6 text-center transition-colors hover:border-foreground/30 hover:bg-muted/30 data-[dragging=true]:border-primary data-[dragging=true]:bg-primary/5", children: selectedFile ? (_jsxs(_Fragment, { children: [_jsx(FileText, { className: "size-6 text-muted-foreground", "aria-hidden": "true" }), _jsx("span", { className: "max-w-full truncate font-medium text-sm", children: selectedFile.name }), _jsxs("span", { className: "text-muted-foreground text-xs", children: [formatUploadSize(selectedFile.size), selectedFile.type ? ` - ${selectedFile.type}` : ""] })] })) : (_jsxs(_Fragment, { children: [_jsx(Upload, { className: "size-6 text-muted-foreground", "aria-hidden": "true" }), _jsx("span", { className: "text-muted-foreground text-sm", children: messages.attachmentDialog.placeholders.file })] })) }), _jsx("input", { ref: fileInputRef, type: "file", className: "hidden", onChange: onFileChange }), fileError ? _jsx("p", { className: "text-xs text-destructive", children: fileError }) : null] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.attachmentDialog.fields.name }), _jsx(Input, { ...form.register("name"), placeholder: messages.attachmentDialog.placeholders.name }), form.formState.errors.name && (_jsx("p", { className: "text-xs text-destructive", children: form.formState.errors.name.message }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.attachmentDialog.fields.kind }), _jsxs(Select, { items: kindItems, value: form.watch("kind"), onValueChange: (value) => form.setValue("kind", value ?? undefined, {
|
|
144
|
+
shouldDirty: true,
|
|
145
|
+
shouldValidate: true,
|
|
146
|
+
}), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: kindItems.map((item) => (_jsx(SelectItem, { value: item.value, children: item.label }, item.value))) })] })] })] }), submitError ? _jsx("p", { className: "text-xs text-destructive", children: submitError.message }) : null] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: messages.common.cancel }), _jsxs(Button, { type: "submit", disabled: isSubmitting, children: [isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing ? messages.common.saveChanges : messages.attachmentDialog.actions.create] })] })] })] }) }));
|
|
147
|
+
}
|
|
148
|
+
function formatUploadSize(bytes) {
|
|
149
|
+
if (bytes < 1024)
|
|
150
|
+
return `${bytes} B`;
|
|
151
|
+
if (bytes < 1024 * 1024)
|
|
152
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
153
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
71
154
|
}
|
|
@@ -4,13 +4,20 @@ export interface ContractDetailPageProps {
|
|
|
4
4
|
id: string;
|
|
5
5
|
onBackToContracts?: () => void;
|
|
6
6
|
renderContractDialog?: (props: ContractDetailDialogRenderProps) => ReactNode;
|
|
7
|
+
renderReference?: (props: ContractReferenceRenderProps) => ReactNode;
|
|
7
8
|
getAttachmentDownloadHref?: (attachment: LegalContractAttachmentRecord) => string;
|
|
8
9
|
}
|
|
10
|
+
export type ContractReferenceKind = "person" | "organization" | "supplier" | "channel" | "booking" | "order";
|
|
11
|
+
export interface ContractReferenceRenderProps {
|
|
12
|
+
kind: ContractReferenceKind;
|
|
13
|
+
id: string;
|
|
14
|
+
contract: LegalContractRecord;
|
|
15
|
+
}
|
|
9
16
|
export interface ContractDetailDialogRenderProps {
|
|
10
17
|
open: boolean;
|
|
11
18
|
onOpenChange: (open: boolean) => void;
|
|
12
19
|
contract: LegalContractRecord;
|
|
13
20
|
onSuccess: () => void;
|
|
14
21
|
}
|
|
15
|
-
export declare function ContractDetailPage({ id, onBackToContracts, renderContractDialog, getAttachmentDownloadHref, }: ContractDetailPageProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
export declare function ContractDetailPage({ id, onBackToContracts, renderContractDialog, renderReference, getAttachmentDownloadHref, }: ContractDetailPageProps): import("react/jsx-runtime").JSX.Element;
|
|
16
23
|
//# 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;
|
|
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;AAY9B,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;CAClF;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,GAC1B,EAAE,uBAAuB,2CAwVzB"}
|
|
@@ -2,8 +2,9 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useQueryClient } from "@tanstack/react-query";
|
|
3
3
|
import { formatMessage } from "@voyantjs/i18n";
|
|
4
4
|
import { useLegalContract, useLegalContractAttachmentMutation, useLegalContractAttachments, useLegalContractMutation, useLegalContractSignatures, } from "@voyantjs/legal-react";
|
|
5
|
-
import { Badge, Button
|
|
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";
|
|
7
8
|
import { ArrowLeft, ExternalLink, FileText, Pencil, Plus, Trash2 } from "lucide-react";
|
|
8
9
|
import { useState } from "react";
|
|
9
10
|
import { useLegalUiI18nOrDefault, useLegalUiMessagesOrDefault } from "../i18n/index.js";
|
|
@@ -18,7 +19,7 @@ const statusVariant = {
|
|
|
18
19
|
expired: "destructive",
|
|
19
20
|
void: "destructive",
|
|
20
21
|
};
|
|
21
|
-
export function ContractDetailPage({ id, onBackToContracts, renderContractDialog, getAttachmentDownloadHref, }) {
|
|
22
|
+
export function ContractDetailPage({ id, onBackToContracts, renderContractDialog, renderReference, getAttachmentDownloadHref, }) {
|
|
22
23
|
const queryClient = useQueryClient();
|
|
23
24
|
const i18n = useLegalUiI18nOrDefault();
|
|
24
25
|
const messages = useLegalUiMessagesOrDefault();
|
|
@@ -44,7 +45,8 @@ export function ContractDetailPage({ id, onBackToContracts, renderContractDialog
|
|
|
44
45
|
}
|
|
45
46
|
const status = contract.status;
|
|
46
47
|
const canAddSignature = status === "issued" || status === "sent" || status === "signed";
|
|
47
|
-
|
|
48
|
+
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 !== "void" ? (_jsx(Button, { size: "sm", variant: "destructive", onClick: () => {
|
|
48
50
|
if (confirm(f.voidConfirm)) {
|
|
49
51
|
voidContract.mutate(id);
|
|
50
52
|
}
|
|
@@ -52,20 +54,22 @@ export function ContractDetailPage({ id, onBackToContracts, renderContractDialog
|
|
|
52
54
|
if (confirm(formatMessage(f.deleteConfirm, { title: contract.title }))) {
|
|
53
55
|
remove.mutate(id, { onSuccess: () => onBackToContracts?.() });
|
|
54
56
|
}
|
|
55
|
-
}, disabled: remove.isPending, children: [_jsx(Trash2, { className: "mr-2 size-4", "aria-hidden": "true" }), messages.common.delete] })) : null] })] }), _jsxs("
|
|
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: _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: _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 &&
|
|
56
58
|
!contract.organizationId &&
|
|
57
59
|
!contract.supplierId &&
|
|
58
|
-
!contract.channelId
|
|
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: _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: _jsx(ContractSection, { title: f.sections.documents, action: _jsxs(Button, { size: "sm", onClick: () => {
|
|
59
63
|
setEditingAttachment(undefined);
|
|
60
64
|
setAttachOpen(true);
|
|
61
|
-
}, children: [_jsx(Plus, { className: "mr-2 size-4", "aria-hidden": "true" }), f.actions.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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?.({
|
|
69
73
|
open: editOpen,
|
|
70
74
|
onOpenChange: setEditOpen,
|
|
71
75
|
contract,
|
|
@@ -86,6 +90,9 @@ export function ContractDetailPage({ id, onBackToContracts, renderContractDialog
|
|
|
86
90
|
function DetailRow({ label, children }) {
|
|
87
91
|
return (_jsxs("div", { children: [_jsxs("span", { className: "text-muted-foreground", children: [label, ":"] }), " ", _jsx("span", { children: children })] }));
|
|
88
92
|
}
|
|
93
|
+
function ContractSection({ title, action, children, }) {
|
|
94
|
+
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
|
+
}
|
|
89
96
|
function AttachmentRow({ attachment, downloadHref, onEdit, onDelete, }) {
|
|
90
97
|
const messages = useLegalUiMessagesOrDefault();
|
|
91
98
|
const f = messages.contractDetailPage;
|
|
@@ -4,11 +4,27 @@ export interface ContractDialogRenderProps {
|
|
|
4
4
|
onOpenChange: (open: boolean) => void;
|
|
5
5
|
onSuccess: () => void;
|
|
6
6
|
}
|
|
7
|
+
export interface ContractPersonSummary {
|
|
8
|
+
id: string;
|
|
9
|
+
firstName?: string | null;
|
|
10
|
+
lastName?: string | null;
|
|
11
|
+
email?: string | null;
|
|
12
|
+
phone?: string | null;
|
|
13
|
+
}
|
|
14
|
+
export interface ContractPersonFilterRenderProps {
|
|
15
|
+
personId: string | null;
|
|
16
|
+
selectedPerson: ContractPersonSummary | null;
|
|
17
|
+
onPersonChange: (personId: string | null, person?: ContractPersonSummary | null) => void;
|
|
18
|
+
}
|
|
7
19
|
export interface ContractsPageProps {
|
|
8
20
|
className?: string;
|
|
21
|
+
defaultPersonId?: string | null;
|
|
22
|
+
personId?: string | null;
|
|
23
|
+
onPersonIdChange?: (personId: string | null) => void;
|
|
9
24
|
onOpenContract?: (contractId: string) => void;
|
|
10
25
|
renderContractDialog?: (props: ContractDialogRenderProps) => ReactNode;
|
|
11
|
-
renderPersonCell?: (personId: string | null) => ReactNode;
|
|
26
|
+
renderPersonCell?: (personId: string | null, person: ContractPersonSummary | null) => ReactNode;
|
|
27
|
+
renderPersonFilter?: (props: ContractPersonFilterRenderProps) => ReactNode;
|
|
12
28
|
}
|
|
13
|
-
export declare function ContractsPage({ className, onOpenContract, renderContractDialog, renderPersonCell, }?: ContractsPageProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
export declare function ContractsPage({ className, defaultPersonId, personId, onPersonIdChange, onOpenContract, renderContractDialog, renderPersonCell, renderPersonFilter, }?: ContractsPageProps): import("react/jsx-runtime").JSX.Element;
|
|
14
30
|
//# sourceMappingURL=contracts-page.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contracts-page.d.ts","sourceRoot":"","sources":["../../src/components/contracts-page.tsx"],"names":[],"mappings":"
|
|
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,12 +1,15 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { usePeople } from "@voyantjs/crm-react";
|
|
2
3
|
import { formatMessage } from "@voyantjs/i18n";
|
|
3
4
|
import { useLegalContracts } from "@voyantjs/legal-react";
|
|
4
5
|
import { Badge, Button, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
|
|
6
|
+
import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, } from "@voyantjs/ui/components/command";
|
|
7
|
+
import { Popover, PopoverContent, PopoverTrigger } from "@voyantjs/ui/components/popover";
|
|
5
8
|
import { Skeleton } from "@voyantjs/ui/components/skeleton";
|
|
6
9
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
|
|
7
10
|
import { cn } from "@voyantjs/ui/lib/utils";
|
|
8
|
-
import { Plus, Search } from "lucide-react";
|
|
9
|
-
import { useState } from "react";
|
|
11
|
+
import { ChevronDown, Plus, Search, User, X } from "lucide-react";
|
|
12
|
+
import { useMemo, useState } from "react";
|
|
10
13
|
import { useLegalUiI18nOrDefault, useLegalUiMessagesOrDefault } from "../i18n/index.js";
|
|
11
14
|
import { legalContractScopes, legalContractStatuses } from "../i18n/messages.js";
|
|
12
15
|
const PAGE_SIZE = 25;
|
|
@@ -21,19 +24,23 @@ const statusVariant = {
|
|
|
21
24
|
expired: "destructive",
|
|
22
25
|
void: "destructive",
|
|
23
26
|
};
|
|
24
|
-
export function ContractsPage({ className, onOpenContract, renderContractDialog, renderPersonCell, } = {}) {
|
|
27
|
+
export function ContractsPage({ className, defaultPersonId, personId, onPersonIdChange, onOpenContract, renderContractDialog, renderPersonCell, renderPersonFilter, } = {}) {
|
|
25
28
|
const i18n = useLegalUiI18nOrDefault();
|
|
26
29
|
const messages = useLegalUiMessagesOrDefault();
|
|
27
30
|
const f = messages.contractsPage;
|
|
28
31
|
const [search, setSearch] = useState("");
|
|
29
32
|
const [scope, setScope] = useState(SCOPE_ALL);
|
|
30
33
|
const [status, setStatus] = useState(STATUS_ALL);
|
|
34
|
+
const [internalPersonId, setInternalPersonId] = useState(() => getInitialPersonId(defaultPersonId));
|
|
35
|
+
const [selectedPerson, setSelectedPerson] = useState(null);
|
|
31
36
|
const [dialogOpen, setDialogOpen] = useState(false);
|
|
32
37
|
const [pageIndex, setPageIndex] = useState(0);
|
|
38
|
+
const resolvedPersonId = personId ?? internalPersonId;
|
|
33
39
|
const { data, isPending, isFetching, isError, refetch } = useLegalContracts({
|
|
34
40
|
search,
|
|
35
41
|
scope: scope === SCOPE_ALL ? "all" : scope,
|
|
36
42
|
status: status === STATUS_ALL ? "all" : status,
|
|
43
|
+
personId: resolvedPersonId || undefined,
|
|
37
44
|
limit: PAGE_SIZE,
|
|
38
45
|
offset: pageIndex * PAGE_SIZE,
|
|
39
46
|
});
|
|
@@ -43,6 +50,25 @@ export function ContractsPage({ className, onOpenContract, renderContractDialog,
|
|
|
43
50
|
const pageCount = Math.max(1, Math.ceil(total / PAGE_SIZE));
|
|
44
51
|
const showSkeleton = isPending || (isFetching && contracts.length === 0);
|
|
45
52
|
const resetPage = () => setPageIndex(0);
|
|
53
|
+
const contractPersonSummary = useMemo(() => {
|
|
54
|
+
if (!resolvedPersonId)
|
|
55
|
+
return null;
|
|
56
|
+
if (selectedPerson?.id === resolvedPersonId)
|
|
57
|
+
return selectedPerson;
|
|
58
|
+
for (const contract of contracts) {
|
|
59
|
+
const person = getContractPersonSummary(contract);
|
|
60
|
+
if (person?.id === resolvedPersonId)
|
|
61
|
+
return person;
|
|
62
|
+
}
|
|
63
|
+
return { id: resolvedPersonId };
|
|
64
|
+
}, [contracts, resolvedPersonId, selectedPerson]);
|
|
65
|
+
const handlePersonChange = (nextPersonId, person) => {
|
|
66
|
+
if (personId === undefined)
|
|
67
|
+
setInternalPersonId(nextPersonId ?? "");
|
|
68
|
+
setSelectedPerson(person ?? null);
|
|
69
|
+
onPersonIdChange?.(nextPersonId);
|
|
70
|
+
resetPage();
|
|
71
|
+
};
|
|
46
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) => {
|
|
47
73
|
setSearch(event.target.value);
|
|
48
74
|
resetPage();
|
|
@@ -52,7 +78,11 @@ export function ContractsPage({ className, onOpenContract, renderContractDialog,
|
|
|
52
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) => {
|
|
53
79
|
setStatus(value ?? STATUS_ALL);
|
|
54
80
|
resetPage();
|
|
55
|
-
}, 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)))] })] })
|
|
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?.({
|
|
56
86
|
open: dialogOpen,
|
|
57
87
|
onOpenChange: setDialogOpen,
|
|
58
88
|
onSuccess: () => {
|
|
@@ -62,6 +92,81 @@ export function ContractsPage({ className, onOpenContract, renderContractDialog,
|
|
|
62
92
|
},
|
|
63
93
|
})] }));
|
|
64
94
|
}
|
|
95
|
+
function getInitialPersonId(defaultPersonId) {
|
|
96
|
+
if (defaultPersonId !== undefined)
|
|
97
|
+
return defaultPersonId ?? "";
|
|
98
|
+
if (typeof window === "undefined")
|
|
99
|
+
return "";
|
|
100
|
+
return new URLSearchParams(window.location.search).get("personId") ?? "";
|
|
101
|
+
}
|
|
102
|
+
function personSummaryFromRecord(person) {
|
|
103
|
+
return {
|
|
104
|
+
id: person.id,
|
|
105
|
+
firstName: person.firstName,
|
|
106
|
+
lastName: person.lastName,
|
|
107
|
+
email: person.email,
|
|
108
|
+
phone: person.phone,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function getContractPersonSummary(contract) {
|
|
112
|
+
if (!contract.personId)
|
|
113
|
+
return null;
|
|
114
|
+
return {
|
|
115
|
+
id: contract.personId,
|
|
116
|
+
firstName: contract.personFirstName,
|
|
117
|
+
lastName: contract.personLastName,
|
|
118
|
+
email: contract.personEmail,
|
|
119
|
+
phone: contract.personPhone,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function formatPersonName(person) {
|
|
123
|
+
if (!person)
|
|
124
|
+
return null;
|
|
125
|
+
const name = [person.firstName, person.lastName].filter(Boolean).join(" ").trim();
|
|
126
|
+
return name || null;
|
|
127
|
+
}
|
|
128
|
+
function formatPersonLabel(person, fallback) {
|
|
129
|
+
return formatPersonName(person) ?? person?.email ?? person?.phone ?? fallback ?? null;
|
|
130
|
+
}
|
|
131
|
+
function ContractsPersonFilter({ value, selectedPerson, onChange, }) {
|
|
132
|
+
const messages = useLegalUiMessagesOrDefault().contractsPage.filters;
|
|
133
|
+
const [open, setOpen] = useState(false);
|
|
134
|
+
const [search, setSearch] = useState("");
|
|
135
|
+
const peopleQuery = usePeople({
|
|
136
|
+
search: search.trim() || undefined,
|
|
137
|
+
limit: 25,
|
|
138
|
+
enabled: open,
|
|
139
|
+
});
|
|
140
|
+
const people = peopleQuery.data?.data ?? [];
|
|
141
|
+
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) => {
|
|
143
|
+
const summary = personSummaryFromRecord(person);
|
|
144
|
+
const name = formatPersonName(summary);
|
|
145
|
+
return (_jsx(CommandItem, { value: `${name ?? ""} ${person.email ?? ""} ${person.phone ?? ""}`, onSelect: () => {
|
|
146
|
+
onChange(person.id, summary);
|
|
147
|
+
setOpen(false);
|
|
148
|
+
setSearch("");
|
|
149
|
+
}, children: _jsxs("div", { className: "flex min-w-0 flex-1 flex-col leading-tight", children: [_jsx("span", { className: "truncate font-medium text-sm", children: name ?? person.id }), (person.email || person.phone) && (_jsx("span", { className: "truncate text-muted-foreground text-xs", children: person.email ?? person.phone }))] }) }, person.id));
|
|
150
|
+
}) }), value ? (_jsxs(_Fragment, { children: [_jsx(CommandSeparator, {}), _jsx(CommandGroup, { children: _jsxs(CommandItem, { value: "__clear_person_filter", onSelect: () => {
|
|
151
|
+
onChange(null, null);
|
|
152
|
+
setOpen(false);
|
|
153
|
+
setSearch("");
|
|
154
|
+
}, children: [_jsx(X, { className: "mr-2 size-4", "aria-hidden": "true" }), messages.clearPerson] }) })] })) : null] })] }) })] }), value ? (_jsx(Button, { type: "button", variant: "outline", size: "icon", "aria-label": messages.clearPerson, onClick: () => onChange(null, null), children: _jsx(X, { className: "size-4", "aria-hidden": "true" }) })) : null] }));
|
|
155
|
+
}
|
|
156
|
+
function ContractPersonCell({ contract, renderPersonCell, }) {
|
|
157
|
+
const messages = useLegalUiMessagesOrDefault();
|
|
158
|
+
const person = getContractPersonSummary(contract);
|
|
159
|
+
if (renderPersonCell)
|
|
160
|
+
return renderPersonCell(contract.personId, person);
|
|
161
|
+
if (!contract.personId) {
|
|
162
|
+
return _jsx("span", { className: "text-muted-foreground text-xs", children: messages.common.noResultsDash });
|
|
163
|
+
}
|
|
164
|
+
const name = formatPersonName(person);
|
|
165
|
+
if (!name && !person?.email && !person?.phone) {
|
|
166
|
+
return _jsx("span", { className: "font-mono text-xs", children: contract.personId });
|
|
167
|
+
}
|
|
168
|
+
return (_jsxs("div", { className: "flex min-w-0 flex-col leading-tight", children: [_jsx("span", { className: "truncate font-medium text-sm", children: name ?? contract.personId }), (person?.email || person?.phone) && (_jsx("span", { className: "truncate text-muted-foreground text-xs", children: person.email ?? person.phone }))] }));
|
|
169
|
+
}
|
|
65
170
|
function PaginationBar({ shown, total, page, pageCount, onPrevious, onNext, canGoBack, canGoForward, }) {
|
|
66
171
|
const messages = useLegalUiMessagesOrDefault();
|
|
67
172
|
const f = messages.contractsPage.pagination;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"policy-rule-dialog.d.ts","sourceRoot":"","sources":["../../src/components/policy-rule-dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAA8B,MAAM,uBAAuB,CAAA;
|
|
1
|
+
{"version":3,"file":"policy-rule-dialog.d.ts","sourceRoot":"","sources":["../../src/components/policy-rule-dialog.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAA8B,MAAM,uBAAuB,CAAA;AAsD9F,MAAM,MAAM,QAAQ,GAAG,qBAAqB,CAAA;AAE5C,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,IAAI,EACJ,SAAS,GACV,EAAE,qBAAqB,2CAwMvB"}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useLegalPolicyRuleMutation } from "@voyantjs/legal-react";
|
|
3
3
|
import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyantjs/ui/components";
|
|
4
4
|
import { CurrencyCombobox } from "@voyantjs/ui/components/currency-combobox";
|
|
5
|
+
import { CurrencyInput } from "@voyantjs/ui/components/currency-input";
|
|
5
6
|
import { zodResolver } from "@voyantjs/ui/lib/zod-resolver";
|
|
6
7
|
import { Loader2 } from "lucide-react";
|
|
7
8
|
import { useEffect } from "react";
|
|
@@ -93,5 +94,8 @@ export function PolicyRuleDialog({ open, onOpenChange, versionId, rule, onSucces
|
|
|
93
94
|
: messages.policyRuleDialog.titles.create }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.policyRuleDialog.fields.ruleType }), _jsxs(Select, { items: ruleTypeItems, value: form.watch("ruleType"), onValueChange: (v) => form.setValue("ruleType", v, { shouldValidate: true }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: ruleTypeItems.map((t) => (_jsx(SelectItem, { value: t.value, children: t.label }, t.value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.policyRuleDialog.fields.sortOrder }), _jsx(Input, { ...form.register("sortOrder"), type: "number" })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.policyRuleDialog.fields.label }), _jsx(Input, { ...form.register("label"), placeholder: messages.policyRuleDialog.placeholders.label })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.policyRuleDialog.fields.daysBeforeDeparture }), _jsx(Input, { ...form.register("daysBeforeDeparture"), type: "number", placeholder: messages.policyRuleDialog.placeholders.daysBeforeDeparture })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.policyRuleDialog.fields.refundPercent }), _jsx(Input, { ...form.register("refundPercent"), type: "number", placeholder: messages.policyRuleDialog.placeholders.refundPercent })] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.policyRuleDialog.fields.refundType }), _jsxs(Select, { items: refundTypeItems, value: form.watch("refundType") ?? "", onValueChange: (v) => form.setValue("refundType", (v || undefined)), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, { placeholder: messages.common.selectPlaceholder }) }), _jsx(SelectContent, { children: refundTypeItems.map((t) => (_jsx(SelectItem, { value: t.value, children: t.label }, t.value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.policyRuleDialog.fields.currency }), _jsx(CurrencyCombobox, { value: form.watch("currency") || null, onChange: (next) => form.setValue("currency", next ?? "EUR" /* i18n-literal-ok domain default currency */, {
|
|
94
95
|
shouldValidate: true,
|
|
95
96
|
shouldDirty: true,
|
|
96
|
-
}) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.policyRuleDialog.fields.flatAmountCents }), _jsx(
|
|
97
|
+
}) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: messages.policyRuleDialog.fields.flatAmountCents }), _jsx(CurrencyInput, { value: form.watch("flatAmountCents"), onChange: (next) => form.setValue("flatAmountCents", next ?? undefined, {
|
|
98
|
+
shouldDirty: true,
|
|
99
|
+
shouldValidate: true,
|
|
100
|
+
}), currency: form.watch("currency"), placeholder: messages.policyRuleDialog.placeholders.flatAmountCents })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: messages.common.cancel }), _jsxs(Button, { type: "submit", disabled: form.formState.isSubmitting, children: [form.formState.isSubmitting && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), isEditing ? messages.common.saveChanges : messages.policyRuleDialog.actions.create] })] })] })] }) }));
|
|
97
101
|
}
|