@voyantjs/bookings-ui 0.80.17 → 0.81.1

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.
@@ -1,22 +1,4 @@
1
1
  import { type BookingRecord } from "@voyantjs/bookings-react";
2
- import { type OptionUnitsStepperUnit } from "./option-units-stepper-section.js";
3
- /**
4
- * Pick the unit for a traveler. Priorities:
5
- * 1. If we have an age (from DOB) and it falls into a unit's
6
- * `[minAge, maxAge]` window, use that unit.
7
- * 2. Otherwise honor an explicit role hint (Child / Infant / Adult
8
- * buttons) by mapping the hint to a representative age and
9
- * matching the age band. This works for products whose units
10
- * encode the band in the code (`child_0_5`, `child_6_12`) instead
11
- * of bare `CHILD`/`INFANT`.
12
- * 3. Fall back to code/name matching for legacy products that don't
13
- * configure min/max ages.
14
- *
15
- * `roleHint` covers the common case where the operator knows the
16
- * traveler is a child but doesn't have the exact DOB. Without it, a
17
- * roleless traveler would silently default to Adult pricing.
18
- */
19
- export declare function pickUnitForAge(units: OptionUnitsStepperUnit[], age: number | null, roleHint?: "adult" | "child" | "infant" | null): OptionUnitsStepperUnit | undefined;
20
2
  export interface BookingCreateDialogProps {
21
3
  open: boolean;
22
4
  onOpenChange: (open: boolean) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"booking-create-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-create-dialog.tsx"],"names":[],"mappings":"AASA,OAAO,EAML,KAAK,aAAa,EAGnB,MAAM,0BAA0B,CAAA;AAiCjC,OAAO,EAGL,KAAK,sBAAsB,EAE5B,MAAM,mCAAmC,CAAA;AA6J1C;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,sBAAsB,EAAE,EAC/B,GAAG,EAAE,MAAM,GAAG,IAAI,EAClB,QAAQ,GAAE,OAAO,GAAG,OAAO,GAAG,QAAQ,GAAG,IAAW,GACnD,sBAAsB,GAAG,SAAS,CAoCpC;AA4JD,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,aAAa,GACd,EAAE,wBAAwB,2CAsB1B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,OAAc,EACd,QAAQ,GACT,EAAE,sBAAsB,2CAo5BxB"}
1
+ {"version":3,"file":"booking-create-dialog.d.ts","sourceRoot":"","sources":["../../src/components/booking-create-dialog.tsx"],"names":[],"mappings":"AAeA,OAAO,EAKL,KAAK,aAAa,EAInB,MAAM,0BAA0B,CAAA;AA4PjC,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,OAAO,CAAA;IACb,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAA;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAC5C,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;CACtB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,IAAI,EACJ,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,aAAa,GACd,EAAE,wBAAwB,2CAsB1B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,OAAc,EACd,QAAQ,GACT,EAAE,sBAAsB,2CAm8BxB"}
@@ -2,7 +2,8 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { useQueries, useQuery } from "@tanstack/react-query";
4
4
  import { getSlotQueryOptions, useSlots, useSlotUnitAvailability, useVoyantAvailabilityContext, } from "@voyantjs/availability-react";
5
- import { useBookingCreateMutation, useBookingTaxPreview, } from "@voyantjs/bookings-react";
5
+ import { resolveBookingDraft, resolveBookingExtraLines, travelersToRows, } from "@voyantjs/bookings/pricing-assignment";
6
+ import { useBookingCreateMutation, useBookingTaxPreview, VoyantApiError, } from "@voyantjs/bookings-react";
6
7
  import { useOrganization, usePerson } from "@voyantjs/crm-react";
7
8
  import { useProductExtras } from "@voyantjs/extras-react";
8
9
  import { useAddresses } from "@voyantjs/identity-react";
@@ -20,7 +21,7 @@ import { emptyPersonPickerValue, PersonPickerSection, } from "./person-picker-se
20
21
  import { PriceBreakdownSection } from "./price-breakdown-section.js";
21
22
  import { ProductPickerSection } from "./product-picker-section.js";
22
23
  import { emptySharedRoomValue, SharedRoomSection, } from "./shared-room-section.js";
23
- import { computeAgeYears, emptyTravelerListValue, TravelersSection, } from "./travelers-section.js";
24
+ import { emptyTravelerListValue, TravelersSection, } from "./travelers-section.js";
24
25
  import { emptyVoucherPickerValue, VoucherPickerSection, } from "./voucher-picker-section.js";
