@voyantjs/bookings-ui 0.100.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.
- package/dist/components/booking-create-sheet.d.ts.map +1 -1
- package/dist/components/booking-create-sheet.js +242 -56
- package/dist/components/booking-create-utils.d.ts +19 -1
- package/dist/components/booking-create-utils.d.ts.map +1 -1
- package/dist/components/booking-create-utils.js +69 -3
- package/dist/components/option-units-stepper-section.d.ts.map +1 -1
- package/dist/components/option-units-stepper-section.js +27 -14
- package/dist/components/price-breakdown-section.d.ts +12 -1
- package/dist/components/price-breakdown-section.d.ts.map +1 -1
- package/dist/components/price-breakdown-section.js +113 -72
- package/dist/components/traveler-category-buttons.d.ts +5 -0
- package/dist/components/traveler-category-buttons.d.ts.map +1 -1
- package/dist/components/traveler-category-buttons.js +7 -0
- package/dist/components/travelers-section.d.ts +19 -1
- package/dist/components/travelers-section.d.ts.map +1 -1
- package/dist/components/travelers-section.js +101 -33
- package/dist/i18n/en.d.ts +1 -0
- package/dist/i18n/en.d.ts.map +1 -1
- package/dist/i18n/en.js +1 -0
- package/dist/i18n/messages.d.ts +1 -0
- package/dist/i18n/messages.d.ts.map +1 -1
- package/dist/i18n/provider.d.ts +2 -0
- package/dist/i18n/provider.d.ts.map +1 -1
- package/dist/i18n/ro.d.ts +1 -0
- package/dist/i18n/ro.d.ts.map +1 -1
- package/dist/i18n/ro.js +1 -0
- package/package.json +32 -32
|
@@ -110,12 +110,10 @@ export function OptionUnitsStepperSection({ value, onChange, productId, slotId,
|
|
|
110
110
|
React.useEffect(() => {
|
|
111
111
|
onUnitsChange?.(units);
|
|
112
112
|
}, [onUnitsChange, units]);
|
|
113
|
-
//
|
|
114
|
-
//
|
|
115
|
-
//
|
|
116
|
-
// each
|
|
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()).
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
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;
|
|
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
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
unitId,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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:
|
|
159
|
-
totalAmountCents:
|
|
160
|
-
tierLabel: merged.
|
|
161
|
-
isGroupRate
|
|
209
|
+
quantity: selection.quantity,
|
|
210
|
+
unitAmountCents: unitAmount,
|
|
211
|
+
totalAmountCents: lineTotal,
|
|
212
|
+
tierLabel: isGroupRate ? merged.groupRate : null,
|
|
213
|
+
isGroupRate,
|
|
162
214
|
});
|
|
163
|
-
|
|
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":"
|
|
1
|
+
{"version":3,"file":"travelers-section.d.ts","sourceRoot":"","sources":["../../src/components/travelers-section.tsx"],"names":[],"mappings":"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"}
|