@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.
Files changed (59) hide show
  1. package/cjs/dist/react/FulfillmentDetails.d.ts +139 -15
  2. package/cjs/dist/react/FulfillmentDetails.js +128 -24
  3. package/cjs/dist/react/ItemDetails.d.ts +4 -4
  4. package/cjs/dist/react/ItemDetails.js +5 -6
  5. package/cjs/dist/react/Settings.d.ts +8 -0
  6. package/cjs/dist/react/Settings.js +18 -25
  7. package/cjs/dist/react/core/FulfillmentDetails.d.ts +73 -7
  8. package/cjs/dist/react/core/FulfillmentDetails.js +133 -86
  9. package/cjs/dist/react/core/ItemDetails.d.ts +1 -1
  10. package/cjs/dist/react/core/ItemDetails.js +5 -16
  11. package/cjs/dist/react/core/Settings.d.ts +19 -4
  12. package/cjs/dist/react/core/Settings.js +29 -20
  13. package/cjs/dist/services/fulfillment-details-service.d.ts +127 -0
  14. package/cjs/dist/services/fulfillment-details-service.js +351 -0
  15. package/cjs/dist/services/fulfillments-service.js +67 -43
  16. package/cjs/dist/services/index.d.ts +2 -0
  17. package/cjs/dist/services/index.js +1 -0
  18. package/cjs/dist/services/item-details-service.d.ts +1 -1
  19. package/cjs/dist/services/item-details-service.js +10 -16
  20. package/cjs/dist/services/olo-settings-service.d.ts +0 -1
  21. package/cjs/dist/services/olo-settings-service.js +1 -3
  22. package/cjs/dist/services/utils.d.ts +4 -0
  23. package/cjs/dist/services/utils.js +12 -0
  24. package/cjs/dist/types/fulfillments-types.d.ts +18 -1
  25. package/cjs/dist/types/fulfillments-types.js +9 -0
  26. package/cjs/dist/utils/date-utils.d.ts +164 -0
  27. package/cjs/dist/utils/date-utils.js +297 -0
  28. package/cjs/dist/utils/fulfillments-utils.d.ts +3 -2
  29. package/cjs/dist/utils/fulfillments-utils.js +13 -57
  30. package/dist/react/FulfillmentDetails.d.ts +139 -15
  31. package/dist/react/FulfillmentDetails.js +128 -24
  32. package/dist/react/ItemDetails.d.ts +4 -4
  33. package/dist/react/ItemDetails.js +5 -6
  34. package/dist/react/Settings.d.ts +8 -0
  35. package/dist/react/Settings.js +18 -25
  36. package/dist/react/core/FulfillmentDetails.d.ts +73 -7
  37. package/dist/react/core/FulfillmentDetails.js +133 -86
  38. package/dist/react/core/ItemDetails.d.ts +1 -1
  39. package/dist/react/core/ItemDetails.js +5 -16
  40. package/dist/react/core/Settings.d.ts +19 -4
  41. package/dist/react/core/Settings.js +29 -20
  42. package/dist/services/fulfillment-details-service.d.ts +127 -0
  43. package/dist/services/fulfillment-details-service.js +351 -0
  44. package/dist/services/fulfillments-service.js +67 -43
  45. package/dist/services/index.d.ts +2 -0
  46. package/dist/services/index.js +1 -0
  47. package/dist/services/item-details-service.d.ts +1 -1
  48. package/dist/services/item-details-service.js +10 -16
  49. package/dist/services/olo-settings-service.d.ts +0 -1
  50. package/dist/services/olo-settings-service.js +1 -3
  51. package/dist/services/utils.d.ts +4 -0
  52. package/dist/services/utils.js +12 -0
  53. package/dist/types/fulfillments-types.d.ts +18 -1
  54. package/dist/types/fulfillments-types.js +9 -0
  55. package/dist/utils/date-utils.d.ts +164 -0
  56. package/dist/utils/date-utils.js +297 -0
  57. package/dist/utils/fulfillments-utils.d.ts +3 -2
  58. package/dist/utils/fulfillments-utils.js +13 -57
  59. 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, loadFulfillmentsServiceConfig, } from '../../services/fulfillments-service.js';
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
- * <CoreSettings.Root fulfillmentsServiceConfig={config}>
14
- * <CoreSettings.CurrentTimeSlot>
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
- * </CoreSettings.CurrentTimeSlot>
19
- * </CoreSettings.Root>
32
+ * </Settings.CurrentTimeSlot>
33
+ * </Settings.Root>
20
34
  * ```
21
35
  */
22
36
  export const Root = ({ children, fulfillmentsServiceConfig, }) => {
23
- const service = useService(OLOSettingsServiceDefinition);
24
- const [config, setConfig] = useState(fulfillmentsServiceConfig);
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, config), children: children({ isLoading: !config }) }));
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
+ }