@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
|
@@ -1,39 +1,43 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useState } from 'react';
|
|
3
2
|
import { WixServices, useService } from '@wix/services-manager-react';
|
|
4
3
|
import { OLOSettingsServiceDefinition } from '../../services/olo-settings-service.js';
|
|
5
|
-
import { FulfillmentsService, FulfillmentsServiceDefinition,
|
|
4
|
+
import { FulfillmentsService, FulfillmentsServiceDefinition, } from '../../services/fulfillments-service.js';
|
|
6
5
|
import { createServicesMap } from '@wix/services-manager';
|
|
7
6
|
/**
|
|
8
7
|
* Core Settings Root component that provides service context
|
|
9
8
|
* Wraps children with both OLOSettingsService and FulfillmentsService providers
|
|
10
9
|
*
|
|
10
|
+
* Note: Use loadFulfillmentsServiceConfig from '@wix/headless-restaurants-olo/services'
|
|
11
|
+
* to load the config before rendering this component.
|
|
12
|
+
*
|
|
11
13
|
* @example
|
|
12
14
|
* ```tsx
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
+
* import { loadFulfillmentsServiceConfig, OLOSettingsServiceDefinition } from '@wix/headless-restaurants-olo/services';
|
|
16
|
+
*
|
|
17
|
+
* // In your component:
|
|
18
|
+
* const service = useService(OLOSettingsServiceDefinition);
|
|
19
|
+
* const [config, setConfig] = useState(null);
|
|
20
|
+
*
|
|
21
|
+
* useEffect(() => {
|
|
22
|
+
* loadFulfillmentsServiceConfig(service.operation?.get()).then(setConfig);
|
|
23
|
+
* }, []);
|
|
24
|
+
*
|
|
25
|
+
* if (!config) return <div>Loading...</div>;
|
|
26
|
+
*
|
|
27
|
+
* <Settings.Root fulfillmentsServiceConfig={config}>
|
|
28
|
+
* <Settings.CurrentTimeSlot>
|
|
15
29
|
* {({ timeSlot, hasDetails }) => (
|
|
16
30
|
* <div>{timeSlot?.dispatchType}</div>
|
|
17
31
|
* )}
|
|
18
|
-
* </
|
|
19
|
-
* </
|
|
32
|
+
* </Settings.CurrentTimeSlot>
|
|
33
|
+
* </Settings.Root>
|
|
20
34
|
* ```
|
|
21
35
|
*/
|
|
22
36
|
export const Root = ({ children, fulfillmentsServiceConfig, }) => {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
useEffect(() => {
|
|
26
|
-
if (!fulfillmentsServiceConfig) {
|
|
27
|
-
loadFulfillmentsServiceConfig(service.operation?.get()).then((loadedConfig) => {
|
|
28
|
-
setConfig(loadedConfig);
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
}, [fulfillmentsServiceConfig]);
|
|
32
|
-
service.isLoading.set(!config);
|
|
33
|
-
if (!config) {
|
|
34
|
-
return children({ isLoading: !config });
|
|
37
|
+
if (!fulfillmentsServiceConfig) {
|
|
38
|
+
return children({ isLoading: true });
|
|
35
39
|
}
|
|
36
|
-
return (_jsx(WixServices, { servicesMap: createServicesMap().addService(FulfillmentsServiceDefinition, FulfillmentsService,
|
|
40
|
+
return (_jsx(WixServices, { servicesMap: createServicesMap().addService(FulfillmentsServiceDefinition, FulfillmentsService, fulfillmentsServiceConfig), children: children({ isLoading: false }) }));
|
|
37
41
|
};
|
|
38
42
|
/**
|
|
39
43
|
* Component that provides access to current time slot
|
|
@@ -160,6 +164,10 @@ export const MinOrderAmount = ({ children }) => {
|
|
|
160
164
|
export const AcceptingOrders = ({ children, }) => {
|
|
161
165
|
const service = useService(OLOSettingsServiceDefinition);
|
|
162
166
|
const fulfillmentsService = useService(FulfillmentsServiceDefinition);
|
|
167
|
+
// Check if FulfillmentsService is available (it won't be during loading)
|
|
168
|
+
if (!fulfillmentsService?.selectedFulfillment) {
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
163
171
|
const operation = service.operation?.get();
|
|
164
172
|
const selectedFulfillment = fulfillmentsService.selectedFulfillment?.get();
|
|
165
173
|
const acceptingOrders = Boolean(operation?.enabled) && Boolean(selectedFulfillment);
|
|
@@ -184,7 +192,6 @@ export const AcceptingOrders = ({ children, }) => {
|
|
|
184
192
|
export const DeliveryFee = ({ children }) => {
|
|
185
193
|
const fulfillmentsService = useService(FulfillmentsServiceDefinition);
|
|
186
194
|
const selectedDispatchInfo = fulfillmentsService.selectedDispatchInfoSignal?.get();
|
|
187
|
-
console.log('selectedDispatchInfo', selectedDispatchInfo);
|
|
188
195
|
const deliveryFee = selectedDispatchInfo?.deliveryFee ?? undefined;
|
|
189
196
|
const hasDeliveryFee = Boolean(deliveryFee);
|
|
190
197
|
return children({ deliveryFee, hasDeliveryFee });
|
|
@@ -231,6 +238,8 @@ export const FreeDeliveryThreshold = ({ children, }) => {
|
|
|
231
238
|
export const SelectedAddress = ({ children, }) => {
|
|
232
239
|
const fulfillmentsService = useService(FulfillmentsServiceDefinition);
|
|
233
240
|
const selectedDispatchInfo = fulfillmentsService.selectedDispatchInfoSignal?.get();
|
|
241
|
+
const fulfillments = fulfillmentsService.fulfillments?.get() ?? null;
|
|
242
|
+
console.log('fulfillments', fulfillments);
|
|
234
243
|
const selectedAddress = selectedDispatchInfo?.address ?? null;
|
|
235
244
|
const hasAddress = Boolean(selectedAddress);
|
|
236
245
|
const selectedTimeSlot = fulfillmentsService.selectedTimeSlot?.get();
|
|
@@ -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
|
+
}
|