@wix/headless-bookings 0.0.11 → 0.0.13
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/index.d.ts +1 -0
- package/cjs/dist/api/index.js +1 -0
- package/cjs/dist/api/query-services/index.d.ts +6 -7
- package/cjs/dist/api/query-services/index.js +4 -4
- package/cjs/dist/api/time-slot-list/index.d.ts +53 -0
- package/cjs/dist/api/time-slot-list/index.js +133 -0
- package/cjs/dist/react/core/booking/Booking.d.ts +7 -7
- package/cjs/dist/react/core/booking/Booking.js +5 -5
- package/cjs/dist/react/core/location/Location.d.ts +3 -3
- package/cjs/dist/react/core/location/Location.js +2 -2
- package/cjs/dist/react/core/service/Service.d.ts +8 -8
- package/cjs/dist/react/core/service-list/ServiceList.d.ts +2 -2
- package/cjs/dist/react/location/Location.d.ts +3 -3
- package/cjs/dist/react/service/Service.d.ts +9 -9
- package/cjs/dist/react/service/Service.js +5 -5
- package/cjs/dist/react/service-list/ServiceList.d.ts +3 -3
- package/cjs/dist/services/booking/booking.d.ts +25 -16
- package/cjs/dist/services/booking/booking.js +50 -16
- package/cjs/dist/services/index.d.ts +2 -1
- package/cjs/dist/services/index.js +1 -0
- package/cjs/dist/services/service/service.d.ts +5 -5
- package/cjs/dist/services/service/service.js +20 -21
- package/cjs/dist/services/service-list/service-list.d.ts +3 -3
- package/cjs/dist/services/time-slot-list/index.d.ts +6 -0
- package/cjs/dist/services/time-slot-list/index.js +6 -0
- package/cjs/dist/services/time-slot-list/time-slot-list.d.ts +58 -0
- package/cjs/dist/services/time-slot-list/time-slot-list.def.d.ts +84 -0
- package/cjs/dist/services/time-slot-list/time-slot-list.def.js +17 -0
- package/cjs/dist/services/time-slot-list/time-slot-list.js +349 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/query-services/index.d.ts +6 -7
- package/dist/api/query-services/index.js +4 -4
- package/dist/api/time-slot-list/index.d.ts +53 -0
- package/dist/api/time-slot-list/index.js +133 -0
- package/dist/react/core/booking/Booking.d.ts +7 -7
- package/dist/react/core/booking/Booking.js +5 -5
- package/dist/react/core/location/Location.d.ts +3 -3
- package/dist/react/core/location/Location.js +2 -2
- package/dist/react/core/service/Service.d.ts +8 -8
- package/dist/react/core/service-list/ServiceList.d.ts +2 -2
- package/dist/react/location/Location.d.ts +3 -3
- package/dist/react/service/Service.d.ts +9 -9
- package/dist/react/service/Service.js +5 -5
- package/dist/react/service-list/ServiceList.d.ts +3 -3
- package/dist/services/booking/booking.d.ts +25 -16
- package/dist/services/booking/booking.js +50 -16
- package/dist/services/index.d.ts +2 -1
- package/dist/services/index.js +1 -0
- package/dist/services/service/service.d.ts +5 -5
- package/dist/services/service/service.js +20 -21
- package/dist/services/service-list/service-list.d.ts +3 -3
- package/dist/services/time-slot-list/index.d.ts +6 -0
- package/dist/services/time-slot-list/index.js +6 -0
- package/dist/services/time-slot-list/time-slot-list.d.ts +58 -0
- package/dist/services/time-slot-list/time-slot-list.def.d.ts +84 -0
- package/dist/services/time-slot-list/time-slot-list.def.js +17 -0
- package/dist/services/time-slot-list/time-slot-list.js +349 -0
- package/package.json +2 -2
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Handles querying and pagination of services list (NO selection management)
|
|
4
4
|
*/
|
|
5
5
|
import { type Signal, type ReadOnlySignal } from '@wix/services-definitions/core-services/signals';
|
|
6
|
-
import {
|
|
6
|
+
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
7
7
|
import type { QueryServicesRequest, PagingMetadata } from '../../api/query-services/index.js';
|
|
8
8
|
export type { ServiceListFilters } from '../../api/query-services/index.js';
|
|
9
9
|
/**
|
|
@@ -15,7 +15,7 @@ export type QueryOptions = Omit<QueryServicesRequest, 'pagingMetadata'>;
|
|
|
15
15
|
* Configuration for the ServiceList service
|
|
16
16
|
*/
|
|
17
17
|
export interface ServiceListServiceConfig {
|
|
18
|
-
services?:
|
|
18
|
+
services?: Service[];
|
|
19
19
|
options?: QueryServicesRequest;
|
|
20
20
|
}
|
|
21
21
|
/**
|
|
@@ -30,7 +30,7 @@ export interface ServiceListActions {
|
|
|
30
30
|
*/
|
|
31
31
|
export interface ServiceListServiceAPI {
|
|
32
32
|
/** Reactive signal containing the list of services */
|
|
33
|
-
servicesSignal: Signal<
|
|
33
|
+
servicesSignal: Signal<Service[]>;
|
|
34
34
|
/** Reactive signal containing current query options (appId, filters, sort) - changes trigger re-fetch */
|
|
35
35
|
queryOptionsSignal: Signal<QueryOptions>;
|
|
36
36
|
/** Reactive signal containing pagination metadata (limit, offset, count, hasNext) - updated after fetches */
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimeSlotList Service
|
|
3
|
+
* Main export file for time slot list service
|
|
4
|
+
*/
|
|
5
|
+
export { TimeSlotListServiceDefinition, ViewMode, type TimeSlotListServiceAPI, type TimeSlotListServiceActions, type TimeSlotListServiceConfig, type SlotSelection, } from './time-slot-list.def.js';
|
|
6
|
+
export { TimeSlotListService, loadTimeSlotListConfig, type LoadTimeSlotListConfigOptions, type LoadTimeSlotListConfigResult, } from './time-slot-list.js';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimeSlotList Service Implementation
|
|
3
|
+
* Manages time slot fetching, selection, and state
|
|
4
|
+
*/
|
|
5
|
+
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
6
|
+
import { type TimeSlotListServiceConfig, ViewMode } from './time-slot-list.def.js';
|
|
7
|
+
export declare const TimeSlotListService: import("@wix/services-definitions").ServiceFactory<string & {
|
|
8
|
+
__api: import("./time-slot-list.def.js").TimeSlotListServiceAPI;
|
|
9
|
+
__config: {};
|
|
10
|
+
isServiceDefinition?: boolean;
|
|
11
|
+
} & import("./time-slot-list.def.js").TimeSlotListServiceAPI, TimeSlotListServiceConfig>;
|
|
12
|
+
/**
|
|
13
|
+
* Options for loading time slots configuration on the server
|
|
14
|
+
*/
|
|
15
|
+
export interface LoadTimeSlotListConfigOptions {
|
|
16
|
+
/** Services to fetch time slots for */
|
|
17
|
+
services?: Service[];
|
|
18
|
+
/** Start date for the time range (defaults to current date) */
|
|
19
|
+
startDate?: Date;
|
|
20
|
+
/** End date for the time range (defaults to 7 days from start) */
|
|
21
|
+
endDate?: Date;
|
|
22
|
+
/** Timezone for formatting (defaults to UTC) */
|
|
23
|
+
timezone?: string;
|
|
24
|
+
/** View mode (defaults to CLASSIC) */
|
|
25
|
+
viewMode?: ViewMode;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Result type for SSR loader - either success with config or error
|
|
29
|
+
*/
|
|
30
|
+
export type LoadTimeSlotListConfigResult = {
|
|
31
|
+
type: 'success';
|
|
32
|
+
config: TimeSlotListServiceConfig;
|
|
33
|
+
} | {
|
|
34
|
+
type: 'error';
|
|
35
|
+
error: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* SSR Loader: Pre-fetches time slots for server-side rendering
|
|
39
|
+
* Use this in Astro/Next.js getServerSideProps or page loaders
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* // In Astro page
|
|
44
|
+
* const loadResult = await loadTimeSlotListConfig({
|
|
45
|
+
* services: bookingServices,
|
|
46
|
+
* startDate: new Date(),
|
|
47
|
+
* endDate: addDays(new Date(), 7),
|
|
48
|
+
* timezone: 'America/New_York',
|
|
49
|
+
* });
|
|
50
|
+
*
|
|
51
|
+
* if (loadResult.type === 'error') {
|
|
52
|
+
* // Handle error
|
|
53
|
+
* }
|
|
54
|
+
*
|
|
55
|
+
* const timeSlotListConfig = loadResult.config;
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function loadTimeSlotListConfig(options: LoadTimeSlotListConfigOptions): Promise<LoadTimeSlotListConfigResult>;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimeSlotList Service Definition
|
|
3
|
+
* Defines interfaces and service contract for time slot management
|
|
4
|
+
*/
|
|
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';
|
|
7
|
+
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
8
|
+
export declare enum ViewMode {
|
|
9
|
+
CLASSIC = "classic",
|
|
10
|
+
AGENDA = "agenda"
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Maps a location to its available staff members
|
|
14
|
+
*/
|
|
15
|
+
export interface StaffByLocation {
|
|
16
|
+
location: {
|
|
17
|
+
id: string;
|
|
18
|
+
name: string;
|
|
19
|
+
};
|
|
20
|
+
staffMembers: {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
}[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Grouped time slot that represents a unique time with all available locations/staff
|
|
27
|
+
* Extends the API TimeSlot type but replaces location-specific fields with aggregated data
|
|
28
|
+
*/
|
|
29
|
+
export interface TimeSlot extends Omit<ApiTimeSlot, 'location' | 'availableResources'> {
|
|
30
|
+
/** Maps each location to its available staff members */
|
|
31
|
+
staffByLocation: StaffByLocation[];
|
|
32
|
+
/** Original API slots for this time (needed for booking service) */
|
|
33
|
+
originalSlots: ApiTimeSlot[];
|
|
34
|
+
/** Unique identifier for the time slot */
|
|
35
|
+
id: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* User's selection state
|
|
39
|
+
*/
|
|
40
|
+
export interface SlotSelection {
|
|
41
|
+
slot: TimeSlot;
|
|
42
|
+
locationId: string | null;
|
|
43
|
+
staffMemberId: string | null;
|
|
44
|
+
}
|
|
45
|
+
export interface TimeSlotListServiceConfig {
|
|
46
|
+
/** Services to fetch time slots for */
|
|
47
|
+
services?: Service[];
|
|
48
|
+
/** Pre-loaded time slots (from SSR) - already grouped by time */
|
|
49
|
+
timeSlots?: TimeSlot[];
|
|
50
|
+
/** Start date for the time range */
|
|
51
|
+
startDate?: Date;
|
|
52
|
+
/** End date for the time range */
|
|
53
|
+
endDate?: Date;
|
|
54
|
+
/** Timezone for formatting */
|
|
55
|
+
timezone?: string;
|
|
56
|
+
/** View mode */
|
|
57
|
+
viewMode?: ViewMode;
|
|
58
|
+
}
|
|
59
|
+
export interface TimeSlotListServiceActions {
|
|
60
|
+
selectSlot: (slot: TimeSlot) => void;
|
|
61
|
+
selectLocation: (locationId: string) => void;
|
|
62
|
+
selectStaffMember: (staffMemberId: string) => void;
|
|
63
|
+
clearPreferences: () => void;
|
|
64
|
+
loadMore: () => Promise<void>;
|
|
65
|
+
setDateRange: (start: Date, end: Date) => void;
|
|
66
|
+
setTimezone: (tz: string) => void;
|
|
67
|
+
}
|
|
68
|
+
export interface TimeSlotListServiceAPI {
|
|
69
|
+
timeSlots: Signal<TimeSlot[]>;
|
|
70
|
+
selection: Signal<SlotSelection | null>;
|
|
71
|
+
isLoading: Signal<boolean>;
|
|
72
|
+
error: Signal<string | null>;
|
|
73
|
+
hasMore: Signal<boolean>;
|
|
74
|
+
viewMode: Signal<ViewMode>;
|
|
75
|
+
startDate: Signal<Date>;
|
|
76
|
+
endDate: Signal<Date>;
|
|
77
|
+
timezone: Signal<string | undefined>;
|
|
78
|
+
actions: TimeSlotListServiceActions;
|
|
79
|
+
}
|
|
80
|
+
export declare const TimeSlotListServiceDefinition: string & {
|
|
81
|
+
__api: TimeSlotListServiceAPI;
|
|
82
|
+
__config: {};
|
|
83
|
+
isServiceDefinition?: boolean;
|
|
84
|
+
} & TimeSlotListServiceAPI;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimeSlotList Service Definition
|
|
3
|
+
* Defines interfaces and service contract for time slot management
|
|
4
|
+
*/
|
|
5
|
+
import { defineService } from '@wix/services-definitions';
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Enums
|
|
8
|
+
// ============================================================================
|
|
9
|
+
export var ViewMode;
|
|
10
|
+
(function (ViewMode) {
|
|
11
|
+
ViewMode["CLASSIC"] = "classic";
|
|
12
|
+
ViewMode["AGENDA"] = "agenda";
|
|
13
|
+
})(ViewMode || (ViewMode = {}));
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Service Definition
|
|
16
|
+
// ============================================================================
|
|
17
|
+
export const TimeSlotListServiceDefinition = defineService('time-slot-list');
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimeSlotList Service Implementation
|
|
3
|
+
* Manages time slot fetching, selection, and state
|
|
4
|
+
*/
|
|
5
|
+
import { implementService } from '@wix/services-definitions';
|
|
6
|
+
import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
|
|
7
|
+
import { TimeSlotListServiceDefinition, ViewMode, } from './time-slot-list.def.js';
|
|
8
|
+
import { fetchAvailability } from '../../api/time-slot-list/index.js';
|
|
9
|
+
import { BookingServiceDefinition } from '../booking/booking.js';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Helpers
|
|
12
|
+
// ============================================================================
|
|
13
|
+
function getDefaultEndDate() {
|
|
14
|
+
return new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);
|
|
15
|
+
}
|
|
16
|
+
function getClientTimezone() {
|
|
17
|
+
return typeof Intl !== 'undefined'
|
|
18
|
+
? Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
19
|
+
: undefined;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Formats a Date to local ISO string (YYYY-MM-DDThh:mm:ss)
|
|
23
|
+
*/
|
|
24
|
+
function toLocalDateString(date) {
|
|
25
|
+
const year = date.getFullYear();
|
|
26
|
+
const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
27
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
28
|
+
const hours = String(date.getHours()).padStart(2, '0');
|
|
29
|
+
const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
30
|
+
const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
31
|
+
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}`;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Merges new grouped slots with existing ones
|
|
35
|
+
* Slots with the same start time are merged
|
|
36
|
+
*/
|
|
37
|
+
function mergeGroupedSlots(existing, newSlots) {
|
|
38
|
+
const mergedMap = new Map();
|
|
39
|
+
for (const slot of existing) {
|
|
40
|
+
mergedMap.set(slot.localStartDate, slot);
|
|
41
|
+
}
|
|
42
|
+
for (const newSlot of newSlots) {
|
|
43
|
+
const key = newSlot.localStartDate;
|
|
44
|
+
const existingSlot = mergedMap.get(key);
|
|
45
|
+
if (existingSlot) {
|
|
46
|
+
// Merge staffByLocation and originalSlots
|
|
47
|
+
existingSlot.staffByLocation.push(...newSlot.staffByLocation);
|
|
48
|
+
existingSlot.originalSlots.push(...newSlot.originalSlots);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
mergedMap.set(key, newSlot);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return Array.from(mergedMap.values());
|
|
55
|
+
}
|
|
56
|
+
// ============================================================================
|
|
57
|
+
// Service Implementation
|
|
58
|
+
// ============================================================================
|
|
59
|
+
export const TimeSlotListService = implementService.withConfig()(TimeSlotListServiceDefinition, ({ getService, config }) => {
|
|
60
|
+
// Get dependent services
|
|
61
|
+
const signalsService = getService(SignalsServiceDefinition);
|
|
62
|
+
const bookingService = getService(BookingServiceDefinition);
|
|
63
|
+
// Initialize default values
|
|
64
|
+
const currentDate = new Date();
|
|
65
|
+
// Initialize Signals
|
|
66
|
+
const timeSlotsSignal = signalsService.signal(config.timeSlots || []);
|
|
67
|
+
const selectionSignal = signalsService.signal(null);
|
|
68
|
+
const isLoadingSignal = signalsService.signal(false);
|
|
69
|
+
const errorSignal = signalsService.signal(null);
|
|
70
|
+
const hasMoreSignal = signalsService.signal(false);
|
|
71
|
+
const viewModeSignal = signalsService.signal(config.viewMode || ViewMode.CLASSIC);
|
|
72
|
+
const startDateSignal = signalsService.signal(config.startDate || currentDate);
|
|
73
|
+
const endDateSignal = signalsService.signal(config.endDate || getDefaultEndDate());
|
|
74
|
+
const timezoneSignal = signalsService.signal(config.timezone ?? getClientTimezone());
|
|
75
|
+
const cursorSignal = signalsService.signal(undefined);
|
|
76
|
+
/**
|
|
77
|
+
* Helper: Get services from config or BookingService
|
|
78
|
+
*/
|
|
79
|
+
const getServices = () => {
|
|
80
|
+
if (config.services?.length)
|
|
81
|
+
return config.services;
|
|
82
|
+
return bookingService.bookingSelections.get().map((s) => s.service);
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Fetch time slots from API
|
|
86
|
+
* Called on initialization or when filters change
|
|
87
|
+
*/
|
|
88
|
+
const fetchTimeSlots = async () => {
|
|
89
|
+
const selectedServices = getServices();
|
|
90
|
+
isLoadingSignal.set(true);
|
|
91
|
+
errorSignal.set(null);
|
|
92
|
+
cursorSignal.set(undefined);
|
|
93
|
+
try {
|
|
94
|
+
const response = await fetchAvailability({
|
|
95
|
+
services: selectedServices,
|
|
96
|
+
fromLocalDate: toLocalDateString(startDateSignal.get()),
|
|
97
|
+
toLocalDate: toLocalDateString(endDateSignal.get()),
|
|
98
|
+
timeZone: timezoneSignal.get(),
|
|
99
|
+
viewMode: viewModeSignal.get(),
|
|
100
|
+
});
|
|
101
|
+
timeSlotsSignal.set(response.timeSlots);
|
|
102
|
+
const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
|
|
103
|
+
cursorSignal.set(nextCursor);
|
|
104
|
+
hasMoreSignal.set(!!nextCursor);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
const message = error instanceof Error
|
|
108
|
+
? error.message
|
|
109
|
+
: 'Failed to load time slots';
|
|
110
|
+
errorSignal.set(message);
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
isLoadingSignal.set(false);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Action: Select a time slot
|
|
118
|
+
* Only updates booking service if there's exactly one location and one resource
|
|
119
|
+
* Otherwise, user must select location/resource before booking
|
|
120
|
+
*/
|
|
121
|
+
const selectSlot = (slot) => {
|
|
122
|
+
const currentSelection = selectionSignal.get();
|
|
123
|
+
if (currentSelection?.slot?.id === slot.id)
|
|
124
|
+
return;
|
|
125
|
+
const hasOneLocation = slot.staffByLocation.length === 1;
|
|
126
|
+
const hasOneResource = hasOneLocation && slot.staffByLocation[0].staffMembers.length === 1;
|
|
127
|
+
if (hasOneLocation && hasOneResource) {
|
|
128
|
+
const originalSlot = slot.originalSlots[0];
|
|
129
|
+
const { location, staffMembers } = slot.staffByLocation[0];
|
|
130
|
+
const staffMemberId = staffMembers[0].id;
|
|
131
|
+
selectionSignal.set({
|
|
132
|
+
slot,
|
|
133
|
+
locationId: location.id,
|
|
134
|
+
staffMemberId,
|
|
135
|
+
});
|
|
136
|
+
bookingService.actions.setTimeSlot(originalSlot);
|
|
137
|
+
bookingService.actions.setLocation({
|
|
138
|
+
id: location.id,
|
|
139
|
+
name: location.name,
|
|
140
|
+
});
|
|
141
|
+
bookingService.actions.updateBookingSelection({
|
|
142
|
+
staff: {
|
|
143
|
+
_id: staffMemberId,
|
|
144
|
+
name: staffMembers[0].name,
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
selectionSignal.set({
|
|
150
|
+
slot,
|
|
151
|
+
locationId: null,
|
|
152
|
+
staffMemberId: null,
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Action: Select a location for the current slot
|
|
158
|
+
* Updates the booking service with the correct original slot for this location
|
|
159
|
+
*/
|
|
160
|
+
const selectLocation = (locationId) => {
|
|
161
|
+
const currentSelections = selectionSignal.get();
|
|
162
|
+
if (!currentSelections?.slot)
|
|
163
|
+
return;
|
|
164
|
+
selectionSignal.set({ ...currentSelections, locationId });
|
|
165
|
+
const location = currentSelections.slot.originalSlots.find((s) => s.location?._id === locationId)?.location;
|
|
166
|
+
if (location) {
|
|
167
|
+
bookingService.actions.setLocation({
|
|
168
|
+
id: location._id,
|
|
169
|
+
name: location.name,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Action: Select a staff member for the current slot
|
|
175
|
+
* Updates the booking service with the selected resource for the slot's service
|
|
176
|
+
*/
|
|
177
|
+
const selectStaffMember = (staffMemberId) => {
|
|
178
|
+
const currentSelections = selectionSignal.get();
|
|
179
|
+
if (!currentSelections?.slot)
|
|
180
|
+
return;
|
|
181
|
+
selectionSignal.set({ ...currentSelections, staffMemberId });
|
|
182
|
+
const slot = currentSelections.slot;
|
|
183
|
+
// Find the resource info from original slots
|
|
184
|
+
for (const originalSlot of slot.originalSlots) {
|
|
185
|
+
for (const resourceGroup of originalSlot.availableResources || []) {
|
|
186
|
+
const staff = resourceGroup.resources?.find((r) => r._id === staffMemberId);
|
|
187
|
+
if (staff) {
|
|
188
|
+
bookingService.actions.updateBookingSelection({
|
|
189
|
+
staff,
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
/**
|
|
197
|
+
* Action: Clear preferences (location and staff) while keeping the slot selected
|
|
198
|
+
*/
|
|
199
|
+
const clearPreferences = () => {
|
|
200
|
+
const currentSelection = selectionSignal.get();
|
|
201
|
+
if (!currentSelection?.slot)
|
|
202
|
+
return;
|
|
203
|
+
// Keep the slot, but clear location and staff selections
|
|
204
|
+
selectionSignal.set({
|
|
205
|
+
slot: currentSelection.slot,
|
|
206
|
+
locationId: null,
|
|
207
|
+
staffMemberId: null,
|
|
208
|
+
});
|
|
209
|
+
// Clear location in booking service
|
|
210
|
+
bookingService.actions.clearLocation();
|
|
211
|
+
// Clear resources (staff) in booking service
|
|
212
|
+
const serviceId = currentSelection.slot.serviceId;
|
|
213
|
+
if (serviceId) {
|
|
214
|
+
bookingService.actions.updateBookingSelection({
|
|
215
|
+
staff: undefined,
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* Action: Load more slots (pagination)
|
|
221
|
+
*/
|
|
222
|
+
const loadMore = async () => {
|
|
223
|
+
const cursor = cursorSignal.get();
|
|
224
|
+
if (!cursor || isLoadingSignal.get())
|
|
225
|
+
return;
|
|
226
|
+
const selectedServices = getServices();
|
|
227
|
+
if (selectedServices.length === 0)
|
|
228
|
+
return;
|
|
229
|
+
isLoadingSignal.set(true);
|
|
230
|
+
try {
|
|
231
|
+
const response = await fetchAvailability({
|
|
232
|
+
services: selectedServices,
|
|
233
|
+
fromLocalDate: toLocalDateString(startDateSignal.get()),
|
|
234
|
+
toLocalDate: toLocalDateString(endDateSignal.get()),
|
|
235
|
+
timeZone: timezoneSignal.get(),
|
|
236
|
+
cursorPaging: { cursor },
|
|
237
|
+
viewMode: viewModeSignal.get(),
|
|
238
|
+
});
|
|
239
|
+
// API returns grouped slots, merge with existing
|
|
240
|
+
const existing = timeSlotsSignal.get();
|
|
241
|
+
timeSlotsSignal.set(mergeGroupedSlots(existing, response.timeSlots));
|
|
242
|
+
const nextCursor = response.cursorPagingMetadata?.cursors?.next ?? undefined;
|
|
243
|
+
cursorSignal.set(nextCursor);
|
|
244
|
+
hasMoreSignal.set(!!nextCursor);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
const message = error instanceof Error
|
|
248
|
+
? error.message
|
|
249
|
+
: 'Failed to load more time slots';
|
|
250
|
+
errorSignal.set(message);
|
|
251
|
+
}
|
|
252
|
+
finally {
|
|
253
|
+
isLoadingSignal.set(false);
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
/**
|
|
257
|
+
* Action: Update date range and refetch slots
|
|
258
|
+
*/
|
|
259
|
+
const setDateRange = (newStartDate, newEndDate) => {
|
|
260
|
+
startDateSignal.set(newStartDate);
|
|
261
|
+
endDateSignal.set(newEndDate);
|
|
262
|
+
fetchTimeSlots();
|
|
263
|
+
};
|
|
264
|
+
/**
|
|
265
|
+
* Action: Update timezone and refetch slots
|
|
266
|
+
*/
|
|
267
|
+
const setTimezone = (newTimezone) => {
|
|
268
|
+
timezoneSignal.set(newTimezone);
|
|
269
|
+
fetchTimeSlots();
|
|
270
|
+
};
|
|
271
|
+
// Auto-fetch on initialization if no pre-loaded data provided
|
|
272
|
+
if (!config.timeSlots && config.services && config.services.length > 0) {
|
|
273
|
+
fetchTimeSlots();
|
|
274
|
+
}
|
|
275
|
+
// Return service API
|
|
276
|
+
return {
|
|
277
|
+
// State Signals
|
|
278
|
+
timeSlots: timeSlotsSignal,
|
|
279
|
+
selection: selectionSignal,
|
|
280
|
+
isLoading: isLoadingSignal,
|
|
281
|
+
error: errorSignal,
|
|
282
|
+
hasMore: hasMoreSignal,
|
|
283
|
+
// Configuration Signals
|
|
284
|
+
viewMode: viewModeSignal,
|
|
285
|
+
startDate: startDateSignal,
|
|
286
|
+
endDate: endDateSignal,
|
|
287
|
+
timezone: timezoneSignal,
|
|
288
|
+
// Actions
|
|
289
|
+
actions: {
|
|
290
|
+
selectSlot,
|
|
291
|
+
selectLocation,
|
|
292
|
+
selectStaffMember,
|
|
293
|
+
clearPreferences,
|
|
294
|
+
loadMore,
|
|
295
|
+
setDateRange,
|
|
296
|
+
setTimezone,
|
|
297
|
+
},
|
|
298
|
+
};
|
|
299
|
+
});
|
|
300
|
+
/**
|
|
301
|
+
* SSR Loader: Pre-fetches time slots for server-side rendering
|
|
302
|
+
* Use this in Astro/Next.js getServerSideProps or page loaders
|
|
303
|
+
*
|
|
304
|
+
* @example
|
|
305
|
+
* ```typescript
|
|
306
|
+
* // In Astro page
|
|
307
|
+
* const loadResult = await loadTimeSlotListConfig({
|
|
308
|
+
* services: bookingServices,
|
|
309
|
+
* startDate: new Date(),
|
|
310
|
+
* endDate: addDays(new Date(), 7),
|
|
311
|
+
* timezone: 'America/New_York',
|
|
312
|
+
* });
|
|
313
|
+
*
|
|
314
|
+
* if (loadResult.type === 'error') {
|
|
315
|
+
* // Handle error
|
|
316
|
+
* }
|
|
317
|
+
*
|
|
318
|
+
* const timeSlotListConfig = loadResult.config;
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
export async function loadTimeSlotListConfig(options) {
|
|
322
|
+
const now = new Date();
|
|
323
|
+
const startDate = options.startDate || now;
|
|
324
|
+
const endDate = options.endDate || getDefaultEndDate();
|
|
325
|
+
const viewMode = options.viewMode || ViewMode.CLASSIC;
|
|
326
|
+
// check for location and staff in config
|
|
327
|
+
try {
|
|
328
|
+
const response = await fetchAvailability({
|
|
329
|
+
services: options.services || [],
|
|
330
|
+
fromLocalDate: toLocalDateString(startDate),
|
|
331
|
+
toLocalDate: toLocalDateString(endDate),
|
|
332
|
+
timeZone: options.timezone ?? getClientTimezone(),
|
|
333
|
+
viewMode,
|
|
334
|
+
});
|
|
335
|
+
const config = {
|
|
336
|
+
services: options.services,
|
|
337
|
+
timeSlots: response.timeSlots || [],
|
|
338
|
+
startDate,
|
|
339
|
+
endDate,
|
|
340
|
+
timezone: options.timezone,
|
|
341
|
+
viewMode,
|
|
342
|
+
};
|
|
343
|
+
return { type: 'success', config };
|
|
344
|
+
}
|
|
345
|
+
catch (error) {
|
|
346
|
+
const message = error instanceof Error ? error.message : 'Failed to load time slots';
|
|
347
|
+
return { type: 'error', error: message };
|
|
348
|
+
}
|
|
349
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/headless-bookings",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -68,5 +68,5 @@
|
|
|
68
68
|
"groupId": "com.wixpress.headless-components"
|
|
69
69
|
}
|
|
70
70
|
},
|
|
71
|
-
"falconPackageHash": "
|
|
71
|
+
"falconPackageHash": "fb9d6c4ddf1f28c1393d4c042861417e97c5e95f9ab301c1f5faaabe"
|
|
72
72
|
}
|