@voyantjs/bookings-ui 0.108.0 → 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.
Files changed (31) hide show
  1. package/dist/admin/booking-contract-dialog.d.ts +22 -0
  2. package/dist/admin/booking-contract-dialog.d.ts.map +1 -0
  3. package/dist/admin/booking-contract-dialog.js +161 -0
  4. package/dist/admin/booking-detail-host.d.ts +123 -0
  5. package/dist/admin/booking-detail-host.d.ts.map +1 -0
  6. package/dist/admin/booking-detail-host.js +143 -0
  7. package/dist/admin/booking-detail-skeleton.d.ts +7 -0
  8. package/dist/admin/booking-detail-skeleton.d.ts.map +1 -0
  9. package/dist/admin/booking-detail-skeleton.js +24 -0
  10. package/dist/admin/booking-documents-table.d.ts +13 -0
  11. package/dist/admin/booking-documents-table.d.ts.map +1 -0
  12. package/dist/admin/booking-documents-table.js +258 -0
  13. package/dist/admin/booking-invoice-sheet.d.ts +18 -0
  14. package/dist/admin/booking-invoice-sheet.d.ts.map +1 -0
  15. package/dist/admin/booking-invoice-sheet.js +101 -0
  16. package/dist/admin/bookings-host.d.ts +26 -0
  17. package/dist/admin/bookings-host.d.ts.map +1 -0
  18. package/dist/admin/bookings-host.js +18 -0
  19. package/dist/admin/bookings-list-skeleton.d.ts +10 -0
  20. package/dist/admin/bookings-list-skeleton.d.ts.map +1 -0
  21. package/dist/admin/bookings-list-skeleton.js +25 -0
  22. package/dist/admin/index.d.ts +197 -0
  23. package/dist/admin/index.d.ts.map +1 -0
  24. package/dist/admin/index.js +187 -0
  25. package/dist/admin/person-bookings-widget.d.ts +13 -0
  26. package/dist/admin/person-bookings-widget.d.ts.map +1 -0
  27. package/dist/admin/person-bookings-widget.js +48 -0
  28. package/dist/admin/use-booking-action-ledger-events.d.ts +15 -0
  29. package/dist/admin/use-booking-action-ledger-events.d.ts.map +1 -0
  30. package/dist/admin/use-booking-action-ledger-events.js +66 -0
  31. package/package.json +38 -31