25
26
  function generateBookingNumber() {
26
27
  const now = new Date();
@@ -117,186 +118,6 @@ function stripOptionPrefix(name) {
117
118
  const idx = name.indexOf(" - ");
118
119
  return idx > 0 ? name.slice(idx + 3) : name;
119
120
  }
120
- /**
121
- * Pick the unit for a traveler. Priorities:
122
- * 1. If we have an age (from DOB) and it falls into a unit's
123
- * `[minAge, maxAge]` window, use that unit.
124
- * 2. Otherwise honor an explicit role hint (Child / Infant / Adult
125
- * buttons) by mapping the hint to a representative age and
126
- * matching the age band. This works for products whose units
127
- * encode the band in the code (`child_0_5`, `child_6_12`) instead
128
- * of bare `CHILD`/`INFANT`.
129
- * 3. Fall back to code/name matching for legacy products that don't
130
- * configure min/max ages.
131
- *
132
- * `roleHint` covers the common case where the operator knows the
133
- * traveler is a child but doesn't have the exact DOB. Without it, a
134
- * roleless traveler would silently default to Adult pricing.
135
- */
136
- export function pickUnitForAge(units, age, roleHint = null) {
137
- if (units.length === 0)
138
- return undefined;
139
- const personUnits = units.filter((u) => u.unitType == null || u.unitType === "person");
140
- const pool = personUnits.length > 0 ? personUnits : units;
141
- const sorted = [...pool].sort((a, b) => (a.minAge ?? 0) - (b.minAge ?? 0));
142
- const matchByAge = (target) => sorted.find((u) => (u.minAge == null || target >= u.minAge) && (u.maxAge == null || target <= u.maxAge));
143
- if (age != null) {
144
- const match = matchByAge(age);
145
- if (match)
146
- return match;
147
- }
148
- if (roleHint) {
149
- const HINT_AGE = { adult: 30, child: 8, infant: 1 };
150
- const hintAge = HINT_AGE[roleHint];
151
- // Only consider units with at least one explicit age bound. Without
152
- // this, legacy units with null min/max (just bare ADULT/CHILD codes)
153
- // would all match every hint age and collapse onto the first sorted
154
- // entry (almost always Adult). Code-matching below handles those.
155
- const banded = sorted.filter((u) => u.minAge != null || u.maxAge != null);
156
- const match = banded.find((u) => (u.minAge == null || hintAge >= u.minAge) && (u.maxAge == null || hintAge <= u.maxAge));
157
- if (match)
158
- return match;
159
- }
160
- const findByCode = (code) => sorted.find((u) => (u.unitCode ?? "").toUpperCase() === code) ??
161
- sorted.find((u) => new RegExp(`\\b${code}\\b`, "i").test(u.unitName));
162
- if (roleHint === "child")
163
- return findByCode("CHILD") ?? sorted[0];
164
- if (roleHint === "infant")
165
- return findByCode("INFANT") ?? sorted[0];
166
- return findByCode("ADULT") ?? sorted[sorted.length - 1] ?? sorted[0];
167
- }
168
- /**
169
- * Take the operator-picked per-option quantities (which are tracked
170
- * against each option's primary "Adult" unit by the stepper) plus the
171
- * travelers list, and redistribute both so that:
172
- * - each traveler's `roomUnitId` points at the age-banded unit
173
- * matching their DOB (Adult / Child / Infant / etc.)
174
- * - `quantities` reflects the per-unit counts after redistribution —
175
- * a 3-pax "Standard double" with 2 adults + 1 child becomes
176
- * `{ adultUnit: 2, childUnit: 1 }` instead of `{ adultUnit: 3 }`.
177
- *
178
- * Slots without a configured `dateOfBirth` keep the option's adult
179
- * default so partially-filled bookings still typecheck.
180
- */
181
- /**
182
- * Rebuild stepper quantities from per-traveler unit assignments.
183
- *
184
- * Each traveler's `roomUnitId` is now the operator's explicit choice
185
- * (DOB-pre-picked at attach, overridable via the dynamic category
186
- * buttons), so we count assignments directly and add any per-option
187
- * residual on the adult/primary unit when the stepper qty exceeds the
188
- * number of travelers actually assigned. Unlike the older
189
- * DOB-driven rewrite, this never moves a traveler off their chosen
190
- * unit — operator selection always wins.
191
- */
192
- function redistributeByAge(quantities, travelers, units) {
193
- if (units.length === 0)
194
- return { quantities, travelers };
195
- const unitsByOption = new Map();
196
- for (const unit of units) {
197
- if (!unit.optionId)
198
- continue;
199
- const list = unitsByOption.get(unit.optionId);
200
- if (list)
201
- list.push(unit);
202
- else
203
- unitsByOption.set(unit.optionId, [unit]);
204
- }
205
- const unitToOption = new Map(units.map((u) => [u.optionUnitId, u.optionId]));
206
- const unitById = new Map(units.map((u) => [u.optionUnitId, u]));
207
- // Per-option total from the stepper. This is the count the operator
208
- // committed to when picking rooms.
209
- const totalByOption = new Map();
210
- for (const [unitId, qty] of Object.entries(quantities)) {
211
- if (qty <= 0)
212
- continue;
213
- const optionId = unitToOption.get(unitId);
214
- if (!optionId)
215
- continue;
216
- totalByOption.set(optionId, (totalByOption.get(optionId) ?? 0) + qty);
217
- }
218
- const assignedForDefaulting = new Map();
219
- for (const traveler of travelers) {
220
- if (!traveler.roomUnitId)
221
- continue;
222
- const optionId = unitToOption.get(traveler.roomUnitId);
223
- if (!optionId)
224
- continue;
225
- assignedForDefaulting.set(optionId, (assignedForDefaulting.get(optionId) ?? 0) + 1);
226
- }
227
- const optionDemand = Array.from(totalByOption.entries());
228
- const nextTravelers = travelers.map((traveler) => {
229
- if (traveler.roomUnitId && unitById.has(traveler.roomUnitId))
230
- return traveler;
231
- const optionId = optionDemand.find(([candidate, total]) => (assignedForDefaulting.get(candidate) ?? 0) < total)?.[0] ?? optionDemand[0]?.[0];
232
- if (!optionId)
233
- return traveler;
234
- const age = computeAgeYears(traveler.dateOfBirth);
235
- const roleHint = traveler.role === "adult" || traveler.role === "child" || traveler.role === "infant"
236
- ? traveler.role
237
- : null;
238
- const unit = pickUnitForAge(unitsByOption.get(optionId) ?? [], age, roleHint);
239
- if (!unit)
240
- return traveler;
241
- assignedForDefaulting.set(optionId, (assignedForDefaulting.get(optionId) ?? 0) + 1);
242
- return { ...traveler, roomUnitId: unit.optionUnitId };
243
- });
244
- // Count actual traveler assignments per unit + per option.
245
- const next = {};
246
- const assignedByOption = new Map();
247
- for (const t of nextTravelers) {
248
- if (!t.roomUnitId)
249
- continue;
250
- const optionId = unitToOption.get(t.roomUnitId);
251
- if (!optionId)
252
- continue;
253
- next[t.roomUnitId] = (next[t.roomUnitId] ?? 0) + 1;
254
- assignedByOption.set(optionId, (assignedByOption.get(optionId) ?? 0) + 1);
255
- }
256
- // Residual = operator picked N rooms but only added M travelers; put
257
- // the leftover on the option's adult/primary unit so the price total
258
- // matches the stepper.
259
- for (const [optionId, total] of totalByOption) {
260
- const assigned = assignedByOption.get(optionId) ?? 0;
261
- const residual = Math.max(0, total - assigned);
262
- if (residual === 0)
263
- continue;
264
- const adult = pickUnitForAge(unitsByOption.get(optionId) ?? [], null);
265
- if (!adult)
266
- continue;
267
- next[adult.optionUnitId] = (next[adult.optionUnitId] ?? 0) + residual;
268
- }
269
- return { quantities: next, travelers: nextTravelers };
270
- }
271
- function travelersToRows(value) {
272
- return value.travelers.map((traveler) => {
273
- // Age-derived category (DOB-driven). The `role` field still
274
- // carries the `lead` flag separately for the booking primary; the
275
- // demographic category comes from age, not from a manual select.
276
- const age = computeAgeYears(traveler.dateOfBirth);
277
- const ageCategory = age == null
278
- ? traveler.role === "child" || traveler.role === "infant" || traveler.role === "adult"
279
- ? traveler.role
280
- : null
281
- : age < 2
282
- ? "infant"
283
- : age < 18
284
- ? "child"
285
- : "adult";
286
- return {
287
- personId: traveler.personId,
288
- firstName: traveler.firstName.trim(),
289
- lastName: traveler.lastName.trim(),
290
- email: traveler.email.trim() || null,
291
- phone: traveler.phone.trim() || null,
292
- preferredLanguage: traveler.preferredLanguage.trim() || null,
293
- participantType: "traveler",
294
- travelerCategory: ageCategory,
295
- isPrimary: traveler.role === "lead",
296
- roomUnitId: traveler.roomUnitId,
297
- };
298
- });
299
- }
300
121
  function sameRoomUnits(left, right) {
301
122
  if (left.length !== right.length)
302
123
  return false;
@@ -310,6 +131,33 @@ function sameRoomUnits(left, right) {
310
131
  unit.remaining === other.remaining);
311
132
  });
312
133
  }
