@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.
Files changed (29) hide show
  1. package/cjs/dist/api/time-slot-list/index.d.ts +21 -1
  2. package/cjs/dist/api/time-slot-list/index.js +46 -8
  3. package/cjs/dist/react/core/time-slot-list/TimeSlot.d.ts +22 -18
  4. package/cjs/dist/react/core/time-slot-list/TimeSlot.js +41 -28
  5. package/cjs/dist/react/core/time-slot-list/TimeSlotList.d.ts +39 -7
  6. package/cjs/dist/react/core/time-slot-list/TimeSlotList.js +56 -11
  7. package/cjs/dist/react/time-slot-list/TimeSlot.js +3 -3
  8. package/cjs/dist/react/time-slot-list/TimeSlotList.d.ts +55 -12
  9. package/cjs/dist/react/time-slot-list/TimeSlotList.js +48 -18
  10. package/cjs/dist/services/index.d.ts +2 -2
  11. package/cjs/dist/services/time-slot-list/index.d.ts +1 -1
  12. package/cjs/dist/services/time-slot-list/time-slot-list.d.ts +3 -3
  13. package/cjs/dist/services/time-slot-list/time-slot-list.def.d.ts +23 -4
  14. package/cjs/dist/services/time-slot-list/time-slot-list.js +37 -9
  15. package/dist/api/time-slot-list/index.d.ts +21 -1
  16. package/dist/api/time-slot-list/index.js +46 -8
  17. package/dist/react/core/time-slot-list/TimeSlot.d.ts +22 -18
  18. package/dist/react/core/time-slot-list/TimeSlot.js +41 -28
  19. package/dist/react/core/time-slot-list/TimeSlotList.d.ts +39 -7
  20. package/dist/react/core/time-slot-list/TimeSlotList.js +56 -11
  21. package/dist/react/time-slot-list/TimeSlot.js +3 -3
  22. package/dist/react/time-slot-list/TimeSlotList.d.ts +55 -12
  23. package/dist/react/time-slot-list/TimeSlotList.js +48 -18
  24. package/dist/services/index.d.ts +2 -2
  25. package/dist/services/time-slot-list/index.d.ts +1 -1
  26. package/dist/services/time-slot-list/time-slot-list.d.ts +3 -3
  27. package/dist/services/time-slot-list/time-slot-list.def.d.ts +23 -4
  28. package/dist/services/time-slot-list/time-slot-list.js +37 -9
  29. 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 primitive
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
- * Timezone: {timezone || 'Not set'}
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 primitive
33
+ * // asChild with render function for custom formatting
33
34
  * <TimeSlotList.Timezone asChild>
34
- * <span className="font-medium" />
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
- * Timezone: {timezone || 'Not set'}
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: timezone }) })) }));
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 = ({ slot, children, }) => {
111
- return (_jsx(TimeSlotProvider, { slot: slot, children: _jsx(CoreTimeSlotInfo, { children: ({ bookable, isSelected }) => (_jsx("div", { "data-testid": TestIds.timeSlotListTimeSlot, "data-slot-start": slot.localStartDate, "data-slot-id": slot.id, "data-bookable": bookable, "data-selected": isSelected, children: children })) }) }));
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: slot, children: itemChildren }) => (_jsx(TimeSlotItemWrapper, { slot: slot, children: itemChildren }, slot.id)), children: children }));
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, ViewMode } from './time-slot-list.def.js';
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
- /** View mode (defaults to CLASSIC) */
25
- viewMode?: ViewMode;
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
- /** View mode */
57
- viewMode?: ViewMode;
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
- const viewModeSignal = signalsService.signal(config.viewMode || ViewMode.CLASSIC);
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
- viewMode,
416
+ ...options.filters,
387
417
  });
388
418
  const config = {
389
- services: options.services,
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
- const serviceId = selectedServices[0]?._id;
108
- if (!serviceId) {
109
- throw new Error('Service ID is required');
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
- // Phase 2: Class support
130
- throw new Error('Class bookings not yet supported');
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 { TimeSlot as TimeSlotType } from '../../../services/time-slot-list/time-slot-list.def.js';
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
- slot: TimeSlotType;
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
- selectSlot: () => void;
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
- slot: TimeSlotType;
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 slot */
91
+ /** Raw localStartDate string from timeSlot */
92
92
  startDate: string | undefined;
93
- /** Raw localEndDate string from slot */
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 slot is bookable */
97
+ /** Whether the timeSlot is bookable */
98
98
  bookable: boolean;
99
- /** Whether this slot is currently selected */
99
+ /** Whether this timeSlot is currently selected */
100
100
  isSelected: boolean;
101
- /** The raw slot object for advanced use cases */
102
- slot: TimeSlotType;
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 slot */
129
- selectSlot: () => void;
128
+ /** Action to select this timeSlot */
129
+ selectTimeSlot: () => void;
130
130
  /** Action to clear preferences */
131
131
  clearPreferences?: () => void;
132
- /** The raw slot object */
133
- slot: TimeSlotType;
134
- /** Whether the slot is bookable */
132
+ /** The raw timeSlot object */
133
+ timeSlot: TimeSlotType;
134
+ /** Whether the timeSlot is bookable */
135
135
  bookable: boolean;
136
- /** Whether this slot is currently selected */
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