@@ -0,0 +1,197 @@
1
+ import { type AdminExtension } from "@voyantjs/admin";
2
+ import { z } from "zod";
3
+ import type { BookingDetailTabValue } from "../components/booking-detail-page.js";
4
+ import type { BookingListFiltersState } from "../components/booking-list.js";
5
+ /**
6
+ * Semantic destinations the bookings admin surfaces navigate to
7
+ * (packaged-admin RFC §4.7). The booking pages link into routes they do not
8
+ * own — the CRM person/organization pages, the product editor, the finance
9
+ * payment/invoice pages — so instead of importing a host route tree they
10
+ * resolve these keys through `useAdminHref`/`useAdminNavigate` from
11
+ * `@voyantjs/admin`. Hosts register one resolver per key
12
+ * (`satisfies AdminDestinationResolvers`).
13
+ *
14
+ * `booking.detail`/`booking.list`/`booking.create` are declared here even
15
+ * though bookings pages are their first consumers: other domains' packaged
16
+ * pages navigate TO bookings through the same keys.
17
+ */
18
+ declare module "@voyantjs/admin" {
19
+ interface AdminDestinations {
20
+ /** The bookings list page. */
21
+ "booking.list": Record<string, never>;
22
+ /** A booking's detail page; `tab` deep-links a specific tab. */
23
+ "booking.detail": {
24
+ bookingId: string;
25
+ tab?: BookingDetailTabValue;
26
+ };
27
+ /** The "New booking" entry point (product picker → unified journey). */
28
+ "booking.create": Record<string, never>;
29
+ /** A CRM person's detail page. */
30
+ "person.detail": {
31
+ personId: string;
32
+ };
33
+ /** A CRM organization's detail page. */
34
+ "organization.detail": {
35
+ organizationId: string;
36
+ };
37
+ /**
38
+ * The owned-product editor/detail page. Also declared by
39
+ * `@voyantjs/catalog-ui/admin` — interface merging requires the member
40
+ * shape to stay identical across packages.
41
+ */
42
+ "product.detail": {
43
+ productId: string;
44
+ };
45
+ /** An availability slot's detail page. */
46
+ "availabilitySlot.detail": {
47
+ slotId: string;
48
+ };
49
+ /** A payment's detail page in the finance area. */
50
+ "payment.detail": {
51
+ paymentId: string;
52
+ };
53
+ /** An invoice's full detail page in the finance area. */
54
+ "invoice.detail": {
55
+ invoiceId: string;
56
+ };
57
+ /**
58
+ * A legal contract's detail page. Also declared by
59
+ * `@voyantjs/legal-ui/admin` — interface merging requires the member
60
+ * shape to stay identical across packages. Declared here because the
61
+ * booking Documents tab links contract rows to their detail page.
62
+ */
63
+ "contract.detail": {
64
+ contractId: string;
65
+ };
66
+ }
67
+ }
68
+ export { BookingContractDialog, type BookingContractDialogProps, } from "./booking-contract-dialog.js";
69
+ export { BookingDetailHost, type BookingDetailHostProps, type BookingDetailHostSlot, type BookingDetailHostSlotContext, type BookingDetailHostSlots, bookingDetailFinanceEndSlot, bookingDetailFinanceStartSlot, bookingDetailInvoicesTabSlot, } from "./booking-detail-host.js";
70
+ export { BookingDetailSkeleton } from "./booking-detail-skeleton.js";
71
+ export { BookingDocumentsTable, type BookingDocumentsTableProps, } from "./booking-documents-table.js";
72
+ export { BookingInvoiceSheet, type BookingInvoiceSheetProps, } from "./booking-invoice-sheet.js";
73
+ export { BookingsHost, type BookingsHostProps } from "./bookings-host.js";
74
+ export { BookingsListSkeleton } from "./bookings-list-skeleton.js";
75
+ export { PersonBookingsWidget, type PersonBookingsWidgetProps, } from "./person-bookings-widget.js";
76
+ export { useBookingActionLedgerEvents } from "./use-booking-action-ledger-events.js";
77
+ /**
78
+ * Search contract for the bookings list page: the URL projection of
79
+ * `BookingListFiltersState` (filters, sort, paging). Package-owned so the
80
+ * route file, the host, and the extension contribution validate the same
81
+ * shape. Defaults are absent from the URL — see
82
+ * {@link bookingsFiltersToSearch}.
83
+ */
84
+ export declare const bookingsIndexSearchSchema: z.ZodObject<{
85
+ search: z.ZodOptional<z.ZodString>;
86
+ status: z.ZodOptional<z.ZodString>;
87
+ productId: z.ZodOptional<z.ZodString>;
88
+ optionId: z.ZodOptional<z.ZodString>;
89
+ supplierId: z.ZodOptional<z.ZodString>;
90
+ productCategoryId: z.ZodOptional<z.ZodString>;
91
+ personId: z.ZodOptional<z.ZodString>;
92
+ organizationId: z.ZodOptional<z.ZodString>;
93
+ availabilitySlotId: z.ZodOptional<z.ZodString>;
94
+ dateFrom: z.ZodOptional<z.ZodString>;
95
+ dateTo: z.ZodOptional<z.ZodString>;
96
+ paxMin: z.ZodOptional<z.ZodString>;
97
+ paxMax: z.ZodOptional<z.ZodString>;
98
+ sortBy: z.ZodOptional<z.ZodEnum<{
99
+ createdAt: "createdAt";
100
+ bookingNumber: "bookingNumber";
101
+ status: "status";
102
+ sellAmount: "sellAmount";
103
+ pax: "pax";
104
+ startDate: "startDate";
105
+ endDate: "endDate";
106
+ }>>;
107
+ sortDir: z.ZodOptional<z.ZodEnum<{
108
+ asc: "asc";
109
+ desc: "desc";
110
+ }>>;
111
+ offset: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
112
+ }, z.core.$strip>;
113
+ export type BookingsIndexSearchParams = z.infer<typeof bookingsIndexSearchSchema>;
114
+ /** URL search params → `BookingList` initial state. Empty / `"all"` /
115
+ * default values are absent in the URL; we let `BookingList`'s defaults
116
+ * fill them in. */
117
+ export declare function bookingsSearchToFilters(search: BookingsIndexSearchParams): Partial<BookingListFiltersState>;
118
+ /** Project the filter snapshot back into URL search params, dropping
119
+ * any value that matches the component's default so the URL stays
120
+ * clean when the operator is viewing the unfiltered list. */
121
+ export declare function bookingsFiltersToSearch(filters: BookingListFiltersState): BookingsIndexSearchParams;
122
+ /** Tab values of the canonical `BookingDetailPage`, as a search-param schema. */
123
+ export declare const bookingDetailTabSchema: z.ZodEnum<{
124
+ activity: "activity";
125
+ metadata: "metadata";
126
+ items: "items";
127
+ travelers: "travelers";
128
+ documents: "documents";
129
+ suppliers: "suppliers";
130
+ invoices: "invoices";
131
+ finance: "finance";
132
+ }>;
133
+ /**
134
+ * Search contract for the booking detail page. `productId`/`slotId` only
135
+ * matter for the `"new"` pseudo-id: a deep link with a product pre-chosen
136
+ * redirects into the unified booking journey (host route concern).
137
+ */
138
+ export declare const bookingDetailSearchSchema: z.ZodObject<{
139
+ productId: z.ZodOptional<z.ZodString>;
140
+ slotId: z.ZodOptional<z.ZodString>;
141
+ tab: z.ZodOptional<z.ZodEnum<{
142
+ activity: "activity";
143
+ metadata: "metadata";
144
+ items: "items";
145
+ travelers: "travelers";
146
+ documents: "documents";
147
+ suppliers: "suppliers";
148
+ invoices: "invoices";
149
+ finance: "finance";
150
+ }>>;
151
+ }, z.core.$strip>;
152
+ export type BookingDetailSearchParams = z.infer<typeof bookingDetailSearchSchema>;
153
+ export interface CreateBookingsAdminExtensionOptions {
154
+ /** Mount path of the bookings pages inside the admin workspace. Default `/bookings`. */
155
+ basePath?: string;
156
+ /** Localized page titles. Defaults are the English operator nav labels. */
157
+ labels?: {
158
+ bookings?: string;
159
+ };
160
+ }
161
+ /**
162
+ * The bookings admin contribution (packaged-admin RFC Phase 3,
163
+ * `@voyantjs/<domain>-ui/admin` convention).
164
+ *
165
+ * NAVIGATION: deliberately none. The Bookings nav item is part of the BASE
166
+ * operator navigation — see `createOperatorAdminNavigation` in
167
+ * `@voyantjs/admin` — so contributing nav entries here would duplicate it.
168
+ * If the base nav ever drops the bookings item, this extension is where the
169
+ * entry moves.
170
+ *
171
+ * ROUTES: contributions are metadata + the package-owned search contracts
172
+ * ({@link bookingsIndexSearchSchema} for the list,
173
+ * {@link bookingDetailSearchSchema} for the detail page). The PAGES are
174
+ * package-owned too: {@link BookingsHost} and {@link BookingDetailHost}
175
+ * bind the canonical bookings pages to their data wiring (bookings/finance
176
+ * provider context) and resolve every cross-route link through the semantic
177
+ * destinations declared above — no app RPC client, no host route tree.
178
+ *
179
+ * `component:` is intentionally NOT attached to these contributions yet:
180
+ * the contribution contract renders zero-prop pages (route components read
181
+ * params via the router, per RFC §4.2), while both bookings hosts take
182
+ * route params/search state as props. Host route files stay the thin
183
+ * binding layer (`Route.useParams()`/`Route.useSearch()` → host props)
184
+ * until the §4.2 code-based route assembly gives packaged pages a
185
+ * router-agnostic way to read route state.
186
+ *
187
+ * WIDGETS: the crm-ui ↔ bookings-ui cycle resolution (RFC §4.7). The CRM
188
+ * person detail page mounts a Bookings tab, but this package depends on
189
+ * `@voyantjs/crm-ui`, so crm-ui's host cannot import the bookings-owned
190
+ * card. Instead this extension contributes {@link PersonBookingsWidget} on
191
+ * the `person.details.bookings-tab` slot crm-ui's `PersonDetailHost`
192
+ * exposes; the host mounts its Bookings tab whenever a contribution targets
193
+ * that slot and hands the widget its typed slot context
194
+ * (`PersonDetailBookingsTabContext`) as props.
195
+ */
196
+ export declare function createBookingsAdminExtension(options?: CreateBookingsAdminExtensionOptions): AdminExtension;
197
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/admin/index.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,187 @@
1
+ import { defineAdminExtension, } from "@voyantjs/admin";
2
+ // Importing the slot id also binds the crm-ui `AdminDestinations`
3
+ // augmentation (`person.list`, `organization.list`, ...) into this program;
4
+ // this package already peer-depends on `@voyantjs/crm-ui`.
5
+ import { personDetailBookingsTabSlot } from "@voyantjs/crm-ui/admin";
6
+ import { z } from "zod";
7
+ import { PersonBookingsWidget } from "./person-bookings-widget.js";
8
+ // Packaged admin hosts (packaged-admin RFC Phase 3): the bookings pages
9
+ // bound to their data wiring + semantic-destination navigation. Host route
10
+ // files only bind route params/search state onto these.
11
+ export { BookingContractDialog, } from "./booking-contract-dialog.js";
12
+ export { BookingDetailHost, bookingDetailFinanceEndSlot, bookingDetailFinanceStartSlot, bookingDetailInvoicesTabSlot, } from "./booking-detail-host.js";
13
+ export { BookingDetailSkeleton } from "./booking-detail-skeleton.js";
14
+ export { BookingDocumentsTable, } from "./booking-documents-table.js";
15
+ export { BookingInvoiceSheet, } from "./booking-invoice-sheet.js";
16
+ export { BookingsHost } from "./bookings-host.js";
17
+ export { BookingsListSkeleton } from "./bookings-list-skeleton.js";
18
+ export { PersonBookingsWidget, } from "./person-bookings-widget.js";
19
+ export { useBookingActionLedgerEvents } from "./use-booking-action-ledger-events.js";
20
+ const bookingsListSortBySchema = z.enum([
21
+ "bookingNumber",
22
+ "status",
23
+ "sellAmount",
24
+ "pax",
25
+ "startDate",
26
+ "endDate",
27
+ "createdAt",
28
+ ]);
29
+ const bookingsListSortDirSchema = z.enum(["asc", "desc"]);
30
+ /**
31
+ * Search contract for the bookings list page: the URL projection of
32
+ * `BookingListFiltersState` (filters, sort, paging). Package-owned so the
33
+ * route file, the host, and the extension contribution validate the same
34
+ * shape. Defaults are absent from the URL — see
35
+ * {@link bookingsFiltersToSearch}.
36
+ */
37
+ export const bookingsIndexSearchSchema = z.object({
38
+ search: z.string().optional(),
39
+ status: z.string().optional(),
40
+ productId: z.string().optional(),
41
+ optionId: z.string().optional(),
42
+ supplierId: z.string().optional(),
43
+ productCategoryId: z.string().optional(),
44
+ personId: z.string().optional(),
45
+ organizationId: z.string().optional(),
46
+ availabilitySlotId: z.string().optional(),
47
+ dateFrom: z.string().optional(),
48
+ dateTo: z.string().optional(),
49
+ paxMin: z.string().optional(),
50
+ paxMax: z.string().optional(),
51
+ sortBy: bookingsListSortBySchema.optional(),
52
+ sortDir: bookingsListSortDirSchema.optional(),
53
+ offset: z.coerce.number().int().min(0).optional(),
54
+ });
55
+ /** URL search params → `BookingList` initial state. Empty / `"all"` /
56
+ * default values are absent in the URL; we let `BookingList`'s defaults
57
+ * fill them in. */
58
+ export function bookingsSearchToFilters(search) {
59
+ return {
60
+ search: search.search,
61
+ status: search.status,
62
+ productId: search.productId ?? null,
63
+ optionId: search.optionId ?? null,
64
+ supplierId: search.supplierId ?? null,
65
+ productCategoryId: search.productCategoryId ?? null,
66
+ personId: search.personId ?? null,
67
+ organizationId: search.organizationId ?? null,
68
+ availabilitySlotId: search.availabilitySlotId ?? null,
69
+ dateFrom: search.dateFrom ?? null,
70
+ dateTo: search.dateTo ?? null,
71
+ paxMin: search.paxMin,
72
+ paxMax: search.paxMax,
73
+ sortBy: search.sortBy,
74
+ sortDir: search.sortDir,
75
+ offset: search.offset,
76
+ };
77
+ }
78
+ /** Project the filter snapshot back into URL search params, dropping
79
+ * any value that matches the component's default so the URL stays
80
+ * clean when the operator is viewing the unfiltered list. */
81
+ export function bookingsFiltersToSearch(filters) {
82
+ return {
83
+ search: filters.search || undefined,
84
+ status: filters.status === "all" ? undefined : filters.status,
85
+ productId: filters.productId ?? undefined,
86
+ optionId: filters.optionId ?? undefined,
87
+ supplierId: filters.supplierId ?? undefined,
88
+ productCategoryId: filters.productCategoryId ?? undefined,
89
+ personId: filters.personId ?? undefined,
90
+ organizationId: filters.organizationId ?? undefined,
91
+ availabilitySlotId: filters.availabilitySlotId ?? undefined,
92
+ dateFrom: filters.dateFrom ?? undefined,
93
+ dateTo: filters.dateTo ?? undefined,
94
+ paxMin: filters.paxMin || undefined,
95
+ paxMax: filters.paxMax || undefined,
96
+ sortBy: filters.sortBy === "createdAt" ? undefined : filters.sortBy,
97
+ sortDir: filters.sortDir === "desc" ? undefined : filters.sortDir,
98
+ offset: filters.offset === 0 ? undefined : filters.offset,
99
+ };
100
+ }
101
+ /** Tab values of the canonical `BookingDetailPage`, as a search-param schema. */
102
+ export const bookingDetailTabSchema = z.enum([
103
+ "items",
104
+ "travelers",
105
+ "finance",
106
+ "invoices",
107
+ "documents",
108
+ "suppliers",
109
+ "activity",
110
+ "metadata",
111
+ ]);
112
+ /**
113
+ * Search contract for the booking detail page. `productId`/`slotId` only
114
+ * matter for the `"new"` pseudo-id: a deep link with a product pre-chosen
115
+ * redirects into the unified booking journey (host route concern).
116
+ */
117
+ export const bookingDetailSearchSchema = z.object({
118
+ productId: z.string().optional(),
119
+ slotId: z.string().optional(),
120
+ tab: bookingDetailTabSchema.optional(),
121
+ });
122
+ /**
123
+ * The bookings admin contribution (packaged-admin RFC Phase 3,
124
+ * `@voyantjs/<domain>-ui/admin` convention).
125
+ *
126
+ * NAVIGATION: deliberately none. The Bookings nav item is part of the BASE
127
+ * operator navigation — see `createOperatorAdminNavigation` in
128
+ * `@voyantjs/admin` — so contributing nav entries here would duplicate it.
129
+ * If the base nav ever drops the bookings item, this extension is where the
130
+ * entry moves.
131
+ *
132
+ * ROUTES: contributions are metadata + the package-owned search contracts
133
+ * ({@link bookingsIndexSearchSchema} for the list,
134
+ * {@link bookingDetailSearchSchema} for the detail page). The PAGES are
135
+ * package-owned too: {@link BookingsHost} and {@link BookingDetailHost}
136
+ * bind the canonical bookings pages to their data wiring (bookings/finance
137
+ * provider context) and resolve every cross-route link through the semantic
138
+ * destinations declared above — no app RPC client, no host route tree.
139
+ *
140
+ * `component:` is intentionally NOT attached to these contributions yet:
141
+ * the contribution contract renders zero-prop pages (route components read
142
+ * params via the router, per RFC §4.2), while both bookings hosts take
143
+ * route params/search state as props. Host route files stay the thin
144
+ * binding layer (`Route.useParams()`/`Route.useSearch()` → host props)
145
+ * until the §4.2 code-based route assembly gives packaged pages a
146
+ * router-agnostic way to read route state.
147
+ *
148
+ * WIDGETS: the crm-ui ↔ bookings-ui cycle resolution (RFC §4.7). The CRM
149
+ * person detail page mounts a Bookings tab, but this package depends on
150
+ * `@voyantjs/crm-ui`, so crm-ui's host cannot import the bookings-owned
151
+ * card. Instead this extension contributes {@link PersonBookingsWidget} on
152
+ * the `person.details.bookings-tab` slot crm-ui's `PersonDetailHost`
153
+ * exposes; the host mounts its Bookings tab whenever a contribution targets
154
+ * that slot and hands the widget its typed slot context
155
+ * (`PersonDetailBookingsTabContext`) as props.
156
+ */
157
+ export function createBookingsAdminExtension(options = {}) {
158
+ const { basePath = "/bookings", labels = {} } = options;
159
+ const { bookings = "Bookings" } = labels;
160
+ return defineAdminExtension({
161
+ id: "bookings",
162
+ routes: [
163
+ {
164
+ id: "bookings-index",
165
+ path: basePath,
166
+ title: bookings,
167
+ validateSearch: (search) => bookingsIndexSearchSchema.parse(search),
168
+ },
169
+ {
170
+ id: "bookings-detail",
171
+ path: `${basePath}/$id`,
172
+ title: bookings,
173
+ validateSearch: (search) => bookingDetailSearchSchema.parse(search),
174
+ },
175
+ ],
176
+ widgets: [
177
+ {
178
+ id: "bookings-person-bookings",
179
+ slot: personDetailBookingsTabSlot,
180
+ // The widget registry is untyped (`Record<string, unknown>` props);
181
+ // the typed contract is `PersonDetailBookingsTabContext`, which
182
+ // crm-ui's person detail host passes verbatim to this slot's widgets.
183
+ component: PersonBookingsWidget,
184
+ },
185
+ ],
186
+ });
187
+ }
@@ -0,0 +1,13 @@
1
+ import type { PersonDetailBookingsTabContext } from "@voyantjs/crm-ui/admin";
2
+ export type PersonBookingsWidgetProps = PersonDetailBookingsTabContext;
3
+ /**
4
+ * The person detail page's Bookings tab, contributed as a widget on
5
+ * crm-ui's `person.details.bookings-tab` slot (packaged-admin RFC §4.7
6
+ * cycle resolution): this package depends on `@voyantjs/crm-ui`, so the
7
+ * person detail host cannot import this card — the contribution travels
8
+ * the widget seam instead. Receives the slot's typed context
9
+ * (`PersonDetailBookingsTabContext`) as props and opens rows through the
10
+ * semantic `booking.detail` destination.
11
+ */
12
+ export declare function PersonBookingsWidget({ personId }: PersonBookingsWidgetProps): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=person-bookings-widget.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"person-bookings-widget.d.ts","sourceRoot":"","sources":["../../src/admin/person-bookings-widget.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,8BAA8B,EAAE,MAAM,wBAAwB,CAAA;AAY5E,MAAM,MAAM,yBAAyB,GAAG,8BAA8B,CAAA;AAEtE;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,EAAE,QAAQ,EAAE,EAAE,yBAAyB,2CAmF3E"}
@@ -0,0 +1,48 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useAdminNavigate, useOperatorAdminMessages } from "@voyantjs/admin";
4
+ import { bookingStatusBadgeVariant, useBookings } from "@voyantjs/bookings-react";
5
+ import { Badge } from "@voyantjs/ui/components/badge";
6
+ import { Skeleton } from "@voyantjs/ui/components/skeleton";
7
+ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyantjs/ui/components/table";
8
+ /**
9
+ * The person detail page's Bookings tab, contributed as a widget on
10
+ * crm-ui's `person.details.bookings-tab` slot (packaged-admin RFC §4.7
11
+ * cycle resolution): this package depends on `@voyantjs/crm-ui`, so the
12
+ * person detail host cannot import this card — the contribution travels
13
+ * the widget seam instead. Receives the slot's typed context
14
+ * (`PersonDetailBookingsTabContext`) as props and opens rows through the
15
+ * semantic `booking.detail` destination.
16
+ */
17
+ export function PersonBookingsWidget({ personId }) {
18
+ const adminMessages = useOperatorAdminMessages();
19
+ const messages = adminMessages.bookings.list;
20
+ const navigateTo = useAdminNavigate();
21
+ const bookingsQuery = useBookings({ personId, limit: 50 });
22
+ const bookings = bookingsQuery.data?.data ?? [];
23
+ if (bookingsQuery.isPending) {
24
+ return (_jsxs("div", { className: "space-y-2 p-4", children: [_jsx(Skeleton, { className: "h-10 w-full" }), _jsx(Skeleton, { className: "h-10 w-full" }), _jsx(Skeleton, { className: "h-10 w-full" })] }));
25
+ }
26
+ if (bookingsQuery.isError) {
27
+ return (_jsx("div", { className: "p-6 text-center text-destructive text-sm", children: adminMessages.crm.personDetail.notFound }));
28
+ }
29
+ if (bookings.length === 0) {
30
+ return (_jsx("div", { className: "p-6 text-center text-muted-foreground text-sm", children: messages.relatedEmpty }));
31
+ }
32
+ return (_jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: messages.tableBookingNumber }), _jsx(TableHead, { children: messages.tableItems }), _jsx(TableHead, { children: messages.tableStatus }), _jsx(TableHead, { children: messages.tableSellAmount }), _jsx(TableHead, { children: messages.tablePax }), _jsx(TableHead, { children: messages.tableStartDate })] }) }), _jsx(TableBody, { children: bookings.map((booking) => {
33
+ const firstItem = booking.items?.[0];
34
+ const itemLabel = firstItem?.productName ?? firstItem?.title ?? "—";
35
+ const extraItems = Math.max((booking.items?.length ?? 0) - 1, 0);
36
+ return (_jsxs(TableRow, { onClick: () => navigateTo("booking.detail", { bookingId: booking.id }), className: "cursor-pointer", children: [_jsx(TableCell, { className: "font-medium", children: booking.bookingNumber }), _jsx(TableCell, { children: _jsxs("div", { className: "max-w-[280px] truncate", title: itemLabel, children: [itemLabel, extraItems > 0 ? (_jsxs("span", { className: "ml-1 text-muted-foreground text-xs", children: ["+", extraItems] })) : null] }) }), _jsx(TableCell, { children: _jsx(Badge, { variant: bookingStatusBadgeVariant[booking.status], children: booking.status }) }), _jsx(TableCell, { children: booking.sellAmountCents == null
37
+ ? "—"
38
+ : `${(booking.sellAmountCents / 100).toFixed(2)} ${booking.sellCurrency}` }), _jsx(TableCell, { children: booking.pax ?? "—" }), _jsx(TableCell, { children: formatBookingDate(booking.startsAt ?? booking.startDate) })] }, booking.id));
39
+ }) })] }) }));
40
+ }
41
+ function formatBookingDate(value) {
42
+ if (!value)
43
+ return "—";
44
+ const date = new Date(/^\d{4}-\d{2}-\d{2}$/.test(value) ? `${value}T00:00:00` : value);
45
+ if (Number.isNaN(date.getTime()))
46
+ return "—";
47
+ return date.toLocaleDateString();
48
+ }
@@ -0,0 +1,15 @@
1
+ import type { TimelineEvent } from "../components/booking-activity-timeline.js";
2
+ /**
3
+ * Fetch the booking's central action-ledger entries and map them into
4
+ * `TimelineEvent`s so they can be merged into `BookingActivityTimeline`.
5
+ * Replaces the standalone Ledger tab — operators see one chronological
6
+ * activity feed instead of two sibling tabs.
7
+ *
8
+ * Returns a "Load more" `footer` element when the cursor pager hasn't
9
+ * exhausted yet so the timeline can render it below the event list.
10
+ */
11
+ export declare function useBookingActionLedgerEvents(bookingId: string): {
12
+ events: TimelineEvent[];
13
+ footer: React.ReactNode | null;
14
+ };
15
+ //# sourceMappingURL=use-booking-action-ledger-events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-booking-action-ledger-events.d.ts","sourceRoot":"","sources":["../../src/admin/use-booking-action-ledger-events.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,4CAA4C,CAAA;AAE/E;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,CAAC,SAAS,EAAE,MAAM,GAAG;IAC/D,MAAM,EAAE,aAAa,EAAE,CAAA;IACvB,MAAM,EAAE,KAAK,CAAC,SAAS,GAAG,IAAI,CAAA;CAC/B,CAgDA"}
@@ -0,0 +1,66 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useOperatorAdminMessages } from "@voyantjs/admin";
4
+ import { useBookingActionLedger, } from "@voyantjs/bookings-react";
5
+ import { Button } from "@voyantjs/ui/components/button";
6
+ import { ScrollText } from "lucide-react";
7
+ import { useMemo } from "react";
8
+ /**
9
+ * Fetch the booking's central action-ledger entries and map them into
10
+ * `TimelineEvent`s so they can be merged into `BookingActivityTimeline`.
11
+ * Replaces the standalone Ledger tab — operators see one chronological
12
+ * activity feed instead of two sibling tabs.
13
+ *
14
+ * Returns a "Load more" `footer` element when the cursor pager hasn't
15
+ * exhausted yet so the timeline can render it below the event list.
16
+ */
17
+ export function useBookingActionLedgerEvents(bookingId) {
18
+ const t = useOperatorAdminMessages().bookings.detail.actionLedger;
19
+ const ledgerQuery = useBookingActionLedger(bookingId);
20
+ const pages = useMemo(() => ledgerQuery.data?.pages ?? [], [ledgerQuery.data]);
21
+ const travelers = pages[0]?.travelers ?? [];
22
+ const travelersById = useMemo(() => new Map(travelers.map((traveler) => [traveler.id, traveler])), [travelers]);
23
+ const events = useMemo(() => {
24
+ const all = [];
25
+ for (const page of pages) {
26
+ for (const entry of page.data) {
27
+ const traveler = travelersById.get(entry.targetId) ?? null;
28
+ all.push({
29
+ id: `action:${entry.id}`,
30
+ source: "action",
31
+ title: formatActionName(entry.actionName),
32
+ description: formatLedgerDescription(entry, traveler, t.travelerFallback, t.targetBooking),
33
+ actorId: entry.principalId,
34
+ timestamp: entry.occurredAt,
35
+ icon: ScrollText,
36
+ });
37
+ }
38
+ }
39
+ return all;
40
+ }, [pages, travelersById, t.targetBooking, t.travelerFallback]);
41
+ const footer = ledgerQuery.hasNextPage ? (_jsx(Button, { type: "button", variant: "outline", size: "sm", disabled: ledgerQuery.isFetchingNextPage, onClick: () => void ledgerQuery.fetchNextPage(), children: ledgerQuery.isFetchingNextPage ? t.loadingMore : t.loadMore })) : null;
42
+ return { events, footer };
43
+ }
44
+ function formatActionName(value) {
45
+ return value.replaceAll(".", " / ").replaceAll("_", " ");
46
+ }
47
+ function formatTarget(entry, traveler, travelerFallback, bookingFallback) {
48
+ if (traveler) {
49
+ return [traveler.firstName, traveler.lastName].filter(Boolean).join(" ") || travelerFallback;
50
+ }
51
+ if (entry.targetType === "booking")
52
+ return bookingFallback;
53
+ return entry.targetType.replaceAll("_", " ");
54
+ }
55
+ function formatLedgerDescription(entry, traveler, travelerFallback, bookingFallback) {
56
+ // status / target / risk are machine-readable enum strings the
57
+ // ledger ships unmodified (matches the standalone panel behaviour).
58
+ const parts = [
59
+ entry.status,
60
+ formatTarget(entry, traveler, travelerFallback, bookingFallback),
61
+ entry.evaluatedRisk,
62
+ ];
63
+ if (entry.routeOrToolName)
64
+ parts.push(entry.routeOrToolName);
65
+ return parts.join(" · ");
66
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voyantjs/bookings-ui",
3
- "version": "0.108.0",
3
+ "version": "0.109.0",
4
4
  "license": "Apache-2.0",
5
5
  "repository": {
6
6
  "type": "git",
@@ -38,6 +38,11 @@
38
38
  "import": "./dist/journey/index.js",
39
39
  "default": "./dist/journey/index.js"
40
40
  },
41
+ "./admin": {
42
+ "types": "./dist/admin/index.d.ts",
43
+ "import": "./dist/admin/index.js",
44
+ "default": "./dist/admin/index.js"
45
+ },
41
46
  "./components/*": {
42
47
  "types": "./dist/components/*.d.ts",
43
48
  "import": "./dist/components/*.js",
@@ -52,21 +57,22 @@
52
57
  "react-dom": "^19.0.0",
53
58
  "react-hook-form": "^7.60.0",
54
59
  "zod": "^4.3.6",
55
- "@voyantjs/availability-react": "^0.105.2",
56
- "@voyantjs/bookings": "^0.108.0",
57
- "@voyantjs/bookings-react": "^0.108.0",
58
- "@voyantjs/catalog": "^0.106.0",
59
- "@voyantjs/catalog-react": "^0.106.0",
60
- "@voyantjs/crm-react": "^0.108.0",
61
- "@voyantjs/crm-ui": "^0.108.0",
62
- "@voyantjs/extras-react": "^0.108.0",
63
- "@voyantjs/finance-react": "^0.108.0",
64
- "@voyantjs/identity-react": "^0.108.0",
65
- "@voyantjs/legal-react": "^0.108.0",
66
- "@voyantjs/pricing-react": "^0.108.0",
67
- "@voyantjs/products-react": "^0.108.0",
68
- "@voyantjs/suppliers-react": "^0.105.2",
69
- "@voyantjs/ui": "^0.105.1"
60
+ "@voyantjs/admin": "^0.106.0",
61
+ "@voyantjs/availability-react": "^0.106.0",
62
+ "@voyantjs/bookings": "^0.109.0",
63
+ "@voyantjs/bookings-react": "^0.109.0",
64
+ "@voyantjs/catalog": "^0.107.0",
65
+ "@voyantjs/catalog-react": "^0.107.0",
66
+ "@voyantjs/crm-react": "^0.109.0",
67
+ "@voyantjs/crm-ui": "^0.109.0",
68
+ "@voyantjs/extras-react": "^0.109.0",
69
+ "@voyantjs/finance-react": "^0.109.0",
70
+ "@voyantjs/identity-react": "^0.109.0",
71
+ "@voyantjs/legal-react": "^0.109.0",
72
+ "@voyantjs/pricing-react": "^0.109.0",
73
+ "@voyantjs/products-react": "^0.109.0",
74
+ "@voyantjs/suppliers-react": "^0.106.0",
75
+ "@voyantjs/ui": "^0.106.0"
70
76
  },
71
77
  "dependencies": {
72
78
  "sonner": "^2.0.7",
@@ -85,21 +91,22 @@
85
91
  "typescript": "^6.0.2",
86
92
  "vitest": "^4.1.2",
87
93
  "zod": "^4.3.6",
88
- "@voyantjs/availability-react": "^0.105.2",
89
- "@voyantjs/bookings": "^0.108.0",
90
- "@voyantjs/bookings-react": "^0.108.0",
91
- "@voyantjs/catalog": "^0.106.0",
92
- "@voyantjs/catalog-react": "^0.106.0",
93
- "@voyantjs/crm-react": "^0.108.0",
94
- "@voyantjs/crm-ui": "^0.108.0",
95
- "@voyantjs/extras-react": "^0.108.0",
96
- "@voyantjs/finance-react": "^0.108.0",
97
- "@voyantjs/identity-react": "^0.108.0",
98
- "@voyantjs/legal-react": "^0.108.0",
99
- "@voyantjs/pricing-react": "^0.108.0",
100
- "@voyantjs/products-react": "^0.108.0",
101
- "@voyantjs/suppliers-react": "^0.105.2",
102
- "@voyantjs/ui": "^0.105.1",
94
+ "@voyantjs/admin": "^0.106.0",
95
+ "@voyantjs/availability-react": "^0.106.0",
96
+ "@voyantjs/bookings": "^0.109.0",
97
+ "@voyantjs/bookings-react": "^0.109.0",
98
+ "@voyantjs/catalog": "^0.107.0",
99
+ "@voyantjs/catalog-react": "^0.107.0",
100
+ "@voyantjs/crm-react": "^0.109.0",
101
+ "@voyantjs/crm-ui": "^0.109.0",
102
+ "@voyantjs/finance-react": "^0.109.0",
103
+ "@voyantjs/identity-react": "^0.109.0",
104
+ "@voyantjs/legal-react": "^0.109.0",
105
+ "@voyantjs/pricing-react": "^0.109.0",
106
+ "@voyantjs/products-react": "^0.109.0",
107
+ "@voyantjs/suppliers-react": "^0.106.0",
108
+ "@voyantjs/extras-react": "^0.109.0",
109
+ "@voyantjs/ui": "^0.106.0",
103
110
  "@voyantjs/voyant-typescript-config": "^0.1.0"
104
111
  },
105
112
  "files": [