@wix/headless-bookings 0.0.36 → 0.0.38
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.
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Manages time slot fetching, selection, and state
|
|
4
4
|
*/
|
|
5
5
|
import { implementService } from '@wix/services-definitions';
|
|
6
|
-
import { SignalsServiceDefinition
|
|
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/fetch-availability/index.js';
|
|
9
9
|
import { BookingServiceDefinition, ANY_STAFF_MEMBER_ID, } from '../booking/booking.js';
|
|
@@ -15,31 +15,6 @@ function getClientTimezone() {
|
|
|
15
15
|
? Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
16
16
|
: undefined;
|
|
17
17
|
}
|
|
18
|
-
/** Compare two string arrays for equality (order-independent) */
|
|
19
|
-
function arraysEqual(a, b) {
|
|
20
|
-
if (a.length !== b.length)
|
|
21
|
-
return false;
|
|
22
|
-
const sortedA = [...a].sort();
|
|
23
|
-
const sortedB = [...b].sort();
|
|
24
|
-
return sortedA.every((val, i) => val === sortedB[i]);
|
|
25
|
-
}
|
|
26
|
-
/** Create a memoized value that only updates when IDs change */
|
|
27
|
-
function createMemoizedByIds({ fallback, getValue, extractId, }) {
|
|
28
|
-
let cachedIds = [];
|
|
29
|
-
let cachedValue = fallback;
|
|
30
|
-
return () => {
|
|
31
|
-
const value = getValue();
|
|
32
|
-
const items = value ?? [];
|
|
33
|
-
const newIds = items.length && typeof items[0] === 'string'
|
|
34
|
-
? items
|
|
35
|
-
: items.map(extractId);
|
|
36
|
-
if (!arraysEqual(newIds, cachedIds)) {
|
|
37
|
-
cachedIds = newIds;
|
|
38
|
-
cachedValue = items.length ? value : fallback;
|
|
39
|
-
}
|
|
40
|
-
return cachedValue;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
18
|
// ============================================================================
|
|
44
19
|
// Service Implementation
|
|
45
20
|
// ============================================================================
|
|
@@ -59,30 +34,24 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
59
34
|
end: config.endDate,
|
|
60
35
|
});
|
|
61
36
|
const cursorSignal = signalsService.signal(undefined);
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
getValue: () => (bookingService.serviceSelections.get()[0]?.resources ?? []).map((resource) => resource._id),
|
|
70
|
-
}));
|
|
71
|
-
const filtersSignal = signalsService.computed(() => {
|
|
37
|
+
const getServices = () => {
|
|
38
|
+
const serviceSelections = bookingService.serviceSelections.peek();
|
|
39
|
+
return serviceSelections.length
|
|
40
|
+
? serviceSelections.map((s) => s.service)
|
|
41
|
+
: (config.services ?? []);
|
|
42
|
+
};
|
|
43
|
+
const getFilters = () => {
|
|
72
44
|
const baseFilters = config.filters ?? {};
|
|
73
|
-
const selectedLocation = bookingService.location.
|
|
45
|
+
const selectedLocation = bookingService.location.peek();
|
|
74
46
|
const location = selectedLocation
|
|
75
|
-
? {
|
|
76
|
-
id: selectedLocation._id,
|
|
77
|
-
locationType: selectedLocation.type,
|
|
78
|
-
}
|
|
47
|
+
? { id: selectedLocation._id, locationType: selectedLocation.type }
|
|
79
48
|
: baseFilters.location;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
};
|
|
85
|
-
}
|
|
49
|
+
const selectedResources = bookingService.serviceSelections.peek()[0]?.resources ?? [];
|
|
50
|
+
const staffMemberIds = selectedResources.length
|
|
51
|
+
? selectedResources.map((r) => r._id)
|
|
52
|
+
: baseFilters.staffMemberIds;
|
|
53
|
+
return { ...baseFilters, location, staffMemberIds };
|
|
54
|
+
};
|
|
86
55
|
const fetchTimeSlots = async () => {
|
|
87
56
|
isLoadingSignal.set(true);
|
|
88
57
|
errorSignal.set(null);
|
|
@@ -90,12 +59,12 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
90
59
|
timeSlotsSignal.set([]);
|
|
91
60
|
try {
|
|
92
61
|
const response = await fetchAvailability({
|
|
93
|
-
services:
|
|
94
|
-
timeZone: bookingService.timezone.
|
|
95
|
-
viewMode: viewModeSignal.
|
|
96
|
-
fromLocalDate: dateRangeSignal.
|
|
97
|
-
toLocalDate: dateRangeSignal.
|
|
98
|
-
...
|
|
62
|
+
services: getServices(),
|
|
63
|
+
timeZone: bookingService.timezone.peek(),
|
|
64
|
+
viewMode: viewModeSignal.peek(),
|
|
65
|
+
fromLocalDate: dateRangeSignal.peek().start,
|
|
66
|
+
toLocalDate: dateRangeSignal.peek().end,
|
|
67
|
+
...getFilters(),
|
|
99
68
|
});
|
|
100
69
|
timeSlotsSignal.set(response.timeSlots);
|
|
101
70
|
const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
|
|
@@ -143,13 +112,13 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
143
112
|
* Updates the booking service with the selected resource
|
|
144
113
|
*/
|
|
145
114
|
const selectStaffMember = (staffMemberId) => {
|
|
146
|
-
const selectedTimeSlot = bookingService.serviceSelections.
|
|
115
|
+
const selectedTimeSlot = bookingService.serviceSelections.peek()[0]?.timeSlot;
|
|
147
116
|
if (!selectedTimeSlot)
|
|
148
117
|
return;
|
|
149
118
|
const getStaffName = (id) => {
|
|
150
119
|
if (id === ANY_STAFF_MEMBER_ID)
|
|
151
120
|
return 'Any staff member';
|
|
152
|
-
const slots = timeSlotsSignal.
|
|
121
|
+
const slots = timeSlotsSignal.peek();
|
|
153
122
|
return (slots
|
|
154
123
|
.flatMap((slot) => slot.availableResources[0].resources)
|
|
155
124
|
.find((r) => r._id === id)?.name ?? undefined);
|
|
@@ -171,7 +140,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
171
140
|
* Action: Clear staff selection while keeping the selected time slot
|
|
172
141
|
*/
|
|
173
142
|
const clearStaffSelection = () => {
|
|
174
|
-
const selectedTimeSlot = bookingService.serviceSelections.
|
|
143
|
+
const selectedTimeSlot = bookingService.serviceSelections.peek()[0]?.timeSlot;
|
|
175
144
|
if (!selectedTimeSlot)
|
|
176
145
|
return;
|
|
177
146
|
bookingService.actions.updateItem({
|
|
@@ -182,7 +151,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
182
151
|
* Action: Load more slots (pagination)
|
|
183
152
|
*/
|
|
184
153
|
const loadMore = async () => {
|
|
185
|
-
const cursor = cursorSignal.
|
|
154
|
+
const cursor = cursorSignal.peek();
|
|
186
155
|
if (!cursor) {
|
|
187
156
|
return;
|
|
188
157
|
}
|
|
@@ -190,16 +159,16 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
190
159
|
errorSignal.set(null);
|
|
191
160
|
try {
|
|
192
161
|
const response = await fetchAvailability({
|
|
193
|
-
services:
|
|
194
|
-
fromLocalDate: dateRangeSignal.
|
|
195
|
-
toLocalDate: dateRangeSignal.
|
|
196
|
-
timeZone: bookingService.timezone.
|
|
197
|
-
viewMode: viewModeSignal.
|
|
162
|
+
services: getServices(),
|
|
163
|
+
fromLocalDate: dateRangeSignal.peek().start,
|
|
164
|
+
toLocalDate: dateRangeSignal.peek().end,
|
|
165
|
+
timeZone: bookingService.timezone.peek(),
|
|
166
|
+
viewMode: viewModeSignal.peek(),
|
|
198
167
|
cursor,
|
|
199
|
-
...
|
|
168
|
+
...getFilters(),
|
|
200
169
|
});
|
|
201
170
|
const newSlots = response.timeSlots;
|
|
202
|
-
timeSlotsSignal.set([...timeSlotsSignal.
|
|
171
|
+
timeSlotsSignal.set([...timeSlotsSignal.peek(), ...newSlots]);
|
|
203
172
|
const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
|
|
204
173
|
cursorSignal.set(nextCursor);
|
|
205
174
|
hasMoreSignal.set(!!nextCursor);
|
|
@@ -218,18 +187,26 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
218
187
|
* Action: Update date range (effect will handle refetch)
|
|
219
188
|
*/
|
|
220
189
|
const setDateRange = (newStartDate, newEndDate) => {
|
|
221
|
-
const { start: currentStartDate, end: currentEndDate } = dateRangeSignal.
|
|
190
|
+
const { start: currentStartDate, end: currentEndDate } = dateRangeSignal.peek();
|
|
222
191
|
if (currentStartDate.getTime() === newStartDate.getTime() &&
|
|
223
192
|
currentEndDate.getTime() === newEndDate.getTime()) {
|
|
224
193
|
return;
|
|
225
194
|
}
|
|
226
195
|
dateRangeSignal.set({ start: newStartDate, end: newEndDate });
|
|
227
196
|
};
|
|
197
|
+
const preSelectedStaffMemberId = signalsService.computed(() => {
|
|
198
|
+
const serviceSelections = bookingService.serviceSelections.peek();
|
|
199
|
+
return serviceSelections.length
|
|
200
|
+
? (serviceSelections[0]?.resources?.[0]?._id ?? undefined)
|
|
201
|
+
: undefined;
|
|
202
|
+
});
|
|
228
203
|
let isFirstRun = true;
|
|
229
204
|
signalsService.effect(() => {
|
|
230
|
-
|
|
205
|
+
// Dependencies - trigger refetch when these change
|
|
231
206
|
dateRangeSignal.get();
|
|
232
207
|
bookingService.timezone.get();
|
|
208
|
+
bookingService.location.get();
|
|
209
|
+
preSelectedStaffMemberId.get();
|
|
233
210
|
if (isFirstRun) {
|
|
234
211
|
isFirstRun = false;
|
|
235
212
|
if (config.timeSlots) {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Manages time slot fetching, selection, and state
|
|
4
4
|
*/
|
|
5
5
|
import { implementService } from '@wix/services-definitions';
|
|
6
|
-
import { SignalsServiceDefinition
|
|
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/fetch-availability/index.js';
|
|
9
9
|
import { BookingServiceDefinition, ANY_STAFF_MEMBER_ID, } from '../booking/booking.js';
|
|
@@ -15,31 +15,6 @@ function getClientTimezone() {
|
|
|
15
15
|
? Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
16
16
|
: undefined;
|
|
17
17
|
}
|
|
18
|
-
/** Compare two string arrays for equality (order-independent) */
|
|
19
|
-
function arraysEqual(a, b) {
|
|
20
|
-
if (a.length !== b.length)
|
|
21
|
-
return false;
|
|
22
|
-
const sortedA = [...a].sort();
|
|
23
|
-
const sortedB = [...b].sort();
|
|
24
|
-
return sortedA.every((val, i) => val === sortedB[i]);
|
|
25
|
-
}
|
|
26
|
-
/** Create a memoized value that only updates when IDs change */
|
|
27
|
-
function createMemoizedByIds({ fallback, getValue, extractId, }) {
|
|
28
|
-
let cachedIds = [];
|
|
29
|
-
let cachedValue = fallback;
|
|
30
|
-
return () => {
|
|
31
|
-
const value = getValue();
|
|
32
|
-
const items = value ?? [];
|
|
33
|
-
const newIds = items.length && typeof items[0] === 'string'
|
|
34
|
-
? items
|
|
35
|
-
: items.map(extractId);
|
|
36
|
-
if (!arraysEqual(newIds, cachedIds)) {
|
|
37
|
-
cachedIds = newIds;
|
|
38
|
-
cachedValue = items.length ? value : fallback;
|
|
39
|
-
}
|
|
40
|
-
return cachedValue;
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
18
|
// ============================================================================
|
|
44
19
|
// Service Implementation
|
|
45
20
|
// ============================================================================
|
|
@@ -59,30 +34,24 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
59
34
|
end: config.endDate,
|
|
60
35
|
});
|
|
61
36
|
const cursorSignal = signalsService.signal(undefined);
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
getValue: () => (bookingService.serviceSelections.get()[0]?.resources ?? []).map((resource) => resource._id),
|
|
70
|
-
}));
|
|
71
|
-
const filtersSignal = signalsService.computed(() => {
|
|
37
|
+
const getServices = () => {
|
|
38
|
+
const serviceSelections = bookingService.serviceSelections.peek();
|
|
39
|
+
return serviceSelections.length
|
|
40
|
+
? serviceSelections.map((s) => s.service)
|
|
41
|
+
: (config.services ?? []);
|
|
42
|
+
};
|
|
43
|
+
const getFilters = () => {
|
|
72
44
|
const baseFilters = config.filters ?? {};
|
|
73
|
-
const selectedLocation = bookingService.location.
|
|
45
|
+
const selectedLocation = bookingService.location.peek();
|
|
74
46
|
const location = selectedLocation
|
|
75
|
-
? {
|
|
76
|
-
id: selectedLocation._id,
|
|
77
|
-
locationType: selectedLocation.type,
|
|
78
|
-
}
|
|
47
|
+
? { id: selectedLocation._id, locationType: selectedLocation.type }
|
|
79
48
|
: baseFilters.location;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
};
|
|
85
|
-
}
|
|
49
|
+
const selectedResources = bookingService.serviceSelections.peek()[0]?.resources ?? [];
|
|
50
|
+
const staffMemberIds = selectedResources.length
|
|
51
|
+
? selectedResources.map((r) => r._id)
|
|
52
|
+
: baseFilters.staffMemberIds;
|
|
53
|
+
return { ...baseFilters, location, staffMemberIds };
|
|
54
|
+
};
|
|
86
55
|
const fetchTimeSlots = async () => {
|
|
87
56
|
isLoadingSignal.set(true);
|
|
88
57
|
errorSignal.set(null);
|
|
@@ -90,12 +59,12 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
90
59
|
timeSlotsSignal.set([]);
|
|
91
60
|
try {
|
|
92
61
|
const response = await fetchAvailability({
|
|
93
|
-
services:
|
|
94
|
-
timeZone: bookingService.timezone.
|
|
95
|
-
viewMode: viewModeSignal.
|
|
96
|
-
fromLocalDate: dateRangeSignal.
|
|
97
|
-
toLocalDate: dateRangeSignal.
|
|
98
|
-
...
|
|
62
|
+
services: getServices(),
|
|
63
|
+
timeZone: bookingService.timezone.peek(),
|
|
64
|
+
viewMode: viewModeSignal.peek(),
|
|
65
|
+
fromLocalDate: dateRangeSignal.peek().start,
|
|
66
|
+
toLocalDate: dateRangeSignal.peek().end,
|
|
67
|
+
...getFilters(),
|
|
99
68
|
});
|
|
100
69
|
timeSlotsSignal.set(response.timeSlots);
|
|
101
70
|
const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
|
|
@@ -143,13 +112,13 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
143
112
|
* Updates the booking service with the selected resource
|
|
144
113
|
*/
|
|
145
114
|
const selectStaffMember = (staffMemberId) => {
|
|
146
|
-
const selectedTimeSlot = bookingService.serviceSelections.
|
|
115
|
+
const selectedTimeSlot = bookingService.serviceSelections.peek()[0]?.timeSlot;
|
|
147
116
|
if (!selectedTimeSlot)
|
|
148
117
|
return;
|
|
149
118
|
const getStaffName = (id) => {
|
|
150
119
|
if (id === ANY_STAFF_MEMBER_ID)
|
|
151
120
|
return 'Any staff member';
|
|
152
|
-
const slots = timeSlotsSignal.
|
|
121
|
+
const slots = timeSlotsSignal.peek();
|
|
153
122
|
return (slots
|
|
154
123
|
.flatMap((slot) => slot.availableResources[0].resources)
|
|
155
124
|
.find((r) => r._id === id)?.name ?? undefined);
|
|
@@ -171,7 +140,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
171
140
|
* Action: Clear staff selection while keeping the selected time slot
|
|
172
141
|
*/
|
|
173
142
|
const clearStaffSelection = () => {
|
|
174
|
-
const selectedTimeSlot = bookingService.serviceSelections.
|
|
143
|
+
const selectedTimeSlot = bookingService.serviceSelections.peek()[0]?.timeSlot;
|
|
175
144
|
if (!selectedTimeSlot)
|
|
176
145
|
return;
|
|
177
146
|
bookingService.actions.updateItem({
|
|
@@ -182,7 +151,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
182
151
|
* Action: Load more slots (pagination)
|
|
183
152
|
*/
|
|
184
153
|
const loadMore = async () => {
|
|
185
|
-
const cursor = cursorSignal.
|
|
154
|
+
const cursor = cursorSignal.peek();
|
|
186
155
|
if (!cursor) {
|
|
187
156
|
return;
|
|
188
157
|
}
|
|
@@ -190,16 +159,16 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
190
159
|
errorSignal.set(null);
|
|
191
160
|
try {
|
|
192
161
|
const response = await fetchAvailability({
|
|
193
|
-
services:
|
|
194
|
-
fromLocalDate: dateRangeSignal.
|
|
195
|
-
toLocalDate: dateRangeSignal.
|
|
196
|
-
timeZone: bookingService.timezone.
|
|
197
|
-
viewMode: viewModeSignal.
|
|
162
|
+
services: getServices(),
|
|
163
|
+
fromLocalDate: dateRangeSignal.peek().start,
|
|
164
|
+
toLocalDate: dateRangeSignal.peek().end,
|
|
165
|
+
timeZone: bookingService.timezone.peek(),
|
|
166
|
+
viewMode: viewModeSignal.peek(),
|
|
198
167
|
cursor,
|
|
199
|
-
...
|
|
168
|
+
...getFilters(),
|
|
200
169
|
});
|
|
201
170
|
const newSlots = response.timeSlots;
|
|
202
|
-
timeSlotsSignal.set([...timeSlotsSignal.
|
|
171
|
+
timeSlotsSignal.set([...timeSlotsSignal.peek(), ...newSlots]);
|
|
203
172
|
const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
|
|
204
173
|
cursorSignal.set(nextCursor);
|
|
205
174
|
hasMoreSignal.set(!!nextCursor);
|
|
@@ -218,18 +187,26 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
218
187
|
* Action: Update date range (effect will handle refetch)
|
|
219
188
|
*/
|
|
220
189
|
const setDateRange = (newStartDate, newEndDate) => {
|
|
221
|
-
const { start: currentStartDate, end: currentEndDate } = dateRangeSignal.
|
|
190
|
+
const { start: currentStartDate, end: currentEndDate } = dateRangeSignal.peek();
|
|
222
191
|
if (currentStartDate.getTime() === newStartDate.getTime() &&
|
|
223
192
|
currentEndDate.getTime() === newEndDate.getTime()) {
|
|
224
193
|
return;
|
|
225
194
|
}
|
|
226
195
|
dateRangeSignal.set({ start: newStartDate, end: newEndDate });
|
|
227
196
|
};
|
|
197
|
+
const preSelectedStaffMemberId = signalsService.computed(() => {
|
|
198
|
+
const serviceSelections = bookingService.serviceSelections.peek();
|
|
199
|
+
return serviceSelections.length
|
|
200
|
+
? (serviceSelections[0]?.resources?.[0]?._id ?? undefined)
|
|
201
|
+
: undefined;
|
|
202
|
+
});
|
|
228
203
|
let isFirstRun = true;
|
|
229
204
|
signalsService.effect(() => {
|
|
230
|
-
|
|
205
|
+
// Dependencies - trigger refetch when these change
|
|
231
206
|
dateRangeSignal.get();
|
|
232
207
|
bookingService.timezone.get();
|
|
208
|
+
bookingService.location.get();
|
|
209
|
+
preSelectedStaffMemberId.get();
|
|
233
210
|
if (isFirstRun) {
|
|
234
211
|
isFirstRun = false;
|
|
235
212
|
if (config.timeSlots) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-bookings",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.38",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -72,5 +72,5 @@
|
|
|
72
72
|
"groupId": "com.wixpress.headless-components"
|
|
73
73
|
}
|
|
74
74
|
},
|
|
75
|
-
"falconPackageHash": "
|
|
75
|
+
"falconPackageHash": "9f684dcab2e31f9a11e4fa26eb6c63aef1f039fd24342eb4e056f05d"
|
|
76
76
|
}
|