@wix/headless-restaurants-olo 0.0.33 → 0.0.35
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/FulfillmentDetails.d.ts +139 -15
- package/cjs/dist/react/FulfillmentDetails.js +128 -24
- package/cjs/dist/react/ItemDetails.d.ts +4 -4
- package/cjs/dist/react/ItemDetails.js +5 -6
- package/cjs/dist/react/Settings.d.ts +8 -0
- package/cjs/dist/react/Settings.js +18 -25
- package/cjs/dist/react/core/FulfillmentDetails.d.ts +73 -7
- package/cjs/dist/react/core/FulfillmentDetails.js +133 -86
- package/cjs/dist/react/core/ItemDetails.d.ts +1 -1
- package/cjs/dist/react/core/ItemDetails.js +5 -16
- package/cjs/dist/react/core/Settings.d.ts +19 -4
- package/cjs/dist/react/core/Settings.js +29 -20
- package/cjs/dist/services/fulfillment-details-service.d.ts +127 -0
- package/cjs/dist/services/fulfillment-details-service.js +351 -0
- package/cjs/dist/services/fulfillments-service.js +67 -43
- package/cjs/dist/services/index.d.ts +2 -0
- package/cjs/dist/services/index.js +1 -0
- package/cjs/dist/services/item-details-service.d.ts +1 -1
- package/cjs/dist/services/item-details-service.js +10 -16
- package/cjs/dist/services/olo-settings-service.d.ts +0 -1
- package/cjs/dist/services/olo-settings-service.js +1 -3
- package/cjs/dist/services/utils.d.ts +4 -0
- package/cjs/dist/services/utils.js +12 -0
- package/cjs/dist/types/fulfillments-types.d.ts +18 -1
- package/cjs/dist/types/fulfillments-types.js +9 -0
- package/cjs/dist/utils/date-utils.d.ts +164 -0
- package/cjs/dist/utils/date-utils.js +297 -0
- package/cjs/dist/utils/fulfillments-utils.d.ts +3 -2
- package/cjs/dist/utils/fulfillments-utils.js +13 -57
- package/dist/react/FulfillmentDetails.d.ts +139 -15
- package/dist/react/FulfillmentDetails.js +128 -24
- package/dist/react/ItemDetails.d.ts +4 -4
- package/dist/react/ItemDetails.js +5 -6
- package/dist/react/Settings.d.ts +8 -0
- package/dist/react/Settings.js +18 -25
- package/dist/react/core/FulfillmentDetails.d.ts +73 -7
- package/dist/react/core/FulfillmentDetails.js +133 -86
- package/dist/react/core/ItemDetails.d.ts +1 -1
- package/dist/react/core/ItemDetails.js +5 -16
- package/dist/react/core/Settings.d.ts +19 -4
- package/dist/react/core/Settings.js +29 -20
- package/dist/services/fulfillment-details-service.d.ts +127 -0
- package/dist/services/fulfillment-details-service.js +351 -0
- package/dist/services/fulfillments-service.js +67 -43
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +1 -0
- package/dist/services/item-details-service.d.ts +1 -1
- package/dist/services/item-details-service.js +10 -16
- package/dist/services/olo-settings-service.d.ts +0 -1
- package/dist/services/olo-settings-service.js +1 -3
- package/dist/services/utils.d.ts +4 -0
- package/dist/services/utils.js +12 -0
- package/dist/types/fulfillments-types.d.ts +18 -1
- package/dist/types/fulfillments-types.js +9 -0
- package/dist/utils/date-utils.d.ts +164 -0
- package/dist/utils/date-utils.js +297 -0
- package/dist/utils/fulfillments-utils.d.ts +3 -2
- package/dist/utils/fulfillments-utils.js +13 -57
- package/package.json +3 -2
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
|
|
2
|
+
import { DispatchType, TimeSlot, FulfillmentTypeEnum } from '../types/fulfillments-types.js';
|
|
3
|
+
import { Operation } from '../types/operation.js';
|
|
4
|
+
import { Address } from '@wix/auto_sdk_restaurants_operations';
|
|
5
|
+
/**
|
|
6
|
+
* Date availability information
|
|
7
|
+
*/
|
|
8
|
+
export interface AvailableDate {
|
|
9
|
+
/** The date that is available */
|
|
10
|
+
date: Date;
|
|
11
|
+
/** Whether this date has available slots */
|
|
12
|
+
hasAvailableSlots: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* API interface for the Fulfillment Details service
|
|
16
|
+
* Provides reactive state management for fulfillment scheduling
|
|
17
|
+
*/
|
|
18
|
+
export interface FulfillmentDetailsServiceAPI {
|
|
19
|
+
/** Currently selected time slot */
|
|
20
|
+
timeslot: Signal<TimeSlot | null>;
|
|
21
|
+
/** Delivery/pickup address */
|
|
22
|
+
address: Signal<Address | null>;
|
|
23
|
+
/** Selected date for fulfillment */
|
|
24
|
+
date: Signal<Date | null>;
|
|
25
|
+
/** Dispatch type (PICKUP or DELIVERY) */
|
|
26
|
+
dispatchType: Signal<DispatchType | null>;
|
|
27
|
+
/** ASAP time slot (when user wants the order as soon as possible) */
|
|
28
|
+
asapTimeSlot: Signal<TimeSlot | null>;
|
|
29
|
+
/** Loading state for async operations */
|
|
30
|
+
isLoading: Signal<boolean>;
|
|
31
|
+
/** Error state */
|
|
32
|
+
error: Signal<string | null>;
|
|
33
|
+
/** Available dates within the configured range */
|
|
34
|
+
availableDates: Signal<AvailableDate[]>;
|
|
35
|
+
/** Available time slots for the selected date */
|
|
36
|
+
availableTimeSlotsForDate: Signal<TimeSlot[]>;
|
|
37
|
+
/** Available time slots filtered by current dispatch type */
|
|
38
|
+
availableTimeSlots: ReadOnlySignal<TimeSlot[]>;
|
|
39
|
+
/** Selected scheduling type (ASAP or PREORDER) */
|
|
40
|
+
schedulingType: Signal<FulfillmentTypeEnum | undefined>;
|
|
41
|
+
/**
|
|
42
|
+
* Get the currently selected time slot
|
|
43
|
+
* @returns The selected time slot or null
|
|
44
|
+
*/
|
|
45
|
+
getTimeslot: () => TimeSlot | null;
|
|
46
|
+
/**
|
|
47
|
+
* Set the selected time slot
|
|
48
|
+
* @param timeslot - The time slot to select
|
|
49
|
+
*/
|
|
50
|
+
setTimeslot: (timeslot: TimeSlot | null) => void;
|
|
51
|
+
/**
|
|
52
|
+
* Set the delivery/pickup address
|
|
53
|
+
* @param address - The address to set
|
|
54
|
+
*/
|
|
55
|
+
setAddress: (address: Address | null) => void;
|
|
56
|
+
/**
|
|
57
|
+
* Set the selected date for fulfillment
|
|
58
|
+
* Also triggers fetching available time slots for the date
|
|
59
|
+
* @param date - The date to select
|
|
60
|
+
*/
|
|
61
|
+
setDate: (date: Date | null) => Promise<void>;
|
|
62
|
+
/**
|
|
63
|
+
* Set the dispatch type (PICKUP or DELIVERY)
|
|
64
|
+
* @param type - The dispatch type to set
|
|
65
|
+
*/
|
|
66
|
+
setDispatchType: (type: DispatchType | null) => void;
|
|
67
|
+
/**
|
|
68
|
+
* Set the ASAP time slot
|
|
69
|
+
* @param timeslot - The ASAP time slot to set
|
|
70
|
+
*/
|
|
71
|
+
setAsapTimeSlot: (timeslot: TimeSlot | null) => void;
|
|
72
|
+
/**
|
|
73
|
+
* Set the scheduling type (ASAP or PREORDER)
|
|
74
|
+
* Also selects an appropriate time slot based on the type
|
|
75
|
+
* @param type - The scheduling type to set
|
|
76
|
+
*/
|
|
77
|
+
setSchedulingType: (type: FulfillmentTypeEnum | undefined) => void;
|
|
78
|
+
/**
|
|
79
|
+
* Calculate available dates within a date range
|
|
80
|
+
* @param startDate - Start of the date range
|
|
81
|
+
* @param endDate - End of the date range
|
|
82
|
+
* @returns Promise that resolves to available dates
|
|
83
|
+
*/
|
|
84
|
+
calculateAvailableDatesInRange: (startDate: Date, endDate: Date) => Promise<AvailableDate[]>;
|
|
85
|
+
/**
|
|
86
|
+
* Calculate available time slots for a specific date
|
|
87
|
+
* @param date - The date to get time slots for
|
|
88
|
+
* @returns Promise that resolves to available time slots
|
|
89
|
+
*/
|
|
90
|
+
calculateAvailableTimeSlotsForDate: (date: Date) => Promise<TimeSlot[]>;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Configuration for the Fulfillment Details service
|
|
94
|
+
*/
|
|
95
|
+
export interface FulfillmentDetailsServiceConfig {
|
|
96
|
+
/** The operation to get fulfillment details for */
|
|
97
|
+
operation?: Operation;
|
|
98
|
+
/** Initial dispatch type */
|
|
99
|
+
initialDispatchType?: DispatchType;
|
|
100
|
+
/** Initial address */
|
|
101
|
+
initialAddress?: Address;
|
|
102
|
+
/** Number of days ahead to calculate available dates (default: 30) */
|
|
103
|
+
daysAhead?: number;
|
|
104
|
+
}
|
|
105
|
+
export declare const FulfillmentDetailsServiceDefinition: string & {
|
|
106
|
+
__api: FulfillmentDetailsServiceAPI;
|
|
107
|
+
__config: {};
|
|
108
|
+
isServiceDefinition?: boolean;
|
|
109
|
+
} & FulfillmentDetailsServiceAPI;
|
|
110
|
+
export declare const FulfillmentDetailsService: import("@wix/services-definitions").ServiceFactory<string & {
|
|
111
|
+
__api: FulfillmentDetailsServiceAPI;
|
|
112
|
+
__config: {};
|
|
113
|
+
isServiceDefinition?: boolean;
|
|
114
|
+
} & FulfillmentDetailsServiceAPI, FulfillmentDetailsServiceConfig>;
|
|
115
|
+
/**
|
|
116
|
+
* Load configuration for the FulfillmentDetailsService
|
|
117
|
+
* Used for SSR initialization
|
|
118
|
+
*
|
|
119
|
+
* @param operation - The operation to configure the service with
|
|
120
|
+
* @param options - Additional configuration options
|
|
121
|
+
* @returns The service configuration
|
|
122
|
+
*/
|
|
123
|
+
export declare function loadFulfillmentDetailsServiceConfig(operation: Operation, options?: {
|
|
124
|
+
initialDispatchType?: DispatchType;
|
|
125
|
+
initialAddress?: Address;
|
|
126
|
+
daysAhead?: number;
|
|
127
|
+
}): FulfillmentDetailsServiceConfig;
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
import { defineService, implementService } from '@wix/services-definitions';
|
|
2
|
+
import { SignalsServiceDefinition, } from '@wix/services-definitions/core-services/signals';
|
|
3
|
+
import * as operationsSDK from '@wix/auto_sdk_restaurants_operations';
|
|
4
|
+
import { DispatchType, FulfillmentTypeEnum, } from '../types/fulfillments-types.js';
|
|
5
|
+
import { convertDateToTimezone, createTimeSlotId, getLocale, getTimezone, resolveFulfillmentInfo, } from '../utils/fulfillments-utils.js';
|
|
6
|
+
import { createDateFromYMD } from '../utils/date-utils.js';
|
|
7
|
+
import { FulfillmentsServiceDefinition } from './fulfillments-service.js';
|
|
8
|
+
// ========================================
|
|
9
|
+
// Service Definition
|
|
10
|
+
// ========================================
|
|
11
|
+
export const FulfillmentDetailsServiceDefinition = defineService('fulfillment-details');
|
|
12
|
+
// ========================================
|
|
13
|
+
// Service Implementation
|
|
14
|
+
// ========================================
|
|
15
|
+
export const FulfillmentDetailsService = implementService.withConfig()(FulfillmentDetailsServiceDefinition, ({ getService, config }) => {
|
|
16
|
+
if (!config.operation) {
|
|
17
|
+
throw new Error('Operation is required for FulfillmentDetailsService');
|
|
18
|
+
}
|
|
19
|
+
const fulfillmentsService = getService(FulfillmentsServiceDefinition);
|
|
20
|
+
const signalsService = getService(SignalsServiceDefinition);
|
|
21
|
+
const operation = config.operation;
|
|
22
|
+
const daysAhead = config.daysAhead ?? 30;
|
|
23
|
+
// ========================================
|
|
24
|
+
// Initialize Signals
|
|
25
|
+
// ========================================
|
|
26
|
+
const timeslot = signalsService.signal(null);
|
|
27
|
+
const address = signalsService.signal(config?.initialAddress ??
|
|
28
|
+
fulfillmentsService.selectedDispatchInfoSignal?.get()?.address ??
|
|
29
|
+
null);
|
|
30
|
+
const date = signalsService.signal(null);
|
|
31
|
+
const dispatchType = signalsService.signal(config.initialDispatchType ??
|
|
32
|
+
fulfillmentsService.selectedFulfillment.get()
|
|
33
|
+
?.type ??
|
|
34
|
+
null);
|
|
35
|
+
const asapTimeSlot = signalsService.signal(null);
|
|
36
|
+
const isLoading = signalsService.signal(false);
|
|
37
|
+
const error = signalsService.signal(null);
|
|
38
|
+
const availableDates = signalsService.signal([]);
|
|
39
|
+
const availableTimeSlotsForDate = signalsService.signal([]);
|
|
40
|
+
const schedulingType = signalsService.signal(fulfillmentsService.schedulingType?.get());
|
|
41
|
+
// ========================================
|
|
42
|
+
// Helper Functions
|
|
43
|
+
// ========================================
|
|
44
|
+
/**
|
|
45
|
+
* Process time slots from SDK response to our TimeSlot format
|
|
46
|
+
*/
|
|
47
|
+
const processTimeSlots = (timeSlotsResponse, targetDispatchType) => {
|
|
48
|
+
if (!timeSlotsResponse)
|
|
49
|
+
return [];
|
|
50
|
+
const slots = [];
|
|
51
|
+
for (const fulfillmentSlot of timeSlotsResponse) {
|
|
52
|
+
const { timeSlot, fulfilmentType, fulfillmentInfo } = fulfillmentSlot;
|
|
53
|
+
// Filter by dispatch type if specified
|
|
54
|
+
const slotDispatchType = fulfilmentType === 'DELIVERY'
|
|
55
|
+
? DispatchType.DELIVERY
|
|
56
|
+
: DispatchType.PICKUP;
|
|
57
|
+
if (targetDispatchType && slotDispatchType !== targetDispatchType) {
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
const { startTime, endTime, orderSchedulingType } = timeSlot ?? {};
|
|
61
|
+
if (!startTime || !endTime)
|
|
62
|
+
continue;
|
|
63
|
+
const selectedFulfillmentInfo = resolveFulfillmentInfo(fulfillmentInfo ?? []);
|
|
64
|
+
const fulfillmentDetails = {
|
|
65
|
+
maxTimeOptions: selectedFulfillmentInfo?.maxTime,
|
|
66
|
+
durationRangeOptions: selectedFulfillmentInfo?.durationRange,
|
|
67
|
+
...selectedFulfillmentInfo,
|
|
68
|
+
};
|
|
69
|
+
const timezone = getTimezone();
|
|
70
|
+
const locale = getLocale();
|
|
71
|
+
slots.push({
|
|
72
|
+
id: createTimeSlotId(startTime, endTime),
|
|
73
|
+
startTime: convertDateToTimezone(startTime, timezone, locale),
|
|
74
|
+
endTime: convertDateToTimezone(endTime, timezone, locale),
|
|
75
|
+
dispatchType: slotDispatchType,
|
|
76
|
+
startsNow: orderSchedulingType === operationsSDK.OrderSchedulingType.ASAP,
|
|
77
|
+
fulfillmentDetails,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return slots;
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* Check if two dates are on the same day
|
|
84
|
+
*/
|
|
85
|
+
const isSameDay = (date1, date2) => {
|
|
86
|
+
return (date1.getFullYear() === date2.getFullYear() &&
|
|
87
|
+
date1.getMonth() === date2.getMonth() &&
|
|
88
|
+
date1.getDate() === date2.getDate());
|
|
89
|
+
};
|
|
90
|
+
// ========================================
|
|
91
|
+
// Action Implementations
|
|
92
|
+
// ========================================
|
|
93
|
+
const getTimeslot = () => {
|
|
94
|
+
return timeslot.get();
|
|
95
|
+
};
|
|
96
|
+
const setTimeslot = (slot) => {
|
|
97
|
+
timeslot.set(slot);
|
|
98
|
+
// Update date if timeslot has a start time
|
|
99
|
+
if (slot?.startTime) {
|
|
100
|
+
date.set(slot.startTime);
|
|
101
|
+
}
|
|
102
|
+
// Update dispatch type based on timeslot
|
|
103
|
+
if (slot?.dispatchType) {
|
|
104
|
+
dispatchType.set(slot.dispatchType);
|
|
105
|
+
}
|
|
106
|
+
// Update asap timeslot if this is an ASAP slot
|
|
107
|
+
if (slot?.startsNow) {
|
|
108
|
+
asapTimeSlot.set(slot);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const setAddress = (addr) => {
|
|
112
|
+
address.set(addr);
|
|
113
|
+
};
|
|
114
|
+
const setDate = async (selectedDate) => {
|
|
115
|
+
date.set(selectedDate);
|
|
116
|
+
// Fetch time slots for the selected date
|
|
117
|
+
if (selectedDate) {
|
|
118
|
+
await calculateAvailableTimeSlotsForDateFn(selectedDate);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
availableTimeSlotsForDate.set([]);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const setDispatchType = (type) => {
|
|
125
|
+
console.log('setDispatchType', type);
|
|
126
|
+
dispatchType.set(type);
|
|
127
|
+
// Re-filter available time slots based on new dispatch type
|
|
128
|
+
const currentDate = date.get();
|
|
129
|
+
if (currentDate) {
|
|
130
|
+
calculateAvailableTimeSlotsForDateFn(currentDate);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
const setAsapTimeSlot = (slot) => {
|
|
134
|
+
asapTimeSlot.set(slot);
|
|
135
|
+
if (slot) {
|
|
136
|
+
timeslot.set(slot);
|
|
137
|
+
if (slot.startTime) {
|
|
138
|
+
date.set(slot.startTime);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
const setSchedulingType = (type) => {
|
|
143
|
+
schedulingType.set(type);
|
|
144
|
+
// Find appropriate time slot based on type
|
|
145
|
+
const slots = availableTimeSlotsForDate.get();
|
|
146
|
+
const targetSlot = slots.find((slot) => {
|
|
147
|
+
if (type === FulfillmentTypeEnum.ASAP) {
|
|
148
|
+
return slot.startsNow;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
return !slot.startsNow;
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
if (targetSlot) {
|
|
155
|
+
setTimeslot(targetSlot);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
// ========================================
|
|
159
|
+
// Fetch Method Implementations
|
|
160
|
+
// ========================================
|
|
161
|
+
const calculateAvailableDatesInRangeFn = async (startDate, endDate) => {
|
|
162
|
+
isLoading.set(true);
|
|
163
|
+
error.set(null);
|
|
164
|
+
try {
|
|
165
|
+
// Call the SDK to get available dates in the range
|
|
166
|
+
const response = await operationsSDK.calculateAvailableDatesInRange(operation.id, {
|
|
167
|
+
from: {
|
|
168
|
+
year: startDate.getFullYear(),
|
|
169
|
+
month: startDate.getMonth() + 1, // SDK uses 1-based months
|
|
170
|
+
day: startDate.getDate(),
|
|
171
|
+
},
|
|
172
|
+
until: {
|
|
173
|
+
year: endDate.getFullYear(),
|
|
174
|
+
month: endDate.getMonth() + 1,
|
|
175
|
+
day: endDate.getDate(),
|
|
176
|
+
},
|
|
177
|
+
deliveryAddress: address.get() ?? undefined,
|
|
178
|
+
});
|
|
179
|
+
const { availableDatesPerFulfillmentType } = response;
|
|
180
|
+
// Process the response to extract available dates
|
|
181
|
+
const datesMap = new Map();
|
|
182
|
+
const currentDispatchType = dispatchType.get();
|
|
183
|
+
for (const fulfillmentDates of availableDatesPerFulfillmentType ??
|
|
184
|
+
[]) {
|
|
185
|
+
const { fulfilmentType, dates } = fulfillmentDates;
|
|
186
|
+
// Filter by dispatch type if set
|
|
187
|
+
if (currentDispatchType) {
|
|
188
|
+
const slotType = fulfilmentType === 'DELIVERY'
|
|
189
|
+
? DispatchType.DELIVERY
|
|
190
|
+
: DispatchType.PICKUP;
|
|
191
|
+
if (slotType !== currentDispatchType)
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (dates) {
|
|
195
|
+
for (const sdkDate of dates) {
|
|
196
|
+
if (!sdkDate.year || !sdkDate.month || !sdkDate.day)
|
|
197
|
+
continue;
|
|
198
|
+
const dateObj = createDateFromYMD(sdkDate.year, sdkDate.month, sdkDate.day);
|
|
199
|
+
const dateKey = dateObj.toISOString().split('T')[0];
|
|
200
|
+
if (!datesMap.has(dateKey)) {
|
|
201
|
+
datesMap.set(dateKey, {
|
|
202
|
+
date: dateObj,
|
|
203
|
+
hasAvailableSlots: true,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const dates = Array.from(datesMap.values()).sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
210
|
+
availableDates.set(dates);
|
|
211
|
+
return dates;
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
const errorMessage = err instanceof Error
|
|
215
|
+
? err.message
|
|
216
|
+
: 'Failed to calculate available dates';
|
|
217
|
+
error.set(errorMessage);
|
|
218
|
+
console.error('Error calculating available dates:', err);
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
isLoading.set(false);
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
const calculateAvailableTimeSlotsForDateFn = async (targetDate) => {
|
|
226
|
+
isLoading.set(true);
|
|
227
|
+
error.set(null);
|
|
228
|
+
try {
|
|
229
|
+
// Call the SDK to get time slots for the specific date
|
|
230
|
+
const response = await operationsSDK.calculateAvailableTimeSlotsForDate(operation.id, {
|
|
231
|
+
date: {
|
|
232
|
+
year: targetDate.getFullYear(),
|
|
233
|
+
month: targetDate.getMonth() + 1,
|
|
234
|
+
day: targetDate.getDate(),
|
|
235
|
+
},
|
|
236
|
+
deliveryAddress: address.get() ?? undefined,
|
|
237
|
+
});
|
|
238
|
+
const { timeslotsPerFulfillmentType } = response;
|
|
239
|
+
const currentDispatchType = dispatchType.get();
|
|
240
|
+
const slots = processTimeSlots(timeslotsPerFulfillmentType, currentDispatchType).reverse();
|
|
241
|
+
availableTimeSlotsForDate.set(slots);
|
|
242
|
+
// Auto-select first available slot if none selected
|
|
243
|
+
if (slots.length > 0 && !timeslot.get()) {
|
|
244
|
+
const firstAsapSlot = slots.find((s) => s.startsNow);
|
|
245
|
+
setTimeslot(firstAsapSlot ?? slots[0]);
|
|
246
|
+
}
|
|
247
|
+
return slots;
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
const errorMessage = err instanceof Error
|
|
251
|
+
? err.message
|
|
252
|
+
: 'Failed to calculate time slots for date';
|
|
253
|
+
error.set(errorMessage);
|
|
254
|
+
console.error('Error calculating time slots for date:', err);
|
|
255
|
+
return [];
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
isLoading.set(false);
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
// ========================================
|
|
262
|
+
// Initialize with default date range
|
|
263
|
+
// ========================================
|
|
264
|
+
// Auto-load available dates on service initialization
|
|
265
|
+
const initializeDates = async () => {
|
|
266
|
+
const startDate = new Date();
|
|
267
|
+
startDate.setHours(0, 0, 0, 0);
|
|
268
|
+
const endDate = new Date();
|
|
269
|
+
endDate.setDate(endDate.getDate() + daysAhead);
|
|
270
|
+
endDate.setHours(23, 59, 59, 999);
|
|
271
|
+
await calculateAvailableDatesInRangeFn(startDate, endDate);
|
|
272
|
+
// If we have available dates, auto-select today or first available
|
|
273
|
+
const dates = availableDates.get();
|
|
274
|
+
if (dates.length > 0) {
|
|
275
|
+
const today = new Date();
|
|
276
|
+
const todayAvailable = dates.find((d) => isSameDay(d.date, today) && d.hasAvailableSlots);
|
|
277
|
+
const firstAvailable = todayAvailable ?? dates.find((d) => d.hasAvailableSlots);
|
|
278
|
+
if (firstAvailable) {
|
|
279
|
+
await setDate(firstAvailable.date);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
// Initialize asynchronously (don't block service creation)
|
|
284
|
+
initializeDates().catch((err) => {
|
|
285
|
+
console.error('Error initializing fulfillment details:', err);
|
|
286
|
+
error.set('Failed to initialize fulfillment details');
|
|
287
|
+
});
|
|
288
|
+
// ========================================
|
|
289
|
+
// Computed Signals
|
|
290
|
+
// ========================================
|
|
291
|
+
/**
|
|
292
|
+
* Available time slots filtered by current dispatch type
|
|
293
|
+
*/
|
|
294
|
+
const availableTimeSlots = signalsService.computed(() => {
|
|
295
|
+
const currentDispatchType = dispatchType.get();
|
|
296
|
+
console.log('currentDispatchType', currentDispatchType);
|
|
297
|
+
const allTimeSlots = availableTimeSlotsForDate.get();
|
|
298
|
+
if (!currentDispatchType) {
|
|
299
|
+
return allTimeSlots;
|
|
300
|
+
}
|
|
301
|
+
return allTimeSlots.filter((slot) => slot.dispatchType === currentDispatchType);
|
|
302
|
+
});
|
|
303
|
+
// ========================================
|
|
304
|
+
// Return Service API
|
|
305
|
+
// ========================================
|
|
306
|
+
return {
|
|
307
|
+
// Signals
|
|
308
|
+
timeslot,
|
|
309
|
+
address,
|
|
310
|
+
date,
|
|
311
|
+
dispatchType,
|
|
312
|
+
asapTimeSlot,
|
|
313
|
+
isLoading,
|
|
314
|
+
error,
|
|
315
|
+
availableDates,
|
|
316
|
+
availableTimeSlotsForDate,
|
|
317
|
+
availableTimeSlots,
|
|
318
|
+
schedulingType,
|
|
319
|
+
// Actions
|
|
320
|
+
getTimeslot,
|
|
321
|
+
setTimeslot,
|
|
322
|
+
setAddress,
|
|
323
|
+
setDate,
|
|
324
|
+
setDispatchType,
|
|
325
|
+
setAsapTimeSlot,
|
|
326
|
+
setSchedulingType,
|
|
327
|
+
// Fetch methods
|
|
328
|
+
calculateAvailableDatesInRange: calculateAvailableDatesInRangeFn,
|
|
329
|
+
calculateAvailableTimeSlotsForDate: calculateAvailableTimeSlotsForDateFn,
|
|
330
|
+
};
|
|
331
|
+
});
|
|
332
|
+
// ========================================
|
|
333
|
+
// Config Loader
|
|
334
|
+
// ========================================
|
|
335
|
+
/**
|
|
336
|
+
* Load configuration for the FulfillmentDetailsService
|
|
337
|
+
* Used for SSR initialization
|
|
338
|
+
*
|
|
339
|
+
* @param operation - The operation to configure the service with
|
|
340
|
+
* @param options - Additional configuration options
|
|
341
|
+
* @returns The service configuration
|
|
342
|
+
*/
|
|
343
|
+
export function loadFulfillmentDetailsServiceConfig(operation, options) {
|
|
344
|
+
console.log('options', options);
|
|
345
|
+
return {
|
|
346
|
+
operation,
|
|
347
|
+
initialDispatchType: options?.initialDispatchType,
|
|
348
|
+
initialAddress: options?.initialAddress,
|
|
349
|
+
daysAhead: options?.daysAhead ?? 30,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
@@ -2,8 +2,8 @@ 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 { processFulfillments, processFulfillmentTimeSlotByOperationList, } from '../utils/fulfillments-utils.js';
|
|
6
|
-
import { DispatchType, } from '../types/fulfillments-types.js';
|
|
5
|
+
import { convertDateToTimezone, getLocale, getTimezone, processFulfillments, processFulfillmentTimeSlotByOperationList, } from '../utils/fulfillments-utils.js';
|
|
6
|
+
import { DispatchType, FulfillmentTypeEnum, } from '../types/fulfillments-types.js';
|
|
7
7
|
export const FulfillmentsServiceDefinition = defineService('fulfillments');
|
|
8
8
|
export const FulfillmentsService = implementService.withConfig()(FulfillmentsServiceDefinition, ({ getService, config }) => {
|
|
9
9
|
if (!config.operation) {
|
|
@@ -12,42 +12,80 @@ export const FulfillmentsService = implementService.withConfig()(FulfillmentsSer
|
|
|
12
12
|
const signalsService = getService(SignalsServiceDefinition);
|
|
13
13
|
const firstAvailableTimeSlotsByOperationList = new Map(config.firstAvailableTimeSlots?.map(processFulfillmentTimeSlotByOperationList) ?? []);
|
|
14
14
|
const fulfillments = signalsService.signal(config.fulfillments ?? []);
|
|
15
|
-
const firstAvailableTimeSlots = signalsService.signal(firstAvailableTimeSlotsByOperationList.get(
|
|
16
|
-
// @ts-expect-error - operation is not typed
|
|
17
|
-
config.operation?.id ?? '') ?? []);
|
|
15
|
+
const firstAvailableTimeSlots = signalsService.signal(firstAvailableTimeSlotsByOperationList.get(config.operation?.id ?? '') ?? []);
|
|
18
16
|
const isLoading = signalsService.signal(false);
|
|
19
17
|
const error = signalsService.signal(null);
|
|
20
18
|
const dispatchesInfoSignal = signalsService.signal({});
|
|
21
19
|
const selectedDispatchInfoSignal = signalsService.signal(null);
|
|
22
20
|
const asapTimeSignal = signalsService.signal(undefined);
|
|
23
21
|
const asapTimeRangeSignal = signalsService.signal(undefined);
|
|
24
|
-
|
|
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
|
-
});
|
|
22
|
+
const selectedFulfillment = signalsService.signal(null);
|
|
39
23
|
const pickupTimeSlot = firstAvailableTimeSlots
|
|
40
24
|
.get()
|
|
41
25
|
?.find((t) => t.dispatchType === DispatchType.PICKUP) ?? null;
|
|
42
26
|
const initialSelected = pickupTimeSlot ?? firstAvailableTimeSlots.get()?.[0] ?? null;
|
|
43
27
|
const selectedTimeSlot = signalsService.signal(initialSelected);
|
|
44
|
-
const
|
|
28
|
+
const { dispatchesInfo } = processFulfillments(fulfillments.get() ?? [], firstAvailableTimeSlots.get() ?? [], config.operation ?? undefined) ?? {};
|
|
29
|
+
// Initialize selected fulfillment for initial time slot
|
|
30
|
+
if (initialSelected && fulfillments.get()?.length > 0) {
|
|
31
|
+
const timezone = getTimezone();
|
|
32
|
+
const locale = getLocale();
|
|
33
|
+
initialSelected.startTime = convertDateToTimezone(initialSelected.startTime, timezone, locale);
|
|
34
|
+
initialSelected.endTime = convertDateToTimezone(initialSelected.endTime, timezone, locale);
|
|
35
|
+
const fulfillmentList = fulfillments.get();
|
|
36
|
+
const matchingFulfillment = fulfillmentList.find((f) => f.type === initialSelected.dispatchType) ?? null;
|
|
37
|
+
selectedFulfillment.set(matchingFulfillment);
|
|
38
|
+
}
|
|
39
|
+
dispatchesInfoSignal.set(dispatchesInfo ?? {});
|
|
40
|
+
const selectedDispatchInfo = dispatchesInfoSignal.get()[selectedFulfillment.get()?.type];
|
|
41
|
+
selectedDispatchInfoSignal.set(selectedDispatchInfo ?? null);
|
|
42
|
+
const { maxTimeOptions, durationRangeOptions } = selectedDispatchInfo ?? {};
|
|
43
|
+
// const isDelivery = !!address;
|
|
44
|
+
const isAsap = !!selectedTimeSlot.get()?.startsNow;
|
|
45
|
+
// if (isDelivery && isAsap) {
|
|
46
|
+
const { fulfillmentDetails } = selectedTimeSlot.get() ?? {};
|
|
47
|
+
if (isAsap) {
|
|
48
|
+
asapTimeSignal.set(fulfillmentDetails?.maxTimeOptions ?? maxTimeOptions);
|
|
49
|
+
asapTimeRangeSignal.set(fulfillmentDetails?.durationRangeOptions ?? durationRangeOptions);
|
|
50
|
+
}
|
|
51
|
+
// });
|
|
45
52
|
const availableTypes = signalsService.signal([]);
|
|
53
|
+
const getSchedulingType = (timeSlot) => {
|
|
54
|
+
if (!timeSlot)
|
|
55
|
+
return undefined;
|
|
56
|
+
if (config.operation?.operationType === operationsSDK.SchedulingType.ASAP) {
|
|
57
|
+
return timeSlot.startsNow
|
|
58
|
+
? FulfillmentTypeEnum.ASAP
|
|
59
|
+
: FulfillmentTypeEnum.ASAP_AND_FUTURE;
|
|
60
|
+
}
|
|
61
|
+
return FulfillmentTypeEnum.PREORDER;
|
|
62
|
+
};
|
|
63
|
+
const schedulingType = signalsService.signal(getSchedulingType(initialSelected));
|
|
64
|
+
// Compute initial fulfillment type options based on time slots
|
|
65
|
+
const computeFulfillmentTypeOptions = (timeSlots) => {
|
|
66
|
+
const hasAsap = timeSlots
|
|
67
|
+
.filter((slot) => slot.dispatchType === selectedFulfillment.get()?.type)
|
|
68
|
+
.some((slot) => slot.startsNow);
|
|
69
|
+
const hasFuture = !!config.operation?.allowAsapFutureHandling;
|
|
70
|
+
return {
|
|
71
|
+
hasAsapOption: hasAsap,
|
|
72
|
+
hasPreorderOption: !hasAsap && hasFuture,
|
|
73
|
+
hasAsapAndFutureOption: hasAsap && hasFuture,
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
const initialOptions = computeFulfillmentTypeOptions(firstAvailableTimeSlots.get() ?? []);
|
|
77
|
+
const hasAsapOption = signalsService.signal(initialOptions.hasAsapOption);
|
|
78
|
+
const hasPreorderOption = signalsService.signal(initialOptions.hasPreorderOption);
|
|
79
|
+
const hasAsapAndFutureOption = signalsService.signal(initialOptions.hasAsapAndFutureOption);
|
|
46
80
|
selectedTimeSlot.set(initialSelected);
|
|
47
81
|
const selectFulfillmentType = (type) => {
|
|
48
82
|
const timeSlot = firstAvailableTimeSlots.get()?.find((t) => t.dispatchType === type) ??
|
|
49
83
|
null;
|
|
50
84
|
if (timeSlot) {
|
|
85
|
+
const timezone = getTimezone();
|
|
86
|
+
const locale = getLocale();
|
|
87
|
+
timeSlot.startTime = convertDateToTimezone(timeSlot.startTime, timezone, locale);
|
|
88
|
+
timeSlot.endTime = convertDateToTimezone(timeSlot.endTime, timezone, locale);
|
|
51
89
|
setSelectedTimeSlot(timeSlot);
|
|
52
90
|
}
|
|
53
91
|
};
|
|
@@ -56,39 +94,21 @@ export const FulfillmentsService = implementService.withConfig()(FulfillmentsSer
|
|
|
56
94
|
* This action updates:
|
|
57
95
|
* - selectedTimeSlot signal
|
|
58
96
|
* - selectedFulfillment signal (based on dispatch type)
|
|
97
|
+
* - schedulingType signal (based on order scheduling type)
|
|
59
98
|
* - minOrderPrice signal (based on fulfillment method)
|
|
60
99
|
*/
|
|
61
100
|
const setSelectedTimeSlot = (timeSlot) => {
|
|
101
|
+
console.log('setSelectedTimeSlot', timeSlot);
|
|
62
102
|
// Update the selected time slot
|
|
63
103
|
selectedTimeSlot.set(timeSlot);
|
|
104
|
+
// Update scheduling type based on order scheduling configuration
|
|
105
|
+
schedulingType.set(getSchedulingType(timeSlot));
|
|
64
106
|
// Update selected fulfillment based on dispatch type
|
|
65
107
|
const matchingFulfillment = fulfillments.get().find((f) => f.type === timeSlot.dispatchType) ??
|
|
66
108
|
null;
|
|
67
109
|
selectedFulfillment.set(matchingFulfillment);
|
|
68
110
|
selectedDispatchInfoSignal.set(dispatchesInfoSignal.get()[matchingFulfillment?.type] ?? null);
|
|
69
111
|
};
|
|
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 =
|
|
81
|
-
// @ts-expect-error - operation is not typed
|
|
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
|
-
}
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
112
|
availableTypes.set(Array.from(new Set(firstAvailableTimeSlots.get()?.map((t) => t.dispatchType) ?? [])).sort((a, _) => (a === DispatchType.PICKUP ? -1 : 1)));
|
|
93
113
|
return {
|
|
94
114
|
firstAvailableTimeSlots,
|
|
@@ -100,6 +120,10 @@ export const FulfillmentsService = implementService.withConfig()(FulfillmentsSer
|
|
|
100
120
|
asapTimeRangeSignal,
|
|
101
121
|
selectedTimeSlot,
|
|
102
122
|
availableTypes,
|
|
123
|
+
schedulingType,
|
|
124
|
+
hasAsapOption,
|
|
125
|
+
hasPreorderOption,
|
|
126
|
+
hasAsapAndFutureOption,
|
|
103
127
|
isLoading,
|
|
104
128
|
error,
|
|
105
129
|
setSelectedTimeSlot,
|
|
@@ -2,3 +2,5 @@ export { ItemService, ItemServiceDefinition, loadItemServiceConfig, ItemServiceC
|
|
|
2
2
|
export { OLOSettingsService, OLOSettingsServiceDefinition, loadOLOSettingsServiceConfig, type OLOSettingsServiceConfig, type OLOSettingsServiceAPI, } from './olo-settings-service.js';
|
|
3
3
|
export { AvailabilityStatus, AvailabilityStatusMap, RuleType, RuleTypeMap, AddToCartButtonState, AddToCartButtonLabelMap, } from './common-types.js';
|
|
4
4
|
export { FulfillmentsService, FulfillmentsServiceDefinition, loadFulfillmentsServiceConfig, } from './fulfillments-service.js';
|
|
5
|
+
export { type FulfillmentsServiceConfig } from '../types/fulfillments-types.js';
|
|
6
|
+
export { FulfillmentDetailsService, FulfillmentDetailsServiceDefinition, loadFulfillmentDetailsServiceConfig, type FulfillmentDetailsServiceAPI, type FulfillmentDetailsServiceConfig, type AvailableDate, } from './fulfillment-details-service.js';
|
|
@@ -2,3 +2,4 @@ export { ItemService, ItemServiceDefinition, loadItemServiceConfig, } from './it
|
|
|
2
2
|
export { OLOSettingsService, OLOSettingsServiceDefinition, loadOLOSettingsServiceConfig, } from './olo-settings-service.js';
|
|
3
3
|
export { AvailabilityStatus, RuleType, AddToCartButtonState, } from './common-types.js';
|
|
4
4
|
export { FulfillmentsService, FulfillmentsServiceDefinition, loadFulfillmentsServiceConfig, } from './fulfillments-service.js';
|
|
5
|
+
export { FulfillmentDetailsService, FulfillmentDetailsServiceDefinition, loadFulfillmentDetailsServiceConfig, } from './fulfillment-details-service.js';
|
|
@@ -16,6 +16,7 @@ export interface ItemServiceAPI {
|
|
|
16
16
|
quantity: Signal<number>;
|
|
17
17
|
specialRequest: Signal<string>;
|
|
18
18
|
lineItem: ReadOnlySignal<LineItem>;
|
|
19
|
+
onHandleAddToCart: (onClick: (lineItem: LineItem) => void) => void;
|
|
19
20
|
buttonState: ReadOnlySignal<AddToCartButtonState | undefined>;
|
|
20
21
|
addToCartButtonDisabled: Signal<boolean>;
|
|
21
22
|
price: ReadOnlySignal<number>;
|
|
@@ -43,7 +44,6 @@ export interface ItemServiceAPI {
|
|
|
43
44
|
endDate?: Date;
|
|
44
45
|
weeklyAvailabilitySummary?: WeeklyAvailability;
|
|
45
46
|
};
|
|
46
|
-
doesModifierGroupHaveError: ReadOnlySignal<boolean>;
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* Service definition for the Item service.
|