@ticketboothapp/booking 1.2.103 → 1.2.105
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/package.json +1 -1
- package/src/components/booking/AdminChangeBookingFlow.tsx +80 -49
- package/src/components/booking/ChangeBookingFlow.tsx +79 -45
- package/src/components/booking/NewBookingFlow.tsx +117 -48
- package/src/components/booking/PickupLocationSelector.tsx +47 -18
- package/src/components/booking/PrivateShuttleBookingFlow.tsx +68 -34
- package/src/contexts/AvailabilitiesCacheContext.tsx +4 -2
- package/src/lib/booking/pricing.ts +5 -1
- package/src/lib/booking-api.ts +2 -0
package/package.json
CHANGED
|
@@ -333,18 +333,30 @@ function findMergedAvailabilityForSelection(
|
|
|
333
333
|
selected: Availability | null
|
|
334
334
|
): Availability | undefined {
|
|
335
335
|
if (!selected) return undefined;
|
|
336
|
-
const optId = selected
|
|
336
|
+
const optId = getAvailabilityOptionId(selected) || undefined;
|
|
337
337
|
const dt = selected.dateTime;
|
|
338
338
|
const availId = selected.availabilityId?.trim();
|
|
339
339
|
if (availId && optId) {
|
|
340
340
|
const exact = merged.find(
|
|
341
341
|
(a) =>
|
|
342
342
|
a.availabilityId === availId &&
|
|
343
|
-
(a
|
|
343
|
+
getAvailabilityOptionId(a) === optId
|
|
344
344
|
);
|
|
345
345
|
if (exact) return exact;
|
|
346
346
|
}
|
|
347
|
-
return merged.find((a) => a.dateTime === dt && a
|
|
347
|
+
return merged.find((a) => a.dateTime === dt && getAvailabilityOptionId(a) === optId);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function getAvailabilityOptionId(availability: Availability, fallbackOptionId?: string): string {
|
|
351
|
+
const productOptionId = availability.productOptionId?.trim();
|
|
352
|
+
if (productOptionId) return productOptionId;
|
|
353
|
+
const productId = availability.productId?.trim();
|
|
354
|
+
if (productId?.startsWith('po_')) return productId;
|
|
355
|
+
return fallbackOptionId?.trim() ?? '';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function getAvailabilityCacheKey(availability: Availability): string {
|
|
359
|
+
return `${availability.dateTime}-${getAvailabilityOptionId(availability)}`;
|
|
348
360
|
}
|
|
349
361
|
|
|
350
362
|
/** Ticket rates for one availability row — mirrors main cart [pricing] useMemo so baseline slots match BE. */
|
|
@@ -358,7 +370,7 @@ function buildPricingFromAvailability(
|
|
|
358
370
|
isSimplifiedPricingView: boolean,
|
|
359
371
|
) {
|
|
360
372
|
if (!selectedAvailability || !pricingConfig) return [];
|
|
361
|
-
const optionId = selectedAvailability
|
|
373
|
+
const optionId = getAvailabilityOptionId(selectedAvailability);
|
|
362
374
|
const selectedOption = activeOptions.find((opt) => opt.optionId === optionId);
|
|
363
375
|
const precomputed = optionId ? precomputedPricesByOption?.[optionId] : undefined;
|
|
364
376
|
const rateToDisplayPrice = (backendInDisplayCurrency: number) =>
|
|
@@ -376,11 +388,17 @@ function buildPricingFromAvailability(
|
|
|
376
388
|
appliedAdjustments: Array<{ type: string; id: string; name: string; changeByCurrency?: Record<string, number> }>,
|
|
377
389
|
) => {
|
|
378
390
|
const basePriceCAD = selectedOption?.pricing?.[category.toUpperCase()] ?? 0;
|
|
391
|
+
const hasOptionScopedPrice = baseInDisplayCurrency > 0;
|
|
392
|
+
const priceCADForBreakdown =
|
|
393
|
+
hasOptionScopedPrice && currency === 'CAD' ? baseInDisplayCurrency : backendPriceCAD;
|
|
394
|
+
const displayCurrencyPriceForFallback = hasOptionScopedPrice
|
|
395
|
+
? baseInDisplayCurrency
|
|
396
|
+
: backendInDisplayCurrency;
|
|
379
397
|
const isPublicMode = isSimplifiedPricingView;
|
|
380
398
|
const breakdown = computePriceBreakdown(
|
|
381
399
|
pricingConfig,
|
|
382
400
|
currency,
|
|
383
|
-
|
|
401
|
+
priceCADForBreakdown,
|
|
384
402
|
basePriceCAD,
|
|
385
403
|
hasFees,
|
|
386
404
|
appliedAdjustments,
|
|
@@ -388,8 +406,8 @@ function buildPricingFromAvailability(
|
|
|
388
406
|
baseInDisplayCurrency,
|
|
389
407
|
isPublicMode,
|
|
390
408
|
);
|
|
391
|
-
const price = breakdown?.finalPrice ?? rateToDisplayPrice(
|
|
392
|
-
return { category, baseInDisplayCurrency, appliedAdjustments, price, priceCAD:
|
|
409
|
+
const price = breakdown?.finalPrice ?? rateToDisplayPrice(displayCurrencyPriceForFallback);
|
|
410
|
+
return { category, baseInDisplayCurrency, appliedAdjustments, price, priceCAD: priceCADForBreakdown };
|
|
393
411
|
};
|
|
394
412
|
return (
|
|
395
413
|
selectedAvailability.rates?.map((rate) => {
|
|
@@ -974,6 +992,14 @@ export function AdminChangeBookingFlow({
|
|
|
974
992
|
return '';
|
|
975
993
|
}
|
|
976
994
|
});
|
|
995
|
+
const selectedDateRangeExtensionKey = useMemo(() => {
|
|
996
|
+
if (!visibleRange || !selectedDate) return '';
|
|
997
|
+
try {
|
|
998
|
+
return isAfter(parseISO(selectedDate), visibleRange.end) ? selectedDate : '';
|
|
999
|
+
} catch {
|
|
1000
|
+
return '';
|
|
1001
|
+
}
|
|
1002
|
+
}, [selectedDate, visibleRange]);
|
|
977
1003
|
const [isItinerarySticky, setIsItinerarySticky] = useState(false);
|
|
978
1004
|
const isItineraryStickyRef = useRef(false);
|
|
979
1005
|
const [isMobile, setIsMobile] = useState(false);
|
|
@@ -1273,10 +1299,10 @@ export function AdminChangeBookingFlow({
|
|
|
1273
1299
|
let mergedOut: Availability[] = [];
|
|
1274
1300
|
setAvailabilities((prev) => {
|
|
1275
1301
|
const existingMap = new Map(
|
|
1276
|
-
prev.map((avail) => [
|
|
1302
|
+
prev.map((avail) => [getAvailabilityCacheKey(avail), avail])
|
|
1277
1303
|
);
|
|
1278
1304
|
allFetchedAvailabilities.forEach((avail) => {
|
|
1279
|
-
existingMap.set(
|
|
1305
|
+
existingMap.set(getAvailabilityCacheKey(avail), avail);
|
|
1280
1306
|
});
|
|
1281
1307
|
mergedOut = Array.from(existingMap.values());
|
|
1282
1308
|
return mergedOut;
|
|
@@ -1303,16 +1329,17 @@ export function AdminChangeBookingFlow({
|
|
|
1303
1329
|
activeOptionIdsKey,
|
|
1304
1330
|
appliedPromoCode,
|
|
1305
1331
|
pricingProfileIdForAvailabilities,
|
|
1332
|
+
cancellationPolicyProfileIdForAvailabilities,
|
|
1306
1333
|
)
|
|
1307
1334
|
: null;
|
|
1308
1335
|
if (cacheKey && availabilitiesCache) {
|
|
1309
1336
|
const existingCache = availabilitiesCache.get(cacheKey);
|
|
1310
1337
|
const existingAvailabilities = existingCache?.availabilities ?? [];
|
|
1311
1338
|
const mergedAvailabilitiesMap = new Map(
|
|
1312
|
-
existingAvailabilities.map((a) => [
|
|
1339
|
+
existingAvailabilities.map((a) => [getAvailabilityCacheKey(a), a])
|
|
1313
1340
|
);
|
|
1314
1341
|
allFetchedAvailabilities.forEach((a) => {
|
|
1315
|
-
mergedAvailabilitiesMap.set(
|
|
1342
|
+
mergedAvailabilitiesMap.set(getAvailabilityCacheKey(a), a);
|
|
1316
1343
|
});
|
|
1317
1344
|
const mergedPrecomputed = { ...(existingCache?.precomputedPricesByOption ?? {}) };
|
|
1318
1345
|
results.forEach((r) => {
|
|
@@ -1427,11 +1454,11 @@ export function AdminChangeBookingFlow({
|
|
|
1427
1454
|
? endOfLatestDay
|
|
1428
1455
|
: visibleRange.end;
|
|
1429
1456
|
|
|
1430
|
-
//
|
|
1431
|
-
//
|
|
1432
|
-
if (
|
|
1457
|
+
// Include an externally seeded selected date only when it falls outside the current calendar window.
|
|
1458
|
+
// Normal date clicks are already inside visibleRange and should not re-fetch the broad availability range.
|
|
1459
|
+
if (selectedDateRangeExtensionKey) {
|
|
1433
1460
|
try {
|
|
1434
|
-
const selectedDateObj = parseISO(
|
|
1461
|
+
const selectedDateObj = parseISO(selectedDateRangeExtensionKey);
|
|
1435
1462
|
if (isAfter(selectedDateObj, clampedEnd)) {
|
|
1436
1463
|
clampedEnd = selectedDateObj;
|
|
1437
1464
|
}
|
|
@@ -1447,6 +1474,7 @@ export function AdminChangeBookingFlow({
|
|
|
1447
1474
|
activeOptionIdsKey,
|
|
1448
1475
|
appliedPromoCode,
|
|
1449
1476
|
pricingProfileIdForAvailabilities,
|
|
1477
|
+
cancellationPolicyProfileIdForAvailabilities,
|
|
1450
1478
|
)
|
|
1451
1479
|
: null;
|
|
1452
1480
|
const cached = cacheKey ? availabilitiesCache!.get(cacheKey) : undefined;
|
|
@@ -1588,12 +1616,12 @@ export function AdminChangeBookingFlow({
|
|
|
1588
1616
|
// Merge with existing availabilities (avoid duplicates by dateTime + productOptionId)
|
|
1589
1617
|
setAvailabilities(prev => {
|
|
1590
1618
|
const existingMap = new Map(
|
|
1591
|
-
prev.map(avail => [
|
|
1619
|
+
prev.map(avail => [getAvailabilityCacheKey(avail), avail])
|
|
1592
1620
|
);
|
|
1593
1621
|
|
|
1594
1622
|
// Merge new availabilities - update existing ones or add new ones
|
|
1595
1623
|
allFetchedAvailabilities.forEach(avail => {
|
|
1596
|
-
const key =
|
|
1624
|
+
const key = getAvailabilityCacheKey(avail);
|
|
1597
1625
|
// Always update to get latest data (vacancies, prices, etc.)
|
|
1598
1626
|
existingMap.set(key, avail);
|
|
1599
1627
|
});
|
|
@@ -1622,10 +1650,10 @@ export function AdminChangeBookingFlow({
|
|
|
1622
1650
|
const existingCache = availabilitiesCache.get(cacheKey);
|
|
1623
1651
|
const existingAvailabilities = existingCache?.availabilities ?? [];
|
|
1624
1652
|
const mergedAvailabilitiesMap = new Map(
|
|
1625
|
-
existingAvailabilities.map((a) => [
|
|
1653
|
+
existingAvailabilities.map((a) => [getAvailabilityCacheKey(a), a])
|
|
1626
1654
|
);
|
|
1627
1655
|
allFetchedAvailabilities.forEach((a) => {
|
|
1628
|
-
mergedAvailabilitiesMap.set(
|
|
1656
|
+
mergedAvailabilitiesMap.set(getAvailabilityCacheKey(a), a);
|
|
1629
1657
|
});
|
|
1630
1658
|
const mergedPrecomputed = { ...(existingCache?.precomputedPricesByOption ?? {}) };
|
|
1631
1659
|
results.forEach((r) => {
|
|
@@ -1673,7 +1701,7 @@ export function AdminChangeBookingFlow({
|
|
|
1673
1701
|
activeOptionIdsKey,
|
|
1674
1702
|
isPrivateShuttle,
|
|
1675
1703
|
companyTimezone,
|
|
1676
|
-
|
|
1704
|
+
selectedDateRangeExtensionKey,
|
|
1677
1705
|
appliedPromoCode,
|
|
1678
1706
|
pricingProfileIdForAvailabilities,
|
|
1679
1707
|
cancellationPolicyProfileIdForAvailabilities,
|
|
@@ -1871,7 +1899,7 @@ export function AdminChangeBookingFlow({
|
|
|
1871
1899
|
timesForSelectedDate
|
|
1872
1900
|
.map(
|
|
1873
1901
|
(avail) =>
|
|
1874
|
-
`${avail.dateTime}|${avail
|
|
1902
|
+
`${avail.dateTime}|${getAvailabilityOptionId(avail)}|${avail.vacancies ?? 0}`,
|
|
1875
1903
|
)
|
|
1876
1904
|
.join('||'),
|
|
1877
1905
|
[timesForSelectedDate],
|
|
@@ -2305,7 +2333,9 @@ export function AdminChangeBookingFlow({
|
|
|
2305
2333
|
const calculateStaySummary = useCallback((returnDateTime: Date): string | null => {
|
|
2306
2334
|
if (!selectedAvailability) return null;
|
|
2307
2335
|
|
|
2308
|
-
const availabilityProductOptionId =
|
|
2336
|
+
const availabilityProductOptionId =
|
|
2337
|
+
getAvailabilityOptionId(selectedAvailability) ||
|
|
2338
|
+
(activeOptions.length === 1 ? activeOptions[0]?.optionId : undefined);
|
|
2309
2339
|
const selectedOption = activeOptions.find(opt => opt.optionId === availabilityProductOptionId);
|
|
2310
2340
|
if (!selectedOption) return null;
|
|
2311
2341
|
|
|
@@ -2381,7 +2411,9 @@ export function AdminChangeBookingFlow({
|
|
|
2381
2411
|
// Helper function to compute itinerary display for storage (returns same shape as "Your Itinerary" box)
|
|
2382
2412
|
const computeItineraryDisplay = useCallback((): ItineraryDisplayStep[] | null => {
|
|
2383
2413
|
if (!selectedAvailability) return null;
|
|
2384
|
-
const availabilityProductOptionId =
|
|
2414
|
+
const availabilityProductOptionId =
|
|
2415
|
+
getAvailabilityOptionId(selectedAvailability) ||
|
|
2416
|
+
(activeOptions.length === 1 ? activeOptions[0]?.optionId : undefined);
|
|
2385
2417
|
const selectedOption = activeOptions.find(opt => opt.optionId === availabilityProductOptionId);
|
|
2386
2418
|
if (!selectedOption) return null;
|
|
2387
2419
|
const tourStartTime = parseISO(selectedAvailability.dateTime);
|
|
@@ -2620,13 +2652,13 @@ export function AdminChangeBookingFlow({
|
|
|
2620
2652
|
const selectedMs = parseAvailabilityDateTime(selectedAvailability.dateTime).getTime();
|
|
2621
2653
|
const initialMs = parseAvailabilityDateTime(initialValues.dateTime).getTime();
|
|
2622
2654
|
if (selectedMs !== initialMs) return false;
|
|
2623
|
-
const selectedOpt = normalizeProductOptionIdForChangeFlow(selectedAvailability
|
|
2655
|
+
const selectedOpt = normalizeProductOptionIdForChangeFlow(getAvailabilityOptionId(selectedAvailability));
|
|
2624
2656
|
const initialOpt = changeFlowResolvedInitialProductOptionId;
|
|
2625
2657
|
if (selectedOpt != null && initialOpt != null) {
|
|
2626
2658
|
return selectedOpt === initialOpt;
|
|
2627
2659
|
}
|
|
2628
2660
|
const legacyInitial = initialValues.productOptionId?.trim() || null;
|
|
2629
|
-
const legacySelected = selectedAvailability
|
|
2661
|
+
const legacySelected = getAvailabilityOptionId(selectedAvailability) || null;
|
|
2630
2662
|
return !legacySelected || !legacyInitial || legacySelected === legacyInitial;
|
|
2631
2663
|
}, [
|
|
2632
2664
|
selectedAvailability,
|
|
@@ -2653,13 +2685,13 @@ export function AdminChangeBookingFlow({
|
|
|
2653
2685
|
return false;
|
|
2654
2686
|
}
|
|
2655
2687
|
if (!selectedDate || selectedDate !== initialDay) return false;
|
|
2656
|
-
const selectedOpt = normalizeProductOptionIdForChangeFlow(selectedAvailability
|
|
2688
|
+
const selectedOpt = normalizeProductOptionIdForChangeFlow(getAvailabilityOptionId(selectedAvailability));
|
|
2657
2689
|
const initialOpt = changeFlowResolvedInitialProductOptionId;
|
|
2658
2690
|
if (selectedOpt != null && initialOpt != null) {
|
|
2659
2691
|
return selectedOpt === initialOpt;
|
|
2660
2692
|
}
|
|
2661
2693
|
const legacyInitial = initialValues.productOptionId?.trim() || null;
|
|
2662
|
-
const legacySelected = selectedAvailability
|
|
2694
|
+
const legacySelected = getAvailabilityOptionId(selectedAvailability) || null;
|
|
2663
2695
|
return Boolean(legacySelected && legacyInitial && legacySelected === legacyInitial);
|
|
2664
2696
|
}, [
|
|
2665
2697
|
selectedDate,
|
|
@@ -2856,7 +2888,9 @@ export function AdminChangeBookingFlow({
|
|
|
2856
2888
|
// Price breakdown: mid-layer returns line items (base + one per rule/deal). UI renders each line; rate in brackets when used.
|
|
2857
2889
|
const getPriceBreakdown = useCallback((category: string, priceCAD: number, baseInDisplayCurrency: number | undefined, appliedAdjustments: Array<{ type: string; id: string; name: string; changeByCurrency?: Record<string, number> }> = []): PriceBreakdownData | null => {
|
|
2858
2890
|
if (!pricingConfig) return null;
|
|
2859
|
-
const selectedOption = activeOptions.find(
|
|
2891
|
+
const selectedOption = activeOptions.find(
|
|
2892
|
+
opt => opt.optionId === (selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : '')
|
|
2893
|
+
);
|
|
2860
2894
|
const basePriceCAD = selectedOption?.pricing?.[category.toUpperCase()] ?? 0;
|
|
2861
2895
|
const isPublicMode = isSimplifiedPricingView;
|
|
2862
2896
|
return computePriceBreakdown(
|
|
@@ -3332,7 +3366,7 @@ export function AdminChangeBookingFlow({
|
|
|
3332
3366
|
if (!appliedPromoCode || !selectedAvailability || totalQuantity === 0) return '';
|
|
3333
3367
|
const companyId = product.companyId ?? env.COMPANY_ID;
|
|
3334
3368
|
if (!companyId) return '';
|
|
3335
|
-
const optionId = selectedAvailability
|
|
3369
|
+
const optionId = getAvailabilityOptionId(selectedAvailability);
|
|
3336
3370
|
if (!optionId || !quantitiesSignature) return '';
|
|
3337
3371
|
return [
|
|
3338
3372
|
appliedPromoCode,
|
|
@@ -3349,7 +3383,7 @@ export function AdminChangeBookingFlow({
|
|
|
3349
3383
|
}, [
|
|
3350
3384
|
appliedPromoCode,
|
|
3351
3385
|
selectedAvailability?.dateTime,
|
|
3352
|
-
selectedAvailability
|
|
3386
|
+
selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : '',
|
|
3353
3387
|
selectedAvailability?.availabilityId,
|
|
3354
3388
|
totalQuantity,
|
|
3355
3389
|
product.companyId,
|
|
@@ -3392,7 +3426,7 @@ export function AdminChangeBookingFlow({
|
|
|
3392
3426
|
} = promoDiscountParamsRef.current;
|
|
3393
3427
|
if (!code || !sel) return;
|
|
3394
3428
|
const companyId = product.companyId ?? env.COMPANY_ID;
|
|
3395
|
-
const optionId = sel
|
|
3429
|
+
const optionId = getAvailabilityOptionId(sel);
|
|
3396
3430
|
if (!companyId || !optionId) return;
|
|
3397
3431
|
const items = lines.map((l) => ({ category: l.category, qty: l.qty }));
|
|
3398
3432
|
if (items.length === 0) return;
|
|
@@ -3792,7 +3826,7 @@ export function AdminChangeBookingFlow({
|
|
|
3792
3826
|
const initialOpt =
|
|
3793
3827
|
changeFlowResolvedInitialProductOptionId ??
|
|
3794
3828
|
(initialValues.productOptionId?.trim() || null);
|
|
3795
|
-
const selectedOpt = normalizeProductOptionIdForChangeFlow(selectedAvailability
|
|
3829
|
+
const selectedOpt = normalizeProductOptionIdForChangeFlow(getAvailabilityOptionId(selectedAvailability));
|
|
3796
3830
|
const optionChanged = Boolean(
|
|
3797
3831
|
selectedOpt != null && initialOpt != null && initialOpt !== selectedOpt
|
|
3798
3832
|
);
|
|
@@ -3923,7 +3957,7 @@ export function AdminChangeBookingFlow({
|
|
|
3923
3957
|
bookingReference: initialValues?.bookingReference?.trim() ?? '',
|
|
3924
3958
|
lastName: lastName.trim().toLowerCase(),
|
|
3925
3959
|
parentProductId: product.productId,
|
|
3926
|
-
optionId: selectedAvailability
|
|
3960
|
+
optionId: selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : '',
|
|
3927
3961
|
dateTime: selectedAvailability?.dateTime ?? '',
|
|
3928
3962
|
availabilityId: selectedAvailability?.availabilityId ?? null,
|
|
3929
3963
|
pickupLocationId: pickupLocationId ?? null,
|
|
@@ -3938,7 +3972,7 @@ export function AdminChangeBookingFlow({
|
|
|
3938
3972
|
initialValues?.bookingReference,
|
|
3939
3973
|
lastName,
|
|
3940
3974
|
product.productId,
|
|
3941
|
-
selectedAvailability
|
|
3975
|
+
selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : '',
|
|
3942
3976
|
selectedAvailability?.dateTime,
|
|
3943
3977
|
selectedAvailability?.availabilityId,
|
|
3944
3978
|
activeOptions,
|
|
@@ -4496,8 +4530,7 @@ export function AdminChangeBookingFlow({
|
|
|
4496
4530
|
return;
|
|
4497
4531
|
}
|
|
4498
4532
|
|
|
4499
|
-
const optionId =
|
|
4500
|
-
selectedAvailability.productOptionId?.trim() || activeOptions[0]?.optionId;
|
|
4533
|
+
const optionId = getAvailabilityOptionId(selectedAvailability);
|
|
4501
4534
|
if (!optionId) {
|
|
4502
4535
|
setLatestChangeQuote(null);
|
|
4503
4536
|
setChangeQuoteLoading(false);
|
|
@@ -4595,7 +4628,7 @@ export function AdminChangeBookingFlow({
|
|
|
4595
4628
|
hasChangeSelection,
|
|
4596
4629
|
selectedAvailability,
|
|
4597
4630
|
selectedAvailability?.dateTime,
|
|
4598
|
-
selectedAvailability
|
|
4631
|
+
selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : '',
|
|
4599
4632
|
initialValues?.bookingReference,
|
|
4600
4633
|
lastName,
|
|
4601
4634
|
missingRequiredReturnSelection,
|
|
@@ -4661,7 +4694,7 @@ export function AdminChangeBookingFlow({
|
|
|
4661
4694
|
|
|
4662
4695
|
const mostPopularOption = activeOptions.find(opt => opt.mostPopular);
|
|
4663
4696
|
const candidate = mostPopularOption
|
|
4664
|
-
? timesForSelectedDate.find(avail => avail
|
|
4697
|
+
? timesForSelectedDate.find(avail => getAvailabilityOptionId(avail) === mostPopularOption.optionId && avail.vacancies > 0)
|
|
4665
4698
|
: null;
|
|
4666
4699
|
const fallback = timesForSelectedDate.find(avail => avail.vacancies > 0);
|
|
4667
4700
|
const toSelect = candidate ?? fallback;
|
|
@@ -4687,9 +4720,9 @@ export function AdminChangeBookingFlow({
|
|
|
4687
4720
|
useEffect(() => {
|
|
4688
4721
|
if (selectedAvailability && availabilities.length > 0) {
|
|
4689
4722
|
const updatedAvailability = availabilities.find(
|
|
4690
|
-
|
|
4723
|
+
avail =>
|
|
4691
4724
|
avail.dateTime === selectedAvailability.dateTime &&
|
|
4692
|
-
avail
|
|
4725
|
+
getAvailabilityOptionId(avail) === getAvailabilityOptionId(selectedAvailability)
|
|
4693
4726
|
);
|
|
4694
4727
|
if (updatedAvailability) {
|
|
4695
4728
|
setSelectedAvailability(updatedAvailability);
|
|
@@ -4720,7 +4753,7 @@ export function AdminChangeBookingFlow({
|
|
|
4720
4753
|
setImplicitReturnBaselineId(null);
|
|
4721
4754
|
}, [
|
|
4722
4755
|
selectedAvailability?.dateTime,
|
|
4723
|
-
selectedAvailability
|
|
4756
|
+
selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : '',
|
|
4724
4757
|
initialValues?.returnAvailabilityId,
|
|
4725
4758
|
initialValues?.returnDateTime,
|
|
4726
4759
|
]);
|
|
@@ -4808,7 +4841,7 @@ export function AdminChangeBookingFlow({
|
|
|
4808
4841
|
}, [selectedAvailability?.returnOptions, selectedReturnOption]);
|
|
4809
4842
|
|
|
4810
4843
|
// Fetch add-ons when availability (product option) is selected; clear selections when option changes
|
|
4811
|
-
const availabilityProductOptionId = selectedAvailability
|
|
4844
|
+
const availabilityProductOptionId = selectedAvailability ? getAvailabilityOptionId(selectedAvailability) || null : null;
|
|
4812
4845
|
const prevAvailabilityProductOptionIdRef = useRef<string | null>(null);
|
|
4813
4846
|
useEffect(() => {
|
|
4814
4847
|
if (!availabilityProductOptionId || !product.companyId) {
|
|
@@ -4828,7 +4861,7 @@ export function AdminChangeBookingFlow({
|
|
|
4828
4861
|
if (date === selectedDate) return;
|
|
4829
4862
|
if (selectedAvailability) {
|
|
4830
4863
|
changeFlowOutboundAnchorRef.current = {
|
|
4831
|
-
productOptionId: selectedAvailability
|
|
4864
|
+
productOptionId: getAvailabilityOptionId(selectedAvailability) || null,
|
|
4832
4865
|
minutesFromMidnight: getMinutesFromMidnightInTimezone(
|
|
4833
4866
|
selectedAvailability.dateTime,
|
|
4834
4867
|
companyTimezone
|
|
@@ -4965,8 +4998,8 @@ export function AdminChangeBookingFlow({
|
|
|
4965
4998
|
[selectedAvailability]
|
|
4966
4999
|
);
|
|
4967
5000
|
const selectedAvailabilityKey = useMemo(
|
|
4968
|
-
() => `${selectedAvailability?.dateTime ?? ''}::${selectedAvailability
|
|
4969
|
-
[selectedAvailability
|
|
5001
|
+
() => `${selectedAvailability?.dateTime ?? ''}::${selectedAvailability ? getAvailabilityOptionId(selectedAvailability) : ''}`,
|
|
5002
|
+
[selectedAvailability]
|
|
4970
5003
|
);
|
|
4971
5004
|
// Remember where promo was successfully applied to avoid self-clearing on same selection.
|
|
4972
5005
|
const promoAppliedSelectionKeyRef = useRef<string | null>(null);
|
|
@@ -5121,8 +5154,7 @@ export function AdminChangeBookingFlow({
|
|
|
5121
5154
|
.map(([category, count]) => ({ category, count }));
|
|
5122
5155
|
|
|
5123
5156
|
// Get the productOptionId from the selected availability (we tagged it when fetching)
|
|
5124
|
-
const availabilityProductOptionId = selectedAvailability
|
|
5125
|
-
|| activeOptions[0]?.optionId;
|
|
5157
|
+
const availabilityProductOptionId = getAvailabilityOptionId(selectedAvailability);
|
|
5126
5158
|
|
|
5127
5159
|
if (!availabilityProductOptionId) {
|
|
5128
5160
|
setError('No product option selected');
|
|
@@ -5657,8 +5689,7 @@ export function AdminChangeBookingFlow({
|
|
|
5657
5689
|
.map(([category, count]) => ({ category, count }));
|
|
5658
5690
|
const availabilityProductOptionId =
|
|
5659
5691
|
adminChoiceData.availabilityProductOptionId ||
|
|
5660
|
-
selectedAvailability
|
|
5661
|
-
activeOptions[0]?.optionId;
|
|
5692
|
+
getAvailabilityOptionId(selectedAvailability);
|
|
5662
5693
|
if (!availabilityProductOptionId) {
|
|
5663
5694
|
setError('No product option selected');
|
|
5664
5695
|
setLoading(false);
|