@voyantjs/bookings-ui 0.52.3 → 0.52.4
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.
|
@@ -67,4 +67,24 @@ export interface OptionUnitsStepperSectionProps {
|
|
|
67
67
|
* would 409 at insert time.
|
|
68
68
|
*/
|
|
69
69
|
export declare function OptionUnitsStepperSection({ value, onChange, productId, slotId, optionId, enabled, onUnitsChange, labels, }: OptionUnitsStepperSectionProps): import("react/jsx-runtime").JSX.Element;
|
|
70
|
+
/**
|
|
71
|
+
* Returns the `optionId` the slot is bound to, derived from the first
|
|
72
|
+
* slot-availability row whose `optionUnitId` we can map to a known
|
|
73
|
+
* product option. Falls back to the caller's `fallbackOptionId` (the
|
|
74
|
+
* dialog's currently-selected option) when no rows resolve — that lets
|
|
75
|
+
* the existing `optionId` prop drive the previous-behavior path for
|
|
76
|
+
* unit pickers that haven't loaded yet.
|
|
77
|
+
*/
|
|
78
|
+
export declare function resolveSlotOptionId(slotRows: ReadonlyArray<{
|
|
79
|
+
optionUnitId: string;
|
|
80
|
+
}>, optionByUnitId: ReadonlyMap<string, string>, fallbackOptionId: string | null): string | null;
|
|
81
|
+
/**
|
|
82
|
+
* Merges slot-bound per-unit availability with the product's option-unit
|
|
83
|
+
* catalog. Slot rows are authoritative for the slot's option (they carry
|
|
84
|
+
* real-time `remaining`); product-level rows fill in the other options
|
|
85
|
+
* the product offers so the operator can still pick mixes the slot isn't
|
|
86
|
+
* explicitly tracking. When the slot is product-level (no `option_id`)
|
|
87
|
+
* or hasn't loaded slot rows yet, the product-level rows cover everything.
|
|
88
|
+
*/
|
|
89
|
+
export declare function mergeStepperUnits(slotRows: ReadonlyArray<OptionUnitsStepperUnit>, productRows: ReadonlyArray<OptionUnitsStepperUnit>, slotOptionId: string | null, hasSlot: boolean): OptionUnitsStepperUnit[];
|
|
70
90
|
//# sourceMappingURL=option-units-stepper-section.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"option-units-stepper-section.d.ts","sourceRoot":"","sources":["../../src/components/option-units-stepper-section.tsx"],"names":[],"mappings":"AAgBA,iEAAiE;AACjE,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC;AAED,eAAO,MAAM,4BAA4B,EAAE,uBAA4C,CAAA;AAEvF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,yFAAyF;IACzF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,IAAI,CAAA;IAC/E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,8BAA8B;IAC7C,KAAK,EAAE,uBAAuB,CAAA;IAC9B,QAAQ,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAClD,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,EAAE,KAAK,IAAI,CAAA;IACzD,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,MAAM,EACN,QAAQ,EACR,OAAc,EACd,aAAa,EACb,MAAM,GACP,EAAE,8BAA8B,
|
|
1
|
+
{"version":3,"file":"option-units-stepper-section.d.ts","sourceRoot":"","sources":["../../src/components/option-units-stepper-section.tsx"],"names":[],"mappings":"AAgBA,iEAAiE;AACjE,MAAM,WAAW,uBAAuB;IACtC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC;AAED,eAAO,MAAM,4BAA4B,EAAE,uBAA4C,CAAA;AAEvF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,YAAY,EAAE,MAAM,CAAA;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,yFAAyF;IACzF,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,gEAAgE;IAChE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,0FAA0F;IAC1F,QAAQ,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,IAAI,CAAA;IAC/E,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,8BAA8B;IAC7C,KAAK,EAAE,uBAAuB,CAAA;IAC9B,QAAQ,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAA;IAClD,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,sBAAsB,EAAE,KAAK,IAAI,CAAA;IACzD,MAAM,CAAC,EAAE;QACP,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;QACjB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,SAAS,CAAC,EAAE,MAAM,CAAA;KACnB,CAAA;CACF;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,yBAAyB,CAAC,EACxC,KAAK,EACL,QAAQ,EACR,SAAS,EACT,MAAM,EACN,QAAQ,EACR,OAAc,EACd,aAAa,EACb,MAAM,GACP,EAAE,8BAA8B,2CA+MhC;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,aAAa,CAAC;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,EACjD,cAAc,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAC3C,gBAAgB,EAAE,MAAM,GAAG,IAAI,GAC9B,MAAM,GAAG,IAAI,CAMf;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,aAAa,CAAC,sBAAsB,CAAC,EAC/C,WAAW,EAAE,aAAa,CAAC,sBAAsB,CAAC,EAClD,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,OAAO,EAAE,OAAO,GACf,sBAAsB,EAAE,CAM1B"}
|
|
@@ -68,16 +68,36 @@ export function OptionUnitsStepperSection({ value, onChange, productId, slotId,
|
|
|
68
68
|
});
|
|
69
69
|
return rows;
|
|
70
70
|
}, [productOptions, optionUnitQueries]);
|
|
71
|
+
// optionUnitId → optionId lookup, derived from the product's own option
|
|
72
|
+
// catalog. The slot-availability endpoint only returns option_unit rows
|
|
73
|
+
// for the slot's bound option and doesn't stamp the option_id on each
|
|
74
|
+
// row, so we look it up from the units we already fetched per option.
|
|
75
|
+
const optionByUnitId = React.useMemo(() => {
|
|
76
|
+
const map = new Map();
|
|
77
|
+
productOptions.forEach((option, index) => {
|
|
78
|
+
const units = optionUnitQueries[index]?.data?.data ?? [];
|
|
79
|
+
for (const unit of units) {
|
|
80
|
+
map.set(unit.id, option.id);
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
return map;
|
|
84
|
+
}, [productOptions, optionUnitQueries]);
|
|
85
|
+
// The slot's bound option, derived from the first availability row.
|
|
86
|
+
// `null` when the slot is product-level (no option_id) — that path goes
|
|
87
|
+
// through the product-level fallback below.
|
|
88
|
+
const slotOptionId = React.useMemo(() => resolveSlotOptionId(availability.data?.data ?? [], optionByUnitId, optionId ?? null), [availability.data?.data, optionByUnitId, optionId]);
|
|
71
89
|
const availabilityUnitRows = React.useMemo(() => (availability.data?.data ?? []).map((unit) => ({
|
|
72
90
|
...unit,
|
|
73
|
-
optionId: optionId ?? null,
|
|
74
|
-
})), [availability.data?.data, optionId]);
|
|
75
|
-
// Slot-
|
|
76
|
-
//
|
|
77
|
-
// the
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
91
|
+
optionId: slotOptionId ?? optionId ?? null,
|
|
92
|
+
})), [availability.data?.data, slotOptionId, optionId]);
|
|
93
|
+
// Slot-bound per-unit availability stays authoritative for the slot's
|
|
94
|
+
// option (real-time `remaining` from active bookings). For *other*
|
|
95
|
+
// options the same product offers, fall back to the product-level
|
|
96
|
+
// option_units so the operator can still pick a DBL/TWN even when the
|
|
97
|
+
// slot is option-scoped to SGL. Product-level slots (no option_id) hit
|
|
98
|
+
// the no-slot-rows branch and use the product fallback for everything.
|
|
99
|
+
// See issue #960.
|
|
100
|
+
const units = React.useMemo(() => mergeStepperUnits(availabilityUnitRows, optionUnitRows, slotOptionId, Boolean(slotId)), [availabilityUnitRows, optionUnitRows, slotOptionId, slotId]);
|
|
81
101
|
React.useEffect(() => {
|
|
82
102
|
onUnitsChange?.(units);
|
|
83
103
|
}, [onUnitsChange, units]);
|
|
@@ -149,6 +169,37 @@ export function OptionUnitsStepperSection({ value, onChange, productId, slotId,
|
|
|
149
169
|
return (_jsxs("div", { className: "flex items-center gap-3 rounded-md border px-3 py-2", children: [_jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "text-sm font-medium", children: optionName }), _jsx("div", { className: "text-xs text-muted-foreground", children: remainingLabel })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => setQuantity(primary.optionUnitId, Math.max(0, qty - 1)), disabled: qty <= 0, "aria-label": `${merged.decreaseUnitPrefix} ${optionName}`, children: _jsx(Minus, { className: "h-3.5 w-3.5" }) }), _jsx("span", { className: "min-w-[1.5rem] text-center text-sm tabular-nums", children: qty }), _jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "h-7 w-7 p-0", onClick: () => setQuantity(primary.optionUnitId, qty + 1), disabled: atMax, "aria-label": `${merged.increaseUnitPrefix} ${optionName}`, children: _jsx(Plus, { className: "h-3.5 w-3.5" }) })] })] }, optionKey));
|
|
150
170
|
}) })] }));
|
|
151
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Returns the `optionId` the slot is bound to, derived from the first
|
|
174
|
+
* slot-availability row whose `optionUnitId` we can map to a known
|
|
175
|
+
* product option. Falls back to the caller's `fallbackOptionId` (the
|
|
176
|
+
* dialog's currently-selected option) when no rows resolve — that lets
|
|
177
|
+
* the existing `optionId` prop drive the previous-behavior path for
|
|
178
|
+
* unit pickers that haven't loaded yet.
|
|
179
|
+
*/
|
|
180
|
+
export function resolveSlotOptionId(slotRows, optionByUnitId, fallbackOptionId) {
|
|
181
|
+
for (const row of slotRows) {
|
|
182
|
+
const resolved = optionByUnitId.get(row.optionUnitId);
|
|
183
|
+
if (resolved)
|
|
184
|
+
return resolved;
|
|
185
|
+
}
|
|
186
|
+
return fallbackOptionId;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Merges slot-bound per-unit availability with the product's option-unit
|
|
190
|
+
* catalog. Slot rows are authoritative for the slot's option (they carry
|
|
191
|
+
* real-time `remaining`); product-level rows fill in the other options
|
|
192
|
+
* the product offers so the operator can still pick mixes the slot isn't
|
|
193
|
+
* explicitly tracking. When the slot is product-level (no `option_id`)
|
|
194
|
+
* or hasn't loaded slot rows yet, the product-level rows cover everything.
|
|
195
|
+
*/
|
|
196
|
+
export function mergeStepperUnits(slotRows, productRows, slotOptionId, hasSlot) {
|
|
197
|
+
if (!hasSlot || slotRows.length === 0 || !slotOptionId) {
|
|
198
|
+
return [...productRows];
|
|
199
|
+
}
|
|
200
|
+
const otherOptionRows = productRows.filter((row) => row.optionId !== slotOptionId);
|
|
201
|
+
return [...slotRows, ...otherOptionRows];
|
|
202
|
+
}
|
|
152
203
|
function isAdultUnit(unit) {
|
|
153
204
|
// The seed creates ADULT / CHILD / SENIOR unit codes; the stepper
|
|
154
205
|
// unit object doesn't carry the code, so fall back to name-matching
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voyantjs/bookings-ui",
|
|
3
|
-
"version": "0.52.
|
|
3
|
+
"version": "0.52.4",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -51,21 +51,21 @@
|
|
|
51
51
|
"react-dom": "^19.0.0",
|
|
52
52
|
"react-hook-form": "^7.60.0",
|
|
53
53
|
"zod": "^4.3.6",
|
|
54
|
-
"@voyantjs/availability-react": "0.52.
|
|
55
|
-
"@voyantjs/bookings-react": "0.52.
|
|
56
|
-
"@voyantjs/catalog": "0.52.
|
|
57
|
-
"@voyantjs/catalog-react": "0.52.
|
|
58
|
-
"@voyantjs/crm-react": "0.52.
|
|
59
|
-
"@voyantjs/crm-ui": "0.52.
|
|
60
|
-
"@voyantjs/finance-react": "0.52.
|
|
61
|
-
"@voyantjs/identity-react": "0.52.
|
|
62
|
-
"@voyantjs/legal-react": "0.52.
|
|
63
|
-
"@voyantjs/products-react": "0.52.
|
|
64
|
-
"@voyantjs/suppliers-react": "0.52.
|
|
65
|
-
"@voyantjs/ui": "0.52.
|
|
54
|
+
"@voyantjs/availability-react": "0.52.4",
|
|
55
|
+
"@voyantjs/bookings-react": "0.52.4",
|
|
56
|
+
"@voyantjs/catalog": "0.52.4",
|
|
57
|
+
"@voyantjs/catalog-react": "0.52.4",
|
|
58
|
+
"@voyantjs/crm-react": "0.52.4",
|
|
59
|
+
"@voyantjs/crm-ui": "0.52.4",
|
|
60
|
+
"@voyantjs/finance-react": "0.52.4",
|
|
61
|
+
"@voyantjs/identity-react": "0.52.4",
|
|
62
|
+
"@voyantjs/legal-react": "0.52.4",
|
|
63
|
+
"@voyantjs/products-react": "0.52.4",
|
|
64
|
+
"@voyantjs/suppliers-react": "0.52.4",
|
|
65
|
+
"@voyantjs/ui": "0.52.4"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@voyantjs/i18n": "0.52.
|
|
68
|
+
"@voyantjs/i18n": "0.52.4"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@tanstack/react-query": "^5.96.2",
|
|
@@ -78,19 +78,19 @@
|
|
|
78
78
|
"typescript": "^6.0.2",
|
|
79
79
|
"vitest": "^4.1.2",
|
|
80
80
|
"zod": "^4.3.6",
|
|
81
|
-
"@voyantjs/availability-react": "0.52.
|
|
82
|
-
"@voyantjs/bookings-react": "0.52.
|
|
83
|
-
"@voyantjs/catalog": "0.52.
|
|
84
|
-
"@voyantjs/catalog-react": "0.52.
|
|
85
|
-
"@voyantjs/crm-react": "0.52.
|
|
86
|
-
"@voyantjs/crm-ui": "0.52.
|
|
87
|
-
"@voyantjs/finance-react": "0.52.
|
|
88
|
-
"@voyantjs/identity-react": "0.52.
|
|
89
|
-
"@voyantjs/legal-react": "0.52.
|
|
90
|
-
"@voyantjs/products-react": "0.52.
|
|
91
|
-
"@voyantjs/suppliers-react": "0.52.
|
|
81
|
+
"@voyantjs/availability-react": "0.52.4",
|
|
82
|
+
"@voyantjs/bookings-react": "0.52.4",
|
|
83
|
+
"@voyantjs/catalog": "0.52.4",
|
|
84
|
+
"@voyantjs/catalog-react": "0.52.4",
|
|
85
|
+
"@voyantjs/crm-react": "0.52.4",
|
|
86
|
+
"@voyantjs/crm-ui": "0.52.4",
|
|
87
|
+
"@voyantjs/finance-react": "0.52.4",
|
|
88
|
+
"@voyantjs/identity-react": "0.52.4",
|
|
89
|
+
"@voyantjs/legal-react": "0.52.4",
|
|
90
|
+
"@voyantjs/products-react": "0.52.4",
|
|
91
|
+
"@voyantjs/suppliers-react": "0.52.4",
|
|
92
92
|
"@voyantjs/voyant-typescript-config": "0.1.0",
|
|
93
|
-
"@voyantjs/ui": "0.52.
|
|
93
|
+
"@voyantjs/ui": "0.52.4"
|
|
94
94
|
},
|
|
95
95
|
"files": [
|
|
96
96
|
"dist",
|