@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.
Files changed (114) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +30 -0
  3. package/dist/admin/admin-trips-page-controls.d.ts +28 -0
  4. package/dist/admin/admin-trips-page-controls.d.ts.map +1 -0
  5. package/dist/admin/admin-trips-page-controls.js +28 -0
  6. package/dist/admin/admin-trips-page-model.d.ts +87 -0
  7. package/dist/admin/admin-trips-page-model.d.ts.map +1 -0
  8. package/dist/admin/admin-trips-page-model.js +457 -0
  9. package/dist/admin/admin-trips-page.d.ts +6 -0
  10. package/dist/admin/admin-trips-page.d.ts.map +1 -0
  11. package/dist/admin/admin-trips-page.js +322 -0
  12. package/dist/admin/admin-trips-panels.d.ts +11 -0
  13. package/dist/admin/admin-trips-panels.d.ts.map +1 -0
  14. package/dist/admin/admin-trips-panels.js +11 -0
  15. package/dist/admin/index.d.ts +63 -0
  16. package/dist/admin/index.d.ts.map +1 -0
  17. package/dist/admin/index.js +119 -0
  18. package/dist/admin/pages/trip-detail-page.d.ts +10 -0
  19. package/dist/admin/pages/trip-detail-page.d.ts.map +1 -0
  20. package/dist/admin/pages/trip-detail-page.js +12 -0
  21. package/dist/admin/trip-component-display.d.ts +10 -0
  22. package/dist/admin/trip-component-display.d.ts.map +1 -0
  23. package/dist/admin/trip-component-display.js +137 -0
  24. package/dist/admin/trip-detail-host.d.ts +12 -0
  25. package/dist/admin/trip-detail-host.d.ts.map +1 -0
  26. package/dist/admin/trip-detail-host.js +37 -0
  27. package/dist/admin/trip-detail-record-model.d.ts +30 -0
  28. package/dist/admin/trip-detail-record-model.d.ts.map +1 -0
  29. package/dist/admin/trip-detail-record-model.js +56 -0
  30. package/dist/admin/trip-detail-record.d.ts +47 -0
  31. package/dist/admin/trip-detail-record.d.ts.map +1 -0
  32. package/dist/admin/trip-detail-record.js +170 -0
  33. package/dist/admin/trip-list-filters.d.ts +33 -0
  34. package/dist/admin/trip-list-filters.d.ts.map +1 -0
  35. package/dist/admin/trip-list-filters.js +94 -0
  36. package/dist/admin/trips-host.d.ts +15 -0
  37. package/dist/admin/trips-host.d.ts.map +1 -0
  38. package/dist/admin/trips-host.js +233 -0
  39. package/dist/admin/trips-panels/catalog-configurator.d.ts +34 -0
  40. package/dist/admin/trips-panels/catalog-configurator.d.ts.map +1 -0
  41. package/dist/admin/trips-panels/catalog-configurator.js +200 -0
  42. package/dist/admin/trips-panels/catalog-options.d.ts +58 -0
  43. package/dist/admin/trips-panels/catalog-options.d.ts.map +1 -0
  44. package/dist/admin/trips-panels/catalog-options.js +124 -0
  45. package/dist/admin/trips-panels/committed-component-card.d.ts +27 -0
  46. package/dist/admin/trips-panels/committed-component-card.d.ts.map +1 -0
  47. package/dist/admin/trips-panels/committed-component-card.js +44 -0
  48. package/dist/admin/trips-panels/display.d.ts +51 -0
  49. package/dist/admin/trips-panels/display.d.ts.map +1 -0
  50. package/dist/admin/trips-panels/display.js +336 -0
  51. package/dist/admin/trips-panels/flight-configurator.d.ts +34 -0
  52. package/dist/admin/trips-panels/flight-configurator.d.ts.map +1 -0
  53. package/dist/admin/trips-panels/flight-configurator.js +208 -0
  54. package/dist/admin/trips-panels/manual-configurators.d.ts +16 -0
  55. package/dist/admin/trips-panels/manual-configurators.d.ts.map +1 -0
  56. package/dist/admin/trips-panels/manual-configurators.js +41 -0
  57. package/dist/admin/trips-panels/pending-component-card.d.ts +16 -0
  58. package/dist/admin/trips-panels/pending-component-card.d.ts.map +1 -0
  59. package/dist/admin/trips-panels/pending-component-card.js +29 -0
  60. package/dist/admin/trips-panels/shared.d.ts +122 -0
  61. package/dist/admin/trips-panels/shared.d.ts.map +1 -0
  62. package/dist/admin/trips-panels/shared.js +152 -0
  63. package/dist/admin/trips-panels/travelers-section.d.ts +53 -0
  64. package/dist/admin/trips-panels/travelers-section.d.ts.map +1 -0
  65. package/dist/admin/trips-panels/travelers-section.js +183 -0
  66. package/dist/admin/trips-panels/trip-preview-rail.d.ts +52 -0
  67. package/dist/admin/trips-panels/trip-preview-rail.d.ts.map +1 -0
  68. package/dist/admin/trips-panels/trip-preview-rail.js +122 -0
  69. package/dist/cache.d.ts +9 -0
  70. package/dist/cache.d.ts.map +1 -0
  71. package/dist/cache.js +21 -0
  72. package/dist/client.d.ts +15 -0
  73. package/dist/client.d.ts.map +1 -0
  74. package/dist/client.js +51 -0
  75. package/dist/hooks/index.d.ts +7 -0
  76. package/dist/hooks/index.d.ts.map +1 -0
  77. package/dist/hooks/index.js +6 -0
  78. package/dist/hooks/use-price-trip.d.ts +3 -0
  79. package/dist/hooks/use-price-trip.d.ts.map +1 -0
  80. package/dist/hooks/use-price-trip.js +17 -0
  81. package/dist/hooks/use-reserve-trip.d.ts +3 -0
  82. package/dist/hooks/use-reserve-trip.d.ts.map +1 -0
  83. package/dist/hooks/use-reserve-trip.js +17 -0
  84. package/dist/hooks/use-trip-checkout.d.ts +3 -0
  85. package/dist/hooks/use-trip-checkout.d.ts.map +1 -0
  86. package/dist/hooks/use-trip-checkout.js +17 -0
  87. package/dist/hooks/use-trip-components.d.ts +40 -0
  88. package/dist/hooks/use-trip-components.d.ts.map +1 -0
  89. package/dist/hooks/use-trip-components.js +13 -0
  90. package/dist/hooks/use-trip.d.ts +5 -0
  91. package/dist/hooks/use-trip.d.ts.map +1 -0
  92. package/dist/hooks/use-trip.js +13 -0
  93. package/dist/hooks/use-trips.d.ts +6 -0
  94. package/dist/hooks/use-trips.d.ts.map +1 -0
  95. package/dist/hooks/use-trips.js +12 -0
  96. package/dist/index.d.ts +9 -0
  97. package/dist/index.d.ts.map +1 -0
  98. package/dist/index.js +8 -0
  99. package/dist/operations.d.ts +212 -0
  100. package/dist/operations.d.ts.map +1 -0
  101. package/dist/operations.js +92 -0
  102. package/dist/provider.d.ts +2 -0
  103. package/dist/provider.d.ts.map +1 -0
  104. package/dist/provider.js +1 -0
  105. package/dist/query-keys.d.ts +10 -0
  106. package/dist/query-keys.d.ts.map +1 -0
  107. package/dist/query-keys.js +9 -0
  108. package/dist/query-options.d.ts +167 -0
  109. package/dist/query-options.d.ts.map +1 -0
  110. package/dist/query-options.js +22 -0
  111. package/dist/schemas.d.ts +69 -0
  112. package/dist/schemas.d.ts.map +1 -0
  113. package/dist/schemas.js +16 -0
  114. package/package.json +133 -0