134
+ function isPayloadResolverMismatchBody(body) {
135
+ if (typeof body !== "object" || body === null)
136
+ return false;
137
+ const candidate = body;
138
+ return (candidate.code === "payload_resolver_mismatch" &&
139
+ Array.isArray(candidate.mismatches) &&
140
+ candidate.mismatches.every((mismatch) => {
141
+ if (typeof mismatch !== "object" || mismatch === null)
142
+ return false;
143
+ const item = mismatch;
144
+ return ((item.kind === "qty" || item.kind === "missing" || item.kind === "extra") &&
145
+ typeof item.optionUnitId === "string" &&
146
+ typeof item.submittedQuantity === "number" &&
147
+ typeof item.resolvedQuantity === "number");
148
+ }));
149
+ }
150
+ function formatPayloadResolverMismatchError(body, unitLabels) {
151
+ const details = body.mismatches
152
+ .map((mismatch) => {
153
+ const label = unitLabels[mismatch.optionUnitId] ?? mismatch.optionUnitId;
154
+ return `${label}: sent ${mismatch.submittedQuantity}, expected ${mismatch.resolvedQuantity}`;
155
+ })
156
+ .join("; ");
157
+ return details
158
+ ? `Booking options are out of sync. Review these lines: ${details}.`
159
+ : "Booking options are out of sync. Review the selected traveler and option lines.";
160
+ }
313
161
  /**
314
162
  * Operator booking-create dialog. Composes the booking-create picker
315
163
  * sections — product, departure, rooms, person, shared-room, travelers,
@@ -361,6 +209,7 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
361
209
  // bundle. Defaults on so the operator opts out, not in.
362
210
  const [notifyTraveler, setNotifyTraveler] = React.useState(true);
363
211
  const [error, setError] = React.useState(null);
212
+ const [payloadMismatchUnitIds, setPayloadMismatchUnitIds] = React.useState([]);
364
213
  const { formatDate } = useBookingsUiI18nOrDefault();
365
214
  const messages = useBookingsUiMessagesOrDefault();
366
215
  const availabilityClient = useVoyantAvailabilityContext();
@@ -392,6 +241,7 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
392
241
  setCreateAsDraft(false);
393
242
  setNotifyTraveler(true);
394
243
  setError(null);
244
+ setPayloadMismatchUnitIds([]);
395
245
  }
396
246
  else if (resolvedDefaultProductId) {
397
247
  setProduct((prev) => {
@@ -415,6 +265,7 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
415
265
  setRoomUnits([]);
416
266
  setSharedRoom(emptySharedRoomValue);
417
267
  setExtraLines([]);
268
+ setPayloadMismatchUnitIds([]);
418
269
  }, [product.productId, defaultSlotId]);
419
270
  const [slotsFromIso, setSlotsFromIso] = React.useState(() => new Date().toISOString());
420
271
  React.useEffect(() => {
@@ -443,6 +294,7 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
443
294
  }, [slotsData?.data, slotsFromIso, product.optionId, allOpenSlots]);
444
295
  const selectedSlot = React.useMemo(() => slots.find((slot) => slot.id === slotId) ?? (defaultSlot?.id === slotId ? defaultSlot : null), [slots, slotId, defaultSlot]);
445
296
  const setSelectedSlot = React.useCallback((nextSlotId) => {
297
+ setPayloadMismatchUnitIds([]);
446
298
  const selectedSlot = nextSlotId ? allOpenSlots.find((slot) => slot.id === nextSlotId) : null;
447
299
  if (selectedSlot?.optionId && selectedSlot.optionId !== product.optionId) {
448
300
  setProduct((prev) => ({ ...prev, optionId: selectedSlot.optionId }));
@@ -452,6 +304,7 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
452
304
  React.useEffect(() => {
453
305
  setRooms(emptyOptionUnitsStepperValue);
454
306
  setRoomUnits([]);
307
+ setPayloadMismatchUnitIds([]);
455
308
  if (!slotId || !product.optionId)
456
309
  return;
457
310
  const selectedDeparture = allOpenSlots.find((slot) => slot.id === slotId) ??
@@ -487,9 +340,9 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
487
340
  // (Adult / Child / Senior). The age-band lives separately on the
488
341
  // traveler and only affects pricing; both an adult and a child sit
489
342
  // in the same Standard double room. Each entry's `unitId` is set to
490
- // the option's primary unit so existing `roomUnitId`-keyed plumbing
491
- // (assignment, redistribution) keeps working — `redistributeByAge`
492
- // moves the traveler to the matching age-banded unit at submit.
343
+ // the option's primary unit so the existing `roomUnitId`-keyed
344
+ // plumbing keeps working — `resolveBookingDraft` moves the traveler
345
+ // to the matching age-banded unit at preview + submit.
493
346
  const roomUnitOptions = React.useMemo(() => {
494
347
  const units = getTravelerAssignableStepperUnits(roomUnits.length > 0 ? roomUnits : (slotUnitAvailability.data?.data ?? []));
495
348
  if (units.length === 0)
@@ -577,12 +430,20 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
577
430
  }
578
431
  return Array.from(groups.values());
579
432
  }, [roomUnits]);
580
- // Apply the same age-banded redistribution we use at submit so the
581
- // live price preview matches what the operator will actually be
582
- // billed. Without this, the breakdown sees only the option's primary
583
- // (Adult) unit qty from the stepper, missing the per-traveler split
584
- // between adult / child / infant tiers.
585
- const displayQuantities = React.useMemo(() => redistributeByAge(rooms.quantities, travelers.travelers, getTravelerAssignableStepperUnits(roomUnits)).quantities, [rooms.quantities, travelers.travelers, roomUnits]);
433
+ // Apply the same draft resolver we use at submit so live pricing
434
+ // and persisted item lines cannot drift. Person-priced options
435
+ // (excursions) derive line quantities from the traveler list;
436
+ // accommodation options preserve operator-picked stepper quantities.
437
+ const displayDraft = React.useMemo(() => resolveBookingDraft({
438
+ quantities: rooms.quantities,
439
+ travelers: travelers.travelers,
440
+ units: getTravelerAssignableStepperUnits(roomUnits),
441
+ }), [rooms.quantities, travelers.travelers, roomUnits]);
442
+ const displayQuantities = displayDraft.quantities;
443
+ const displayExtraLines = React.useMemo(() => resolveBookingExtraLines({
444
+ extraLines,
445
+ travelerCount: travelers.travelers.length,
446
+ }), [extraLines, travelers.travelers.length]);
586
447
  // Currency placeholder — used for voucher + payment schedule display.
587
448
  // Consumers hooking in real product data should override this by wrapping
588
449
  // the component or swapping in their own currency-aware hook.
@@ -622,6 +483,7 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
622
483
  const hasSelectedUnits = React.useMemo(() => Object.values(rooms.quantities).some((qty) => qty > 0), [rooms.quantities]);
623
484
  const handleSubmit = async () => {
624
485
  setError(null);
486
+ setPayloadMismatchUnitIds([]);
625
487
  if (!product.productId) {
626
488
  setError(messages.bookingCreateDialog.validation.selectProduct);
627
489
  return;
@@ -683,17 +545,28 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
683
545
  return;
684
546
  }
685
547
  const paymentSchedules = paymentScheduleToRows(paymentSchedule, pricingCurrency, confirmedSellAmountCents);
686
- // Age-banded redistribution: turn the operator's per-option
687
- // quantities + raw traveler list into per-unit quantities + each
688
- // traveler's matching unit assignment, driven by DOB.
548
+ // Resolve the draft once, then derive every shape the wire
549
+ // format needs from the result. Person-priced options get
550
+ // per-band quantities (1 adult + 1 child + 1 infant, not
551
+ // "3 x Adult"); accommodation options keep operator-picked
552
+ // room quantities. Server gets `clientLineKey` + `travelerIndexes`
553
+ // on each line so it can write `booking_item_travelers` rows.
689
554
  const submitUnits = roomUnits.length > 0
690
555
  ? getTravelerAssignableStepperUnits(roomUnits)
691
556
  : getTravelerAssignableStepperUnits((slotUnitAvailability.data?.data ?? []).map((unit) => ({
692
557
  ...unit,
693
558
  optionId: product.optionId,
694
559
  })));
695
- const redistributed = redistributeByAge(rooms.quantities, travelers.travelers, submitUnits);
696
- const itemLines = itemLinesToRows(redistributed.quantities, submitUnits, pricing);
560
+ const redistributed = resolveBookingDraft({
561
+ quantities: rooms.quantities,
562
+ travelers: travelers.travelers,
563
+ units: submitUnits,
564
+ });
565
+ const itemLines = itemLinesToRows(redistributed.quantities, submitUnits, pricing, redistributed.travelerIndexesByUnitId);
566
+ const resolvedExtraLines = resolveBookingExtraLines({
567
+ extraLines,
568
+ travelerCount: travelers.travelers.length,
569
+ });
697
570
  const travelerRows = travelersToRows({ travelers: redistributed.travelers });
698
571
  const voucherRedemption = voucher.picked && voucher.picked.remainingAmountCents != null
699
572
  ? {
@@ -773,7 +646,7 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
773
646
  confirmedSellAmountCents,
774
647
  priceOverrideReason: priceOverrideReason || null,
775
648
  itemLines: itemLines.length > 0 ? itemLines : undefined,
776
- extraLines: extraLines.length > 0 ? extraLines : undefined,
649
+ extraLines: resolvedExtraLines.length > 0 ? resolvedExtraLines : undefined,
777
650
  travelers: travelerRows.length > 0 ? travelerRows : undefined,
778
651
  paymentSchedules: paymentSchedules.length > 0 ? paymentSchedules : undefined,
779
652
  voucherRedemption,
@@ -792,15 +665,26 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
792
665
  onCreated?.(booking);
793
666
  }
794
667
  catch (err) {
668
+ if (err instanceof VoyantApiError && isPayloadResolverMismatchBody(err.body)) {
669
+ setPayloadMismatchUnitIds(Array.from(new Set(err.body.mismatches.map((mismatch) => mismatch.optionUnitId))));
670
+ setError(formatPayloadResolverMismatchError(err.body, roomUnitLabels));
671
+ return;
672
+ }
795
673
  setError(err instanceof Error ? err.message : messages.bookingCreateDialog.validation.createFailed);
796
674
  }
797
675
  };
798
676
  const isSubmitting = createBookingMutation.isPending;
799
- return (_jsxs("div", { className: "grid min-h-0 flex-1 gap-6 lg:grid-cols-12", children: [_jsxs("div", { className: "flex min-h-0 min-w-0 flex-col lg:col-span-8", children: [_jsxs("div", { className: "flex flex-1 flex-col gap-4 overflow-y-auto px-1 pb-2", children: [_jsx(ProductPickerSection, { value: product, onChange: setProduct, enabled: enabled, lockProduct: Boolean(defaultProductId || defaultSlotId), labels: {
677
+ return (_jsxs("div", { className: "grid min-h-0 flex-1 gap-6 lg:grid-cols-12", children: [_jsxs("div", { className: "flex min-h-0 min-w-0 flex-col lg:col-span-8", children: [_jsxs("div", { className: "flex flex-1 flex-col gap-4 overflow-y-auto px-1 pb-2", children: [_jsx(ProductPickerSection, { value: product, onChange: (next) => {
678
+ setPayloadMismatchUnitIds([]);
679
+ setProduct(next);
680
+ }, enabled: enabled, lockProduct: Boolean(defaultProductId || defaultSlotId), labels: {
800
681
  optionNone: messages.bookingCreateDialog.labels.noSpecificOption,
801
- }, showOptionPicker: false }), product.productId ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { children: messages.bookingCreateDialog.fields.departure }), _jsx(AsyncCombobox, { value: slotId, onChange: (v) => setSelectedSlot(v), items: slots, selectedItem: selectedSlot, getKey: (slot) => slot.id, getLabel: (slot) => formatSlotLabel(slot), placeholder: messages.bookingCreateDialog.placeholders.departure, emptyText: messages.bookingCreateDialog.placeholders.departureEmpty, triggerClassName: "w-full", disabled: Boolean(defaultSlotId), clearable: !defaultSlotId })] })) : null, product.productId && slotId ? (_jsx(OptionUnitsStepperSection, { value: rooms, onChange: setRooms, productId: product.productId, slotId: slotId, optionId: product.optionId, enabled: enabled, onUnitsChange: handleRoomUnitsChange, slotHasFiniteCapacity: Boolean(selectedSlot) &&
682
+ }, showOptionPicker: false }), product.productId ? (_jsxs("div", { className: "flex flex-col gap-1", children: [_jsx(Label, { children: messages.bookingCreateDialog.fields.departure }), _jsx(AsyncCombobox, { value: slotId, onChange: (v) => setSelectedSlot(v), items: slots, selectedItem: selectedSlot, getKey: (slot) => slot.id, getLabel: (slot) => formatSlotLabel(slot), placeholder: messages.bookingCreateDialog.placeholders.departure, emptyText: messages.bookingCreateDialog.placeholders.departureEmpty, triggerClassName: "w-full", disabled: Boolean(defaultSlotId), clearable: !defaultSlotId })] })) : null, product.productId && slotId ? (_jsx(OptionUnitsStepperSection, { value: rooms, onChange: (next) => {
683
+ setPayloadMismatchUnitIds([]);
684
+ setRooms(next);
685
+ }, productId: product.productId, slotId: slotId, optionId: product.optionId, enabled: enabled, onUnitsChange: handleRoomUnitsChange, slotHasFiniteCapacity: Boolean(selectedSlot) &&
802
686
  !selectedSlot?.unlimited &&
803
- typeof selectedSlot?.remainingPax === "number", labels: {
687
+ typeof selectedSlot?.remainingPax === "number", invalidOptionUnitIds: payloadMismatchUnitIds, labels: {
804
688
  heading: messages.bookingCreateDialog.labels.roomsHeading,
805
689
  noOption: messages.bookingCreateDialog.labels.roomsNoOption,
806
690
  noSlot: messages.bookingCreateDialog.labels.roomsNoSlot,
@@ -825,7 +709,10 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
825
709
  noGroups: messages.bookingCreateDialog.labels.sharedRoomNoGroups,
826
710
  createHint: messages.bookingCreateDialog.labels.sharedRoomCreateHint,
827
711
  remove: messages.bookingCreateDialog.labels.sharedRoomRemove,
828
- } })) : null, product.productId && slotId ? (_jsx(TravelersSection, { value: travelers, onChange: setTravelers, roomUnits: roomUnitOptions.length > 0 ? roomUnitOptions : undefined, roomGroups: roomGroups.length > 0 ? roomGroups : undefined, billingPersonId: (person.billTo ?? "person") === "person" ? person.personId : null, labels: {
712
+ } })) : null, product.productId && slotId ? (_jsx(TravelersSection, { value: travelers, onChange: (next) => {
713
+ setPayloadMismatchUnitIds([]);
714
+ setTravelers(next);
715
+ }, roomUnits: roomUnitOptions.length > 0 ? roomUnitOptions : undefined, roomGroups: roomGroups.length > 0 ? roomGroups : undefined, billingPersonId: (person.billTo ?? "person") === "person" ? person.personId : null, labels: {
829
716
  heading: messages.bookingCreateDialog.labels.travelerHeading,
830
717
  addTraveler: messages.bookingCreateDialog.labels.addTraveler,
831
718
  person: messages.bookingCreateDialog.labels.travelerPerson,
@@ -858,7 +745,7 @@ export function BookingCreateForm({ onCreated, defaultProductId, defaultSlotId,
858
745
  : messages.bookingCreateDialog.actions.createAwaitingPaymentBooking] })] })] }), _jsxs("div", { className: "flex flex-col gap-4 lg:col-span-4", children: [_jsx(BookingPreviewCard, { productId: product.productId, optionId: product.optionId, slotId: slotId, slotLabel: (() => {
859
746
  const slot = slots.find((s) => s.id === slotId);
860
747
  return slot ? formatSlotLabel(slot) : null;
861
- })(), unitQuantities: displayQuantities, unitLabels: roomUnitLabels, extraLines: extraLines, travelers: travelers.travelers, messages: messages, onPricingChange: setPricing }), product.productId && slotId ? (_jsx(VoucherPickerSection, { value: voucher, onChange: setVoucher, currency: currency, labels: {
748
+ })(), unitQuantities: displayQuantities, unitLabels: roomUnitLabels, extraLines: displayExtraLines, travelers: travelers.travelers, messages: messages, onPricingChange: setPricing }), product.productId && slotId ? (_jsx(VoucherPickerSection, { value: voucher, onChange: setVoucher, currency: currency, labels: {
862
749
  heading: messages.bookingCreateDialog.labels.voucherHeading,
863
750
  codePlaceholder: messages.bookingCreateDialog.labels.voucherCodePlaceholder,
864
751
  apply: messages.bookingCreateDialog.labels.voucherApply,
@@ -42,7 +42,7 @@ export declare function getBookableDepartureSlots<TSlot extends DepartureSlotSea
42
42
  nowIso: string;
43
43
  optionId: string | null;
44
44
  }): TSlot[];
45
- export declare function itemLinesToRows(quantities: Record<string, number>, units: BookingCreateUnitLineRecord[], pricing: BookingCreatePricingRecord | null): BookingCreateItemLineInput[];
45
+ export declare function itemLinesToRows(quantities: Record<string, number>, units: BookingCreateUnitLineRecord[], pricing: BookingCreatePricingRecord | null, travelerIndexesByUnitId?: Record<string, number[]>): BookingCreateItemLineInput[];
46
46
  export declare function getSelectedSharedRoomUnitId(quantities: Record<string, number>): string | null;
47
47
  export {};
48
48
  //# sourceMappingURL=booking-create-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"booking-create-utils.d.ts","sourceRoot":"","sources":["../../src/components/booking-create-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAA;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAE7D,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,GAAG,cAAc,CAAC,CAAA;AAEpG,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,8BAA8B;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC;AAED,MAAM,WAAW,0BAA0B;IACzC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,KAAK,EAAE,8BAA8B,EAAE,CAAA;CACxC;AAED,KAAK,4BAA4B,GAC7B,QAAQ,GACR,OAAO,GACP,MAAM,GACN,SAAS,GACT,SAAS,GACT,OAAO,GACP,IAAI,CAAA;AAER,MAAM,WAAW,yCAAyC;IACxD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,4BAA4B,CAAA;CACxC;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAIjG;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACrD,KAAK,EAAE,MAAM,GACZ,OAAO,CAKT;AAED,MAAM,MAAM,oCAAoC,GAAG,OAAO,GAAG,iBAAiB,GAAG,eAAe,CAAA;AAEhG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAI5E;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAC3E,oCAAoC,CAOtC;AAED,wBAAgB,iCAAiC,CAC/C,KAAK,SAAS,yCAAyC,EACvD,KAAK,EAAE,SAAS,KAAK,EAAE,GAAG,KAAK,EAAE,CAclC;AAED,wBAAgB,yBAAyB,CAAC,KAAK,SAAS,yBAAyB,EAC/E,KAAK,EAAE,SAAS,KAAK,EAAE,EACvB,OAAO,EAAE;IACP,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,GACA,KAAK,EAAE,CAST;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,KAAK,EAAE,2BAA2B,EAAE,EACpC,OAAO,EAAE,0BAA0B,GAAG,IAAI,GACzC,0BAA0B,EAAE,CAyC9B;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAE7F"}
1
+ {"version":3,"file":"booking-create-utils.d.ts","sourceRoot":"","sources":["../../src/components/booking-create-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,0BAA0B,CAAA;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAE7D,MAAM,MAAM,yBAAyB,GAAG,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,MAAM,GAAG,cAAc,CAAC,CAAA;AAEpG,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,8BAA8B;IAC7C,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC;AAED,MAAM,WAAW,0BAA0B;IACzC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,KAAK,EAAE,8BAA8B,EAAE,CAAA;CACxC;AAED,KAAK,4BAA4B,GAC7B,QAAQ,GACR,OAAO,GACP,MAAM,GACN,SAAS,GACT,SAAS,GACT,OAAO,GACP,IAAI,CAAA;AAER,MAAM,WAAW,yCAAyC;IACxD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,4BAA4B,CAAA;CACxC;AAED,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAIjG;AAED,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,yBAAyB,GAAG,IAAI,GAAG,SAAS,EACrD,KAAK,EAAE,MAAM,GACZ,OAAO,CAKT;AAED,MAAM,MAAM,oCAAoC,GAAG,OAAO,GAAG,iBAAiB,GAAG,eAAe,CAAA;AAEhG,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAI5E;AAED,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAC3E,oCAAoC,CAOtC;AAED,wBAAgB,iCAAiC,CAC/C,KAAK,SAAS,yCAAyC,EACvD,KAAK,EAAE,SAAS,KAAK,EAAE,GAAG,KAAK,EAAE,CAoBlC;AAED,wBAAgB,yBAAyB,CAAC,KAAK,SAAS,yBAAyB,EAC/E,KAAK,EAAE,SAAS,KAAK,EAAE,EACvB,OAAO,EAAE;IACP,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB,GACA,KAAK,EAAE,CAST;AAED,wBAAgB,eAAe,CAC7B,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,KAAK,EAAE,2BAA2B,EAAE,EACpC,OAAO,EAAE,0BAA0B,GAAG,IAAI,EAC1C,uBAAuB,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAM,GACrD,0BAA0B,EAAE,CA+C9B;AAED,wBAAgB,2BAA2B,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,IAAI,CAE7F"}
@@ -32,20 +32,26 @@ export function validateBillingPersonContact(contact) {
32
32
  return "valid";
33
33
  }
34
34
  export function getTravelerAssignableStepperUnits(units) {
35
- const hasRoomUnitByOption = new Map();
35
+ // "Inventory" units (rooms, vehicles) are containers a traveler is
36
+ // placed into. When an option configures one, person-typed
37
+ // pricing-tier units on the same option are hidden from the
38
+ // stepper since their pricing folds into the container's per-pax
39
+ // accounting at submit time.
40
+ const isInventoryType = (unit) => unit.unitType === "room" || unit.unitType === "vehicle";
41
+ const hasInventoryByOption = new Map();
36
42
  for (const unit of units) {
37
43
  const optionKey = unit.optionId ?? unit.optionUnitId;
38
- if (unit.unitType === "room")
39
- hasRoomUnitByOption.set(optionKey, true);
40
- else if (!hasRoomUnitByOption.has(optionKey))
41
- hasRoomUnitByOption.set(optionKey, false);
44
+ if (isInventoryType(unit))
45
+ hasInventoryByOption.set(optionKey, true);
46
+ else if (!hasInventoryByOption.has(optionKey))
47
+ hasInventoryByOption.set(optionKey, false);
42
48
  }
43
49
  return units.filter((unit) => {
44
- if (unit.unitType === "room")
50
+ if (isInventoryType(unit))
45
51
  return true;
46
52
  if (unit.unitType !== "person")
47
53
  return false;
48
- return !hasRoomUnitByOption.get(unit.optionId ?? unit.optionUnitId);
54
+ return !hasInventoryByOption.get(unit.optionId ?? unit.optionUnitId);
49
55
  });
50
56
  }
51
57
  export function getBookableDepartureSlots(slots, options) {
@@ -59,7 +65,7 @@ export function getBookableDepartureSlots(slots, options) {
59
65
  })
60
66
  .sort((left, right) => left.startsAt.localeCompare(right.startsAt));
61
67
  }
62
- export function itemLinesToRows(quantities, units, pricing) {
68
+ export function itemLinesToRows(quantities, units, pricing, travelerIndexesByUnitId = {}) {
63
69
  const unitsById = new Map(units.map((unit) => [unit.optionUnitId, unit]));
64
70
  const unitNames = new Map(units.map((unit) => [unit.optionUnitId, unit.unitName]));
65
71
  const pricedLines = new Map((pricing?.lines ?? []).map((line) => [line.unitId, line]));
@@ -86,13 +92,19 @@ export function itemLinesToRows(quantities, units, pricing) {
86
92
  }
87
93
  const unitSellAmountCents = pricedLine?.unitAmountCents ??
88
94
  (totalSellAmountCents != null ? Math.floor(totalSellAmountCents / quantity) : null);
95
+ const travelerIndexes = travelerIndexesByUnitId[optionUnitId];
89
96
  return {
97
+ // Server uses `clientLineKey` to look up this item after insert
98
+ // and link it to travelers via `booking_item_travelers`. Only
99
+ // stamp when there's an actual traveler mapping to write.
100
+ clientLineKey: travelerIndexes?.length ? `unit:${optionUnitId}` : undefined,
90
101
  optionId: unitsById.get(optionUnitId)?.optionId ?? null,
91
102
  optionUnitId,
92
103
  quantity,
93
104
  title: pricedLine?.label ?? unitNames.get(optionUnitId) ?? null,
94
105
  unitSellAmountCents,
95
106
  totalSellAmountCents,
107
+ travelerIndexes: travelerIndexes?.length ? travelerIndexes : undefined,
96
108
  };
97
109
  });
98
110
  }
@@ -1 +1 @@
1
- {"version":3,"file":"booking-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/booking-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,0BAA0B,CAAA;AA+BjC,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAkBhD;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6EAA6E;IAC7E,OAAO,EAAE,SAAS,GAAG,CAAC,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAC,CAAA;CAC7D;AAED,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IAC9C,sDAAsD;IACtD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACpD,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACrD,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACnD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACtD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACpD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IAClD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACjD,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACnD,wEAAwE;IACxE,WAAW,CAAC,EAAE,oBAAoB,CAAA;IAClC,wDAAwD;IACxD,SAAS,CAAC,EAAE,oBAAoB,CAAA;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;gEAC4D;IAC5D,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,kBAAkB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAA;IACrD,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IACnD,wEAAwE;IACxE,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAClD,KAAK,CAAC,EAAE,sBAAsB,CAAA;CAC/B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,EAAE,EACF,SAAS,EACT,MAAM,EACN,cAAc,EACd,MAAM,EACN,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,KAAK,GACN,EAAE,sBAAsB,2CA4RxB;AASD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,2CA8DhF"}
1
+ {"version":3,"file":"booking-detail-page.d.ts","sourceRoot":"","sources":["../../src/components/booking-detail-page.tsx"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,aAAa,EAInB,MAAM,0BAA0B,CAAA;AAgCjC,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAkBhD;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6EAA6E;IAC7E,OAAO,EAAE,SAAS,GAAG,CAAC,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAC,CAAA;CAC7D;AAED,MAAM,WAAW,sBAAsB;IACrC,2DAA2D;IAC3D,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IAC9C,sDAAsD;IACtD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACpD,aAAa,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACrD,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACnD,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACtD,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACpD,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IAClD,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACjD,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,SAAS,CAAA;IACnD,wEAAwE;IACxE,WAAW,CAAC,EAAE,oBAAoB,CAAA;IAClC,wDAAwD;IACxD,SAAS,CAAC,EAAE,oBAAoB,CAAA;CACjC;AAED,MAAM,WAAW,sBAAsB;IACrC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;gEAC4D;IAC5D,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,MAAM,CAAC,EAAE,MAAM,IAAI,CAAA;IACnB,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,kBAAkB,CAAC,EAAE,CAAC,cAAc,EAAE,MAAM,KAAK,IAAI,CAAA;IACrD,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IACnD,wEAAwE;IACxE,eAAe,CAAC,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,IAAI,CAAA;IAClD,KAAK,CAAC,EAAE,sBAAsB,CAAA;CAC/B;AAED,wBAAgB,iBAAiB,CAAC,EAChC,EAAE,EACF,SAAS,EACT,MAAM,EACN,cAAc,EACd,MAAM,EACN,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,KAAK,GACN,EAAE,sBAAsB,2CAiSxB;AASD;;;;;;;GAOG;AACH,wBAAgB,yBAAyB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,aAAa,CAAA;CAAE,2CA8DhF"}
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import { bookingStatusBadgeVariant, useBooking, useBookingMutation, } from "@voyantjs/bookings-react";
4
4
  import { useOrganization, usePerson } from "@voyantjs/crm-react";
5
+ import { useInvoiceMutation } from "@voyantjs/finance-react";
5
6
  import { Badge, Button, Card, CardContent, CardHeader, CardTitle, cn, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@voyantjs/ui/components";
6
7
  import { Tabs, TabsContent, TabsList, TabsTrigger } from "@voyantjs/ui/components/tabs";
7
8
  import { Ban, Calendar, ChevronRight, CreditCard, Mail, MapPin, MoreHorizontal, Pencil, Phone, RefreshCw, Trash2, Users, } from "lucide-react";
@@ -31,6 +32,7 @@ export function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBac
31
32
  const [cancelDialogOpen, setCancelDialogOpen] = useState(false);
32
33
  const { data: bookingData, isPending } = useBooking(id);
33
34
  const { remove } = useBookingMutation();
35
+ const { convertToInvoice } = useInvoiceMutation();
34
36
  if (isPending) {
35
37
  return (_jsx("div", { className: cn("flex items-center justify-center py-12", className), children: _jsx("p", { className: "text-sm text-muted-foreground", children: messages.common.loading }) }));
36
38
  }
@@ -49,7 +51,7 @@ export function BookingDetailPage({ id, className, locale, hideBreadcrumb, onBac
49
51
  }
50
52
  }, children: [_jsx(Trash2, { className: "h-4 w-4", "aria-hidden": "true" }), detailMessages.deleteAction] })] })] }), slots?.header?.(booking), _jsx(Card, { children: _jsxs(CardContent, { className: "grid grid-cols-2 gap-6 py-6 sm:grid-cols-4", children: [_jsx(SummaryStat, { label: detailMessages.summarySell, value: formatAmount(booking.sellAmountCents, booking.sellCurrency, resolvedLocale, detailMessages.noValue), hint: sellHint }), _jsx(SummaryStat, { label: detailMessages.summaryCostMargin, value: formatAmount(booking.costAmountCents, booking.sellCurrency, resolvedLocale, detailMessages.noValue), hint: formatMargin(booking.marginPercent, detailMessages.noValue) }), _jsx(SummaryStat, { label: detailMessages.summaryDates, value: booking.startDate
51
53
  ? `${formatDate(booking.startDate, resolvedLocale, detailMessages.noValue)} - ${formatDate(booking.endDate, resolvedLocale, detailMessages.noValue)}`
52
- : detailMessages.tbd, icon: _jsx(Calendar, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }), _jsx(SummaryStat, { label: detailMessages.summaryTravelers, value: booking.pax != null ? String(booking.pax) : detailMessages.noValue, icon: _jsx(Users, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }), booking.personId ? (_jsx(SummaryPersonLink, { label: detailMessages.summaryPerson, personId: booking.personId, onOpen: onPersonOpen })) : null, booking.organizationId ? (_jsx(SummaryOrganizationLink, { label: detailMessages.summaryOrganization, organizationId: booking.organizationId, onOpen: onOrganizationOpen })) : null, _jsx(SummaryStat, { label: detailMessages.summaryCreated, value: formatDate(booking.createdAt, resolvedLocale, detailMessages.noValue) }), _jsx(SummaryStat, { label: detailMessages.summaryUpdated, value: formatDate(booking.updatedAt, resolvedLocale, detailMessages.noValue) })] }) }), slots?.afterSummary?.(booking), _jsxs(Tabs, { defaultValue: "overview", children: [_jsxs(TabsList, { className: "w-full justify-start", children: [_jsx(TabsTrigger, { value: "overview", children: detailMessages.tabOverview }), _jsx(TabsTrigger, { value: "travelers", children: detailMessages.tabTravelers }), _jsx(TabsTrigger, { value: "finance", children: detailMessages.tabFinance }), slots?.invoicesTab ? (_jsx(TabsTrigger, { value: "invoices", children: slots.invoicesTab.label ?? detailMessages.tabInvoices })) : null, _jsx(TabsTrigger, { value: "suppliers", children: detailMessages.tabSuppliers }), _jsx(TabsTrigger, { value: "documents", children: detailMessages.tabDocuments }), _jsx(TabsTrigger, { value: "activity", children: detailMessages.tabActivity }), slots?.ledgerTab ? (_jsx(TabsTrigger, { value: "ledger", children: slots.ledgerTab.label ?? detailMessages.tabLedger })) : null] }), _jsxs(TabsContent, { value: "overview", className: "mt-4 flex flex-col gap-6", children: [slots?.overviewStart?.(booking), _jsx(BookingItemList, { bookingId: id }), _jsx(BookingGroupSection, { bookingId: id }), visibleInternalNotes(booking.internalNotes) ? (_jsx(Card, { children: _jsxs(CardContent, { className: "py-5", children: [_jsx("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: detailMessages.internalNotesLabel }), _jsx("p", { className: "whitespace-pre-wrap text-sm", children: visibleInternalNotes(booking.internalNotes) })] }) })) : null, slots?.overviewEnd?.(booking)] }), _jsxs(TabsContent, { value: "travelers", className: "mt-4 flex flex-col gap-6", children: [slots?.travelersStart?.(booking), _jsx(BookingBillingContextCard, { booking: booking }), _jsx(TravelerList, { bookingId: id, autoReveal: true })] }), _jsxs(TabsContent, { value: "finance", className: "mt-4 flex flex-col gap-6", children: [onCollectPayment || onRecordPayment ? (_jsxs("div", { className: "flex items-center justify-end gap-2", children: [onRecordPayment ? (_jsx(Button, { variant: "outline", onClick: () => onRecordPayment(booking), children: detailMessages.recordPaymentAction })) : null, onCollectPayment ? (_jsx(Button, { onClick: () => onCollectPayment(booking), children: detailMessages.collectPaymentAction })) : null] })) : null, slots?.financeStart?.(booking), _jsx(BookingPaymentReconciliationBanner, { bookingId: id }), _jsx(BookingPaymentsSummary, { bookingId: id, variant: "admin" }), _jsx(BookingPaymentScheduleList, { bookingId: id }), _jsx(BookingGuaranteeList, { bookingId: id }), slots?.financeEnd?.(booking)] }), slots?.invoicesTab ? (_jsx(TabsContent, { value: "invoices", className: "mt-4 flex flex-col gap-6", children: renderTabSlot(slots.invoicesTab.content, booking) })) : null, _jsx(TabsContent, { value: "suppliers", className: "mt-4", children: _jsx(SupplierStatusList, { bookingId: id }) }), _jsx(TabsContent, { value: "documents", className: "mt-4 flex flex-col gap-4", children: slots?.documents ? (slots.documents(booking)) : (_jsx("p", { className: "rounded-md border border-dashed p-4 text-sm text-muted-foreground", children: detailMessages.documentsSlotEmpty })) }), _jsxs(TabsContent, { value: "activity", className: "mt-4 flex flex-col gap-6", children: [_jsx(BookingActivityTimeline, { bookingId: id }), _jsx(BookingNotes, { bookingId: id }), slots?.activityEnd?.(booking)] }), slots?.ledgerTab ? (_jsx(TabsContent, { value: "ledger", className: "mt-4 flex flex-col gap-6", children: renderTabSlot(slots.ledgerTab.content, booking) })) : null] }), _jsx(BookingDialog, { open: editOpen, onOpenChange: setEditOpen, booking: booking }), _jsx(StatusChangeDialog, { open: statusDialogOpen, onOpenChange: setStatusDialogOpen, bookingId: id, currentStatus: booking.status }), _jsx(BookingCancellationDialog, { open: cancelDialogOpen, onOpenChange: setCancelDialogOpen, booking: booking })] }));
54
+ : detailMessages.tbd, icon: _jsx(Calendar, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }), _jsx(SummaryStat, { label: detailMessages.summaryTravelers, value: booking.pax != null ? String(booking.pax) : detailMessages.noValue, icon: _jsx(Users, { className: "h-3.5 w-3.5", "aria-hidden": "true" }) }), booking.personId ? (_jsx(SummaryPersonLink, { label: detailMessages.summaryPerson, personId: booking.personId, onOpen: onPersonOpen })) : null, booking.organizationId ? (_jsx(SummaryOrganizationLink, { label: detailMessages.summaryOrganization, organizationId: booking.organizationId, onOpen: onOrganizationOpen })) : null, _jsx(SummaryStat, { label: detailMessages.summaryCreated, value: formatDate(booking.createdAt, resolvedLocale, detailMessages.noValue) }), _jsx(SummaryStat, { label: detailMessages.summaryUpdated, value: formatDate(booking.updatedAt, resolvedLocale, detailMessages.noValue) })] }) }), slots?.afterSummary?.(booking), _jsxs(Tabs, { defaultValue: "overview", children: [_jsxs(TabsList, { className: "w-full justify-start", children: [_jsx(TabsTrigger, { value: "overview", children: detailMessages.tabOverview }), _jsx(TabsTrigger, { value: "travelers", children: detailMessages.tabTravelers }), _jsx(TabsTrigger, { value: "finance", children: detailMessages.tabFinance }), slots?.invoicesTab ? (_jsx(TabsTrigger, { value: "invoices", children: slots.invoicesTab.label ?? detailMessages.tabInvoices })) : null, _jsx(TabsTrigger, { value: "suppliers", children: detailMessages.tabSuppliers }), _jsx(TabsTrigger, { value: "documents", children: detailMessages.tabDocuments }), _jsx(TabsTrigger, { value: "activity", children: detailMessages.tabActivity }), slots?.ledgerTab ? (_jsx(TabsTrigger, { value: "ledger", children: slots.ledgerTab.label ?? detailMessages.tabLedger })) : null] }), _jsxs(TabsContent, { value: "overview", className: "mt-4 flex flex-col gap-6", children: [slots?.overviewStart?.(booking), _jsx(BookingItemList, { bookingId: id }), _jsx(BookingGroupSection, { bookingId: id }), visibleInternalNotes(booking.internalNotes) ? (_jsx(Card, { children: _jsxs(CardContent, { className: "py-5", children: [_jsx("p", { className: "mb-1 text-xs font-medium text-muted-foreground", children: detailMessages.internalNotesLabel }), _jsx("p", { className: "whitespace-pre-wrap text-sm", children: visibleInternalNotes(booking.internalNotes) })] }) })) : null, slots?.overviewEnd?.(booking)] }), _jsxs(TabsContent, { value: "travelers", className: "mt-4 flex flex-col gap-6", children: [slots?.travelersStart?.(booking), _jsx(BookingBillingContextCard, { booking: booking }), _jsx(TravelerList, { bookingId: id, autoReveal: true })] }), _jsxs(TabsContent, { value: "finance", className: "mt-4 flex flex-col gap-6", children: [onCollectPayment || onRecordPayment ? (_jsxs("div", { className: "flex items-center justify-end gap-2", children: [onRecordPayment ? (_jsx(Button, { variant: "outline", onClick: () => onRecordPayment(booking), children: detailMessages.recordPaymentAction })) : null, onCollectPayment ? (_jsx(Button, { onClick: () => onCollectPayment(booking), children: detailMessages.collectPaymentAction })) : null] })) : null, slots?.financeStart?.(booking), _jsx(BookingPaymentReconciliationBanner, { bookingId: id }), _jsx(BookingPaymentsSummary, { bookingId: id, variant: "admin", onConvertProforma: (row) => convertToInvoice.mutateAsync({ id: row.invoiceId }) }), _jsx(BookingPaymentScheduleList, { bookingId: id }), _jsx(BookingGuaranteeList, { bookingId: id }), slots?.financeEnd?.(booking)] }), slots?.invoicesTab ? (_jsx(TabsContent, { value: "invoices", className: "mt-4 flex flex-col gap-6", children: renderTabSlot(slots.invoicesTab.content, booking) })) : null, _jsx(TabsContent, { value: "suppliers", className: "mt-4", children: _jsx(SupplierStatusList, { bookingId: id }) }), _jsx(TabsContent, { value: "documents", className: "mt-4 flex flex-col gap-4", children: slots?.documents ? (slots.documents(booking)) : (_jsx("p", { className: "rounded-md border border-dashed p-4 text-sm text-muted-foreground", children: detailMessages.documentsSlotEmpty })) }), _jsxs(TabsContent, { value: "activity", className: "mt-4 flex flex-col gap-6", children: [_jsx(BookingActivityTimeline, { bookingId: id }), _jsx(BookingNotes, { bookingId: id }), slots?.activityEnd?.(booking)] }), slots?.ledgerTab ? (_jsx(TabsContent, { value: "ledger", className: "mt-4 flex flex-col gap-6", children: renderTabSlot(slots.ledgerTab.content, booking) })) : null] }), _jsx(BookingDialog, { open: editOpen, onOpenChange: setEditOpen, booking: booking }), _jsx(StatusChangeDialog, { open: statusDialogOpen, onOpenChange: setStatusDialogOpen, bookingId: id, currentStatus: booking.status }), _jsx(BookingCancellationDialog, { open: cancelDialogOpen, onOpenChange: setCancelDialogOpen, booking: booking })] }));
53
55
  }
54
56
  function renderTabSlot(content, booking) {
55
57
  return typeof content === "function" ? content(booking) : content;
@@ -2,6 +2,7 @@ export interface BookingPaymentsSummaryRow {
2
2
  id: string;
3
3
  invoiceId: string;
4
4
  invoiceNumber: string;
5
+ invoiceType?: "invoice" | "proforma" | "credit_note";
5
6
  amountCents: number;
6
7
  currency: string;
7
8
  status: string;
@@ -32,6 +33,8 @@ export interface BookingPaymentsSummaryProps {
32
33
  * on menu items, so this is a click handler rather than an href.
33
34
  */
