@wix/headless-bookings 0.0.15 → 0.0.17
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/api/time-slot-list/index.d.ts +21 -1
- package/cjs/dist/api/time-slot-list/index.js +46 -8
- package/cjs/dist/react/core/booking/Booking.d.ts +17 -9
- package/cjs/dist/react/core/booking/Booking.js +13 -7
- package/cjs/dist/react/core/time-slot-list/TimeSlot.d.ts +22 -18
- package/cjs/dist/react/core/time-slot-list/TimeSlot.js +41 -28
- package/cjs/dist/react/core/time-slot-list/TimeSlotList.d.ts +39 -7
- package/cjs/dist/react/core/time-slot-list/TimeSlotList.js +56 -11
- package/cjs/dist/react/time-slot-list/TimeSlot.js +3 -3
- package/cjs/dist/react/time-slot-list/TimeSlotList.d.ts +55 -12
- package/cjs/dist/react/time-slot-list/TimeSlotList.js +47 -17
- package/cjs/dist/services/booking/booking.d.ts +48 -30
- package/cjs/dist/services/booking/booking.js +74 -44
- package/cjs/dist/services/index.d.ts +3 -3
- package/cjs/dist/services/service/service.js +10 -7
- package/cjs/dist/services/time-slot-list/index.d.ts +1 -1
- package/cjs/dist/services/time-slot-list/time-slot-list.d.ts +3 -3
- package/cjs/dist/services/time-slot-list/time-slot-list.def.d.ts +23 -4
- package/cjs/dist/services/time-slot-list/time-slot-list.js +146 -65
- package/dist/api/time-slot-list/index.d.ts +21 -1
- package/dist/api/time-slot-list/index.js +46 -8
- package/dist/react/core/booking/Booking.d.ts +17 -9
- package/dist/react/core/booking/Booking.js +13 -7
- package/dist/react/core/time-slot-list/TimeSlot.d.ts +22 -18
- package/dist/react/core/time-slot-list/TimeSlot.js +41 -28
- package/dist/react/core/time-slot-list/TimeSlotList.d.ts +39 -7
- package/dist/react/core/time-slot-list/TimeSlotList.js +56 -11
- package/dist/react/time-slot-list/TimeSlot.js +3 -3
- package/dist/react/time-slot-list/TimeSlotList.d.ts +55 -12
- package/dist/react/time-slot-list/TimeSlotList.js +47 -17
- package/dist/services/booking/booking.d.ts +48 -30
- package/dist/services/booking/booking.js +74 -44
- package/dist/services/index.d.ts +3 -3
- package/dist/services/service/service.js +10 -7
- package/dist/services/time-slot-list/index.d.ts +1 -1
- package/dist/services/time-slot-list/time-slot-list.d.ts +3 -3
- package/dist/services/time-slot-list/time-slot-list.def.d.ts +23 -4
- package/dist/services/time-slot-list/time-slot-list.js +146 -65
- package/package.json +3 -2
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Defines interfaces and service contract for time slot management
|
|
4
4
|
*/
|
|
5
5
|
import type { Signal } from '@wix/services-definitions/core-services/signals';
|
|
6
|
-
import type { TimeSlot as ApiTimeSlot } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
6
|
+
import type { TimeSlot as ApiTimeSlot, Location } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
7
7
|
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
8
8
|
export declare enum ViewMode {
|
|
9
9
|
CLASSIC = "classic",
|
|
@@ -42,6 +42,26 @@ export interface SlotSelection {
|
|
|
42
42
|
locationId: string | null;
|
|
43
43
|
staffMemberId: string | null;
|
|
44
44
|
}
|
|
45
|
+
/**
|
|
46
|
+
* Filters for narrowing down available time slots
|
|
47
|
+
* Matches Wix Bookings API filter parameters
|
|
48
|
+
*/
|
|
49
|
+
export interface TimeSlotsFilters {
|
|
50
|
+
/** Filter by specific locations */
|
|
51
|
+
locations?: Location[];
|
|
52
|
+
/** Filter by specific staff member IDs */
|
|
53
|
+
staffMemberIds?: string[];
|
|
54
|
+
/** Maximum slots to return per day (useful for calendar views) */
|
|
55
|
+
slotsPerDay?: number;
|
|
56
|
+
/** Filter by bookable status. If undefined, returns both bookable and non-bookable */
|
|
57
|
+
bookable?: boolean;
|
|
58
|
+
/** Filter by booking policy violations */
|
|
59
|
+
bookingPolicyViolations?: {
|
|
60
|
+
tooEarlyToBook?: boolean;
|
|
61
|
+
tooLateToBook?: boolean;
|
|
62
|
+
bookOnlineDisabled?: boolean;
|
|
63
|
+
};
|
|
64
|
+
}
|
|
45
65
|
export interface TimeSlotListServiceConfig {
|
|
46
66
|
/** Services to fetch time slots for */
|
|
47
67
|
services?: Service[];
|
|
@@ -53,8 +73,8 @@ export interface TimeSlotListServiceConfig {
|
|
|
53
73
|
endDate?: Date;
|
|
54
74
|
/** Timezone for formatting */
|
|
55
75
|
timezone?: string;
|
|
56
|
-
/**
|
|
57
|
-
|
|
76
|
+
/** Filters for narrowing down time slots */
|
|
77
|
+
filters?: TimeSlotsFilters;
|
|
58
78
|
}
|
|
59
79
|
export interface TimeSlotListServiceActions {
|
|
60
80
|
selectSlot: (slot: TimeSlot) => void;
|
|
@@ -71,7 +91,6 @@ export interface TimeSlotListServiceAPI {
|
|
|
71
91
|
isLoading: Signal<boolean>;
|
|
72
92
|
error: Signal<string | null>;
|
|
73
93
|
hasMore: Signal<boolean>;
|
|
74
|
-
viewMode: Signal<ViewMode>;
|
|
75
94
|
startDate: Signal<Date>;
|
|
76
95
|
endDate: Signal<Date>;
|
|
77
96
|
timezone: Signal<string | undefined>;
|
|
@@ -6,7 +6,7 @@ import { implementService } from '@wix/services-definitions';
|
|
|
6
6
|
import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
|
|
7
7
|
import { TimeSlotListServiceDefinition, ViewMode, } from './time-slot-list.def.js';
|
|
8
8
|
import { fetchAvailability } from '../../api/time-slot-list/index.js';
|
|
9
|
-
import { BookingServiceDefinition } from '../booking/booking.js';
|
|
9
|
+
import { BookingServiceDefinition, } from '../booking/booking.js';
|
|
10
10
|
// ============================================================================
|
|
11
11
|
// Helpers
|
|
12
12
|
// ============================================================================
|
|
@@ -68,7 +68,8 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
68
68
|
const isLoadingSignal = signalsService.signal(false);
|
|
69
69
|
const errorSignal = signalsService.signal(null);
|
|
70
70
|
const hasMoreSignal = signalsService.signal(false);
|
|
71
|
-
|
|
71
|
+
// Internal only - viewMode is auto-detected based on service types
|
|
72
|
+
const viewModeSignal = signalsService.signal(ViewMode.CLASSIC);
|
|
72
73
|
const startDateSignal = signalsService.signal(config.startDate || currentDate);
|
|
73
74
|
const endDateSignal = signalsService.signal(config.endDate || getDefaultEndDate());
|
|
74
75
|
const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
|
|
@@ -79,7 +80,29 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
79
80
|
const getServices = () => {
|
|
80
81
|
if (config.services?.length)
|
|
81
82
|
return config.services;
|
|
82
|
-
return bookingService.
|
|
83
|
+
return bookingService.bookingItems.get().map((s) => s.service);
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Helper: Get filters from config or BookingService
|
|
87
|
+
*/
|
|
88
|
+
const getFilters = () => {
|
|
89
|
+
// Get BookingService selections (these take priority)
|
|
90
|
+
const location = bookingService.location.get();
|
|
91
|
+
const selections = bookingService.bookingItems.get();
|
|
92
|
+
const selectedStaffMemberIds = selections
|
|
93
|
+
.map(({ resources }) => resources.map((r) => r._id))
|
|
94
|
+
.flat();
|
|
95
|
+
const selectedLocation = location ? { _id: location.id } : null;
|
|
96
|
+
// Merge config filters with BookingService selections (selections win)
|
|
97
|
+
return {
|
|
98
|
+
...config.filters,
|
|
99
|
+
locations: selectedLocation
|
|
100
|
+
? [selectedLocation]
|
|
101
|
+
: config.filters?.locations,
|
|
102
|
+
staffMemberIds: selectedStaffMemberIds.length
|
|
103
|
+
? selectedStaffMemberIds
|
|
104
|
+
: config.filters?.staffMemberIds,
|
|
105
|
+
};
|
|
83
106
|
};
|
|
84
107
|
/**
|
|
85
108
|
* Fetch time slots from API
|
|
@@ -87,6 +110,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
87
110
|
*/
|
|
88
111
|
const fetchTimeSlots = async () => {
|
|
89
112
|
const selectedServices = getServices();
|
|
113
|
+
const filters = getFilters();
|
|
90
114
|
isLoadingSignal.set(true);
|
|
91
115
|
errorSignal.set(null);
|
|
92
116
|
cursorSignal.set(undefined);
|
|
@@ -97,6 +121,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
97
121
|
toLocalDate: toLocalDateString(endDateSignal.get()),
|
|
98
122
|
timeZone: timezoneSignal.get(),
|
|
99
123
|
viewMode: viewModeSignal.get(),
|
|
124
|
+
...filters,
|
|
100
125
|
});
|
|
101
126
|
timeSlotsSignal.set(response.timeSlots);
|
|
102
127
|
const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
|
|
@@ -115,82 +140,139 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
115
140
|
};
|
|
116
141
|
/**
|
|
117
142
|
* Action: Select a time slot
|
|
118
|
-
*
|
|
119
|
-
*
|
|
143
|
+
* Auto-selects location/staff based on availability:
|
|
144
|
+
* - One location + one staff → auto-select both
|
|
145
|
+
* - One location + many staff → auto-select location only
|
|
146
|
+
* - Many locations + one staff → auto-select staff only
|
|
147
|
+
* - Many locations + many staff → don't auto-select anything
|
|
120
148
|
*/
|
|
121
149
|
const selectSlot = (slot) => {
|
|
122
150
|
const currentSelection = selectionSignal.get();
|
|
123
151
|
if (currentSelection?.slot?.id === slot.id)
|
|
124
152
|
return;
|
|
153
|
+
const timeSlot = {
|
|
154
|
+
startDate: slot.localStartDate,
|
|
155
|
+
endDate: slot.localEndDate,
|
|
156
|
+
totalCapacity: slot.totalCapacity ?? undefined,
|
|
157
|
+
remainingCapacity: slot.remainingCapacity ?? undefined,
|
|
158
|
+
scheduleId: slot.scheduleId ?? undefined,
|
|
159
|
+
eventInfo: slot.eventInfo ?? undefined,
|
|
160
|
+
};
|
|
125
161
|
const hasOneLocation = slot.staffByLocation.length === 1;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
bookingService.actions.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
162
|
+
// Collect all unique staff members across all locations
|
|
163
|
+
const allStaffMembers = slot.staffByLocation.flatMap((loc) => loc.staffMembers);
|
|
164
|
+
const uniqueStaffIds = new Set(allStaffMembers.map((s) => s.id));
|
|
165
|
+
const hasOneStaff = uniqueStaffIds.size === 1;
|
|
166
|
+
// Determine what to auto-select
|
|
167
|
+
let locationId = null;
|
|
168
|
+
let staffMemberId = null;
|
|
169
|
+
if (hasOneLocation) {
|
|
170
|
+
const { location } = slot.staffByLocation[0];
|
|
171
|
+
locationId = location.id;
|
|
172
|
+
bookingService.actions.setLocation(location);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
bookingService.actions.clearLocation();
|
|
176
|
+
}
|
|
177
|
+
if (hasOneStaff) {
|
|
178
|
+
const staffMember = allStaffMembers[0];
|
|
179
|
+
staffMemberId = staffMember.id;
|
|
180
|
+
bookingService.actions.updateItem({
|
|
181
|
+
timeSlot,
|
|
182
|
+
resources: [
|
|
183
|
+
{
|
|
184
|
+
_id: staffMemberId,
|
|
185
|
+
name: staffMember.name,
|
|
186
|
+
},
|
|
187
|
+
],
|
|
146
188
|
});
|
|
147
189
|
}
|
|
148
190
|
else {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
staffMemberId: null,
|
|
191
|
+
bookingService.actions.updateItem({
|
|
192
|
+
timeSlot,
|
|
193
|
+
resources: [],
|
|
153
194
|
});
|
|
154
195
|
}
|
|
196
|
+
selectionSignal.set({
|
|
197
|
+
slot,
|
|
198
|
+
locationId,
|
|
199
|
+
staffMemberId,
|
|
200
|
+
});
|
|
155
201
|
};
|
|
156
202
|
/**
|
|
157
203
|
* Action: Select a location for the current slot
|
|
158
|
-
* Updates the booking service with the
|
|
204
|
+
* Updates the booking service with the location
|
|
205
|
+
* If the location has only one staff member, auto-selects that staff member too
|
|
159
206
|
*/
|
|
160
207
|
const selectLocation = (locationId) => {
|
|
161
208
|
const currentSelections = selectionSignal.get();
|
|
162
209
|
if (!currentSelections?.slot)
|
|
163
210
|
return;
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
211
|
+
const slot = currentSelections.slot;
|
|
212
|
+
const locationEntry = slot.staffByLocation.find((loc) => loc.location.id === locationId);
|
|
213
|
+
if (!locationEntry)
|
|
214
|
+
return;
|
|
215
|
+
const { location, staffMembers } = locationEntry;
|
|
216
|
+
bookingService.actions.setLocation(location);
|
|
217
|
+
// If this location has only one staff member, auto-select them
|
|
218
|
+
const hasOneStaff = staffMembers.length === 1;
|
|
219
|
+
if (hasOneStaff) {
|
|
220
|
+
const staffMember = staffMembers[0];
|
|
221
|
+
selectionSignal.set({
|
|
222
|
+
...currentSelections,
|
|
223
|
+
locationId,
|
|
224
|
+
staffMemberId: staffMember.id,
|
|
225
|
+
});
|
|
226
|
+
bookingService.actions.updateItem({
|
|
227
|
+
resources: [
|
|
228
|
+
{
|
|
229
|
+
_id: staffMember.id,
|
|
230
|
+
name: staffMember.name,
|
|
231
|
+
},
|
|
232
|
+
],
|
|
170
233
|
});
|
|
171
234
|
}
|
|
235
|
+
else {
|
|
236
|
+
selectionSignal.set({ ...currentSelections, locationId });
|
|
237
|
+
}
|
|
172
238
|
};
|
|
173
239
|
/**
|
|
174
240
|
* Action: Select a staff member for the current slot
|
|
175
|
-
* Updates the booking service with the selected resource
|
|
241
|
+
* Updates the booking service with the selected resource
|
|
242
|
+
* If the staff member is only available at one location, auto-selects that location too
|
|
176
243
|
*/
|
|
177
244
|
const selectStaffMember = (staffMemberId) => {
|
|
178
245
|
const currentSelections = selectionSignal.get();
|
|
179
246
|
if (!currentSelections?.slot)
|
|
180
247
|
return;
|
|
181
|
-
selectionSignal.set({ ...currentSelections, staffMemberId });
|
|
182
248
|
const slot = currentSelections.slot;
|
|
183
|
-
// Find
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
249
|
+
// Find all locations that have this staff member
|
|
250
|
+
const locationsWithThisStaff = slot.staffByLocation.filter((loc) => loc.staffMembers.some((s) => s.id === staffMemberId));
|
|
251
|
+
// Find the staff member info
|
|
252
|
+
const staffMember = locationsWithThisStaff[0]?.staffMembers.find((s) => s.id === staffMemberId);
|
|
253
|
+
if (!staffMember)
|
|
254
|
+
return;
|
|
255
|
+
bookingService.actions.updateItem({
|
|
256
|
+
resources: [
|
|
257
|
+
{
|
|
258
|
+
_id: staffMember.id,
|
|
259
|
+
name: staffMember.name,
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
});
|
|
263
|
+
// If this staff member is only at one location, auto-select that location
|
|
264
|
+
const hasOneLocation = locationsWithThisStaff.length === 1;
|
|
265
|
+
if (hasOneLocation) {
|
|
266
|
+
const location = locationsWithThisStaff[0].location;
|
|
267
|
+
selectionSignal.set({
|
|
268
|
+
...currentSelections,
|
|
269
|
+
staffMemberId,
|
|
270
|
+
locationId: location.id,
|
|
271
|
+
});
|
|
272
|
+
bookingService.actions.setLocation(location);
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
selectionSignal.set({ ...currentSelections, staffMemberId });
|
|
194
276
|
}
|
|
195
277
|
};
|
|
196
278
|
/**
|
|
@@ -206,15 +288,11 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
206
288
|
locationId: null,
|
|
207
289
|
staffMemberId: null,
|
|
208
290
|
});
|
|
209
|
-
// Clear
|
|
291
|
+
// Clear selections in booking service
|
|
210
292
|
bookingService.actions.clearLocation();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
bookingService.actions.updateBookingSelection({
|
|
215
|
-
staff: undefined,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
293
|
+
bookingService.actions.updateItem({
|
|
294
|
+
resources: [],
|
|
295
|
+
});
|
|
218
296
|
};
|
|
219
297
|
/**
|
|
220
298
|
* Action: Load more slots (pagination)
|
|
@@ -226,6 +304,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
226
304
|
const selectedServices = getServices();
|
|
227
305
|
if (selectedServices.length === 0)
|
|
228
306
|
return;
|
|
307
|
+
const filters = getFilters();
|
|
229
308
|
isLoadingSignal.set(true);
|
|
230
309
|
try {
|
|
231
310
|
const response = await fetchAvailability({
|
|
@@ -235,6 +314,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
235
314
|
timeZone: timezoneSignal.get(),
|
|
236
315
|
cursorPaging: { cursor },
|
|
237
316
|
viewMode: viewModeSignal.get(),
|
|
317
|
+
...filters,
|
|
238
318
|
});
|
|
239
319
|
// API returns grouped slots, merge with existing
|
|
240
320
|
const existing = timeSlotsSignal.get();
|
|
@@ -257,6 +337,12 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
257
337
|
* Action: Update date range and refetch slots
|
|
258
338
|
*/
|
|
259
339
|
const setDateRange = (newStartDate, newEndDate) => {
|
|
340
|
+
const currentStartDate = startDateSignal.get();
|
|
341
|
+
const currentEndDate = endDateSignal.get();
|
|
342
|
+
if (currentStartDate.getTime() === newStartDate.getTime() &&
|
|
343
|
+
currentEndDate.getTime() === newEndDate.getTime()) {
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
260
346
|
startDateSignal.set(newStartDate);
|
|
261
347
|
endDateSignal.set(newEndDate);
|
|
262
348
|
fetchTimeSlots();
|
|
@@ -280,8 +366,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
280
366
|
isLoading: isLoadingSignal,
|
|
281
367
|
error: errorSignal,
|
|
282
368
|
hasMore: hasMoreSignal,
|
|
283
|
-
// Configuration Signals
|
|
284
|
-
viewMode: viewModeSignal,
|
|
369
|
+
// Configuration Signals (viewMode is internal only)
|
|
285
370
|
startDate: startDateSignal,
|
|
286
371
|
endDate: endDateSignal,
|
|
287
372
|
timezone: timezoneSignal,
|
|
@@ -322,23 +407,19 @@ export async function loadTimeSlotListConfig(options) {
|
|
|
322
407
|
const now = new Date();
|
|
323
408
|
const startDate = options.startDate || now;
|
|
324
409
|
const endDate = options.endDate || getDefaultEndDate();
|
|
325
|
-
const viewMode = options.viewMode || ViewMode.CLASSIC;
|
|
326
|
-
// check for location and staff in config
|
|
327
410
|
try {
|
|
328
411
|
const response = await fetchAvailability({
|
|
329
412
|
services: options.services || [],
|
|
330
413
|
fromLocalDate: toLocalDateString(startDate),
|
|
331
414
|
toLocalDate: toLocalDateString(endDate),
|
|
332
415
|
timeZone: options.timezone ?? getClientTimezone(),
|
|
333
|
-
|
|
416
|
+
...options.filters,
|
|
334
417
|
});
|
|
335
418
|
const config = {
|
|
336
|
-
|
|
419
|
+
...options,
|
|
337
420
|
timeSlots: response.timeSlots || [],
|
|
338
421
|
startDate,
|
|
339
422
|
endDate,
|
|
340
|
-
timezone: options.timezone,
|
|
341
|
-
viewMode,
|
|
342
423
|
};
|
|
343
424
|
return { type: 'success', config };
|
|
344
425
|
}
|
|
@@ -11,8 +11,20 @@ export interface FetchAvailabilityOptions {
|
|
|
11
11
|
toLocalDate?: string | null;
|
|
12
12
|
timeZone?: string | null;
|
|
13
13
|
locations?: Location[];
|
|
14
|
+
/** Filter by specific staff member IDs (passed as resourceIds to API) */
|
|
15
|
+
staffMemberIds?: string[];
|
|
14
16
|
cursorPaging?: CursorPaging;
|
|
15
17
|
viewMode?: ViewMode;
|
|
18
|
+
/** Maximum slots to return per day (useful for calendar views) */
|
|
19
|
+
slotsPerDay?: number;
|
|
20
|
+
/** Filter by bookable status. If undefined, returns both bookable and non-bookable */
|
|
21
|
+
bookable?: boolean;
|
|
22
|
+
/** Filter by booking policy violations */
|
|
23
|
+
bookingPolicyViolations?: {
|
|
24
|
+
tooEarlyToBook?: boolean;
|
|
25
|
+
tooLateToBook?: boolean;
|
|
26
|
+
bookOnlineDisabled?: boolean;
|
|
27
|
+
};
|
|
16
28
|
}
|
|
17
29
|
/**
|
|
18
30
|
* Response from fetchAvailability with grouped time slots
|
|
@@ -42,12 +54,20 @@ export interface FetchAvailabilityResponse {
|
|
|
42
54
|
* // response.timeSlots are grouped by time
|
|
43
55
|
* ```
|
|
44
56
|
*/
|
|
45
|
-
export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone, locations, cursorPaging, }: {
|
|
57
|
+
export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursorPaging, slotsPerDay, bookable, bookingPolicyViolations, }: {
|
|
46
58
|
services: Service[];
|
|
47
59
|
fromLocalDate: string;
|
|
48
60
|
toLocalDate: string;
|
|
49
61
|
timeZone?: string;
|
|
50
62
|
locations?: Location[];
|
|
63
|
+
staffMemberIds?: string[];
|
|
51
64
|
cursorPaging?: CursorPaging;
|
|
52
65
|
viewMode?: ViewMode;
|
|
66
|
+
slotsPerDay?: number;
|
|
67
|
+
bookable?: boolean;
|
|
68
|
+
bookingPolicyViolations?: {
|
|
69
|
+
tooEarlyToBook?: boolean;
|
|
70
|
+
tooLateToBook?: boolean;
|
|
71
|
+
bookOnlineDisabled?: boolean;
|
|
72
|
+
};
|
|
53
73
|
}): Promise<FetchAvailabilityResponse>;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Fetches available time slots for booking services from Wix Bookings
|
|
4
4
|
*/
|
|
5
5
|
import { listAvailabilityTimeSlots, } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
6
|
+
import { listEventTimeSlots } from '@wix/auto_sdk_bookings_event-time-slots';
|
|
6
7
|
import { ServiceType } from '@wix/auto_sdk_bookings_services';
|
|
7
8
|
import { ViewMode, } from '../../services/time-slot-list/time-slot-list.def.js';
|
|
8
9
|
// ============================================================================
|
|
@@ -101,20 +102,31 @@ function detectFlow(bookingServices, viewMode) {
|
|
|
101
102
|
* // response.timeSlots are grouped by time
|
|
102
103
|
* ```
|
|
103
104
|
*/
|
|
104
|
-
export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone, locations, cursorPaging, }) {
|
|
105
|
+
export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursorPaging, slotsPerDay, bookable, bookingPolicyViolations, }) {
|
|
105
106
|
const flow = detectFlow(selectedServices, viewMode);
|
|
107
|
+
const serviceIds = selectedServices.map((s) => s._id);
|
|
106
108
|
if (flow === FlowType.SINGLE_SERVICE) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
109
|
+
// Build resourceTypes filter for staff members
|
|
110
|
+
// Staff member resource type ID is 1cd44cf8-756f-41c3-bd90-3e2ffcaf1155
|
|
111
|
+
const resourceTypes = staffMemberIds?.length
|
|
112
|
+
? [
|
|
113
|
+
{
|
|
114
|
+
resourceTypeId: '1cd44cf8-756f-41c3-bd90-3e2ffcaf1155',
|
|
115
|
+
resourceIds: staffMemberIds,
|
|
116
|
+
},
|
|
117
|
+
]
|
|
118
|
+
: undefined;
|
|
111
119
|
const response = await listAvailabilityTimeSlots({
|
|
112
|
-
serviceId,
|
|
120
|
+
serviceId: serviceIds[0],
|
|
113
121
|
fromLocalDate,
|
|
114
122
|
toLocalDate,
|
|
115
123
|
timeZone,
|
|
116
124
|
locations,
|
|
117
125
|
cursorPaging,
|
|
126
|
+
resourceTypes,
|
|
127
|
+
timeSlotsPerDay: slotsPerDay,
|
|
128
|
+
bookable,
|
|
129
|
+
bookingPolicyViolations,
|
|
118
130
|
});
|
|
119
131
|
return {
|
|
120
132
|
timeSlots: groupTimeSlotsByTime(response.timeSlots || []),
|
|
@@ -126,8 +138,34 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
|
|
|
126
138
|
throw new Error('Multi-service appointments not yet supported');
|
|
127
139
|
}
|
|
128
140
|
if (flow === FlowType.CLASS) {
|
|
129
|
-
//
|
|
130
|
-
|
|
141
|
+
// Build eventFilter for location and staff filtering
|
|
142
|
+
const locationIds = locations?.map((l) => l._id);
|
|
143
|
+
const eventFilter = {};
|
|
144
|
+
if (locationIds?.length) {
|
|
145
|
+
eventFilter['location.id'] = locationIds;
|
|
146
|
+
}
|
|
147
|
+
if (staffMemberIds?.length) {
|
|
148
|
+
eventFilter['resources.id'] = { $hasSome: staffMemberIds };
|
|
149
|
+
}
|
|
150
|
+
const response = await listEventTimeSlots({
|
|
151
|
+
fromLocalDate,
|
|
152
|
+
toLocalDate,
|
|
153
|
+
timeZone,
|
|
154
|
+
serviceIds: serviceIds.length ? serviceIds : undefined,
|
|
155
|
+
cursorPaging,
|
|
156
|
+
eventFilter: Object.keys(eventFilter).length ? eventFilter : undefined,
|
|
157
|
+
maxSlotsPerDay: slotsPerDay,
|
|
158
|
+
// bookable=true means only bookable, so includeNonBookable=false
|
|
159
|
+
// bookable=false means only non-bookable, so includeNonBookable=true
|
|
160
|
+
// bookable=undefined means both, so includeNonBookable=true (default)
|
|
161
|
+
includeNonBookable: bookable === undefined ? undefined : !bookable,
|
|
162
|
+
bookingPolicyViolations,
|
|
163
|
+
});
|
|
164
|
+
return {
|
|
165
|
+
timeSlots: groupTimeSlotsByTime(response.timeSlots || []),
|
|
166
|
+
// Note: listEventTimeSlots uses pagingMetadata instead of cursorPagingMetadata
|
|
167
|
+
cursorPagingMetadata: response.pagingMetadata,
|
|
168
|
+
};
|
|
131
169
|
}
|
|
132
170
|
throw new Error(`Unknown flow type: ${flow}`);
|
|
133
171
|
}
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Provides low-level access to BookingService via render props and service setup
|
|
4
4
|
*/
|
|
5
5
|
import React from 'react';
|
|
6
|
-
import { type BookingServiceConfig, type
|
|
7
|
-
import type {
|
|
6
|
+
import { type BookingServiceConfig, type BookingItem, type Location, type BookingServiceActions } from '../../../services/booking/booking.js';
|
|
7
|
+
import type { FormValues } from '@wix/form-public';
|
|
8
8
|
/**
|
|
9
9
|
* Props for Booking.Root component
|
|
10
10
|
*/
|
|
@@ -28,14 +28,22 @@ export interface RootProps {
|
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
30
|
export declare function Root(props: RootProps): React.ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* Data exposed by Booking.Data component
|
|
33
|
+
* Mirrors BookingServiceConfig structure with actions
|
|
34
|
+
*/
|
|
35
|
+
export interface BookingData {
|
|
36
|
+
bookingItems: BookingItem[];
|
|
37
|
+
location: Location | null;
|
|
38
|
+
formValues: FormValues | null;
|
|
39
|
+
numberOfParticipants: number;
|
|
40
|
+
actions: BookingServiceActions;
|
|
41
|
+
}
|
|
31
42
|
/**
|
|
32
43
|
* Props for Data render prop component
|
|
33
44
|
*/
|
|
34
45
|
export interface DataProps {
|
|
35
|
-
children: (data:
|
|
36
|
-
bookingSelections: BookingSelection[];
|
|
37
|
-
selectedTimeSlot: TimeSlot | null;
|
|
38
|
-
}) => React.ReactNode;
|
|
46
|
+
children: (data: BookingData) => React.ReactNode;
|
|
39
47
|
}
|
|
40
48
|
/**
|
|
41
49
|
* Core component that provides access to booking data via render props.
|
|
@@ -45,9 +53,9 @@ export interface DataProps {
|
|
|
45
53
|
* @example
|
|
46
54
|
* ```tsx
|
|
47
55
|
* <Booking.Data>
|
|
48
|
-
* {({
|
|
49
|
-
*
|
|
50
|
-
* <div>{
|
|
56
|
+
* {({ bookingItems, location, formValues, numberOfParticipants, actions }) => (
|
|
57
|
+
* bookingItems.length > 0 ? (
|
|
58
|
+
* <div>{bookingItems.map(item => item.service.name).join(', ')}</div>
|
|
51
59
|
* ) : (
|
|
52
60
|
* <div>No services selected</div>
|
|
53
61
|
* )
|
|
@@ -28,9 +28,9 @@ export function Root(props) {
|
|
|
28
28
|
* @example
|
|
29
29
|
* ```tsx
|
|
30
30
|
* <Booking.Data>
|
|
31
|
-
* {({
|
|
32
|
-
*
|
|
33
|
-
* <div>{
|
|
31
|
+
* {({ bookingItems, location, formValues, numberOfParticipants, actions }) => (
|
|
32
|
+
* bookingItems.length > 0 ? (
|
|
33
|
+
* <div>{bookingItems.map(item => item.service.name).join(', ')}</div>
|
|
34
34
|
* ) : (
|
|
35
35
|
* <div>No services selected</div>
|
|
36
36
|
* )
|
|
@@ -41,10 +41,16 @@ export function Root(props) {
|
|
|
41
41
|
export function Data(props) {
|
|
42
42
|
const { children } = props;
|
|
43
43
|
const bookingService = useService(BookingServiceDefinition);
|
|
44
|
-
const
|
|
45
|
-
const
|
|
44
|
+
const bookingItems = bookingService.bookingItems.get();
|
|
45
|
+
const location = bookingService.location.get();
|
|
46
|
+
const formValues = bookingService.formValues.get();
|
|
47
|
+
const numberOfParticipants = bookingService.numberOfParticipants.get();
|
|
48
|
+
const { actions } = bookingService;
|
|
46
49
|
return children({
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
bookingItems,
|
|
51
|
+
location,
|
|
52
|
+
formValues,
|
|
53
|
+
numberOfParticipants,
|
|
54
|
+
actions,
|
|
49
55
|
});
|
|
50
56
|
}
|