@wix/headless-restaurants-olo 0.0.30 → 0.0.32
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/cjs/dist/react/ClickableItem.d.ts +18 -3
- package/cjs/dist/react/ClickableItem.js +16 -9
- package/cjs/dist/react/FulfillmentDetails.d.ts +233 -0
- package/cjs/dist/react/FulfillmentDetails.js +255 -0
- package/cjs/dist/react/ItemDetails.d.ts +233 -36
- package/cjs/dist/react/ItemDetails.js +208 -0
- package/cjs/dist/react/ModifierGroup.js +0 -3
- package/cjs/dist/react/OLO.d.ts +5 -160
- package/cjs/dist/react/OLO.js +6 -122
- package/cjs/dist/react/OLOMenus.d.ts +8 -2
- package/cjs/dist/react/OLOMenus.js +3 -4
- package/cjs/dist/react/Settings.d.ts +176 -48
- package/cjs/dist/react/Settings.js +276 -26
- package/cjs/dist/react/core/ClickableItem.d.ts +12 -5
- package/cjs/dist/react/core/ClickableItem.js +13 -14
- package/cjs/dist/react/core/FulfillmentDetails.d.ts +78 -0
- package/cjs/dist/react/core/FulfillmentDetails.js +177 -0
- package/cjs/dist/react/core/ItemDetails.js +2 -4
- package/cjs/dist/react/core/OLO.d.ts +6 -74
- package/cjs/dist/react/core/OLO.js +5 -44
- package/cjs/dist/react/core/OLOMenus.d.ts +1 -1
- package/cjs/dist/react/core/OLOMenus.js +2 -3
- package/cjs/dist/react/core/Settings.d.ts +138 -22
- package/cjs/dist/react/core/Settings.js +157 -34
- package/cjs/dist/react/core/index.d.ts +1 -0
- package/cjs/dist/react/core/index.js +1 -0
- package/cjs/dist/react/index.d.ts +2 -0
- package/cjs/dist/react/index.js +1 -0
- package/cjs/dist/services/fulfillments-service.js +80 -22
- package/cjs/dist/types/fulfillments-types.d.ts +49 -2
- package/cjs/dist/types/operation.d.ts +1 -1
- package/cjs/dist/utils/fulfillments-utils.d.ts +34 -1
- package/cjs/dist/utils/fulfillments-utils.js +209 -1
- package/dist/react/ClickableItem.d.ts +18 -3
- package/dist/react/ClickableItem.js +16 -9
- package/dist/react/FulfillmentDetails.d.ts +233 -0
- package/dist/react/FulfillmentDetails.js +255 -0
- package/dist/react/ItemDetails.d.ts +233 -36
- package/dist/react/ItemDetails.js +208 -0
- package/dist/react/ModifierGroup.js +0 -3
- package/dist/react/OLO.d.ts +5 -160
- package/dist/react/OLO.js +6 -122
- package/dist/react/OLOMenus.d.ts +8 -2
- package/dist/react/OLOMenus.js +3 -4
- package/dist/react/Settings.d.ts +176 -48
- package/dist/react/Settings.js +276 -26
- package/dist/react/core/ClickableItem.d.ts +12 -5
- package/dist/react/core/ClickableItem.js +13 -14
- package/dist/react/core/FulfillmentDetails.d.ts +78 -0
- package/dist/react/core/FulfillmentDetails.js +177 -0
- package/dist/react/core/ItemDetails.js +2 -4
- package/dist/react/core/OLO.d.ts +6 -74
- package/dist/react/core/OLO.js +5 -44
- package/dist/react/core/OLOMenus.d.ts +1 -1
- package/dist/react/core/OLOMenus.js +2 -3
- package/dist/react/core/Settings.d.ts +138 -22
- package/dist/react/core/Settings.js +157 -34
- package/dist/react/core/index.d.ts +1 -0
- package/dist/react/core/index.js +1 -0
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.js +1 -0
- package/dist/services/fulfillments-service.js +80 -22
- package/dist/types/fulfillments-types.d.ts +49 -2
- package/dist/types/operation.d.ts +1 -1
- package/dist/utils/fulfillments-utils.d.ts +34 -1
- package/dist/utils/fulfillments-utils.js +209 -1
- package/package.json +2 -2
|
@@ -2,46 +2,108 @@ import { defineService, implementService } from '@wix/services-definitions';
|
|
|
2
2
|
import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
|
|
3
3
|
import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
|
|
4
4
|
import * as fulfillemtMethodsSDK from '@wix/auto_sdk_restaurants_fulfillment-methods';
|
|
5
|
-
import { processFulfillmentTimeSlotByOperationList } from '../utils/fulfillments-utils.js';
|
|
5
|
+
import { processFulfillments, processFulfillmentTimeSlotByOperationList, } from '../utils/fulfillments-utils.js';
|
|
6
|
+
import { DispatchType, } from '../types/fulfillments-types.js';
|
|
6
7
|
export const FulfillmentsServiceDefinition = defineService('fulfillments');
|
|
7
8
|
export const FulfillmentsService = implementService.withConfig()(FulfillmentsServiceDefinition, ({ getService, config }) => {
|
|
8
|
-
console.log('config', config);
|
|
9
9
|
if (!config.operation) {
|
|
10
10
|
throw new Error('Operation ID is required');
|
|
11
11
|
}
|
|
12
12
|
const signalsService = getService(SignalsServiceDefinition);
|
|
13
|
-
const
|
|
13
|
+
const firstAvailableTimeSlotsByOperationList = new Map(config.firstAvailableTimeSlots?.map(processFulfillmentTimeSlotByOperationList) ?? []);
|
|
14
14
|
const fulfillments = signalsService.signal(config.fulfillments ?? []);
|
|
15
|
-
const
|
|
15
|
+
const firstAvailableTimeSlots = signalsService.signal(firstAvailableTimeSlotsByOperationList.get(
|
|
16
16
|
// @ts-expect-error - operation is not typed
|
|
17
|
-
|
|
17
|
+
config.operation?.id ?? '') ?? []);
|
|
18
18
|
const isLoading = signalsService.signal(false);
|
|
19
19
|
const error = signalsService.signal(null);
|
|
20
|
-
const
|
|
20
|
+
const dispatchesInfoSignal = signalsService.signal({});
|
|
21
|
+
const selectedDispatchInfoSignal = signalsService.signal(null);
|
|
22
|
+
const asapTimeSignal = signalsService.signal(undefined);
|
|
23
|
+
const asapTimeRangeSignal = signalsService.signal(undefined);
|
|
24
|
+
processFulfillments(fulfillments.get() ?? [], firstAvailableTimeSlots.get() ?? [], config.operation ?? undefined).then((result) => {
|
|
25
|
+
const { dispatchesInfo } = result ?? {};
|
|
26
|
+
dispatchesInfoSignal.set(dispatchesInfo ?? {});
|
|
27
|
+
const selectedDispatchInfo = dispatchesInfoSignal.get()[selectedFulfillment.get()?.type];
|
|
28
|
+
selectedDispatchInfoSignal.set(selectedDispatchInfo ?? null);
|
|
29
|
+
const { maxTimeOptions, durationRangeOptions } = selectedDispatchInfo ?? {};
|
|
30
|
+
// const isDelivery = !!address;
|
|
31
|
+
const isAsap = !!selectedTimeSlot.get()?.startsNow;
|
|
32
|
+
// if (isDelivery && isAsap) {
|
|
33
|
+
const { fulfillmentDetails } = selectedTimeSlot.get() ?? {};
|
|
34
|
+
if (isAsap) {
|
|
35
|
+
asapTimeSignal.set(fulfillmentDetails?.maxTimeOptions ?? maxTimeOptions);
|
|
36
|
+
asapTimeRangeSignal.set(fulfillmentDetails?.durationRangeOptions ?? durationRangeOptions);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
const pickupTimeSlot = firstAvailableTimeSlots
|
|
40
|
+
.get()
|
|
41
|
+
?.find((t) => t.dispatchType === DispatchType.PICKUP) ?? null;
|
|
42
|
+
const initialSelected = pickupTimeSlot ?? firstAvailableTimeSlots.get()?.[0] ?? null;
|
|
21
43
|
const selectedTimeSlot = signalsService.signal(initialSelected);
|
|
44
|
+
const selectedFulfillment = signalsService.signal(null);
|
|
45
|
+
const availableTypes = signalsService.signal([]);
|
|
22
46
|
selectedTimeSlot.set(initialSelected);
|
|
47
|
+
const selectFulfillmentType = (type) => {
|
|
48
|
+
const timeSlot = firstAvailableTimeSlots.get()?.find((t) => t.dispatchType === type) ??
|
|
49
|
+
null;
|
|
50
|
+
if (timeSlot) {
|
|
51
|
+
setSelectedTimeSlot(timeSlot);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Service Action: Set the selected time slot and update related fulfillment data
|
|
56
|
+
* This action updates:
|
|
57
|
+
* - selectedTimeSlot signal
|
|
58
|
+
* - selectedFulfillment signal (based on dispatch type)
|
|
59
|
+
* - minOrderPrice signal (based on fulfillment method)
|
|
60
|
+
*/
|
|
23
61
|
const setSelectedTimeSlot = (timeSlot) => {
|
|
62
|
+
// Update the selected time slot
|
|
24
63
|
selectedTimeSlot.set(timeSlot);
|
|
64
|
+
// Update selected fulfillment based on dispatch type
|
|
65
|
+
const matchingFulfillment = fulfillments.get().find((f) => f.type === timeSlot.dispatchType) ??
|
|
66
|
+
null;
|
|
67
|
+
selectedFulfillment.set(matchingFulfillment);
|
|
68
|
+
selectedDispatchInfoSignal.set(dispatchesInfoSignal.get()[matchingFulfillment?.type] ?? null);
|
|
25
69
|
};
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
70
|
+
// Initialize selected fulfillment for initial time slot
|
|
71
|
+
if (initialSelected && fulfillments.get()?.length > 0) {
|
|
72
|
+
const fulfillmentList = fulfillments.get();
|
|
73
|
+
const matchingFulfillment = fulfillmentList.find((f) => f.type === initialSelected.dispatchType) ?? null;
|
|
74
|
+
selectedFulfillment.set(matchingFulfillment);
|
|
75
|
+
}
|
|
76
|
+
// Load time slots and fulfillments if not provided
|
|
77
|
+
if (!config.firstAvailableTimeSlots && config.operation) {
|
|
78
|
+
loadFulfillmentsServiceConfig(config.operation).then((loadedConfig) => {
|
|
79
|
+
const timeSlotsMap = new Map(loadedConfig.firstAvailableTimeSlots?.map(processFulfillmentTimeSlotByOperationList) ?? []);
|
|
80
|
+
const newTimeSlots =
|
|
31
81
|
// @ts-expect-error - operation is not typed
|
|
32
|
-
timeSlotsMap.get(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
82
|
+
timeSlotsMap.get(loadedConfig.operation?.id ?? '') ?? [];
|
|
83
|
+
firstAvailableTimeSlots.set(newTimeSlots);
|
|
84
|
+
fulfillments.set(loadedConfig.fulfillments ?? []);
|
|
85
|
+
// Set initial time slot using the action to ensure all related signals are updated
|
|
86
|
+
const newInitialSelected = newTimeSlots[0];
|
|
87
|
+
if (newInitialSelected) {
|
|
88
|
+
setSelectedTimeSlot(newInitialSelected);
|
|
89
|
+
}
|
|
36
90
|
});
|
|
37
91
|
}
|
|
92
|
+
availableTypes.set(Array.from(new Set(firstAvailableTimeSlots.get()?.map((t) => t.dispatchType) ?? [])).sort((a, _) => (a === DispatchType.PICKUP ? -1 : 1)));
|
|
38
93
|
return {
|
|
39
|
-
|
|
94
|
+
firstAvailableTimeSlots,
|
|
40
95
|
fulfillments,
|
|
96
|
+
selectedFulfillment,
|
|
97
|
+
dispatchesInfoSignal,
|
|
98
|
+
selectedDispatchInfoSignal,
|
|
99
|
+
asapTimeSignal,
|
|
100
|
+
asapTimeRangeSignal,
|
|
41
101
|
selectedTimeSlot,
|
|
102
|
+
availableTypes,
|
|
42
103
|
isLoading,
|
|
43
104
|
error,
|
|
44
105
|
setSelectedTimeSlot,
|
|
106
|
+
selectFulfillmentType,
|
|
45
107
|
};
|
|
46
108
|
});
|
|
47
109
|
export const loadFulfillmentsServiceConfig = async (operation) => {
|
|
@@ -55,13 +117,9 @@ export const loadFulfillmentsServiceConfig = async (operation) => {
|
|
|
55
117
|
.in('_id', operation?.fulfillmentIds)
|
|
56
118
|
.find(),
|
|
57
119
|
]);
|
|
58
|
-
|
|
59
|
-
// operationId,
|
|
60
|
-
// ]);
|
|
61
|
-
console.log('fulfillments', fulfillments.items);
|
|
62
|
-
// const fulfillmentsMap = new Map(fulfillments.timeSlotsPerOperation?.map(processFulfillmentTimeSlotByOperationList));
|
|
120
|
+
console.log('operation', operation);
|
|
63
121
|
return {
|
|
64
|
-
|
|
122
|
+
firstAvailableTimeSlots: timeSlots.timeSlotsPerOperation,
|
|
65
123
|
operation: operation,
|
|
66
124
|
fulfillments: fulfillments.items.filter((f) => f.enabled),
|
|
67
125
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
|
|
2
2
|
import { Signal } from '@wix/services-definitions/core-services/signals';
|
|
3
3
|
import * as fulfillemtMethodsSDK from '@wix/auto_sdk_restaurants_fulfillment-methods';
|
|
4
|
+
import { Address, DurationRange } from '@wix/auto_sdk_restaurants_operations';
|
|
4
5
|
export declare enum DispatchType {
|
|
5
6
|
/** Pickup fulfillment */
|
|
6
7
|
PICKUP = "PICKUP",
|
|
@@ -21,16 +22,62 @@ export interface Fulfillment {
|
|
|
21
22
|
type: string;
|
|
22
23
|
isAvailable: boolean;
|
|
23
24
|
}
|
|
25
|
+
export type DispatchInfo = {
|
|
26
|
+
address?: Address;
|
|
27
|
+
selectedTimeSlot?: TimeSlot;
|
|
28
|
+
maxTimeOptions?: number;
|
|
29
|
+
durationRangeOptions?: DurationRange;
|
|
30
|
+
minOrder?: string;
|
|
31
|
+
deliveryFee?: string;
|
|
32
|
+
freeFulfillmentPriceThreshold?: string;
|
|
33
|
+
minDate?: Date;
|
|
34
|
+
};
|
|
35
|
+
export type DispatchesInfo = Record<DispatchType, DispatchInfo>;
|
|
24
36
|
export interface FulfillmentsServiceAPI {
|
|
25
|
-
|
|
37
|
+
/** Available time slots for the operation */
|
|
38
|
+
firstAvailableTimeSlots: Signal<TimeSlot[]>;
|
|
39
|
+
/** Available fulfillment methods (pickup, delivery, etc.) */
|
|
26
40
|
fulfillments: Signal<fulfillemtMethodsSDK.FulfillmentMethod[]>;
|
|
41
|
+
/** Currently selected fulfillment method based on dispatch type */
|
|
42
|
+
selectedFulfillment: Signal<fulfillemtMethodsSDK.FulfillmentMethod | null>;
|
|
43
|
+
/** Currently selected time slot */
|
|
27
44
|
selectedTimeSlot: Signal<TimeSlot | null>;
|
|
45
|
+
/** Currently selected ASAP time */
|
|
46
|
+
asapTimeSignal: Signal<number | undefined>;
|
|
47
|
+
/** Currently selected ASAP time range */
|
|
48
|
+
asapTimeRangeSignal: Signal<operationsSDK.DurationRange | undefined>;
|
|
49
|
+
/** Loading state */
|
|
28
50
|
isLoading: Signal<boolean>;
|
|
51
|
+
/** Error state */
|
|
29
52
|
error: Signal<string | null>;
|
|
53
|
+
/** Available dispatch types */
|
|
54
|
+
availableTypes: Signal<DispatchType[]>;
|
|
55
|
+
/**
|
|
56
|
+
* Dispatches info by dispatch type
|
|
57
|
+
*/
|
|
58
|
+
dispatchesInfoSignal: Signal<DispatchesInfo>;
|
|
59
|
+
/**
|
|
60
|
+
* Currently selected dispatch info
|
|
61
|
+
*/
|
|
62
|
+
selectedDispatchInfoSignal: Signal<DispatchInfo | null>;
|
|
63
|
+
/**
|
|
64
|
+
* Action: Set the selected time slot and update related fulfillment data
|
|
65
|
+
* This action automatically updates:
|
|
66
|
+
* - selectedTimeSlot signal
|
|
67
|
+
* - selectedFulfillment signal (based on dispatch type)
|
|
68
|
+
* - dispatchesInfo signal (based on dispatch type)
|
|
69
|
+
*
|
|
70
|
+
* @param timeSlot - The time slot to select
|
|
71
|
+
*/
|
|
30
72
|
setSelectedTimeSlot: (timeSlot: TimeSlot) => void;
|
|
73
|
+
/**
|
|
74
|
+
* Action: Select a fulfillment type and update related fulfillment data
|
|
75
|
+
* This action automatically updates the selected time slot and fulfillment data
|
|
76
|
+
*/
|
|
77
|
+
selectFulfillmentType: (type: DispatchType) => void;
|
|
31
78
|
}
|
|
32
79
|
export interface FulfillmentsServiceConfig {
|
|
33
|
-
|
|
80
|
+
firstAvailableTimeSlots?: operationsSDK.TimeSlotForOperation[];
|
|
34
81
|
operation?: operationsSDK.Operation;
|
|
35
82
|
fulfillments?: fulfillemtMethodsSDK.FulfillmentMethod[];
|
|
36
83
|
}
|
|
@@ -11,7 +11,7 @@ export type PreorderScheduling = {
|
|
|
11
11
|
timeWindowDuration: number;
|
|
12
12
|
timeInAdvance: MinMaxRange;
|
|
13
13
|
};
|
|
14
|
-
export type OperationType = 'PRE_ORDER' | 'ASAP';
|
|
14
|
+
export type OperationType = 'PRE_ORDER' | 'ASAP' | 'ASAP_AND_FUTURE';
|
|
15
15
|
type BaseOperation = {
|
|
16
16
|
id: string;
|
|
17
17
|
name: string;
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
|
|
2
|
-
import { TimeSlot } from '../types/fulfillments-types.js';
|
|
2
|
+
import { DispatchesInfo, TimeSlot } from '../types/fulfillments-types.js';
|
|
3
|
+
import { Operation } from '../types/operation.js';
|
|
4
|
+
import * as fulfillemtMethodsSDK from '@wix/auto_sdk_restaurants_fulfillment-methods';
|
|
5
|
+
import { FulfillmentDetails } from '@wix/auto_sdk_restaurants_operations';
|
|
6
|
+
/**
|
|
7
|
+
* Format a string with placeholders like {0}, {1}, etc.
|
|
8
|
+
* Similar to C#'s String.Format
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* stringFormat("Hello {0}, you have {1} messages", "John", 5)
|
|
12
|
+
* // Returns: "Hello John, you have 5 messages"
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* stringFormat("{0} + {1} = {2}", 1, 2, 3)
|
|
16
|
+
* // Returns: "1 + 2 = 3"
|
|
17
|
+
*/
|
|
18
|
+
export declare const stringFormat: (template: string, ...args: (string | number | boolean | null | undefined)[]) => string;
|
|
3
19
|
export declare const createTimeSlotId: (startTime: Date, endTime: Date, maxTimeOptions?: number) => string;
|
|
4
20
|
export declare function hasSameByField<T>(field: keyof T, arr: T[]): boolean;
|
|
5
21
|
export declare function getMinValueObjects<T>(key: keyof T, arr: T[]): T[];
|
|
@@ -21,3 +37,20 @@ export declare const resolveDifferentMinOrderPriceOptionByFulfillmentInfo: (fulf
|
|
|
21
37
|
export declare const resolveSameMinOrderPriceOptionByFulfillmentInfo: (fulfillmentInfo: operationsSDK.FulfillmentInfo[]) => operationsSDK.FulfillmentInfo;
|
|
22
38
|
export declare const resolveFulfillmentInfo: (fulfillmentInfo: operationsSDK.FulfillmentInfo[]) => operationsSDK.FulfillmentInfo | undefined;
|
|
23
39
|
export declare const processFulfillmentTimeSlotByOperationList: (operationTimeSlot: operationsSDK.TimeSlotForOperation) => [string, TimeSlot[] | undefined];
|
|
40
|
+
export declare function getFastestTimeOptions(arr: FulfillmentDetails[]): FulfillmentDetails[];
|
|
41
|
+
export declare function getSlowestTimeOption(arr: FulfillmentDetails[]): FulfillmentDetails;
|
|
42
|
+
export declare const createTimeRange: (arr: FulfillmentDetails[]) => {
|
|
43
|
+
maxTimeOptions: number | undefined;
|
|
44
|
+
durationRangeOptions?: undefined;
|
|
45
|
+
} | {
|
|
46
|
+
durationRangeOptions: {
|
|
47
|
+
minDuration: number | undefined;
|
|
48
|
+
maxDuration: number | undefined;
|
|
49
|
+
};
|
|
50
|
+
maxTimeOptions?: undefined;
|
|
51
|
+
};
|
|
52
|
+
export declare const processFulfillments: (fulfillments: fulfillemtMethodsSDK.FulfillmentMethod[], timeSlots: TimeSlot[], operation: Operation) => Promise<{
|
|
53
|
+
dispatchesInfo: DispatchesInfo;
|
|
54
|
+
isPickupConfigured: boolean;
|
|
55
|
+
isDeliveryConfigured: boolean;
|
|
56
|
+
} | undefined>;
|
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
|
|
2
|
-
import { DispatchType } from '../types/fulfillments-types.js';
|
|
2
|
+
import { DispatchType, } from '../types/fulfillments-types.js';
|
|
3
|
+
import { EntitiesDayOfWeek as DayOfWeek, } from '@wix/auto_sdk_restaurants_fulfillment-methods';
|
|
4
|
+
import { FulfillmentTimeType, } from '@wix/auto_sdk_restaurants_operations';
|
|
5
|
+
/**
|
|
6
|
+
* Format a string with placeholders like {0}, {1}, etc.
|
|
7
|
+
* Similar to C#'s String.Format
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* stringFormat("Hello {0}, you have {1} messages", "John", 5)
|
|
11
|
+
* // Returns: "Hello John, you have 5 messages"
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* stringFormat("{0} + {1} = {2}", 1, 2, 3)
|
|
15
|
+
* // Returns: "1 + 2 = 3"
|
|
16
|
+
*/
|
|
17
|
+
export const stringFormat = (template, ...args) => {
|
|
18
|
+
return template.replace(/{(\d+)}/g, (match, index) => {
|
|
19
|
+
const argIndex = parseInt(index, 10);
|
|
20
|
+
const arg = args[argIndex];
|
|
21
|
+
return arg !== undefined && arg !== null ? String(arg) : match;
|
|
22
|
+
});
|
|
23
|
+
};
|
|
3
24
|
export const createTimeSlotId = (startTime, endTime, maxTimeOptions = 0) =>
|
|
4
25
|
// maxTimeOptions is used to avoid collisions when order pacing is enabled
|
|
5
26
|
`${startTime?.toUTCString()}-${endTime?.toUTCString()}-${maxTimeOptions}`;
|
|
@@ -142,3 +163,190 @@ export const processFulfillmentTimeSlotByOperationList = (operationTimeSlot) =>
|
|
|
142
163
|
}),
|
|
143
164
|
];
|
|
144
165
|
};
|
|
166
|
+
const DAY_OF_WEEK = {
|
|
167
|
+
[DayOfWeek.MON]: 1,
|
|
168
|
+
[DayOfWeek.TUE]: 2,
|
|
169
|
+
[DayOfWeek.WED]: 3,
|
|
170
|
+
[DayOfWeek.THU]: 4,
|
|
171
|
+
[DayOfWeek.FRI]: 5,
|
|
172
|
+
[DayOfWeek.SAT]: 6,
|
|
173
|
+
[DayOfWeek.SUN]: 7,
|
|
174
|
+
};
|
|
175
|
+
const convertTimeOfDayToDateTime = (time, timezone) => {
|
|
176
|
+
const now = new Date();
|
|
177
|
+
if (timezone) {
|
|
178
|
+
// Get today's date components in the specified timezone
|
|
179
|
+
const options = {
|
|
180
|
+
timeZone: timezone,
|
|
181
|
+
year: 'numeric',
|
|
182
|
+
month: '2-digit',
|
|
183
|
+
day: '2-digit',
|
|
184
|
+
};
|
|
185
|
+
const parts = new Intl.DateTimeFormat('en-US', options).formatToParts(now);
|
|
186
|
+
const year = parts.find((p) => p.type === 'year')?.value;
|
|
187
|
+
const month = parts.find((p) => p.type === 'month')?.value;
|
|
188
|
+
const day = parts.find((p) => p.type === 'day')?.value;
|
|
189
|
+
// Create an ISO string with the date in the timezone and the specified time
|
|
190
|
+
const hours = String(time.hours ?? 0).padStart(2, '0');
|
|
191
|
+
const minutes = String(time.minutes ?? 0).padStart(2, '0');
|
|
192
|
+
const isoString = `${year}-${month}-${day}T${hours}:${minutes}:00.000`;
|
|
193
|
+
// Convert to Date - this will be interpreted as local time
|
|
194
|
+
const localDate = new Date(isoString);
|
|
195
|
+
// Get what this time would be formatted as in the target timezone
|
|
196
|
+
const formattedInTz = new Date(localDate.toLocaleString('en-US', { timeZone: timezone }));
|
|
197
|
+
// Calculate the difference and adjust
|
|
198
|
+
const offset = localDate.getTime() - formattedInTz.getTime();
|
|
199
|
+
return new Date(localDate.getTime() + offset);
|
|
200
|
+
}
|
|
201
|
+
// If no timezone specified, use local time
|
|
202
|
+
const date = new Date();
|
|
203
|
+
date.setHours(time.hours ?? 0, time.minutes ?? 0, 0, 0);
|
|
204
|
+
return date;
|
|
205
|
+
};
|
|
206
|
+
const isInTimeRange = (time, range, timeZone) => {
|
|
207
|
+
const startTime = range.startTime
|
|
208
|
+
? convertTimeOfDayToDateTime(range.startTime, timeZone)
|
|
209
|
+
: time;
|
|
210
|
+
const endTime = range.endTime
|
|
211
|
+
? convertTimeOfDayToDateTime(range.endTime, timeZone)
|
|
212
|
+
: time;
|
|
213
|
+
return (time.getTime() >= startTime.getTime() && time.getTime() <= endTime.getTime());
|
|
214
|
+
};
|
|
215
|
+
const isFulfillmentAvailable = (fulfillment, operation) => {
|
|
216
|
+
const { availability: { timeZone, availableTimes } = {} } = fulfillment;
|
|
217
|
+
const { asapOptions: { maxInMinutes, rangeInMinutes } = {} } = operation;
|
|
218
|
+
const deliveryTime = fulfillment.deliveryOptions?.deliveryTimeInMinutes ?? 0;
|
|
219
|
+
const zone = timeZone;
|
|
220
|
+
const timeNow = new Date();
|
|
221
|
+
timeNow.setMinutes(timeNow.getMinutes() +
|
|
222
|
+
(maxInMinutes ?? rangeInMinutes?.min ?? 0) +
|
|
223
|
+
deliveryTime);
|
|
224
|
+
// getDay() returns 0 (Sunday) to 6 (Saturday), but we need 1 (Monday) to 7 (Sunday)
|
|
225
|
+
const currentDayOfWeek = timeNow.getDay() === 0 ? 7 : timeNow.getDay();
|
|
226
|
+
return !!availableTimes?.some((availableTime) => {
|
|
227
|
+
const { dayOfWeek, timeRanges } = availableTime;
|
|
228
|
+
const dayOfWeekNumber = DAY_OF_WEEK[dayOfWeek];
|
|
229
|
+
const isAvailable = dayOfWeekNumber === currentDayOfWeek &&
|
|
230
|
+
!!timeRanges?.some((range) => isInTimeRange(timeNow, range, zone));
|
|
231
|
+
return isAvailable;
|
|
232
|
+
});
|
|
233
|
+
};
|
|
234
|
+
const isAddressValid = (address) => {
|
|
235
|
+
return (address.country && (address.geocode?.latitude || address.geocode?.longitude));
|
|
236
|
+
};
|
|
237
|
+
const getConsistentValue = (values) => {
|
|
238
|
+
return values.length === 0 || values.some((value) => value !== values[0])
|
|
239
|
+
? undefined
|
|
240
|
+
: (values[0] ?? undefined);
|
|
241
|
+
};
|
|
242
|
+
export function getFastestTimeOptions(arr) {
|
|
243
|
+
return arr.slice(1).reduce((minArr, item) => {
|
|
244
|
+
const { durationRangeOptions, maxTimeOptions, fulfillmentTimeType } = item;
|
|
245
|
+
const min = minArr[0];
|
|
246
|
+
const minTime = min?.fulfillmentTimeType === FulfillmentTimeType.DURATION_RANGE
|
|
247
|
+
? Number(min?.durationRangeOptions?.minDuration)
|
|
248
|
+
: Number(min?.maxTimeOptions);
|
|
249
|
+
const currTime = fulfillmentTimeType === FulfillmentTimeType.DURATION_RANGE
|
|
250
|
+
? Number(durationRangeOptions.minDuration)
|
|
251
|
+
: Number(maxTimeOptions);
|
|
252
|
+
if (currTime < minTime) {
|
|
253
|
+
return [item];
|
|
254
|
+
}
|
|
255
|
+
else if (currTime === minTime) {
|
|
256
|
+
return [...minArr, item];
|
|
257
|
+
}
|
|
258
|
+
return minArr;
|
|
259
|
+
}, (arr[0] ? [arr[0]] : []));
|
|
260
|
+
}
|
|
261
|
+
export function getSlowestTimeOption(arr) {
|
|
262
|
+
return arr.reduce((max, item) => {
|
|
263
|
+
const { durationRangeOptions, maxTimeOptions, fulfillmentTimeType } = item;
|
|
264
|
+
let maxTime;
|
|
265
|
+
if (max.fulfillmentTimeType === FulfillmentTimeType.DURATION_RANGE) {
|
|
266
|
+
maxTime = Number(max.durationRangeOptions?.maxDuration || 0);
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
maxTime = Number(max.maxTimeOptions || 0);
|
|
270
|
+
}
|
|
271
|
+
if (durationRangeOptions &&
|
|
272
|
+
fulfillmentTimeType === FulfillmentTimeType.DURATION_RANGE) {
|
|
273
|
+
return Number(durationRangeOptions.maxDuration) > maxTime ? item : max;
|
|
274
|
+
}
|
|
275
|
+
else {
|
|
276
|
+
return Number(maxTimeOptions) > maxTime ? item : max;
|
|
277
|
+
}
|
|
278
|
+
}, {});
|
|
279
|
+
}
|
|
280
|
+
export const createTimeRange = (arr) => {
|
|
281
|
+
const fastestOption = getFastestTimeOptions(arr)[0];
|
|
282
|
+
const slowestOption = getSlowestTimeOption(arr);
|
|
283
|
+
const minDuration = fastestOption?.durationRangeOptions?.minDuration ??
|
|
284
|
+
fastestOption?.maxTimeOptions;
|
|
285
|
+
const maxDuration = slowestOption.durationRangeOptions?.maxDuration ??
|
|
286
|
+
slowestOption.maxTimeOptions;
|
|
287
|
+
if (minDuration === maxDuration) {
|
|
288
|
+
return { maxTimeOptions: minDuration };
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
return { durationRangeOptions: { minDuration, maxDuration } };
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
export const processFulfillments = async (fulfillments, timeSlots, operation) => {
|
|
295
|
+
const shouldConsiderAvailability = timeSlots.length > 0;
|
|
296
|
+
try {
|
|
297
|
+
console.log('fulfillments :>>', fulfillments);
|
|
298
|
+
const pickupFulfillment = fulfillments.find((fulfillment) => fulfillment.type === 'PICKUP' && fulfillment.enabled);
|
|
299
|
+
let deliveryFulfillments = fulfillments.filter((fulfillment) => fulfillment.type === 'DELIVERY' && fulfillment.enabled);
|
|
300
|
+
const isPickupConfigured = !!pickupFulfillment;
|
|
301
|
+
const isDeliveryConfigured = deliveryFulfillments.length > 0;
|
|
302
|
+
if (shouldConsiderAvailability) {
|
|
303
|
+
deliveryFulfillments = deliveryFulfillments.filter((fulfillment) => isFulfillmentAvailable(fulfillment, operation));
|
|
304
|
+
}
|
|
305
|
+
const dispatchesInfo = {};
|
|
306
|
+
const locationAddress = operation.locationDetails?.address &&
|
|
307
|
+
isAddressValid(operation.locationDetails?.address)
|
|
308
|
+
? operation.locationDetails.address
|
|
309
|
+
: undefined;
|
|
310
|
+
if (isPickupConfigured) {
|
|
311
|
+
const pickupAddress = (locationAddress ?? pickupFulfillment?.pickupOptions?.address)
|
|
312
|
+
? pickupFulfillment.pickupOptions?.address
|
|
313
|
+
: undefined;
|
|
314
|
+
dispatchesInfo[DispatchType.PICKUP] = {
|
|
315
|
+
address: pickupAddress,
|
|
316
|
+
minOrder: pickupFulfillment?.minOrderPrice ?? undefined,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
if (isDeliveryConfigured) {
|
|
320
|
+
const deliveryFee = getConsistentValue(deliveryFulfillments.map((f) => f.fee));
|
|
321
|
+
const freeFulfillmentPriceThreshold = getConsistentValue(deliveryFulfillments.map((f) => f.deliveryOptions?.freeDeliveryThreshold));
|
|
322
|
+
const minOrder = getConsistentValue(deliveryFulfillments.map((f) => f.minOrderPrice));
|
|
323
|
+
const { asapOptions: { maxInMinutes, rangeInMinutes } = {} } = operation;
|
|
324
|
+
const fulfillmentDetails = deliveryFulfillments.map((f) => {
|
|
325
|
+
const deliveryTime = f.deliveryOptions?.deliveryTimeInMinutes ?? 0;
|
|
326
|
+
const timeObject = typeof maxInMinutes === 'number'
|
|
327
|
+
? {
|
|
328
|
+
maxTimeOptions: deliveryTime + maxInMinutes,
|
|
329
|
+
FulfillmentTimeType: FulfillmentTimeType.MAX_TIME,
|
|
330
|
+
}
|
|
331
|
+
: {
|
|
332
|
+
durationRangeOptions: {
|
|
333
|
+
minDuration: deliveryTime + (rangeInMinutes?.min ?? 0),
|
|
334
|
+
maxDuration: deliveryTime + (rangeInMinutes?.max ?? 0),
|
|
335
|
+
},
|
|
336
|
+
fulfillmentTimeType: FulfillmentTimeType.DURATION_RANGE,
|
|
337
|
+
};
|
|
338
|
+
return timeObject;
|
|
339
|
+
});
|
|
340
|
+
dispatchesInfo[DispatchType.DELIVERY] = {
|
|
341
|
+
minOrder,
|
|
342
|
+
deliveryFee,
|
|
343
|
+
freeFulfillmentPriceThreshold,
|
|
344
|
+
...((typeof maxInMinutes === 'number' || !!rangeInMinutes) &&
|
|
345
|
+
fulfillmentDetails.length > 0 &&
|
|
346
|
+
createTimeRange(fulfillmentDetails)),
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
return { dispatchesInfo, isPickupConfigured, isDeliveryConfigured };
|
|
350
|
+
}
|
|
351
|
+
catch (error) { }
|
|
352
|
+
};
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { type SelectedItemData } from './core/ClickableItem.js';
|
|
3
|
+
/**
|
|
4
|
+
* ClickableItem component
|
|
5
|
+
*
|
|
6
|
+
* A wrapper that makes an item clickable and passes the selected item data up via callback.
|
|
7
|
+
* Uses direct props pattern - no global state.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <ClickableItem onItemSelected={(item) => setSelectedItem(item)}>
|
|
12
|
+
* <div>Click me</div>
|
|
13
|
+
* </ClickableItem>
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
declare function ClickableItem({ onItemSelected, children, className, }: {
|
|
17
|
+
onItemSelected: (item: SelectedItemData) => void;
|
|
4
18
|
children: React.ReactNode;
|
|
19
|
+
className?: string;
|
|
5
20
|
}): import("react/jsx-runtime").JSX.Element;
|
|
6
21
|
export declare const Actions: {
|
|
7
22
|
Select: typeof ClickableItem;
|
|
8
23
|
};
|
|
9
|
-
export {};
|
|
24
|
+
export type { SelectedItemData } from './core/ClickableItem.js';
|
|
@@ -1,13 +1,20 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { CoreClickableItem } from './core/ClickableItem.js';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
import { CoreClickableItem, } from './core/ClickableItem.js';
|
|
3
|
+
/**
|
|
4
|
+
* ClickableItem component
|
|
5
|
+
*
|
|
6
|
+
* A wrapper that makes an item clickable and passes the selected item data up via callback.
|
|
7
|
+
* Uses direct props pattern - no global state.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```tsx
|
|
11
|
+
* <ClickableItem onItemSelected={(item) => setSelectedItem(item)}>
|
|
12
|
+
* <div>Click me</div>
|
|
13
|
+
* </ClickableItem>
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
function ClickableItem({ onItemSelected, children, className, }) {
|
|
17
|
+
return (_jsx(CoreClickableItem, { children: ({ itemData }) => (_jsx("div", { onClick: () => onItemSelected(itemData), className: className, children: children })) }));
|
|
11
18
|
}
|
|
12
19
|
export const Actions = {
|
|
13
20
|
Select: ClickableItem,
|