@wix/headless-bookings 0.0.44 → 0.0.46
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/__mocks__/booking.d.ts +9 -4
- package/cjs/dist/__mocks__/booking.js +48 -3
- package/cjs/dist/__mocks__/services.d.ts +21 -0
- package/cjs/dist/__mocks__/services.js +54 -0
- package/cjs/dist/__mocks__/time-slots.d.ts +28 -0
- package/cjs/dist/__mocks__/time-slots.js +116 -0
- package/cjs/dist/api/index.d.ts +1 -0
- package/cjs/dist/api/index.js +1 -0
- package/cjs/dist/api/query-categories/index.d.ts +34 -0
- package/cjs/dist/api/query-categories/index.js +28 -0
- package/cjs/dist/api/query-services/index.js +9 -2
- package/cjs/dist/react/core/service-list/ServiceListFilter.d.ts +55 -0
- package/cjs/dist/react/core/service-list/ServiceListFilter.js +59 -0
- package/cjs/dist/react/service-list/ServiceList.d.ts +62 -1
- package/cjs/dist/react/service-list/ServiceList.js +53 -1
- package/cjs/dist/services/service-list/service-list.d.ts +14 -1
- package/cjs/dist/services/service-list/service-list.js +94 -12
- package/dist/__mocks__/booking.d.ts +9 -4
- package/dist/__mocks__/booking.js +48 -3
- package/dist/__mocks__/services.d.ts +21 -0
- package/dist/__mocks__/services.js +54 -0
- package/dist/__mocks__/time-slots.d.ts +28 -0
- package/dist/__mocks__/time-slots.js +116 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -0
- package/dist/api/query-categories/index.d.ts +34 -0
- package/dist/api/query-categories/index.js +28 -0
- package/dist/api/query-services/index.js +9 -2
- package/dist/react/core/service-list/ServiceListFilter.d.ts +55 -0
- package/dist/react/core/service-list/ServiceListFilter.js +59 -0
- package/dist/react/service-list/ServiceList.d.ts +62 -1
- package/dist/react/service-list/ServiceList.js +53 -1
- package/dist/services/service-list/service-list.d.ts +14 -1
- package/dist/services/service-list/service-list.js +94 -12
- package/package.json +2 -2
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared mock data for booking service tests
|
|
3
|
-
*/
|
|
4
1
|
import type { Resource } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
5
2
|
import type { FormValues } from '@wix/form-public';
|
|
6
|
-
import type { ServiceSelection, ServiceSelectionTimeSlot, BookingServiceConfig } from '../services/booking/booking.js';
|
|
3
|
+
import type { ServiceSelection, ServiceSelectionTimeSlot, BookingServiceConfig, BookingService } from '../services/booking/booking.js';
|
|
4
|
+
import type { Signal } from '@wix/services-definitions/core-services/signals';
|
|
5
|
+
export declare function mockSignal<T>(initialValue: T): Signal<T>;
|
|
6
|
+
export declare function createMockSignalsService(): {
|
|
7
|
+
signal: typeof mockSignal;
|
|
8
|
+
computed: <T>(fn: () => T) => Signal<T>;
|
|
9
|
+
effect: (fn: () => void) => void;
|
|
10
|
+
};
|
|
11
|
+
export declare function createMockBookingService(initialSelections?: ServiceSelection[]): BookingService;
|
|
7
12
|
/**
|
|
8
13
|
* Mock resource (staff member)
|
|
9
14
|
*/
|
|
@@ -1,7 +1,52 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Shared mock data for booking service tests
|
|
3
|
-
*/
|
|
4
1
|
import { mockService, mockServiceClass } from './services.js';
|
|
2
|
+
import { vi } from 'vitest';
|
|
3
|
+
export function mockSignal(initialValue) {
|
|
4
|
+
let value = initialValue;
|
|
5
|
+
return {
|
|
6
|
+
get: () => value,
|
|
7
|
+
peek: () => value,
|
|
8
|
+
set: (newValue) => {
|
|
9
|
+
value = newValue;
|
|
10
|
+
},
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function createMockSignalsService() {
|
|
14
|
+
return {
|
|
15
|
+
signal: mockSignal,
|
|
16
|
+
computed: (fn) => mockSignal(fn()),
|
|
17
|
+
effect: vi.fn((fn) => {
|
|
18
|
+
fn();
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function createMockBookingService(initialSelections = []) {
|
|
23
|
+
const serviceSelectionsSignal = mockSignal(initialSelections);
|
|
24
|
+
const locationSignal = mockSignal(null);
|
|
25
|
+
const timezoneSignal = mockSignal('UTC');
|
|
26
|
+
const formSubmissionSignal = mockSignal(null);
|
|
27
|
+
return {
|
|
28
|
+
serviceSelections: serviceSelectionsSignal,
|
|
29
|
+
location: locationSignal,
|
|
30
|
+
timezone: timezoneSignal,
|
|
31
|
+
formSubmission: formSubmissionSignal,
|
|
32
|
+
actions: {
|
|
33
|
+
updateItem: vi.fn(),
|
|
34
|
+
setItem: vi.fn(),
|
|
35
|
+
addItem: vi.fn(),
|
|
36
|
+
removeItem: vi.fn(),
|
|
37
|
+
clearItems: vi.fn(),
|
|
38
|
+
setResourceById: vi.fn(),
|
|
39
|
+
getResourceById: vi.fn(),
|
|
40
|
+
setLocation: vi.fn(),
|
|
41
|
+
clearLocation: vi.fn(),
|
|
42
|
+
setTimezone: vi.fn(),
|
|
43
|
+
setFormSubmission: vi.fn(),
|
|
44
|
+
clearFormSubmission: vi.fn(),
|
|
45
|
+
clearAll: vi.fn(),
|
|
46
|
+
book: vi.fn(),
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
}
|
|
5
50
|
/**
|
|
6
51
|
* Mock resource (staff member)
|
|
7
52
|
*/
|
|
@@ -5,6 +5,7 @@ import type { Service } from '@wix/auto_sdk_bookings_services';
|
|
|
5
5
|
import type { ServiceServiceConfig } from '../services/service/service.js';
|
|
6
6
|
import type { ServiceListServiceConfig } from '../services/service-list/service-list.js';
|
|
7
7
|
import type { PagingMetadata } from '../api/query-services/index.js';
|
|
8
|
+
import type { Category } from '../api/query-categories/index.js';
|
|
8
9
|
/**
|
|
9
10
|
* Booking app ID constant
|
|
10
11
|
*/
|
|
@@ -73,3 +74,23 @@ export declare const mockServiceConfig: ServiceServiceConfig;
|
|
|
73
74
|
* Helper to create a large list of services for pagination tests
|
|
74
75
|
*/
|
|
75
76
|
export declare function createLargeServiceList(count: number): Service[];
|
|
77
|
+
/**
|
|
78
|
+
* Mock categories for filter tests
|
|
79
|
+
*/
|
|
80
|
+
export declare const mockCategories: Category[];
|
|
81
|
+
/**
|
|
82
|
+
* Empty categories array
|
|
83
|
+
*/
|
|
84
|
+
export declare const mockEmptyCategories: Category[];
|
|
85
|
+
/**
|
|
86
|
+
* Service list config with categories for filter testing
|
|
87
|
+
*/
|
|
88
|
+
export declare const mockServiceListConfigWithCategories: ServiceListServiceConfig;
|
|
89
|
+
/**
|
|
90
|
+
* Service list config with categories and hasNext for pagination + filter testing
|
|
91
|
+
*/
|
|
92
|
+
export declare const mockServiceListConfigWithCategoriesAndMore: ServiceListServiceConfig;
|
|
93
|
+
/**
|
|
94
|
+
* Empty service list config with categories
|
|
95
|
+
*/
|
|
96
|
+
export declare const mockEmptyServiceListConfigWithCategories: ServiceListServiceConfig;
|
|
@@ -306,3 +306,57 @@ export function createLargeServiceList(count) {
|
|
|
306
306
|
},
|
|
307
307
|
}));
|
|
308
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Mock categories for filter tests
|
|
311
|
+
*/
|
|
312
|
+
export const mockCategories = [
|
|
313
|
+
{
|
|
314
|
+
_id: 'category-1',
|
|
315
|
+
name: 'Wellness',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
_id: 'category-2',
|
|
319
|
+
name: 'Fitness',
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
_id: 'category-3',
|
|
323
|
+
name: 'Beauty',
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
/**
|
|
327
|
+
* Empty categories array
|
|
328
|
+
*/
|
|
329
|
+
export const mockEmptyCategories = [];
|
|
330
|
+
/**
|
|
331
|
+
* Service list config with categories for filter testing
|
|
332
|
+
*/
|
|
333
|
+
export const mockServiceListConfigWithCategories = {
|
|
334
|
+
services: mockServices,
|
|
335
|
+
categories: mockCategories,
|
|
336
|
+
options: {
|
|
337
|
+
appId: BOOKING_APP_ID,
|
|
338
|
+
pagingMetadata: mockPagingMetadata,
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Service list config with categories and hasNext for pagination + filter testing
|
|
343
|
+
*/
|
|
344
|
+
export const mockServiceListConfigWithCategoriesAndMore = {
|
|
345
|
+
services: mockServices,
|
|
346
|
+
categories: mockCategories,
|
|
347
|
+
options: {
|
|
348
|
+
appId: BOOKING_APP_ID,
|
|
349
|
+
pagingMetadata: mockPagingMetadataWithMore,
|
|
350
|
+
},
|
|
351
|
+
};
|
|
352
|
+
/**
|
|
353
|
+
* Empty service list config with categories
|
|
354
|
+
*/
|
|
355
|
+
export const mockEmptyServiceListConfigWithCategories = {
|
|
356
|
+
services: [],
|
|
357
|
+
categories: mockCategories,
|
|
358
|
+
options: {
|
|
359
|
+
appId: BOOKING_APP_ID,
|
|
360
|
+
pagingMetadata: mockPagingMetadataEmpty,
|
|
361
|
+
},
|
|
362
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { TimeSlot } from '@wix/auto_sdk_bookings_availability-time-slots';
|
|
2
|
+
import type { TimeSlotListServiceConfig, TimeSlotListServiceAPI } from '../services/time-slot-list/time-slot-list.def.js';
|
|
3
|
+
import type { BookingService } from '../services/booking/booking.js';
|
|
4
|
+
export declare function createTimeSlotListService(overrides?: Partial<TimeSlotListServiceConfig>): {
|
|
5
|
+
service: TimeSlotListServiceAPI;
|
|
6
|
+
bookingService: BookingService;
|
|
7
|
+
};
|
|
8
|
+
export declare function createTimeSlot(overrides?: Partial<TimeSlot>): TimeSlot;
|
|
9
|
+
export declare function createTimeSlots(count: number): TimeSlot[];
|
|
10
|
+
export declare function createTimeSlotListConfig(overrides?: Partial<TimeSlotListServiceConfig>): TimeSlotListServiceConfig;
|
|
11
|
+
export declare const mockTimeSlot: TimeSlot;
|
|
12
|
+
export declare const mockTimeSlotMultipleStaff: TimeSlot;
|
|
13
|
+
export declare const mockTimeSlotNotBookable: TimeSlot;
|
|
14
|
+
export declare const mockTimeSlots: TimeSlot[];
|
|
15
|
+
export declare const mockStartDate: Date;
|
|
16
|
+
export declare const mockEndDate: Date;
|
|
17
|
+
export declare const mockTimezone = "America/New_York";
|
|
18
|
+
export declare const mockTimeSlotListConfig: TimeSlotListServiceConfig;
|
|
19
|
+
export declare const mockEmptyTimeSlotListConfig: TimeSlotListServiceConfig;
|
|
20
|
+
export declare const mockCursorPagingMetadataWithMore: {
|
|
21
|
+
cursors: {
|
|
22
|
+
next: string;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export declare const mockCursorPagingMetadataNoMore: {
|
|
26
|
+
cursors: {};
|
|
27
|
+
};
|
|
28
|
+
export declare function createMockTimeSlotListService(config?: Partial<TimeSlotListServiceConfig>): TimeSlotListServiceAPI;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { vi } from 'vitest';
|
|
2
|
+
import { TimeSlotListService } from '../services/time-slot-list/time-slot-list.js';
|
|
3
|
+
import { createMockBookingService, createMockSignalsService, mockSignal, } from './booking.js';
|
|
4
|
+
export function createTimeSlotListService(overrides = {}) {
|
|
5
|
+
const config = createTimeSlotListConfig(overrides);
|
|
6
|
+
const mockBookingService = createMockBookingService();
|
|
7
|
+
const mockSignalsService = createMockSignalsService();
|
|
8
|
+
const getService = (definition) => {
|
|
9
|
+
const def = definition;
|
|
10
|
+
if (def.name === 'signals') {
|
|
11
|
+
return mockSignalsService;
|
|
12
|
+
}
|
|
13
|
+
if (def.name === 'booking') {
|
|
14
|
+
return mockBookingService;
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`Unknown service: ${def.name}`);
|
|
17
|
+
};
|
|
18
|
+
const service = TimeSlotListService({
|
|
19
|
+
getService,
|
|
20
|
+
config,
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
service,
|
|
24
|
+
bookingService: mockBookingService,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function createTimeSlot(overrides = {}) {
|
|
28
|
+
return {
|
|
29
|
+
localStartDate: '2024-01-15T10:00:00',
|
|
30
|
+
localEndDate: '2024-01-15T11:00:00',
|
|
31
|
+
bookable: true,
|
|
32
|
+
totalCapacity: 10,
|
|
33
|
+
remainingCapacity: 5,
|
|
34
|
+
scheduleId: 'schedule-1',
|
|
35
|
+
location: { _id: 'loc-1', name: 'Main Office' },
|
|
36
|
+
availableResources: [{ resources: [{ _id: 'staff-1', name: 'John Doe' }] }],
|
|
37
|
+
...overrides,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
export function createTimeSlots(count) {
|
|
41
|
+
return Array.from({ length: count }, (_, i) => createTimeSlot({
|
|
42
|
+
localStartDate: `2024-01-15T${String(9 + (i % 8)).padStart(2, '0')}:00:00`,
|
|
43
|
+
localEndDate: `2024-01-15T${String(10 + (i % 8)).padStart(2, '0')}:00:00`,
|
|
44
|
+
scheduleId: `schedule-${i}`,
|
|
45
|
+
}));
|
|
46
|
+
}
|
|
47
|
+
export function createTimeSlotListConfig(overrides = {}) {
|
|
48
|
+
const { startDate = new Date('2024-01-15T00:00:00'), endDate = new Date('2024-01-22T00:00:00'), timezone = 'America/New_York', timeSlots, ...rest } = overrides;
|
|
49
|
+
return {
|
|
50
|
+
startDate,
|
|
51
|
+
endDate,
|
|
52
|
+
timezone,
|
|
53
|
+
...rest,
|
|
54
|
+
...(timeSlots !== undefined ? { timeSlots } : {}),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export const mockTimeSlot = createTimeSlot();
|
|
58
|
+
export const mockTimeSlotMultipleStaff = createTimeSlot({
|
|
59
|
+
availableResources: [
|
|
60
|
+
{
|
|
61
|
+
resources: [
|
|
62
|
+
{ _id: 'staff-1', name: 'John Doe' },
|
|
63
|
+
{ _id: 'staff-2', name: 'Jane Smith' },
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
export const mockTimeSlotNotBookable = createTimeSlot({
|
|
69
|
+
localStartDate: '2024-01-15T14:00:00',
|
|
70
|
+
localEndDate: '2024-01-15T15:00:00',
|
|
71
|
+
bookable: false,
|
|
72
|
+
remainingCapacity: 0,
|
|
73
|
+
location: { _id: 'loc-2', name: 'Downtown Branch' },
|
|
74
|
+
});
|
|
75
|
+
export const mockTimeSlots = [
|
|
76
|
+
mockTimeSlot,
|
|
77
|
+
mockTimeSlotMultipleStaff,
|
|
78
|
+
mockTimeSlotNotBookable,
|
|
79
|
+
];
|
|
80
|
+
export const mockStartDate = new Date('2024-01-15T00:00:00');
|
|
81
|
+
export const mockEndDate = new Date('2024-01-22T00:00:00');
|
|
82
|
+
export const mockTimezone = 'America/New_York';
|
|
83
|
+
export const mockTimeSlotListConfig = createTimeSlotListConfig({
|
|
84
|
+
timeSlots: mockTimeSlots,
|
|
85
|
+
});
|
|
86
|
+
export const mockEmptyTimeSlotListConfig = createTimeSlotListConfig();
|
|
87
|
+
export const mockCursorPagingMetadataWithMore = {
|
|
88
|
+
cursors: { next: 'next-cursor-token' },
|
|
89
|
+
};
|
|
90
|
+
export const mockCursorPagingMetadataNoMore = { cursors: {} };
|
|
91
|
+
export function createMockTimeSlotListService(config = {}) {
|
|
92
|
+
const timeSlotsSignal = mockSignal(config.timeSlots || []);
|
|
93
|
+
const isLoadingSignal = mockSignal(false);
|
|
94
|
+
const errorSignal = mockSignal(null);
|
|
95
|
+
const hasMoreSignal = mockSignal(false);
|
|
96
|
+
const dateRangeSignal = mockSignal({
|
|
97
|
+
start: config.startDate || mockStartDate,
|
|
98
|
+
end: config.endDate || mockEndDate,
|
|
99
|
+
});
|
|
100
|
+
return {
|
|
101
|
+
timeSlots: timeSlotsSignal,
|
|
102
|
+
isLoading: isLoadingSignal,
|
|
103
|
+
error: errorSignal,
|
|
104
|
+
hasMore: hasMoreSignal,
|
|
105
|
+
dateRange: dateRangeSignal,
|
|
106
|
+
actions: {
|
|
107
|
+
selectSlot: vi.fn(),
|
|
108
|
+
selectStaffMember: vi.fn(),
|
|
109
|
+
clearStaffSelection: vi.fn(),
|
|
110
|
+
loadMore: vi.fn(),
|
|
111
|
+
setDateRange: vi.fn((start, end) => {
|
|
112
|
+
dateRangeSignal.set({ start, end });
|
|
113
|
+
}),
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
package/cjs/dist/api/index.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export { fetchAvailability } from './fetch-availability/index.js';
|
|
|
9
9
|
export { createBooking, type CreateBookingOptions, type Booking, type CreateBookingRequest, type CreateBookingResponse, type ParticipantNotification, type ContactDetails, } from './create-booking/index.js';
|
|
10
10
|
export { createCheckout, ChannelType, type CreateCheckoutRequest, type CreateCheckoutResponse, } from './create-checkout/index.js';
|
|
11
11
|
export { queryLocations, type QueryLocationsResult, type LocationData, } from './query-locations/index.js';
|
|
12
|
+
export { queryCategories, type QueryCategoriesResult, type Category, } from './query-categories/index.js';
|
|
12
13
|
/**
|
|
13
14
|
* PagingMetadata includes:
|
|
14
15
|
* - limit: Number of items per page
|
package/cjs/dist/api/index.js
CHANGED
|
@@ -9,6 +9,7 @@ export { fetchAvailability } from './fetch-availability/index.js';
|
|
|
9
9
|
export { createBooking, } from './create-booking/index.js';
|
|
10
10
|
export { createCheckout, ChannelType, } from './create-checkout/index.js';
|
|
11
11
|
export { queryLocations, } from './query-locations/index.js';
|
|
12
|
+
export { queryCategories, } from './query-categories/index.js';
|
|
12
13
|
/**
|
|
13
14
|
* PagingMetadata includes:
|
|
14
15
|
* - limit: Number of items per page
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Categories API
|
|
3
|
+
* Fetches booking service categories from Wix Bookings
|
|
4
|
+
*
|
|
5
|
+
* @module API/QueryCategories
|
|
6
|
+
*/
|
|
7
|
+
import { categoriesV2 } from '@wix/bookings';
|
|
8
|
+
/**
|
|
9
|
+
* Category type from the Wix Bookings Categories V2 API
|
|
10
|
+
*/
|
|
11
|
+
export type Category = categoriesV2.Category;
|
|
12
|
+
/**
|
|
13
|
+
* Result from querying categories
|
|
14
|
+
*/
|
|
15
|
+
export interface QueryCategoriesResult {
|
|
16
|
+
/** Array of booking service categories */
|
|
17
|
+
categories: Category[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Query all booking service categories.
|
|
21
|
+
* Returns an array of categories sorted by creation date (ascending).
|
|
22
|
+
*
|
|
23
|
+
* @returns Promise resolving to categories array
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const { categories } = await queryCategories();
|
|
28
|
+
*
|
|
29
|
+
* categories.forEach(category => {
|
|
30
|
+
* console.log(category.name, category._id);
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function queryCategories(): Promise<QueryCategoriesResult>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Query Categories API
|
|
3
|
+
* Fetches booking service categories from Wix Bookings
|
|
4
|
+
*
|
|
5
|
+
* @module API/QueryCategories
|
|
6
|
+
*/
|
|
7
|
+
import { categoriesV2 } from '@wix/bookings';
|
|
8
|
+
/**
|
|
9
|
+
* Query all booking service categories.
|
|
10
|
+
* Returns an array of categories sorted by creation date (ascending).
|
|
11
|
+
*
|
|
12
|
+
* @returns Promise resolving to categories array
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const { categories } = await queryCategories();
|
|
17
|
+
*
|
|
18
|
+
* categories.forEach(category => {
|
|
19
|
+
* console.log(category.name, category._id);
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export async function queryCategories() {
|
|
24
|
+
const result = await categoriesV2.queryCategories().find();
|
|
25
|
+
return {
|
|
26
|
+
categories: result.items || [],
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -74,8 +74,15 @@ const buildServicesQueryRequest = ({ appId, filters, paging, sort, }) => {
|
|
|
74
74
|
if (filters?.serviceIds && filters.serviceIds.length > 0) {
|
|
75
75
|
filter['_id'] = { $in: filters.serviceIds };
|
|
76
76
|
}
|
|
77
|
-
// Add
|
|
78
|
-
|
|
77
|
+
// Add categoryIds filter if provided
|
|
78
|
+
if (filters?.categoryIds && filters.categoryIds.length > 0) {
|
|
79
|
+
filter['category.id'] = { $in: filters.categoryIds };
|
|
80
|
+
}
|
|
81
|
+
// Add locationIds filter if provided
|
|
82
|
+
if (filters?.locationIds && filters.locationIds.length > 0) {
|
|
83
|
+
filter['locations.business.id'] = { $hasSome: filters.locationIds };
|
|
84
|
+
}
|
|
85
|
+
// staffMemberIds to be added later
|
|
79
86
|
const query = {
|
|
80
87
|
filter: Object.keys(filter).length > 0 ? filter : undefined,
|
|
81
88
|
paging: paging || {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core ServiceListFilter Components
|
|
3
|
+
* Provides render props for filter data from ServiceListService and BookingService
|
|
4
|
+
*/
|
|
5
|
+
import React from 'react';
|
|
6
|
+
import type { FilterOption } from '@wix/headless-components/react';
|
|
7
|
+
import { type ServiceListFilters } from '../../../services/service-list/service-list.js';
|
|
8
|
+
/**
|
|
9
|
+
* Render props for the Root component
|
|
10
|
+
*/
|
|
11
|
+
export interface RootRenderProps {
|
|
12
|
+
/** Filter options configuration derived from categories */
|
|
13
|
+
filterOptions: FilterOption[];
|
|
14
|
+
/** Current filter state */
|
|
15
|
+
currentFilter: ServiceListFilters | null;
|
|
16
|
+
/** Function to update filter (includes location from BookingService) */
|
|
17
|
+
updateFilter: (filter: ServiceListFilters) => void;
|
|
18
|
+
/** Function to clear all filters */
|
|
19
|
+
clearFilters: () => void;
|
|
20
|
+
/** Whether any filters are active */
|
|
21
|
+
isFiltered: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Props for the Root component
|
|
25
|
+
*/
|
|
26
|
+
export interface RootProps {
|
|
27
|
+
/** Render function that receives filter data */
|
|
28
|
+
children: (props: RootRenderProps) => React.ReactNode;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Core render props component that provides filter data from services.
|
|
32
|
+
* Reads categories from ServiceListService and location from BookingService.
|
|
33
|
+
*
|
|
34
|
+
* @component
|
|
35
|
+
* @example
|
|
36
|
+
* ```tsx
|
|
37
|
+
* <ServiceListFilter.Root>
|
|
38
|
+
* {({ filterOptions, currentFilter, updateFilter, clearFilters, isFiltered }) => (
|
|
39
|
+
* <Filter.Root
|
|
40
|
+
* value={currentFilter}
|
|
41
|
+
* onChange={updateFilter}
|
|
42
|
+
* filterOptions={filterOptions}
|
|
43
|
+
* >
|
|
44
|
+
* <Filter.FilterOptions>
|
|
45
|
+
* <Filter.FilterOptionRepeater>
|
|
46
|
+
* <Filter.FilterOption.Label />
|
|
47
|
+
* <Filter.FilterOption.SingleFilter />
|
|
48
|
+
* </Filter.FilterOptionRepeater>
|
|
49
|
+
* </Filter.FilterOptions>
|
|
50
|
+
* </Filter.Root>
|
|
51
|
+
* )}
|
|
52
|
+
* </ServiceListFilter.Root>
|
|
53
|
+
* ```
|
|
54
|
+
*/
|
|
55
|
+
export declare function Root(props: RootProps): React.ReactNode;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core ServiceListFilter Components
|
|
3
|
+
* Provides render props for filter data from ServiceListService and BookingService
|
|
4
|
+
*/
|
|
5
|
+
import { useService } from '@wix/services-manager-react';
|
|
6
|
+
import { ServiceListServiceDefinition, } from '../../../services/service-list/service-list.js';
|
|
7
|
+
import { BookingServiceDefinition } from '../../../services/booking/booking.js';
|
|
8
|
+
/**
|
|
9
|
+
* Core render props component that provides filter data from services.
|
|
10
|
+
* Reads categories from ServiceListService and location from BookingService.
|
|
11
|
+
*
|
|
12
|
+
* @component
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* <ServiceListFilter.Root>
|
|
16
|
+
* {({ filterOptions, currentFilter, updateFilter, clearFilters, isFiltered }) => (
|
|
17
|
+
* <Filter.Root
|
|
18
|
+
* value={currentFilter}
|
|
19
|
+
* onChange={updateFilter}
|
|
20
|
+
* filterOptions={filterOptions}
|
|
21
|
+
* >
|
|
22
|
+
* <Filter.FilterOptions>
|
|
23
|
+
* <Filter.FilterOptionRepeater>
|
|
24
|
+
* <Filter.FilterOption.Label />
|
|
25
|
+
* <Filter.FilterOption.SingleFilter />
|
|
26
|
+
* </Filter.FilterOptionRepeater>
|
|
27
|
+
* </Filter.FilterOptions>
|
|
28
|
+
* </Filter.Root>
|
|
29
|
+
* )}
|
|
30
|
+
* </ServiceListFilter.Root>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function Root(props) {
|
|
34
|
+
const serviceListService = useService(ServiceListServiceDefinition);
|
|
35
|
+
const bookingService = useService(BookingServiceDefinition);
|
|
36
|
+
// Get reactive data from signals
|
|
37
|
+
const filterOptions = serviceListService.filterOptionsSignal.get();
|
|
38
|
+
const currentFilter = serviceListService.filterSignal.get();
|
|
39
|
+
const isFiltered = serviceListService.isFilteredSignal.get();
|
|
40
|
+
const selectedLocation = bookingService.location.get();
|
|
41
|
+
const updateFilter = (newFilter) => {
|
|
42
|
+
// Include locationId from BookingService in the filter
|
|
43
|
+
const locationId = selectedLocation?.business?._id;
|
|
44
|
+
serviceListService.actions.setFilter({
|
|
45
|
+
...newFilter,
|
|
46
|
+
...(locationId ? { locationIds: [locationId] } : {}),
|
|
47
|
+
});
|
|
48
|
+
};
|
|
49
|
+
const clearFilters = () => {
|
|
50
|
+
serviceListService.actions.resetFilter();
|
|
51
|
+
};
|
|
52
|
+
return props.children({
|
|
53
|
+
filterOptions,
|
|
54
|
+
currentFilter,
|
|
55
|
+
updateFilter,
|
|
56
|
+
clearFilters,
|
|
57
|
+
isFiltered,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Provides components for displaying services list with pagination
|
|
4
4
|
*/
|
|
5
5
|
import React from 'react';
|
|
6
|
-
import { ListVariant, GenericListLoadMoreRenderProps } from '@wix/headless-components/react';
|
|
6
|
+
import { ListVariant, GenericListLoadMoreRenderProps, Filter as FilterPrimitive } from '@wix/headless-components/react';
|
|
7
7
|
import { AsChildChildren } from '@wix/headless-utils/react';
|
|
8
8
|
import type { ServiceListServiceConfig } from '../../services/service-list/service-list.js';
|
|
9
9
|
import type { Service as ServiceType } from '@wix/auto_sdk_bookings_services';
|
|
@@ -185,3 +185,64 @@ export declare const LoadMore: React.ForwardRefExoticComponent<LoadMoreProps & R
|
|
|
185
185
|
export declare const Actions: {
|
|
186
186
|
LoadMore: React.ForwardRefExoticComponent<LoadMoreProps & React.RefAttributes<HTMLButtonElement>>;
|
|
187
187
|
};
|
|
188
|
+
/**
|
|
189
|
+
* Data available through asChild pattern
|
|
190
|
+
*/
|
|
191
|
+
export interface FilterAsChildProps {
|
|
192
|
+
/** Filter options configuration derived from categories */
|
|
193
|
+
filterOptions: FilterPrimitive.FilterOption[];
|
|
194
|
+
/** Current filter state */
|
|
195
|
+
currentFilter: FilterPrimitive.Filter;
|
|
196
|
+
/** Function to update filter */
|
|
197
|
+
updateFilter: (filter: FilterPrimitive.Filter) => void;
|
|
198
|
+
/** Function to clear all active filters */
|
|
199
|
+
clearFilters: () => void;
|
|
200
|
+
/** Whether any filters are currently active */
|
|
201
|
+
isFiltered: boolean;
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Props for ServiceList Filter component
|
|
205
|
+
*/
|
|
206
|
+
export interface FilterProps {
|
|
207
|
+
/** Child components that will have access to filter functionality */
|
|
208
|
+
children: React.ReactNode | ((props: FilterAsChildProps) => React.ReactNode);
|
|
209
|
+
/** When true, the component will not render its own div wrapper */
|
|
210
|
+
asChild?: boolean;
|
|
211
|
+
/** CSS classes to apply to the filter container */
|
|
212
|
+
className?: string;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Filter component that provides filtering functionality for service lists.
|
|
216
|
+
* Integrates with ServiceListService for categories and BookingService for location.
|
|
217
|
+
*
|
|
218
|
+
* @component
|
|
219
|
+
* @example
|
|
220
|
+
* ```tsx
|
|
221
|
+
* import { Filter } from '@wix/headless-components/react';
|
|
222
|
+
*
|
|
223
|
+
* <ServiceList.Filter className="filter-sidebar">
|
|
224
|
+
* <Filter.FilterOptions>
|
|
225
|
+
* <Filter.FilterOptionRepeater>
|
|
226
|
+
* <Filter.FilterOption.Label />
|
|
227
|
+
* <Filter.FilterOption.SingleFilter />
|
|
228
|
+
* </Filter.FilterOptionRepeater>
|
|
229
|
+
* </Filter.FilterOptions>
|
|
230
|
+
* <Filter.Filtered>
|
|
231
|
+
* <Filter.Action.Clear label="Clear Filters" />
|
|
232
|
+
* </Filter.Filtered>
|
|
233
|
+
* </ServiceList.Filter>
|
|
234
|
+
*
|
|
235
|
+
* // With asChild pattern
|
|
236
|
+
* <ServiceList.Filter asChild>
|
|
237
|
+
* <aside className="filter-sidebar">
|
|
238
|
+
* <Filter.FilterOptions>
|
|
239
|
+
* <Filter.FilterOptionRepeater>
|
|
240
|
+
* <Filter.FilterOption.Label />
|
|
241
|
+
* <Filter.FilterOption.SingleFilter />
|
|
242
|
+
* </Filter.FilterOptionRepeater>
|
|
243
|
+
* </Filter.FilterOptions>
|
|
244
|
+
* </aside>
|
|
245
|
+
* </ServiceList.Filter>
|
|
246
|
+
* ```
|
|
247
|
+
*/
|
|
248
|
+
export declare const Filter: React.ForwardRefExoticComponent<FilterProps & React.RefAttributes<HTMLElement>>;
|
|
@@ -4,8 +4,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
4
4
|
* Provides components for displaying services list with pagination
|
|
5
5
|
*/
|
|
6
6
|
import React from 'react';
|
|
7
|
-
import { GenericList, } from '@wix/headless-components/react';
|
|
7
|
+
import { GenericList, Filter as FilterPrimitive, } from '@wix/headless-components/react';
|
|
8
|
+
import { AsChildSlot } from '@wix/headless-utils/react';
|
|
8
9
|
import * as CoreServiceList from '../core/service-list/ServiceList.js';
|
|
10
|
+
import * as CoreServiceListFilter from '../core/service-list/ServiceListFilter.js';
|
|
9
11
|
import * as Service from '../service/Service.js';
|
|
10
12
|
/**
|
|
11
13
|
* Booking app ID constant
|
|
@@ -17,6 +19,7 @@ var TestIds;
|
|
|
17
19
|
TestIds["serviceListServices"] = "service-list-services";
|
|
18
20
|
TestIds["serviceListService"] = "service-list-service";
|
|
19
21
|
TestIds["serviceListLoadMore"] = "service-list-load-more";
|
|
22
|
+
TestIds["serviceListFilter"] = "service-list-filter";
|
|
20
23
|
})(TestIds || (TestIds = {}));
|
|
21
24
|
/**
|
|
22
25
|
* Root component that provides the ServiceList service context for rendering services lists.
|
|
@@ -170,3 +173,52 @@ LoadMore.displayName = 'ServiceList.Actions.LoadMore';
|
|
|
170
173
|
export const Actions = {
|
|
171
174
|
LoadMore,
|
|
172
175
|
};
|
|
176
|
+
/**
|
|
177
|
+
* Filter component that provides filtering functionality for service lists.
|
|
178
|
+
* Integrates with ServiceListService for categories and BookingService for location.
|
|
179
|
+
*
|
|
180
|
+
* @component
|
|
181
|
+
* @example
|
|
182
|
+
* ```tsx
|
|
183
|
+
* import { Filter } from '@wix/headless-components/react';
|
|
184
|
+
*
|
|
185
|
+
* <ServiceList.Filter className="filter-sidebar">
|
|
186
|
+
* <Filter.FilterOptions>
|
|
187
|
+
* <Filter.FilterOptionRepeater>
|
|
188
|
+
* <Filter.FilterOption.Label />
|
|
189
|
+
* <Filter.FilterOption.SingleFilter />
|
|
190
|
+
* </Filter.FilterOptionRepeater>
|
|
191
|
+
* </Filter.FilterOptions>
|
|
192
|
+
* <Filter.Filtered>
|
|
193
|
+
* <Filter.Action.Clear label="Clear Filters" />
|
|
194
|
+
* </Filter.Filtered>
|
|
195
|
+
* </ServiceList.Filter>
|
|
196
|
+
*
|
|
197
|
+
* // With asChild pattern
|
|
198
|
+
* <ServiceList.Filter asChild>
|
|
199
|
+
* <aside className="filter-sidebar">
|
|
200
|
+
* <Filter.FilterOptions>
|
|
201
|
+
* <Filter.FilterOptionRepeater>
|
|
202
|
+
* <Filter.FilterOption.Label />
|
|
203
|
+
* <Filter.FilterOption.SingleFilter />
|
|
204
|
+
* </Filter.FilterOptionRepeater>
|
|
205
|
+
* </Filter.FilterOptions>
|
|
206
|
+
* </aside>
|
|
207
|
+
* </ServiceList.Filter>
|
|
208
|
+
* ```
|
|
209
|
+
*/
|
|
210
|
+
export const Filter = React.forwardRef((props, ref) => {
|
|
211
|
+
const { children, asChild, className, ...otherProps } = props;
|
|
212
|
+
return (_jsx(CoreServiceListFilter.Root, { children: ({ filterOptions, currentFilter, updateFilter, clearFilters, isFiltered, }) => {
|
|
213
|
+
// Prepare asChildProps for function children
|
|
214
|
+
const asChildProps = {
|
|
215
|
+
filterOptions,
|
|
216
|
+
currentFilter: currentFilter,
|
|
217
|
+
updateFilter: (value) => updateFilter(value),
|
|
218
|
+
clearFilters,
|
|
219
|
+
isFiltered,
|
|
220
|
+
};
|
|
221
|
+
return (_jsx(FilterPrimitive.Root, { value: currentFilter, onChange: (value) => updateFilter(value), filterOptions: filterOptions, children: _jsx(AsChildSlot, { ref: ref, asChild: asChild, className: className, "data-testid": TestIds.serviceListFilter, "data-is-filtered": isFiltered, customElement: children, customElementProps: asChildProps, ...otherProps, children: children }) }));
|
|
222
|
+
} }));
|
|
223
|
+
});
|
|
224
|
+
Filter.displayName = 'ServiceList.Filter';
|