@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 @@
|
|
|
1
|
+
{"version":3,"file":"policy-assignment-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/policy-assignment-dialog.tsx"],"names":[],"mappings":"AA6BA,OAAO,EAAE,KAAK,2BAA2B,EAAoC,MAAM,aAAa,CAAA;AA4BhG,MAAM,MAAM,cAAc,GAAG,2BAA2B,CAAA;AAExD,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,cAAc,CAAA;IAC3B,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,SAAS,GACV,EAAE,2BAA2B,2CAiN7B"}
|
|
@@ -0,0 +1,234 @@
|
|
|
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 { useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
4
|
+
import { useMarket, useMarkets } from "@voyant-travel/commerce-react/markets";
|
|
5
|
+
import { useChannel, useChannels } from "@voyant-travel/distribution-react";
|
|
6
|
+
import { useSupplier, useSuppliers } from "@voyant-travel/distribution-react/suppliers";
|
|
7
|
+
import { useProduct, useProducts } from "@voyant-travel/inventory-react";
|
|
8
|
+
import { useOrganization, useOrganizations } from "@voyant-travel/relationships-react";
|
|
9
|
+
import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyant-travel/ui/components";
|
|
10
|
+
import { DatePicker } from "@voyant-travel/ui/components/date-picker";
|
|
11
|
+
import { zodResolver } from "@voyant-travel/ui/lib/zod-resolver";
|
|
12
|
+
import { Loader2 } from "lucide-react";
|
|
13
|
+
import { useEffect, useMemo, useState } from "react";
|
|
14
|
+
import { useForm } from "react-hook-form";
|
|
15
|
+
import { z } from "zod/v4";
|
|
16
|
+
import { useLegalPolicyAssignmentMutation } from "../index.js";
|
|
17
|
+
import { mergeUniqueOptions, SearchableSelect } from "./legal-admin-shared.js";
|
|
18
|
+
// Parity with the previous operator-local entity combobox, which showed a
|
|
19
|
+
// hardcoded "No results." empty state; the loading label is localized via
|
|
20
|
+
// the shared legal contract-dialog messages.
|
|
21
|
+
const PICKER_EMPTY_LABEL = "No results.";
|
|
22
|
+
const SCOPE_VALUES = ["product", "channel", "supplier", "market", "organization", "global"];
|
|
23
|
+
const assignmentFormSchema = z.object({
|
|
24
|
+
policyId: z.string().min(1, "policyIdRequired"),
|
|
25
|
+
scope: z.enum(SCOPE_VALUES),
|
|
26
|
+
productId: z.string().optional(),
|
|
27
|
+
channelId: z.string().optional(),
|
|
28
|
+
supplierId: z.string().optional(),
|
|
29
|
+
marketId: z.string().optional(),
|
|
30
|
+
organizationId: z.string().optional(),
|
|
31
|
+
validFrom: z.string().optional(),
|
|
32
|
+
validTo: z.string().optional(),
|
|
33
|
+
priority: z.coerce.number().int().optional(),
|
|
34
|
+
});
|
|
35
|
+
export function PolicyAssignmentDialog({ open, onOpenChange, policyId, assignment, onSuccess, }) {
|
|
36
|
+
const isEditing = !!assignment;
|
|
37
|
+
const t = useOperatorAdminMessages().legal.policyAssignmentDialog;
|
|
38
|
+
const { create, update } = useLegalPolicyAssignmentMutation();
|
|
39
|
+
const form = useForm({
|
|
40
|
+
resolver: zodResolver(assignmentFormSchema),
|
|
41
|
+
defaultValues: {
|
|
42
|
+
policyId,
|
|
43
|
+
scope: "global",
|
|
44
|
+
productId: "",
|
|
45
|
+
channelId: "",
|
|
46
|
+
supplierId: "",
|
|
47
|
+
marketId: "",
|
|
48
|
+
organizationId: "",
|
|
49
|
+
validFrom: "",
|
|
50
|
+
validTo: "",
|
|
51
|
+
priority: 0,
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (open && assignment) {
|
|
56
|
+
form.reset({
|
|
57
|
+
policyId: assignment.policyId,
|
|
58
|
+
scope: assignment.scope,
|
|
59
|
+
productId: assignment.productId ?? "",
|
|
60
|
+
channelId: assignment.channelId ?? "",
|
|
61
|
+
supplierId: assignment.supplierId ?? "",
|
|
62
|
+
marketId: assignment.marketId ?? "",
|
|
63
|
+
organizationId: assignment.organizationId ?? "",
|
|
64
|
+
validFrom: assignment.validFrom ?? "",
|
|
65
|
+
validTo: assignment.validTo ?? "",
|
|
66
|
+
priority: assignment.priority,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
else if (open) {
|
|
70
|
+
form.reset({ policyId, scope: "global", priority: 0 });
|
|
71
|
+
}
|
|
72
|
+
}, [open, assignment, policyId, form]);
|
|
73
|
+
const onSubmit = async (values) => {
|
|
74
|
+
const payload = {
|
|
75
|
+
policyId: values.policyId,
|
|
76
|
+
scope: values.scope,
|
|
77
|
+
productId: values.productId || undefined,
|
|
78
|
+
channelId: values.channelId || undefined,
|
|
79
|
+
supplierId: values.supplierId || undefined,
|
|
80
|
+
marketId: values.marketId || undefined,
|
|
81
|
+
organizationId: values.organizationId || undefined,
|
|
82
|
+
validFrom: values.validFrom || undefined,
|
|
83
|
+
validTo: values.validTo || undefined,
|
|
84
|
+
priority: values.priority ?? 0,
|
|
85
|
+
};
|
|
86
|
+
if (isEditing && assignment) {
|
|
87
|
+
await update.mutateAsync({ id: assignment.id, input: payload });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
await create.mutateAsync(payload);
|
|
91
|
+
}
|
|
92
|
+
onSuccess();
|
|
93
|
+
};
|
|
94
|
+
const watchedScope = form.watch("scope");
|
|
95
|
+
const setReferenceField = (field, value) => {
|
|
96
|
+
form.setValue(field, value ?? "", { shouldDirty: true, shouldValidate: true });
|
|
97
|
+
};
|
|
98
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? t.titleEdit : t.titleNew }) }), _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: t.scopeLabel }), _jsxs(Select, { items: SCOPE_VALUES.map((value) => ({ value, label: t.scopeOptions[value] })), value: form.watch("scope"), onValueChange: (v) => {
|
|
99
|
+
form.setValue("scope", v, {
|
|
100
|
+
shouldDirty: true,
|
|
101
|
+
shouldValidate: true,
|
|
102
|
+
});
|
|
103
|
+
form.setValue("productId", "");
|
|
104
|
+
form.setValue("channelId", "");
|
|
105
|
+
form.setValue("supplierId", "");
|
|
106
|
+
form.setValue("marketId", "");
|
|
107
|
+
form.setValue("organizationId", "");
|
|
108
|
+
}, children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: SCOPE_VALUES.map((value) => (_jsx(SelectItem, { value: value, children: t.scopeOptions[value] }, value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.priorityLabel }), _jsx(Input, { ...form.register("priority"), type: "number" })] })] }), watchedScope === "product" && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.productLabel }), _jsx(ProductPicker, { value: form.watch("productId") || null, onChange: (id) => setReferenceField("productId", id), placeholder: t.productSearchPlaceholder })] })), watchedScope === "channel" && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.channelLabel }), _jsx(ChannelPicker, { value: form.watch("channelId") || null, onChange: (id) => setReferenceField("channelId", id), placeholder: t.channelSearchPlaceholder })] })), watchedScope === "supplier" && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.supplierLabel }), _jsx(SupplierPicker, { value: form.watch("supplierId") || null, onChange: (id) => setReferenceField("supplierId", id), placeholder: t.supplierSearchPlaceholder })] })), watchedScope === "market" && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.marketLabel }), _jsx(MarketPicker, { value: form.watch("marketId") || null, onChange: (id) => setReferenceField("marketId", id), placeholder: t.marketSearchPlaceholder })] })), watchedScope === "organization" && (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.organizationLabel }), _jsx(OrganizationPicker, { value: form.watch("organizationId") || null, onChange: (id) => setReferenceField("organizationId", id), placeholder: t.organizationSearchPlaceholder })] })), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.validFromLabel }), _jsx(DatePicker, { value: form.watch("validFrom") || null, onChange: (next) => form.setValue("validFrom", next ?? "", {
|
|
109
|
+
shouldValidate: true,
|
|
110
|
+
shouldDirty: true,
|
|
111
|
+
}), placeholder: t.validFromPlaceholder, className: "w-full" })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.validToLabel }), _jsx(DatePicker, { value: form.watch("validTo") || null, onChange: (next) => form.setValue("validTo", next ?? "", {
|
|
112
|
+
shouldValidate: true,
|
|
113
|
+
shouldDirty: true,
|
|
114
|
+
}), placeholder: t.validToPlaceholder, className: "w-full" })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: t.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 ? t.saveChanges : t.createAction] })] })] })] }) }));
|
|
115
|
+
}
|
|
116
|
+
function usePickerLoadingLabel() {
|
|
117
|
+
return useOperatorAdminMessages().legal.contractDialog.loading;
|
|
118
|
+
}
|
|
119
|
+
function ProductPicker({ value, onChange, placeholder }) {
|
|
120
|
+
const loadingLabel = usePickerLoadingLabel();
|
|
121
|
+
const [search, setSearch] = useState("");
|
|
122
|
+
const listQuery = useProducts({ search: search || undefined, limit: 25 });
|
|
123
|
+
const selectedQuery = useProduct(value ?? undefined, { enabled: Boolean(value) });
|
|
124
|
+
const options = useMemo(() => {
|
|
125
|
+
const describe = (product) => [product.status, product.bookingMode].filter(Boolean).join(" / ") || undefined;
|
|
126
|
+
return mergeUniqueOptions(listQuery.data?.data.map((product) => ({
|
|
127
|
+
value: product.id,
|
|
128
|
+
label: product.name,
|
|
129
|
+
description: describe(product),
|
|
130
|
+
})), selectedQuery.data
|
|
131
|
+
? [
|
|
132
|
+
{
|
|
133
|
+
value: selectedQuery.data.id,
|
|
134
|
+
label: selectedQuery.data.name,
|
|
135
|
+
description: describe(selectedQuery.data),
|
|
136
|
+
},
|
|
137
|
+
]
|
|
138
|
+
: undefined);
|
|
139
|
+
}, [listQuery.data?.data, selectedQuery.data]);
|
|
140
|
+
return (_jsx(SearchableSelect, { value: value, onChange: onChange, options: options, placeholder: placeholder, searchPlaceholder: placeholder, emptyLabel: PICKER_EMPTY_LABEL, loadingLabel: loadingLabel, loading: listQuery.isPending || (Boolean(value) && selectedQuery.isPending), onSearchChange: setSearch }));
|
|
141
|
+
}
|
|
142
|
+
function ChannelPicker({ value, onChange, placeholder }) {
|
|
143
|
+
const loadingLabel = usePickerLoadingLabel();
|
|
144
|
+
// Channels expose no server-side search filter; load a wide page and let
|
|
145
|
+
// the combobox narrow client-side (same approach as the contract dialog).
|
|
146
|
+
const listQuery = useChannels({ limit: 250 });
|
|
147
|
+
const selectedQuery = useChannel(value, { enabled: Boolean(value) });
|
|
148
|
+
const options = useMemo(() => {
|
|
149
|
+
const describe = (channel) => [channel.kind, channel.status].filter(Boolean).join(" / ") || undefined;
|
|
150
|
+
return mergeUniqueOptions(listQuery.data?.data.map((channel) => ({
|
|
151
|
+
value: channel.id,
|
|
152
|
+
label: channel.name,
|
|
153
|
+
description: describe(channel),
|
|
154
|
+
})), selectedQuery.data
|
|
155
|
+
? [
|
|
156
|
+
{
|
|
157
|
+
value: selectedQuery.data.id,
|
|
158
|
+
label: selectedQuery.data.name,
|
|
159
|
+
description: describe(selectedQuery.data),
|
|
160
|
+
},
|
|
161
|
+
]
|
|
162
|
+
: undefined);
|
|
163
|
+
}, [listQuery.data?.data, selectedQuery.data]);
|
|
164
|
+
return (_jsx(SearchableSelect, { value: value, onChange: onChange, options: options, placeholder: placeholder, searchPlaceholder: placeholder, emptyLabel: PICKER_EMPTY_LABEL, loadingLabel: loadingLabel, loading: listQuery.isPending || (Boolean(value) && selectedQuery.isPending) }));
|
|
165
|
+
}
|
|
166
|
+
function SupplierPicker({ value, onChange, placeholder }) {
|
|
167
|
+
const loadingLabel = usePickerLoadingLabel();
|
|
168
|
+
const [search, setSearch] = useState("");
|
|
169
|
+
const listQuery = useSuppliers({ search: search || undefined, limit: 25 });
|
|
170
|
+
const selectedQuery = useSupplier(value ?? "", { enabled: Boolean(value) });
|
|
171
|
+
const options = useMemo(() => {
|
|
172
|
+
const describe = (supplier) => [supplier.city, supplier.country].filter(Boolean).join(" / ") || undefined;
|
|
173
|
+
return mergeUniqueOptions(listQuery.data?.data.map((supplier) => ({
|
|
174
|
+
value: supplier.id,
|
|
175
|
+
label: supplier.name,
|
|
176
|
+
description: describe(supplier),
|
|
177
|
+
})), selectedQuery.data
|
|
178
|
+
? [
|
|
179
|
+
{
|
|
180
|
+
value: selectedQuery.data.data.id,
|
|
181
|
+
label: selectedQuery.data.data.name,
|
|
182
|
+
description: describe(selectedQuery.data.data),
|
|
183
|
+
},
|
|
184
|
+
]
|
|
185
|
+
: undefined);
|
|
186
|
+
}, [listQuery.data?.data, selectedQuery.data]);
|
|
187
|
+
return (_jsx(SearchableSelect, { value: value, onChange: onChange, options: options, placeholder: placeholder, searchPlaceholder: placeholder, emptyLabel: PICKER_EMPTY_LABEL, loadingLabel: loadingLabel, loading: listQuery.isPending || (Boolean(value) && selectedQuery.isPending), onSearchChange: setSearch }));
|
|
188
|
+
}
|
|
189
|
+
function MarketPicker({ value, onChange, placeholder }) {
|
|
190
|
+
const loadingLabel = usePickerLoadingLabel();
|
|
191
|
+
const [search, setSearch] = useState("");
|
|
192
|
+
const listQuery = useMarkets({ search: search || undefined, limit: 25 });
|
|
193
|
+
const selectedQuery = useMarket(value, { enabled: Boolean(value) });
|
|
194
|
+
const options = useMemo(() => {
|
|
195
|
+
const describe = (market) => [market.code, market.defaultCurrency].filter(Boolean).join(" / ") || undefined;
|
|
196
|
+
return mergeUniqueOptions(listQuery.data?.data.map((market) => ({
|
|
197
|
+
value: market.id,
|
|
198
|
+
label: market.name,
|
|
199
|
+
description: describe(market),
|
|
200
|
+
})), selectedQuery.data
|
|
201
|
+
? [
|
|
202
|
+
{
|
|
203
|
+
value: selectedQuery.data.id,
|
|
204
|
+
label: selectedQuery.data.name,
|
|
205
|
+
description: describe(selectedQuery.data),
|
|
206
|
+
},
|
|
207
|
+
]
|
|
208
|
+
: undefined);
|
|
209
|
+
}, [listQuery.data?.data, selectedQuery.data]);
|
|
210
|
+
return (_jsx(SearchableSelect, { value: value, onChange: onChange, options: options, placeholder: placeholder, searchPlaceholder: placeholder, emptyLabel: PICKER_EMPTY_LABEL, loadingLabel: loadingLabel, loading: listQuery.isPending || (Boolean(value) && selectedQuery.isPending), onSearchChange: setSearch }));
|
|
211
|
+
}
|
|
212
|
+
function OrganizationPicker({ value, onChange, placeholder }) {
|
|
213
|
+
const loadingLabel = usePickerLoadingLabel();
|
|
214
|
+
const [search, setSearch] = useState("");
|
|
215
|
+
const listQuery = useOrganizations({ search: search || undefined, limit: 25 });
|
|
216
|
+
const selectedQuery = useOrganization(value ?? undefined, { enabled: Boolean(value) });
|
|
217
|
+
const options = useMemo(() => {
|
|
218
|
+
const describe = (organization) => [organization.website, organization.industry].filter(Boolean).join(" / ") || undefined;
|
|
219
|
+
return mergeUniqueOptions(listQuery.data?.data.map((organization) => ({
|
|
220
|
+
value: organization.id,
|
|
221
|
+
label: organization.name,
|
|
222
|
+
description: describe(organization),
|
|
223
|
+
})), selectedQuery.data
|
|
224
|
+
? [
|
|
225
|
+
{
|
|
226
|
+
value: selectedQuery.data.id,
|
|
227
|
+
label: selectedQuery.data.name,
|
|
228
|
+
description: describe(selectedQuery.data),
|
|
229
|
+
},
|
|
230
|
+
]
|
|
231
|
+
: undefined);
|
|
232
|
+
}, [listQuery.data?.data, selectedQuery.data]);
|
|
233
|
+
return (_jsx(SearchableSelect, { value: value, onChange: onChange, options: options, placeholder: placeholder, searchPlaceholder: placeholder, emptyLabel: PICKER_EMPTY_LABEL, loadingLabel: loadingLabel, loading: listQuery.isPending || (Boolean(value) && selectedQuery.isPending), onSearchChange: setSearch }));
|
|
234
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface PolicyDetailHostProps {
|
|
2
|
+
id: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Packaged admin host for the operator-grade policy detail page
|
|
6
|
+
* (packaged-admin RFC Phase 3). Back-navigation resolves through the
|
|
7
|
+
* `policy.list` semantic destination; the edit and assignment dialogs are
|
|
8
|
+
* the packaged {@link PolicyDialog} / {@link PolicyAssignmentDialog}
|
|
9
|
+
* (scoped pickers bound to their domain react hooks).
|
|
10
|
+
*/
|
|
11
|
+
export declare function PolicyDetailHost({ id }: PolicyDetailHostProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
//# sourceMappingURL=policy-detail-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-detail-host.d.ts","sourceRoot":"","sources":["../../src/admin/policy-detail-host.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,EAAE,EAAE,qBAAqB,2CAW7D"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useAdminNavigate } from "@voyant-travel/admin";
|
|
4
|
+
import { PolicyDetailPage } from "../components/policy-detail-page.js";
|
|
5
|
+
import { PolicyAssignmentDialog } from "./policy-assignment-dialog.js";
|
|
6
|
+
import { PolicyDialog } from "./policy-dialog.js";
|
|
7
|
+
/**
|
|
8
|
+
* Packaged admin host for the operator-grade policy detail page
|
|
9
|
+
* (packaged-admin RFC Phase 3). Back-navigation resolves through the
|
|
10
|
+
* `policy.list` semantic destination; the edit and assignment dialogs are
|
|
11
|
+
* the packaged {@link PolicyDialog} / {@link PolicyAssignmentDialog}
|
|
12
|
+
* (scoped pickers bound to their domain react hooks).
|
|
13
|
+
*/
|
|
14
|
+
export function PolicyDetailHost({ id }) {
|
|
15
|
+
const navigateTo = useAdminNavigate();
|
|
16
|
+
return (_jsx(PolicyDetailPage, { id: id, onBackToPolicies: () => navigateTo("policy.list", {}), renderPolicyDialog: (props) => _jsx(PolicyDialog, { ...props }), renderPolicyAssignmentDialog: (props) => _jsx(PolicyAssignmentDialog, { ...props }) }));
|
|
17
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type LegalPolicyRecord } from "../index.js";
|
|
2
|
+
type PolicyDialogProps = {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
policy?: LegalPolicyRecord;
|
|
6
|
+
onSuccess: () => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function PolicyDialog({ open, onOpenChange, policy, onSuccess }: PolicyDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=policy-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"policy-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/policy-dialog.tsx"],"names":[],"mappings":"AAuBA,OAAO,EAAE,KAAK,iBAAiB,EAA0B,MAAM,aAAa,CAAA;AA4B5E,KAAK,iBAAiB,GAAG;IACvB,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,MAAM,CAAC,EAAE,iBAAiB,CAAA;IAC1B,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CAmIxF"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
3
|
+
import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyant-travel/ui/components";
|
|
4
|
+
import { zodResolver } from "@voyant-travel/ui/lib/zod-resolver";
|
|
5
|
+
import { Loader2 } from "lucide-react";
|
|
6
|
+
import { useEffect } from "react";
|
|
7
|
+
import { useForm } from "react-hook-form";
|
|
8
|
+
import { z } from "zod/v4";
|
|
9
|
+
import { useLegalPolicyMutation } from "../index.js";
|
|
10
|
+
const KIND_VALUES = [
|
|
11
|
+
"cancellation",
|
|
12
|
+
"payment",
|
|
13
|
+
"terms_and_conditions",
|
|
14
|
+
"privacy",
|
|
15
|
+
"refund",
|
|
16
|
+
"commission",
|
|
17
|
+
"guarantee",
|
|
18
|
+
"other",
|
|
19
|
+
];
|
|
20
|
+
const policyFormSchema = z.object({
|
|
21
|
+
kind: z.enum(KIND_VALUES),
|
|
22
|
+
name: z.string().min(1, "nameRequired"),
|
|
23
|
+
slug: z
|
|
24
|
+
.string()
|
|
25
|
+
.min(1, "slugRequired")
|
|
26
|
+
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "slugKebabCase"),
|
|
27
|
+
description: z.string().optional(),
|
|
28
|
+
language: z.string().min(2).max(10).optional(),
|
|
29
|
+
});
|
|
30
|
+
export function PolicyDialog({ open, onOpenChange, policy, onSuccess }) {
|
|
31
|
+
const isEditing = !!policy;
|
|
32
|
+
const t = useOperatorAdminMessages().legal.policyDialog;
|
|
33
|
+
const { create, update } = useLegalPolicyMutation();
|
|
34
|
+
const validationByCode = {
|
|
35
|
+
nameRequired: t.validation.nameRequired,
|
|
36
|
+
slugRequired: t.validation.slugRequired,
|
|
37
|
+
slugKebabCase: t.validation.slugKebabCase,
|
|
38
|
+
};
|
|
39
|
+
const resolveValidation = (code) => (code && validationByCode[code]) || code || "";
|
|
40
|
+
const form = useForm({
|
|
41
|
+
resolver: zodResolver(policyFormSchema),
|
|
42
|
+
defaultValues: {
|
|
43
|
+
kind: "cancellation",
|
|
44
|
+
name: "",
|
|
45
|
+
slug: "",
|
|
46
|
+
description: "",
|
|
47
|
+
language: "en",
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (open && policy) {
|
|
52
|
+
form.reset({
|
|
53
|
+
kind: policy.kind,
|
|
54
|
+
name: policy.name,
|
|
55
|
+
slug: policy.slug,
|
|
56
|
+
description: policy.description ?? "",
|
|
57
|
+
language: policy.language,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
else if (open) {
|
|
61
|
+
form.reset();
|
|
62
|
+
}
|
|
63
|
+
}, [open, policy, form]);
|
|
64
|
+
const onSubmit = async (values) => {
|
|
65
|
+
const payload = {
|
|
66
|
+
kind: values.kind,
|
|
67
|
+
name: values.name,
|
|
68
|
+
slug: values.slug,
|
|
69
|
+
description: values.description || undefined,
|
|
70
|
+
language: values.language || "en",
|
|
71
|
+
};
|
|
72
|
+
if (isEditing && policy) {
|
|
73
|
+
await update.mutateAsync({ id: policy.id, input: payload });
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
await create.mutateAsync(payload);
|
|
77
|
+
}
|
|
78
|
+
onSuccess();
|
|
79
|
+
};
|
|
80
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? t.titleEdit : t.titleNew }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.kindLabel }), _jsxs(Select, { items: KIND_VALUES.map((value) => ({ value, label: t.kindOptions[value] })), value: form.watch("kind"), onValueChange: (v) => form.setValue("kind", v), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: KIND_VALUES.map((value) => (_jsx(SelectItem, { value: value, children: t.kindOptions[value] }, value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.nameLabel }), _jsx(Input, { ...form.register("name"), placeholder: t.namePlaceholder }), form.formState.errors.name && (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.name.message) }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.slugLabel }), _jsx(Input, { ...form.register("slug"), placeholder: t.slugPlaceholder }), form.formState.errors.slug && (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.slug.message) }))] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.descriptionLabel }), _jsx(Textarea, { ...form.register("description"), placeholder: t.descriptionPlaceholder })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.languageLabel }), _jsx(Input, { ...form.register("language"), placeholder: t.languagePlaceholder, maxLength: 10, className: "max-w-[120px]" })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: t.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 ? t.saveChanges : t.createAction] })] })] })] }) }));
|
|
81
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface TemplateDetailHostProps {
|
|
2
|
+
id: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Packaged admin host for the operator-grade contract template detail page
|
|
6
|
+
* (packaged-admin RFC Phase 3). Back-navigation resolves through the
|
|
7
|
+
* `contractTemplate.list` semantic destination; the edit/version dialogs
|
|
8
|
+
* (rich-text editor) stay lazily loaded inside the package.
|
|
9
|
+
*/
|
|
10
|
+
export declare function TemplateDetailHost({ id }: TemplateDetailHostProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
//# sourceMappingURL=template-detail-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-detail-host.d.ts","sourceRoot":"","sources":["../../src/admin/template-detail-host.tsx"],"names":[],"mappings":"AAiBA,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAA;CACX;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,EAAE,EAAE,uBAAuB,2CAmBjE"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useAdminNavigate } from "@voyant-travel/admin";
|
|
4
|
+
import { lazy, Suspense } from "react";
|
|
5
|
+
import { TemplateDetailPage } from "../components/template-detail-page.js";
|
|
6
|
+
// Lazy: both dialogs use the RichTextEditor (tiptap + prosemirror).
|
|
7
|
+
const TemplateDialog = lazy(() => import("./template-dialog.js").then((m) => ({ default: m.TemplateDialog })));
|
|
8
|
+
const TemplateVersionDialog = lazy(() => import("./template-version-dialog.js").then((m) => ({
|
|
9
|
+
default: m.TemplateVersionDialog,
|
|
10
|
+
})));
|
|
11
|
+
/**
|
|
12
|
+
* Packaged admin host for the operator-grade contract template detail page
|
|
13
|
+
* (packaged-admin RFC Phase 3). Back-navigation resolves through the
|
|
14
|
+
* `contractTemplate.list` semantic destination; the edit/version dialogs
|
|
15
|
+
* (rich-text editor) stay lazily loaded inside the package.
|
|
16
|
+
*/
|
|
17
|
+
export function TemplateDetailHost({ id }) {
|
|
18
|
+
const navigateTo = useAdminNavigate();
|
|
19
|
+
return (_jsx(TemplateDetailPage, { id: id, onBackToTemplates: () => navigateTo("contractTemplate.list", {}), renderTemplateDialog: (props) => (_jsx(Suspense, { fallback: null, children: _jsx(TemplateDialog, { ...props }) })), renderTemplateVersionDialog: (props) => (_jsx(Suspense, { fallback: null, children: _jsx(TemplateVersionDialog, { ...props }) })) }));
|
|
20
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type LegalContractTemplateRecord } from "../index.js";
|
|
2
|
+
type TemplateDialogProps = {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
template?: LegalContractTemplateRecord;
|
|
6
|
+
onSuccess: () => void;
|
|
7
|
+
};
|
|
8
|
+
export declare function TemplateDialog({ open, onOpenChange, template, onSuccess }: TemplateDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=template-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/template-dialog.tsx"],"names":[],"mappings":"AAgCA,OAAO,EACL,KAAK,2BAA2B,EAGjC,MAAM,aAAa,CAAA;AAqBpB,KAAK,mBAAmB,GAAG;IACzB,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,CAAC,EAAE,2BAA2B,CAAA;IACtC,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAcD,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,EAAE,mBAAmB,2CAqN9F"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
3
|
+
import { Button, ContractTemplateAuthoringHelp, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Textarea, } from "@voyant-travel/ui/components";
|
|
4
|
+
import { RichTextEditor } from "@voyant-travel/ui/components/rich-text-editor";
|
|
5
|
+
import { insertPlainText, insertVariableToken, } from "@voyant-travel/ui/components/rich-text-variable-extension";
|
|
6
|
+
import { Switch } from "@voyant-travel/ui/components/switch";
|
|
7
|
+
import { zodResolver } from "@voyant-travel/ui/lib/zod-resolver";
|
|
8
|
+
import { languages } from "@voyant-travel/utils";
|
|
9
|
+
import { Loader2 } from "lucide-react";
|
|
10
|
+
import { useEffect, useMemo, useState } from "react";
|
|
11
|
+
import { useForm } from "react-hook-form";
|
|
12
|
+
import { z } from "zod/v4";
|
|
13
|
+
import { useLegalContractTemplateAuthoring, useLegalContractTemplateMutation, } from "../index.js";
|
|
14
|
+
const SCOPE_VALUES = ["customer", "supplier", "partner", "channel", "other"];
|
|
15
|
+
const templateFormSchema = z.object({
|
|
16
|
+
name: z.string().min(1, "nameRequired"),
|
|
17
|
+
slug: z
|
|
18
|
+
.string()
|
|
19
|
+
.min(1, "slugRequired")
|
|
20
|
+
.regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "slugKebabCase"),
|
|
21
|
+
scope: z.enum(SCOPE_VALUES),
|
|
22
|
+
language: z.string().min(2).max(10).optional(),
|
|
23
|
+
description: z.string().optional(),
|
|
24
|
+
body: z.string().min(1, "bodyRequired"),
|
|
25
|
+
active: z.boolean(),
|
|
26
|
+
});
|
|
27
|
+
/**
|
|
28
|
+
* Language picker options derived from the canonical ISO 639-1 list
|
|
29
|
+
* shipped by `@voyant-travel/utils`. Sorted alphabetically by the English
|
|
30
|
+
* display name for predictable scanning. Lifted to module scope so
|
|
31
|
+
* the array reference is stable across re-renders (otherwise the
|
|
32
|
+
* Select primitive's `items` identity changes every render and
|
|
33
|
+
* forces internal state churn).
|
|
34
|
+
*/
|
|
35
|
+
const LANGUAGE_ITEMS = Object.entries(languages)
|
|
36
|
+
.map(([code, label]) => ({ value: code, label: `${label} (${code})` }))
|
|
37
|
+
.sort((a, b) => a.label.localeCompare(b.label));
|
|
38
|
+
export function TemplateDialog({ open, onOpenChange, template, onSuccess }) {
|
|
39
|
+
const isEditing = !!template;
|
|
40
|
+
const t = useOperatorAdminMessages().legal.templateDialog;
|
|
41
|
+
const { create, update } = useLegalContractTemplateMutation();
|
|
42
|
+
const validationByCode = {
|
|
43
|
+
nameRequired: t.validation.nameRequired,
|
|
44
|
+
slugRequired: t.validation.slugRequired,
|
|
45
|
+
slugKebabCase: t.validation.slugKebabCase,
|
|
46
|
+
bodyRequired: t.validation.bodyRequired,
|
|
47
|
+
};
|
|
48
|
+
const resolveValidation = (code) => (code && validationByCode[code]) || code || "";
|
|
49
|
+
const { variableCatalog, liquidSnippets } = useLegalContractTemplateAuthoring();
|
|
50
|
+
const [editorInstance, setEditorInstance] = useState(null);
|
|
51
|
+
const variableGroups = useMemo(() => variableCatalog.map((group) => ({
|
|
52
|
+
...group,
|
|
53
|
+
variables: group.variables.map((variable) => ({
|
|
54
|
+
...variable,
|
|
55
|
+
example: String(variable.example),
|
|
56
|
+
})),
|
|
57
|
+
})), [variableCatalog]);
|
|
58
|
+
const form = useForm({
|
|
59
|
+
resolver: zodResolver(templateFormSchema),
|
|
60
|
+
defaultValues: {
|
|
61
|
+
name: "",
|
|
62
|
+
slug: "",
|
|
63
|
+
scope: "customer",
|
|
64
|
+
language: "en",
|
|
65
|
+
description: "",
|
|
66
|
+
body: "",
|
|
67
|
+
active: true,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (open && template) {
|
|
72
|
+
form.reset({
|
|
73
|
+
name: template.name,
|
|
74
|
+
slug: template.slug,
|
|
75
|
+
scope: template.scope,
|
|
76
|
+
language: template.language,
|
|
77
|
+
description: template.description ?? "",
|
|
78
|
+
body: template.body,
|
|
79
|
+
active: template.active,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
else if (open) {
|
|
83
|
+
form.reset();
|
|
84
|
+
}
|
|
85
|
+
}, [open, template, form]);
|
|
86
|
+
const onSubmit = async (values) => {
|
|
87
|
+
const payload = {
|
|
88
|
+
name: values.name,
|
|
89
|
+
slug: values.slug,
|
|
90
|
+
scope: values.scope,
|
|
91
|
+
language: values.language || "en",
|
|
92
|
+
description: values.description || undefined,
|
|
93
|
+
body: values.body,
|
|
94
|
+
active: values.active,
|
|
95
|
+
};
|
|
96
|
+
if (isEditing && template) {
|
|
97
|
+
await update.mutateAsync({ id: template.id, input: payload });
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
await create.mutateAsync(payload);
|
|
101
|
+
}
|
|
102
|
+
onSuccess();
|
|
103
|
+
};
|
|
104
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing ? t.titleEdit : t.titleNew }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4 max-h-[70vh]", children: [_jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.nameLabel }), _jsx(Input, { ...form.register("name"), placeholder: t.namePlaceholder }), form.formState.errors.name ? (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.name.message) })) : null] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.slugLabel }), _jsx(Input, { ...form.register("slug"), placeholder: t.slugPlaceholder }), form.formState.errors.slug ? (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.slug.message) })) : null] })] }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.scopeLabel }), _jsxs(Select, { items: SCOPE_VALUES.map((value) => ({ value, label: t.scopeOptions[value] })), value: form.watch("scope"), onValueChange: (value) => form.setValue("scope", value), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: SCOPE_VALUES.map((value) => (_jsx(SelectItem, { value: value, children: t.scopeOptions[value] }, value))) })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.languageLabel }), _jsxs(Select, { items: LANGUAGE_ITEMS, value: form.watch("language") ?? "en", onValueChange: (value) => form.setValue("language", value ?? "en", { shouldDirty: true }), children: [_jsx(SelectTrigger, { className: "w-full", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: LANGUAGE_ITEMS.map((lang) => (_jsx(SelectItem, { value: lang.value, children: lang.label }, lang.value))) })] })] })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.descriptionLabel }), _jsx(Textarea, { ...form.register("description"), placeholder: t.descriptionPlaceholder })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.bodyLabel }), _jsx(RichTextEditor, { value: form.watch("body"), onChange: (value) => form.setValue("body", value, {
|
|
105
|
+
shouldDirty: true,
|
|
106
|
+
shouldTouch: true,
|
|
107
|
+
shouldValidate: true,
|
|
108
|
+
}), placeholder: t.bodyPlaceholder, enableVariables: true, onEditorReady: setEditorInstance }), form.formState.errors.body ? (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.body.message) })) : (_jsx("p", { className: "text-[11px] text-muted-foreground", children: t.bodyHelp }))] }), _jsx(ContractTemplateAuthoringHelp, { variableGroups: variableGroups, snippets: liquidSnippets, onInsertVariable: (variable) => {
|
|
109
|
+
if (!editorInstance)
|
|
110
|
+
return;
|
|
111
|
+
insertVariableToken(editorInstance, variable.key);
|
|
112
|
+
}, onInsertSnippet: (snippet) => {
|
|
113
|
+
if (!editorInstance)
|
|
114
|
+
return;
|
|
115
|
+
insertPlainText(editorInstance, snippet.code);
|
|
116
|
+
} }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Switch, { checked: form.watch("active"), onCheckedChange: (checked) => form.setValue("active", checked) }), _jsx(Label, { children: t.activeLabel })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: t.cancel }), _jsxs(Button, { type: "submit", disabled: form.formState.isSubmitting, children: [form.formState.isSubmitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : null, isEditing ? t.saveChanges : t.createAction] })] })] })] }) }));
|
|
117
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
type TemplateVersionDialogProps = {
|
|
2
|
+
open: boolean;
|
|
3
|
+
onOpenChange: (open: boolean) => void;
|
|
4
|
+
templateId: string;
|
|
5
|
+
onSuccess: () => void;
|
|
6
|
+
};
|
|
7
|
+
export declare function TemplateVersionDialog({ open, onOpenChange, templateId, onSuccess, }: TemplateVersionDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=template-version-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-version-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/template-version-dialog.tsx"],"names":[],"mappings":"AAsCA,KAAK,0BAA0B,GAAG;IAChC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB,CAAA;AAED,wBAAgB,qBAAqB,CAAC,EACpC,IAAI,EACJ,YAAY,EACZ,UAAU,EACV,SAAS,GACV,EAAE,0BAA0B,2CAoH5B"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useOperatorAdminMessages } from "@voyant-travel/admin";
|
|
3
|
+
import { Button, ContractTemplateAuthoringHelp, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, } from "@voyant-travel/ui/components";
|
|
4
|
+
import { RichTextEditor } from "@voyant-travel/ui/components/rich-text-editor";
|
|
5
|
+
import { insertPlainText, insertVariableToken, } from "@voyant-travel/ui/components/rich-text-variable-extension";
|
|
6
|
+
import { zodResolver } from "@voyant-travel/ui/lib/zod-resolver";
|
|
7
|
+
import { Loader2 } from "lucide-react";
|
|
8
|
+
import { useEffect, useMemo, useState } from "react";
|
|
9
|
+
import { useForm } from "react-hook-form";
|
|
10
|
+
import { z } from "zod/v4";
|
|
11
|
+
import { useLegalContractTemplateAuthoring, useLegalContractTemplateVersionMutation, } from "../index.js";
|
|
12
|
+
const versionFormSchema = z.object({
|
|
13
|
+
body: z.string().min(1, "bodyRequired"),
|
|
14
|
+
changelog: z.string().optional(),
|
|
15
|
+
createdBy: z.string().optional(),
|
|
16
|
+
});
|
|
17
|
+
export function TemplateVersionDialog({ open, onOpenChange, templateId, onSuccess, }) {
|
|
18
|
+
const t = useOperatorAdminMessages().legal.templateVersionDialog;
|
|
19
|
+
const { create } = useLegalContractTemplateVersionMutation();
|
|
20
|
+
const { variableCatalog, liquidSnippets } = useLegalContractTemplateAuthoring();
|
|
21
|
+
const [editorInstance, setEditorInstance] = useState(null);
|
|
22
|
+
const resolveValidation = (code) => code === "bodyRequired" ? t.validation.bodyRequired : code || "";
|
|
23
|
+
const variableGroups = useMemo(() => variableCatalog.map((group) => ({
|
|
24
|
+
...group,
|
|
25
|
+
variables: group.variables.map((variable) => ({
|
|
26
|
+
...variable,
|
|
27
|
+
example: String(variable.example),
|
|
28
|
+
})),
|
|
29
|
+
})), [variableCatalog]);
|
|
30
|
+
const form = useForm({
|
|
31
|
+
resolver: zodResolver(versionFormSchema),
|
|
32
|
+
defaultValues: {
|
|
33
|
+
body: "",
|
|
34
|
+
changelog: "",
|
|
35
|
+
createdBy: "",
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (open) {
|
|
40
|
+
form.reset();
|
|
41
|
+
}
|
|
42
|
+
}, [open, form]);
|
|
43
|
+
const onSubmit = async (values) => {
|
|
44
|
+
await create.mutateAsync({
|
|
45
|
+
templateId,
|
|
46
|
+
input: {
|
|
47
|
+
body: values.body,
|
|
48
|
+
changelog: values.changelog || undefined,
|
|
49
|
+
createdBy: values.createdBy || undefined,
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
onSuccess();
|
|
53
|
+
};
|
|
54
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { size: "lg", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: t.titleNew }) }), _jsxs("form", { onSubmit: form.handleSubmit(onSubmit), children: [_jsxs(DialogBody, { className: "grid gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.bodyLabel }), _jsx(RichTextEditor, { value: form.watch("body"), onChange: (value) => form.setValue("body", value, {
|
|
55
|
+
shouldDirty: true,
|
|
56
|
+
shouldTouch: true,
|
|
57
|
+
shouldValidate: true,
|
|
58
|
+
}), placeholder: t.bodyPlaceholder, enableVariables: true, onEditorReady: setEditorInstance }), form.formState.errors.body ? (_jsx("p", { className: "text-xs text-destructive", children: resolveValidation(form.formState.errors.body.message) })) : null] }), _jsx(ContractTemplateAuthoringHelp, { variableGroups: variableGroups, snippets: liquidSnippets, onInsertVariable: (variable) => {
|
|
59
|
+
if (!editorInstance)
|
|
60
|
+
return;
|
|
61
|
+
insertVariableToken(editorInstance, variable.key);
|
|
62
|
+
}, onInsertSnippet: (snippet) => {
|
|
63
|
+
if (!editorInstance)
|
|
64
|
+
return;
|
|
65
|
+
insertPlainText(editorInstance, snippet.code);
|
|
66
|
+
} }), _jsxs("div", { className: "grid grid-cols-2 gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.changelogLabel }), _jsx(Input, { ...form.register("changelog"), placeholder: t.changelogPlaceholder })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.createdByLabel }), _jsx(Input, { ...form.register("createdBy"), placeholder: t.createdByPlaceholder })] })] })] }), _jsxs(DialogFooter, { children: [_jsx(Button, { type: "button", variant: "ghost", onClick: () => onOpenChange(false), children: t.cancel }), _jsxs(Button, { type: "submit", disabled: form.formState.isSubmitting, children: [form.formState.isSubmitting ? (_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" })) : null, t.createAction] })] })] })] }) }));
|
|
67
|
+
}
|