@@ -0,0 +1,336 @@
1
+ "use client";
2
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
3
+ import { emptyPaymentScheduleValue } from "@voyant-travel/bookings-react/components/payment-schedule-section";
4
+ import { BedDouble, ExternalLink, Landmark, Plane, Route as RouteIcon } from "lucide-react";
5
+ export function resolveBillingDisplay(billing, person, org, messages) {
6
+ if (billing.billTo === "organization" && org?.name) {
7
+ return { primary: org.name, secondary: messages.billingPreview.organizationSecondary };
8
+ }
9
+ if (billing.mode === "new") {
10
+ const name = [billing.newPerson.firstName, billing.newPerson.lastName]
11
+ .filter((part) => part.trim().length > 0)
12
+ .join(" ")
13
+ .trim();
14
+ return {
15
+ primary: name || billing.newPerson.email.trim(),
16
+ secondary: name ? billing.newPerson.email.trim() : "",
17
+ };
18
+ }
19
+ if (person) {
20
+ const name = [person.firstName, person.lastName]
21
+ .filter((part) => (part ?? "").trim().length > 0)
22
+ .join(" ")
23
+ .trim();
24
+ return { primary: name || (person.email ?? ""), secondary: name ? (person.email ?? "") : "" };
25
+ }
26
+ return { primary: "", secondary: "" };
27
+ }
28
+ export function Reference({ label, value, href, }) {
29
+ if (!value)
30
+ return null;
31
+ if (!href)
32
+ return (_jsxs("span", { children: [label, ": ", _jsxs("span", { className: "font-mono", children: [value.slice(0, 12), "\u2026"] })] }));
33
+ return (_jsxs("a", { className: "inline-flex items-center gap-1 text-primary", href: href, children: [label, ": ", _jsxs("span", { className: "font-mono", children: [value.slice(0, 12), "\u2026"] }), _jsx(ExternalLink, { className: "size-3" })] }));
34
+ }
35
+ export function componentIcon(component) {
36
+ if (component.kind === "flight_placeholder" || component.kind === "flight_order")
37
+ return Plane;
38
+ if (component.entityModule === "accommodations")
39
+ return BedDouble;
40
+ if (component.kind === "manual_placeholder" || component.kind === "external_order")
41
+ return Landmark;
42
+ return RouteIcon;
43
+ }
44
+ export function readComponentSchedule(component) {
45
+ const md = component.metadata;
46
+ if (md?.scheduledStartsAt) {
47
+ return { start: md.scheduledStartsAt, end: md.scheduledEndsAt ?? null };
48
+ }
49
+ const dateRange = md?.bookingDraftV1?.configure?.dateRange;
50
+ if (dateRange?.checkIn) {
51
+ return { start: dateRange.checkIn, end: dateRange.checkOut ?? null };
52
+ }
53
+ const flight = md?.flightDraft;
54
+ if (flight?.departDate) {
55
+ return { start: flight.departDate, end: flight.returnDate ?? null };
56
+ }
57
+ const cruise = md?.cruiseDraft;
58
+ if (cruise?.embarkationDate) {
59
+ return { start: cruise.embarkationDate, end: null };
60
+ }
61
+ return { start: null, end: null };
62
+ }
63
+ export function formatScheduleLabel(component) {
64
+ const { start, end } = readComponentSchedule(component);
65
+ if (!start)
66
+ return null;
67
+ const startLabel = formatDateTime(start);
68
+ if (!end || end === start)
69
+ return startLabel;
70
+ return `${startLabel} → ${formatDateTime(end)}`;
71
+ }
72
+ export function sortComponentsBySchedule(components) {
73
+ return [...components].sort((a, b) => {
74
+ const aStart = readComponentSchedule(a).start;
75
+ const bStart = readComponentSchedule(b).start;
76
+ const aMs = aStart ? new Date(aStart).getTime() : Number.NaN;
77
+ const bMs = bStart ? new Date(bStart).getTime() : Number.NaN;
78
+ // Components without a schedule fall to the bottom, then ordered by sequence.
79
+ const aMissing = Number.isNaN(aMs);
80
+ const bMissing = Number.isNaN(bMs);
81
+ if (aMissing && bMissing)
82
+ return a.sequence - b.sequence;
83
+ if (aMissing)
84
+ return 1;
85
+ if (bMissing)
86
+ return -1;
87
+ if (aMs !== bMs)
88
+ return aMs - bMs;
89
+ return a.sequence - b.sequence;
90
+ });
91
+ }
92
+ export function readPendingSchedule(pending) {
93
+ if (pending.kind === "product" || pending.kind === "stay") {
94
+ return { start: pending.startsAt || null, end: pending.endsAt || null };
95
+ }
96
+ if (pending.kind === "flight") {
97
+ const offerSchedule = readFlightOfferSchedule(pending.selectedOffer);
98
+ if (offerSchedule.start)
99
+ return offerSchedule;
100
+ return { start: pending.departDate || null, end: pending.returnDate || null };
101
+ }
102
+ if (pending.kind === "cruise") {
103
+ return { start: pending.embarkationDate || null, end: null };
104
+ }
105
+ return { start: pending.startsAt || null, end: pending.endsAt || null };
106
+ }
107
+ export function readFlightOfferSchedule(offer) {
108
+ if (!offer)
109
+ return { start: null, end: null };
110
+ const firstItinerary = offer.itineraries[0];
111
+ const lastItinerary = offer.itineraries[offer.itineraries.length - 1];
112
+ const firstSegment = firstItinerary?.segments[0];
113
+ const lastSegment = lastItinerary?.segments[lastItinerary.segments.length - 1];
114
+ return {
115
+ start: firstSegment?.departure.at ?? null,
116
+ end: lastSegment?.arrival.at ?? null,
117
+ };
118
+ }
119
+ export function toRange(start, end) {
120
+ if (!start)
121
+ return null;
122
+ const startMs = new Date(start).getTime();
123
+ if (Number.isNaN(startMs))
124
+ return null;
125
+ const endMs = end ? new Date(end).getTime() : startMs;
126
+ if (Number.isNaN(endMs))
127
+ return [startMs, startMs];
128
+ return [startMs, Math.max(endMs, startMs)];
129
+ }
130
+ export function findOverlappingComponent(pending, committed) {
131
+ const { start: pStart, end: pEnd } = readPendingSchedule(pending);
132
+ const pendingRange = toRange(pStart, pEnd);
133
+ if (!pendingRange)
134
+ return null;
135
+ for (const component of committed) {
136
+ const { start, end } = readComponentSchedule(component);
137
+ const range = toRange(start, end);
138
+ if (!range)
139
+ continue;
140
+ // Half-open overlap: [a1, a2) ∩ [b1, b2) ≠ ∅ iff a1 < b2 ∧ b1 < a2.
141
+ if (pendingRange[0] < range[1] && range[0] < pendingRange[1])
142
+ return component;
143
+ }
144
+ return null;
145
+ }
146
+ export function componentTitleFor(component, resolvedEntityName) {
147
+ const metadata = component.metadata;
148
+ const entityName = cleanDisplayLabel(resolvedEntityName);
149
+ if (entityName)
150
+ return entityName;
151
+ const catalogName = cleanDisplayLabel(metadata?.catalogItem?.name);
152
+ if (catalogName)
153
+ return catalogName;
154
+ if (component.kind === "flight_placeholder" || component.kind === "flight_order") {
155
+ const origin = cleanDisplayLabel(metadata?.flightDraft?.origin);
156
+ const destination = cleanDisplayLabel(metadata?.flightDraft?.destination);
157
+ if (origin && destination)
158
+ return `${origin} → ${destination}`;
159
+ }
160
+ if (component.entityModule === "cruises") {
161
+ const cabin = cleanDisplayLabel(metadata?.cruiseDraft?.cabin);
162
+ if (cabin)
163
+ return `Cabin ${cabin}`;
164
+ }
165
+ if (component.entityModule === "accommodations") {
166
+ const accommodationName = cleanDisplayLabel(metadata?.accommodation?.propertyName) ??
167
+ cleanDisplayLabel(metadata?.accommodation?.name) ??
168
+ cleanDisplayLabel(metadata?.accommodation?.roomTypeName);
169
+ if (accommodationName)
170
+ return accommodationName;
171
+ }
172
+ if (metadata?.cruiseDraft) {
173
+ const cabin = cleanDisplayLabel(metadata.cruiseDraft.cabin);
174
+ if (cabin)
175
+ return `Cabin ${cabin}`;
176
+ }
177
+ if (component.kind === "manual_placeholder") {
178
+ const serviceName = cleanDisplayLabel(metadata?.manualService?.name);
179
+ if (serviceName)
180
+ return serviceName;
181
+ const title = cleanDisplayLabel(component.title);
182
+ if (title)
183
+ return title;
184
+ const description = cleanDisplayLabel(component.description);
185
+ if (description)
186
+ return description;
187
+ }
188
+ return componentReferenceLabelFor(component);
189
+ }
190
+ export function componentOptionSummaryFor(component) {
191
+ const metadata = component.metadata;
192
+ if (metadata?.flightDraft) {
193
+ const flightLabels = flightSelectionLabels(metadata.flightDraft.selectedOffer ?? null, metadata.flightDraft.ancillaries ?? null);
194
+ if (flightLabels.length > 0)
195
+ return flightLabels.join(", ");
196
+ }
197
+ const selections = metadata?.bookingDraftV1?.configure?.optionSelections ?? [];
198
+ const labels = selections.flatMap((selection) => {
199
+ const quantity = typeof selection.quantity === "number" && Number.isFinite(selection.quantity)
200
+ ? selection.quantity
201
+ : 0;
202
+ if (quantity <= 0)
203
+ return [];
204
+ const name = cleanDisplayLabel(selection.optionName) ??
205
+ cleanDisplayLabel(selection.optionUnitName) ??
206
+ cleanDisplayLabel(selection.optionId) ??
207
+ cleanDisplayLabel(selection.optionUnitId);
208
+ if (!name)
209
+ return [];
210
+ return [`${quantity} × ${name}`];
211
+ });
212
+ return labels.length > 0 ? labels.join(", ") : null;
213
+ }
214
+ export function flightSelectionLabels(offer, ancillaries) {
215
+ if (!offer || !ancillaries)
216
+ return [];
217
+ const labels = [];
218
+ const bundles = offer.fareBundles ?? [];
219
+ const bundleCounts = countById(ancillaries.fareBundle?.map((pick) => pick.bundleId) ?? []);
220
+ for (const [bundleId, quantity] of bundleCounts) {
221
+ const bundle = bundles.find((candidate) => candidate.id === bundleId);
222
+ labels.push(`${quantity} × ${bundle?.label ?? bundleId}`);
223
+ }
224
+ const baggageCount = ancillaries.baggage?.reduce((sum, pick) => sum + (pick.quantity ?? 1), 0) ?? 0;
225
+ if (baggageCount > 0)
226
+ labels.push(`${baggageCount} bag${baggageCount === 1 ? "" : "s"}`);
227
+ const assistanceCount = ancillaries.assistance?.length ?? 0;
228
+ if (assistanceCount > 0) {
229
+ labels.push(`${assistanceCount} assistance request${assistanceCount === 1 ? "" : "s"}`);
230
+ }
231
+ const extrasCount = ancillaries.extras?.reduce((sum, pick) => sum + (pick.quantity ?? 1), 0) ?? 0;
232
+ if (extrasCount > 0)
233
+ labels.push(`${extrasCount} extra${extrasCount === 1 ? "" : "s"}`);
234
+ return labels;
235
+ }
236
+ function countById(values) {
237
+ const counts = new Map();
238
+ for (const value of values)
239
+ counts.set(value, (counts.get(value) ?? 0) + 1);
240
+ return counts;
241
+ }
242
+ export function componentThumbnailFor(component) {
243
+ const metadata = component.metadata;
244
+ return cleanDisplayLabel(metadata?.catalogItem?.thumbnailUrl);
245
+ }
246
+ export function componentReferenceLabelFor(component) {
247
+ const reference = component.providerRef ??
248
+ component.supplierRef ??
249
+ component.bookingId ??
250
+ component.orderId ??
251
+ component.paymentSessionId ??
252
+ component.sourceRef ??
253
+ component.entityId ??
254
+ component.id;
255
+ return reference.length > 18 ? reference.slice(0, 18) : reference;
256
+ }
257
+ export function cleanDisplayLabel(value) {
258
+ const trimmed = value?.trim();
259
+ if (!trimmed)
260
+ return null;
261
+ const normalized = trimmed.toLowerCase();
262
+ if (normalized === "untitled trip" ||
263
+ normalized === "untitled component" ||
264
+ normalized === "flight placeholder" ||
265
+ normalized.startsWith("flight placeholder ") ||
266
+ normalized === "manual placeholder" ||
267
+ normalized === "cruise" ||
268
+ normalized === "cruise placeholder" ||
269
+ normalized === "catalog booking" ||
270
+ normalized === "product booking" ||
271
+ normalized === "stay booking" ||
272
+ normalized === "cruise booking" ||
273
+ normalized === "external order" ||
274
+ normalized === "flight order" ||
275
+ /^component \d+$/.test(normalized)) {
276
+ return null;
277
+ }
278
+ return trimmed;
279
+ }
280
+ export function recordFromUnknown(value) {
281
+ return value && typeof value === "object" && !Array.isArray(value)
282
+ ? value
283
+ : null;
284
+ }
285
+ export function paymentScheduleValueFromUnknown(value) {
286
+ const record = recordFromUnknown(value);
287
+ const mode = record?.mode === "split" ? "split" : record?.mode === "full" ? "full" : null;
288
+ if (!mode)
289
+ return { ...emptyPaymentScheduleValue };
290
+ return {
291
+ ...emptyPaymentScheduleValue,
292
+ ...record,
293
+ mode,
294
+ };
295
+ }
296
+ // Backend-emitted noise codes that just acknowledge how the price was set —
297
+ // staff-built trips always carry `manual_placeholder_price` for manual /
298
+ // external / flight placeholders. `currency_mismatch:*` is also noise here
299
+ // because the rail already breaks totals out per currency.
300
+ export function isUserVisibleWarning(code) {
301
+ if (code === "manual_placeholder_price")
302
+ return false;
303
+ if (code.startsWith("currency_mismatch"))
304
+ return false;
305
+ return true;
306
+ }
307
+ export function formatDateTime(iso) {
308
+ const parsed = new Date(iso);
309
+ if (Number.isNaN(parsed.getTime()))
310
+ return iso;
311
+ const hasTime = iso.includes("T") || iso.includes(" ");
312
+ return new Intl.DateTimeFormat(undefined, {
313
+ year: "numeric",
314
+ month: "short",
315
+ day: "numeric",
316
+ ...(hasTime ? { hour: "2-digit", minute: "2-digit" } : {}),
317
+ }).format(parsed);
318
+ }
319
+ export function formatDepartureLabel(slot) {
320
+ const date = formatDateTime(slot.startsAt);
321
+ const duration = slot.nights ? ` · ${slot.nights}n` : slot.days ? ` · ${slot.days}d` : "";
322
+ const capacity = slot.unlimited
323
+ ? ""
324
+ : slot.remainingPax != null
325
+ ? ` · ${slot.remainingPax} left`
326
+ : "";
327
+ return `${date}${duration}${capacity}`;
328
+ }
329
+ export function formatMoney(amountCents, currencyCode) {
330
+ if (amountCents == null)
331
+ return "-";
332
+ return (amountCents / 100).toLocaleString(undefined, {
333
+ style: "currency",
334
+ currency: currencyCode ?? "EUR",
335
+ });
336
+ }
@@ -0,0 +1,34 @@
1
+ import type { CabinClass, FlightOffer, FlightPassenger, PassengerCounts } from "@voyant-travel/flights/contract/types";
2
+ import { type PendingComponent } from "./shared.js";
3
+ import type { TripTraveler } from "./travelers-section.js";
4
+ export declare function FlightConfigurator({ pending, travelers, onChange, }: {
5
+ pending: Extract<PendingComponent, {
6
+ kind: "flight";
7
+ }>;
8
+ travelers: TripTraveler[];
9
+ onChange(next: PendingComponent): void;
10
+ }): import("react/jsx-runtime").JSX.Element;
11
+ export declare function CabinSelector({ value, onChange, }: {
12
+ value: CabinClass;
13
+ onChange(next: CabinClass): void;
14
+ }): import("react/jsx-runtime").JSX.Element;
15
+ interface FlightPricingBreakdown {
16
+ currency: string;
17
+ subtotalAmountCents: number;
18
+ taxAmountCents: number;
19
+ ancillaryAmountCents: number;
20
+ totalAmountCents: number;
21
+ }
22
+ export declare function flightPricingFromPending(pending: Extract<PendingComponent, {
23
+ kind: "flight";
24
+ }>): FlightPricingBreakdown;
25
+ export declare function flightAncillaryAmountCents(pending: Extract<PendingComponent, {
26
+ kind: "flight";
27
+ }>, currencyCode: string): number;
28
+ export declare function legOffer(offer: FlightOffer, index: number): FlightOffer;
29
+ export declare function moneyToCents(amount: string): number;
30
+ export declare function passengerCountsFromTripTravelers(travelers: TripTraveler[]): PassengerCounts;
31
+ export declare function flightPassengersFromTripTravelers(travelers: TripTraveler[]): FlightPassenger[];
32
+ export declare function fallbackDobForPassengerType(type: FlightPassenger["type"]): string;
33
+ export {};
34
+ //# sourceMappingURL=flight-configurator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flight-configurator.d.ts","sourceRoot":"","sources":["../../../src/admin/trips-panels/flight-configurator.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,eAAe,EACf,eAAe,EAChB,MAAM,uCAAuC,CAAA;AAgB9C,OAAO,EAAS,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC1D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,wBAAwB,CAAA;AAE1D,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACP,SAAS,EACT,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,OAAO,CAAC,gBAAgB,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAA;IACtD,SAAS,EAAE,YAAY,EAAE,CAAA;IACzB,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAA;CACvC,2CAgSA;AAED,wBAAgB,aAAa,CAAC,EAC5B,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAA;CACjC,2CAuBA;AAED,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,cAAc,EAAE,MAAM,CAAA;IACtB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,gBAAgB,EAAE,MAAM,CAAA;CACzB;AAED,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,GACrD,sBAAsB,CAyBxB;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,EACtD,YAAY,EAAE,MAAM,GACnB,MAAM,CAiCR;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,WAAW,CAGvE;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED,wBAAgB,gCAAgC,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,eAAe,CAe3F;AAED,wBAAgB,iCAAiC,CAAC,SAAS,EAAE,YAAY,EAAE,GAAG,eAAe,EAAE,CAkB9F;AAED,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,MAAM,CAIjF"}
@@ -0,0 +1,208 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useOperatorAdminMessages as useAdminMessages } from "@voyant-travel/admin";
4
+ import { useFlightAncillaries, useFlightSearch } from "@voyant-travel/flights-react";
5
+ import { AirportCombobox, FlightBaggageStep, FlightFareUpsellStep, FlightOfferRow, FlightServicesStep, } from "@voyant-travel/flights-react/ui";
6
+ import { formatMessage } from "@voyant-travel/i18n";
7
+ import { Button } from "@voyant-travel/ui/components/button";
8
+ import { DatePicker } from "@voyant-travel/ui/components/date-picker";
9
+ import { Loader2, X } from "lucide-react";
10
+ import * as React from "react";
11
+ import { formatMoney } from "./display.js";
12
+ import { Field } from "./shared.js";
13
+ export function FlightConfigurator({ pending, travelers, onChange, }) {
14
+ const t = useAdminMessages().trips.adminComposer.panels;
15
+ const passengers = React.useMemo(() => flightPassengersFromTripTravelers(travelers), [travelers]);
16
+ const passengerCounts = React.useMemo(() => passengerCountsFromTripTravelers(travelers), [travelers]);
17
+ const isRoundTrip = pending.tripType === "round_trip";
18
+ const ready = Boolean(pending.origin &&
19
+ pending.destination &&
20
+ pending.departDate &&
21
+ (!isRoundTrip || pending.returnDate));
22
+ const slices = React.useMemo(() => {
23
+ if (!pending.origin || !pending.destination || !pending.departDate)
24
+ return [];
25
+ const next = [
26
+ {
27
+ origin: pending.origin,
28
+ destination: pending.destination,
29
+ departureDate: pending.departDate,
30
+ },
31
+ ];
32
+ if (pending.tripType === "round_trip" && pending.returnDate) {
33
+ next.push({
34
+ origin: pending.destination,
35
+ destination: pending.origin,
36
+ departureDate: pending.returnDate,
37
+ });
38
+ }
39
+ return next;
40
+ }, [
41
+ pending.departDate,
42
+ pending.destination,
43
+ pending.origin,
44
+ pending.returnDate,
45
+ pending.tripType,
46
+ ]);
47
+ const search = useFlightSearch({
48
+ slices,
49
+ passengers: passengerCounts,
50
+ cabin: pending.cabin,
51
+ }, { enabled: ready });
52
+ const offers = search.data?.offers ?? [];
53
+ const selectedOffer = pending.selectedOffer &&
54
+ offers.some((offer) => offer.offerId === pending.selectedOffer?.offerId)
55
+ ? pending.selectedOffer
56
+ : pending.selectedOffer;
57
+ const ancillaryQuery = useFlightAncillaries(selectedOffer ? { offerId: selectedOffer.offerId, offer: selectedOffer } : null, { enabled: Boolean(selectedOffer) });
58
+ const ancillaryCatalog = ancillaryQuery.data?.catalog ?? pending.ancillaryCatalog;
59
+ const priced = flightPricingFromPending({
60
+ ...pending,
61
+ selectedOffer,
62
+ ancillaryCatalog,
63
+ });
64
+ const patch = (next) => {
65
+ onChange({ ...pending, ...next });
66
+ };
67
+ const resetSelection = (next) => {
68
+ patch({
69
+ selectedOffer: null,
70
+ ancillaryCatalog: null,
71
+ fareBundlePicks: [],
72
+ baggagePicks: [],
73
+ assistancePicks: [],
74
+ extrasPicks: [],
75
+ ...next,
76
+ });
77
+ };
78
+ return (_jsxs("div", { className: "flex flex-col gap-5", children: [_jsxs("div", { className: "flex flex-wrap gap-2", children: [_jsx(Button, { type: "button", size: "sm", variant: !isRoundTrip ? "default" : "outline", onClick: () => resetSelection({ tripType: "one_way", returnDate: "" }), children: t.flightOneWay }), _jsx(Button, { type: "button", size: "sm", variant: isRoundTrip ? "default" : "outline", onClick: () => resetSelection({ tripType: "round_trip" }), children: t.flightRoundTrip })] }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsx(Field, { label: t.flightOrigin, children: _jsx(AirportCombobox, { value: pending.origin, placeholder: t.fromPlaceholder, onChange: (code) => resetSelection({ origin: code }), className: "w-full" }) }), _jsx(Field, { label: t.flightDestination, children: _jsx(AirportCombobox, { value: pending.destination, placeholder: t.toPlaceholder, onChange: (code) => resetSelection({ destination: code }), className: "w-full" }) }), _jsx(Field, { label: t.flightDepart, children: _jsx(DatePicker, { value: pending.departDate, onChange: (departDate) => resetSelection({ departDate: departDate ?? "" }), placeholder: t.pickDate }) }), isRoundTrip ? (_jsx(Field, { label: t.flightReturn, children: _jsxs("div", { className: "flex gap-2", children: [_jsx(DatePicker, { value: pending.returnDate, onChange: (returnDate) => resetSelection({ returnDate: returnDate ?? "" }), placeholder: t.pickDate, className: "flex-1" }), _jsx(Button, { type: "button", variant: "outline", size: "icon", "aria-label": t.clearReturnDate, onClick: () => resetSelection({ returnDate: "" }), children: _jsx(X, { className: "size-4" }) })] }) })) : null] }), _jsxs("div", { className: "flex flex-wrap items-center justify-between gap-3 rounded-md border bg-muted/20 p-3", children: [_jsxs("div", { className: "flex flex-col gap-0.5 text-sm", children: [_jsx("span", { className: "font-medium", children: t.travelersWord }), _jsxs("span", { className: "text-muted-foreground text-xs", children: [passengerCounts.adults === 1
79
+ ? t.travelerCountAdultSingular
80
+ : formatMessage(t.travelerCountAdultPlural, { count: passengerCounts.adults }), passengerCounts.children
81
+ ? formatMessage(passengerCounts.children === 1 ? t.travelerCountChild : t.travelerCountChildren, { count: passengerCounts.children })
82
+ : "", passengerCounts.infants
83
+ ? formatMessage(passengerCounts.infants === 1 ? t.travelerCountInfant : t.travelerCountInfants, { count: passengerCounts.infants })
84
+ : ""] })] }), _jsx(CabinSelector, { value: pending.cabin, onChange: (cabin) => resetSelection({ cabin }) })] }), ready ? (_jsxs("div", { className: "flex flex-col gap-3", children: [_jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsx("h4", { className: "font-medium text-sm", children: t.flightOptionsHeading }), search.isFetching ? (_jsxs("span", { className: "flex items-center gap-1 text-muted-foreground text-xs", children: [_jsx(Loader2, { className: "size-3 animate-spin" }), t.flightSearching] })) : offers.length > 0 ? (_jsx("span", { className: "text-muted-foreground text-xs", children: formatMessage(t.flightOptionsCount, { count: offers.length }) })) : null] }), search.isError ? (_jsx("p", { className: "rounded-md bg-destructive/10 px-3 py-2 text-destructive text-sm", children: t.flightSearchFailed })) : null, !search.isFetching && offers.length === 0 && !search.isError ? (_jsx("p", { className: "rounded-md border border-dashed p-3 text-muted-foreground text-sm", children: t.flightNoOptions })) : null, offers.slice(0, 5).map((offer) => (_jsx(FlightOfferRow, { offer: offer, selected: selectedOffer?.offerId === offer.offerId, selectLabel: selectedOffer?.offerId === offer.offerId ? t.flightSelected : t.flightSelect, onSelect: (nextOffer) => patch({
85
+ selectedOffer: nextOffer,
86
+ ancillaryCatalog: null,
87
+ fareBundlePicks: [],
88
+ baggagePicks: [],
89
+ assistancePicks: [],
90
+ extrasPicks: [],
91
+ }) }, offer.offerId)))] })) : (_jsx("p", { className: "rounded-md border border-dashed p-3 text-muted-foreground text-sm", children: t.flightSelectSearchHint })), selectedOffer ? (_jsxs("div", { className: "flex flex-col gap-5 border-t pt-4", children: [_jsx(FlightFareUpsellStep, { outboundOffer: legOffer(selectedOffer, 0), returnOffer: selectedOffer.itineraries[1] ? legOffer(selectedOffer, 1) : undefined, passengers: passengers, passengerCounts: passengerCounts, value: pending.fareBundlePicks, onChange: (fareBundlePicks) => patch({ fareBundlePicks }), sameForAllPassengers: pending.sameFareForAllPassengers, onSameForAllPassengersChange: (sameFareForAllPassengers) => patch({ sameFareForAllPassengers }) }), ancillaryQuery.isError ? (_jsx("p", { className: "rounded-md border border-dashed p-3 text-muted-foreground text-sm", children: t.flightAncillariesUnavailable })) : (_jsx(FlightBaggageStep, { outboundCatalog: ancillaryCatalog, returnCatalog: selectedOffer.itineraries[1] ? ancillaryCatalog : null, outboundOffer: legOffer(selectedOffer, 0), returnOffer: selectedOffer.itineraries[1] ? legOffer(selectedOffer, 1) : undefined, passengers: passengers, passengerCounts: passengerCounts, value: pending.baggagePicks, onChange: (baggagePicks) => patch({ baggagePicks, ancillaryCatalog: ancillaryCatalog ?? null }), sameForBothDirections: pending.sameBaggageBothDirections, onSameForBothDirectionsChange: (sameBaggageBothDirections) => patch({ sameBaggageBothDirections }), loading: ancillaryQuery.isFetching })), ancillaryCatalog ? (_jsx(FlightServicesStep, { outboundCatalog: ancillaryCatalog, returnCatalog: selectedOffer.itineraries[1] ? ancillaryCatalog : null, outboundOffer: legOffer(selectedOffer, 0), returnOffer: selectedOffer.itineraries[1] ? legOffer(selectedOffer, 1) : undefined, passengers: passengers, passengerCounts: passengerCounts, assistance: pending.assistancePicks, extras: pending.extrasPicks, onAssistanceChange: (assistancePicks) => patch({ assistancePicks }), onExtrasChange: (extrasPicks) => patch({ extrasPicks }), loading: ancillaryQuery.isFetching })) : null, _jsxs("div", { className: "flex items-center justify-between rounded-md border bg-muted/30 p-3", children: [_jsx("span", { className: "text-muted-foreground text-sm", children: t.flightTotal }), _jsx("span", { className: "font-semibold text-base", children: formatMoney(priced.totalAmountCents, priced.currency) })] })] })) : null] }));
92
+ }
93
+ export function CabinSelector({ value, onChange, }) {
94
+ const t = useAdminMessages().trips.adminComposer.panels;
95
+ const cabins = [
96
+ { value: "economy", label: t.cabinClasses.economy },
97
+ { value: "premium_economy", label: t.cabinClasses.premium_economy },
98
+ { value: "business", label: t.cabinClasses.business },
99
+ { value: "first", label: t.cabinClasses.first },
100
+ ];
101
+ return (_jsx("div", { className: "flex flex-wrap gap-1", children: cabins.map((cabin) => (_jsx(Button, { type: "button", size: "sm", variant: value === cabin.value ? "default" : "outline", onClick: () => onChange(cabin.value), children: cabin.label }, cabin.value))) }));
102
+ }
103
+ export function flightPricingFromPending(pending) {
104
+ const offer = pending.selectedOffer;
105
+ if (!offer) {
106
+ return {
107
+ currency: "EUR",
108
+ subtotalAmountCents: 0,
109
+ taxAmountCents: 0,
110
+ ancillaryAmountCents: 0,
111
+ totalAmountCents: 0,
112
+ };
113
+ }
114
+ const currencyCode = offer.totalPrice.currency;
115
+ const offerTotal = moneyToCents(offer.totalPrice.amount);
116
+ const fareTax = offer.fareBreakdowns.reduce((sum, line) => sum + moneyToCents(line.taxes.amount), 0);
117
+ const ancillaryAmount = flightAncillaryAmountCents(pending, currencyCode);
118
+ return {
119
+ currency: currencyCode,
120
+ subtotalAmountCents: Math.max(0, offerTotal - fareTax) + ancillaryAmount,
121
+ taxAmountCents: fareTax,
122
+ ancillaryAmountCents: ancillaryAmount,
123
+ totalAmountCents: offerTotal + ancillaryAmount,
124
+ };
125
+ }
126
+ export function flightAncillaryAmountCents(pending, currencyCode) {
127
+ const offer = pending.selectedOffer;
128
+ const catalog = pending.ancillaryCatalog;
129
+ if (!offer)
130
+ return 0;
131
+ const fareBundles = offer.fareBundles ?? [];
132
+ const fareBundleTotal = pending.fareBundlePicks.reduce((sum, pick) => {
133
+ const bundle = fareBundles.find((candidate) => candidate.id === pick.bundleId);
134
+ if (!bundle || bundle.priceDelta.currency !== currencyCode)
135
+ return sum;
136
+ return sum + moneyToCents(bundle.priceDelta.amount);
137
+ }, 0);
138
+ if (!catalog)
139
+ return fareBundleTotal;
140
+ const baggageTotal = pending.baggagePicks.reduce((sum, pick) => {
141
+ const option = catalog.baggage.find((candidate) => candidate.id === pick.optionId);
142
+ if (!option || option.price.currency !== currencyCode)
143
+ return sum;
144
+ return sum + moneyToCents(option.price.amount) * (pick.quantity ?? 1);
145
+ }, 0);
146
+ const assistanceTotal = pending.assistancePicks.reduce((sum, pick) => {
147
+ const option = catalog.assistance.find((candidate) => candidate.id === pick.optionId);
148
+ if (!option?.price || option.price.currency !== currencyCode)
149
+ return sum;
150
+ return sum + moneyToCents(option.price.amount);
151
+ }, 0);
152
+ const extrasTotal = pending.extrasPicks.reduce((sum, pick) => {
153
+ const option = catalog.extras.find((candidate) => candidate.id === pick.optionId);
154
+ if (!option || option.price.currency !== currencyCode)
155
+ return sum;
156
+ return sum + moneyToCents(option.price.amount) * (pick.quantity ?? 1);
157
+ }, 0);
158
+ return fareBundleTotal + baggageTotal + assistanceTotal + extrasTotal;
159
+ }
160
+ export function legOffer(offer, index) {
161
+ const itinerary = offer.itineraries[index];
162
+ return itinerary ? { ...offer, itineraries: [itinerary] } : offer;
163
+ }
164
+ export function moneyToCents(amount) {
165
+ const parsed = Number.parseFloat(amount);
166
+ return Number.isFinite(parsed) ? Math.round(parsed * 100) : 0;
167
+ }
168
+ export function passengerCountsFromTripTravelers(travelers) {
169
+ const counts = travelers.reduce((acc, traveler) => {
170
+ if (traveler.role === "child")
171
+ acc.children += 1;
172
+ else if (traveler.role === "infant")
173
+ acc.infants += 1;
174
+ else
175
+ acc.adults += 1;
176
+ return acc;
177
+ }, { adults: 0, children: 0, infants: 0 });
178
+ return {
179
+ adults: Math.max(1, counts.adults),
180
+ children: counts.children,
181
+ infants: counts.infants,
182
+ };
183
+ }
184
+ export function flightPassengersFromTripTravelers(travelers) {
185
+ return travelers.map((traveler, index) => {
186
+ const type = traveler.role === "child" ? "child" : traveler.role === "infant" ? "infant" : "adult";
187
+ return {
188
+ passengerId: traveler.localId || `traveler_${index + 1}`,
189
+ type,
190
+ // fallback names sent verbatim to the flight provider's API as ASCII
191
+ // passenger placeholders when the operator hasn't yet filled in the
192
+ // real traveler.
193
+ firstName:
194
+ // i18n-literal-ok
195
+ traveler.firstName || (type === "adult" ? "Adult" : type === "child" ? "Child" : "Infant"),
196
+ lastName: traveler.lastName || `${index + 1}`,
197
+ dateOfBirth: traveler.dateOfBirth || fallbackDobForPassengerType(type),
198
+ ...(traveler.email ? { email: traveler.email } : {}),
199
+ };
200
+ });
201
+ }
202
+ export function fallbackDobForPassengerType(type) {
203
+ if (type === "child")
204
+ return "2016-01-01";
205
+ if (type === "infant")
206
+ return "2025-01-01";
207
+ return "1990-01-01";
208
+ }
@@ -0,0 +1,16 @@
1
+ import { type PendingComponent } from "./shared.js";
2
+ export declare function CruiseConfigurator({ pending, onChange, }: {
3
+ pending: Extract<PendingComponent, {
4
+ kind: "cruise";
5
+ }>;
6
+ onChange(next: PendingComponent): void;
7
+ }): import("react/jsx-runtime").JSX.Element;
8
+ export declare function PlaceholderConfigurator({ pending, onChange, }: {
9
+ pending: Extract<PendingComponent, {
10
+ kind: "manual";
11
+ }>;
12
+ onChange(next: PendingComponent): void;
13
+ }): import("react/jsx-runtime").JSX.Element;
14
+ export declare function pendingComponentIsValid(pending: PendingComponent): boolean;
15
+ export declare function ComponentsEmpty(): import("react/jsx-runtime").JSX.Element;
16
+ //# sourceMappingURL=manual-configurators.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manual-configurators.d.ts","sourceRoot":"","sources":["../../../src/admin/trips-panels/manual-configurators.tsx"],"names":[],"mappings":"AAkBA,OAAO,EAAmC,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAEpF,wBAAgB,kBAAkB,CAAC,EACjC,OAAO,EACP,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,OAAO,CAAC,gBAAgB,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAA;IACtD,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAA;CACvC,2CAqCA;AAED,wBAAgB,uBAAuB,CAAC,EACtC,OAAO,EACP,QAAQ,GACT,EAAE;IACD,OAAO,EAAE,OAAO,CAAC,gBAAgB,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAA;IACtD,QAAQ,CAAC,IAAI,EAAE,gBAAgB,GAAG,IAAI,CAAA;CACvC,2CA4EA;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAmB1E;AAED,wBAAgB,eAAe,4CAa9B"}
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useOperatorAdminMessages as useAdminMessages } from "@voyant-travel/admin";
4
+ import { CurrencyCombobox } from "@voyant-travel/ui/components/currency-combobox";
5
+ import { CurrencyInput } from "@voyant-travel/ui/components/currency-input";
6
+ import { DateTimeField } from "@voyant-travel/ui/components/date-time-field";
7
+ import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle, } from "@voyant-travel/ui/components/empty";
8
+ import { Input } from "@voyant-travel/ui/components/input";
9
+ import { Textarea } from "@voyant-travel/ui/components/textarea";
10
+ import { Route as RouteIcon } from "lucide-react";
11
+ import { formatMoney } from "./display.js";
12
+ import { computePlaceholderTotals, Field } from "./shared.js";
13
+ export function CruiseConfigurator({ pending, onChange, }) {
14
+ const t = useAdminMessages().trips.adminComposer.panels;
15
+ return (_jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsx(Field, { label: t.cruisePlaceholder.embarkationDate, children: _jsx(Input, { type: "date", value: pending.embarkationDate, onChange: (event) => onChange({ ...pending, embarkationDate: event.target.value }) }) }), _jsx(Field, { label: t.cruisePlaceholder.cabin, children: _jsx(Input, { value: pending.cabin, placeholder: t.cabinPlaceholder, onChange: (event) => onChange({ ...pending, cabin: event.target.value }) }) })] }), _jsx(Field, { label: t.cruisePlaceholder.description, children: _jsx(Textarea, { rows: 2, value: pending.description, onChange: (event) => onChange({ ...pending, description: event.target.value }) }) }), _jsx(Field, { label: t.cruisePlaceholder.estimatedAmount, children: _jsx(Input, { inputMode: "decimal", value: pending.estimatedAmount, placeholder: "0.00", onChange: (event) => onChange({ ...pending, estimatedAmount: event.target.value }) }) })] }));
16
+ }
17
+ export function PlaceholderConfigurator({ pending, onChange, }) {
18
+ const t = useAdminMessages().trips.adminComposer.panels;
19
+ const totals = computePlaceholderTotals(pending.subtotalCents, pending.taxRatePct);
20
+ return (_jsxs("div", { className: "flex flex-col gap-4", children: [_jsx(Field, { label: t.manualPlaceholder.nameLabel, children: _jsx(Input, { value: pending.name, placeholder: t.manualPlaceholder.namePlaceholder, onChange: (event) => onChange({ ...pending, name: event.target.value }) }) }), _jsx(Field, { label: t.manualPlaceholder.descriptionLabel, children: _jsx(Textarea, { rows: 2, value: pending.description, placeholder: t.notesPlaceholder, onChange: (event) => onChange({ ...pending, description: event.target.value }) }) }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [_jsx(Field, { label: t.fromLabel, children: _jsx(DateTimeField, { value: pending.startsAt, onChange: (value) => onChange({ ...pending, startsAt: value ?? "" }) }) }), _jsx(Field, { label: t.toLabel, children: _jsx(DateTimeField, { value: pending.endsAt, onChange: (value) => onChange({ ...pending, endsAt: value ?? "" }) }) })] }), _jsxs("div", { className: "grid gap-4 sm:grid-cols-[140px_minmax(0,1fr)_140px]", children: [_jsx(Field, { label: t.manualPlaceholder.currencyLabel, children: _jsx(CurrencyCombobox, { value: pending.currency, onChange: (value) => onChange({ ...pending, currency: value ?? "EUR" }) }) }), _jsx(Field, { label: t.manualPlaceholder.subtotalLabel, children: _jsx(CurrencyInput, { value: pending.subtotalCents, onChange: (value) => onChange({ ...pending, subtotalCents: value }), currency: pending.currency, placeholder: "0.00" }) }), _jsx(Field, { label: t.manualPlaceholder.taxRateLabel, children: _jsxs("div", { className: "relative", children: [_jsx(Input, { inputMode: "decimal", value: pending.taxRatePct, placeholder: "0", onChange: (event) => onChange({ ...pending, taxRatePct: event.target.value }), className: "pr-8" }), _jsx("span", { className: "-translate-y-1/2 pointer-events-none absolute top-1/2 right-3 text-muted-foreground text-xs", children: "%" })] }) })] }), _jsxs("div", { className: "flex flex-col gap-1 rounded-md border bg-muted/30 p-3 text-sm", children: [_jsxs("div", { className: "flex items-center justify-between text-muted-foreground", children: [_jsx("span", { children: t.tax }), _jsx("span", { children: formatMoney(totals.tax, pending.currency) })] }), _jsxs("div", { className: "flex items-center justify-between font-semibold", children: [_jsx("span", { children: t.total }), _jsx("span", { children: formatMoney(totals.total, pending.currency) })] })] })] }));
21
+ }
22
+ export function pendingComponentIsValid(pending) {
23
+ switch (pending.kind) {
24
+ case "product":
25
+ return Boolean(pending.catalogEntityId &&
26
+ pending.catalogSourceKind &&
27
+ pending.bookingDraft?.configure.departureSlotId);
28
+ case "stay":
29
+ return Boolean(pending.catalogEntityId && pending.catalogSourceKind && pending.bookingDraft);
30
+ case "flight":
31
+ return Boolean(pending.origin && pending.destination && pending.departDate && pending.selectedOffer);
32
+ case "cruise":
33
+ return Boolean(pending.embarkationDate);
34
+ case "manual":
35
+ return Boolean(pending.name && pending.subtotalCents && pending.subtotalCents > 0);
36
+ }
37
+ }
38
+ export function ComponentsEmpty() {
39
+ const t = useAdminMessages().trips.adminComposer.panels;
40
+ return (_jsx(Empty, { className: "border bg-card", children: _jsxs(EmptyHeader, { children: [_jsx(EmptyMedia, { variant: "icon", children: _jsx(RouteIcon, {}) }), _jsx(EmptyTitle, { children: t.emptyTimeline }), _jsx(EmptyDescription, { children: t.emptyTimelineHint })] }) }));
41
+ }