34
35
  onViewPayment?: (row: BookingPaymentsSummaryRow) => void;
36
+ /** Convert the row's proforma invoice into a final invoice. */
37
+ onConvertProforma?: (row: BookingPaymentsSummaryRow) => Promise<unknown> | unknown;
35
38
  /** Edit handler — typically opens a dialog pre-filled with the row. */
36
39
  onEditPayment?: (row: BookingPaymentsSummaryRow) => void;
37
40
  /**
@@ -60,5 +63,5 @@ export interface BookingPaymentsSummaryProps {
60
63
  * first as the primary identifier — that's the difference between
61
64
  * "list of payments" and "list of invoice line-items".
62
65
  */
63
- export declare function BookingPaymentsSummary({ bookingId, variant, getInvoiceHref, onViewPayment, onEditPayment, onDeletePayment, }: BookingPaymentsSummaryProps): import("react/jsx-runtime").JSX.Element;
66
+ export declare function BookingPaymentsSummary({ bookingId, variant, getInvoiceHref, onViewPayment, onConvertProforma, onEditPayment, onDeletePayment, }: BookingPaymentsSummaryProps): import("react/jsx-runtime").JSX.Element;
64
67
  //# sourceMappingURL=booking-payments-summary.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"booking-payments-summary.d.ts","sourceRoot":"","sources":["../../src/components/booking-payments-summary.tsx"],"names":[],"mappings":"AAiEA,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;IAC5B;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAC9E;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,IAAI,CAAA;IACxD,uEAAuE;IACvE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,IAAI,CAAA;IACxD;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC3E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,SAAS,EACT,OAAkB,EAClB,cAAc,EACd,aAAa,EACb,aAAa,EACb,eAAe,GAChB,EAAE,2BAA2B,2CAsP7B"}
1
+ {"version":3,"file":"booking-payments-summary.d.ts","sourceRoot":"","sources":["../../src/components/booking-payments-summary.tsx"],"names":[],"mappings":"AAkEA,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;IACpD,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,EAAE,MAAM,CAAA;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,MAAM,WAAW,2BAA2B;IAC1C,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;IAC5B;;;;OAIG;IACH,cAAc,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAA;IAC9E;;;;OAIG;IACH,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,IAAI,CAAA;IACxD,+DAA+D;IAC/D,iBAAiB,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAA;IAClF,uEAAuE;IACvE,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,IAAI,CAAA;IACxD;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,yBAAyB,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;CAC3E;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,SAAS,EACT,OAAkB,EAClB,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,eAAe,GAChB,EAAE,2BAA2B,2CAiR7B"}