@wix/headless-bookings 0.0.16 → 0.0.18
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/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 +48 -18
- package/cjs/dist/services/index.d.ts +2 -2
- 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 +37 -9
- 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/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 +48 -18
- package/dist/services/index.d.ts +2 -2
- 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 +37 -9
- package/package.json +3 -2
|
@@ -7,14 +7,21 @@ import { ListVariant } from '@wix/headless-components/react';
|
|
|
7
7
|
import { AsChildChildren } from '@wix/headless-utils/react';
|
|
8
8
|
import type { TimeSlotListServiceConfig } from '../../services/time-slot-list/time-slot-list.def.js';
|
|
9
9
|
export type { StartDateProps as TimeSlotStartDateProps, EndDateProps as TimeSlotEndDateProps, DurationProps as TimeSlotDurationProps, SelectProps as TimeSlotSelectProps, LocationsProps as TimeSlotLocationsProps, LocationRepeaterProps as TimeSlotLocationRepeaterProps, LocationNameProps as TimeSlotLocationNameProps, SelectLocationProps as TimeSlotSelectLocationProps, StaffMembersProps as TimeSlotStaffMembersProps, StaffMemberRepeaterProps as TimeSlotStaffMemberRepeaterProps, StaffMemberNameProps as TimeSlotStaffMemberNameProps, SelectStaffMemberProps as TimeSlotSelectStaffMemberProps, } from './TimeSlot.js';
|
|
10
|
+
/**
|
|
11
|
+
* Render props for TimeSlotList.Timezone component
|
|
12
|
+
*/
|
|
13
|
+
export interface TimezoneRenderProps {
|
|
14
|
+
/** Raw IANA timezone string (e.g., "America/New_York") */
|
|
15
|
+
timezone: string | undefined;
|
|
16
|
+
/** User-friendly display name (e.g., "Eastern Standard Time") */
|
|
17
|
+
displayName: string;
|
|
18
|
+
}
|
|
10
19
|
/**
|
|
11
20
|
* Props for TimeSlotList.Timezone component
|
|
12
21
|
*/
|
|
13
22
|
export interface TimezoneProps {
|
|
14
23
|
asChild?: boolean;
|
|
15
|
-
children?: AsChildChildren<
|
|
16
|
-
timezone: string | undefined;
|
|
17
|
-
}>;
|
|
24
|
+
children?: AsChildChildren<TimezoneRenderProps>;
|
|
18
25
|
className?: string;
|
|
19
26
|
}
|
|
20
27
|
/**
|
|
@@ -23,19 +30,14 @@ export interface TimezoneProps {
|
|
|
23
30
|
* @component
|
|
24
31
|
* @example
|
|
25
32
|
* ```tsx
|
|
26
|
-
* // Default usage
|
|
33
|
+
* // Default usage - shows display name
|
|
27
34
|
* <TimeSlotList.Timezone className="text-sm text-gray-600" />
|
|
28
35
|
*
|
|
29
|
-
* // asChild with
|
|
30
|
-
* <TimeSlotList.Timezone asChild>
|
|
31
|
-
* <span className="font-medium" />
|
|
32
|
-
* </TimeSlotList.Timezone>
|
|
33
|
-
*
|
|
34
|
-
* // asChild with render function
|
|
36
|
+
* // asChild with render function for custom formatting
|
|
35
37
|
* <TimeSlotList.Timezone asChild>
|
|
36
|
-
* {({ timezone }) => (
|
|
38
|
+
* {({ timezone, displayName }) => (
|
|
37
39
|
* <span className="text-sm">
|
|
38
|
-
*
|
|
40
|
+
* {displayName} // "Eastern Standard Time"
|
|
39
41
|
* </span>
|
|
40
42
|
* )}
|
|
41
43
|
* </TimeSlotList.Timezone>
|
|
@@ -153,6 +155,47 @@ export declare const LoadMore: React.ForwardRefExoticComponent<LoadMoreProps & R
|
|
|
153
155
|
export declare const Actions: {
|
|
154
156
|
LoadMore: React.ForwardRefExoticComponent<LoadMoreProps & React.RefAttributes<HTMLButtonElement>>;
|
|
155
157
|
};
|
|
158
|
+
/**
|
|
159
|
+
* Props for DateRange.Input component
|
|
160
|
+
*/
|
|
161
|
+
export interface DateRangeInputProps {
|
|
162
|
+
/** Whether to render as a child component */
|
|
163
|
+
asChild?: boolean;
|
|
164
|
+
/** Children for custom rendering */
|
|
165
|
+
children?: AsChildChildren<{
|
|
166
|
+
startDate: Date;
|
|
167
|
+
endDate: Date;
|
|
168
|
+
onChange: (start: Date, end: Date) => void;
|
|
169
|
+
}>;
|
|
170
|
+
/** CSS classes to apply to the container */
|
|
171
|
+
className?: string;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Date range input for setting the start and end date filter.
|
|
175
|
+
* Uses the core DateRange component to access service data.
|
|
176
|
+
* Both dates are set together and trigger a single fetch.
|
|
177
|
+
*
|
|
178
|
+
* @component
|
|
179
|
+
* @example
|
|
180
|
+
* ```tsx
|
|
181
|
+
* <TimeSlotList.DateRange.Input asChild>
|
|
182
|
+
* {({ startDate, endDate, onChange }) => (
|
|
183
|
+
* <MyDateRangePicker
|
|
184
|
+
* start={startDate}
|
|
185
|
+
* end={endDate}
|
|
186
|
+
* onRangeChange={(start, end) => onChange(start, end)}
|
|
187
|
+
* />
|
|
188
|
+
* )}
|
|
189
|
+
* </TimeSlotList.DateRange.Input>
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
export declare const DateRangeInput: React.ForwardRefExoticComponent<DateRangeInputProps & React.RefAttributes<HTMLElement>>;
|
|
193
|
+
/**
|
|
194
|
+
* DateRange namespace for TimeSlotList date range filter
|
|
195
|
+
*/
|
|
196
|
+
export declare const DateRange: {
|
|
197
|
+
Input: React.ForwardRefExoticComponent<DateRangeInputProps & React.RefAttributes<HTMLElement>>;
|
|
198
|
+
};
|
|
156
199
|
/**
|
|
157
200
|
* TimeSlot namespace containing components for displaying individual time slot data.
|
|
158
201
|
* These components must be used within TimeSlotList.TimeSlotRepeater.
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* TimeSlotList - High-level component for displaying time slot list information
|
|
4
4
|
* Provides components for displaying timezone and time slot data
|
|
@@ -8,7 +8,7 @@ import { GenericList } from '@wix/headless-components/react';
|
|
|
8
8
|
import { AsChildSlot } from '@wix/headless-utils/react';
|
|
9
9
|
import * as CoreTimeSlotList from '../core/time-slot-list/TimeSlotList.js';
|
|
10
10
|
import { TimeSlotProvider, Info as CoreTimeSlotInfo, } from '../core/time-slot-list/TimeSlot.js';
|
|
11
|
-
import { StartDate, EndDate, Duration, Actions as TimeSlotActions, Locations, LocationRepeater, Location, StaffMembers, StaffMemberRepeater, StaffMember, } from './TimeSlot.js';
|
|
11
|
+
import { StartDate as TimeSlotStartDate, EndDate as TimeSlotEndDate, Duration, Actions as TimeSlotActions, Locations, LocationRepeater, Location, StaffMembers, StaffMemberRepeater, StaffMember, } from './TimeSlot.js';
|
|
12
12
|
/**
|
|
13
13
|
* TimeSlot with required id field for GenericList
|
|
14
14
|
*/
|
|
@@ -19,6 +19,7 @@ var TestIds;
|
|
|
19
19
|
TestIds["timeSlotListTimeSlot"] = "time-slot-list-time-slot";
|
|
20
20
|
TestIds["timeSlotListTimezone"] = "time-slot-list-timezone";
|
|
21
21
|
TestIds["timeSlotListLoadMore"] = "time-slot-list-load-more";
|
|
22
|
+
TestIds["timeSlotListDateRangeInput"] = "time-slot-list-date-range-input";
|
|
22
23
|
})(TestIds || (TestIds = {}));
|
|
23
24
|
/**
|
|
24
25
|
* Displays the current timezone with customizable rendering.
|
|
@@ -26,19 +27,14 @@ var TestIds;
|
|
|
26
27
|
* @component
|
|
27
28
|
* @example
|
|
28
29
|
* ```tsx
|
|
29
|
-
* // Default usage
|
|
30
|
+
* // Default usage - shows display name
|
|
30
31
|
* <TimeSlotList.Timezone className="text-sm text-gray-600" />
|
|
31
32
|
*
|
|
32
|
-
* // asChild with
|
|
33
|
+
* // asChild with render function for custom formatting
|
|
33
34
|
* <TimeSlotList.Timezone asChild>
|
|
34
|
-
*
|
|
35
|
-
* </TimeSlotList.Timezone>
|
|
36
|
-
*
|
|
37
|
-
* // asChild with render function
|
|
38
|
-
* <TimeSlotList.Timezone asChild>
|
|
39
|
-
* {({ timezone }) => (
|
|
35
|
+
* {({ timezone, displayName }) => (
|
|
40
36
|
* <span className="text-sm">
|
|
41
|
-
*
|
|
37
|
+
* {displayName} // "Eastern Standard Time"
|
|
42
38
|
* </span>
|
|
43
39
|
* )}
|
|
44
40
|
* </TimeSlotList.Timezone>
|
|
@@ -46,7 +42,7 @@ var TestIds;
|
|
|
46
42
|
*/
|
|
47
43
|
export const Timezone = React.forwardRef((props, ref) => {
|
|
48
44
|
const { asChild, children, className } = props;
|
|
49
|
-
return (_jsx(CoreTimeSlotList.Timezone, { children: ({ timezone }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotListTimezone, customElement: children, customElementProps: { timezone }, children: _jsx("span", { children:
|
|
45
|
+
return (_jsx(CoreTimeSlotList.Timezone, { children: ({ timezone, displayName }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotListTimezone, customElement: children, customElementProps: { timezone, displayName }, children: _jsx("span", { children: displayName }) })) }));
|
|
50
46
|
});
|
|
51
47
|
Timezone.displayName = 'TimeSlotList.Timezone';
|
|
52
48
|
/**
|
|
@@ -81,7 +77,7 @@ Root.displayName = 'TimeSlotList.Root';
|
|
|
81
77
|
*/
|
|
82
78
|
const RootContent = React.forwardRef((props, ref) => {
|
|
83
79
|
const { children, className, variant } = props;
|
|
84
|
-
return (_jsx(CoreTimeSlotList.ListData, { children: ({ timeSlots, hasMore, isLoading, loadMore }) => (_jsx(GenericList.Root, { items: timeSlots, loadMore: loadMore, hasMore: hasMore, isLoading: isLoading, className: className, ref: ref, "data-testid": TestIds.timeSlotListRoot, variant: variant, children: children })) }));
|
|
80
|
+
return (_jsx(CoreTimeSlotList.ListData, { children: ({ timeSlots, hasMore, isLoading, error, loadMore }) => (_jsx(GenericList.Root, { items: timeSlots, loadMore: loadMore, hasMore: hasMore, isLoading: isLoading, error: error, className: className, ref: ref, "data-testid": TestIds.timeSlotListRoot, variant: variant, children: children })) }));
|
|
85
81
|
});
|
|
86
82
|
RootContent.displayName = 'TimeSlotList.RootContent';
|
|
87
83
|
/**
|
|
@@ -107,8 +103,8 @@ TimeSlots.displayName = 'TimeSlotList.TimeSlots';
|
|
|
107
103
|
* Internal component that wraps each time slot item with context provider.
|
|
108
104
|
* This is separate to allow using the service hook within the repeater.
|
|
109
105
|
*/
|
|
110
|
-
const TimeSlotItemWrapper = ({
|
|
111
|
-
return (_jsx(TimeSlotProvider, {
|
|
106
|
+
const TimeSlotItemWrapper = ({ timeSlot, children, }) => {
|
|
107
|
+
return (_jsx(TimeSlotProvider, { timeSlot: timeSlot, children: _jsx(CoreTimeSlotInfo, { children: ({ bookable, isSelected }) => (_jsx("div", { "data-testid": TestIds.timeSlotListTimeSlot, "data-slot-start": timeSlot.localStartDate, "data-slot-id": timeSlot.id, "data-bookable": bookable, "data-selected": isSelected, children: children })) }) }));
|
|
112
108
|
};
|
|
113
109
|
/**
|
|
114
110
|
* Repeater component that renders each time slot.
|
|
@@ -127,7 +123,7 @@ const TimeSlotItemWrapper = ({ slot, children, }) => {
|
|
|
127
123
|
*/
|
|
128
124
|
export const TimeSlotRepeater = React.forwardRef((props, ref) => {
|
|
129
125
|
const { children } = props;
|
|
130
|
-
return (_jsx(GenericList.Repeater, { ref: ref, itemWrapper: ({ item:
|
|
126
|
+
return (_jsx(GenericList.Repeater, { ref: ref, itemWrapper: ({ item: timeSlot, children: itemChildren }) => (_jsx(TimeSlotItemWrapper, { timeSlot: timeSlot, children: itemChildren }, timeSlot.id)), children: children }));
|
|
131
127
|
});
|
|
132
128
|
TimeSlotRepeater.displayName = 'TimeSlotList.TimeSlotRepeater';
|
|
133
129
|
/**
|
|
@@ -153,6 +149,40 @@ LoadMore.displayName = 'TimeSlotList.Actions.LoadMore';
|
|
|
153
149
|
export const Actions = {
|
|
154
150
|
LoadMore,
|
|
155
151
|
};
|
|
152
|
+
/**
|
|
153
|
+
* Date range input for setting the start and end date filter.
|
|
154
|
+
* Uses the core DateRange component to access service data.
|
|
155
|
+
* Both dates are set together and trigger a single fetch.
|
|
156
|
+
*
|
|
157
|
+
* @component
|
|
158
|
+
* @example
|
|
159
|
+
* ```tsx
|
|
160
|
+
* <TimeSlotList.DateRange.Input asChild>
|
|
161
|
+
* {({ startDate, endDate, onChange }) => (
|
|
162
|
+
* <MyDateRangePicker
|
|
163
|
+
* start={startDate}
|
|
164
|
+
* end={endDate}
|
|
165
|
+
* onRangeChange={(start, end) => onChange(start, end)}
|
|
166
|
+
* />
|
|
167
|
+
* )}
|
|
168
|
+
* </TimeSlotList.DateRange.Input>
|
|
169
|
+
* ```
|
|
170
|
+
*/
|
|
171
|
+
export const DateRangeInput = React.forwardRef((props, ref) => {
|
|
172
|
+
const { asChild, children, className, ...otherProps } = props;
|
|
173
|
+
return (_jsx(CoreTimeSlotList.DateRange, { children: ({ startDate, endDate, setDateRange }) => (_jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.timeSlotListDateRangeInput, customElement: children, customElementProps: {
|
|
174
|
+
startDate,
|
|
175
|
+
endDate,
|
|
176
|
+
onChange: setDateRange,
|
|
177
|
+
}, ...otherProps, children: _jsxs("div", { children: [startDate.toLocaleDateString(), " - ", endDate.toLocaleDateString()] }) })) }));
|
|
178
|
+
});
|
|
179
|
+
DateRangeInput.displayName = 'TimeSlotList.DateRange.Input';
|
|
180
|
+
/**
|
|
181
|
+
* DateRange namespace for TimeSlotList date range filter
|
|
182
|
+
*/
|
|
183
|
+
export const DateRange = {
|
|
184
|
+
Input: DateRangeInput,
|
|
185
|
+
};
|
|
156
186
|
// ============================================================================
|
|
157
187
|
// TimeSlot Entity Components (re-exported)
|
|
158
188
|
// ============================================================================
|
|
@@ -186,8 +216,8 @@ export const Actions = {
|
|
|
186
216
|
* ```
|
|
187
217
|
*/
|
|
188
218
|
export const TimeSlot = {
|
|
189
|
-
StartDate,
|
|
190
|
-
EndDate,
|
|
219
|
+
StartDate: TimeSlotStartDate,
|
|
220
|
+
EndDate: TimeSlotEndDate,
|
|
191
221
|
Duration,
|
|
192
222
|
Actions: TimeSlotActions,
|
|
193
223
|
Locations,
|
|
@@ -4,5 +4,5 @@
|
|
|
4
4
|
*/
|
|
5
5
|
export { ServiceService, ServiceServiceDefinition, type ServiceServiceAPI, type ServiceServiceConfig, type ServiceActions, type ServicePaymentData, loadServiceServiceInitialData, type SuccessServiceServiceConfigResult, type NotFoundServiceServiceConfigResult, } from './service/service.js';
|
|
6
6
|
export { BookingService, BookingServiceDefinition, type BookingServiceConfig, type Location, type BookingServiceActions, } from './booking/booking.js';
|
|
7
|
-
export { TimeSlotListService, TimeSlotListServiceDefinition, ViewMode, loadTimeSlotListConfig, type TimeSlotListServiceAPI, type TimeSlotListServiceActions, type TimeSlotListServiceConfig, type SlotSelection, type LoadTimeSlotListConfigOptions, type LoadTimeSlotListConfigResult, } from './time-slot-list/index.js';
|
|
8
|
-
export { ServiceListService, ServiceListServiceDefinition, type ServiceListServiceAPI, type ServiceListServiceConfig, type ServiceListActions, type QueryOptions, loadServiceListServiceInitialData, type SuccessServiceListServiceConfigResult, type ErrorServiceListServiceConfigResult, } from './service-list/service-list.js';
|
|
7
|
+
export { TimeSlotListService, TimeSlotListServiceDefinition, ViewMode, loadTimeSlotListConfig, type TimeSlotListServiceAPI, type TimeSlotListServiceActions, type TimeSlotListServiceConfig, type SlotSelection, type TimeSlot, type LoadTimeSlotListConfigOptions, type LoadTimeSlotListConfigResult, } from './time-slot-list/index.js';
|
|
8
|
+
export { ServiceListService, ServiceListServiceDefinition, type ServiceListServiceAPI, type ServiceListServiceConfig, type ServiceListFilters, type ServiceListActions, type QueryOptions, loadServiceListServiceInitialData, type SuccessServiceListServiceConfigResult, type ErrorServiceListServiceConfigResult, } from './service-list/service-list.js';
|
|
@@ -2,5 +2,5 @@
|
|
|
2
2
|
* TimeSlotList Service
|
|
3
3
|
* Main export file for time slot list service
|
|
4
4
|
*/
|
|
5
|
-
export { TimeSlotListServiceDefinition, ViewMode, type TimeSlot, type TimeSlotListServiceAPI, type TimeSlotListServiceActions, type TimeSlotListServiceConfig, type SlotSelection, } from './time-slot-list.def.js';
|
|
5
|
+
export { TimeSlotListServiceDefinition, ViewMode, type TimeSlot, type TimeSlotListServiceAPI, type TimeSlotListServiceActions, type TimeSlotListServiceConfig, type TimeSlotsFilters, type SlotSelection, } from './time-slot-list.def.js';
|
|
6
6
|
export { TimeSlotListService, loadTimeSlotListConfig, type LoadTimeSlotListConfigOptions, type LoadTimeSlotListConfigResult, } from './time-slot-list.js';
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Manages time slot fetching, selection, and state
|
|
4
4
|
*/
|
|
5
5
|
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
6
|
-
import { type TimeSlotListServiceConfig,
|
|
6
|
+
import { type TimeSlotListServiceConfig, type TimeSlotsFilters } from './time-slot-list.def.js';
|
|
7
7
|
export declare const TimeSlotListService: import("@wix/services-definitions").ServiceFactory<string & {
|
|
8
8
|
__api: import("./time-slot-list.def.js").TimeSlotListServiceAPI;
|
|
9
9
|
__config: {};
|
|
@@ -21,8 +21,8 @@ export interface LoadTimeSlotListConfigOptions {
|
|
|
21
21
|
endDate?: Date;
|
|
22
22
|
/** Timezone for formatting (defaults to UTC) */
|
|
23
23
|
timezone?: string;
|
|
24
|
-
/**
|
|
25
|
-
|
|
24
|
+
/** Filters for narrowing down time slots */
|
|
25
|
+
filters?: TimeSlotsFilters;
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Result type for SSR loader - either success with config or error
|
|
@@ -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>;
|
|
@@ -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());
|
|
@@ -81,12 +82,35 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
81
82
|
return config.services;
|
|
82
83
|
return bookingService.bookingItems.get().map((s) => s.service);
|
|
83
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
|
+
};
|
|
106
|
+
};
|
|
84
107
|
/**
|
|
85
108
|
* Fetch time slots from API
|
|
86
109
|
* Called on initialization or when filters change
|
|
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;
|
|
@@ -279,6 +304,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
279
304
|
const selectedServices = getServices();
|
|
280
305
|
if (selectedServices.length === 0)
|
|
281
306
|
return;
|
|
307
|
+
const filters = getFilters();
|
|
282
308
|
isLoadingSignal.set(true);
|
|
283
309
|
try {
|
|
284
310
|
const response = await fetchAvailability({
|
|
@@ -288,6 +314,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
288
314
|
timeZone: timezoneSignal.get(),
|
|
289
315
|
cursorPaging: { cursor },
|
|
290
316
|
viewMode: viewModeSignal.get(),
|
|
317
|
+
...filters,
|
|
291
318
|
});
|
|
292
319
|
// API returns grouped slots, merge with existing
|
|
293
320
|
const existing = timeSlotsSignal.get();
|
|
@@ -310,6 +337,12 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
310
337
|
* Action: Update date range and refetch slots
|
|
311
338
|
*/
|
|
312
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
|
+
}
|
|
313
346
|
startDateSignal.set(newStartDate);
|
|
314
347
|
endDateSignal.set(newEndDate);
|
|
315
348
|
fetchTimeSlots();
|
|
@@ -333,8 +366,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
333
366
|
isLoading: isLoadingSignal,
|
|
334
367
|
error: errorSignal,
|
|
335
368
|
hasMore: hasMoreSignal,
|
|
336
|
-
// Configuration Signals
|
|
337
|
-
viewMode: viewModeSignal,
|
|
369
|
+
// Configuration Signals (viewMode is internal only)
|
|
338
370
|
startDate: startDateSignal,
|
|
339
371
|
endDate: endDateSignal,
|
|
340
372
|
timezone: timezoneSignal,
|
|
@@ -375,23 +407,19 @@ export async function loadTimeSlotListConfig(options) {
|
|
|
375
407
|
const now = new Date();
|
|
376
408
|
const startDate = options.startDate || now;
|
|
377
409
|
const endDate = options.endDate || getDefaultEndDate();
|
|
378
|
-
const viewMode = options.viewMode || ViewMode.CLASSIC;
|
|
379
|
-
// check for location and staff in config
|
|
380
410
|
try {
|
|
381
411
|
const response = await fetchAvailability({
|
|
382
412
|
services: options.services || [],
|
|
383
413
|
fromLocalDate: toLocalDateString(startDate),
|
|
384
414
|
toLocalDate: toLocalDateString(endDate),
|
|
385
415
|
timeZone: options.timezone ?? getClientTimezone(),
|
|
386
|
-
|
|
416
|
+
...options.filters,
|
|
387
417
|
});
|
|
388
418
|
const config = {
|
|
389
|
-
|
|
419
|
+
...options,
|
|
390
420
|
timeSlots: response.timeSlots || [],
|
|
391
421
|
startDate,
|
|
392
422
|
endDate,
|
|
393
|
-
timezone: options.timezone,
|
|
394
|
-
viewMode,
|
|
395
423
|
};
|
|
396
424
|
return { type: 'success', config };
|
|
397
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
|
}
|
|
@@ -6,17 +6,17 @@
|
|
|
6
6
|
* which provides the TimeSlotProvider context.
|
|
7
7
|
*/
|
|
8
8
|
import React from 'react';
|
|
9
|
-
import type
|
|
9
|
+
import { type TimeSlot as TimeSlotType } from '../../../services/time-slot-list/time-slot-list.def.js';
|
|
10
10
|
/**
|
|
11
11
|
* Context value for TimeSlot components
|
|
12
12
|
*/
|
|
13
13
|
export interface TimeSlotContextValue {
|
|
14
14
|
/** The current time slot data */
|
|
15
|
-
|
|
16
|
-
/** Whether this slot is currently selected */
|
|
15
|
+
timeSlot: TimeSlotType;
|
|
16
|
+
/** Whether this time slot is currently selected */
|
|
17
17
|
isSelected: boolean;
|
|
18
|
-
/** Action to select this slot */
|
|
19
|
-
|
|
18
|
+
/** Action to select this time slot */
|
|
19
|
+
selectTimeSlot: () => void;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
22
22
|
* Hook to access TimeSlot context
|
|
@@ -28,7 +28,7 @@ export declare function useTimeSlotContext(): TimeSlotContextValue;
|
|
|
28
28
|
*/
|
|
29
29
|
export interface TimeSlotProviderProps {
|
|
30
30
|
children: React.ReactNode;
|
|
31
|
-
|
|
31
|
+
timeSlot: TimeSlotType;
|
|
32
32
|
}
|
|
33
33
|
/**
|
|
34
34
|
* Provider component used by TimeSlotRepeater to provide context.
|
|
@@ -88,18 +88,18 @@ export declare function StaffMemberProvider(props: StaffMemberProviderProps): Re
|
|
|
88
88
|
*/
|
|
89
89
|
export interface InfoProps {
|
|
90
90
|
children: (data: {
|
|
91
|
-
/** Raw localStartDate string from
|
|
91
|
+
/** Raw localStartDate string from timeSlot */
|
|
92
92
|
startDate: string | undefined;
|
|
93
|
-
/** Raw localEndDate string from
|
|
93
|
+
/** Raw localEndDate string from timeSlot */
|
|
94
94
|
endDate: string | undefined;
|
|
95
95
|
/** Calculated duration in minutes (null if dates unavailable) */
|
|
96
96
|
durationInMinutes: number | null;
|
|
97
|
-
/** Whether the
|
|
97
|
+
/** Whether the timeSlot is bookable */
|
|
98
98
|
bookable: boolean;
|
|
99
|
-
/** Whether this
|
|
99
|
+
/** Whether this timeSlot is currently selected */
|
|
100
100
|
isSelected: boolean;
|
|
101
|
-
/** The raw
|
|
102
|
-
|
|
101
|
+
/** The raw timeSlot object for advanced use cases */
|
|
102
|
+
timeSlot: TimeSlotType;
|
|
103
103
|
}) => React.ReactNode;
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
@@ -125,15 +125,15 @@ export declare function Info(props: InfoProps): React.ReactNode;
|
|
|
125
125
|
*/
|
|
126
126
|
export interface ActionsProps {
|
|
127
127
|
children: (data: {
|
|
128
|
-
/** Action to select this
|
|
129
|
-
|
|
128
|
+
/** Action to select this timeSlot */
|
|
129
|
+
selectTimeSlot: () => void;
|
|
130
130
|
/** Action to clear preferences */
|
|
131
131
|
clearPreferences?: () => void;
|
|
132
|
-
/** The raw
|
|
133
|
-
|
|
134
|
-
/** Whether the
|
|
132
|
+
/** The raw timeSlot object */
|
|
133
|
+
timeSlot: TimeSlotType;
|
|
134
|
+
/** Whether the timeSlot is bookable */
|
|
135
135
|
bookable: boolean;
|
|
136
|
-
/** Whether this
|
|
136
|
+
/** Whether this timeSlot is currently selected */
|
|
137
137
|
isSelected: boolean;
|
|
138
138
|
}) => React.ReactNode;
|
|
139
139
|
}
|
|
@@ -187,6 +187,8 @@ export interface LocationsProps {
|
|
|
187
187
|
* Extracts locations from slot.staffByLocation and enriches with name from originalSlots.
|
|
188
188
|
* Each location includes isSelected and isSelectable based on current selection state.
|
|
189
189
|
*
|
|
190
|
+
* Returns null if there's only 1 location (nothing to choose from).
|
|
191
|
+
*
|
|
190
192
|
* @component
|
|
191
193
|
* @example
|
|
192
194
|
* ```tsx
|
|
@@ -291,6 +293,8 @@ export interface StaffMembersProps {
|
|
|
291
293
|
* Extracts unique staff members from slot.staffByLocation (names already included).
|
|
292
294
|
* Each staff member includes isSelected and isSelectable based on current selection state.
|
|
293
295
|
*
|
|
296
|
+
* Returns null if there's only 1 staff member (nothing to choose from).
|
|
297
|
+
*
|
|
294
298
|
* @component
|
|
295
299
|
* @example
|
|
296
300
|
* ```tsx
|