@voyantjs/bookings-ui 0.101.0 → 0.101.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.
@@ -110,12 +110,10 @@ export function OptionUnitsStepperSection({ value, onChange, productId, slotId,
110
110
  React.useEffect(() => {
111
111
  onUnitsChange?.(units);
112
112
  }, [onUnitsChange, units]);
113
- // Group the unit rows by option operators choose how many of each
114
- // *option* to book, not how many of each age-banded unit. The age
115
- // categorization (Adult / Child / Senior / Infant) is derived from
116
- // each traveler's date of birth and resolved on submit, so we route
117
- // the per-option quantity through the option's "primary" unit
118
- // (preferring `ADULT` by code, otherwise the first unit) here.
113
+ // Person-priced options are grouped by option: operators choose pax
114
+ // count, then traveler rows split Adult / Child / Infant. Inventory
115
+ // options are different: rooms and vehicles are physical containers,
116
+ // so each room/vehicle unit must be selectable independently.
119
117
  const optionRows = React.useMemo(() => {
120
118
  const groups = new Map();
121
119
  for (const unit of units) {
@@ -132,8 +130,18 @@ export function OptionUnitsStepperSection({ value, onChange, productId, slotId,
132
130
  groups.set(key, { primary: unit, allUnits: [unit] });
133
131
  }
134
132
  }
135
- return Array.from(groups.entries()).map(([optionKey, group]) => {
133
+ return Array.from(groups.entries()).flatMap(([optionKey, group]) => {
136
134
  const optionName = productOptions.find((option) => option.id === optionKey)?.name ?? group.primary.unitName;
135
+ const inventoryUnits = group.allUnits.filter(isInventoryUnit);
136
+ if (inventoryUnits.length > 0) {
137
+ return inventoryUnits.map((unit) => ({
138
+ optionKey: unit.optionUnitId,
139
+ optionName: unit.unitName,
140
+ primary: unit,
141
+ allUnits: [unit],
142
+ totalRemaining: unit.remaining,
143
+ }));
144
+ }
137
145
  const totalRemaining = group.allUnits.reduce((acc, unit) => {
138
146
  if (unit.remaining === null)
139
147
  return null;
@@ -141,13 +149,15 @@ export function OptionUnitsStepperSection({ value, onChange, productId, slotId,
141
149
  return null;
142
150
  return acc + unit.remaining;
143
151
  }, 0);
144
- return {
145
- optionKey,
146
- optionName,
147
- primary: group.primary,
148
- allUnits: group.allUnits,
149
- totalRemaining,
150
- };
152
+ return [
153
+ {
154
+ optionKey,
155
+ optionName,
156
+ primary: group.primary,
157
+ allUnits: group.allUnits,
158
+ totalRemaining,
159
+ },
160
+ ];
151
161
  });
152
162
  }, [units, productOptions]);
153
163
  if (!slotId && !productId && !optionId) {
@@ -238,6 +248,9 @@ function isAdultUnit(unit) {
238
248
  function isPersonUnit(unit) {
239
249
  return unit.unitType === "person";
240
250
  }
251
+ function isInventoryUnit(unit) {
252
+ return unit.unitType === "room" || unit.unitType === "vehicle";
253
+ }
241
254
  function optionUnitToStepperUnit(option, unit, unitCount) {
242
255
  return {
243
256
  optionId: option.id,
@@ -1,5 +1,6 @@
1
1
  export interface PriceBreakdownLine {
2
2
  unitId: string;
3
+ pricingCategoryId?: string | null;
3
4
  label: string;
4
5
  quantity: number;
5
6
  /** Per-unit price for the matched tier/row. `null` = on-request pricing. */
@@ -29,6 +30,10 @@ export interface PriceBreakdownSectionProps {
29
30
  unitQuantities: Record<string, number>;
30
31
  /** Display labels keyed by option_unit id. */
31
32
  unitLabels?: Record<string, string>;
33
+ /** Traveler pricing quantities keyed by option_unit id and pricing_category id. */
34
+ pricingCategoryQuantities?: Record<string, Record<string, number>>;
35
+ /** Display labels keyed by pricing_category id. */
36
+ pricingCategoryLabels?: Record<string, string>;
32
37
  /**
33
38
  * Force a specific catalog. Defaults to the public catalog the storefront
34
39
  * uses — matches what a customer would see.
@@ -56,6 +61,11 @@ export interface PriceBreakdownSectionProps {
56
61
  */
57
62
  flat?: boolean;
58
63
  }
64
+ interface UnitPriceLookupRow {
65
+ unitId: string;
66
+ pricingCategoryId?: string | null;
67
+ }
68
+ export declare function createUnitPriceLookup<TUnitPrice extends UnitPriceLookupRow>(unitPrices: ReadonlyArray<TUnitPrice>): (unitId: string, pricingCategoryId: string | null) => TUnitPrice | undefined;
59
69
  /**
60
70
  * Live price-breakdown preview for booking-create flows. Read-only — uses
61
71
  * `usePricingPreview` (#237) to fetch the catalog-resolved snapshot the
@@ -68,5 +78,6 @@ export interface PriceBreakdownSectionProps {
68
78
  * - `free` / `included` — render 0.00 without an on-request badge.
69
79
  * - `on_request` / anything else — render "On request"; total excludes it.
70
80
  */
71
- export declare function PriceBreakdownSection({ productId, optionId, unitQuantities, unitLabels, catalogId, labels, onChange, flat, }: PriceBreakdownSectionProps): import("react/jsx-runtime").JSX.Element | null;
81
+ export declare function PriceBreakdownSection({ productId, optionId, unitQuantities, unitLabels, pricingCategoryQuantities, pricingCategoryLabels, catalogId, labels, onChange, flat, }: PriceBreakdownSectionProps): import("react/jsx-runtime").JSX.Element | null;
82
+ export {};
72
83
  //# sourceMappingURL=price-breakdown-section.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"price-breakdown-section.d.ts","sourceRoot":"","sources":["../../src/components/price-breakdown-section.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,4DAA4D;IAC5D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,gBAAgB,EAAE,OAAO,CAAA;IACzB,cAAc,EAAE,OAAO,CAAA;IACvB,KAAK,EAAE,kBAAkB,EAAE,CAAA;CAC5B;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACtC,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,yBAAyB,CAAC,EAAE,MAAM,CAAA;QAClC,sBAAsB,CAAC,EAAE,MAAM,CAAA;KAChC,CAAA;IACD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAA;IAC/C;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AA0BD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,QAAQ,EACR,cAAc,EACd,UAAU,EACV,SAAS,EACT,MAAM,EACN,QAAQ,EACR,IAAY,GACb,EAAE,0BAA0B,kDAiT5B"}
1
+ {"version":3,"file":"price-breakdown-section.d.ts","sourceRoot":"","sources":["../../src/components/price-breakdown-section.tsx"],"names":[],"mappings":"AASA,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,MAAM,CAAA;IAChB,4EAA4E;IAC5E,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,4DAA4D;IAC5D,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,WAAW,EAAE,OAAO,CAAA;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,oBAAoB,EAAE,MAAM,GAAG,IAAI,CAAA;IACnC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,mBAAmB,EAAE,MAAM,CAAA;IAC3B,gBAAgB,EAAE,OAAO,CAAA;IACzB,cAAc,EAAE,OAAO,CAAA;IACvB,KAAK,EAAE,kBAAkB,EAAE,CAAA;CAC5B;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,6EAA6E;IAC7E,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACtC,8CAA8C;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IACnC,mFAAmF;IACnF,yBAAyB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;IAClE,mDAAmD;IACnD,qBAAqB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC9C;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,eAAe,CAAC,EAAE,MAAM,CAAA;QACxB,cAAc,CAAC,EAAE,MAAM,CAAA;QACvB,yBAAyB,CAAC,EAAE,MAAM,CAAA;QAClC,sBAAsB,CAAC,EAAE,MAAM,CAAA;KAChC,CAAA;IACD,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,mBAAmB,KAAK,IAAI,CAAA;IAC/C;;;;OAIG;IACH,IAAI,CAAC,EAAE,OAAO,CAAA;CACf;AAQD,UAAU,kBAAkB;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,iBAAiB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAClC;AAED,wBAAgB,qBAAqB,CAAC,UAAU,SAAS,kBAAkB,EACzE,UAAU,EAAE,aAAa,CAAC,UAAU,CAAC,GACpC,CAAC,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,GAAG,IAAI,KAAK,UAAU,GAAG,SAAS,CAwB9E;AAoBD;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,EACpC,SAAS,EACT,QAAQ,EACR,cAAc,EACd,UAAU,EACV,yBAAyB,EACzB,qBAAqB,EACrB,SAAS,EACT,MAAM,EACN,QAAQ,EACR,IAAY,GACb,EAAE,0BAA0B,kDA2U5B"}
@@ -6,6 +6,25 @@ import { Button, Label, Textarea } from "@voyantjs/ui/components";
6
6
  import { CurrencyInput } from "@voyantjs/ui/components/currency-input";
7
7
  import * as React from "react";
8
8
  import { useBookingsUiI18nOrDefault, useBookingsUiMessagesOrDefault } from "../i18n/provider.js";
9
+ export function createUnitPriceLookup(unitPrices) {
10
+ const defaultUnitPricesByUnit = new Map();
11
+ const unitPricesByUnitAndCategory = new Map();
12
+ for (const unitPrice of unitPrices) {
13
+ if (unitPrice.pricingCategoryId) {
14
+ unitPricesByUnitAndCategory.set(`${unitPrice.unitId}:${unitPrice.pricingCategoryId}`, unitPrice);
15
+ continue;
16
+ }
17
+ if (!defaultUnitPricesByUnit.has(unitPrice.unitId)) {
18
+ defaultUnitPricesByUnit.set(unitPrice.unitId, unitPrice);
19
+ }
20
+ }
21
+ return (unitId, pricingCategoryId) => {
22
+ if (!pricingCategoryId)
23
+ return defaultUnitPricesByUnit.get(unitId);
24
+ return (unitPricesByUnitAndCategory.get(`${unitId}:${pricingCategoryId}`) ??
25
+ defaultUnitPricesByUnit.get(unitId));
26
+ };
27
+ }
9
28
  /**
10
29
  * Picks the tier whose quantity range contains `qty`. Tiers are expected
11
30
  * oldest-to-newest, `minQuantity`-ascending. Ties are broken by first-match —
@@ -34,7 +53,7 @@ function matchTier(tiers, qty) {
34
53
  * - `free` / `included` — render 0.00 without an on-request badge.
35
54
  * - `on_request` / anything else — render "On request"; total excludes it.
36
55
  */
37
- export function PriceBreakdownSection({ productId, optionId, unitQuantities, unitLabels, catalogId, labels, onChange, flat = false, }) {
56
+ export function PriceBreakdownSection({ productId, optionId, unitQuantities, unitLabels, pricingCategoryQuantities, pricingCategoryLabels, catalogId, labels, onChange, flat = false, }) {
38
57
  const { formatCurrency, formatNumber } = useBookingsUiI18nOrDefault();
39
58
  const messages = useBookingsUiMessagesOrDefault();
40
59
  const merged = { ...messages.priceBreakdownSection.labels, ...labels };
@@ -96,85 +115,105 @@ export function PriceBreakdownSection({ productId, optionId, unitQuantities, uni
96
115
  existing.push(rule);
97
116
  rulesByOption.set(rule.optionId, existing);
98
117
  }
99
- const unitPricesByUnit = new Map();
100
- for (const up of snapshot.unitPrices) {
101
- if (!unitPricesByUnit.has(up.unitId)) {
102
- unitPricesByUnit.set(up.unitId, up);
103
- }
104
- }
118
+ const findUnitPrice = createUnitPriceLookup(snapshot.unitPrices);
105
119
  for (const [unitId, quantity] of Object.entries(unitQuantities)) {
106
120
  if (quantity <= 0)
107
121
  continue;
108
- const up = unitPricesByUnit.get(unitId);
109
- if (!up) {
110
- // The unit isn't priced in this catalog — show it on-request so the
111
- // operator knows they need to quote manually.
112
- out.push({
113
- unitId,
114
- label: unitLabels?.[unitId] ?? unitId,
115
- quantity,
116
- unitAmountCents: null,
117
- totalAmountCents: null,
118
- tierLabel: null,
119
- isGroupRate: false,
120
- });
121
- anyOnRequest = true;
122
- continue;
123
- }
124
- const label = unitLabels?.[unitId] ?? up.unitName ?? unitId;
125
- if (up.pricingMode === "on_request") {
126
- out.push({
127
- unitId,
128
- label,
129
- quantity,
130
- unitAmountCents: null,
131
- totalAmountCents: null,
132
- tierLabel: merged.onRequest,
133
- isGroupRate: false,
134
- });
135
- anyOnRequest = true;
136
- continue;
137
- }
138
- if (up.pricingMode === "free" || up.pricingMode === "included") {
139
- out.push({
140
- unitId,
141
- label,
142
- quantity,
143
- unitAmountCents: 0,
144
- totalAmountCents: 0,
145
- tierLabel: null,
146
- isGroupRate: false,
147
- });
148
- continue;
149
- }
150
- // per_unit (and anything else that falls through to explicit amounts).
151
- const matchedTier = matchTier(up.tiers, quantity);
152
- const unitAmount = matchedTier?.sellAmountCents ?? up.sellAmountCents;
153
- if (unitAmount === null) {
122
+ const categoryEntries = Object.entries(pricingCategoryQuantities?.[unitId] ?? {}).filter(([, categoryQuantity]) => categoryQuantity > 0);
123
+ const selections = categoryEntries.length > 0
124
+ ? categoryEntries.map(([pricingCategoryId, categoryQuantity]) => ({
125
+ pricingCategoryId,
126
+ quantity: categoryQuantity,
127
+ unitPrice: findUnitPrice(unitId, pricingCategoryId),
128
+ }))
129
+ : [
130
+ {
131
+ pricingCategoryId: null,
132
+ quantity,
133
+ unitPrice: findUnitPrice(unitId, null),
134
+ },
135
+ ];
136
+ for (const selection of selections) {
137
+ const up = selection.unitPrice;
138
+ if (!up) {
139
+ // The unit isn't priced in this catalog — show it on-request so the
140
+ // operator knows they need to quote manually.
141
+ out.push({
142
+ unitId,
143
+ pricingCategoryId: selection.pricingCategoryId,
144
+ label: unitLabels?.[unitId] ?? unitId,
145
+ quantity: selection.quantity,
146
+ unitAmountCents: null,
147
+ totalAmountCents: null,
148
+ tierLabel: null,
149
+ isGroupRate: false,
150
+ });
151
+ anyOnRequest = true;
152
+ continue;
153
+ }
154
+ const categoryLabel = selection.pricingCategoryId
155
+ ? pricingCategoryLabels?.[selection.pricingCategoryId]
156
+ : null;
157
+ const unitLabel = unitLabels?.[unitId] ?? up.unitName ?? unitId;
158
+ const label = categoryLabel ? `${unitLabel} · ${categoryLabel}` : unitLabel;
159
+ if (up.pricingMode === "on_request") {
160
+ out.push({
161
+ unitId,
162
+ pricingCategoryId: selection.pricingCategoryId,
163
+ label,
164
+ quantity: selection.quantity,
165
+ unitAmountCents: null,
166
+ totalAmountCents: null,
167
+ tierLabel: merged.onRequest,
168
+ isGroupRate: false,
169
+ });
170
+ anyOnRequest = true;
171
+ continue;
172
+ }
173
+ if (up.pricingMode === "free" || up.pricingMode === "included") {
174
+ out.push({
175
+ unitId,
176
+ pricingCategoryId: selection.pricingCategoryId,
177
+ label,
178
+ quantity: selection.quantity,
179
+ unitAmountCents: 0,
180
+ totalAmountCents: 0,
181
+ tierLabel: null,
182
+ isGroupRate: false,
183
+ });
184
+ continue;
185
+ }
186
+ // per_unit (and anything else that falls through to explicit amounts).
187
+ const matchedTier = matchTier(up.tiers, selection.quantity);
188
+ const unitAmount = matchedTier?.sellAmountCents ?? up.sellAmountCents;
189
+ if (unitAmount === null) {
190
+ out.push({
191
+ unitId,
192
+ pricingCategoryId: selection.pricingCategoryId,
193
+ label,
194
+ quantity: selection.quantity,
195
+ unitAmountCents: null,
196
+ totalAmountCents: null,
197
+ tierLabel: merged.onRequest,
198
+ isGroupRate: false,
199
+ });
200
+ anyOnRequest = true;
201
+ continue;
202
+ }
203
+ const lineTotal = unitAmount * selection.quantity;
204
+ const isGroupRate = matchedTier !== null && matchedTier.minQuantity > 1;
154
205
  out.push({
155
206
  unitId,
207
+ pricingCategoryId: selection.pricingCategoryId,
156
208
  label,
157
- quantity,
158
- unitAmountCents: null,
159
- totalAmountCents: null,
160
- tierLabel: merged.onRequest,
161
- isGroupRate: false,
209
+ quantity: selection.quantity,
210
+ unitAmountCents: unitAmount,
211
+ totalAmountCents: lineTotal,
212
+ tierLabel: isGroupRate ? merged.groupRate : null,
213
+ isGroupRate,
162
214
  });
163
- anyOnRequest = true;
164
- continue;
215
+ runningTotal += lineTotal;
165
216
  }
166
- const lineTotal = unitAmount * quantity;
167
- const isGroupRate = matchedTier !== null && matchedTier.minQuantity > 1;
168
- out.push({
169
- unitId,
170
- label,
171
- quantity,
172
- unitAmountCents: unitAmount,
173
- totalAmountCents: lineTotal,
174
- tierLabel: isGroupRate ? merged.groupRate : null,
175
- isGroupRate,
176
- });
177
- runningTotal += lineTotal;
178
217
  }
179
218
  return { lines: out, total: anyOnRequest ? null : runningTotal };
180
219
  }, [
@@ -183,6 +222,8 @@ export function PriceBreakdownSection({ productId, optionId, unitQuantities, uni
183
222
  fallbackUnitAmountCents,
184
223
  unitQuantities,
185
224
  unitLabels,
225
+ pricingCategoryQuantities,
226
+ pricingCategoryLabels,
186
227
  merged.onRequest,
187
228
  merged.groupRate,
188
229
  ]);
@@ -7,6 +7,11 @@ export interface TravelerCategoryUnitState {
7
7
  unitId: string;
8
8
  unitCode: string | null;
9
9
  }
10
+ export interface TravelerCategorySelectableUnitState {
11
+ unitType?: string | null;
12
+ }
13
+ export declare function getSelectableTravelerCategoryUnits<TUnit extends TravelerCategorySelectableUnitState>(units: readonly TUnit[]): TUnit[];
14
+ export declare function shouldUseStaticTravelerCategoryFallback(hasGroup: boolean, selectableUnitCount: number): boolean;
10
15
  export declare function inferTravelerRoleFromUnit(unit: TravelerCategoryUnitState): Exclude<TravelerCategoryRole, "lead">;
11
16
  export declare function getStaticTravelerCategoryButtonState(traveler: TravelerCategoryState, category: Exclude<TravelerCategoryRole, "lead">): {
12
17
  active: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"traveler-category-buttons.d.ts","sourceRoot":"","sources":["../../src/components/traveler-category-buttons.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAA;AAExE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,oBAAoB,CAAA;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAKvC;AAED,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,qBAAqB,EAC/B,QAAQ,EAAE,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC,GAC9C;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAS5E;AAED,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,qBAAqB,EAC/B,IAAI,EAAE,yBAAyB,GAC9B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAY5E"}
1
+ {"version":3,"file":"traveler-category-buttons.d.ts","sourceRoot":"","sources":["../../src/components/traveler-category-buttons.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,QAAQ,CAAA;AAExE,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,oBAAoB,CAAA;IAC1B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CACxB;AAED,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,wBAAgB,kCAAkC,CAChD,KAAK,SAAS,mCAAmC,EACjD,KAAK,EAAE,SAAS,KAAK,EAAE,GAAG,KAAK,EAAE,CAOlC;AAED,wBAAgB,uCAAuC,CACrD,QAAQ,EAAE,OAAO,EACjB,mBAAmB,EAAE,MAAM,GAC1B,OAAO,CAET;AAED,wBAAgB,yBAAyB,CACvC,IAAI,EAAE,yBAAyB,GAC9B,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAKvC;AAED,wBAAgB,oCAAoC,CAClD,QAAQ,EAAE,qBAAqB,EAC/B,QAAQ,EAAE,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC,GAC9C;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAS5E;AAED,wBAAgB,qCAAqC,CACnD,QAAQ,EAAE,qBAAqB,EAC/B,IAAI,EAAE,yBAAyB,GAC9B;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,EAAE,oBAAoB,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CAY5E"}
@@ -1,3 +1,10 @@
1
+ export function getSelectableTravelerCategoryUnits(units) {
2
+ const hasInventoryUnits = units.some((unit) => unit.unitType === "room" || unit.unitType === "vehicle");
3
+ return units.filter((unit) => unit.unitType === "person" || (!hasInventoryUnits && unit.unitType == null));
4
+ }
5
+ export function shouldUseStaticTravelerCategoryFallback(hasGroup, selectableUnitCount) {
6
+ return !hasGroup || selectableUnitCount === 0;
7
+ }
1
8
  export function inferTravelerRoleFromUnit(unit) {
2
9
  const codeLower = (unit.unitCode ?? "").toLowerCase();
3
10
  if (codeLower === "child")
@@ -16,6 +16,8 @@ export interface TravelerEntry {
16
16
  dateOfBirth: string | null;
17
17
  /** option_unit_id of the person pricing tier this traveler is billed as. */
18
18
  pricingUnitId: string | null;
19
+ /** pricing_category_id selected from the product's traveler price matrix, when applicable. */
20
+ pricingCategoryId: string | null;
19
21
  /** option_unit_id of the room/vehicle this traveler occupies, when applicable. */
20
22
  inventoryUnitId: string | null;
21
23
  /** Operator intent for `pricingUnitId`; defaults to `auto` when omitted. */
@@ -40,6 +42,8 @@ export { computeAgeYears } from "@voyantjs/bookings/pricing-assignment";
40
42
  * - adult: 18+
41
43
  */
42
44
  export declare function deriveTravelerRoleFromDob(dob: string | null): TravelerRole;
45
+ export declare function categoryMatchesDob(category: TravelerPricingCategoryOption, dob: string | null): boolean;
46
+ export declare function matchPricingCategoryForTraveler(categories: ReadonlyArray<TravelerPricingCategoryOption> | undefined, dob: string | null, role: TravelerRole | null, inventoryUnitId: string | null): string | null;
43
47
  export interface RoomUnitOption {
44
48
  unitId: string;
45
49
  unitName: string;
@@ -75,6 +79,15 @@ export interface RoomGroup {
75
79
  primaryUnitId: string;
76
80
  units: RoomGroupUnit[];
77
81
  }
82
+ export interface TravelerPricingCategoryOption {
83
+ categoryId: string;
84
+ name: string;
85
+ code: string | null;
86
+ categoryType: string;
87
+ minAge: number | null;
88
+ maxAge: number | null;
89
+ unitIds: string[];
90
+ }
78
91
  export interface TravelersSectionProps {
79
92
  value: TravelerListValue;
80
93
  onChange: (value: TravelerListValue) => void;
@@ -90,6 +103,11 @@ export interface TravelersSectionProps {
90
103
  * Required for category buttons to render.
91
104
  */
92
105
  roomGroups?: RoomGroup[];
106
+ /**
107
+ * Product pricing categories surfaced by the room x traveler price matrix.
108
+ * Room-only accommodation products use these as the traveler category select.
109
+ */
110
+ pricingCategories?: TravelerPricingCategoryOption[];
93
111
  billingPersonId?: string | null;
94
112
  labels?: {
95
113
  heading?: string;
@@ -137,5 +155,5 @@ export interface TravelersSectionProps {
137
155
  * here. The UI lets the operator pick whichever layout they want, then
138
156
  * the submit handler errors if the invariant isn't met.
139
157
  */
140
- export declare function TravelersSection({ value, onChange, roomUnits, roomGroups, billingPersonId, labels, }: TravelersSectionProps): import("react/jsx-runtime").JSX.Element;
158
+ export declare function TravelersSection({ value, onChange, roomUnits, roomGroups, pricingCategories, billingPersonId, labels, }: TravelersSectionProps): import("react/jsx-runtime").JSX.Element;
141
159
  //# sourceMappingURL=travelers-section.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"travelers-section.d.ts","sourceRoot":"","sources":["../../src/components/travelers-section.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"travelers-section.d.ts","sourceRoot":"","sources":["../../src/components/travelers-section.tsx"],"names":[],"mappings":"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"}