@wix/headless-bookings 0.0.20 → 0.0.22
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 → fetch-availability}/index.d.ts +4 -3
- package/cjs/dist/api/{time-slot-list → fetch-availability}/index.js +36 -15
- package/cjs/dist/api/index.d.ts +1 -1
- package/cjs/dist/api/index.js +1 -1
- package/cjs/dist/react/booking/Booking.d.ts +2 -1
- package/cjs/dist/react/booking/Booking.js +1 -1
- package/cjs/dist/react/core/booking/Booking.d.ts +3 -2
- package/cjs/dist/react/core/booking/Booking.js +5 -2
- package/cjs/dist/react/core/time-slot-list/TimeSlot.d.ts +4 -4
- package/cjs/dist/react/core/time-slot-list/TimeSlot.js +17 -7
- package/cjs/dist/react/core/time-slot-list/TimeSlotList.js +3 -2
- package/cjs/dist/react/time-slot-list/TimeSlot.d.ts +6 -6
- package/cjs/dist/react/time-slot-list/TimeSlot.js +6 -6
- package/cjs/dist/react/time-slot-list/TimeSlotList.d.ts +4 -4
- package/cjs/dist/services/booking/booking.d.ts +6 -0
- package/cjs/dist/services/booking/booking.js +21 -1
- package/cjs/dist/services/time-slot-list/time-slot-list.def.d.ts +12 -4
- package/cjs/dist/services/time-slot-list/time-slot-list.js +37 -34
- package/dist/api/{time-slot-list → fetch-availability}/index.d.ts +4 -3
- package/dist/api/{time-slot-list → fetch-availability}/index.js +36 -15
- package/dist/api/index.d.ts +1 -1
- package/dist/api/index.js +1 -1
- package/dist/react/booking/Booking.d.ts +2 -1
- package/dist/react/booking/Booking.js +1 -1
- package/dist/react/core/booking/Booking.d.ts +3 -2
- package/dist/react/core/booking/Booking.js +5 -2
- package/dist/react/core/time-slot-list/TimeSlot.d.ts +4 -4
- package/dist/react/core/time-slot-list/TimeSlot.js +17 -7
- package/dist/react/core/time-slot-list/TimeSlotList.js +3 -2
- package/dist/react/time-slot-list/TimeSlot.d.ts +6 -6
- package/dist/react/time-slot-list/TimeSlot.js +6 -6
- package/dist/react/time-slot-list/TimeSlotList.d.ts +4 -4
- package/dist/services/booking/booking.d.ts +6 -0
- package/dist/services/booking/booking.js +21 -1
- package/dist/services/time-slot-list/time-slot-list.def.d.ts +12 -4
- package/dist/services/time-slot-list/time-slot-list.js +37 -34
- package/package.json +2 -2
|
@@ -4,10 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { implementService } from '@wix/services-definitions';
|
|
6
6
|
import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
|
|
7
|
-
import { format } from 'date-fns';
|
|
8
7
|
import { TimeSlotListServiceDefinition, ViewMode, } from './time-slot-list.def.js';
|
|
9
|
-
import { fetchAvailability } from '../../api/
|
|
10
|
-
import { BookingServiceDefinition, } from '../booking/booking.js';
|
|
8
|
+
import { fetchAvailability } from '../../api/fetch-availability/index.js';
|
|
9
|
+
import { BookingServiceDefinition, ANY_STAFF_MEMBER_ID, } from '../booking/booking.js';
|
|
11
10
|
// ============================================================================
|
|
12
11
|
// Helpers
|
|
13
12
|
// ============================================================================
|
|
@@ -19,13 +18,6 @@ function getClientTimezone() {
|
|
|
19
18
|
? Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
20
19
|
: undefined;
|
|
21
20
|
}
|
|
22
|
-
/**
|
|
23
|
-
* Formats a Date to local ISO string (YYYY-MM-DDThh:mm:ss)
|
|
24
|
-
*/
|
|
25
|
-
function toLocalDateString(date) {
|
|
26
|
-
const pattern = "yyyy-MM-dd'T'HH:mm:ss";
|
|
27
|
-
return format(date, pattern);
|
|
28
|
-
}
|
|
29
21
|
/**
|
|
30
22
|
* Merges new grouped slots with existing ones
|
|
31
23
|
* Slots with the same start time are merged
|
|
@@ -68,7 +60,6 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
68
60
|
const viewModeSignal = signalsService.signal(ViewMode.CLASSIC);
|
|
69
61
|
const startDateSignal = signalsService.signal(config.startDate || currentDate);
|
|
70
62
|
const endDateSignal = signalsService.signal(config.endDate || getDefaultEndDate());
|
|
71
|
-
const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
|
|
72
63
|
const cursorSignal = signalsService.signal(undefined);
|
|
73
64
|
/**
|
|
74
65
|
* Helper: Get services from config or BookingService
|
|
@@ -84,9 +75,9 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
84
75
|
const getFilters = () => {
|
|
85
76
|
// Get BookingService selections (these take priority)
|
|
86
77
|
const location = bookingService.location.get();
|
|
87
|
-
const
|
|
88
|
-
const selectedStaffMemberIds =
|
|
89
|
-
.map((
|
|
78
|
+
const serviceSelections = bookingService.serviceSelections.get();
|
|
79
|
+
const selectedStaffMemberIds = serviceSelections
|
|
80
|
+
.map((serviceSelection) => serviceSelection.resources.map((r) => r._id))
|
|
90
81
|
.flat();
|
|
91
82
|
const selectedLocation = location ? { _id: location.id } : null;
|
|
92
83
|
// Merge config filters with BookingService selections (selections win)
|
|
@@ -114,9 +105,9 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
114
105
|
try {
|
|
115
106
|
const response = await fetchAvailability({
|
|
116
107
|
services: selectedServices,
|
|
117
|
-
fromLocalDate:
|
|
118
|
-
toLocalDate:
|
|
119
|
-
timeZone:
|
|
108
|
+
fromLocalDate: startDateSignal.get(),
|
|
109
|
+
toLocalDate: endDateSignal.get(),
|
|
110
|
+
timeZone: bookingService.timezone.get(),
|
|
120
111
|
viewMode: viewModeSignal.get(),
|
|
121
112
|
cursor: cursorSignal.get(),
|
|
122
113
|
...filters,
|
|
@@ -243,6 +234,18 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
243
234
|
const currentSelections = selectionSignal.get();
|
|
244
235
|
if (!currentSelections?.slot)
|
|
245
236
|
return;
|
|
237
|
+
if (staffMemberId === ANY_STAFF_MEMBER_ID) {
|
|
238
|
+
bookingService.actions.updateItem({
|
|
239
|
+
resources: [
|
|
240
|
+
{
|
|
241
|
+
_id: ANY_STAFF_MEMBER_ID,
|
|
242
|
+
name: 'Any staff member',
|
|
243
|
+
},
|
|
244
|
+
],
|
|
245
|
+
});
|
|
246
|
+
selectionSignal.set({ ...currentSelections, staffMemberId });
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
246
249
|
const slot = currentSelections.slot;
|
|
247
250
|
// Find all locations that have this staff member
|
|
248
251
|
const locationsWithThisStaff = slot.staffByLocation.filter((loc) => loc.staffMembers.some((s) => s.id === staffMemberId));
|
|
@@ -300,16 +303,14 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
300
303
|
if (!cursor || isLoadingSignal.get())
|
|
301
304
|
return;
|
|
302
305
|
const selectedServices = getServices();
|
|
303
|
-
if (selectedServices.length === 0)
|
|
304
|
-
return;
|
|
305
306
|
const filters = getFilters();
|
|
306
307
|
isLoadingSignal.set(true);
|
|
307
308
|
try {
|
|
308
309
|
const response = await fetchAvailability({
|
|
309
310
|
services: selectedServices,
|
|
310
|
-
fromLocalDate:
|
|
311
|
-
toLocalDate:
|
|
312
|
-
timeZone:
|
|
311
|
+
fromLocalDate: startDateSignal.get(),
|
|
312
|
+
toLocalDate: endDateSignal.get(),
|
|
313
|
+
timeZone: bookingService.timezone.get(),
|
|
313
314
|
cursor,
|
|
314
315
|
viewMode: viewModeSignal.get(),
|
|
315
316
|
...filters,
|
|
@@ -345,17 +346,21 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
345
346
|
endDateSignal.set(newEndDate);
|
|
346
347
|
fetchTimeSlots();
|
|
347
348
|
};
|
|
348
|
-
/**
|
|
349
|
-
* Action: Update timezone and refetch slots
|
|
350
|
-
*/
|
|
351
|
-
const setTimezone = (newTimezone) => {
|
|
352
|
-
timezoneSignal.set(newTimezone);
|
|
353
|
-
fetchTimeSlots();
|
|
354
|
-
};
|
|
355
349
|
// Auto-fetch on initialization if no pre-loaded data provided
|
|
356
|
-
if (!config.timeSlots
|
|
350
|
+
if (!config.timeSlots) {
|
|
357
351
|
fetchTimeSlots();
|
|
358
352
|
}
|
|
353
|
+
// Subscribe to timezone changes from BookingService to auto-refetch
|
|
354
|
+
// NOTE: This should depend ONLY on bookingService.timezone.
|
|
355
|
+
let firstTimezoneEffectRun = true;
|
|
356
|
+
signalsService.effect(() => {
|
|
357
|
+
bookingService.timezone.get();
|
|
358
|
+
if (firstTimezoneEffectRun) {
|
|
359
|
+
firstTimezoneEffectRun = false;
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
fetchTimeSlots();
|
|
363
|
+
});
|
|
359
364
|
// Return service API
|
|
360
365
|
return {
|
|
361
366
|
// State Signals
|
|
@@ -367,7 +372,6 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
367
372
|
// Configuration Signals (viewMode is internal only)
|
|
368
373
|
startDate: startDateSignal,
|
|
369
374
|
endDate: endDateSignal,
|
|
370
|
-
timezone: timezoneSignal,
|
|
371
375
|
// Actions
|
|
372
376
|
actions: {
|
|
373
377
|
selectSlot,
|
|
@@ -376,7 +380,6 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
376
380
|
clearPreferences,
|
|
377
381
|
loadMore,
|
|
378
382
|
setDateRange,
|
|
379
|
-
setTimezone,
|
|
380
383
|
},
|
|
381
384
|
};
|
|
382
385
|
});
|
|
@@ -408,8 +411,8 @@ export async function loadTimeSlotListConfig(options) {
|
|
|
408
411
|
try {
|
|
409
412
|
const response = await fetchAvailability({
|
|
410
413
|
services: options.services || [],
|
|
411
|
-
fromLocalDate:
|
|
412
|
-
toLocalDate:
|
|
414
|
+
fromLocalDate: startDate,
|
|
415
|
+
toLocalDate: endDate,
|
|
413
416
|
timeZone: options.timezone ?? getClientTimezone(),
|
|
414
417
|
...options.filters,
|
|
415
418
|
});
|
|
@@ -52,10 +52,10 @@ export interface FetchAvailabilityResponse {
|
|
|
52
52
|
* // response.timeSlots are grouped by time
|
|
53
53
|
* ```
|
|
54
54
|
*/
|
|
55
|
-
export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, }: {
|
|
55
|
+
export declare function fetchAvailability({ services: selectedServices, viewMode, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, timeSlotsPerDay, }: {
|
|
56
56
|
services: Service[];
|
|
57
|
-
fromLocalDate:
|
|
58
|
-
toLocalDate:
|
|
57
|
+
fromLocalDate: Date;
|
|
58
|
+
toLocalDate: Date;
|
|
59
59
|
timeZone?: string;
|
|
60
60
|
locations?: Location[];
|
|
61
61
|
staffMemberIds?: string[];
|
|
@@ -68,4 +68,5 @@ export declare function fetchAvailability({ services: selectedServices, viewMode
|
|
|
68
68
|
bookOnlineDisabled?: boolean;
|
|
69
69
|
};
|
|
70
70
|
slotsPerPage?: number;
|
|
71
|
+
timeSlotsPerDay?: number;
|
|
71
72
|
}): Promise<FetchAvailabilityResponse>;
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
import { listAvailabilityTimeSlots, } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
6
6
|
import { listEventTimeSlots } from '@wix/auto_sdk_bookings_event-time-slots';
|
|
7
7
|
import { ServiceType } from '@wix/auto_sdk_bookings_services';
|
|
8
|
+
import { format } from 'date-fns';
|
|
9
|
+
import { ANY_STAFF_MEMBER_ID } from '../../services/booking/booking.js';
|
|
8
10
|
import { ViewMode, } from '../../services/time-slot-list/time-slot-list.def.js';
|
|
9
11
|
// ============================================================================
|
|
10
12
|
// Internal Types
|
|
@@ -17,6 +19,16 @@ var FlowType;
|
|
|
17
19
|
})(FlowType || (FlowType = {}));
|
|
18
20
|
const STAFF_RESOURCE_TYPE_ID = '1cd44cf8-756f-41c3-bd90-3e2ffcaf1155';
|
|
19
21
|
// ============================================================================
|
|
22
|
+
// Helpers
|
|
23
|
+
// ============================================================================
|
|
24
|
+
/**
|
|
25
|
+
* Formats a Date to local ISO string (YYYY-MM-DDThh:mm:ss)
|
|
26
|
+
*/
|
|
27
|
+
function toLocalDateString(date) {
|
|
28
|
+
const pattern = "yyyy-MM-dd'T'HH:mm:ss";
|
|
29
|
+
return format(date, pattern);
|
|
30
|
+
}
|
|
31
|
+
// ============================================================================
|
|
20
32
|
// Grouping Functions
|
|
21
33
|
// ============================================================================
|
|
22
34
|
/**
|
|
@@ -103,11 +115,22 @@ function detectFlow(bookingServices, viewMode) {
|
|
|
103
115
|
* // response.timeSlots are grouped by time
|
|
104
116
|
* ```
|
|
105
117
|
*/
|
|
106
|
-
export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, }) {
|
|
118
|
+
export async function fetchAvailability({ services: selectedServices, viewMode = ViewMode.CLASSIC, fromLocalDate, toLocalDate, timeZone, locations, staffMemberIds, cursor, slotsPerPage, bookable, bookingPolicyViolations, timeSlotsPerDay, }) {
|
|
107
119
|
const flow = detectFlow(selectedServices, viewMode);
|
|
108
120
|
const serviceIds = selectedServices.map((s) => s._id);
|
|
121
|
+
// Convert Date objects to local ISO strings
|
|
122
|
+
const fromLocalDateStr = toLocalDateString(fromLocalDate);
|
|
123
|
+
const toLocalDateStr = toLocalDateString(toLocalDate);
|
|
124
|
+
const isFetchAvailabileDays = timeSlotsPerDay === 1;
|
|
125
|
+
const cursorPaging = isFetchAvailabileDays
|
|
126
|
+
? undefined
|
|
127
|
+
: {
|
|
128
|
+
cursor,
|
|
129
|
+
limit: slotsPerPage,
|
|
130
|
+
};
|
|
109
131
|
if (flow === FlowType.SINGLE_SERVICE) {
|
|
110
|
-
const
|
|
132
|
+
const isAnyStaffMemberSelected = staffMemberIds?.length === 1 && staffMemberIds[0] === ANY_STAFF_MEMBER_ID;
|
|
133
|
+
const resourceTypes = staffMemberIds?.length && !isAnyStaffMemberSelected
|
|
111
134
|
? [
|
|
112
135
|
{
|
|
113
136
|
resourceTypeId: STAFF_RESOURCE_TYPE_ID,
|
|
@@ -117,18 +140,18 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
|
|
|
117
140
|
: undefined;
|
|
118
141
|
const response = await listAvailabilityTimeSlots({
|
|
119
142
|
serviceId: serviceIds[0],
|
|
120
|
-
fromLocalDate,
|
|
121
|
-
toLocalDate,
|
|
143
|
+
fromLocalDate: fromLocalDateStr,
|
|
144
|
+
toLocalDate: toLocalDateStr,
|
|
122
145
|
timeZone,
|
|
123
146
|
locations,
|
|
124
|
-
cursorPaging
|
|
125
|
-
cursor,
|
|
126
|
-
limit: slotsPerPage,
|
|
127
|
-
},
|
|
147
|
+
cursorPaging,
|
|
128
148
|
resourceTypes,
|
|
129
149
|
bookable,
|
|
130
150
|
bookingPolicyViolations,
|
|
131
|
-
includeResourceTypeIds:
|
|
151
|
+
includeResourceTypeIds: isAnyStaffMemberSelected || isFetchAvailabileDays
|
|
152
|
+
? undefined
|
|
153
|
+
: [STAFF_RESOURCE_TYPE_ID],
|
|
154
|
+
timeSlotsPerDay,
|
|
132
155
|
});
|
|
133
156
|
return {
|
|
134
157
|
timeSlots: groupTimeSlotsByTime(response.timeSlots || []),
|
|
@@ -150,17 +173,15 @@ export async function fetchAvailability({ services: selectedServices, viewMode =
|
|
|
150
173
|
eventFilter['resources.id'] = { $hasSome: staffMemberIds };
|
|
151
174
|
}
|
|
152
175
|
const response = await listEventTimeSlots({
|
|
153
|
-
fromLocalDate,
|
|
154
|
-
toLocalDate,
|
|
176
|
+
fromLocalDate: fromLocalDateStr,
|
|
177
|
+
toLocalDate: toLocalDateStr,
|
|
155
178
|
timeZone,
|
|
156
179
|
serviceIds: serviceIds.length ? serviceIds : undefined,
|
|
157
|
-
cursorPaging
|
|
158
|
-
cursor,
|
|
159
|
-
limit: slotsPerPage,
|
|
160
|
-
},
|
|
180
|
+
cursorPaging,
|
|
161
181
|
eventFilter: Object.keys(eventFilter).length ? eventFilter : undefined,
|
|
162
182
|
includeNonBookable: bookable ? !bookable : undefined,
|
|
163
183
|
bookingPolicyViolations,
|
|
184
|
+
maxSlotsPerDay: timeSlotsPerDay,
|
|
164
185
|
});
|
|
165
186
|
return {
|
|
166
187
|
timeSlots: groupTimeSlotsByTime(response.timeSlots || []),
|
package/dist/api/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module API
|
|
6
6
|
*/
|
|
7
7
|
export { getServiceById, getServiceBySlug, queryServices, type PagingMetadata, type ServiceListFilters, type QueryServicesRequest, } from './query-services/index.js';
|
|
8
|
-
export { fetchAvailability } from './
|
|
8
|
+
export { fetchAvailability } from './fetch-availability/index.js';
|
|
9
9
|
/**
|
|
10
10
|
* PagingMetadata includes:
|
|
11
11
|
* - limit: Number of items per page
|
package/dist/api/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @module API
|
|
6
6
|
*/
|
|
7
7
|
export { getServiceById, getServiceBySlug, queryServices, } from './query-services/index.js';
|
|
8
|
-
export { fetchAvailability } from './
|
|
8
|
+
export { fetchAvailability } from './fetch-availability/index.js';
|
|
9
9
|
/**
|
|
10
10
|
* PagingMetadata includes:
|
|
11
11
|
* - limit: Number of items per page
|
|
@@ -4,12 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import React from 'react';
|
|
6
6
|
import type { BookingServiceConfig } from '../../services/booking/booking.js';
|
|
7
|
+
export type { BookingData, DataProps } from '../core/booking/Booking.js';
|
|
7
8
|
/**
|
|
8
9
|
* Props for Booking.Root component
|
|
9
10
|
*/
|
|
10
11
|
export interface BookingRootProps {
|
|
11
12
|
children: React.ReactNode;
|
|
12
|
-
bookingServiceConfig
|
|
13
|
+
bookingServiceConfig?: BookingServiceConfig;
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
16
|
* Root component that provides booking context to the entire app.
|
|
@@ -19,7 +19,7 @@ import * as CoreBooking from '../core/booking/Booking.js';
|
|
|
19
19
|
* ```
|
|
20
20
|
*/
|
|
21
21
|
export const Root = (props) => {
|
|
22
|
-
const { children, bookingServiceConfig } = props;
|
|
22
|
+
const { children, bookingServiceConfig = {} } = props;
|
|
23
23
|
return (_jsx(CoreBooking.Root, { bookingServiceConfig: bookingServiceConfig, children: children }));
|
|
24
24
|
};
|
|
25
25
|
Root.displayName = 'Booking.Root';
|
|
@@ -10,7 +10,7 @@ import type { FormValues } from '@wix/form-public';
|
|
|
10
10
|
*/
|
|
11
11
|
export interface RootProps {
|
|
12
12
|
children: React.ReactNode;
|
|
13
|
-
bookingServiceConfig
|
|
13
|
+
bookingServiceConfig?: BookingServiceConfig;
|
|
14
14
|
}
|
|
15
15
|
/**
|
|
16
16
|
* Root component that provides booking context to the entire app.
|
|
@@ -35,6 +35,7 @@ export declare function Root(props: RootProps): React.ReactNode;
|
|
|
35
35
|
export interface BookingData {
|
|
36
36
|
serviceSelections: ServiceSelection[];
|
|
37
37
|
location: Location | null;
|
|
38
|
+
timezone: string | undefined;
|
|
38
39
|
formSubmission: FormValues | null;
|
|
39
40
|
actions: BookingServiceActions;
|
|
40
41
|
}
|
|
@@ -52,7 +53,7 @@ export interface DataProps {
|
|
|
52
53
|
* @example
|
|
53
54
|
* ```tsx
|
|
54
55
|
* <Booking.Data>
|
|
55
|
-
* {({ serviceSelections, location, formSubmission, actions }) => (
|
|
56
|
+
* {({ serviceSelections, location, timezone, formSubmission, actions }) => (
|
|
56
57
|
* serviceSelections.length > 0 ? (
|
|
57
58
|
* <div>{serviceSelections.map(item => item.service.name).join(', ')}</div>
|
|
58
59
|
* ) : (
|
|
@@ -18,7 +18,8 @@ import { BookingServiceDefinition, BookingService, } from '../../../services/boo
|
|
|
18
18
|
* ```
|
|
19
19
|
*/
|
|
20
20
|
export function Root(props) {
|
|
21
|
-
|
|
21
|
+
const { bookingServiceConfig = {}, children } = props;
|
|
22
|
+
return (_jsx(WixServices, { servicesMap: createServicesMap().addService(BookingServiceDefinition, BookingService, bookingServiceConfig), children: children }));
|
|
22
23
|
}
|
|
23
24
|
/**
|
|
24
25
|
* Core component that provides access to booking data via render props.
|
|
@@ -28,7 +29,7 @@ export function Root(props) {
|
|
|
28
29
|
* @example
|
|
29
30
|
* ```tsx
|
|
30
31
|
* <Booking.Data>
|
|
31
|
-
* {({ serviceSelections, location, formSubmission, actions }) => (
|
|
32
|
+
* {({ serviceSelections, location, timezone, formSubmission, actions }) => (
|
|
32
33
|
* serviceSelections.length > 0 ? (
|
|
33
34
|
* <div>{serviceSelections.map(item => item.service.name).join(', ')}</div>
|
|
34
35
|
* ) : (
|
|
@@ -43,11 +44,13 @@ export function Data(props) {
|
|
|
43
44
|
const bookingService = useService(BookingServiceDefinition);
|
|
44
45
|
const serviceSelections = bookingService.serviceSelections.get();
|
|
45
46
|
const location = bookingService.location.get();
|
|
47
|
+
const timezone = bookingService.timezone.get();
|
|
46
48
|
const formSubmission = bookingService.formSubmission.get();
|
|
47
49
|
const { actions } = bookingService;
|
|
48
50
|
return children({
|
|
49
51
|
serviceSelections,
|
|
50
52
|
location,
|
|
53
|
+
timezone,
|
|
51
54
|
formSubmission,
|
|
52
55
|
actions,
|
|
53
56
|
});
|
|
@@ -88,10 +88,10 @@ export declare function StaffMemberProvider(props: StaffMemberProviderProps): Re
|
|
|
88
88
|
*/
|
|
89
89
|
export interface InfoProps {
|
|
90
90
|
children: (data: {
|
|
91
|
-
/**
|
|
92
|
-
startDate:
|
|
93
|
-
/**
|
|
94
|
-
endDate:
|
|
91
|
+
/** Parsed localStartDate as Date (constructed from the raw localStartDate string) */
|
|
92
|
+
startDate: Date | undefined;
|
|
93
|
+
/** Parsed localEndDate as Date (constructed from the raw localEndDate string) */
|
|
94
|
+
endDate: Date | undefined;
|
|
95
95
|
/** Calculated duration in minutes (null if dates unavailable) */
|
|
96
96
|
durationInMinutes: number | null;
|
|
97
97
|
/** Whether the timeSlot is bookable */
|
|
@@ -9,6 +9,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
9
9
|
import React from 'react';
|
|
10
10
|
import { useService } from '@wix/services-manager-react';
|
|
11
11
|
import { TimeSlotListServiceDefinition, } from '../../../services/time-slot-list/time-slot-list.def.js';
|
|
12
|
+
import { ANY_STAFF_MEMBER_ID } from '../../../services/booking/booking.js';
|
|
12
13
|
const TimeSlotContext = React.createContext(null);
|
|
13
14
|
/**
|
|
14
15
|
* Hook to access TimeSlot context
|
|
@@ -115,15 +116,17 @@ export function StaffMemberProvider(props) {
|
|
|
115
116
|
*/
|
|
116
117
|
export function Info(props) {
|
|
117
118
|
const { timeSlot, isSelected } = useTimeSlotContext();
|
|
118
|
-
const startDate = timeSlot.localStartDate
|
|
119
|
-
|
|
119
|
+
const startDate = timeSlot.localStartDate
|
|
120
|
+
? new Date(timeSlot.localStartDate)
|
|
121
|
+
: undefined;
|
|
122
|
+
const endDate = timeSlot.localEndDate
|
|
123
|
+
? new Date(timeSlot.localEndDate)
|
|
124
|
+
: undefined;
|
|
120
125
|
const bookable = timeSlot.bookable ?? false;
|
|
121
126
|
// Calculate duration from start and end dates
|
|
122
127
|
let durationInMinutes = null;
|
|
123
128
|
if (startDate && endDate) {
|
|
124
|
-
|
|
125
|
-
const endTime = new Date(endDate).getTime();
|
|
126
|
-
durationInMinutes = Math.round((endTime - startTime) / (1000 * 60));
|
|
129
|
+
durationInMinutes = Math.round((endDate.getTime() - startDate.getTime()) / (1000 * 60));
|
|
127
130
|
}
|
|
128
131
|
return props.children({
|
|
129
132
|
startDate,
|
|
@@ -202,12 +205,13 @@ export function Locations(props) {
|
|
|
202
205
|
const selection = service.selection.get();
|
|
203
206
|
const selectedLocationId = selection?.locationId ?? null;
|
|
204
207
|
const selectedStaffMemberId = selection?.staffMemberId ?? null;
|
|
208
|
+
const isAnyStaffMemberSelected = selectedStaffMemberId === ANY_STAFF_MEMBER_ID;
|
|
205
209
|
// Build locations array from staffByLocation (names already included)
|
|
206
210
|
const locations = timeSlot.staffByLocation.map(({ location, staffMembers }) => {
|
|
207
211
|
// Check if this location is selectable based on selected staff
|
|
208
212
|
// If no staff selected, all locations are selectable
|
|
209
213
|
// If staff selected, only locations where that staff works are selectable
|
|
210
|
-
const isSelectable = selectedStaffMemberId
|
|
214
|
+
const isSelectable = !isAnyStaffMemberSelected && selectedStaffMemberId
|
|
211
215
|
? staffMembers.some((s) => s.id === selectedStaffMemberId)
|
|
212
216
|
: true;
|
|
213
217
|
return {
|
|
@@ -311,12 +315,18 @@ export function StaffMembers(props) {
|
|
|
311
315
|
isSelectable: !selectedLocationId ||
|
|
312
316
|
entries.some((e) => e.locationId === selectedLocationId),
|
|
313
317
|
}));
|
|
318
|
+
const anyStaffOption = {
|
|
319
|
+
id: ANY_STAFF_MEMBER_ID,
|
|
320
|
+
name: 'Any staff member',
|
|
321
|
+
isSelected: selectedStaffMemberId === ANY_STAFF_MEMBER_ID,
|
|
322
|
+
isSelectable: true,
|
|
323
|
+
};
|
|
314
324
|
// Return null if only 1 staff member - nothing to choose from
|
|
315
325
|
if (staffMembers.length <= 1) {
|
|
316
326
|
return null;
|
|
317
327
|
}
|
|
318
328
|
return props.children({
|
|
319
|
-
staffMembers,
|
|
329
|
+
staffMembers: [anyStaffOption, ...staffMembers],
|
|
320
330
|
selectStaffMember: service.actions.selectStaffMember,
|
|
321
331
|
});
|
|
322
332
|
}
|
|
@@ -2,6 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { useService, WixServices } from '@wix/services-manager-react';
|
|
3
3
|
import { createServicesMap } from '@wix/services-manager';
|
|
4
4
|
import { TimeSlotListServiceDefinition, TimeSlotListService, } from '../../../services/time-slot-list/index.js';
|
|
5
|
+
import { BookingServiceDefinition } from '../../../services/booking/booking.js';
|
|
5
6
|
/**
|
|
6
7
|
* Root component that provides TimeSlotListService context to its children.
|
|
7
8
|
*
|
|
@@ -54,8 +55,8 @@ function getTimezoneDisplayName(timezone) {
|
|
|
54
55
|
* ```
|
|
55
56
|
*/
|
|
56
57
|
export function Timezone(props) {
|
|
57
|
-
const
|
|
58
|
-
const timezone =
|
|
58
|
+
const bookingService = useService(BookingServiceDefinition);
|
|
59
|
+
const timezone = bookingService.timezone.get();
|
|
59
60
|
const displayName = getTimezoneDisplayName(timezone);
|
|
60
61
|
return props.children({ timezone, displayName });
|
|
61
62
|
}
|
|
@@ -16,18 +16,18 @@ export type { LocationData, StaffMemberData, } from '../core/time-slot-list/Time
|
|
|
16
16
|
export interface StartDateProps {
|
|
17
17
|
asChild?: boolean;
|
|
18
18
|
children?: AsChildChildren<{
|
|
19
|
-
startDate:
|
|
19
|
+
startDate: Date | undefined;
|
|
20
20
|
}>;
|
|
21
21
|
className?: string;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
* Displays the time slot start date/time as a
|
|
24
|
+
* Displays the time slot start date/time as a Date.
|
|
25
25
|
* Consumer is responsible for formatting.
|
|
26
26
|
*
|
|
27
27
|
* @component
|
|
28
28
|
* @example
|
|
29
29
|
* ```tsx
|
|
30
|
-
* // Default usage - displays
|
|
30
|
+
* // Default usage - displays Date.toString()
|
|
31
31
|
* <TimeSlot.StartDate className="text-foreground" />
|
|
32
32
|
*
|
|
33
33
|
* // With custom formatting
|
|
@@ -45,18 +45,18 @@ export declare const StartDate: React.ForwardRefExoticComponent<StartDateProps &
|
|
|
45
45
|
export interface EndDateProps {
|
|
46
46
|
asChild?: boolean;
|
|
47
47
|
children?: AsChildChildren<{
|
|
48
|
-
endDate:
|
|
48
|
+
endDate: Date | undefined;
|
|
49
49
|
}>;
|
|
50
50
|
className?: string;
|
|
51
51
|
}
|
|
52
52
|
/**
|
|
53
|
-
* Displays the time slot end date/time as a
|
|
53
|
+
* Displays the time slot end date/time as a Date.
|
|
54
54
|
* Consumer is responsible for formatting.
|
|
55
55
|
*
|
|
56
56
|
* @component
|
|
57
57
|
* @example
|
|
58
58
|
* ```tsx
|
|
59
|
-
* // Default usage - displays
|
|
59
|
+
* // Default usage - displays Date.toString()
|
|
60
60
|
* <TimeSlot.EndDate className="text-foreground" />
|
|
61
61
|
*
|
|
62
62
|
* // With custom formatting
|
|
@@ -34,13 +34,13 @@ var TestIds;
|
|
|
34
34
|
TestIds["timeSlotStaffMemberActionSelect"] = "time-slot-staff-member-action-select";
|
|
35
35
|
})(TestIds || (TestIds = {}));
|
|
36
36
|
/**
|
|
37
|
-
* Displays the time slot start date/time as a
|
|
37
|
+
* Displays the time slot start date/time as a Date.
|
|
38
38
|
* Consumer is responsible for formatting.
|
|
39
39
|
*
|
|
40
40
|
* @component
|
|
41
41
|
* @example
|
|
42
42
|
* ```tsx
|
|
43
|
-
* // Default usage - displays
|
|
43
|
+
* // Default usage - displays Date.toString()
|
|
44
44
|
* <TimeSlot.StartDate className="text-foreground" />
|
|
45
45
|
*
|
|
46
46
|
* // With custom formatting
|
|
@@ -53,17 +53,17 @@ var TestIds;
|
|
|
53
53
|
*/
|
|
54
54
|
export const StartDate = React.forwardRef((props, ref) => {
|
|
55
55
|
const { asChild, children, className } = props;
|
|
56
|
-
return (_jsx(CoreTimeSlot.Info, { children: ({ startDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotStartDate, customElement: children, customElementProps: { startDate }, children: _jsx("span", { children: startDate }) })) }));
|
|
56
|
+
return (_jsx(CoreTimeSlot.Info, { children: ({ startDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotStartDate, customElement: children, customElementProps: { startDate }, children: _jsx("span", { children: startDate?.toString() }) })) }));
|
|
57
57
|
});
|
|
58
58
|
StartDate.displayName = 'TimeSlot.StartDate';
|
|
59
59
|
/**
|
|
60
|
-
* Displays the time slot end date/time as a
|
|
60
|
+
* Displays the time slot end date/time as a Date.
|
|
61
61
|
* Consumer is responsible for formatting.
|
|
62
62
|
*
|
|
63
63
|
* @component
|
|
64
64
|
* @example
|
|
65
65
|
* ```tsx
|
|
66
|
-
* // Default usage - displays
|
|
66
|
+
* // Default usage - displays Date.toString()
|
|
67
67
|
* <TimeSlot.EndDate className="text-foreground" />
|
|
68
68
|
*
|
|
69
69
|
* // With custom formatting
|
|
@@ -76,7 +76,7 @@ StartDate.displayName = 'TimeSlot.StartDate';
|
|
|
76
76
|
*/
|
|
77
77
|
export const EndDate = React.forwardRef((props, ref) => {
|
|
78
78
|
const { asChild, children, className } = props;
|
|
79
|
-
return (_jsx(CoreTimeSlot.Info, { children: ({ endDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotEndDate, customElement: children, customElementProps: { endDate }, children: _jsx("span", { children: endDate }) })) }));
|
|
79
|
+
return (_jsx(CoreTimeSlot.Info, { children: ({ endDate }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotEndDate, customElement: children, customElementProps: { endDate }, children: _jsx("span", { children: endDate?.toString() }) })) }));
|
|
80
80
|
});
|
|
81
81
|
EndDate.displayName = 'TimeSlot.EndDate';
|
|
82
82
|
/**
|
|
@@ -113,10 +113,10 @@ export interface TimeSlotRepeaterChildProps {
|
|
|
113
113
|
isSelected: boolean;
|
|
114
114
|
/** Whether the slot can be booked */
|
|
115
115
|
bookable: boolean;
|
|
116
|
-
/** Start date/time as
|
|
117
|
-
startDate:
|
|
118
|
-
/** End date/time as
|
|
119
|
-
endDate:
|
|
116
|
+
/** Start date/time as Date (constructed from the raw localStartDate string) */
|
|
117
|
+
startDate: Date | undefined;
|
|
118
|
+
/** End date/time as Date (constructed from the raw localEndDate string) */
|
|
119
|
+
endDate: Date | undefined;
|
|
120
120
|
/** Duration in minutes */
|
|
121
121
|
durationInMinutes: number | null;
|
|
122
122
|
/** The raw time slot data */
|
|
@@ -7,6 +7,7 @@ import { type Signal } from '@wix/services-definitions/core-services/signals';
|
|
|
7
7
|
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
8
8
|
import type { Resource, EventInfo } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
9
9
|
import type { FormValues } from '@wix/form-public';
|
|
10
|
+
export declare const ANY_STAFF_MEMBER_ID = "__any_staff_member__";
|
|
10
11
|
/**
|
|
11
12
|
* Minimal time slot data for service selections
|
|
12
13
|
*/
|
|
@@ -57,6 +58,7 @@ export interface BookingServiceActions {
|
|
|
57
58
|
getResourceById(resourceId: string, instanceId?: string): Resource | undefined;
|
|
58
59
|
setLocation(location: Location | null): void;
|
|
59
60
|
clearLocation(): void;
|
|
61
|
+
setTimezone(timezone: string): void;
|
|
60
62
|
setFormSubmission(values: FormValues | null): void;
|
|
61
63
|
clearFormSubmission(): void;
|
|
62
64
|
clearAll(): void;
|
|
@@ -70,6 +72,8 @@ export interface BookingService {
|
|
|
70
72
|
serviceSelections: Signal<ServiceSelection[]>;
|
|
71
73
|
/** Signal containing the selected location */
|
|
72
74
|
location: Signal<Location | null>;
|
|
75
|
+
/** Signal containing the selected timezone */
|
|
76
|
+
timezone: Signal<string | undefined>;
|
|
73
77
|
/** Signal containing the form submission */
|
|
74
78
|
formSubmission: Signal<FormValues | null>;
|
|
75
79
|
/** Actions for modifying booking state */
|
|
@@ -92,6 +96,8 @@ export interface BookingServiceConfig {
|
|
|
92
96
|
serviceSelections?: ServiceSelection[];
|
|
93
97
|
/** Optional initial location */
|
|
94
98
|
location?: Location | null;
|
|
99
|
+
/** Optional initial timezone */
|
|
100
|
+
timezone?: string;
|
|
95
101
|
/** Optional initial form submission */
|
|
96
102
|
formSubmission?: FormValues | null;
|
|
97
103
|
}
|