payload-reserve 2.1.0 → 2.2.0
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/README.md +72 -2
- package/dist/collections/Reservations.js +3 -2
- package/dist/collections/Reservations.js.map +1 -1
- package/dist/components/CalendarView/CalendarView.module.css +11 -0
- package/dist/components/CalendarView/LaneTimelineView.js +3 -1
- package/dist/components/CalendarView/LaneTimelineView.js.map +1 -1
- package/dist/components/CalendarView/index.js +8 -0
- package/dist/components/CalendarView/index.js.map +1 -1
- package/dist/defaults.js +1 -0
- package/dist/defaults.js.map +1 -1
- package/dist/endpoints/checkAvailability.js +1 -0
- package/dist/endpoints/checkAvailability.js.map +1 -1
- package/dist/endpoints/customerSearch.js +12 -0
- package/dist/endpoints/customerSearch.js.map +1 -1
- package/dist/endpoints/getSlots.js +1 -0
- package/dist/endpoints/getSlots.js.map +1 -1
- package/dist/endpoints/resourceAvailability.d.ts +6 -2
- package/dist/endpoints/resourceAvailability.js +19 -1
- package/dist/endpoints/resourceAvailability.js.map +1 -1
- package/dist/hooks/reservations/validateConflicts.js +1 -0
- package/dist/hooks/reservations/validateConflicts.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js.map +1 -1
- package/dist/services/AvailabilityService.d.ts +9 -2
- package/dist/services/AvailabilityService.js +26 -3
- package/dist/services/AvailabilityService.js.map +1 -1
- package/dist/translations/ar.json +1 -0
- package/dist/translations/de.json +1 -0
- package/dist/translations/en.json +1 -0
- package/dist/translations/es.json +1 -0
- package/dist/translations/fa.json +1 -0
- package/dist/translations/fr.json +1 -0
- package/dist/translations/hi.json +1 -0
- package/dist/translations/id.json +1 -0
- package/dist/translations/pl.json +1 -0
- package/dist/translations/ru.json +1 -0
- package/dist/translations/tr.json +1 -0
- package/dist/translations/zh.json +1 -0
- package/dist/types.d.ts +27 -0
- package/dist/types.js.map +1 -1
- package/dist/utilities/computeSlotStates.d.ts +2 -1
- package/dist/utilities/computeSlotStates.js +5 -1
- package/dist/utilities/computeSlotStates.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Designed for salons, clinics, hotels, restaurants, event venues, and any busines
|
|
|
20
20
|
- **Configurable Status Machine** — Define your own statuses, transitions, blocking states, terminal states, and the `confirmStatus`/`cancelStatus` that drive the confirm/cancel hooks and cancellation policy
|
|
21
21
|
- **Double-Booking Prevention** — Server-side conflict detection that enforces both bookings' buffer times and checks each resource only for its own item window; respects capacity modes
|
|
22
22
|
- **Business Timezone** — Set a plugin-level `timezone` (IANA, default `'UTC'`) so schedules, day boundaries, and the admin calendar resolve in your business's timezone regardless of server location — with optional **per-tenant** zones in `multiTenant` mode
|
|
23
|
-
- **Auto End Time** — Calculates `endTime` from `startTime + service.duration` automatically
|
|
23
|
+
- **Auto End Time** — Calculates `endTime` from `startTime + service.duration` automatically for `fixed`/`full-day` services; the `endTime` field stays editable so `flexible`-duration bookings can supply their own end time
|
|
24
24
|
- **Three Duration Types** — `fixed` (service duration), `flexible` (customer-specified end), and `full-day` bookings
|
|
25
25
|
- **Multi-Resource Bookings** — Single reservation that spans multiple resources simultaneously via the `items` array
|
|
26
26
|
- **Capacity and Inventory** — `quantity > 1` allows multiple concurrent bookings per resource; `capacityMode` (`per-reservation` | `per-guest`) controls how capacity is counted
|
|
@@ -37,6 +37,7 @@ Designed for salons, clinics, hotels, restaurants, event venues, and any busines
|
|
|
37
37
|
- **Recurring and Manual Schedules** — Weekly patterns with exception dates, or specific one-off dates
|
|
38
38
|
- **12 Bundled Languages** — Every admin string is translatable; ships with English, French, German, Spanish, Russian, Polish, Turkish, Arabic, Simplified Chinese, Indonesian, Persian/Farsi, and Hindi. Override any string or add your own language
|
|
39
39
|
- **Localization Support** — Collection field *content* can be localized when Payload localization is enabled (separate from the admin-UI language above)
|
|
40
|
+
- **External Busy** — Optional `getExternalBusy` resolver folds busy time from calendar sync, legacy booking systems, or ops tooling into availability, with distinct calendar display and fail-open error handling
|
|
40
41
|
- **Type-Safe** — Full TypeScript support with exported types
|
|
41
42
|
|
|
42
43
|
---
|
|
@@ -268,6 +269,19 @@ payloadReserve({
|
|
|
268
269
|
|
|
269
270
|
Resolution precedence is `tenant.<timezoneField> → global timezone → 'UTC'`; a tenant with no (or an invalid) timezone value transparently falls back to the global default. The zone is resolved server-side from the tenant cookie — the client calendar reads it from `GET /api/reserve/effective-timezone`. This is purely additive: plain single-tenant installs (no tenant relationship / no tenant cookie) keep the global zone with no extra DB read.
|
|
270
271
|
|
|
272
|
+
#### Tenant-scoped customer search
|
|
273
|
+
|
|
274
|
+
The reservation form's customer picker (and its backing `/api/reservation-customer-search` endpoint) restricts results to the **selected tenant** — read from the tenant cookie — whenever the customers collection carries the multi-tenant `tenant` field. This prevents picking a customer from another tenant (which would otherwise fail on save with a tenant mismatch). Like the per-tenant timezone behaviour, it is purely additive: plain single-tenant installs (customers collection without a tenant field, or no tenant cookie) are unaffected and the search spans all customers as before.
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
payloadReserve({
|
|
278
|
+
multiTenant: {
|
|
279
|
+
tenantField: 'tenant', // tenant relationship field on collections (default: 'tenant')
|
|
280
|
+
cookieName: 'payload-tenant', // selected-tenant cookie (default: 'payload-tenant')
|
|
281
|
+
},
|
|
282
|
+
})
|
|
283
|
+
```
|
|
284
|
+
|
|
271
285
|
### Collection Overrides
|
|
272
286
|
|
|
273
287
|
Customize any generated collection without forking the plugin via `collectionOverrides`. Each entry is a `Partial<CollectionConfig>` (minus `fields`/`slug`) plus a `fields` function that receives the plugin's default fields:
|
|
@@ -298,6 +312,62 @@ The `services` relationship on Resources is now optional. This lets a freshly pr
|
|
|
298
312
|
|
|
299
313
|
---
|
|
300
314
|
|
|
315
|
+
## External Busy (Calendar Sync & Other Sources)
|
|
316
|
+
|
|
317
|
+
`getExternalBusy` lets your app fold busy time that payload-reserve doesn't manage — an external calendar, a legacy booking system, ops tooling — into a resource's availability. The plugin never talks to any calendar API itself; it calls a resolver you provide once per candidate window and treats whatever it returns as busy time for that resource.
|
|
318
|
+
|
|
319
|
+
Use it to:
|
|
320
|
+
|
|
321
|
+
- Import busy time from two-way calendar sync (Google Calendar, Outlook, iCal feeds)
|
|
322
|
+
- Respect bookings made in another/legacy booking system during a migration
|
|
323
|
+
- Block maintenance/cleaning windows tracked in an ops system
|
|
324
|
+
- Reflect staff leave recorded in an HR system that isn't modeled as plugin time-off
|
|
325
|
+
|
|
326
|
+
A realistic setup keeps a local collection in sync (via webhooks or a cron job) and has the resolver run a single indexed query against it — the resolver itself never calls a remote API:
|
|
327
|
+
|
|
328
|
+
```ts
|
|
329
|
+
import type { ExternalBusyInterval, GetExternalBusy } from 'payload-reserve'
|
|
330
|
+
|
|
331
|
+
// A sync job (webhook handler or scheduled cron task) keeps this collection's
|
|
332
|
+
// rows current from whatever external source you're integrating — a
|
|
333
|
+
// Google/Outlook calendar sync, a legacy system export, an ops tool, etc.
|
|
334
|
+
// The resolver below never calls out itself; it only reads what the sync
|
|
335
|
+
// job already wrote, so it stays a single cheap, indexed query.
|
|
336
|
+
const getExternalBusy: GetExternalBusy = async ({ end, req, resourceId, start }) => {
|
|
337
|
+
const result = await req.payload.find({
|
|
338
|
+
collection: 'external-busy', // your locally-synced collection
|
|
339
|
+
where: {
|
|
340
|
+
and: [
|
|
341
|
+
{ resource: { equals: resourceId } },
|
|
342
|
+
{ start: { less_than: end.toISOString() } },
|
|
343
|
+
{ end: { greater_than: start.toISOString() } },
|
|
344
|
+
],
|
|
345
|
+
},
|
|
346
|
+
depth: 0,
|
|
347
|
+
limit: 0,
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
return result.docs.map(
|
|
351
|
+
(doc): ExternalBusyInterval => ({
|
|
352
|
+
start: doc.start,
|
|
353
|
+
end: doc.end,
|
|
354
|
+
label: doc.label,
|
|
355
|
+
}),
|
|
356
|
+
)
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
payloadReserve({
|
|
360
|
+
getExternalBusy,
|
|
361
|
+
})
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
- **Enforcement:** any booking (hooks, endpoints, slot listings) overlapping an interval is unavailable. An interval blocks the WHOLE resource (all `quantity` units) — external calendars aren't unit-aware.
|
|
365
|
+
- **Display:** the `resource-availability` endpoint returns the intervals as a separate `external[]` array (not mixed into `busy`), and the calendar renders them as a distinct, non-clickable hatched "External event" slot.
|
|
366
|
+
- **Fail-open:** if the resolver throws, the plugin treats it as no external busy for that call — a sync failure never blocks a real booking or breaks the grid.
|
|
367
|
+
- **Performance:** the resolver is called once per candidate window during slot computation, so keep it cheap — a local table lookup or a per-request cache, never a remote API call per invocation.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
301
371
|
## Internationalization
|
|
302
372
|
|
|
303
373
|
Every admin string the plugin renders — field labels, descriptions, select options, calendar/dashboard components, and validation errors — is translatable. The plugin ships **12 languages**: English, French (`fr`), German (`de`), Spanish (`es`), Russian (`ru`), Polish (`pl`), Turkish (`tr`), Arabic (`ar`), Simplified Chinese (`zh`), Indonesian (`id`), Persian/Farsi (`fa`), and Hindi (`hi`). All but Hindi ship in Payload core and appear in the admin language switcher automatically.
|
|
@@ -327,7 +397,7 @@ This is separate from Payload **field localization** (localizing the *content* o
|
|
|
327
397
|
| Topic | Contents |
|
|
328
398
|
|-------|----------|
|
|
329
399
|
| [Getting Started](https://github.com/elghaied/payload-reserve/blob/main/docs/getting-started.md) | Installation, quick start, what gets created |
|
|
330
|
-
| [Configuration](https://github.com/elghaied/payload-reserve/blob/main/docs/configuration.md) | All plugin options with types and defaults, including `resourceOwnerMode` |
|
|
400
|
+
| [Configuration](https://github.com/elghaied/payload-reserve/blob/main/docs/configuration.md) | All plugin options with types and defaults, including `resourceOwnerMode` and `getExternalBusy` |
|
|
331
401
|
| [Collections](https://github.com/elghaied/payload-reserve/blob/main/docs/collections.md) | Services, Resources, Schedules, Customers, Reservations schemas |
|
|
332
402
|
| [Status Machine](https://github.com/elghaied/payload-reserve/blob/main/docs/status-machine.md) | Default flow, custom machines, business logic hooks, escape hatch |
|
|
333
403
|
| [Booking Features](https://github.com/elghaied/payload-reserve/blob/main/docs/booking-features.md) | Duration types, multi-resource bookings, capacity modes |
|
|
@@ -157,10 +157,11 @@ export function createReservationsCollection(config) {
|
|
|
157
157
|
name: 'endTime',
|
|
158
158
|
type: 'date',
|
|
159
159
|
admin: {
|
|
160
|
+
// Editable: flexible-duration services require a user-supplied endTime
|
|
161
|
+
// (calculateEndTime overwrites it for fixed/full-day on save).
|
|
160
162
|
date: {
|
|
161
163
|
pickerAppearance: 'dayAndTime'
|
|
162
|
-
}
|
|
163
|
-
readOnly: true
|
|
164
|
+
}
|
|
164
165
|
},
|
|
165
166
|
label: ({ t })=>t('reservation:fieldEndTime')
|
|
166
167
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/collections/Reservations.ts"],"sourcesContent":["import type {\n CollectionAfterChangeHook,\n CollectionBeforeChangeHook,\n CollectionConfig,\n CollectionSlug,\n} from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ReservationPluginHooks, ResolvedReservationPluginConfig } from '../types.js'\n\nimport { calculateEndTime } from '../hooks/reservations/calculateEndTime.js'\nimport { checkIdempotency } from '../hooks/reservations/checkIdempotency.js'\nimport { enforceCustomerOwnership } from '../hooks/reservations/enforceCustomerOwnership.js'\nimport { expandRequiredResources } from '../hooks/reservations/expandRequiredResources.js'\nimport { onStatusChange } from '../hooks/reservations/onStatusChange.js'\nimport { validateCancellation } from '../hooks/reservations/validateCancellation.js'\nimport { validateConflicts } from '../hooks/reservations/validateConflicts.js'\nimport { validateGuestBooking } from '../hooks/reservations/validateGuestBooking.js'\nimport { validateStatusTransition } from '../hooks/reservations/validateStatusTransition.js'\nimport { statusToI18nKey } from '../utilities/i18nUtils.js'\nimport { composeAccess, makeReservationOwnerAccess } from '../utilities/ownerAccess.js'\n\nfunction createPluginHooksBeforeCreate(\n hooks: ReservationPluginHooks,\n): CollectionBeforeChangeHook {\n return async ({ context, data, operation, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n if (operation === 'create' && hooks.beforeBookingCreate) {\n let mutatedData = data\n for (const hook of hooks.beforeBookingCreate) {\n const result = await hook({ data: mutatedData, req })\n if (result) {mutatedData = result}\n }\n return mutatedData\n }\n\n return data\n }\n}\n\nfunction createPluginHooksAfterCreate(\n hooks: ReservationPluginHooks,\n): CollectionAfterChangeHook {\n return async ({ context, doc, operation, req }) => {\n if (context?.skipReservationHooks) {return doc}\n if (operation === 'create' && hooks.afterBookingCreate) {\n const docRecord = doc as Record<string, unknown>\n for (const hook of hooks.afterBookingCreate) {\n await hook({ doc: docRecord, req })\n }\n }\n return doc\n }\n}\n\nexport function createReservationsCollection(\n config: ResolvedReservationPluginConfig,\n): CollectionConfig {\n const { statusMachine } = config\n const rom = config.resourceOwnerMode\n const access =\n composeAccess(rom ? makeReservationOwnerAccess(rom) : {}, config.access.reservations)\n\n return {\n slug: config.slugs.reservations,\n access,\n admin: {\n components: {\n views: {\n list: {\n Component: 'payload-reserve/client#CalendarView',\n },\n },\n },\n group: config.adminGroup,\n listSearchableFields: ['status'],\n useAsTitle: 'startTime',\n },\n fields: [\n {\n name: 'service',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldService'),\n relationTo: config.slugs.services as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'resource',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldResource'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'customer',\n type: 'relationship',\n admin: {\n allowCreate: true,\n allowEdit: true,\n components: {\n Field: 'payload-reserve/client#CustomerField',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldCustomer'),\n relationTo: config.slugs.customers as unknown as CollectionSlug,\n required: false,\n },\n {\n name: 'guest',\n type: 'group',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldGuestDesc'),\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestName'),\n maxLength: 200,\n },\n {\n name: 'email',\n type: 'email',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestEmail'),\n },\n {\n name: 'phone',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestPhone'),\n maxLength: 50,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldGuest'),\n },\n {\n name: 'cancellationToken',\n type: 'text',\n access: {\n // Server-generated secret: never settable via the API (the\n // validateGuestBooking hook stamps it), readable only by staff/admin.\n create: () => false,\n read: ({ req }) =>\n Boolean(req.user) && req.user!.collection !== config.slugs.customers,\n update: () => false,\n },\n admin: {\n hidden: true,\n },\n index: true,\n },\n {\n name: 'startTime',\n type: 'date',\n admin: {\n components: {\n Field: 'payload-reserve/client#AvailabilityTimeField',\n },\n date: {\n pickerAppearance: 'dayAndTime',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTime'),\n required: true,\n },\n {\n name: 'endTime',\n type: 'date',\n admin: {\n date: {\n pickerAppearance: 'dayAndTime',\n },\n readOnly: true,\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTime'),\n },\n {\n name: 'status',\n type: 'select',\n defaultValue: statusMachine.defaultStatus,\n label: ({ t }) => (t as PluginT)('reservation:fieldStatus'),\n options: statusMachine.statuses.map((s) => ({\n label: ({ t }) => {\n const key = statusToI18nKey(s)\n const translated = (t as PluginT)(key)\n return translated !== key ? translated : s.charAt(0).toUpperCase() + s.slice(1)\n },\n value: s,\n })),\n },\n {\n name: 'cancellationReason',\n type: 'textarea',\n admin: {\n condition: (_, siblingData) => siblingData?.status === statusMachine.cancelStatus,\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldCancellationReason'),\n },\n {\n name: 'guestCount',\n type: 'number',\n defaultValue: 1,\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestCount'),\n min: 1,\n },\n {\n name: 'notes',\n type: 'textarea',\n label: ({ t }) => (t as PluginT)('reservation:fieldNotes'),\n },\n {\n name: 'items',\n type: 'array',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldItemsDesc'),\n },\n fields: [\n {\n name: 'resource',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldResource'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'service',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldService'),\n relationTo: config.slugs.services as unknown as CollectionSlug,\n },\n {\n name: 'startTime',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayAndTime' } },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTime'),\n },\n {\n name: 'endTime',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayAndTime' }, readOnly: false },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTime'),\n },\n {\n name: 'guestCount',\n type: 'number',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestCount'),\n min: 1,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldItems'),\n },\n {\n name: 'idempotencyKey',\n type: 'text',\n admin: { position: 'sidebar', readOnly: true },\n index: true,\n unique: true,\n },\n ...config.extraReservationFields,\n ],\n hooks: {\n afterChange: [\n createPluginHooksAfterCreate(config.hooks),\n onStatusChange(config),\n ],\n beforeChange: [\n createPluginHooksBeforeCreate(config.hooks),\n enforceCustomerOwnership(config),\n checkIdempotency(config),\n validateGuestBooking(config),\n expandRequiredResources(config),\n calculateEndTime(config),\n validateConflicts(config),\n // validateCancellation runs BEFORE validateStatusTransition so a cancel\n // rejected by the notice period never fires the beforeBookingCancel\n // plugin hooks (e.g. refund initiation) for an update that won't land.\n validateCancellation(config),\n validateStatusTransition(config),\n ],\n },\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionReservations'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionReservations'),\n },\n }\n}\n"],"names":["calculateEndTime","checkIdempotency","enforceCustomerOwnership","expandRequiredResources","onStatusChange","validateCancellation","validateConflicts","validateGuestBooking","validateStatusTransition","statusToI18nKey","composeAccess","makeReservationOwnerAccess","createPluginHooksBeforeCreate","hooks","context","data","operation","req","skipReservationHooks","beforeBookingCreate","mutatedData","hook","result","createPluginHooksAfterCreate","doc","afterBookingCreate","docRecord","createReservationsCollection","config","statusMachine","rom","resourceOwnerMode","access","reservations","slug","slugs","admin","components","views","list","Component","group","adminGroup","listSearchableFields","useAsTitle","fields","name","type","label","t","relationTo","services","required","resources","allowCreate","allowEdit","Field","customers","description","maxLength","create","read","Boolean","user","collection","update","hidden","index","date","pickerAppearance","readOnly","defaultValue","defaultStatus","options","statuses","map","s","key","translated","charAt","toUpperCase","slice","value","condition","_","siblingData","status","cancelStatus","min","position","unique","extraReservationFields","afterChange","beforeChange","labels","plural","singular"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/collections/Reservations.ts"],"sourcesContent":["import type {\n CollectionAfterChangeHook,\n CollectionBeforeChangeHook,\n CollectionConfig,\n CollectionSlug,\n} from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ReservationPluginHooks, ResolvedReservationPluginConfig } from '../types.js'\n\nimport { calculateEndTime } from '../hooks/reservations/calculateEndTime.js'\nimport { checkIdempotency } from '../hooks/reservations/checkIdempotency.js'\nimport { enforceCustomerOwnership } from '../hooks/reservations/enforceCustomerOwnership.js'\nimport { expandRequiredResources } from '../hooks/reservations/expandRequiredResources.js'\nimport { onStatusChange } from '../hooks/reservations/onStatusChange.js'\nimport { validateCancellation } from '../hooks/reservations/validateCancellation.js'\nimport { validateConflicts } from '../hooks/reservations/validateConflicts.js'\nimport { validateGuestBooking } from '../hooks/reservations/validateGuestBooking.js'\nimport { validateStatusTransition } from '../hooks/reservations/validateStatusTransition.js'\nimport { statusToI18nKey } from '../utilities/i18nUtils.js'\nimport { composeAccess, makeReservationOwnerAccess } from '../utilities/ownerAccess.js'\n\nfunction createPluginHooksBeforeCreate(\n hooks: ReservationPluginHooks,\n): CollectionBeforeChangeHook {\n return async ({ context, data, operation, req }) => {\n if (context?.skipReservationHooks) {return data}\n\n if (operation === 'create' && hooks.beforeBookingCreate) {\n let mutatedData = data\n for (const hook of hooks.beforeBookingCreate) {\n const result = await hook({ data: mutatedData, req })\n if (result) {mutatedData = result}\n }\n return mutatedData\n }\n\n return data\n }\n}\n\nfunction createPluginHooksAfterCreate(\n hooks: ReservationPluginHooks,\n): CollectionAfterChangeHook {\n return async ({ context, doc, operation, req }) => {\n if (context?.skipReservationHooks) {return doc}\n if (operation === 'create' && hooks.afterBookingCreate) {\n const docRecord = doc as Record<string, unknown>\n for (const hook of hooks.afterBookingCreate) {\n await hook({ doc: docRecord, req })\n }\n }\n return doc\n }\n}\n\nexport function createReservationsCollection(\n config: ResolvedReservationPluginConfig,\n): CollectionConfig {\n const { statusMachine } = config\n const rom = config.resourceOwnerMode\n const access =\n composeAccess(rom ? makeReservationOwnerAccess(rom) : {}, config.access.reservations)\n\n return {\n slug: config.slugs.reservations,\n access,\n admin: {\n components: {\n views: {\n list: {\n Component: 'payload-reserve/client#CalendarView',\n },\n },\n },\n group: config.adminGroup,\n listSearchableFields: ['status'],\n useAsTitle: 'startTime',\n },\n fields: [\n {\n name: 'service',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldService'),\n relationTo: config.slugs.services as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'resource',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldResource'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'customer',\n type: 'relationship',\n admin: {\n allowCreate: true,\n allowEdit: true,\n components: {\n Field: 'payload-reserve/client#CustomerField',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldCustomer'),\n relationTo: config.slugs.customers as unknown as CollectionSlug,\n required: false,\n },\n {\n name: 'guest',\n type: 'group',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldGuestDesc'),\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestName'),\n maxLength: 200,\n },\n {\n name: 'email',\n type: 'email',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestEmail'),\n },\n {\n name: 'phone',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestPhone'),\n maxLength: 50,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldGuest'),\n },\n {\n name: 'cancellationToken',\n type: 'text',\n access: {\n // Server-generated secret: never settable via the API (the\n // validateGuestBooking hook stamps it), readable only by staff/admin.\n create: () => false,\n read: ({ req }) =>\n Boolean(req.user) && req.user!.collection !== config.slugs.customers,\n update: () => false,\n },\n admin: {\n hidden: true,\n },\n index: true,\n },\n {\n name: 'startTime',\n type: 'date',\n admin: {\n components: {\n Field: 'payload-reserve/client#AvailabilityTimeField',\n },\n date: {\n pickerAppearance: 'dayAndTime',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTime'),\n required: true,\n },\n {\n name: 'endTime',\n type: 'date',\n admin: {\n // Editable: flexible-duration services require a user-supplied endTime\n // (calculateEndTime overwrites it for fixed/full-day on save).\n date: {\n pickerAppearance: 'dayAndTime',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTime'),\n },\n {\n name: 'status',\n type: 'select',\n defaultValue: statusMachine.defaultStatus,\n label: ({ t }) => (t as PluginT)('reservation:fieldStatus'),\n options: statusMachine.statuses.map((s) => ({\n label: ({ t }) => {\n const key = statusToI18nKey(s)\n const translated = (t as PluginT)(key)\n return translated !== key ? translated : s.charAt(0).toUpperCase() + s.slice(1)\n },\n value: s,\n })),\n },\n {\n name: 'cancellationReason',\n type: 'textarea',\n admin: {\n condition: (_, siblingData) => siblingData?.status === statusMachine.cancelStatus,\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldCancellationReason'),\n },\n {\n name: 'guestCount',\n type: 'number',\n defaultValue: 1,\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestCount'),\n min: 1,\n },\n {\n name: 'notes',\n type: 'textarea',\n label: ({ t }) => (t as PluginT)('reservation:fieldNotes'),\n },\n {\n name: 'items',\n type: 'array',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldItemsDesc'),\n },\n fields: [\n {\n name: 'resource',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldResource'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n required: true,\n },\n {\n name: 'service',\n type: 'relationship',\n label: ({ t }) => (t as PluginT)('reservation:fieldService'),\n relationTo: config.slugs.services as unknown as CollectionSlug,\n },\n {\n name: 'startTime',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayAndTime' } },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTime'),\n },\n {\n name: 'endTime',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayAndTime' }, readOnly: false },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTime'),\n },\n {\n name: 'guestCount',\n type: 'number',\n label: ({ t }) => (t as PluginT)('reservation:fieldGuestCount'),\n min: 1,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldItems'),\n },\n {\n name: 'idempotencyKey',\n type: 'text',\n admin: { position: 'sidebar', readOnly: true },\n index: true,\n unique: true,\n },\n ...config.extraReservationFields,\n ],\n hooks: {\n afterChange: [\n createPluginHooksAfterCreate(config.hooks),\n onStatusChange(config),\n ],\n beforeChange: [\n createPluginHooksBeforeCreate(config.hooks),\n enforceCustomerOwnership(config),\n checkIdempotency(config),\n validateGuestBooking(config),\n expandRequiredResources(config),\n calculateEndTime(config),\n validateConflicts(config),\n // validateCancellation runs BEFORE validateStatusTransition so a cancel\n // rejected by the notice period never fires the beforeBookingCancel\n // plugin hooks (e.g. refund initiation) for an update that won't land.\n validateCancellation(config),\n validateStatusTransition(config),\n ],\n },\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionReservations'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionReservations'),\n },\n }\n}\n"],"names":["calculateEndTime","checkIdempotency","enforceCustomerOwnership","expandRequiredResources","onStatusChange","validateCancellation","validateConflicts","validateGuestBooking","validateStatusTransition","statusToI18nKey","composeAccess","makeReservationOwnerAccess","createPluginHooksBeforeCreate","hooks","context","data","operation","req","skipReservationHooks","beforeBookingCreate","mutatedData","hook","result","createPluginHooksAfterCreate","doc","afterBookingCreate","docRecord","createReservationsCollection","config","statusMachine","rom","resourceOwnerMode","access","reservations","slug","slugs","admin","components","views","list","Component","group","adminGroup","listSearchableFields","useAsTitle","fields","name","type","label","t","relationTo","services","required","resources","allowCreate","allowEdit","Field","customers","description","maxLength","create","read","Boolean","user","collection","update","hidden","index","date","pickerAppearance","defaultValue","defaultStatus","options","statuses","map","s","key","translated","charAt","toUpperCase","slice","value","condition","_","siblingData","status","cancelStatus","min","readOnly","position","unique","extraReservationFields","afterChange","beforeChange","labels","plural","singular"],"mappings":"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"}
|
|
@@ -520,6 +520,17 @@
|
|
|
520
520
|
cursor: not-allowed;
|
|
521
521
|
}
|
|
522
522
|
|
|
523
|
+
.slotExternal {
|
|
524
|
+
background: repeating-linear-gradient(
|
|
525
|
+
45deg,
|
|
526
|
+
var(--theme-elevation-100),
|
|
527
|
+
var(--theme-elevation-100) 6px,
|
|
528
|
+
var(--theme-error-50) 6px,
|
|
529
|
+
var(--theme-error-50) 12px
|
|
530
|
+
);
|
|
531
|
+
cursor: not-allowed;
|
|
532
|
+
}
|
|
533
|
+
|
|
523
534
|
.slotFree {
|
|
524
535
|
cursor: pointer;
|
|
525
536
|
}
|
|
@@ -7,6 +7,7 @@ import { getDayKeyInTimezone } from '../../utilities/timezoneUtils.js';
|
|
|
7
7
|
import styles from './CalendarView.module.css';
|
|
8
8
|
import { useResourceAvailability } from './useResourceAvailability.js';
|
|
9
9
|
const SLOT_STATE_KEYS = {
|
|
10
|
+
external: 'reservation:slotExternal',
|
|
10
11
|
free: 'reservation:slotFree',
|
|
11
12
|
full: 'reservation:slotFull',
|
|
12
13
|
'off-shift': 'reservation:slotOffShift',
|
|
@@ -27,6 +28,7 @@ function Lane({ apiBase, day, endHour, onBook, resource, startHour, timeZone })
|
|
|
27
28
|
capacityMode: data.capacityMode,
|
|
28
29
|
dayEnd,
|
|
29
30
|
dayStart,
|
|
31
|
+
external: data.external,
|
|
30
32
|
quantity: data.quantity,
|
|
31
33
|
requiredPools: data.requiredPools,
|
|
32
34
|
shiftWindows: dayAvail.shiftWindows,
|
|
@@ -43,7 +45,7 @@ function Lane({ apiBase, day, endHour, onBook, resource, startHour, timeZone })
|
|
|
43
45
|
/*#__PURE__*/ _jsx("div", {
|
|
44
46
|
className: styles.laneTrack,
|
|
45
47
|
children: slots.map((s)=>{
|
|
46
|
-
const cls = s.state === 'off-shift' ? styles.slotOffShift : s.state === 'time-off' ? styles.slotTimeOff : s.state === 'full' ? styles.slotFull : styles.slotFree;
|
|
48
|
+
const cls = s.state === 'off-shift' ? styles.slotOffShift : s.state === 'time-off' ? styles.slotTimeOff : s.state === 'external' ? styles.slotExternal : s.state === 'full' ? styles.slotFull : styles.slotFree;
|
|
47
49
|
const isFree = s.state === 'free';
|
|
48
50
|
const slotLabel = `${s.start.toLocaleTimeString([], {
|
|
49
51
|
hour: '2-digit',
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/components/CalendarView/LaneTimelineView.tsx"],"sourcesContent":["'use client'\nimport { useTranslation } from '@payloadcms/ui'\nimport React from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { SlotState } from '../../utilities/computeSlotStates.js'\n\nimport { computeSlotStates } from '../../utilities/computeSlotStates.js'\nimport { getDayKeyInTimezone } from '../../utilities/timezoneUtils.js'\nimport styles from './CalendarView.module.css'\nimport { useResourceAvailability } from './useResourceAvailability.js'\n\ntype LaneResource = { id: string; name: string }\n\nconst SLOT_STATE_KEYS: Record<SlotState, string> = {\n free: 'reservation:slotFree',\n full: 'reservation:slotFull',\n 'off-shift': 'reservation:slotOffShift',\n 'time-off': 'reservation:slotTimeOff',\n}\n\nfunction Lane({\n apiBase,\n day,\n endHour,\n onBook,\n resource,\n startHour,\n timeZone,\n}: {\n apiBase: string\n day: Date\n endHour: number\n onBook: (resourceId: string, startIso: string) => void\n resource: LaneResource\n startHour: number\n timeZone: string\n}) {\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n\n const dayStart = new Date(day)\n dayStart.setHours(startHour, 0, 0, 0)\n const dayEnd = new Date(day)\n dayEnd.setHours(endHour, 0, 0, 0)\n\n const { data } = useResourceAvailability(apiBase, resource.id, dayStart, dayEnd)\n const isoDay = getDayKeyInTimezone(day, timeZone)\n const dayAvail = data?.days.find((d) => d.date === isoDay)\n\n const slots = dayAvail\n ? computeSlotStates({\n busy: data!.busy,\n capacityMode: data!.capacityMode,\n dayEnd,\n dayStart,\n quantity: data!.quantity,\n requiredPools: data!.requiredPools,\n shiftWindows: dayAvail.shiftWindows,\n step: 60,\n timeOff: dayAvail.timeOff,\n })\n : []\n\n return (\n <div className={styles.lane}>\n <div className={styles.laneLabel}>{resource.name}</div>\n <div className={styles.laneTrack}>\n {slots.map((s) => {\n const cls =\n s.state === 'off-shift'\n ? styles.slotOffShift\n : s.state === 'time-off'\n ? styles.slotTimeOff\n : s.state === '
|
|
1
|
+
{"version":3,"sources":["../../../src/components/CalendarView/LaneTimelineView.tsx"],"sourcesContent":["'use client'\nimport { useTranslation } from '@payloadcms/ui'\nimport React from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { SlotState } from '../../utilities/computeSlotStates.js'\n\nimport { computeSlotStates } from '../../utilities/computeSlotStates.js'\nimport { getDayKeyInTimezone } from '../../utilities/timezoneUtils.js'\nimport styles from './CalendarView.module.css'\nimport { useResourceAvailability } from './useResourceAvailability.js'\n\ntype LaneResource = { id: string; name: string }\n\nconst SLOT_STATE_KEYS: Record<SlotState, string> = {\n external: 'reservation:slotExternal',\n free: 'reservation:slotFree',\n full: 'reservation:slotFull',\n 'off-shift': 'reservation:slotOffShift',\n 'time-off': 'reservation:slotTimeOff',\n}\n\nfunction Lane({\n apiBase,\n day,\n endHour,\n onBook,\n resource,\n startHour,\n timeZone,\n}: {\n apiBase: string\n day: Date\n endHour: number\n onBook: (resourceId: string, startIso: string) => void\n resource: LaneResource\n startHour: number\n timeZone: string\n}) {\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n\n const dayStart = new Date(day)\n dayStart.setHours(startHour, 0, 0, 0)\n const dayEnd = new Date(day)\n dayEnd.setHours(endHour, 0, 0, 0)\n\n const { data } = useResourceAvailability(apiBase, resource.id, dayStart, dayEnd)\n const isoDay = getDayKeyInTimezone(day, timeZone)\n const dayAvail = data?.days.find((d) => d.date === isoDay)\n\n const slots = dayAvail\n ? computeSlotStates({\n busy: data!.busy,\n capacityMode: data!.capacityMode,\n dayEnd,\n dayStart,\n external: data!.external,\n quantity: data!.quantity,\n requiredPools: data!.requiredPools,\n shiftWindows: dayAvail.shiftWindows,\n step: 60,\n timeOff: dayAvail.timeOff,\n })\n : []\n\n return (\n <div className={styles.lane}>\n <div className={styles.laneLabel}>{resource.name}</div>\n <div className={styles.laneTrack}>\n {slots.map((s) => {\n const cls =\n s.state === 'off-shift'\n ? styles.slotOffShift\n : s.state === 'time-off'\n ? styles.slotTimeOff\n : s.state === 'external'\n ? styles.slotExternal\n : s.state === 'full'\n ? styles.slotFull\n : styles.slotFree\n const isFree = s.state === 'free'\n const slotLabel = `${s.start.toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n timeZone,\n })} — ${t(SLOT_STATE_KEYS[s.state])}`\n return isFree ? (\n <div\n aria-label={slotLabel}\n className={`${styles.laneCell} ${cls}`}\n key={s.start.toISOString()}\n onClick={() => onBook(resource.id, s.start.toISOString())}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onBook(resource.id, s.start.toISOString())\n }\n }}\n role=\"button\"\n tabIndex={0}\n title={slotLabel}\n />\n ) : (\n <div\n className={`${styles.laneCell} ${cls}`}\n key={s.start.toISOString()}\n title={slotLabel}\n />\n )\n })}\n </div>\n </div>\n )\n}\n\nexport function LaneTimelineView({\n apiBase,\n day,\n endHour,\n onBook,\n resources,\n startHour,\n timeZone,\n}: {\n apiBase: string\n day: Date\n endHour: number\n onBook: (resourceId: string, startIso: string) => void\n resources: LaneResource[]\n startHour: number\n timeZone: string\n}) {\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n if (resources.length === 0) {\n return <p className={styles.hint}>{t('reservation:laneNoResources')}</p>\n }\n const hours = Array.from({ length: endHour - startHour }, (_, i) => startHour + i)\n return (\n <div className={styles.lanes}>\n <div className={styles.laneHeader}>\n <div className={styles.laneLabel} />\n <div className={styles.laneTrack}>\n {hours.map((h) => (\n <div className={styles.laneTime} key={h}>\n {String(h).padStart(2, '0')}:00\n </div>\n ))}\n </div>\n </div>\n {resources.map((r) => (\n <Lane\n apiBase={apiBase}\n day={day}\n endHour={endHour}\n key={r.id}\n onBook={onBook}\n resource={r}\n startHour={startHour}\n timeZone={timeZone}\n />\n ))}\n </div>\n )\n}\n"],"names":["useTranslation","React","computeSlotStates","getDayKeyInTimezone","styles","useResourceAvailability","SLOT_STATE_KEYS","external","free","full","Lane","apiBase","day","endHour","onBook","resource","startHour","timeZone","t","_t","dayStart","Date","setHours","dayEnd","data","id","isoDay","dayAvail","days","find","d","date","slots","busy","capacityMode","quantity","requiredPools","shiftWindows","step","timeOff","div","className","lane","laneLabel","name","laneTrack","map","s","cls","state","slotOffShift","slotTimeOff","slotExternal","slotFull","slotFree","isFree","slotLabel","start","toLocaleTimeString","hour","minute","aria-label","laneCell","onClick","toISOString","onKeyDown","e","key","preventDefault","role","tabIndex","title","LaneTimelineView","resources","length","p","hint","hours","Array","from","_","i","lanes","laneHeader","h","laneTime","String","padStart","r"],"mappings":"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"}
|
|
@@ -846,6 +846,7 @@ export const CalendarView = ()=>{
|
|
|
846
846
|
capacityMode: availability.capacityMode,
|
|
847
847
|
dayEnd,
|
|
848
848
|
dayStart,
|
|
849
|
+
external: availability.external,
|
|
849
850
|
quantity: availability.quantity,
|
|
850
851
|
requiredPools: availability.requiredPools,
|
|
851
852
|
shiftWindows: dayAvail.shiftWindows,
|
|
@@ -907,6 +908,9 @@ export const CalendarView = ()=>{
|
|
|
907
908
|
} else if (slotInfo.state === 'time-off') {
|
|
908
909
|
slotClass = styles.slotTimeOff;
|
|
909
910
|
isNonInteractive = true;
|
|
911
|
+
} else if (slotInfo.state === 'external') {
|
|
912
|
+
slotClass = styles.slotExternal;
|
|
913
|
+
isNonInteractive = true;
|
|
910
914
|
} else if (slotInfo.state === 'full') {
|
|
911
915
|
slotClass = styles.slotFull;
|
|
912
916
|
isNonInteractive = true;
|
|
@@ -981,6 +985,7 @@ export const CalendarView = ()=>{
|
|
|
981
985
|
capacityMode: availability.capacityMode,
|
|
982
986
|
dayEnd,
|
|
983
987
|
dayStart,
|
|
988
|
+
external: availability.external,
|
|
984
989
|
quantity: availability.quantity,
|
|
985
990
|
requiredPools: availability.requiredPools,
|
|
986
991
|
shiftWindows: dayAvail.shiftWindows,
|
|
@@ -1013,6 +1018,9 @@ export const CalendarView = ()=>{
|
|
|
1013
1018
|
} else if (slotInfo.state === 'time-off') {
|
|
1014
1019
|
slotClass = styles.slotTimeOff;
|
|
1015
1020
|
isNonInteractive = true;
|
|
1021
|
+
} else if (slotInfo.state === 'external') {
|
|
1022
|
+
slotClass = styles.slotExternal;
|
|
1023
|
+
isNonInteractive = true;
|
|
1016
1024
|
} else if (slotInfo.state === 'full') {
|
|
1017
1025
|
slotClass = styles.slotFull;
|
|
1018
1026
|
isNonInteractive = true;
|