@voyantjs/bookings-ui 0.108.1 → 0.109.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/booking-contract-dialog.d.ts +22 -0
- package/dist/admin/booking-contract-dialog.d.ts.map +1 -0
- package/dist/admin/booking-contract-dialog.js +161 -0
- package/dist/admin/booking-detail-host.d.ts +123 -0
- package/dist/admin/booking-detail-host.d.ts.map +1 -0
- package/dist/admin/booking-detail-host.js +143 -0
- package/dist/admin/booking-detail-skeleton.d.ts +7 -0
- package/dist/admin/booking-detail-skeleton.d.ts.map +1 -0
- package/dist/admin/booking-detail-skeleton.js +24 -0
- package/dist/admin/booking-documents-table.d.ts +13 -0
- package/dist/admin/booking-documents-table.d.ts.map +1 -0
- package/dist/admin/booking-documents-table.js +258 -0
- package/dist/admin/booking-invoice-sheet.d.ts +18 -0
- package/dist/admin/booking-invoice-sheet.d.ts.map +1 -0
- package/dist/admin/booking-invoice-sheet.js +101 -0
- package/dist/admin/bookings-host.d.ts +26 -0
- package/dist/admin/bookings-host.d.ts.map +1 -0
- package/dist/admin/bookings-host.js +18 -0
- package/dist/admin/bookings-list-skeleton.d.ts +10 -0
- package/dist/admin/bookings-list-skeleton.d.ts.map +1 -0
- package/dist/admin/bookings-list-skeleton.js +25 -0
- package/dist/admin/index.d.ts +197 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +187 -0
- package/dist/admin/person-bookings-widget.d.ts +13 -0
- package/dist/admin/person-bookings-widget.d.ts.map +1 -0
- package/dist/admin/person-bookings-widget.js +48 -0
- package/dist/admin/use-booking-action-ledger-events.d.ts +15 -0
- package/dist/admin/use-booking-action-ledger-events.d.ts.map +1 -0
- package/dist/admin/use-booking-action-ledger-events.js +66 -0
- package/package.json +38 -31
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface BookingContractDialogProps {
|
|
2
|
+
open: boolean;
|
|
3
|
+
onOpenChange: (open: boolean) => void;
|
|
4
|
+
bookingId: string;
|
|
5
|
+
bookingNumber?: string | null;
|
|
6
|
+
onSuccess?: () => void;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* "Add contract" dialog for a booking. Two modes:
|
|
10
|
+
*
|
|
11
|
+
* - **Generate** (default): hits the server-side preview branch of
|
|
12
|
+
* `/v1/admin/bookings/:id/generate-contract` (which runs the same
|
|
13
|
+
* template + variable build the customer would see at checkout)
|
|
14
|
+
* and renders the HTML in a sandboxed iframe. Confirm fires the
|
|
15
|
+
* full generate, which creates the contract row + persists the PDF.
|
|
16
|
+
*
|
|
17
|
+
* - **Upload**: operator picks a pre-signed PDF (e.g. countersigned
|
|
18
|
+
* copy). The dialog creates a `signed`-status contract row and
|
|
19
|
+
* attaches the uploaded file via the legal attachment upload route.
|
|
20
|
+
*/
|
|
21
|
+
export declare function BookingContractDialog({ open, onOpenChange, bookingId, bookingNumber, onSuccess, }: BookingContractDialogProps): import("react/jsx-runtime").JSX.Element;
|
|
22
|
+
//# sourceMappingURL=booking-contract-dialog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"booking-contract-dialog.d.ts","sourceRoot":"","sources":["../../src/admin/booking-contract-dialog.tsx"],"names":[],"mappings":"AA0BA,MAAM,WAAW,0BAA0B;IACzC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,aAAa,EACb,SAAS,GACV,EAAE,0BAA0B,2CA8N5B"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
4
|
+
import { useOperatorAdminMessages } from "@voyantjs/admin";
|
|
5
|
+
import { useBookingContractGenerationMutation } from "@voyantjs/bookings-react";
|
|
6
|
+
import { legalQueryKeys, useLegalContractAttachmentMutation, useLegalContractMutation, } from "@voyantjs/legal-react";
|
|
7
|
+
import { Button, Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, Input, Label, Skeleton, } from "@voyantjs/ui/components";
|
|
8
|
+
import { FileText, Loader2, Paperclip, X } from "lucide-react";
|
|
9
|
+
import { useEffect, useState } from "react";
|
|
10
|
+
/**
|
|
11
|
+
* "Add contract" dialog for a booking. Two modes:
|
|
12
|
+
*
|
|
13
|
+
* - **Generate** (default): hits the server-side preview branch of
|
|
14
|
+
* `/v1/admin/bookings/:id/generate-contract` (which runs the same
|
|
15
|
+
* template + variable build the customer would see at checkout)
|
|
16
|
+
* and renders the HTML in a sandboxed iframe. Confirm fires the
|
|
17
|
+
* full generate, which creates the contract row + persists the PDF.
|
|
18
|
+
*
|
|
19
|
+
* - **Upload**: operator picks a pre-signed PDF (e.g. countersigned
|
|
20
|
+
* copy). The dialog creates a `signed`-status contract row and
|
|
21
|
+
* attaches the uploaded file via the legal attachment upload route.
|
|
22
|
+
*/
|
|
23
|
+
export function BookingContractDialog({ open, onOpenChange, bookingId, bookingNumber, onSuccess, }) {
|
|
24
|
+
const t = useOperatorAdminMessages().bookings.detail.contractDialog;
|
|
25
|
+
const queryClient = useQueryClient();
|
|
26
|
+
const { create: createContract } = useLegalContractMutation();
|
|
27
|
+
const { upload: uploadAttachment } = useLegalContractAttachmentMutation();
|
|
28
|
+
const { preview, generate } = useBookingContractGenerationMutation(bookingId);
|
|
29
|
+
const [mode, setMode] = useState("generate");
|
|
30
|
+
const [uploading, setUploading] = useState(false);
|
|
31
|
+
const [error, setError] = useState(null);
|
|
32
|
+
// Upload form state
|
|
33
|
+
const [title, setTitle] = useState("");
|
|
34
|
+
const [file, setFile] = useState(null);
|
|
35
|
+
// Reset on open. Generate is the leading mode so the preview fetch
|
|
36
|
+
// kicks off immediately.
|
|
37
|
+
const resetPreview = preview.reset;
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!open)
|
|
40
|
+
return;
|
|
41
|
+
setMode("generate");
|
|
42
|
+
setTitle("");
|
|
43
|
+
setFile(null);
|
|
44
|
+
setError(null);
|
|
45
|
+
setUploading(false);
|
|
46
|
+
resetPreview();
|
|
47
|
+
}, [open, resetPreview]);
|
|
48
|
+
// Fetch preview HTML every time the dialog opens (or the mode flips
|
|
49
|
+
// back to Generate). The preview reflects current booking + template
|
|
50
|
+
// state so re-fetching is intentional.
|
|
51
|
+
const fetchPreview = preview.mutate;
|
|
52
|
+
useEffect(() => {
|
|
53
|
+
if (!open || mode !== "generate")
|
|
54
|
+
return;
|
|
55
|
+
fetchPreview();
|
|
56
|
+
}, [open, mode, fetchPreview]);
|
|
57
|
+
const handleGenerate = async () => {
|
|
58
|
+
setError(null);
|
|
59
|
+
try {
|
|
60
|
+
await generate.mutateAsync({});
|
|
61
|
+
await queryClient.invalidateQueries({ queryKey: legalQueryKeys.contracts() });
|
|
62
|
+
onSuccess?.();
|
|
63
|
+
onOpenChange(false);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const handleUpload = async () => {
|
|
70
|
+
if (!file) {
|
|
71
|
+
setError(t.uploadFileRequired);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
setUploading(true);
|
|
75
|
+
setError(null);
|
|
76
|
+
try {
|
|
77
|
+
const fallbackTitle = title.trim() ||
|
|
78
|
+
(bookingNumber
|
|
79
|
+
? `Contract ${bookingNumber}`
|
|
80
|
+
: `Contract for booking ${bookingId.slice(-8)}`);
|
|
81
|
+
const created = await createContract.mutateAsync({
|
|
82
|
+
scope: "customer",
|
|
83
|
+
status: "signed",
|
|
84
|
+
title: fallbackTitle,
|
|
85
|
+
bookingId,
|
|
86
|
+
metadata: { uploadedByOperator: true },
|
|
87
|
+
});
|
|
88
|
+
await uploadAttachment.mutateAsync({
|
|
89
|
+
contractId: created.id,
|
|
90
|
+
input: { file, kind: "document", name: file.name },
|
|
91
|
+
});
|
|
92
|
+
onSuccess?.();
|
|
93
|
+
onOpenChange(false);
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
setUploading(false);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const submitting = generate.isPending || uploading;
|
|
103
|
+
const previewReady = preview.data != null;
|
|
104
|
+
const canSubmit = mode === "generate" ? previewReady && !submitting : file != null && !submitting;
|
|
105
|
+
return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "w-full! max-w-4xl! gap-0 p-0", children: [_jsxs(DialogHeader, { className: "shrink-0 border-b px-6 py-4", children: [_jsx(DialogTitle, { children: t.title }), _jsx(DialogDescription, { children: t.description })] }), _jsx("div", { className: "max-h-[70vh] overflow-y-auto", children: _jsxs("div", { className: "flex flex-col gap-4 px-6 py-5", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.modeLabel }), _jsx(SegmentedChoice, { value: mode, onChange: setMode, options: [
|
|
106
|
+
{ value: "generate", label: t.modeGenerate },
|
|
107
|
+
{ value: "upload", label: t.modeUpload },
|
|
108
|
+
] })] }), mode === "generate" ? (_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.previewLabel }), _jsx("div", { className: "overflow-hidden rounded-md border bg-muted/30", children: preview.isPending ? (_jsxs("div", { className: "flex flex-col gap-3 p-6", children: [_jsx(Skeleton, { className: "h-6 w-1/2" }), _jsx(Skeleton, { className: "h-4 w-full" }), _jsx(Skeleton, { className: "h-4 w-5/6" }), _jsx(Skeleton, { className: "h-4 w-4/5" }), _jsx(Skeleton, { className: "h-4 w-full" })] })) : preview.isError ? (_jsxs("p", { className: "p-6 text-destructive text-sm", children: [t.previewErrorPrefix, " ", preview.error instanceof Error ? preview.error.message : t.previewFailed] })) : preview.data ? (_jsx("iframe", { title: preview.data.templateName || t.previewIframeFallback, srcDoc: wrapPreviewHtml(preview.data.html), sandbox: "", className: "h-[60vh] w-full border-0 bg-white" })) : null }), preview.data?.templateName ? (_jsxs("p", { className: "text-muted-foreground text-xs", children: [t.previewTemplateLabel, " ", preview.data.templateName] })) : null] })) : (_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.uploadTitleLabel }), _jsx(Input, { value: title, onChange: (e) => setTitle(e.target.value), placeholder: bookingNumber ? `Contract ${bookingNumber}` : t.uploadTitlePlaceholder }), _jsx("p", { className: "text-muted-foreground text-xs", children: t.uploadTitleHint })] }), _jsxs("div", { className: "flex flex-col gap-2", children: [_jsx(Label, { children: t.uploadFileLabel }), _jsx("input", { type: "file", accept: "application/pdf", onChange: (e) => {
|
|
109
|
+
const next = e.target.files?.[0] ?? null;
|
|
110
|
+
setFile(next);
|
|
111
|
+
}, className: "block w-full text-sm file:mr-3 file:rounded-md file:border file:bg-muted file:px-3 file:py-1.5 file:text-sm file:font-medium hover:file:bg-muted/70" }), file ? (_jsxs("div", { className: "flex items-center justify-between gap-2 rounded-md border bg-background px-3 py-1.5 text-sm", children: [_jsxs("span", { className: "flex min-w-0 items-center gap-2", children: [_jsx(Paperclip, { className: "h-3.5 w-3.5 shrink-0 text-muted-foreground" }), _jsx("span", { className: "truncate", children: file.name })] }), _jsx(Button, { type: "button", variant: "ghost", size: "icon-sm", onClick: () => setFile(null), children: _jsx(X, { className: "h-3.5 w-3.5" }) })] })) : null] })] })), error ? _jsx("p", { className: "text-sm text-destructive", children: error }) : null] }) }), _jsxs("div", { className: "flex shrink-0 items-center justify-end gap-2 border-t px-6 py-4", children: [_jsx(Button, { type: "button", variant: "outline", onClick: () => onOpenChange(false), disabled: submitting, children: t.cancel }), _jsxs(Button, { type: "button", disabled: !canSubmit, onClick: () => {
|
|
112
|
+
if (mode === "generate") {
|
|
113
|
+
void handleGenerate();
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
void handleUpload();
|
|
117
|
+
}
|
|
118
|
+
}, children: [submitting ? _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }) : null, mode === "generate" ? (_jsxs(_Fragment, { children: [_jsx(FileText, { className: "mr-1.5 h-3.5 w-3.5" }), t.generateAction] })) : (t.uploadAction)] })] })] }) }));
|
|
119
|
+
}
|
|
120
|
+
function SegmentedChoice({ value, onChange, options, }) {
|
|
121
|
+
return (_jsx("div", { className: "flex w-full rounded-md border bg-background p-0.5", children: options.map((opt) => {
|
|
122
|
+
const active = opt.value === value;
|
|
123
|
+
return (_jsx("button", { type: "button", onClick: () => onChange(opt.value), className: "flex-1 rounded-sm px-3 py-1.5 text-sm font-medium transition-colors " +
|
|
124
|
+
(active ? "bg-muted text-foreground" : "text-muted-foreground hover:text-foreground"), children: opt.label }, opt.value));
|
|
125
|
+
}) }));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Wrap the rendered template body in a light-theme HTML document so
|
|
129
|
+
* the iframe doesn't inherit the dark dashboard background. Mirrors
|
|
130
|
+
* the storefront's contract-preview wrapper to keep the WYSIWYG
|
|
131
|
+
* promise honest.
|
|
132
|
+
*/
|
|
133
|
+
function wrapPreviewHtml(body) {
|
|
134
|
+
return `<!DOCTYPE html>
|
|
135
|
+
<html lang="en">
|
|
136
|
+
<head>
|
|
137
|
+
<meta charset="utf-8" />
|
|
138
|
+
<style>
|
|
139
|
+
:root { color-scheme: light; }
|
|
140
|
+
html, body { margin: 0; background: #ffffff; color: #111827; }
|
|
141
|
+
body {
|
|
142
|
+
padding: 1.5rem 2rem;
|
|
143
|
+
font-family: ui-serif, Georgia, "Times New Roman", serif;
|
|
144
|
+
font-size: 15px;
|
|
145
|
+
line-height: 1.6;
|
|
146
|
+
}
|
|
147
|
+
h1, h2, h3 { font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; color: #0f172a; }
|
|
148
|
+
h1 { font-size: 1.5rem; margin: 0 0 1rem; }
|
|
149
|
+
h2 { font-size: 1.15rem; margin: 1.5rem 0 0.5rem; }
|
|
150
|
+
p { margin: 0.5rem 0; }
|
|
151
|
+
ul, ol { padding-left: 1.5rem; }
|
|
152
|
+
strong { color: #0f172a; }
|
|
153
|
+
a { color: #2563eb; }
|
|
154
|
+
table { border-collapse: collapse; width: 100%; margin: 0.75rem 0; }
|
|
155
|
+
th, td { border: 1px solid #e5e7eb; padding: 0.5rem 0.75rem; text-align: left; }
|
|
156
|
+
th { background: #f9fafb; }
|
|
157
|
+
</style>
|
|
158
|
+
</head>
|
|
159
|
+
<body>${body}</body>
|
|
160
|
+
</html>`;
|
|
161
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { type BookingRecord } from "@voyantjs/bookings-react";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import { type BookingDetailPageSlots, type BookingDetailTabValue } from "../components/booking-detail-page.js";
|
|
4
|
+
import type { BookingPaymentsSummaryRow } from "../components/booking-payments-summary.js";
|
|
5
|
+
/**
|
|
6
|
+
* Widget slot rendered as the booking detail page's Invoices tab
|
|
7
|
+
* (packaged-admin RFC §4.7 cycle resolution): `@voyantjs/finance-ui` depends
|
|
8
|
+
* on this package, so the host cannot import the finance-owned invoices card
|
|
9
|
+
* directly — instead finance's admin extension contributes a widget targeting
|
|
10
|
+
* this slot and the host mounts the tab whenever a contribution exists.
|
|
11
|
+
* Widgets receive {@link BookingDetailHostSlotContext} as props.
|
|
12
|
+
*/
|
|
13
|
+
export declare const bookingDetailInvoicesTabSlot = "booking.details.invoices-tab";
|
|
14
|
+
/**
|
|
15
|
+
* Widget slot rendered at the top of the booking detail page's Finance tab
|
|
16
|
+
* (same §4.7 cycle resolution as {@link bookingDetailInvoicesTabSlot}).
|
|
17
|
+
* `@voyantjs/finance-ui` contributes its pending payment-sessions card here.
|
|
18
|
+
* Widgets receive {@link BookingDetailHostSlotContext} as props.
|
|
19
|
+
*/
|
|
20
|
+
export declare const bookingDetailFinanceStartSlot = "booking.details.finance-start";
|
|
21
|
+
/**
|
|
22
|
+
* Widget slot rendered at the bottom of the booking detail page's Finance
|
|
23
|
+
* tab. `@voyantjs/finance-ui` contributes its payment-policy override card
|
|
24
|
+
* here. Widgets receive {@link BookingDetailHostSlotContext} as props.
|
|
25
|
+
*/
|
|
26
|
+
export declare const bookingDetailFinanceEndSlot = "booking.details.finance-end";
|
|
27
|
+
/**
|
|
28
|
+
* Render context handed to the host's app-supplied slots AND to widget
|
|
29
|
+
* contributions targeting {@link bookingDetailInvoicesTabSlot}. Carries the
|
|
30
|
+
* booking plus the host-computed payment aggregates and the invoice-sheet
|
|
31
|
+
* opener so contributed cards can participate without re-deriving them.
|
|
32
|
+
*/
|
|
33
|
+
export interface BookingDetailHostSlotContext {
|
|
34
|
+
booking: BookingRecord;
|
|
35
|
+
/** Customer payments summed across non-credit-note, non-draft invoices. */
|
|
36
|
+
paidAmountCents: number | null;
|
|
37
|
+
/**
|
|
38
|
+
* Localized "Booking is fully paid" reason when nothing is left to pay,
|
|
39
|
+
* else `null`. The host already uses it for the Record payment / Add
|
|
40
|
+
* schedule buttons; payment-link slots reuse it for their own gating.
|
|
41
|
+
*/
|
|
42
|
+
fullyPaidReason: string | null;
|
|
43
|
+
/** Open an invoice in the host's side sheet (stays on the booking page). */
|
|
44
|
+
openInvoiceSheet: (invoiceId: string) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Opens the host app's "Generate payment link" flow (a dialog the app
|
|
47
|
+
* owns — `@voyantjs/checkout-ui` depends on this package, so the host
|
|
48
|
+
* cannot import it without a cycle). Forwarded from
|
|
49
|
+
* {@link BookingDetailHostProps.onGenerateLink}; `undefined` when the app
|
|
50
|
+
* didn't wire one, in which case payment-link widgets hide the button.
|
|
51
|
+
*/
|
|
52
|
+
onGenerateLink: (() => void) | undefined;
|
|
53
|
+
}
|
|
54
|
+
export type BookingDetailHostSlot = (context: BookingDetailHostSlotContext) => ReactNode;
|
|
55
|
+
/**
|
|
56
|
+
* App-supplied extension points. The host owns everything package-clean
|
|
57
|
+
* (the Documents tab, the action-ledger timeline merge, the finance-tab
|
|
58
|
+
* widget slots); these slots remain for app-local additions on top.
|
|
59
|
+
*/
|
|
60
|
+
export interface BookingDetailHostSlots {
|
|
61
|
+
/** Top of the Finance tab, rendered before any widget contributions. */
|
|
62
|
+
financeStart?: BookingDetailHostSlot;
|
|
63
|
+
/** Bottom of the Finance tab, rendered before any widget contributions. */
|
|
64
|
+
financeEnd?: BookingDetailHostSlot;
|
|
65
|
+
/** Mounts a dedicated Invoices tab between Payments and Suppliers. */
|
|
66
|
+
invoicesTab?: {
|
|
67
|
+
label?: string;
|
|
68
|
+
content: BookingDetailHostSlot;
|
|
69
|
+
};
|
|
70
|
+
/** Replaces the Documents tab content (default: {@link BookingDocumentsTable}). */
|
|
71
|
+
documents?: BookingDetailHostSlot;
|
|
72
|
+
/** Extra events merged into the Activity-tab timeline (the host already
|
|
73
|
+
* merges the booking's central action-ledger entries). */
|
|
74
|
+
activityExtraEvents?: BookingDetailPageSlots["activityExtraEvents"];
|
|
75
|
+
/** Rendered below the activity timeline events, after the host's
|
|
76
|
+
* action-ledger pager. */
|
|
77
|
+
activityTimelineFooter?: ReactNode;
|
|
78
|
+
}
|
|
79
|
+
export interface BookingDetailHostProps {
|
|
80
|
+
id: string;
|
|
81
|
+
/** Controlled tab value; the route file mirrors it into the URL. */
|
|
82
|
+
activeTab?: BookingDetailTabValue;
|
|
83
|
+
onTabChange?: (tab: BookingDetailTabValue) => void;
|
|
84
|
+
/**
|
|
85
|
+
* Opens the app's record-payment flow (a dialog owned by the host app —
|
|
86
|
+
* the payment dialogs live app-side because `@voyantjs/finance-ui`
|
|
87
|
+
* depends on this package, so importing it here would be a cycle).
|
|
88
|
+
*/
|
|
89
|
+
onRecordPayment?: () => void;
|
|
90
|
+
/** Opens the app's record-payment flow pre-filled with the row. */
|
|
91
|
+
onEditPayment?: (row: BookingPaymentsSummaryRow) => void;
|
|
92
|
+
/**
|
|
93
|
+
* Opens the app's "Generate payment link" flow (a dialog owned by the
|
|
94
|
+
* host app — `@voyantjs/checkout-ui` depends on this package, so
|
|
95
|
+
* importing it here would be a cycle). Forwarded to slot/widget
|
|
96
|
+
* contributions via {@link BookingDetailHostSlotContext.onGenerateLink}.
|
|
97
|
+
*/
|
|
98
|
+
onGenerateLink?: () => void;
|
|
99
|
+
slots?: BookingDetailHostSlots;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Packaged admin host for the canonical `BookingDetailPage` (packaged-admin
|
|
103
|
+
* RFC Phase 3). Owns everything package-clean:
|
|
104
|
+
*
|
|
105
|
+
* - Cross-route links resolve through semantic destinations (RFC §4.7):
|
|
106
|
+
* `booking.list`, `person.detail`, `organization.detail`,
|
|
107
|
+
* `product.detail`, `availabilitySlot.detail`, `payment.detail`,
|
|
108
|
+
* `invoice.detail` — no host route tree import.
|
|
109
|
+
* - Admin chrome breadcrumbs (`useAdminBreadcrumbs`).
|
|
110
|
+
* - Admin widget extension points: the `booking.details.header` and
|
|
111
|
+
* `booking.details.after-summary` slots render through the shared
|
|
112
|
+
* `AdminWidgetSlotRenderer`, which reads the workspace shell's
|
|
113
|
+
* `AdminExtensionsProvider` context.
|
|
114
|
+
* - Paid-amount aggregation across the booking's invoices and the
|
|
115
|
+
* derived fully-paid disabled reasons.
|
|
116
|
+
* - Payment row delete (finance-react mutation) and the in-place
|
|
117
|
+
* invoice sheet ({@link BookingInvoiceSheet}).
|
|
118
|
+
*
|
|
119
|
+
* App-local cards without package-level data access stay injectable via
|
|
120
|
+
* {@link BookingDetailHostSlots}.
|
|
121
|
+
*/
|
|
122
|
+
export declare function BookingDetailHost({ id, activeTab, onTabChange, onRecordPayment, onEditPayment, onGenerateLink, slots, }: BookingDetailHostProps): import("react/jsx-runtime").JSX.Element;
|
|
123
|
+
//# sourceMappingURL=booking-detail-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"booking-detail-host.d.ts","sourceRoot":"","sources":["../../src/admin/booking-detail-host.tsx"],"names":[],"mappings":"AAYA,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,0BAA0B,CAAA;AAGzE,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAEhD,OAAO,EAEL,KAAK,sBAAsB,EAC3B,KAAK,qBAAqB,EAC3B,MAAM,sCAAsC,CAAA;AAC7C,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2CAA2C,CAAA;AAK1F;;;;;;;GAOG;AACH,eAAO,MAAM,4BAA4B,iCAAiC,CAAA;AAE1E;;;;;GAKG;AACH,eAAO,MAAM,6BAA6B,kCAAkC,CAAA;AAE5E;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,gCAAgC,CAAA;AAExE;;;;;GAKG;AACH,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,aAAa,CAAA;IACtB,2EAA2E;IAC3E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B;;;;OAIG;IACH,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,4EAA4E;IAC5E,gBAAgB,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAA;IAC7C;;;;;;OAMG;IACH,cAAc,EAAE,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAA;CACzC;AAED,MAAM,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,4BAA4B,KAAK,SAAS,CAAA;AAExF;;;;GAIG;AACH,MAAM,WAAW,sBAAsB;IACrC,wEAAwE;IACxE,YAAY,CAAC,EAAE,qBAAqB,CAAA;IACpC,2EAA2E;IAC3E,UAAU,CAAC,EAAE,qBAAqB,CAAA;IAClC,sEAAsE;IACtE,WAAW,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,qBAAqB,CAAA;KAAE,CAAA;IAChE,mFAAmF;IACnF,SAAS,CAAC,EAAE,qBAAqB,CAAA;IACjC;+DAC2D;IAC3D,mBAAmB,CAAC,EAAE,sBAAsB,CAAC,qBAAqB,CAAC,CAAA;IACnE;+BAC2B;IAC3B,sBAAsB,CAAC,EAAE,SAAS,CAAA;CACnC;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,oEAAoE;IACpE,SAAS,CAAC,EAAE,qBAAqB,CAAA;IACjC,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,qBAAqB,KAAK,IAAI,CAAA;IAClD;;;;OAIG;IACH,eAAe,CAAC,EAAE,MAAM,IAAI,CAAA;IAC5B,mEAAmE;IACnE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,IAAI,CAAA;IACxD;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,IAAI,CAAA;IAC3B,KAAK,CAAC,EAAE,sBAAsB,CAAA;CAC/B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,iBAAiB,CAAC,EAChC,EAAE,EACF,SAAS,EACT,WAAW,EACX,eAAe,EACf,aAAa,EACb,cAAc,EACd,KAAK,GACN,EAAE,sBAAsB,2CA0KxB"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { AdminWidgetSlotRenderer, resolveAdminWidgets, useAdminBreadcrumbs, useAdminExtensions, useAdminHref, useAdminNavigate, useLocale, useOperatorAdminMessages, } from "@voyantjs/admin";
|
|
4
|
+
import { useBooking } from "@voyantjs/bookings-react";
|
|
5
|
+
import { useInvoices, usePaymentMutation } from "@voyantjs/finance-react";
|
|
6
|
+
import { Sheet, SheetContent } from "@voyantjs/ui/components/sheet";
|
|
7
|
+
import { useState } from "react";
|
|
8
|
+
import { BookingDetailPage, } from "../components/booking-detail-page.js";
|
|
9
|
+
import { BookingDocumentsTable } from "./booking-documents-table.js";
|
|
10
|
+
import { BookingInvoiceSheet } from "./booking-invoice-sheet.js";
|
|
11
|
+
import { useBookingActionLedgerEvents } from "./use-booking-action-ledger-events.js";
|
|
12
|
+
/**
|
|
13
|
+
* Widget slot rendered as the booking detail page's Invoices tab
|
|
14
|
+
* (packaged-admin RFC §4.7 cycle resolution): `@voyantjs/finance-ui` depends
|
|
15
|
+
* on this package, so the host cannot import the finance-owned invoices card
|
|
16
|
+
* directly — instead finance's admin extension contributes a widget targeting
|
|
17
|
+
* this slot and the host mounts the tab whenever a contribution exists.
|
|
18
|
+
* Widgets receive {@link BookingDetailHostSlotContext} as props.
|
|
19
|
+
*/
|
|
20
|
+
export const bookingDetailInvoicesTabSlot = "booking.details.invoices-tab";
|
|
21
|
+
/**
|
|
22
|
+
* Widget slot rendered at the top of the booking detail page's Finance tab
|
|
23
|
+
* (same §4.7 cycle resolution as {@link bookingDetailInvoicesTabSlot}).
|
|
24
|
+
* `@voyantjs/finance-ui` contributes its pending payment-sessions card here.
|
|
25
|
+
* Widgets receive {@link BookingDetailHostSlotContext} as props.
|
|
26
|
+
*/
|
|
27
|
+
export const bookingDetailFinanceStartSlot = "booking.details.finance-start";
|
|
28
|
+
/**
|
|
29
|
+
* Widget slot rendered at the bottom of the booking detail page's Finance
|
|
30
|
+
* tab. `@voyantjs/finance-ui` contributes its payment-policy override card
|
|
31
|
+
* here. Widgets receive {@link BookingDetailHostSlotContext} as props.
|
|
32
|
+
*/
|
|
33
|
+
export const bookingDetailFinanceEndSlot = "booking.details.finance-end";
|
|
34
|
+
/**
|
|
35
|
+
* Packaged admin host for the canonical `BookingDetailPage` (packaged-admin
|
|
36
|
+
* RFC Phase 3). Owns everything package-clean:
|
|
37
|
+
*
|
|
38
|
+
* - Cross-route links resolve through semantic destinations (RFC §4.7):
|
|
39
|
+
* `booking.list`, `person.detail`, `organization.detail`,
|
|
40
|
+
* `product.detail`, `availabilitySlot.detail`, `payment.detail`,
|
|
41
|
+
* `invoice.detail` — no host route tree import.
|
|
42
|
+
* - Admin chrome breadcrumbs (`useAdminBreadcrumbs`).
|
|
43
|
+
* - Admin widget extension points: the `booking.details.header` and
|
|
44
|
+
* `booking.details.after-summary` slots render through the shared
|
|
45
|
+
* `AdminWidgetSlotRenderer`, which reads the workspace shell's
|
|
46
|
+
* `AdminExtensionsProvider` context.
|
|
47
|
+
* - Paid-amount aggregation across the booking's invoices and the
|
|
48
|
+
* derived fully-paid disabled reasons.
|
|
49
|
+
* - Payment row delete (finance-react mutation) and the in-place
|
|
50
|
+
* invoice sheet ({@link BookingInvoiceSheet}).
|
|
51
|
+
*
|
|
52
|
+
* App-local cards without package-level data access stay injectable via
|
|
53
|
+
* {@link BookingDetailHostSlots}.
|
|
54
|
+
*/
|
|
55
|
+
export function BookingDetailHost({ id, activeTab, onTabChange, onRecordPayment, onEditPayment, onGenerateLink, slots, }) {
|
|
56
|
+
const detailMessages = useOperatorAdminMessages().bookings.detail;
|
|
57
|
+
const { resolvedLocale } = useLocale();
|
|
58
|
+
const resolveHref = useAdminHref();
|
|
59
|
+
const navigateTo = useAdminNavigate();
|
|
60
|
+
// Finance (or any extension that may not import this package) contributes
|
|
61
|
+
// the Invoices-tab content as widget contributions; the tab mounts whenever
|
|
62
|
+
// an app slot or at least one widget targets it.
|
|
63
|
+
const adminExtensions = useAdminExtensions();
|
|
64
|
+
const hasInvoicesTabWidgets = resolveAdminWidgets({ slot: bookingDetailInvoicesTabSlot, extensions: adminExtensions })
|
|
65
|
+
.length > 0;
|
|
66
|
+
const [viewingInvoiceId, setViewingInvoiceId] = useState(null);
|
|
67
|
+
const { remove: removePayment } = usePaymentMutation();
|
|
68
|
+
// Mirror the booking fetch so the admin chrome can render breadcrumbs
|
|
69
|
+
// without prop-drilling through the canonical page. TanStack Query
|
|
70
|
+
// dedupes by key, so this doesn't issue a second network request.
|
|
71
|
+
const { data: bookingData } = useBooking(id);
|
|
72
|
+
const booking = bookingData?.data;
|
|
73
|
+
// Sum customer payments across this booking's non-credit-note,
|
|
74
|
+
// non-draft invoices.
|
|
75
|
+
const { data: invoicesData } = useInvoices({ bookingId: id, limit: 20 });
|
|
76
|
+
const paidAmountCents = invoicesData?.data
|
|
77
|
+
? invoicesData.data
|
|
78
|
+
.filter((inv) => {
|
|
79
|
+
const type = inv.invoiceType ?? "invoice";
|
|
80
|
+
return type !== "credit_note" && inv.status !== "draft";
|
|
81
|
+
})
|
|
82
|
+
.reduce((sum, inv) => sum + (inv.paidCents ?? 0), 0)
|
|
83
|
+
: null;
|
|
84
|
+
const fullyPaidReason = booking &&
|
|
85
|
+
booking.sellAmountCents != null &&
|
|
86
|
+
paidAmountCents != null &&
|
|
87
|
+
paidAmountCents >= booking.sellAmountCents
|
|
88
|
+
? detailMessages.generateLinkFullyPaid
|
|
89
|
+
: null;
|
|
90
|
+
const bookingsHref = resolveHref("booking.list", {});
|
|
91
|
+
useAdminBreadcrumbs(booking
|
|
92
|
+
? [
|
|
93
|
+
{ label: detailMessages.breadcrumbBookings, href: bookingsHref },
|
|
94
|
+
{ label: booking.bookingNumber },
|
|
95
|
+
]
|
|
96
|
+
: [{ label: detailMessages.breadcrumbBookings, href: bookingsHref }]);
|
|
97
|
+
const slotContext = (b) => ({
|
|
98
|
+
booking: b,
|
|
99
|
+
paidAmountCents,
|
|
100
|
+
fullyPaidReason,
|
|
101
|
+
openInvoiceSheet: setViewingInvoiceId,
|
|
102
|
+
onGenerateLink,
|
|
103
|
+
});
|
|
104
|
+
// Central action-ledger entries merged into the Activity timeline —
|
|
105
|
+
// package-owned since the feed ships from `@voyantjs/bookings-react`.
|
|
106
|
+
const { events: actionLedgerEvents, footer: actionLedgerFooter } = useBookingActionLedgerEvents(id);
|
|
107
|
+
const activityExtraEvents = slots?.activityExtraEvents
|
|
108
|
+
? [...actionLedgerEvents, ...slots.activityExtraEvents]
|
|
109
|
+
: actionLedgerEvents;
|
|
110
|
+
return (_jsxs(_Fragment, { children: [_jsx(BookingDetailPage, { id: id, locale: resolvedLocale, hideBreadcrumb: true, activeTab: activeTab, onTabChange: onTabChange, onBack: () => navigateTo("booking.list", {}), onPersonOpen: (personId) => navigateTo("person.detail", { personId }), onOrganizationOpen: (organizationId) => navigateTo("organization.detail", { organizationId }), onRecordPayment: onRecordPayment ? () => onRecordPayment() : undefined, recordPaymentDisabledReason: fullyPaidReason, addScheduleDisabledReason: fullyPaidReason, paidAmountCents: paidAmountCents, onItemResourceOpen: (kind, resourceId) => {
|
|
111
|
+
if (kind === "product") {
|
|
112
|
+
navigateTo("product.detail", { productId: resourceId });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (kind === "availabilitySlot") {
|
|
116
|
+
navigateTo("availabilitySlot.detail", { slotId: resourceId });
|
|
117
|
+
}
|
|
118
|
+
}, onInvoiceOpen: (invoiceId) => setViewingInvoiceId(invoiceId), onViewPayment: (row) => navigateTo("payment.detail", { paymentId: row.id }), onEditPayment: onEditPayment, onDeletePayment: async (row) => {
|
|
119
|
+
await removePayment.mutateAsync(row.id);
|
|
120
|
+
}, slots: {
|
|
121
|
+
header: (b) => (_jsx(AdminWidgetSlotRenderer, { slot: "booking.details.header", props: { booking: b } })),
|
|
122
|
+
afterSummary: (b) => (_jsx(AdminWidgetSlotRenderer, { slot: "booking.details.after-summary", props: { booking: b } })),
|
|
123
|
+
financeStart: (b) => (_jsxs(_Fragment, { children: [slots?.financeStart?.(slotContext(b)), _jsx(AdminWidgetSlotRenderer, { slot: bookingDetailFinanceStartSlot, props: { ...slotContext(b) } })] })),
|
|
124
|
+
financeEnd: (b) => (_jsxs(_Fragment, { children: [slots?.financeEnd?.(slotContext(b)), _jsx(AdminWidgetSlotRenderer, { slot: bookingDetailFinanceEndSlot, props: { ...slotContext(b) } })] })),
|
|
125
|
+
invoicesTab: slots?.invoicesTab || hasInvoicesTabWidgets
|
|
126
|
+
? {
|
|
127
|
+
label: slots?.invoicesTab?.label,
|
|
128
|
+
content: (b) => (_jsxs(_Fragment, { children: [slots?.invoicesTab?.content(slotContext(b)), _jsx(AdminWidgetSlotRenderer, { slot: bookingDetailInvoicesTabSlot, props: { ...slotContext(b) } })] })),
|
|
129
|
+
}
|
|
130
|
+
: undefined,
|
|
131
|
+
documents: slots?.documents
|
|
132
|
+
? (b) => slots.documents?.(slotContext(b))
|
|
133
|
+
: () => _jsx(BookingDocumentsTable, { bookingId: id }),
|
|
134
|
+
activityExtraEvents,
|
|
135
|
+
activityTimelineFooter: actionLedgerFooter || slots?.activityTimelineFooter ? (_jsxs(_Fragment, { children: [actionLedgerFooter, slots?.activityTimelineFooter] })) : undefined,
|
|
136
|
+
} }), _jsx(Sheet, { open: Boolean(viewingInvoiceId), onOpenChange: (open) => {
|
|
137
|
+
if (!open)
|
|
138
|
+
setViewingInvoiceId(null);
|
|
139
|
+
}, children: _jsx(SheetContent, { side: "right", className: "w-full! max-w-5xl!", children: viewingInvoiceId ? (_jsx(BookingInvoiceSheet, { invoiceId: viewingInvoiceId, onOpenInvoice: (invoiceId) => {
|
|
140
|
+
setViewingInvoiceId(null);
|
|
141
|
+
navigateTo("invoice.detail", { invoiceId });
|
|
142
|
+
} })) : null }) })] }));
|
|
143
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-level placeholder for the booking detail page. Mirrors the
|
|
3
|
+
* canonical `BookingDetailPage` layout (header row, summary card, tab
|
|
4
|
+
* bar, two list cards) so the pending state doesn't shift the page.
|
|
5
|
+
*/
|
|
6
|
+
export declare function BookingDetailSkeleton(): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=booking-detail-skeleton.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"booking-detail-skeleton.d.ts","sourceRoot":"","sources":["../../src/admin/booking-detail-skeleton.tsx"],"names":[],"mappings":"AAGA;;;;GAIG;AACH,wBAAgB,qBAAqB,4CAUpC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Card, CardContent, CardHeader } from "@voyantjs/ui/components/card";
|
|
3
|
+
import { Skeleton } from "@voyantjs/ui/components/skeleton";
|
|
4
|
+
/**
|
|
5
|
+
* Route-level placeholder for the booking detail page. Mirrors the
|
|
6
|
+
* canonical `BookingDetailPage` layout (header row, summary card, tab
|
|
7
|
+
* bar, two list cards) so the pending state doesn't shift the page.
|
|
8
|
+
*/
|
|
9
|
+
export function BookingDetailSkeleton() {
|
|
10
|
+
return (_jsxs("div", { className: "flex flex-col gap-6 p-6", children: [_jsx(Header, {}), _jsx(SummaryCard, {}), _jsx(TabsBar, {}), _jsx(ListCard, { titleWidth: "w-32", rows: 3 }), _jsx(ListCard, { titleWidth: "w-28", rows: 2 })] }));
|
|
11
|
+
}
|
|
12
|
+
function Header() {
|
|
13
|
+
return (_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Skeleton, { className: "h-7 w-44" }), _jsx(Skeleton, { className: "h-5 w-20 rounded-full" })] }), _jsx(Skeleton, { className: "h-8 w-8 rounded-md" })] }));
|
|
14
|
+
}
|
|
15
|
+
function SummaryCard() {
|
|
16
|
+
return (_jsx(Card, { children: _jsx(CardContent, { className: "grid grid-cols-2 gap-6 py-6 sm:grid-cols-4", children: Array.from({ length: 8 }).map((_, i) => (_jsxs("div", { className: "flex flex-col gap-1.5", children: [_jsx(Skeleton, { className: "h-3 w-20" }), _jsx(Skeleton, { className: "h-5 w-28" }), _jsx(Skeleton, { className: "h-3 w-12" })] }, i))) }) }));
|
|
17
|
+
}
|
|
18
|
+
function TabsBar() {
|
|
19
|
+
const widths = ["w-20", "w-20", "w-16", "w-20", "w-24", "w-16"];
|
|
20
|
+
return (_jsx("div", { className: "flex h-9 w-fit items-center gap-1 rounded-lg bg-muted p-[3px]", children: widths.map((w, i) => (_jsx(Skeleton, { className: `h-7 rounded-md ${w}` }, i))) }));
|
|
21
|
+
}
|
|
22
|
+
function ListCard({ titleWidth, rows }) {
|
|
23
|
+
return (_jsxs(Card, { children: [_jsxs(CardHeader, { className: "flex flex-row items-center justify-between", children: [_jsx(Skeleton, { className: `h-5 ${titleWidth}` }), _jsx(Skeleton, { className: "h-8 w-24" })] }), _jsx(CardContent, { className: "space-y-2", children: Array.from({ length: rows }).map((_, i) => (_jsxs("div", { className: "flex items-center justify-between rounded-md border px-3 py-3", children: [_jsxs("div", { className: "space-y-1.5", children: [_jsx(Skeleton, { className: "h-4 w-48" }), _jsx(Skeleton, { className: "h-3 w-32" })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Skeleton, { className: "h-4 w-16" }), _jsx(Skeleton, { className: "h-5 w-16 rounded-full" })] })] }, i))) })] }));
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface BookingDocumentsTableProps {
|
|
2
|
+
bookingId: string;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Unified Documents tab for a booking — flattens auto-generated legal
|
|
6
|
+
* contracts and per-traveler documents (passport, visa, insurance…) into
|
|
7
|
+
* a single DataTable that matches the rest of the booking detail tabs.
|
|
8
|
+
* Contracts render first (canonical booking docs), traveler-uploaded
|
|
9
|
+
* documents below; each contract row owns its own attachments fetch so
|
|
10
|
+
* we don't need a join endpoint server-side.
|
|
11
|
+
*/
|
|
12
|
+
export declare function BookingDocumentsTable({ bookingId, }: BookingDocumentsTableProps): React.ReactElement;
|
|
13
|
+
//# sourceMappingURL=booking-documents-table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"booking-documents-table.d.ts","sourceRoot":"","sources":["../../src/admin/booking-documents-table.tsx"],"names":[],"mappings":"AA8CA,MAAM,WAAW,0BAA0B;IACzC,SAAS,EAAE,MAAM,CAAA;CAClB;AAmDD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,GACV,EAAE,0BAA0B,GAAG,KAAK,CAAC,YAAY,CA6MjD"}
|