@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,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Packaged admin host for the operator-grade contract templates list page
|
|
3
|
+
* (packaged-admin RFC Phase 3). Zero-prop: list state stays
|
|
4
|
+
* component-local, opening a row resolves through the
|
|
5
|
+
* `contractTemplate.detail` semantic destination, and the create/edit
|
|
6
|
+
* dialog (rich-text editor) stays lazily loaded inside the package.
|
|
7
|
+
*/
|
|
8
|
+
export declare function TemplatesHost(): import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
//# sourceMappingURL=templates-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"templates-host.d.ts","sourceRoot":"","sources":["../../src/admin/templates-host.tsx"],"names":[],"mappings":"AAcA;;;;;;GAMG;AACH,wBAAgB,aAAa,4CAa5B"}
|
|
@@ -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 { TemplatesPage } from "../components/templates-page.js";
|
|
6
|
+
// Lazy-load: the template dialog pulls tiptap + prosemirror (~600 KB raw).
|
|
7
|
+
// Keeping it out of the page chunk means the dialog modules + their
|
|
8
|
+
// dependencies only download when the user opens the dialog.
|
|
9
|
+
const TemplateDialog = lazy(() => import("./template-dialog.js").then((m) => ({ default: m.TemplateDialog })));
|
|
10
|
+
/**
|
|
11
|
+
* Packaged admin host for the operator-grade contract templates list page
|
|
12
|
+
* (packaged-admin RFC Phase 3). Zero-prop: list state stays
|
|
13
|
+
* component-local, opening a row resolves through the
|
|
14
|
+
* `contractTemplate.detail` semantic destination, and the create/edit
|
|
15
|
+
* dialog (rich-text editor) stays lazily loaded inside the package.
|
|
16
|
+
*/
|
|
17
|
+
export function TemplatesHost() {
|
|
18
|
+
const navigateTo = useAdminNavigate();
|
|
19
|
+
return (_jsx(TemplatesPage, { onOpenTemplate: (id) => navigateTo("contractTemplate.detail", { templateId: id }), renderTemplateDialog: (props) => (_jsx(Suspense, { fallback: null, children: _jsx(TemplateDialog, { ...props }) })) }));
|
|
20
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
export type VoyantFetcher = (url: string, init?: RequestInit) => Promise<Response>;
|
|
3
|
+
export declare const defaultFetcher: VoyantFetcher;
|
|
4
|
+
export declare class VoyantApiError extends Error {
|
|
5
|
+
readonly status: number;
|
|
6
|
+
readonly body: unknown;
|
|
7
|
+
constructor(message: string, status: number, body: unknown);
|
|
8
|
+
}
|
|
9
|
+
export interface FetchWithValidationOptions {
|
|
10
|
+
baseUrl: string;
|
|
11
|
+
fetcher: VoyantFetcher;
|
|
12
|
+
}
|
|
13
|
+
export declare function fetchWithValidation<TOut>(path: string, schema: z.ZodType<TOut>, options: FetchWithValidationOptions, init?: RequestInit): Promise<TOut>;
|
|
14
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAE5B,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AAElF,eAAO,MAAM,cAAc,EAAE,aACoB,CAAA;AAEjD,qBAAa,cAAe,SAAQ,KAAK;IACvC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;gBAEV,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO;CAM3D;AAaD,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,aAAa,CAAA;CACvB;AAED,wBAAsB,mBAAmB,CAAC,IAAI,EAC5C,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,EACvB,OAAO,EAAE,0BAA0B,EACnC,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,IAAI,CAAC,CA+Bf"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
export const defaultFetcher = (url, init) => fetch(url, { credentials: "include", ...init });
|
|
2
|
+
export class VoyantApiError extends Error {
|
|
3
|
+
status;
|
|
4
|
+
body;
|
|
5
|
+
constructor(message, status, body) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "VoyantApiError";
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.body = body;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function extractErrorMessage(status, statusText, body) {
|
|
13
|
+
if (typeof body === "object" && body !== null && "error" in body) {
|
|
14
|
+
const err = body.error;
|
|
15
|
+
if (typeof err === "string")
|
|
16
|
+
return err;
|
|
17
|
+
if (typeof err === "object" && err !== null && "message" in err) {
|
|
18
|
+
return String(err.message);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return `Voyant API error: ${status} ${statusText}`;
|
|
22
|
+
}
|
|
23
|
+
export async function fetchWithValidation(path, schema, options, init) {
|
|
24
|
+
const url = joinUrl(options.baseUrl, path);
|
|
25
|
+
const headers = new Headers(init?.headers);
|
|
26
|
+
const isFormDataBody = typeof FormData !== "undefined" && init?.body instanceof FormData;
|
|
27
|
+
if (init?.body !== undefined && !isFormDataBody && !headers.has("Content-Type")) {
|
|
28
|
+
headers.set("Content-Type", "application/json");
|
|
29
|
+
}
|
|
30
|
+
const response = await options.fetcher(url, { ...init, headers });
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
const body = await safeJson(response);
|
|
33
|
+
throw new VoyantApiError(extractErrorMessage(response.status, response.statusText, body), response.status, body);
|
|
34
|
+
}
|
|
35
|
+
if (response.status === 204)
|
|
36
|
+
return schema.parse(undefined);
|
|
37
|
+
const body = await safeJson(response);
|
|
38
|
+
const parsed = schema.safeParse(body);
|
|
39
|
+
if (!parsed.success) {
|
|
40
|
+
throw new VoyantApiError(`Voyant API response failed validation: ${parsed.error.message}`, response.status, body);
|
|
41
|
+
}
|
|
42
|
+
return parsed.data;
|
|
43
|
+
}
|
|
44
|
+
async function safeJson(response) {
|
|
45
|
+
const text = await response.text();
|
|
46
|
+
if (!text)
|
|
47
|
+
return undefined;
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(text);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return text;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function joinUrl(baseUrl, path) {
|
|
56
|
+
const resolvedBaseUrl = resolveBaseUrl(baseUrl);
|
|
57
|
+
const trimmedBase = resolvedBaseUrl.endsWith("/") ? resolvedBaseUrl.slice(0, -1) : resolvedBaseUrl;
|
|
58
|
+
const trimmedPath = path.startsWith("/") ? path : `/${path}`;
|
|
59
|
+
return `${trimmedBase}${trimmedPath}`;
|
|
60
|
+
}
|
|
61
|
+
function resolveBaseUrl(baseUrl) {
|
|
62
|
+
if (baseUrl.trim()) {
|
|
63
|
+
return baseUrl;
|
|
64
|
+
}
|
|
65
|
+
if (typeof window !== "undefined") {
|
|
66
|
+
return `${window.location.origin}/api`;
|
|
67
|
+
}
|
|
68
|
+
return "http://localhost:3300/api";
|
|
69
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type LegalContractAttachmentRecord } from "../index.js";
|
|
2
|
+
type AttachmentDialogProps = {
|
|
3
|
+
open: boolean;
|
|
4
|
+
onOpenChange: (open: boolean) => void;
|
|
5
|
+
contractId: string;
|
|
6
|
+
attachment?: LegalContractAttachmentRecord;
|
|
7
|
+
onSuccess: () => void;
|
|
8
|
+
};
|
|
9
|
+
export declare function AttachmentDialog({ open, onOpenChange, contractId, attachment, onSuccess, }: AttachmentDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=attachment-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachment-dialog.d.ts","sourceRoot":"","sources":["../../src/components/attachment-dialog.tsx"],"names":[],"mappings":"AAuBA,OAAO,EAAE,KAAK,6BAA6B,EAAsC,MAAM,aAAa,CAAA;AAqBpG,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"}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Button, Dialog, DialogBody, DialogContent, DialogFooter, DialogHeader, DialogTitle, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@voyant-travel/ui/components";
|
|
3
|
+
import { zodResolver } from "@voyant-travel/ui/lib/zod-resolver";
|
|
4
|
+
import { FileText, Loader2, Upload } from "lucide-react";
|
|
5
|
+
import { useEffect, useRef, useState } from "react";
|
|
6
|
+
import { useForm } from "react-hook-form";
|
|
7
|
+
import { z } from "zod/v4";
|
|
8
|
+
import { useLegalUiMessagesOrDefault } from "../i18n/index.js";
|
|
9
|
+
import { useLegalContractAttachmentMutation } from "../index.js";
|
|
10
|
+
const attachmentKindValues = ["document", "appendix", "scan"];
|
|
11
|
+
function createAttachmentFormSchema(messages) {
|
|
12
|
+
return z.object({
|
|
13
|
+
name: z.string().min(1, messages.attachmentDialog.validation.nameRequired),
|
|
14
|
+
kind: z.string().min(1).optional(),
|
|
15
|
+
mimeType: z.string().optional(),
|
|
16
|
+
fileSize: z.preprocess((value) => (value === "" || value == null ? undefined : value), z.coerce.number().int().optional()),
|
|
17
|
+
storageKey: z.string().optional(),
|
|
18
|
+
checksum: z.string().optional(),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
export function AttachmentDialog({ open, onOpenChange, contractId, attachment, onSuccess, }) {
|
|
22
|
+
const isEditing = !!attachment;
|
|
23
|
+
const { update, upload, replaceFile } = useLegalContractAttachmentMutation();
|
|
24
|
+
const messages = useLegalUiMessagesOrDefault();
|
|
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
|
+
}
|
|
37
|
+
const form = useForm({
|
|
38
|
+
resolver: zodResolver(attachmentFormSchema),
|
|
39
|
+
defaultValues: {
|
|
40
|
+
name: "",
|
|
41
|
+
kind: "document", // i18n-literal-ok domain attachment kind
|
|
42
|
+
mimeType: "",
|
|
43
|
+
fileSize: undefined,
|
|
44
|
+
storageKey: "",
|
|
45
|
+
checksum: "",
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
if (open && attachment) {
|
|
50
|
+
form.reset({
|
|
51
|
+
name: attachment.name,
|
|
52
|
+
kind: attachment.kind,
|
|
53
|
+
mimeType: attachment.mimeType ?? "",
|
|
54
|
+
fileSize: attachment.fileSize ?? undefined,
|
|
55
|
+
storageKey: attachment.storageKey ?? "",
|
|
56
|
+
checksum: attachment.checksum ?? "",
|
|
57
|
+
});
|
|
58
|
+
setSelectedFile(null);
|
|
59
|
+
setFileError(null);
|
|
60
|
+
}
|
|
61
|
+
else if (open) {
|
|
62
|
+
form.reset();
|
|
63
|
+
setSelectedFile(null);
|
|
64
|
+
setFileError(null);
|
|
65
|
+
}
|
|
66
|
+
setIsDragging(false);
|
|
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
|
+
};
|
|
103
|
+
const onSubmit = async (values) => {
|
|
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;
|
|
115
|
+
}
|
|
116
|
+
if (uploadInput) {
|
|
117
|
+
await upload.mutateAsync({ contractId, input: uploadInput });
|
|
118
|
+
onSuccess();
|
|
119
|
+
return;
|
|
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
|
+
});
|
|
137
|
+
onSuccess();
|
|
138
|
+
};
|
|
139
|
+
const isSubmitting = form.formState.isSubmitting || update.isPending || upload.isPending || replaceFile.isPending;
|
|
140
|
+
const submitError = update.error ?? upload.error ?? replaceFile.error ?? null;
|
|
141
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: isEditing
|
|
142
|
+
? messages.attachmentDialog.titles.edit
|
|
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`;
|
|
154
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { LegalUiMessages } from "../i18n/messages.js";
|
|
2
|
+
export type BookingContractCardLabels = Partial<Omit<LegalUiMessages["bookingContractCard"], "contractStatusLabels">>;
|
|
3
|
+
export interface BookingContractCardProps {
|
|
4
|
+
/** Booking whose contracts we list. Required — the card filters server-side. */
|
|
5
|
+
bookingId: string;
|
|
6
|
+
/** Contract scope used to resolve the default template + active number series. */
|
|
7
|
+
contractScope?: "customer" | "supplier" | "partner" | "channel" | "other";
|
|
8
|
+
/** Optional language preference for default-template resolution. */
|
|
9
|
+
language?: string;
|
|
10
|
+
/** Optional channel preference for default-template resolution. */
|
|
11
|
+
channelId?: string | null;
|
|
12
|
+
/** Optional language fallbacks for default-template resolution. */
|
|
13
|
+
fallbackLanguages?: string[];
|
|
14
|
+
/**
|
|
15
|
+
* API base for attachment download redirects. Defaults to the active
|
|
16
|
+
* `VoyantLegalProvider` base URL; override when a host needs a different
|
|
17
|
+
* download origin than its data hooks use.
|
|
18
|
+
*/
|
|
19
|
+
apiBaseUrl?: string;
|
|
20
|
+
labels?: BookingContractCardLabels;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Operator booking-detail "Contract" card. Mount next to the payments / docs
|
|
24
|
+
* card on the booking detail page. Responsibilities are deliberately narrow:
|
|
25
|
+
* - List contracts linked to this booking
|
|
26
|
+
* - Show each contract's latest status + number
|
|
27
|
+
* - Let the operator download the generated PDF (opens in a new tab)
|
|
28
|
+
* - Let the operator force a regeneration when the template or booking
|
|
29
|
+
* data has changed
|
|
30
|
+
*
|
|
31
|
+
* Contract creation is handled by the package booking generation endpoint.
|
|
32
|
+
* The card only offers the action when the server-visible prerequisites
|
|
33
|
+
* exist: a default template with a current version and exactly one active
|
|
34
|
+
* number series for the selected scope.
|
|
35
|
+
*/
|
|
36
|
+
export declare function BookingContractCard({ bookingId, contractScope, language, channelId, fallbackLanguages, apiBaseUrl, labels, }: BookingContractCardProps): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
//# sourceMappingURL=booking-contract-card.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"booking-contract-card.d.ts","sourceRoot":"","sources":["../../src/components/booking-contract-card.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AA0B1D,MAAM,MAAM,yBAAyB,GAAG,OAAO,CAC7C,IAAI,CAAC,eAAe,CAAC,qBAAqB,CAAC,EAAE,sBAAsB,CAAC,CACrE,CAAA;AAED,MAAM,WAAW,wBAAwB;IACvC,gFAAgF;IAChF,SAAS,EAAE,MAAM,CAAA;IACjB,kFAAkF;IAClF,aAAa,CAAC,EAAE,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAA;IACzE,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,mEAAmE;IACnE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,mEAAmE;IACnE,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC5B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,MAAM,CAAC,EAAE,yBAAyB,CAAA;CACnC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,SAAS,EACT,aAA0B,EAC1B,QAAQ,EACR,SAAS,EACT,iBAAiB,EACjB,UAAU,EACV,MAAM,GACP,EAAE,wBAAwB,2CA2F1B"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Badge, Button, Card, CardContent, CardHeader, CardTitle, } from "@voyant-travel/ui/components";
|
|
4
|
+
import { Download, FilePlus2, FileText, Loader2, RotateCw } from "lucide-react";
|
|
5
|
+
import { useLegalUiI18nOrDefault } from "../i18n/index.js";
|
|
6
|
+
import { useDefaultLegalContractTemplate, useLegalContractAttachments, useLegalContractMutation, useLegalContractNumberSeries, useLegalContracts, useVoyantLegalContext, } from "../index.js";
|
|
7
|
+
/**
|
|
8
|
+
* Status → badge style map. Keeps the card visually in sync with the
|
|
9
|
+
* contract detail page (same variant names, same ordering of severity).
|
|
10
|
+
*/
|
|
11
|
+
const STATUS_VARIANT = {
|
|
12
|
+
draft: "outline",
|
|
13
|
+
issued: "secondary",
|
|
14
|
+
sent: "secondary",
|
|
15
|
+
signed: "default",
|
|
16
|
+
executed: "default",
|
|
17
|
+
expired: "destructive",
|
|
18
|
+
void: "destructive",
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Operator booking-detail "Contract" card. Mount next to the payments / docs
|
|
22
|
+
* card on the booking detail page. Responsibilities are deliberately narrow:
|
|
23
|
+
* - List contracts linked to this booking
|
|
24
|
+
* - Show each contract's latest status + number
|
|
25
|
+
* - Let the operator download the generated PDF (opens in a new tab)
|
|
26
|
+
* - Let the operator force a regeneration when the template or booking
|
|
27
|
+
* data has changed
|
|
28
|
+
*
|
|
29
|
+
* Contract creation is handled by the package booking generation endpoint.
|
|
30
|
+
* The card only offers the action when the server-visible prerequisites
|
|
31
|
+
* exist: a default template with a current version and exactly one active
|
|
32
|
+
* number series for the selected scope.
|
|
33
|
+
*/
|
|
34
|
+
export function BookingContractCard({ bookingId, contractScope = "customer", language, channelId, fallbackLanguages, apiBaseUrl, labels, }) {
|
|
35
|
+
const i18n = useLegalUiI18nOrDefault();
|
|
36
|
+
const { baseUrl } = useVoyantLegalContext();
|
|
37
|
+
const resolvedApiBaseUrl = apiBaseUrl ?? baseUrl;
|
|
38
|
+
const merged = { ...i18n.messages.bookingContractCard, ...labels };
|
|
39
|
+
const contractsQuery = useLegalContracts({ bookingId, limit: 25 });
|
|
40
|
+
const contracts = contractsQuery.data?.data ?? [];
|
|
41
|
+
const shouldCheckGeneration = !contractsQuery.isLoading && contracts.length === 0;
|
|
42
|
+
const defaultTemplateQuery = useDefaultLegalContractTemplate({
|
|
43
|
+
scope: contractScope,
|
|
44
|
+
language,
|
|
45
|
+
channelId: channelId ?? undefined,
|
|
46
|
+
fallbackLanguages,
|
|
47
|
+
enabled: shouldCheckGeneration,
|
|
48
|
+
});
|
|
49
|
+
const numberSeriesQuery = useLegalContractNumberSeries({
|
|
50
|
+
scope: contractScope,
|
|
51
|
+
active: true,
|
|
52
|
+
enabled: shouldCheckGeneration,
|
|
53
|
+
});
|
|
54
|
+
const { generateForBooking } = useLegalContractMutation();
|
|
55
|
+
const activeSeries = numberSeriesQuery.data?.data ?? [];
|
|
56
|
+
const canGenerate = Boolean(defaultTemplateQuery.data?.currentVersionId) && activeSeries.length === 1;
|
|
57
|
+
const generationPrerequisitesLoaded = shouldCheckGeneration && !defaultTemplateQuery.isLoading && !numberSeriesQuery.isLoading;
|
|
58
|
+
const handleGenerateForBooking = () => {
|
|
59
|
+
generateForBooking.mutate({
|
|
60
|
+
bookingId,
|
|
61
|
+
input: {
|
|
62
|
+
scope: contractScope,
|
|
63
|
+
language,
|
|
64
|
+
channelId,
|
|
65
|
+
fallbackLanguages,
|
|
66
|
+
requireNumberSeries: true,
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center gap-2 text-base", children: [_jsx(FileText, { className: "h-4 w-4" }), merged.heading] }) }), _jsx(CardContent, { className: "flex flex-col gap-3", children: contractsQuery.isLoading ? (_jsxs("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" }), i18n.messages.common.loading] })) : contracts.length === 0 ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx("p", { className: "text-xs text-muted-foreground", children: merged.empty }), generationPrerequisitesLoaded && canGenerate ? (_jsxs(Button, { type: "button", size: "sm", onClick: handleGenerateForBooking, disabled: generateForBooking.isPending, className: "w-fit", children: [generateForBooking.isPending ? (_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(FilePlus2, { className: "h-3.5 w-3.5" })), _jsx("span", { className: "ml-1 text-xs", children: generateForBooking.isPending ? merged.generating : merged.generateContract })] })) : generationPrerequisitesLoaded ? (_jsx("p", { className: "text-[11px] text-muted-foreground", children: merged.generateUnavailable })) : null] })) : (contracts.map((contract) => (_jsx(BookingContractRow, { contract: contract, apiBaseUrl: resolvedApiBaseUrl, labels: merged }, contract.id)))) })] }));
|
|
71
|
+
}
|
|
72
|
+
function BookingContractRow({ contract, apiBaseUrl, labels, }) {
|
|
73
|
+
const i18n = useLegalUiI18nOrDefault();
|
|
74
|
+
const attachmentsQuery = useLegalContractAttachments({ contractId: contract.id });
|
|
75
|
+
const attachments = attachmentsQuery.data ?? [];
|
|
76
|
+
const documentAttachments = attachments.filter((a) => a.kind === "document");
|
|
77
|
+
const { generateDocument, regenerateDocument } = useLegalContractMutation();
|
|
78
|
+
const isPending = generateDocument.isPending || regenerateDocument.isPending;
|
|
79
|
+
const hasDocument = documentAttachments.length > 0;
|
|
80
|
+
const handleGenerate = () => {
|
|
81
|
+
const mutation = hasDocument ? regenerateDocument : generateDocument;
|
|
82
|
+
mutation.mutate({ id: contract.id, input: { replaceExisting: true, kind: "document" } });
|
|
83
|
+
};
|
|
84
|
+
return (_jsxs("div", { className: "flex flex-col gap-2 rounded-md border p-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2 text-sm", children: [_jsxs("span", { className: "font-medium", children: [labels.contractNumber, contract.contractNumber ?? labels.unsaved] }), _jsx(Badge, { variant: STATUS_VARIANT[contract.status] ?? "outline", className: "text-[10px]", children: i18n.messages.bookingContractCard.contractStatusLabels[contract.status] })] }), _jsxs(Button, { type: "button", variant: "ghost", size: "sm", onClick: handleGenerate, disabled: isPending, children: [isPending ? (_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin" })) : (_jsx(RotateCw, { className: "h-3.5 w-3.5" })), _jsx("span", { className: "ml-1 text-xs", children: hasDocument ? labels.regenerate : labels.generate })] })] }), contract.issuedAt ? (_jsxs("p", { className: "text-[11px] text-muted-foreground", children: [labels.issuedAt, ": ", i18n.formatDate(contract.issuedAt)] })) : null, documentAttachments.length > 0 ? (_jsx("div", { className: "flex flex-col gap-1", children: documentAttachments.map((attachment) => (_jsx(AttachmentDownloadRow, { attachment: attachment, apiBaseUrl: apiBaseUrl, downloadLabel: labels.download }, attachment.id))) })) : (_jsx("p", { className: "text-[11px] text-muted-foreground", children: labels.noAttachments }))] }));
|
|
85
|
+
}
|
|
86
|
+
function withApiBaseUrl(baseUrl, path) {
|
|
87
|
+
const trimmedBase = baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
88
|
+
const normalizedPath = path.startsWith("/") ? path : `/${path}`;
|
|
89
|
+
return `${trimmedBase}${normalizedPath}`;
|
|
90
|
+
}
|
|
91
|
+
function AttachmentDownloadRow({ attachment, apiBaseUrl, downloadLabel, }) {
|
|
92
|
+
const i18n = useLegalUiI18nOrDefault();
|
|
93
|
+
// The download endpoint returns a 302 to the signed URL. A plain <a> link
|
|
94
|
+
// with target="_blank" lets the browser follow it and open the file in a
|
|
95
|
+
// new tab. The href uses the same API base as the data hooks by default.
|
|
96
|
+
const href = withApiBaseUrl(apiBaseUrl ?? "", `/v1/admin/legal/contracts/attachments/${attachment.id}/download`);
|
|
97
|
+
const sizeKb = typeof attachment.fileSize === "number"
|
|
98
|
+
? `${i18n.formatNumber(Math.round(attachment.fileSize / 1024))} ${i18n.messages.common.kilobytes}`
|
|
99
|
+
: null;
|
|
100
|
+
return (_jsxs("a", { href: href, target: "_blank", rel: "noopener noreferrer", className: "flex items-center justify-between gap-2 rounded-md border px-2 py-1.5 text-xs hover:bg-muted", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-1.5", children: [_jsx(FileText, { className: "h-3.5 w-3.5 shrink-0" }), _jsx("span", { className: "truncate", children: attachment.name }), sizeKb ? _jsxs("span", { className: "text-muted-foreground", children: ["\u00B7 ", sizeKb] }) : null] }), _jsxs("span", { className: "flex items-center gap-1 text-muted-foreground", children: [_jsx(Download, { className: "h-3 w-3" }), downloadLabel] })] }));
|
|
101
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import { type LegalContractAttachmentRecord, type LegalContractRecord } from "../index.js";
|
|
3
|
+
export interface ContractDetailPageProps {
|
|
4
|
+
id: string;
|
|
5
|
+
onBackToContracts?: () => void;
|
|
6
|
+
renderContractDialog?: (props: ContractDetailDialogRenderProps) => ReactNode;
|
|
7
|
+
renderReference?: (props: ContractReferenceRenderProps) => ReactNode;
|
|
8
|
+
getAttachmentDownloadHref?: (attachment: LegalContractAttachmentRecord) => string;
|
|
9
|
+
slots?: ContractDetailPageSlots;
|
|
10
|
+
/**
|
|
11
|
+
* Resolve the recipient email for the Send-contract dialog. Operator
|
|
12
|
+
* templates wire this to the linked CRM person's primary email; if
|
|
13
|
+
* unset the dialog displays a "no recipient" warning and disables Send.
|
|
14
|
+
*/
|
|
15
|
+
resolveSendRecipientEmail?: (contract: LegalContractRecord) => string | null | undefined;
|
|
16
|
+
}
|
|
17
|
+
export interface ContractDetailPageSlots {
|
|
18
|
+
detailsContent?: ReactNode;
|
|
19
|
+
partiesContent?: ReactNode;
|
|
20
|
+
signaturesContent?: ReactNode;
|
|
21
|
+
documentsContent?: ReactNode;
|
|
22
|
+
}
|
|
23
|
+
export type ContractReferenceKind = "person" | "organization" | "supplier" | "channel" | "booking" | "target" | "legacyTransactionOffer" | "legacyTransactionOrder" | "templateVersion" | "series";
|
|
24
|
+
export interface ContractReferenceRenderProps {
|
|
25
|
+
kind: ContractReferenceKind;
|
|
26
|
+
id: string;
|
|
27
|
+
contract: LegalContractRecord;
|
|
28
|
+
}
|
|
29
|
+
export interface ContractDetailDialogRenderProps {
|
|
30
|
+
open: boolean;
|
|
31
|
+
onOpenChange: (open: boolean) => void;
|
|
32
|
+
contract: LegalContractRecord;
|
|
33
|
+
onSuccess: () => void;
|
|
34
|
+
}
|
|
35
|
+
export declare function ContractDetailPage({ id, onBackToContracts, renderContractDialog, renderReference, getAttachmentDownloadHref, slots, resolveSendRecipientEmail, }: ContractDetailPageProps): import("react/jsx-runtime").JSX.Element;
|
|
36
|
+
//# sourceMappingURL=contract-detail-page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contract-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/contract-detail-page.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,OAAO,EACL,KAAK,6BAA6B,EAClC,KAAK,mBAAmB,EAOzB,MAAM,aAAa,CAAA;AA2DpB,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,MAAM,CAAA;IACV,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC9B,oBAAoB,CAAC,EAAE,CAAC,KAAK,EAAE,+BAA+B,KAAK,SAAS,CAAA;IAC5E,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,4BAA4B,KAAK,SAAS,CAAA;IACpE,yBAAyB,CAAC,EAAE,CAAC,UAAU,EAAE,6BAA6B,KAAK,MAAM,CAAA;IACjF,KAAK,CAAC,EAAE,uBAAuB,CAAA;IAC/B;;;;OAIG;IACH,yBAAyB,CAAC,EAAE,CAAC,QAAQ,EAAE,mBAAmB,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;CACzF;AAED,MAAM,WAAW,uBAAuB;IACtC,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,cAAc,CAAC,EAAE,SAAS,CAAA;IAC1B,iBAAiB,CAAC,EAAE,SAAS,CAAA;IAC7B,gBAAgB,CAAC,EAAE,SAAS,CAAA;CAC7B;AAED,MAAM,MAAM,qBAAqB,GAC7B,QAAQ,GACR,cAAc,GACd,UAAU,GACV,SAAS,GACT,SAAS,GACT,QAAQ,GACR,wBAAwB,GACxB,wBAAwB,GACxB,iBAAiB,GACjB,QAAQ,CAAA;AAEZ,MAAM,WAAW,4BAA4B;IAC3C,IAAI,EAAE,qBAAqB,CAAA;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,mBAAmB,CAAA;CAC9B;AAED,MAAM,WAAW,+BAA+B;IAC9C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,QAAQ,EAAE,mBAAmB,CAAA;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAA;CACtB;AAED,wBAAgB,kBAAkB,CAAC,EACjC,EAAE,EACF,iBAAiB,EACjB,oBAAoB,EACpB,eAAe,EACf,yBAAyB,EACzB,KAAK,EACL,yBAAyB,GAC1B,EAAE,uBAAuB,2CAgazB"}
|