@voyant-travel/trips-react 0.110.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 +30 -0
- package/dist/admin/admin-trips-page-controls.d.ts +28 -0
- package/dist/admin/admin-trips-page-controls.d.ts.map +1 -0
- package/dist/admin/admin-trips-page-controls.js +28 -0
- package/dist/admin/admin-trips-page-model.d.ts +87 -0
- package/dist/admin/admin-trips-page-model.d.ts.map +1 -0
- package/dist/admin/admin-trips-page-model.js +457 -0
- package/dist/admin/admin-trips-page.d.ts +6 -0
- package/dist/admin/admin-trips-page.d.ts.map +1 -0
- package/dist/admin/admin-trips-page.js +322 -0
- package/dist/admin/admin-trips-panels.d.ts +11 -0
- package/dist/admin/admin-trips-panels.d.ts.map +1 -0
- package/dist/admin/admin-trips-panels.js +11 -0
- package/dist/admin/index.d.ts +63 -0
- package/dist/admin/index.d.ts.map +1 -0
- package/dist/admin/index.js +119 -0
- package/dist/admin/pages/trip-detail-page.d.ts +10 -0
- package/dist/admin/pages/trip-detail-page.d.ts.map +1 -0
- package/dist/admin/pages/trip-detail-page.js +12 -0
- package/dist/admin/trip-component-display.d.ts +10 -0
- package/dist/admin/trip-component-display.d.ts.map +1 -0
- package/dist/admin/trip-component-display.js +137 -0
- package/dist/admin/trip-detail-host.d.ts +12 -0
- package/dist/admin/trip-detail-host.d.ts.map +1 -0
- package/dist/admin/trip-detail-host.js +37 -0
- package/dist/admin/trip-detail-record-model.d.ts +30 -0
- package/dist/admin/trip-detail-record-model.d.ts.map +1 -0
- package/dist/admin/trip-detail-record-model.js +56 -0
- package/dist/admin/trip-detail-record.d.ts +47 -0
- package/dist/admin/trip-detail-record.d.ts.map +1 -0
- package/dist/admin/trip-detail-record.js +170 -0
- package/dist/admin/trip-list-filters.d.ts +33 -0
- package/dist/admin/trip-list-filters.d.ts.map +1 -0
- package/dist/admin/trip-list-filters.js +94 -0
- package/dist/admin/trips-host.d.ts +15 -0
- package/dist/admin/trips-host.d.ts.map +1 -0
- package/dist/admin/trips-host.js +233 -0
- package/dist/admin/trips-panels/catalog-configurator.d.ts +34 -0
- package/dist/admin/trips-panels/catalog-configurator.d.ts.map +1 -0
- package/dist/admin/trips-panels/catalog-configurator.js +200 -0
- package/dist/admin/trips-panels/catalog-options.d.ts +58 -0
- package/dist/admin/trips-panels/catalog-options.d.ts.map +1 -0
- package/dist/admin/trips-panels/catalog-options.js +124 -0
- package/dist/admin/trips-panels/committed-component-card.d.ts +27 -0
- package/dist/admin/trips-panels/committed-component-card.d.ts.map +1 -0
- package/dist/admin/trips-panels/committed-component-card.js +44 -0
- package/dist/admin/trips-panels/display.d.ts +51 -0
- package/dist/admin/trips-panels/display.d.ts.map +1 -0
- package/dist/admin/trips-panels/display.js +336 -0
- package/dist/admin/trips-panels/flight-configurator.d.ts +34 -0
- package/dist/admin/trips-panels/flight-configurator.d.ts.map +1 -0
- package/dist/admin/trips-panels/flight-configurator.js +208 -0
- package/dist/admin/trips-panels/manual-configurators.d.ts +16 -0
- package/dist/admin/trips-panels/manual-configurators.d.ts.map +1 -0
- package/dist/admin/trips-panels/manual-configurators.js +41 -0
- package/dist/admin/trips-panels/pending-component-card.d.ts +16 -0
- package/dist/admin/trips-panels/pending-component-card.d.ts.map +1 -0
- package/dist/admin/trips-panels/pending-component-card.js +29 -0
- package/dist/admin/trips-panels/shared.d.ts +122 -0
- package/dist/admin/trips-panels/shared.d.ts.map +1 -0
- package/dist/admin/trips-panels/shared.js +152 -0
- package/dist/admin/trips-panels/travelers-section.d.ts +53 -0
- package/dist/admin/trips-panels/travelers-section.d.ts.map +1 -0
- package/dist/admin/trips-panels/travelers-section.js +183 -0
- package/dist/admin/trips-panels/trip-preview-rail.d.ts +52 -0
- package/dist/admin/trips-panels/trip-preview-rail.d.ts.map +1 -0
- package/dist/admin/trips-panels/trip-preview-rail.js +122 -0
- package/dist/cache.d.ts +9 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +21 -0
- package/dist/client.d.ts +15 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +51 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/use-price-trip.d.ts +3 -0
- package/dist/hooks/use-price-trip.d.ts.map +1 -0
- package/dist/hooks/use-price-trip.js +17 -0
- package/dist/hooks/use-reserve-trip.d.ts +3 -0
- package/dist/hooks/use-reserve-trip.d.ts.map +1 -0
- package/dist/hooks/use-reserve-trip.js +17 -0
- package/dist/hooks/use-trip-checkout.d.ts +3 -0
- package/dist/hooks/use-trip-checkout.d.ts.map +1 -0
- package/dist/hooks/use-trip-checkout.js +17 -0
- package/dist/hooks/use-trip-components.d.ts +40 -0
- package/dist/hooks/use-trip-components.d.ts.map +1 -0
- package/dist/hooks/use-trip-components.js +13 -0
- package/dist/hooks/use-trip.d.ts +5 -0
- package/dist/hooks/use-trip.d.ts.map +1 -0
- package/dist/hooks/use-trip.js +13 -0
- package/dist/hooks/use-trips.d.ts +6 -0
- package/dist/hooks/use-trips.d.ts.map +1 -0
- package/dist/hooks/use-trips.js +12 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/operations.d.ts +212 -0
- package/dist/operations.d.ts.map +1 -0
- package/dist/operations.js +92 -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 +10 -0
- package/dist/query-keys.d.ts.map +1 -0
- package/dist/query-keys.js +9 -0
- package/dist/query-options.d.ts +167 -0
- package/dist/query-options.d.ts.map +1 -0
- package/dist/query-options.js +22 -0
- package/dist/schemas.d.ts +69 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +16 -0
- package/package.json +133 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
export function readComponentSchedule(component) {
|
|
2
|
+
const md = component.metadata;
|
|
3
|
+
if (md?.scheduledStartsAt) {
|
|
4
|
+
return { start: md.scheduledStartsAt, end: md.scheduledEndsAt ?? null };
|
|
5
|
+
}
|
|
6
|
+
const dateRange = md?.bookingDraftV1?.configure?.dateRange;
|
|
7
|
+
if (dateRange?.checkIn) {
|
|
8
|
+
return { start: dateRange.checkIn, end: dateRange.checkOut ?? null };
|
|
9
|
+
}
|
|
10
|
+
const flight = md?.flightDraft;
|
|
11
|
+
if (flight?.departDate) {
|
|
12
|
+
return { start: flight.departDate, end: flight.returnDate ?? null };
|
|
13
|
+
}
|
|
14
|
+
const cruise = md?.cruiseDraft;
|
|
15
|
+
if (cruise?.embarkationDate) {
|
|
16
|
+
return { start: cruise.embarkationDate, end: null };
|
|
17
|
+
}
|
|
18
|
+
return { start: null, end: null };
|
|
19
|
+
}
|
|
20
|
+
export function formatScheduleLabel(component) {
|
|
21
|
+
const { start, end } = readComponentSchedule(component);
|
|
22
|
+
if (!start)
|
|
23
|
+
return null;
|
|
24
|
+
const startLabel = formatDateTime(start);
|
|
25
|
+
if (!end || end === start)
|
|
26
|
+
return startLabel;
|
|
27
|
+
return `${startLabel} → ${formatDateTime(end)}`;
|
|
28
|
+
}
|
|
29
|
+
export function sortComponentsBySchedule(components) {
|
|
30
|
+
return [...components].sort((a, b) => {
|
|
31
|
+
const aStart = readComponentSchedule(a).start;
|
|
32
|
+
const bStart = readComponentSchedule(b).start;
|
|
33
|
+
const aMs = aStart ? new Date(aStart).getTime() : Number.NaN;
|
|
34
|
+
const bMs = bStart ? new Date(bStart).getTime() : Number.NaN;
|
|
35
|
+
const aMissing = Number.isNaN(aMs);
|
|
36
|
+
const bMissing = Number.isNaN(bMs);
|
|
37
|
+
if (aMissing && bMissing)
|
|
38
|
+
return a.sequence - b.sequence;
|
|
39
|
+
if (aMissing)
|
|
40
|
+
return 1;
|
|
41
|
+
if (bMissing)
|
|
42
|
+
return -1;
|
|
43
|
+
if (aMs !== bMs)
|
|
44
|
+
return aMs - bMs;
|
|
45
|
+
return a.sequence - b.sequence;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
export function componentTitleFor(component, resolvedEntityName) {
|
|
49
|
+
const metadata = component.metadata;
|
|
50
|
+
const entityName = cleanDisplayLabel(resolvedEntityName);
|
|
51
|
+
if (entityName)
|
|
52
|
+
return entityName;
|
|
53
|
+
const catalogName = cleanDisplayLabel(metadata?.catalogItem?.name);
|
|
54
|
+
if (catalogName)
|
|
55
|
+
return catalogName;
|
|
56
|
+
if (component.kind === "flight_placeholder" || component.kind === "flight_order") {
|
|
57
|
+
const origin = cleanDisplayLabel(metadata?.flightDraft?.origin);
|
|
58
|
+
const destination = cleanDisplayLabel(metadata?.flightDraft?.destination);
|
|
59
|
+
if (origin && destination)
|
|
60
|
+
return `${origin} → ${destination}`;
|
|
61
|
+
}
|
|
62
|
+
if (component.entityModule === "cruises") {
|
|
63
|
+
const cabin = cleanDisplayLabel(metadata?.cruiseDraft?.cabin);
|
|
64
|
+
if (cabin)
|
|
65
|
+
return `Cabin ${cabin}`;
|
|
66
|
+
}
|
|
67
|
+
if (component.entityModule === "accommodations") {
|
|
68
|
+
const accommodationName = cleanDisplayLabel(metadata?.accommodation?.propertyName) ??
|
|
69
|
+
cleanDisplayLabel(metadata?.accommodation?.name) ??
|
|
70
|
+
cleanDisplayLabel(metadata?.accommodation?.roomTypeName);
|
|
71
|
+
if (accommodationName)
|
|
72
|
+
return accommodationName;
|
|
73
|
+
}
|
|
74
|
+
if (metadata?.cruiseDraft) {
|
|
75
|
+
const cabin = cleanDisplayLabel(metadata.cruiseDraft.cabin);
|
|
76
|
+
if (cabin)
|
|
77
|
+
return `Cabin ${cabin}`;
|
|
78
|
+
}
|
|
79
|
+
if (component.kind === "manual_placeholder") {
|
|
80
|
+
const serviceName = cleanDisplayLabel(metadata?.manualService?.name);
|
|
81
|
+
if (serviceName)
|
|
82
|
+
return serviceName;
|
|
83
|
+
const title = cleanDisplayLabel(component.title);
|
|
84
|
+
if (title)
|
|
85
|
+
return title;
|
|
86
|
+
const description = cleanDisplayLabel(component.description);
|
|
87
|
+
if (description)
|
|
88
|
+
return description;
|
|
89
|
+
}
|
|
90
|
+
return componentReferenceLabelFor(component);
|
|
91
|
+
}
|
|
92
|
+
export function componentReferenceLabelFor(component) {
|
|
93
|
+
const reference = component.providerRef ??
|
|
94
|
+
component.supplierRef ??
|
|
95
|
+
component.bookingId ??
|
|
96
|
+
component.orderId ??
|
|
97
|
+
component.paymentSessionId ??
|
|
98
|
+
component.sourceRef ??
|
|
99
|
+
component.entityId ??
|
|
100
|
+
component.id;
|
|
101
|
+
return reference.length > 18 ? reference.slice(0, 18) : reference;
|
|
102
|
+
}
|
|
103
|
+
function cleanDisplayLabel(value) {
|
|
104
|
+
const trimmed = value?.trim();
|
|
105
|
+
if (!trimmed)
|
|
106
|
+
return null;
|
|
107
|
+
const normalized = trimmed.toLowerCase();
|
|
108
|
+
if (normalized === "untitled trip" ||
|
|
109
|
+
normalized === "untitled component" ||
|
|
110
|
+
normalized === "flight placeholder" ||
|
|
111
|
+
normalized.startsWith("flight placeholder ") ||
|
|
112
|
+
normalized === "manual placeholder" ||
|
|
113
|
+
normalized === "cruise" ||
|
|
114
|
+
normalized === "cruise placeholder" ||
|
|
115
|
+
normalized === "catalog booking" ||
|
|
116
|
+
normalized === "product booking" ||
|
|
117
|
+
normalized === "stay booking" ||
|
|
118
|
+
normalized === "cruise booking" ||
|
|
119
|
+
normalized === "external order" ||
|
|
120
|
+
normalized === "flight order" ||
|
|
121
|
+
/^component \d+$/.test(normalized)) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
return trimmed;
|
|
125
|
+
}
|
|
126
|
+
function formatDateTime(iso) {
|
|
127
|
+
const parsed = new Date(iso);
|
|
128
|
+
if (Number.isNaN(parsed.getTime()))
|
|
129
|
+
return iso;
|
|
130
|
+
const hasTime = iso.includes("T") || iso.includes(" ");
|
|
131
|
+
return new Intl.DateTimeFormat(undefined, {
|
|
132
|
+
year: "numeric",
|
|
133
|
+
month: "short",
|
|
134
|
+
day: "numeric",
|
|
135
|
+
...(hasTime ? { hour: "2-digit", minute: "2-digit" } : {}),
|
|
136
|
+
}).format(parsed);
|
|
137
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Packaged admin host for the trip detail page (packaged-admin RFC
|
|
3
|
+
* Phase 3). The `"new"` pseudo-id mounts the admin trips directly;
|
|
4
|
+
* existing trips render the read-only record page (seeded by the
|
|
5
|
+
* `trips-detail` contribution's loader) with an Edit toggle into
|
|
6
|
+
* the composer. Cross-route links (bookings, CRM people, the trips list)
|
|
7
|
+
* resolve through semantic destinations (RFC §4.7).
|
|
8
|
+
*/
|
|
9
|
+
export declare function TripDetailHost({ id }: {
|
|
10
|
+
id: string;
|
|
11
|
+
}): import("react/jsx-runtime").JSX.Element | null;
|
|
12
|
+
//# sourceMappingURL=trip-detail-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trip-detail-host.d.ts","sourceRoot":"","sources":["../../src/admin/trip-detail-host.tsx"],"names":[],"mappings":"AAeA;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,kDA0BpD"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useQuery } from "@tanstack/react-query";
|
|
4
|
+
import { lazy, Suspense, useState } from "react";
|
|
5
|
+
import { useVoyantTripsContext } from "../provider.js";
|
|
6
|
+
import { getTripQueryOptions } from "../query-options.js";
|
|
7
|
+
import { TripRecordPage } from "./trip-detail-record.js";
|
|
8
|
+
const AdminTripsPage = lazy(() => import("./admin-trips-page.js").then((module) => ({
|
|
9
|
+
default: module.AdminTripsPage,
|
|
10
|
+
})));
|
|
11
|
+
/**
|
|
12
|
+
* Packaged admin host for the trip detail page (packaged-admin RFC
|
|
13
|
+
* Phase 3). The `"new"` pseudo-id mounts the admin trips directly;
|
|
14
|
+
* existing trips render the read-only record page (seeded by the
|
|
15
|
+
* `trips-detail` contribution's loader) with an Edit toggle into
|
|
16
|
+
* the composer. Cross-route links (bookings, CRM people, the trips list)
|
|
17
|
+
* resolve through semantic destinations (RFC §4.7).
|
|
18
|
+
*/
|
|
19
|
+
export function TripDetailHost({ id }) {
|
|
20
|
+
const [mode, setMode] = useState("record");
|
|
21
|
+
const { baseUrl, fetcher } = useVoyantTripsContext();
|
|
22
|
+
const isNew = id === "new";
|
|
23
|
+
const tripQuery = useQuery({
|
|
24
|
+
...getTripQueryOptions({ baseUrl, fetcher }, id),
|
|
25
|
+
enabled: !isNew,
|
|
26
|
+
});
|
|
27
|
+
const trip = isNew ? null : (tripQuery.data ?? null);
|
|
28
|
+
if (!trip) {
|
|
29
|
+
if (!isNew && tripQuery.isPending)
|
|
30
|
+
return null;
|
|
31
|
+
return (_jsx(Suspense, { fallback: null, children: _jsx(AdminTripsPage, { initialTrip: null }) }));
|
|
32
|
+
}
|
|
33
|
+
if (mode === "edit") {
|
|
34
|
+
return (_jsx(Suspense, { fallback: null, children: _jsx(AdminTripsPage, { initialTrip: trip }) }));
|
|
35
|
+
}
|
|
36
|
+
return _jsx(TripRecordPage, { trip: trip, onEdit: () => setMode("edit") });
|
|
37
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface BillingRecordValue {
|
|
2
|
+
buyerType?: string;
|
|
3
|
+
personId?: string;
|
|
4
|
+
organizationId?: string;
|
|
5
|
+
contact?: {
|
|
6
|
+
firstName?: string;
|
|
7
|
+
lastName?: string;
|
|
8
|
+
email?: string;
|
|
9
|
+
phone?: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface TripTravelerRecord {
|
|
13
|
+
localId?: string;
|
|
14
|
+
personId?: string | null;
|
|
15
|
+
firstName?: string;
|
|
16
|
+
lastName?: string;
|
|
17
|
+
email?: string;
|
|
18
|
+
role?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function readBilling(travelerParty: Record<string, unknown>): BillingRecordValue | null;
|
|
21
|
+
export declare function readTravelers(travelerParty: Record<string, unknown>): TripTravelerRecord[];
|
|
22
|
+
export declare function formatPersonName(person: {
|
|
23
|
+
firstName?: string | null;
|
|
24
|
+
lastName?: string | null;
|
|
25
|
+
email?: string | null;
|
|
26
|
+
} | undefined | null): string | null;
|
|
27
|
+
export declare function formatContactName(contact: BillingRecordValue["contact"]): string | null;
|
|
28
|
+
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
29
|
+
export declare function stringValue(value: unknown): string | undefined;
|
|
30
|
+
//# sourceMappingURL=trip-detail-record-model.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trip-detail-record-model.d.ts","sourceRoot":"","sources":["../../src/admin/trip-detail-record-model.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,OAAO,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,KAAK,CAAC,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAED,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,wBAAgB,WAAW,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,kBAAkB,GAAG,IAAI,CAiB7F;AAED,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,kBAAkB,EAAE,CAW1F;AAED,wBAAgB,gBAAgB,CAC9B,MAAM,EACF;IACE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACtB,GACD,SAAS,GACT,IAAI,GACP,MAAM,GAAG,IAAI,CAOf;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,CAAC,SAAS,CAAC,GAAG,MAAM,GAAG,IAAI,CAOvF;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAE9D"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export function readBilling(travelerParty) {
|
|
2
|
+
const billing = travelerParty.billing;
|
|
3
|
+
if (!isRecord(billing))
|
|
4
|
+
return null;
|
|
5
|
+
const contact = isRecord(billing.contact) ? billing.contact : undefined;
|
|
6
|
+
return {
|
|
7
|
+
buyerType: stringValue(billing.buyerType),
|
|
8
|
+
personId: stringValue(billing.personId),
|
|
9
|
+
organizationId: stringValue(billing.organizationId),
|
|
10
|
+
contact: contact
|
|
11
|
+
? {
|
|
12
|
+
firstName: stringValue(contact.firstName),
|
|
13
|
+
lastName: stringValue(contact.lastName),
|
|
14
|
+
email: stringValue(contact.email),
|
|
15
|
+
phone: stringValue(contact.phone),
|
|
16
|
+
}
|
|
17
|
+
: undefined,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
export function readTravelers(travelerParty) {
|
|
21
|
+
const travelers = travelerParty.travelers;
|
|
22
|
+
if (!Array.isArray(travelers))
|
|
23
|
+
return [];
|
|
24
|
+
return travelers.filter(isRecord).map((traveler) => ({
|
|
25
|
+
localId: stringValue(traveler.localId),
|
|
26
|
+
personId: stringValue(traveler.personId) ?? null,
|
|
27
|
+
firstName: stringValue(traveler.firstName),
|
|
28
|
+
lastName: stringValue(traveler.lastName),
|
|
29
|
+
email: stringValue(traveler.email),
|
|
30
|
+
role: stringValue(traveler.role),
|
|
31
|
+
}));
|
|
32
|
+
}
|
|
33
|
+
export function formatPersonName(person) {
|
|
34
|
+
if (!person)
|
|
35
|
+
return null;
|
|
36
|
+
const name = [person.firstName, person.lastName]
|
|
37
|
+
.filter((part) => (part ?? "").trim().length > 0)
|
|
38
|
+
.join(" ")
|
|
39
|
+
.trim();
|
|
40
|
+
return name || person.email || null;
|
|
41
|
+
}
|
|
42
|
+
export function formatContactName(contact) {
|
|
43
|
+
if (!contact)
|
|
44
|
+
return null;
|
|
45
|
+
const name = [contact.firstName, contact.lastName]
|
|
46
|
+
.filter((part) => (part ?? "").trim().length > 0)
|
|
47
|
+
.join(" ")
|
|
48
|
+
.trim();
|
|
49
|
+
return name || null;
|
|
50
|
+
}
|
|
51
|
+
export function isRecord(value) {
|
|
52
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
53
|
+
}
|
|
54
|
+
export function stringValue(value) {
|
|
55
|
+
return typeof value === "string" && value.trim().length > 0 ? value : undefined;
|
|
56
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useOperatorAdminMessages as useAdminMessages } from "@voyant-travel/admin";
|
|
2
|
+
import type { Trip, TripComponent } from "@voyant-travel/trips";
|
|
3
|
+
import { Route as RouteIcon } from "lucide-react";
|
|
4
|
+
import { type ReactNode } from "react";
|
|
5
|
+
import { type TripTravelerRecord } from "./trip-detail-record-model.js";
|
|
6
|
+
export declare function TripRecordPage({ trip, onEdit }: {
|
|
7
|
+
trip: Trip;
|
|
8
|
+
onEdit(): void;
|
|
9
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
10
|
+
export declare function BillingRecord({ travelerParty, messages, }: {
|
|
11
|
+
travelerParty: Record<string, unknown>;
|
|
12
|
+
messages: ReturnType<typeof useAdminMessages>["trips"]["detail"];
|
|
13
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
14
|
+
export declare function TravelersRecord({ travelerParty, messages, }: {
|
|
15
|
+
travelerParty: Record<string, unknown>;
|
|
16
|
+
messages: ReturnType<typeof useAdminMessages>["trips"]["detail"];
|
|
17
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
export declare function TravelerRecordRow({ traveler, index, messages, }: {
|
|
19
|
+
traveler: TripTravelerRecord;
|
|
20
|
+
index: number;
|
|
21
|
+
messages: ReturnType<typeof useAdminMessages>["trips"]["detail"];
|
|
22
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
23
|
+
export declare function PersonLink({ personId, children }: {
|
|
24
|
+
personId: string;
|
|
25
|
+
children: ReactNode;
|
|
26
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
export declare function SummaryCard({ label, value, icon: Icon, }: {
|
|
28
|
+
label: string;
|
|
29
|
+
value: string;
|
|
30
|
+
icon: typeof RouteIcon;
|
|
31
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
32
|
+
export declare function ComponentRow({ component, messages, onOpenBooking, }: {
|
|
33
|
+
component: TripComponent;
|
|
34
|
+
messages: ReturnType<typeof useAdminMessages>["trips"]["detail"];
|
|
35
|
+
onOpenBooking(bookingId: string): void;
|
|
36
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
37
|
+
export declare function tripScheduleLabel(components: Trip["components"]): string | null | undefined;
|
|
38
|
+
export declare function SummaryLine({ label, value, strong, }: {
|
|
39
|
+
label: string;
|
|
40
|
+
value: string;
|
|
41
|
+
strong?: boolean;
|
|
42
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
43
|
+
export declare function componentIcon(component: TripComponent): import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
|
|
44
|
+
export declare function formatStatus(value: string): string;
|
|
45
|
+
export declare function formatDate(value: Date | string | null | undefined): string;
|
|
46
|
+
export declare function formatMoney(amountCents: number | null | undefined, currency: string | null | undefined): string;
|
|
47
|
+
//# sourceMappingURL=trip-detail-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trip-detail-record.d.ts","sourceRoot":"","sources":["../../src/admin/trip-detail-record.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,wBAAwB,IAAI,gBAAgB,EAE7C,MAAM,sBAAsB,CAAA;AAI7B,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AAa/D,OAAO,EAUL,KAAK,IAAI,SAAS,EAGnB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAShD,OAAO,EAKL,KAAK,kBAAkB,EACxB,MAAM,+BAA+B,CAAA;AAEtC,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE;IAAE,IAAI,EAAE,IAAI,CAAC;IAAC,MAAM,IAAI,IAAI,CAAA;CAAE,2CAwL9E;AA+BD,wBAAgB,aAAa,CAAC,EAC5B,aAAa,EACb,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC,QAAQ,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAA;CACjE,2CAoEA;AAED,wBAAgB,eAAe,CAAC,EAC9B,aAAa,EACb,QAAQ,GACT,EAAE;IACD,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACtC,QAAQ,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAA;CACjE,2CA4BA;AAED,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,KAAK,EACL,QAAQ,GACT,EAAE;IACD,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAA;CACjE,2CA0BA;AAED,wBAAgB,UAAU,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,SAAS,CAAA;CAAE,2CAe3F;AAED,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAK,EACL,IAAI,EAAE,IAAI,GACX,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,IAAI,EAAE,OAAO,SAAS,CAAA;CACvB,2CAcA;AAED,wBAAgB,YAAY,CAAC,EAC3B,SAAS,EACT,QAAQ,EACR,aAAa,GACd,EAAE;IACD,SAAS,EAAE,aAAa,CAAA;IACxB,QAAQ,EAAE,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAA;IAChE,aAAa,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACvC,2CAuDA;AAED,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,IAAI,CAAC,YAAY,CAAC,6BAO/D;AAED,wBAAgB,WAAW,CAAC,EAC1B,KAAK,EACL,KAAK,EACL,MAAc,GACf,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,2CAOA;AAED,wBAAgB,aAAa,CAAC,SAAS,EAAE,aAAa,6IAKrD;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,UAEzC;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,GAAG,SAAS,UAKjE;AAED,wBAAgB,WAAW,CACzB,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACtC,QAAQ,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,UAOpC"}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { useAdminHref, useOperatorAdminMessages as useAdminMessages, useAdminNavigate, } from "@voyant-travel/admin";
|
|
4
|
+
import { buildPaymentLinkUrl } from "@voyant-travel/finance/payment-link";
|
|
5
|
+
import { formatMessage } from "@voyant-travel/i18n";
|
|
6
|
+
import { useOrganization, usePerson } from "@voyant-travel/relationships-react";
|
|
7
|
+
import { Badge } from "@voyant-travel/ui/components/badge";
|
|
8
|
+
import { Button } from "@voyant-travel/ui/components/button";
|
|
9
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@voyant-travel/ui/components/card";
|
|
10
|
+
import { Separator } from "@voyant-travel/ui/components/separator";
|
|
11
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@voyant-travel/ui/components/table";
|
|
12
|
+
import { ArrowLeft, BedDouble, CalendarClock, Check, Copy, CreditCard, ExternalLink, Pencil, Plane, Route as RouteIcon, Ship, Users, } from "lucide-react";
|
|
13
|
+
import { useState } from "react";
|
|
14
|
+
import { useVoyantTripsContext } from "../provider.js";
|
|
15
|
+
import { componentReferenceLabelFor, componentTitleFor, formatScheduleLabel, sortComponentsBySchedule, } from "./trip-component-display.js";
|
|
16
|
+
import { formatContactName, formatPersonName, readBilling, readTravelers, } from "./trip-detail-record-model.js";
|
|
17
|
+
export function TripRecordPage({ trip, onEdit }) {
|
|
18
|
+
const messages = useAdminMessages().trips;
|
|
19
|
+
const detailMessages = messages.detail;
|
|
20
|
+
const navigateTo = useAdminNavigate();
|
|
21
|
+
const { baseUrl } = useVoyantTripsContext();
|
|
22
|
+
const [copiedPaymentLink, setCopiedPaymentLink] = useState(false);
|
|
23
|
+
const envelope = trip.envelope;
|
|
24
|
+
const activeComponents = sortComponentsBySchedule(trip.components.filter((component) => component.status !== "removed"));
|
|
25
|
+
const bookedComponents = activeComponents.filter((component) => component.bookingId).length;
|
|
26
|
+
const externalRefs = activeComponents.filter((component) => component.orderId || component.paymentSessionId).length;
|
|
27
|
+
const scheduleSummary = tripScheduleLabel(activeComponents);
|
|
28
|
+
return (_jsxs("main", { className: "flex flex-col gap-6 p-6", children: [_jsxs("div", { className: "flex flex-col gap-4 md:flex-row md:items-start md:justify-between", children: [_jsxs("div", { className: "space-y-3", children: [_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => navigateTo("trip.list", {}), children: [_jsx(ArrowLeft, { className: "size-4", "aria-hidden": "true" }), detailMessages.breadcrumb] }), _jsxs("div", { className: "space-y-2", children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx("h1", { className: "font-bold text-2xl tracking-tight", children: detailMessages.title }), _jsx(Badge, { variant: envelope.status === "failed" ? "destructive" : "secondary", children: messages.statuses[envelope.status] })] }), envelope.description ? (_jsx("p", { className: "max-w-3xl text-muted-foreground text-sm", children: envelope.description })) : null, _jsx("p", { className: "text-muted-foreground text-xs", children: scheduleSummary
|
|
29
|
+
? `${scheduleSummary} · ${activeComponents.length} ${activeComponents.length === 1
|
|
30
|
+
? messages.list.componentSingular
|
|
31
|
+
: messages.list.componentPlural} · ${envelope.id}`
|
|
32
|
+
: `${activeComponents.length} ${activeComponents.length === 1
|
|
33
|
+
? messages.list.componentSingular
|
|
34
|
+
: messages.list.componentPlural} · ${envelope.id}` })] })] }), _jsxs(Button, { onClick: onEdit, children: [_jsx(Pencil, { className: "size-4", "aria-hidden": "true" }), detailMessages.editTrip] })] }), _jsxs("section", { className: "grid gap-4 md:grid-cols-4", children: [_jsx(SummaryCard, { label: detailMessages.summary.total, value: formatMoney(envelope.aggregateTotalAmountCents, envelope.aggregateCurrency), icon: CreditCard }), _jsx(SummaryCard, { label: detailMessages.summary.components, value: String(activeComponents.length), icon: RouteIcon }), _jsx(SummaryCard, { label: detailMessages.summary.bookings, value: String(bookedComponents), icon: CalendarClock }), _jsx(SummaryCard, { label: detailMessages.summary.externalRefs, value: String(externalRefs), icon: ExternalLink })] }), _jsxs("section", { className: "grid gap-4 lg:grid-cols-2", children: [_jsx(BillingRecord, { travelerParty: envelope.travelerParty, messages: detailMessages }), _jsx(TravelersRecord, { travelerParty: envelope.travelerParty, messages: detailMessages })] }), _jsxs("section", { className: "grid gap-6 xl:grid-cols-[minmax(0,1fr)_360px]", children: [_jsx("div", { className: "rounded-md border", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: detailMessages.components.component }), _jsx(TableHead, { children: detailMessages.components.status }), _jsx(TableHead, { children: detailMessages.components.tax }), _jsx(TableHead, { children: detailMessages.components.total }), _jsx(TableHead, { className: "text-right", children: detailMessages.components.record })] }) }), _jsx(TableBody, { children: activeComponents.length === 0 ? (_jsx(TableRow, { children: _jsx(TableCell, { colSpan: 5, className: "h-28 text-center text-muted-foreground text-sm", children: detailMessages.noComponentsOnTrip }) })) : (activeComponents.map((component) => (_jsx(ComponentRow, { component: component, messages: detailMessages, onOpenBooking: (bookingId) => navigateTo("booking.detail", { bookingId }) }, component.id)))) })] }) }), _jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: detailMessages.summary.record }) }), _jsxs(CardContent, { className: "space-y-4", children: [_jsx(SummaryLine, { label: detailMessages.summary.status, value: messages.statuses[envelope.status] }), _jsx(SummaryLine, { label: detailMessages.summary.updated, value: formatDate(envelope.updatedAt) }), _jsx(SummaryLine, { label: detailMessages.summary.reserved, value: formatDate(envelope.reservedAt) }), _jsx(SummaryLine, { label: detailMessages.summary.checkoutStarted, value: formatDate(envelope.checkoutStartedAt) }), envelope.paymentSessionId ? (_jsxs("div", { className: "flex items-center justify-between gap-4 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: detailMessages.summary.paymentLink }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => void copyPaymentLink(envelope.paymentSessionId ?? "", baseUrl).then((copied) => {
|
|
35
|
+
setCopiedPaymentLink(copied);
|
|
36
|
+
if (copied) {
|
|
37
|
+
window.setTimeout(() => setCopiedPaymentLink(false), 2000);
|
|
38
|
+
}
|
|
39
|
+
}), children: [copiedPaymentLink ? (_jsx(Check, { className: "size-4", "aria-hidden": "true" })) : (_jsx(Copy, { className: "size-4", "aria-hidden": "true" })), copiedPaymentLink
|
|
40
|
+
? detailMessages.summary.copied
|
|
41
|
+
: detailMessages.summary.copyLink] })] })) : null, _jsx(Separator, {}), _jsx(SummaryLine, { label: detailMessages.summary.subtotal, value: formatMoney(envelope.aggregateSubtotalAmountCents, envelope.aggregateCurrency) }), _jsx(SummaryLine, { label: detailMessages.summary.tax, value: formatMoney(envelope.aggregateTaxAmountCents, envelope.aggregateCurrency) }), _jsx(SummaryLine, { label: detailMessages.summary.total, value: formatMoney(envelope.aggregateTotalAmountCents, envelope.aggregateCurrency), strong: true })] })] })] })] }));
|
|
42
|
+
}
|
|
43
|
+
async function copyPaymentLink(paymentSessionId, apiBaseUrl) {
|
|
44
|
+
if (!paymentSessionId || typeof window === "undefined")
|
|
45
|
+
return false;
|
|
46
|
+
const publicCheckoutBaseUrl = await fetchPublicCheckoutBaseUrl(apiBaseUrl);
|
|
47
|
+
const url = buildPaymentLinkUrl(paymentSessionId, {
|
|
48
|
+
baseUrl: publicCheckoutBaseUrl ?? window.location.origin,
|
|
49
|
+
});
|
|
50
|
+
try {
|
|
51
|
+
await navigator.clipboard.writeText(url);
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function fetchPublicCheckoutBaseUrl(apiBaseUrl) {
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(`${apiBaseUrl}/v1/public/payment-link-config`, {
|
|
61
|
+
headers: { Accept: "application/json" },
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok)
|
|
64
|
+
return null;
|
|
65
|
+
const body = (await res.json());
|
|
66
|
+
return body.data?.publicCheckoutBaseUrl ?? null;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export function BillingRecord({ travelerParty, messages, }) {
|
|
73
|
+
const billing = readBilling(travelerParty);
|
|
74
|
+
const personQuery = usePerson(billing?.personId, { enabled: Boolean(billing?.personId) });
|
|
75
|
+
const orgQuery = useOrganization(billing?.organizationId, {
|
|
76
|
+
enabled: Boolean(billing?.organizationId),
|
|
77
|
+
});
|
|
78
|
+
const resolvedPersonName = formatPersonName(personQuery.data);
|
|
79
|
+
const primary = orgQuery.data?.name ??
|
|
80
|
+
resolvedPersonName ??
|
|
81
|
+
formatContactName(billing?.contact) ??
|
|
82
|
+
billing?.contact?.email ??
|
|
83
|
+
null;
|
|
84
|
+
const secondary = [
|
|
85
|
+
orgQuery.data?.name ? resolvedPersonName : null,
|
|
86
|
+
billing?.contact?.email,
|
|
87
|
+
billing?.buyerType,
|
|
88
|
+
].filter(Boolean);
|
|
89
|
+
return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsx(CardTitle, { className: "text-base", children: messages.billing.title }) }), _jsx(CardContent, { className: "space-y-3", children: primary ? (_jsxs(_Fragment, { children: [_jsxs("div", { children: [_jsx("p", { className: "font-medium", children: billing?.personId && !orgQuery.data?.name ? (_jsx(PersonLink, { personId: billing.personId, children: primary })) : (primary) }), secondary.length > 0 ? (_jsxs("div", { className: "flex flex-wrap gap-x-1 text-muted-foreground text-sm", children: [orgQuery.data?.name && billing?.personId && resolvedPersonName ? (_jsxs(_Fragment, { children: [_jsx(PersonLink, { personId: billing.personId, children: resolvedPersonName }), billing.contact?.email || billing.buyerType ? _jsx("span", { children: "\u00B7" }) : null] })) : null, billing?.contact?.email ? _jsx("span", { children: billing.contact.email }) : null, billing?.contact?.email && billing?.buyerType ? _jsx("span", { children: "\u00B7" }) : null, billing?.buyerType ? _jsx("span", { children: billing.buyerType }) : null] })) : null] }), _jsxs("div", { className: "flex flex-wrap gap-x-4 gap-y-1 text-muted-foreground text-xs", children: [billing?.personId ? (_jsxs("span", { children: [messages.billing.person, ": ", billing.personId] })) : null, billing?.organizationId ? (_jsxs("span", { children: [messages.billing.organization, ": ", billing.organizationId] })) : null] })] })) : (_jsx("p", { className: "text-muted-foreground text-sm", children: messages.billing.noProfile })) })] }));
|
|
90
|
+
}
|
|
91
|
+
export function TravelersRecord({ travelerParty, messages, }) {
|
|
92
|
+
const travelers = readTravelers(travelerParty);
|
|
93
|
+
return (_jsxs(Card, { children: [_jsx(CardHeader, { children: _jsxs(CardTitle, { className: "flex items-center gap-2 text-base", children: [_jsx(Users, { className: "size-4", "aria-hidden": "true" }), messages.travelers.title] }) }), _jsx(CardContent, { children: travelers.length > 0 ? (_jsx("ul", { className: "divide-y", children: travelers.map((traveler, index) => (_jsx(TravelerRecordRow, { traveler: traveler, index: index, messages: messages }, traveler.localId ?? traveler.personId ?? index))) })) : (_jsx("p", { className: "text-muted-foreground text-sm", children: messages.travelers.none })) })] }));
|
|
94
|
+
}
|
|
95
|
+
export function TravelerRecordRow({ traveler, index, messages, }) {
|
|
96
|
+
const personQuery = usePerson(traveler.personId ?? undefined, {
|
|
97
|
+
enabled: Boolean(traveler.personId),
|
|
98
|
+
});
|
|
99
|
+
const inlineName = [traveler.firstName, traveler.lastName]
|
|
100
|
+
.filter((part) => (part ?? "").trim().length > 0)
|
|
101
|
+
.join(" ")
|
|
102
|
+
.trim();
|
|
103
|
+
const name = inlineName ||
|
|
104
|
+
formatPersonName(personQuery.data) ||
|
|
105
|
+
formatMessage(messages.travelers.fallbackName, { index: String(index + 1) });
|
|
106
|
+
const email = traveler.email ?? personQuery.data?.email ?? null;
|
|
107
|
+
return (_jsxs("li", { className: "flex items-center justify-between gap-4 py-3 first:pt-0 last:pb-0", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate font-medium", children: traveler.personId ? _jsx(PersonLink, { personId: traveler.personId, children: name }) : name }), email ? _jsx("p", { className: "truncate text-muted-foreground text-sm", children: email }) : null] }), _jsx(Badge, { variant: "secondary", className: "shrink-0 capitalize", children: traveler.role ?? messages.travelers.fallbackRole })] }));
|
|
108
|
+
}
|
|
109
|
+
export function PersonLink({ personId, children }) {
|
|
110
|
+
const resolveHref = useAdminHref();
|
|
111
|
+
const navigateTo = useAdminNavigate();
|
|
112
|
+
return (_jsx("a", { href: resolveHref("person.detail", { personId }), onClick: (event) => {
|
|
113
|
+
event.preventDefault();
|
|
114
|
+
navigateTo("person.detail", { personId });
|
|
115
|
+
}, className: "text-primary hover:underline", children: children }));
|
|
116
|
+
}
|
|
117
|
+
export function SummaryCard({ label, value, icon: Icon, }) {
|
|
118
|
+
return (_jsx(Card, { children: _jsxs(CardContent, { className: "flex items-center gap-3 p-4", children: [_jsx("span", { className: "flex size-10 shrink-0 items-center justify-center rounded-md bg-muted", children: _jsx(Icon, { className: "size-4", "aria-hidden": "true" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "text-muted-foreground text-xs", children: label }), _jsx("p", { className: "truncate font-semibold text-lg", children: value })] })] }) }));
|
|
119
|
+
}
|
|
120
|
+
export function ComponentRow({ component, messages, onOpenBooking, }) {
|
|
121
|
+
const Icon = componentIcon(component);
|
|
122
|
+
const componentName = componentTitleFor(component);
|
|
123
|
+
const scheduleLabel = formatScheduleLabel(component);
|
|
124
|
+
const referenceLabel = componentReferenceLabelFor(component);
|
|
125
|
+
const secondary = [scheduleLabel, referenceLabel === componentName ? null : referenceLabel]
|
|
126
|
+
.filter(Boolean)
|
|
127
|
+
.join(" · ");
|
|
128
|
+
return (_jsxs(TableRow, { children: [_jsx(TableCell, { children: _jsxs("div", { className: "flex min-w-0 items-center gap-3", children: [_jsx("span", { className: "flex size-9 shrink-0 items-center justify-center rounded-md bg-muted", children: _jsx(Icon, { className: "size-4", "aria-hidden": "true" }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("p", { className: "truncate font-medium", children: componentName }), secondary ? (_jsx("p", { className: "truncate text-muted-foreground text-xs", children: secondary })) : null] })] }) }), _jsx(TableCell, { children: _jsx(Badge, { variant: component.status === "failed" ? "destructive" : "secondary", children: formatStatus(component.status) }) }), _jsx(TableCell, { children: formatMoney(component.componentTaxAmountCents, component.componentCurrency) }), _jsx(TableCell, { children: formatMoney(component.componentTotalAmountCents, component.componentCurrency) }), _jsx(TableCell, { className: "text-right", children: component.bookingId ? (_jsxs(Button, { variant: "ghost", size: "sm", onClick: () => onOpenBooking(component.bookingId ?? ""), children: [_jsx(ExternalLink, { className: "size-4", "aria-hidden": "true" }), messages.components.booking] })) : component.orderId || component.paymentSessionId ? (_jsx("span", { className: "text-muted-foreground text-sm", children: component.orderId ?? component.paymentSessionId })) : (_jsx("span", { className: "text-muted-foreground text-sm", children: messages.components.notCommitted })) })] }));
|
|
129
|
+
}
|
|
130
|
+
export function tripScheduleLabel(components) {
|
|
131
|
+
const labels = components
|
|
132
|
+
.map(formatScheduleLabel)
|
|
133
|
+
.filter((label) => Boolean(label));
|
|
134
|
+
if (labels.length === 0)
|
|
135
|
+
return null;
|
|
136
|
+
if (labels.length === 1)
|
|
137
|
+
return labels[0];
|
|
138
|
+
return `${labels[0]} -> ${labels.at(-1)}`;
|
|
139
|
+
}
|
|
140
|
+
export function SummaryLine({ label, value, strong = false, }) {
|
|
141
|
+
return (_jsxs("div", { className: "flex items-center justify-between gap-4 text-sm", children: [_jsx("span", { className: "text-muted-foreground", children: label }), _jsx("span", { className: strong ? "font-semibold" : "font-medium", children: value })] }));
|
|
142
|
+
}
|
|
143
|
+
export function componentIcon(component) {
|
|
144
|
+
if (component.kind === "flight_placeholder" || component.kind === "flight_order")
|
|
145
|
+
return Plane;
|
|
146
|
+
if (component.entityModule === "accommodations")
|
|
147
|
+
return BedDouble;
|
|
148
|
+
if (component.entityModule === "cruises")
|
|
149
|
+
return Ship;
|
|
150
|
+
return RouteIcon;
|
|
151
|
+
}
|
|
152
|
+
export function formatStatus(value) {
|
|
153
|
+
return value.replaceAll("_", " ");
|
|
154
|
+
}
|
|
155
|
+
export function formatDate(value) {
|
|
156
|
+
if (!value)
|
|
157
|
+
return "-";
|
|
158
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
159
|
+
if (Number.isNaN(date.getTime()))
|
|
160
|
+
return "-";
|
|
161
|
+
return date.toLocaleDateString();
|
|
162
|
+
}
|
|
163
|
+
export function formatMoney(amountCents, currency) {
|
|
164
|
+
if (amountCents == null)
|
|
165
|
+
return "-";
|
|
166
|
+
return (amountCents / 100).toLocaleString(undefined, {
|
|
167
|
+
style: "currency",
|
|
168
|
+
currency: currency ?? "EUR",
|
|
169
|
+
});
|
|
170
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TripEnvelopeStatus } from "@voyant-travel/trips";
|
|
2
|
+
export declare const TRIP_STATUS_ALL = "__all__";
|
|
3
|
+
export type TripStatusFilter = TripEnvelopeStatus | typeof TRIP_STATUS_ALL;
|
|
4
|
+
export interface TripListFiltersPopoverProps {
|
|
5
|
+
open: boolean;
|
|
6
|
+
onOpenChange(open: boolean): void;
|
|
7
|
+
activeFilterCount: number;
|
|
8
|
+
status: TripStatusFilter;
|
|
9
|
+
onStatusChange(value: TripStatusFilter): void;
|
|
10
|
+
productId: string | null;
|
|
11
|
+
onProductIdChange(value: string | null): void;
|
|
12
|
+
accommodationId: string | null;
|
|
13
|
+
onAccommodationIdChange(value: string | null): void;
|
|
14
|
+
cruiseId: string | null;
|
|
15
|
+
onCruiseIdChange(value: string | null): void;
|
|
16
|
+
hasFlight: boolean;
|
|
17
|
+
onHasFlightChange(value: boolean): void;
|
|
18
|
+
totalMin: string;
|
|
19
|
+
onTotalMinChange(value: string): void;
|
|
20
|
+
totalMax: string;
|
|
21
|
+
onTotalMaxChange(value: string): void;
|
|
22
|
+
createdRange: {
|
|
23
|
+
from: string | null;
|
|
24
|
+
to: string | null;
|
|
25
|
+
} | null;
|
|
26
|
+
onCreatedRangeChange(value: {
|
|
27
|
+
from: string | null;
|
|
28
|
+
to: string | null;
|
|
29
|
+
} | null): void;
|
|
30
|
+
onFiltersChanged(): void;
|
|
31
|
+
}
|
|
32
|
+
export declare function TripListFiltersPopover({ open, onOpenChange, activeFilterCount, status, onStatusChange, productId, onProductIdChange, accommodationId, onAccommodationIdChange, cruiseId, onCruiseIdChange, hasFlight, onHasFlightChange, totalMin, onTotalMinChange, totalMax, onTotalMaxChange, createdRange, onCreatedRangeChange, onFiltersChanged, }: TripListFiltersPopoverProps): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
//# sourceMappingURL=trip-list-filters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"trip-list-filters.d.ts","sourceRoot":"","sources":["../../src/admin/trip-list-filters.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAA;AAmB9D,eAAO,MAAM,eAAe,YAAY,CAAA;AACxC,MAAM,MAAM,gBAAgB,GAAG,kBAAkB,GAAG,OAAO,eAAe,CAAA;AAgE1E,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,CAAA;IACjC,iBAAiB,EAAE,MAAM,CAAA;IACzB,MAAM,EAAE,gBAAgB,CAAA;IACxB,cAAc,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAC7C,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAC7C,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IACnD,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAC5C,SAAS,EAAE,OAAO,CAAA;IAClB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAA;IACvC,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,QAAQ,EAAE,MAAM,CAAA;IAChB,gBAAgB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACrC,YAAY,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAA;IAC/D,oBAAoB,CAAC,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,GAAG,IAAI,CAAA;IACpF,gBAAgB,IAAI,IAAI,CAAA;CACzB;AAED,wBAAgB,sBAAsB,CAAC,EACrC,IAAI,EACJ,YAAY,EACZ,iBAAiB,EACjB,MAAM,EACN,cAAc,EACd,SAAS,EACT,iBAAiB,EACjB,eAAe,EACf,uBAAuB,EACvB,QAAQ,EACR,gBAAgB,EAChB,SAAS,EACT,iBAAiB,EACjB,QAAQ,EACR,gBAAgB,EAChB,QAAQ,EACR,gBAAgB,EAChB,YAAY,EACZ,oBAAoB,EACpB,gBAAgB,GACjB,EAAE,2BAA2B,2CA6L7B"}
|