@wix/headless-bookings 0.0.14 → 0.0.16
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/react/core/booking/Booking.d.ts +17 -9
- package/cjs/dist/react/core/booking/Booking.js +13 -7
- package/cjs/dist/react/core/service-list/ServiceList.d.ts +4 -0
- package/cjs/dist/react/core/service-list/ServiceList.js +4 -0
- package/cjs/dist/react/service/Service.js +1 -11
- package/cjs/dist/react/service-list/ServiceList.d.ts +2 -0
- package/cjs/dist/react/service-list/ServiceList.js +23 -35
- package/cjs/dist/services/booking/booking.d.ts +48 -30
- package/cjs/dist/services/booking/booking.js +74 -44
- package/cjs/dist/services/index.d.ts +1 -1
- package/cjs/dist/services/service/service.js +10 -7
- package/cjs/dist/services/time-slot-list/time-slot-list.js +109 -56
- package/dist/react/core/booking/Booking.d.ts +17 -9
- package/dist/react/core/booking/Booking.js +13 -7
- package/dist/react/core/service-list/ServiceList.d.ts +4 -0
- package/dist/react/core/service-list/ServiceList.js +4 -0
- package/dist/react/service/Service.js +1 -11
- package/dist/react/service-list/ServiceList.d.ts +2 -0
- package/dist/react/service-list/ServiceList.js +23 -35
- package/dist/services/booking/booking.d.ts +48 -30
- package/dist/services/booking/booking.js +74 -44
- package/dist/services/index.d.ts +1 -1
- package/dist/services/service/service.js +10 -7
- package/dist/services/time-slot-list/time-slot-list.js +109 -56
- package/package.json +2 -2
|
@@ -6,7 +6,7 @@ import { implementService } from '@wix/services-definitions';
|
|
|
6
6
|
import { SignalsServiceDefinition } from '@wix/services-definitions/core-services/signals';
|
|
7
7
|
import { TimeSlotListServiceDefinition, ViewMode, } from './time-slot-list.def.js';
|
|
8
8
|
import { fetchAvailability } from '../../api/time-slot-list/index.js';
|
|
9
|
-
import { BookingServiceDefinition } from '../booking/booking.js';
|
|
9
|
+
import { BookingServiceDefinition, } from '../booking/booking.js';
|
|
10
10
|
// ============================================================================
|
|
11
11
|
// Helpers
|
|
12
12
|
// ============================================================================
|
|
@@ -79,7 +79,7 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
79
79
|
const getServices = () => {
|
|
80
80
|
if (config.services?.length)
|
|
81
81
|
return config.services;
|
|
82
|
-
return bookingService.
|
|
82
|
+
return bookingService.bookingItems.get().map((s) => s.service);
|
|
83
83
|
};
|
|
84
84
|
/**
|
|
85
85
|
* Fetch time slots from API
|
|
@@ -115,82 +115,139 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
115
115
|
};
|
|
116
116
|
/**
|
|
117
117
|
* Action: Select a time slot
|
|
118
|
-
*
|
|
119
|
-
*
|
|
118
|
+
* Auto-selects location/staff based on availability:
|
|
119
|
+
* - One location + one staff → auto-select both
|
|
120
|
+
* - One location + many staff → auto-select location only
|
|
121
|
+
* - Many locations + one staff → auto-select staff only
|
|
122
|
+
* - Many locations + many staff → don't auto-select anything
|
|
120
123
|
*/
|
|
121
124
|
const selectSlot = (slot) => {
|
|
122
125
|
const currentSelection = selectionSignal.get();
|
|
123
126
|
if (currentSelection?.slot?.id === slot.id)
|
|
124
127
|
return;
|
|
128
|
+
const timeSlot = {
|
|
129
|
+
startDate: slot.localStartDate,
|
|
130
|
+
endDate: slot.localEndDate,
|
|
131
|
+
totalCapacity: slot.totalCapacity ?? undefined,
|
|
132
|
+
remainingCapacity: slot.remainingCapacity ?? undefined,
|
|
133
|
+
scheduleId: slot.scheduleId ?? undefined,
|
|
134
|
+
eventInfo: slot.eventInfo ?? undefined,
|
|
135
|
+
};
|
|
125
136
|
const hasOneLocation = slot.staffByLocation.length === 1;
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
bookingService.actions.
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
137
|
+
// Collect all unique staff members across all locations
|
|
138
|
+
const allStaffMembers = slot.staffByLocation.flatMap((loc) => loc.staffMembers);
|
|
139
|
+
const uniqueStaffIds = new Set(allStaffMembers.map((s) => s.id));
|
|
140
|
+
const hasOneStaff = uniqueStaffIds.size === 1;
|
|
141
|
+
// Determine what to auto-select
|
|
142
|
+
let locationId = null;
|
|
143
|
+
let staffMemberId = null;
|
|
144
|
+
if (hasOneLocation) {
|
|
145
|
+
const { location } = slot.staffByLocation[0];
|
|
146
|
+
locationId = location.id;
|
|
147
|
+
bookingService.actions.setLocation(location);
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
bookingService.actions.clearLocation();
|
|
151
|
+
}
|
|
152
|
+
if (hasOneStaff) {
|
|
153
|
+
const staffMember = allStaffMembers[0];
|
|
154
|
+
staffMemberId = staffMember.id;
|
|
155
|
+
bookingService.actions.updateItem({
|
|
156
|
+
timeSlot,
|
|
157
|
+
resources: [
|
|
158
|
+
{
|
|
159
|
+
_id: staffMemberId,
|
|
160
|
+
name: staffMember.name,
|
|
161
|
+
},
|
|
162
|
+
],
|
|
146
163
|
});
|
|
147
164
|
}
|
|
148
165
|
else {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
staffMemberId: null,
|
|
166
|
+
bookingService.actions.updateItem({
|
|
167
|
+
timeSlot,
|
|
168
|
+
resources: [],
|
|
153
169
|
});
|
|
154
170
|
}
|
|
171
|
+
selectionSignal.set({
|
|
172
|
+
slot,
|
|
173
|
+
locationId,
|
|
174
|
+
staffMemberId,
|
|
175
|
+
});
|
|
155
176
|
};
|
|
156
177
|
/**
|
|
157
178
|
* Action: Select a location for the current slot
|
|
158
|
-
* Updates the booking service with the
|
|
179
|
+
* Updates the booking service with the location
|
|
180
|
+
* If the location has only one staff member, auto-selects that staff member too
|
|
159
181
|
*/
|
|
160
182
|
const selectLocation = (locationId) => {
|
|
161
183
|
const currentSelections = selectionSignal.get();
|
|
162
184
|
if (!currentSelections?.slot)
|
|
163
185
|
return;
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
186
|
+
const slot = currentSelections.slot;
|
|
187
|
+
const locationEntry = slot.staffByLocation.find((loc) => loc.location.id === locationId);
|
|
188
|
+
if (!locationEntry)
|
|
189
|
+
return;
|
|
190
|
+
const { location, staffMembers } = locationEntry;
|
|
191
|
+
bookingService.actions.setLocation(location);
|
|
192
|
+
// If this location has only one staff member, auto-select them
|
|
193
|
+
const hasOneStaff = staffMembers.length === 1;
|
|
194
|
+
if (hasOneStaff) {
|
|
195
|
+
const staffMember = staffMembers[0];
|
|
196
|
+
selectionSignal.set({
|
|
197
|
+
...currentSelections,
|
|
198
|
+
locationId,
|
|
199
|
+
staffMemberId: staffMember.id,
|
|
170
200
|
});
|
|
201
|
+
bookingService.actions.updateItem({
|
|
202
|
+
resources: [
|
|
203
|
+
{
|
|
204
|
+
_id: staffMember.id,
|
|
205
|
+
name: staffMember.name,
|
|
206
|
+
},
|
|
207
|
+
],
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
selectionSignal.set({ ...currentSelections, locationId });
|
|
171
212
|
}
|
|
172
213
|
};
|
|
173
214
|
/**
|
|
174
215
|
* Action: Select a staff member for the current slot
|
|
175
|
-
* Updates the booking service with the selected resource
|
|
216
|
+
* Updates the booking service with the selected resource
|
|
217
|
+
* If the staff member is only available at one location, auto-selects that location too
|
|
176
218
|
*/
|
|
177
219
|
const selectStaffMember = (staffMemberId) => {
|
|
178
220
|
const currentSelections = selectionSignal.get();
|
|
179
221
|
if (!currentSelections?.slot)
|
|
180
222
|
return;
|
|
181
|
-
selectionSignal.set({ ...currentSelections, staffMemberId });
|
|
182
223
|
const slot = currentSelections.slot;
|
|
183
|
-
// Find
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
224
|
+
// Find all locations that have this staff member
|
|
225
|
+
const locationsWithThisStaff = slot.staffByLocation.filter((loc) => loc.staffMembers.some((s) => s.id === staffMemberId));
|
|
226
|
+
// Find the staff member info
|
|
227
|
+
const staffMember = locationsWithThisStaff[0]?.staffMembers.find((s) => s.id === staffMemberId);
|
|
228
|
+
if (!staffMember)
|
|
229
|
+
return;
|
|
230
|
+
bookingService.actions.updateItem({
|
|
231
|
+
resources: [
|
|
232
|
+
{
|
|
233
|
+
_id: staffMember.id,
|
|
234
|
+
name: staffMember.name,
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
});
|
|
238
|
+
// If this staff member is only at one location, auto-select that location
|
|
239
|
+
const hasOneLocation = locationsWithThisStaff.length === 1;
|
|
240
|
+
if (hasOneLocation) {
|
|
241
|
+
const location = locationsWithThisStaff[0].location;
|
|
242
|
+
selectionSignal.set({
|
|
243
|
+
...currentSelections,
|
|
244
|
+
staffMemberId,
|
|
245
|
+
locationId: location.id,
|
|
246
|
+
});
|
|
247
|
+
bookingService.actions.setLocation(location);
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
selectionSignal.set({ ...currentSelections, staffMemberId });
|
|
194
251
|
}
|
|
195
252
|
};
|
|
196
253
|
/**
|
|
@@ -206,15 +263,11 @@ export const TimeSlotListService = implementService.withConfig()(TimeSlotListSer
|
|
|
206
263
|
locationId: null,
|
|
207
264
|
staffMemberId: null,
|
|
208
265
|
});
|
|
209
|
-
// Clear
|
|
266
|
+
// Clear selections in booking service
|
|
210
267
|
bookingService.actions.clearLocation();
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
bookingService.actions.updateBookingSelection({
|
|
215
|
-
staff: undefined,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
268
|
+
bookingService.actions.updateItem({
|
|
269
|
+
resources: [],
|
|
270
|
+
});
|
|
218
271
|
};
|
|
219
272
|
/**
|
|
220
273
|
* Action: Load more slots (pagination)
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* Provides low-level access to BookingService via render props and service setup
|
|
4
4
|
*/
|
|
5
5
|
import React from 'react';
|
|
6
|
-
import { type BookingServiceConfig, type
|
|
7
|
-
import type {
|
|
6
|
+
import { type BookingServiceConfig, type BookingItem, type Location, type BookingServiceActions } from '../../../services/booking/booking.js';
|
|
7
|
+
import type { FormValues } from '@wix/form-public';
|
|
8
8
|
/**
|
|
9
9
|
* Props for Booking.Root component
|
|
10
10
|
*/
|
|
@@ -28,14 +28,22 @@ export interface RootProps {
|
|
|
28
28
|
* ```
|
|
29
29
|
*/
|
|
30
30
|
export declare function Root(props: RootProps): React.ReactNode;
|
|
31
|
+
/**
|
|
32
|
+
* Data exposed by Booking.Data component
|
|
33
|
+
* Mirrors BookingServiceConfig structure with actions
|
|
34
|
+
*/
|
|
35
|
+
export interface BookingData {
|
|
36
|
+
bookingItems: BookingItem[];
|
|
37
|
+
location: Location | null;
|
|
38
|
+
formValues: FormValues | null;
|
|
39
|
+
numberOfParticipants: number;
|
|
40
|
+
actions: BookingServiceActions;
|
|
41
|
+
}
|
|
31
42
|
/**
|
|
32
43
|
* Props for Data render prop component
|
|
33
44
|
*/
|
|
34
45
|
export interface DataProps {
|
|
35
|
-
children: (data:
|
|
36
|
-
bookingSelections: BookingSelection[];
|
|
37
|
-
selectedTimeSlot: TimeSlot | null;
|
|
38
|
-
}) => React.ReactNode;
|
|
46
|
+
children: (data: BookingData) => React.ReactNode;
|
|
39
47
|
}
|
|
40
48
|
/**
|
|
41
49
|
* Core component that provides access to booking data via render props.
|
|
@@ -45,9 +53,9 @@ export interface DataProps {
|
|
|
45
53
|
* @example
|
|
46
54
|
* ```tsx
|
|
47
55
|
* <Booking.Data>
|
|
48
|
-
* {({
|
|
49
|
-
*
|
|
50
|
-
* <div>{
|
|
56
|
+
* {({ bookingItems, location, formValues, numberOfParticipants, actions }) => (
|
|
57
|
+
* bookingItems.length > 0 ? (
|
|
58
|
+
* <div>{bookingItems.map(item => item.service.name).join(', ')}</div>
|
|
51
59
|
* ) : (
|
|
52
60
|
* <div>No services selected</div>
|
|
53
61
|
* )
|
|
@@ -28,9 +28,9 @@ export function Root(props) {
|
|
|
28
28
|
* @example
|
|
29
29
|
* ```tsx
|
|
30
30
|
* <Booking.Data>
|
|
31
|
-
* {({
|
|
32
|
-
*
|
|
33
|
-
* <div>{
|
|
31
|
+
* {({ bookingItems, location, formValues, numberOfParticipants, actions }) => (
|
|
32
|
+
* bookingItems.length > 0 ? (
|
|
33
|
+
* <div>{bookingItems.map(item => item.service.name).join(', ')}</div>
|
|
34
34
|
* ) : (
|
|
35
35
|
* <div>No services selected</div>
|
|
36
36
|
* )
|
|
@@ -41,10 +41,16 @@ export function Root(props) {
|
|
|
41
41
|
export function Data(props) {
|
|
42
42
|
const { children } = props;
|
|
43
43
|
const bookingService = useService(BookingServiceDefinition);
|
|
44
|
-
const
|
|
45
|
-
const
|
|
44
|
+
const bookingItems = bookingService.bookingItems.get();
|
|
45
|
+
const location = bookingService.location.get();
|
|
46
|
+
const formValues = bookingService.formValues.get();
|
|
47
|
+
const numberOfParticipants = bookingService.numberOfParticipants.get();
|
|
48
|
+
const { actions } = bookingService;
|
|
46
49
|
return children({
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
bookingItems,
|
|
51
|
+
location,
|
|
52
|
+
formValues,
|
|
53
|
+
numberOfParticipants,
|
|
54
|
+
actions,
|
|
49
55
|
});
|
|
50
56
|
}
|
|
@@ -46,6 +46,10 @@ export interface ServicesRenderProps {
|
|
|
46
46
|
queryOptions: QueryOptions;
|
|
47
47
|
/** Pagination metadata (limit, offset, count, hasNext) */
|
|
48
48
|
pagingMetadata: PagingMetadata;
|
|
49
|
+
/** Whether more services can be loaded */
|
|
50
|
+
hasMore: boolean;
|
|
51
|
+
/** Function to load more services */
|
|
52
|
+
loadMore: (count: number) => Promise<void>;
|
|
49
53
|
}
|
|
50
54
|
/**
|
|
51
55
|
* Component that provides services list data through render props.
|
|
@@ -48,11 +48,15 @@ export function Services(props) {
|
|
|
48
48
|
const error = serviceListService.errorSignal.get();
|
|
49
49
|
const queryOptions = serviceListService.queryOptionsSignal.get();
|
|
50
50
|
const pagingMetadata = serviceListService.pagingMetadataSignal.get();
|
|
51
|
+
const hasMore = serviceListService.hasMoreServicesSignal.get();
|
|
52
|
+
const loadMore = serviceListService.actions.loadMore;
|
|
51
53
|
return props.children({
|
|
52
54
|
services,
|
|
53
55
|
isLoading,
|
|
54
56
|
error,
|
|
55
57
|
queryOptions,
|
|
56
58
|
pagingMetadata,
|
|
59
|
+
hasMore,
|
|
60
|
+
loadMore,
|
|
57
61
|
});
|
|
58
62
|
}
|
|
@@ -535,17 +535,7 @@ export const StaffMemberRepeater = React.forwardRef((props, ref) => {
|
|
|
535
535
|
const staffMemberItemWrapper = ({ item: member, children: itemChildren, }) => {
|
|
536
536
|
return (_jsxs(React.Fragment, { children: [_jsx("span", { "data-testid": TestIds.serviceStaffMemberRepeater, children: member.name }), itemChildren] }, member.id));
|
|
537
537
|
};
|
|
538
|
-
|
|
539
|
-
if (asChild && typeof children === 'function') {
|
|
540
|
-
return (_jsx(GenericList.Repeater, { ref: ref, asChild: true, itemWrapper: staffMemberItemWrapper, children: (genericProps) => {
|
|
541
|
-
const renderProps = {
|
|
542
|
-
items: genericProps.items,
|
|
543
|
-
itemWrapper: staffMemberItemWrapper,
|
|
544
|
-
};
|
|
545
|
-
return children(renderProps);
|
|
546
|
-
} }));
|
|
547
|
-
}
|
|
548
|
-
return (_jsx(GenericList.Repeater, { ref: ref, itemWrapper: staffMemberItemWrapper, children: children }));
|
|
538
|
+
return (_jsx(GenericList.Repeater, { ref: ref, itemWrapper: staffMemberItemWrapper, asChild: asChild, children: children }));
|
|
549
539
|
});
|
|
550
540
|
StaffMemberRepeater.displayName = 'Service.StaffMembers.StaffMemberRepeater';
|
|
551
541
|
/**
|
|
@@ -15,6 +15,8 @@ export interface ServiceListRootProps {
|
|
|
15
15
|
serviceListConfig?: ServiceListServiceConfig;
|
|
16
16
|
className?: string;
|
|
17
17
|
variant?: ListVariant;
|
|
18
|
+
/** Number of items to load per page. Defaults to config limit or 100 */
|
|
19
|
+
pageSize?: number;
|
|
18
20
|
}
|
|
19
21
|
/**
|
|
20
22
|
* Root component that provides the ServiceList service context for rendering services lists.
|
|
@@ -5,8 +5,6 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
5
5
|
*/
|
|
6
6
|
import React from 'react';
|
|
7
7
|
import { GenericList, } from '@wix/headless-components/react';
|
|
8
|
-
import { useService } from '@wix/services-manager-react';
|
|
9
|
-
import { ServiceListServiceDefinition } from '../../services/service-list/service-list.js';
|
|
10
8
|
import * as CoreServiceList from '../core/service-list/ServiceList.js';
|
|
11
9
|
import * as Service from '../service/Service.js';
|
|
12
10
|
/**
|
|
@@ -44,7 +42,7 @@ var TestIds;
|
|
|
44
42
|
* ```
|
|
45
43
|
*/
|
|
46
44
|
export const Root = React.forwardRef((props, ref) => {
|
|
47
|
-
const { children, serviceListConfig, className, variant } = props;
|
|
45
|
+
const { children, serviceListConfig, className, variant, pageSize } = props;
|
|
48
46
|
const serviceConfig = serviceListConfig || {
|
|
49
47
|
services: [],
|
|
50
48
|
options: {
|
|
@@ -56,22 +54,29 @@ export const Root = React.forwardRef((props, ref) => {
|
|
|
56
54
|
},
|
|
57
55
|
},
|
|
58
56
|
};
|
|
59
|
-
|
|
57
|
+
// Calculate effective page size: prop > config limit > default
|
|
58
|
+
const effectivePageSize = pageSize ??
|
|
59
|
+
serviceListConfig?.options?.pagingMetadata?.limit ??
|
|
60
|
+
DEFAULT_PAGE_SIZE;
|
|
61
|
+
return (_jsx(CoreServiceList.Root, { serviceListConfig: serviceConfig, children: _jsx(RootContent, { className: className, ref: ref, variant: variant, pageSize: effectivePageSize, children: children }) }));
|
|
60
62
|
});
|
|
61
63
|
Root.displayName = 'ServiceList.Root';
|
|
64
|
+
/**
|
|
65
|
+
* Default page size for load more
|
|
66
|
+
*/
|
|
67
|
+
const DEFAULT_PAGE_SIZE = 100;
|
|
62
68
|
/**
|
|
63
69
|
* Internal component to handle the Root content with service access
|
|
64
70
|
*/
|
|
65
71
|
const RootContent = React.forwardRef((props, ref) => {
|
|
66
|
-
const { children, className, variant } = props;
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return (_jsx(GenericList.Root, { items: items, loadMore: () => serviceListService.actions.loadMore(10), hasMore: serviceListService.hasMoreServicesSignal.get(), isLoading: serviceListService.isLoadingSignal.get(), className: className, ref: ref, "data-testid": TestIds.serviceListRoot, variant: variant, children: children }));
|
|
72
|
+
const { children, className, variant, pageSize = DEFAULT_PAGE_SIZE } = props;
|
|
73
|
+
return (_jsx(CoreServiceList.Services, { children: ({ services: servicesList, isLoading, hasMore, loadMore }) => {
|
|
74
|
+
const items = servicesList.map((service) => ({
|
|
75
|
+
...service,
|
|
76
|
+
id: service._id,
|
|
77
|
+
}));
|
|
78
|
+
return (_jsx(GenericList.Root, { items: items, loadMore: () => loadMore(pageSize), hasMore: hasMore, isLoading: isLoading, className: className, ref: ref, "data-testid": TestIds.serviceListRoot, variant: variant, children: children }));
|
|
79
|
+
} }));
|
|
75
80
|
});
|
|
76
81
|
/**
|
|
77
82
|
* Container for the services list with empty state support.
|
|
@@ -126,37 +131,20 @@ Services.displayName = 'ServiceList.Services';
|
|
|
126
131
|
*/
|
|
127
132
|
export const ServiceRepeater = React.forwardRef((props, ref) => {
|
|
128
133
|
const { children, asChild } = props;
|
|
129
|
-
const serviceListService = useService(ServiceListServiceDefinition);
|
|
130
|
-
const appId = serviceListService.queryOptionsSignal.get().appId;
|
|
131
134
|
// Check if children is a function that matches ServiceRootChildren pattern
|
|
132
135
|
// (function with single parameter that receives { selected, bookable })
|
|
133
136
|
const isServiceRootFunction = typeof children === 'function' && children.length === 1 && !asChild;
|
|
134
137
|
// Custom itemWrapper that matches ItemWrapperProps signature
|
|
135
138
|
const serviceItemWrapper = (wrapperProps) => {
|
|
136
139
|
const { item: service, children: itemChildren } = wrapperProps;
|
|
137
|
-
return (_jsx(Service.Root, { service: service,
|
|
140
|
+
return (_jsx(Service.Root, { service: service, "data-testid": TestIds.serviceListService, "data-service-id": service._id, "data-item-id": service._id, children: isServiceRootFunction
|
|
138
141
|
? children
|
|
139
142
|
: itemChildren }, service._id));
|
|
140
143
|
};
|
|
141
|
-
//
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const renderProps = {
|
|
146
|
-
items: genericProps.items,
|
|
147
|
-
variant: genericProps.variant,
|
|
148
|
-
itemWrapper: (props) => serviceItemWrapper({
|
|
149
|
-
item: props.item,
|
|
150
|
-
index: props.index ?? 0,
|
|
151
|
-
children: props.children,
|
|
152
|
-
}),
|
|
153
|
-
};
|
|
154
|
-
return children(renderProps, ref);
|
|
155
|
-
}
|
|
156
|
-
: isServiceRootFunction
|
|
157
|
-
? undefined
|
|
158
|
-
: children;
|
|
159
|
-
return (_jsx(GenericList.Repeater, { ref: ref, asChild: asChild, itemWrapper: serviceItemWrapper, children: transformedChildren }));
|
|
144
|
+
// When isServiceRootFunction is true, children is a function for Service.Root (not repeater render props)
|
|
145
|
+
// so we pass undefined and let itemWrapper handle the children
|
|
146
|
+
const repeaterChildren = isServiceRootFunction ? undefined : children;
|
|
147
|
+
return (_jsx(GenericList.Repeater, { ref: ref, asChild: asChild, itemWrapper: serviceItemWrapper, children: repeaterChildren }));
|
|
160
148
|
});
|
|
161
149
|
ServiceRepeater.displayName = 'ServiceList.ServiceRepeater';
|
|
162
150
|
/**
|
|
@@ -5,19 +5,31 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { type Signal } from '@wix/services-definitions/core-services/signals';
|
|
7
7
|
import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
8
|
-
import type {
|
|
8
|
+
import type { Resource, EventInfo } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
9
9
|
import type { FormValues } from '@wix/form-public';
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* Minimal time slot data for booking items
|
|
12
12
|
*/
|
|
13
|
-
export interface
|
|
13
|
+
export interface BookingItemTimeSlot {
|
|
14
|
+
startDate: string;
|
|
15
|
+
endDate: string;
|
|
16
|
+
totalCapacity?: number;
|
|
17
|
+
remainingCapacity?: number;
|
|
18
|
+
scheduleId?: string;
|
|
19
|
+
eventInfo?: EventInfo;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* BookingItem contains the service AND its related selections
|
|
23
|
+
*/
|
|
24
|
+
export interface BookingItem {
|
|
14
25
|
/** Unique identifier for this booking instance (allows same service multiple times) */
|
|
15
26
|
instanceId: string;
|
|
27
|
+
/** The selected service */
|
|
16
28
|
service: Service;
|
|
17
29
|
/** Selected resources (staff members, rooms, etc.) */
|
|
18
|
-
|
|
19
|
-
/** Per-
|
|
20
|
-
|
|
30
|
+
resources: Resource[];
|
|
31
|
+
/** Per-item time slot data */
|
|
32
|
+
timeSlot?: BookingItemTimeSlot;
|
|
21
33
|
}
|
|
22
34
|
export interface Location {
|
|
23
35
|
id: string;
|
|
@@ -27,21 +39,25 @@ export interface Location {
|
|
|
27
39
|
* BookingService actions - setters only, no API calls
|
|
28
40
|
*/
|
|
29
41
|
export interface BookingServiceActions {
|
|
30
|
-
/** Clears all booking
|
|
31
|
-
|
|
32
|
-
/** Adds
|
|
33
|
-
|
|
34
|
-
/** Updates a booking
|
|
35
|
-
|
|
36
|
-
/** Removes a booking
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
/** Clears all booking items and sets a single item (instanceId is auto-generated) */
|
|
43
|
+
setItem(item: Omit<BookingItem, 'instanceId'>): string;
|
|
44
|
+
/** Adds an item and returns the generated instanceId */
|
|
45
|
+
addItem(item: Omit<BookingItem, 'instanceId'>): string;
|
|
46
|
+
/** Updates a booking item. If no instanceId provided, updates the first item. */
|
|
47
|
+
updateItem(updates: Partial<Omit<BookingItem, 'instanceId'>>, instanceId?: string): void;
|
|
48
|
+
/** Removes a booking item. If no instanceId provided, removes the first item. */
|
|
49
|
+
removeItem(instanceId?: string): void;
|
|
50
|
+
/** Clears all booking items */
|
|
51
|
+
clearItems(): void;
|
|
52
|
+
/** Sets a resource by ID within a booking item. If no instanceId provided, uses the first item. */
|
|
53
|
+
setResourceById(resourceId: string, resource: Resource, instanceId?: string): void;
|
|
54
|
+
/** Gets a resource by ID from a booking item. If no instanceId provided, uses the first item. */
|
|
55
|
+
getResourceById(resourceId: string, instanceId?: string): Resource | undefined;
|
|
41
56
|
setLocation(location: Location | null): void;
|
|
42
57
|
clearLocation(): void;
|
|
43
58
|
setFormValues(values: FormValues | null): void;
|
|
44
59
|
clearFormValues(): void;
|
|
60
|
+
setNumberOfParticipants(count: number): void;
|
|
45
61
|
clearAll(): void;
|
|
46
62
|
}
|
|
47
63
|
/**
|
|
@@ -49,14 +65,14 @@ export interface BookingServiceActions {
|
|
|
49
65
|
* Pure state management - no API calls
|
|
50
66
|
*/
|
|
51
67
|
export interface BookingService {
|
|
52
|
-
/** Signal containing the booking
|
|
53
|
-
|
|
54
|
-
/** Signal containing the
|
|
55
|
-
timeSlot: Signal<TimeSlot | null>;
|
|
56
|
-
/** Signal containing the selected location (derived from timeSlot) */
|
|
68
|
+
/** Signal containing the booking items (services with their related data) */
|
|
69
|
+
bookingItems: Signal<BookingItem[]>;
|
|
70
|
+
/** Signal containing the selected location */
|
|
57
71
|
location: Signal<Location | null>;
|
|
58
72
|
/** Signal containing the form values */
|
|
59
73
|
formValues: Signal<FormValues | null>;
|
|
74
|
+
/** Signal containing the number of participants */
|
|
75
|
+
numberOfParticipants: Signal<number>;
|
|
60
76
|
/** Actions for modifying booking state */
|
|
61
77
|
actions: BookingServiceActions;
|
|
62
78
|
}
|
|
@@ -70,15 +86,17 @@ export declare const BookingServiceDefinition: string & {
|
|
|
70
86
|
} & BookingService;
|
|
71
87
|
/**
|
|
72
88
|
* Configuration interface for BookingService
|
|
73
|
-
*
|
|
89
|
+
* Mirrors BookingService structure (without actions and Signal wrappers)
|
|
74
90
|
*/
|
|
75
91
|
export interface BookingServiceConfig {
|
|
76
|
-
/** Optional initial booking
|
|
77
|
-
|
|
78
|
-
/** Optional initial
|
|
79
|
-
|
|
92
|
+
/** Optional initial booking items to pre-populate */
|
|
93
|
+
bookingItems?: BookingItem[];
|
|
94
|
+
/** Optional initial location */
|
|
95
|
+
location?: Location | null;
|
|
80
96
|
/** Optional initial form values */
|
|
81
|
-
|
|
97
|
+
formValues?: FormValues | null;
|
|
98
|
+
/** Optional initial number of participants (defaults to 1) */
|
|
99
|
+
numberOfParticipants?: number;
|
|
82
100
|
}
|
|
83
101
|
/**
|
|
84
102
|
* Implementation of BookingService
|
|
@@ -98,8 +116,8 @@ export interface BookingServiceConfig {
|
|
|
98
116
|
*
|
|
99
117
|
* // In any page - access booking data
|
|
100
118
|
* <Booking.Data>
|
|
101
|
-
* {({
|
|
102
|
-
* <div>{
|
|
119
|
+
* {({ bookingItems, location, formValues, actions }) => (
|
|
120
|
+
* <div>{bookingItems.map(item => item.service.name).join(', ')}</div>
|
|
103
121
|
* )}
|
|
104
122
|
* </Booking.Data>
|
|
105
123
|
* ```
|