payload-reserve 1.4.0 → 1.5.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.
Files changed (101) hide show
  1. package/README.md +185 -4
  2. package/dist/collections/Reservations.js +47 -2
  3. package/dist/collections/Reservations.js.map +1 -1
  4. package/dist/collections/Resources.d.ts +16 -0
  5. package/dist/collections/Resources.js +35 -10
  6. package/dist/collections/Resources.js.map +1 -1
  7. package/dist/collections/Schedules.js +34 -0
  8. package/dist/collections/Schedules.js.map +1 -1
  9. package/dist/collections/Services.js +34 -1
  10. package/dist/collections/Services.js.map +1 -1
  11. package/dist/components/AvailabilityTimeField/AvailabilityTimeField.module.css +7 -0
  12. package/dist/components/AvailabilityTimeField/index.d.ts +2 -0
  13. package/dist/components/AvailabilityTimeField/index.js +109 -0
  14. package/dist/components/AvailabilityTimeField/index.js.map +1 -0
  15. package/dist/components/CalendarView/CalendarView.module.css +114 -0
  16. package/dist/components/CalendarView/LaneTimelineView.d.ts +12 -0
  17. package/dist/components/CalendarView/LaneTimelineView.js +116 -0
  18. package/dist/components/CalendarView/LaneTimelineView.js.map +1 -0
  19. package/dist/components/CalendarView/index.js +224 -22
  20. package/dist/components/CalendarView/index.js.map +1 -1
  21. package/dist/components/CalendarView/useResourceAvailability.d.ts +9 -0
  22. package/dist/components/CalendarView/useResourceAvailability.js +40 -0
  23. package/dist/components/CalendarView/useResourceAvailability.js.map +1 -0
  24. package/dist/defaults.d.ts +3 -0
  25. package/dist/defaults.js +53 -0
  26. package/dist/defaults.js.map +1 -1
  27. package/dist/endpoints/cancelBooking.js +34 -21
  28. package/dist/endpoints/cancelBooking.js.map +1 -1
  29. package/dist/endpoints/checkAvailability.js +16 -1
  30. package/dist/endpoints/checkAvailability.js.map +1 -1
  31. package/dist/endpoints/createBooking.js +4 -1
  32. package/dist/endpoints/createBooking.js.map +1 -1
  33. package/dist/endpoints/customerSearch.js +24 -5
  34. package/dist/endpoints/customerSearch.js.map +1 -1
  35. package/dist/endpoints/getSlots.js +16 -1
  36. package/dist/endpoints/getSlots.js.map +1 -1
  37. package/dist/endpoints/resourceAvailability.d.ts +43 -0
  38. package/dist/endpoints/resourceAvailability.js +214 -0
  39. package/dist/endpoints/resourceAvailability.js.map +1 -0
  40. package/dist/exports/client.d.ts +1 -0
  41. package/dist/exports/client.js +1 -0
  42. package/dist/exports/client.js.map +1 -1
  43. package/dist/hooks/reservations/calculateEndTime.js +21 -1
  44. package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
  45. package/dist/hooks/reservations/expandRequiredResources.d.ts +9 -0
  46. package/dist/hooks/reservations/expandRequiredResources.js +81 -0
  47. package/dist/hooks/reservations/expandRequiredResources.js.map +1 -0
  48. package/dist/hooks/reservations/validateGuestBooking.d.ts +3 -0
  49. package/dist/hooks/reservations/validateGuestBooking.js +93 -0
  50. package/dist/hooks/reservations/validateGuestBooking.js.map +1 -0
  51. package/dist/hooks/reservations/validateStatusTransition.js +4 -2
  52. package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
  53. package/dist/hooks/users/provisionStaffResource.d.ts +15 -0
  54. package/dist/hooks/users/provisionStaffResource.js +88 -0
  55. package/dist/hooks/users/provisionStaffResource.js.map +1 -0
  56. package/dist/index.d.ts +5 -1
  57. package/dist/index.js +4 -0
  58. package/dist/index.js.map +1 -1
  59. package/dist/plugin.js +19 -2
  60. package/dist/plugin.js.map +1 -1
  61. package/dist/services/AvailabilityService.d.ts +2 -1
  62. package/dist/services/AvailabilityService.js +86 -60
  63. package/dist/services/AvailabilityService.js.map +1 -1
  64. package/dist/translations/ar.json +156 -0
  65. package/dist/translations/de.json +156 -0
  66. package/dist/translations/en.json +32 -1
  67. package/dist/translations/es.json +156 -0
  68. package/dist/translations/fa.json +156 -0
  69. package/dist/translations/fr.json +156 -0
  70. package/dist/translations/hi.json +156 -0
  71. package/dist/translations/id.json +156 -0
  72. package/dist/translations/index.js +44 -0
  73. package/dist/translations/index.js.map +1 -1
  74. package/dist/translations/pl.json +156 -0
  75. package/dist/translations/ru.json +156 -0
  76. package/dist/translations/tr.json +156 -0
  77. package/dist/translations/zh.json +156 -0
  78. package/dist/types.d.ts +46 -0
  79. package/dist/types.js.map +1 -1
  80. package/dist/utilities/computeSlotStates.d.ts +39 -0
  81. package/dist/utilities/computeSlotStates.js +49 -0
  82. package/dist/utilities/computeSlotStates.js.map +1 -0
  83. package/dist/utilities/guestBooking.d.ts +10 -0
  84. package/dist/utilities/guestBooking.js +16 -0
  85. package/dist/utilities/guestBooking.js.map +1 -0
  86. package/dist/utilities/resolveRequiredResources.d.ts +8 -0
  87. package/dist/utilities/resolveRequiredResources.js +27 -0
  88. package/dist/utilities/resolveRequiredResources.js.map +1 -0
  89. package/dist/utilities/scheduleUtils.d.ts +3 -0
  90. package/dist/utilities/scheduleUtils.js +5 -3
  91. package/dist/utilities/scheduleUtils.js.map +1 -1
  92. package/dist/utilities/selectOptions.d.ts +8 -0
  93. package/dist/utilities/selectOptions.js +11 -0
  94. package/dist/utilities/selectOptions.js.map +1 -0
  95. package/dist/utilities/slotUtils.d.ts +19 -0
  96. package/dist/utilities/slotUtils.js +28 -0
  97. package/dist/utilities/slotUtils.js.map +1 -1
  98. package/dist/utilities/userRoles.d.ts +20 -0
  99. package/dist/utilities/userRoles.js +32 -0
  100. package/dist/utilities/userRoles.js.map +1 -0
  101. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/collections/Schedules.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { makeScheduleOwnerAccess } from '../utilities/ownerAccess.js'\n\nconst TIME_REGEX = /^(?:[01]\\d|2[0-3]):[0-5]\\d$/\n\nconst validateTime = (value: null | string | undefined): string | true => {\n if (!value) { return true } // required handles emptiness\n return TIME_REGEX.test(value) || 'Invalid time format. Use HH:mm (e.g., 09:00, 17:30)'\n}\n\nexport function createSchedulesCollection(\n config: ResolvedReservationPluginConfig,\n): CollectionConfig {\n const rom = config.resourceOwnerMode\n const access =\n config.access.schedules ?? (rom ? makeScheduleOwnerAccess(rom) : {})\n\n return {\n slug: config.slugs.schedules,\n access,\n admin: {\n group: config.adminGroup,\n useAsTitle: 'name',\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldName'),\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: 'scheduleType',\n type: 'select',\n defaultValue: 'recurring',\n label: ({ t }) => (t as PluginT)('reservation:fieldScheduleType'),\n options: [\n {\n label: ({ t }) => (t as PluginT)('reservation:scheduleTypeRecurring'),\n value: 'recurring',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:scheduleTypeManual'),\n value: 'manual',\n },\n ],\n },\n {\n name: 'recurringSlots',\n type: 'array',\n admin: {\n condition: (_, siblingData) => siblingData?.scheduleType === 'recurring',\n },\n fields: [\n {\n name: 'day',\n type: 'select',\n label: ({ t }) => (t as PluginT)('reservation:fieldDay'),\n options: [\n { label: ({ t }) => (t as PluginT)('reservation:dayMonday'), value: 'mon' },\n { label: ({ t }) => (t as PluginT)('reservation:dayTuesday'), value: 'tue' },\n { label: ({ t }) => (t as PluginT)('reservation:dayWednesday'), value: 'wed' },\n { label: ({ t }) => (t as PluginT)('reservation:dayThursday'), value: 'thu' },\n { label: ({ t }) => (t as PluginT)('reservation:dayFriday'), value: 'fri' },\n { label: ({ t }) => (t as PluginT)('reservation:daySaturday'), value: 'sat' },\n { label: ({ t }) => (t as PluginT)('reservation:daySunday'), value: 'sun' },\n ],\n required: true,\n },\n {\n name: 'startTime',\n type: 'text',\n admin: {\n placeholder: '09:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n {\n name: 'endTime',\n type: 'text',\n admin: {\n placeholder: '17:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldRecurringSlots'),\n },\n {\n name: 'manualSlots',\n type: 'array',\n admin: {\n condition: (_, siblingData) => siblingData?.scheduleType === 'manual',\n },\n fields: [\n {\n name: 'date',\n type: 'date',\n admin: {\n date: {\n pickerAppearance: 'dayOnly',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldDate'),\n required: true,\n },\n {\n name: 'startTime',\n type: 'text',\n admin: {\n placeholder: '09:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n {\n name: 'endTime',\n type: 'text',\n admin: {\n placeholder: '17:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldManualSlots'),\n },\n {\n name: 'exceptions',\n type: 'array',\n fields: [\n {\n name: 'date',\n type: 'date',\n admin: {\n date: {\n pickerAppearance: 'dayOnly',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldDate'),\n required: true,\n },\n {\n name: 'reason',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldReason'),\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldExceptions'),\n },\n {\n name: 'active',\n type: 'checkbox',\n admin: {\n position: 'sidebar',\n },\n defaultValue: true,\n label: ({ t }) => (t as PluginT)('reservation:fieldActive'),\n },\n ],\n hooks: {\n beforeValidate: [\n ({ data }) => {\n const slots = (data?.recurringSlots as Array<{ endTime?: string; startTime?: string }>) ?? []\n for (const slot of slots) {\n if (slot.startTime && slot.endTime && slot.startTime >= slot.endTime) {\n throw new ValidationError({\n errors: [{ message: 'endTime must be after startTime', path: 'recurringSlots' }],\n })\n }\n }\n const manual = (data?.manualSlots as Array<{ endTime?: string; startTime?: string }>) ?? []\n for (const slot of manual) {\n if (slot.startTime && slot.endTime && slot.startTime >= slot.endTime) {\n throw new ValidationError({\n errors: [{ message: 'endTime must be after startTime', path: 'manualSlots' }],\n })\n }\n }\n return data\n },\n ],\n },\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionSchedules'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionSchedules'),\n },\n }\n}\n"],"names":["ValidationError","makeScheduleOwnerAccess","TIME_REGEX","validateTime","value","test","createSchedulesCollection","config","rom","resourceOwnerMode","access","schedules","slug","slugs","admin","group","adminGroup","useAsTitle","fields","name","type","label","t","required","relationTo","resources","defaultValue","options","condition","_","siblingData","scheduleType","placeholder","validate","date","pickerAppearance","position","hooks","beforeValidate","data","slots","recurringSlots","slot","startTime","endTime","errors","message","path","manual","manualSlots","labels","plural","singular"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/collections/Schedules.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug } from 'payload'\n\nimport { ValidationError } from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { makeScheduleOwnerAccess } from '../utilities/ownerAccess.js'\nimport { buildSelectOptions } from '../utilities/selectOptions.js'\n\nconst TIME_REGEX = /^(?:[01]\\d|2[0-3]):[0-5]\\d$/\n\nconst validateTime = (value: null | string | undefined): string | true => {\n if (!value) { return true } // required handles emptiness\n return TIME_REGEX.test(value) || 'Invalid time format. Use HH:mm (e.g., 09:00, 17:30)'\n}\n\nexport function createSchedulesCollection(\n config: ResolvedReservationPluginConfig,\n): CollectionConfig {\n const rom = config.resourceOwnerMode\n const access =\n config.access.schedules ?? (rom ? makeScheduleOwnerAccess(rom) : {})\n\n return {\n slug: config.slugs.schedules,\n access,\n admin: {\n group: config.adminGroup,\n useAsTitle: 'name',\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldName'),\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: 'scheduleType',\n type: 'select',\n defaultValue: 'recurring',\n label: ({ t }) => (t as PluginT)('reservation:fieldScheduleType'),\n options: [\n {\n label: ({ t }) => (t as PluginT)('reservation:scheduleTypeRecurring'),\n value: 'recurring',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:scheduleTypeManual'),\n value: 'manual',\n },\n ],\n },\n {\n name: 'recurringSlots',\n type: 'array',\n admin: {\n condition: (_, siblingData) => siblingData?.scheduleType === 'recurring',\n },\n fields: [\n {\n name: 'day',\n type: 'select',\n label: ({ t }) => (t as PluginT)('reservation:fieldDay'),\n options: [\n { label: ({ t }) => (t as PluginT)('reservation:dayMonday'), value: 'mon' },\n { label: ({ t }) => (t as PluginT)('reservation:dayTuesday'), value: 'tue' },\n { label: ({ t }) => (t as PluginT)('reservation:dayWednesday'), value: 'wed' },\n { label: ({ t }) => (t as PluginT)('reservation:dayThursday'), value: 'thu' },\n { label: ({ t }) => (t as PluginT)('reservation:dayFriday'), value: 'fri' },\n { label: ({ t }) => (t as PluginT)('reservation:daySaturday'), value: 'sat' },\n { label: ({ t }) => (t as PluginT)('reservation:daySunday'), value: 'sun' },\n ],\n required: true,\n },\n {\n name: 'startTime',\n type: 'text',\n admin: {\n placeholder: '09:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n {\n name: 'endTime',\n type: 'text',\n admin: {\n placeholder: '17:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldRecurringSlots'),\n },\n {\n name: 'manualSlots',\n type: 'array',\n admin: {\n condition: (_, siblingData) => siblingData?.scheduleType === 'manual',\n },\n fields: [\n {\n name: 'date',\n type: 'date',\n admin: {\n date: {\n pickerAppearance: 'dayOnly',\n },\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldDate'),\n required: true,\n },\n {\n name: 'startTime',\n type: 'text',\n admin: {\n placeholder: '09:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldStartTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n {\n name: 'endTime',\n type: 'text',\n admin: {\n placeholder: '17:00',\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndTimeHHmm'),\n required: true,\n validate: validateTime,\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldManualSlots'),\n },\n {\n name: 'exceptions',\n type: 'array',\n fields: [\n {\n name: 'date',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayOnly' } },\n label: ({ t }) => (t as PluginT)('reservation:fieldDate'),\n required: true,\n },\n {\n name: 'endDate',\n type: 'date',\n admin: { date: { pickerAppearance: 'dayOnly' } },\n label: ({ t }) => (t as PluginT)('reservation:fieldEndDate'),\n },\n {\n name: 'type',\n type: 'select',\n label: ({ t }) => (t as PluginT)('reservation:fieldLeaveType'),\n options: buildSelectOptions(config.leaveTypes),\n },\n {\n name: 'reason',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldReason'),\n },\n ],\n label: ({ t }) => (t as PluginT)('reservation:fieldExceptions'),\n },\n {\n name: 'active',\n type: 'checkbox',\n admin: {\n position: 'sidebar',\n },\n defaultValue: true,\n label: ({ t }) => (t as PluginT)('reservation:fieldActive'),\n },\n ],\n hooks: {\n beforeValidate: [\n ({ data }) => {\n const slots = (data?.recurringSlots as Array<{ endTime?: string; startTime?: string }>) ?? []\n for (const slot of slots) {\n if (slot.startTime && slot.endTime && slot.startTime >= slot.endTime) {\n throw new ValidationError({\n errors: [{ message: 'endTime must be after startTime', path: 'recurringSlots' }],\n })\n }\n }\n const manual = (data?.manualSlots as Array<{ endTime?: string; startTime?: string }>) ?? []\n for (const slot of manual) {\n if (slot.startTime && slot.endTime && slot.startTime >= slot.endTime) {\n throw new ValidationError({\n errors: [{ message: 'endTime must be after startTime', path: 'manualSlots' }],\n })\n }\n }\n const exceptions = (data?.exceptions as Array<{ date?: string; endDate?: string }>) ?? []\n for (const exc of exceptions) {\n if (exc.date && exc.endDate) {\n const start = new Date(exc.date).toISOString().split('T')[0]\n const end = new Date(exc.endDate).toISOString().split('T')[0]\n if (end < start) {\n throw new ValidationError({\n errors: [{ message: 'exception endDate must be on or after date', path: 'exceptions' }],\n })\n }\n }\n }\n return data\n },\n ],\n },\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionSchedules'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionSchedules'),\n },\n }\n}\n"],"names":["ValidationError","makeScheduleOwnerAccess","buildSelectOptions","TIME_REGEX","validateTime","value","test","createSchedulesCollection","config","rom","resourceOwnerMode","access","schedules","slug","slugs","admin","group","adminGroup","useAsTitle","fields","name","type","label","t","required","relationTo","resources","defaultValue","options","condition","_","siblingData","scheduleType","placeholder","validate","date","pickerAppearance","leaveTypes","position","hooks","beforeValidate","data","slots","recurringSlots","slot","startTime","endTime","errors","message","path","manual","manualSlots","exceptions","exc","endDate","start","Date","toISOString","split","end","labels","plural","singular"],"mappings":"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"}
@@ -20,7 +20,7 @@ export function createServicesCollection(config) {
20
20
  }
21
21
  ]
22
22
  },
23
- label: 'Owner',
23
+ label: ({ t })=>t('reservation:fieldOwner'),
24
24
  relationTo: config.slugs.customers,
25
25
  required: true
26
26
  } : null;
@@ -109,6 +109,39 @@ export function createServicesCollection(config) {
109
109
  label: ({ t })=>t('reservation:fieldBufferTimeAfter'),
110
110
  min: 0
111
111
  },
112
+ {
113
+ name: 'requiredResources',
114
+ type: 'relationship',
115
+ admin: {
116
+ description: ({ t })=>t('reservation:fieldRequiredResourcesDesc')
117
+ },
118
+ hasMany: true,
119
+ label: ({ t })=>t('reservation:fieldRequiredResources'),
120
+ relationTo: config.slugs.resources
121
+ },
122
+ {
123
+ name: 'allowGuestBooking',
124
+ type: 'select',
125
+ admin: {
126
+ description: ({ t })=>t('reservation:fieldAllowGuestBookingDesc')
127
+ },
128
+ defaultValue: 'inherit',
129
+ label: ({ t })=>t('reservation:fieldAllowGuestBooking'),
130
+ options: [
131
+ {
132
+ label: ({ t })=>t('reservation:guestBookingInherit'),
133
+ value: 'inherit'
134
+ },
135
+ {
136
+ label: ({ t })=>t('reservation:guestBookingEnabled'),
137
+ value: 'enabled'
138
+ },
139
+ {
140
+ label: ({ t })=>t('reservation:guestBookingDisabled'),
141
+ value: 'disabled'
142
+ }
143
+ ]
144
+ },
112
145
  {
113
146
  name: 'active',
114
147
  type: 'checkbox',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/collections/Services.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug, Field } from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { makeServiceOwnerAccess } from '../utilities/ownerAccess.js'\n\nexport function createServicesCollection(config: ResolvedReservationPluginConfig): CollectionConfig {\n const rom = config.resourceOwnerMode\n const ownedServices = rom?.ownedServices ?? false\n const ownerField = rom?.ownerField ?? 'owner'\n\n // Owner field on Services (only when ownedServices: true)\n const ownerFieldDef: Field | null =\n rom && ownedServices\n ? {\n name: ownerField,\n type: 'relationship',\n admin: {\n position: 'sidebar',\n },\n hooks: {\n beforeChange: [\n ({ operation, req, value }) => {\n if (operation === 'create' && req.user) {return req.user.id}\n return value\n },\n ],\n },\n label: 'Owner',\n relationTo: config.slugs.customers as unknown as CollectionSlug,\n required: true,\n }\n : null\n\n // Determine access: app override → owner-mode auto-wired → unrestricted\n const access =\n config.access.services ??\n (rom && ownedServices ? makeServiceOwnerAccess(rom, ownerField) : {})\n\n return {\n slug: config.slugs.services,\n access,\n admin: {\n group: config.adminGroup,\n useAsTitle: 'name',\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldName'),\n ...(config.localized ? { localized: true } : {}),\n maxLength: 200,\n required: true,\n },\n {\n name: 'image',\n type: 'upload',\n label: ({ t }) => (t as PluginT)('reservation:fieldImage'),\n relationTo: config.slugs.media as unknown as CollectionSlug,\n },\n {\n name: 'description',\n type: 'textarea',\n label: ({ t }) => (t as PluginT)('reservation:fieldDescription'),\n ...(config.localized ? { localized: true } : {}),\n },\n {\n name: 'duration',\n type: 'number',\n label: ({ t }) => (t as PluginT)('reservation:fieldDurationMinutes'),\n min: 1,\n required: true,\n },\n {\n name: 'durationType',\n type: 'select',\n defaultValue: 'fixed',\n label: ({ t }) => (t as PluginT)('reservation:fieldDurationType'),\n options: [\n {\n label: ({ t }) => (t as PluginT)('reservation:durationFixed'),\n value: 'fixed',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:durationFlexible'),\n value: 'flexible',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:durationFullDay'),\n value: 'full-day',\n },\n ],\n required: true,\n },\n {\n name: 'price',\n type: 'number',\n admin: {\n step: 0.01,\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldPrice'),\n min: 0,\n },\n {\n name: 'bufferTimeBefore',\n type: 'number',\n defaultValue: 0,\n label: ({ t }) => (t as PluginT)('reservation:fieldBufferTimeBefore'),\n min: 0,\n },\n {\n name: 'bufferTimeAfter',\n type: 'number',\n defaultValue: 0,\n label: ({ t }) => (t as PluginT)('reservation:fieldBufferTimeAfter'),\n min: 0,\n },\n {\n name: 'active',\n type: 'checkbox',\n admin: {\n position: 'sidebar',\n },\n defaultValue: true,\n label: ({ t }) => (t as PluginT)('reservation:fieldActive'),\n },\n ...(ownerFieldDef ? [ownerFieldDef] : []),\n ],\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionServices'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionServices'),\n },\n }\n}\n"],"names":["makeServiceOwnerAccess","createServicesCollection","config","rom","resourceOwnerMode","ownedServices","ownerField","ownerFieldDef","name","type","admin","position","hooks","beforeChange","operation","req","value","user","id","label","relationTo","slugs","customers","required","access","services","slug","group","adminGroup","useAsTitle","fields","t","localized","maxLength","media","min","defaultValue","options","step","labels","plural","singular"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/collections/Services.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug, Field } from 'payload'\n\nimport type { PluginT } from '../translations/index.js'\nimport type { ResolvedReservationPluginConfig } from '../types.js'\n\nimport { makeServiceOwnerAccess } from '../utilities/ownerAccess.js'\n\nexport function createServicesCollection(config: ResolvedReservationPluginConfig): CollectionConfig {\n const rom = config.resourceOwnerMode\n const ownedServices = rom?.ownedServices ?? false\n const ownerField = rom?.ownerField ?? 'owner'\n\n // Owner field on Services (only when ownedServices: true)\n const ownerFieldDef: Field | null =\n rom && ownedServices\n ? {\n name: ownerField,\n type: 'relationship',\n admin: {\n position: 'sidebar',\n },\n hooks: {\n beforeChange: [\n ({ operation, req, value }) => {\n if (operation === 'create' && req.user) {return req.user.id}\n return value\n },\n ],\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldOwner'),\n relationTo: config.slugs.customers as unknown as CollectionSlug,\n required: true,\n }\n : null\n\n // Determine access: app override → owner-mode auto-wired → unrestricted\n const access =\n config.access.services ??\n (rom && ownedServices ? makeServiceOwnerAccess(rom, ownerField) : {})\n\n return {\n slug: config.slugs.services,\n access,\n admin: {\n group: config.adminGroup,\n useAsTitle: 'name',\n },\n fields: [\n {\n name: 'name',\n type: 'text',\n label: ({ t }) => (t as PluginT)('reservation:fieldName'),\n ...(config.localized ? { localized: true } : {}),\n maxLength: 200,\n required: true,\n },\n {\n name: 'image',\n type: 'upload',\n label: ({ t }) => (t as PluginT)('reservation:fieldImage'),\n relationTo: config.slugs.media as unknown as CollectionSlug,\n },\n {\n name: 'description',\n type: 'textarea',\n label: ({ t }) => (t as PluginT)('reservation:fieldDescription'),\n ...(config.localized ? { localized: true } : {}),\n },\n {\n name: 'duration',\n type: 'number',\n label: ({ t }) => (t as PluginT)('reservation:fieldDurationMinutes'),\n min: 1,\n required: true,\n },\n {\n name: 'durationType',\n type: 'select',\n defaultValue: 'fixed',\n label: ({ t }) => (t as PluginT)('reservation:fieldDurationType'),\n options: [\n {\n label: ({ t }) => (t as PluginT)('reservation:durationFixed'),\n value: 'fixed',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:durationFlexible'),\n value: 'flexible',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:durationFullDay'),\n value: 'full-day',\n },\n ],\n required: true,\n },\n {\n name: 'price',\n type: 'number',\n admin: {\n step: 0.01,\n },\n label: ({ t }) => (t as PluginT)('reservation:fieldPrice'),\n min: 0,\n },\n {\n name: 'bufferTimeBefore',\n type: 'number',\n defaultValue: 0,\n label: ({ t }) => (t as PluginT)('reservation:fieldBufferTimeBefore'),\n min: 0,\n },\n {\n name: 'bufferTimeAfter',\n type: 'number',\n defaultValue: 0,\n label: ({ t }) => (t as PluginT)('reservation:fieldBufferTimeAfter'),\n min: 0,\n },\n {\n name: 'requiredResources',\n type: 'relationship',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldRequiredResourcesDesc'),\n },\n hasMany: true,\n label: ({ t }) => (t as PluginT)('reservation:fieldRequiredResources'),\n relationTo: config.slugs.resources as unknown as CollectionSlug,\n },\n {\n name: 'allowGuestBooking',\n type: 'select',\n admin: {\n description: ({ t }) => (t as PluginT)('reservation:fieldAllowGuestBookingDesc'),\n },\n defaultValue: 'inherit',\n label: ({ t }) => (t as PluginT)('reservation:fieldAllowGuestBooking'),\n options: [\n {\n label: ({ t }) => (t as PluginT)('reservation:guestBookingInherit'),\n value: 'inherit',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:guestBookingEnabled'),\n value: 'enabled',\n },\n {\n label: ({ t }) => (t as PluginT)('reservation:guestBookingDisabled'),\n value: 'disabled',\n },\n ],\n },\n {\n name: 'active',\n type: 'checkbox',\n admin: {\n position: 'sidebar',\n },\n defaultValue: true,\n label: ({ t }) => (t as PluginT)('reservation:fieldActive'),\n },\n ...(ownerFieldDef ? [ownerFieldDef] : []),\n ],\n labels: {\n plural: ({ t }) => (t as PluginT)('reservation:collectionServices'),\n singular: ({ t }) => (t as PluginT)('reservation:collectionServices'),\n },\n }\n}\n"],"names":["makeServiceOwnerAccess","createServicesCollection","config","rom","resourceOwnerMode","ownedServices","ownerField","ownerFieldDef","name","type","admin","position","hooks","beforeChange","operation","req","value","user","id","label","t","relationTo","slugs","customers","required","access","services","slug","group","adminGroup","useAsTitle","fields","localized","maxLength","media","min","defaultValue","options","step","description","hasMany","resources","labels","plural","singular"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ .wrapper { margin-bottom: 1rem; }
2
+ .slots { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
3
+ .slot { background: var(--theme-elevation-100); border: 1px solid var(--theme-elevation-200); border-radius: 4px; cursor: pointer; font-size: 13px; padding: 5px 9px; }
4
+ .slot:hover { background: var(--theme-elevation-150); }
5
+ .selected { background: var(--theme-success-500); border-color: var(--theme-success-500); color: #fff; }
6
+ .day, .fallback { background: var(--theme-input-bg); border: 1px solid var(--theme-elevation-200); border-radius: 4px; padding: 6px 8px; }
7
+ .hint { color: var(--theme-elevation-400); font-size: 12px; margin-top: 6px; }
@@ -0,0 +1,2 @@
1
+ import type { DateFieldClientComponent } from 'payload';
2
+ export declare const AvailabilityTimeField: DateFieldClientComponent;
@@ -0,0 +1,109 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { FieldLabel, useConfig, useField, useFormFields, useTranslation } from '@payloadcms/ui';
4
+ import React, { useEffect, useState } from 'react';
5
+ import styles from './AvailabilityTimeField.module.css';
6
+ const extractId = (v)=>typeof v === 'object' && v !== null ? String(v.id ?? '') : String(v ?? '');
7
+ const fmt = (iso)=>new Date(iso).toLocaleString([], {
8
+ day: 'numeric',
9
+ hour: '2-digit',
10
+ minute: '2-digit',
11
+ month: 'short'
12
+ });
13
+ const pad = (n)=>String(n).padStart(2, '0');
14
+ /** Local YYYY-MM-DD for the date picker (not UTC). */ const toLocalDay = (d)=>`${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
15
+ /** Local YYYY-MM-DDTHH:mm for a `datetime-local` input (browsers treat it as local). */ const toLocalInput = (d)=>`${toLocalDay(d)}T${pad(d.getHours())}:${pad(d.getMinutes())}`;
16
+ export const AvailabilityTimeField = ({ field, path: pathProp })=>{
17
+ const fieldPath = pathProp ?? field?.name ?? 'startTime';
18
+ const { config } = useConfig();
19
+ const { t: _t } = useTranslation();
20
+ const t = _t;
21
+ const { setValue, value } = useField({
22
+ path: fieldPath
23
+ });
24
+ const service = useFormFields(([fields])=>fields?.service?.value);
25
+ const resource = useFormFields(([fields])=>fields?.resource?.value);
26
+ const [slots, setSlots] = useState([]);
27
+ const [loading, setLoading] = useState(false);
28
+ const [day, setDay] = useState(()=>value ? toLocalDay(new Date(value)) : toLocalDay(new Date()));
29
+ const serviceId = extractId(service);
30
+ const resourceId = extractId(resource);
31
+ const apiBase = `${config.serverURL ?? ''}${config.routes.api}`;
32
+ useEffect(()=>{
33
+ if (!serviceId || !resourceId || !day) {
34
+ setSlots([]);
35
+ return;
36
+ }
37
+ const load = async ()=>{
38
+ setLoading(true);
39
+ try {
40
+ const res = await fetch(`${apiBase}/reserve/slots?resource=${resourceId}&service=${serviceId}&date=${day}`);
41
+ const json = await res.json();
42
+ setSlots(json.slots ?? []);
43
+ } catch {
44
+ setSlots([]);
45
+ } finally{
46
+ setLoading(false);
47
+ }
48
+ };
49
+ void load();
50
+ }, [
51
+ serviceId,
52
+ resourceId,
53
+ day,
54
+ apiBase
55
+ ]);
56
+ if (!serviceId || !resourceId) {
57
+ return /*#__PURE__*/ _jsxs("div", {
58
+ className: styles.wrapper,
59
+ children: [
60
+ /*#__PURE__*/ _jsx(FieldLabel, {
61
+ label: field?.label,
62
+ path: fieldPath
63
+ }),
64
+ /*#__PURE__*/ _jsx("input", {
65
+ className: styles.fallback,
66
+ onChange: (e)=>setValue(e.target.value ? new Date(e.target.value).toISOString() : ''),
67
+ type: "datetime-local",
68
+ value: value ? toLocalInput(new Date(value)) : ''
69
+ }),
70
+ /*#__PURE__*/ _jsx("p", {
71
+ className: styles.hint,
72
+ children: t('reservation:pickPrompt')
73
+ })
74
+ ]
75
+ });
76
+ }
77
+ return /*#__PURE__*/ _jsxs("div", {
78
+ className: styles.wrapper,
79
+ children: [
80
+ /*#__PURE__*/ _jsx(FieldLabel, {
81
+ label: field?.label,
82
+ path: fieldPath
83
+ }),
84
+ /*#__PURE__*/ _jsx("input", {
85
+ className: styles.day,
86
+ onChange: (e)=>setDay(e.target.value),
87
+ type: "date",
88
+ value: day
89
+ }),
90
+ loading ? /*#__PURE__*/ _jsx("p", {
91
+ className: styles.hint,
92
+ children: t('reservation:pickLoading')
93
+ }) : slots.length === 0 ? /*#__PURE__*/ _jsx("p", {
94
+ className: styles.hint,
95
+ children: t('reservation:pickNone')
96
+ }) : /*#__PURE__*/ _jsx("div", {
97
+ className: styles.slots,
98
+ children: slots.map((s)=>/*#__PURE__*/ _jsx("button", {
99
+ className: value === s.start ? `${styles.slot} ${styles.selected}` : styles.slot,
100
+ onClick: ()=>setValue(s.start),
101
+ type: "button",
102
+ children: fmt(s.start)
103
+ }, s.start))
104
+ })
105
+ ]
106
+ });
107
+ };
108
+
109
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/AvailabilityTimeField/index.tsx"],"sourcesContent":["'use client'\nimport type { DateFieldClientComponent } from 'payload'\n\nimport { FieldLabel, useConfig, useField, useFormFields, useTranslation } from '@payloadcms/ui'\nimport React, { useEffect, useState } from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\n\nimport styles from './AvailabilityTimeField.module.css'\n\ntype Slot = { end: string; start: string }\n\nconst extractId = (v: unknown): string =>\n typeof v === 'object' && v !== null ? String((v as { id?: unknown }).id ?? '') : String(v ?? '')\n\nconst fmt = (iso: string) =>\n new Date(iso).toLocaleString([], {\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n month: 'short',\n })\n\nconst pad = (n: number) => String(n).padStart(2, '0')\n/** Local YYYY-MM-DD for the date picker (not UTC). */\nconst toLocalDay = (d: Date) => `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`\n/** Local YYYY-MM-DDTHH:mm for a `datetime-local` input (browsers treat it as local). */\nconst toLocalInput = (d: Date) => `${toLocalDay(d)}T${pad(d.getHours())}:${pad(d.getMinutes())}`\n\nexport const AvailabilityTimeField: DateFieldClientComponent = ({ field, path: pathProp }) => {\n const fieldPath = pathProp ?? field?.name ?? 'startTime'\n const { config } = useConfig()\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n const { setValue, value } = useField<string>({ path: fieldPath })\n\n const service = useFormFields(([fields]) => fields?.service?.value)\n const resource = useFormFields(([fields]) => fields?.resource?.value)\n\n const [slots, setSlots] = useState<Slot[]>([])\n const [loading, setLoading] = useState(false)\n const [day, setDay] = useState<string>(() =>\n value ? toLocalDay(new Date(value)) : toLocalDay(new Date()),\n )\n\n const serviceId = extractId(service)\n const resourceId = extractId(resource)\n const apiBase = `${config.serverURL ?? ''}${config.routes.api}`\n\n useEffect(() => {\n if (!serviceId || !resourceId || !day) {\n setSlots([])\n return\n }\n const load = async () => {\n setLoading(true)\n try {\n const res = await fetch(\n `${apiBase}/reserve/slots?resource=${resourceId}&service=${serviceId}&date=${day}`,\n )\n const json = await res.json()\n setSlots((json.slots ?? []) as Slot[])\n } catch {\n setSlots([])\n } finally {\n setLoading(false)\n }\n }\n void load()\n }, [serviceId, resourceId, day, apiBase])\n\n if (!serviceId || !resourceId) {\n return (\n <div className={styles.wrapper}>\n <FieldLabel label={field?.label} path={fieldPath} />\n <input\n className={styles.fallback}\n onChange={(e) => setValue(e.target.value ? new Date(e.target.value).toISOString() : '')}\n type=\"datetime-local\"\n value={value ? toLocalInput(new Date(value)) : ''}\n />\n <p className={styles.hint}>{t('reservation:pickPrompt')}</p>\n </div>\n )\n }\n\n return (\n <div className={styles.wrapper}>\n <FieldLabel label={field?.label} path={fieldPath} />\n <input className={styles.day} onChange={(e) => setDay(e.target.value)} type=\"date\" value={day} />\n {loading ? (\n <p className={styles.hint}>{t('reservation:pickLoading')}</p>\n ) : slots.length === 0 ? (\n <p className={styles.hint}>{t('reservation:pickNone')}</p>\n ) : (\n <div className={styles.slots}>\n {slots.map((s) => (\n <button\n className={value === s.start ? `${styles.slot} ${styles.selected}` : styles.slot}\n key={s.start}\n onClick={() => setValue(s.start)}\n type=\"button\"\n >\n {fmt(s.start)}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n"],"names":["FieldLabel","useConfig","useField","useFormFields","useTranslation","React","useEffect","useState","styles","extractId","v","String","id","fmt","iso","Date","toLocaleString","day","hour","minute","month","pad","n","padStart","toLocalDay","d","getFullYear","getMonth","getDate","toLocalInput","getHours","getMinutes","AvailabilityTimeField","field","path","pathProp","fieldPath","name","config","t","_t","setValue","value","service","fields","resource","slots","setSlots","loading","setLoading","setDay","serviceId","resourceId","apiBase","serverURL","routes","api","load","res","fetch","json","div","className","wrapper","label","input","fallback","onChange","e","target","toISOString","type","p","hint","length","map","s","button","start","slot","selected","onClick"],"mappings":"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"}
@@ -487,3 +487,117 @@
487
487
  outline: 2px solid var(--theme-text);
488
488
  outline-offset: 2px;
489
489
  }
490
+
491
+ /* Availability shading (week/day views, resource selected) */
492
+ .slotOffShift {
493
+ background: var(--theme-elevation-50);
494
+ opacity: 0.5;
495
+ pointer-events: none;
496
+ }
497
+
498
+ .slotTimeOff {
499
+ background: repeating-linear-gradient(
500
+ 45deg,
501
+ var(--theme-elevation-50),
502
+ var(--theme-elevation-50) 6px,
503
+ var(--theme-elevation-100) 6px,
504
+ var(--theme-elevation-100) 12px
505
+ );
506
+ pointer-events: none;
507
+ }
508
+
509
+ .slotFull {
510
+ background: var(--theme-error-100);
511
+ cursor: not-allowed;
512
+ }
513
+
514
+ .slotFree {
515
+ cursor: pointer;
516
+ }
517
+
518
+ .capacityBadge {
519
+ font-size: 10px;
520
+ opacity: 0.7;
521
+ position: absolute;
522
+ right: 4px;
523
+ top: 2px;
524
+ }
525
+
526
+ .timeOffLabel {
527
+ font-size: 10px;
528
+ opacity: 0.7;
529
+ }
530
+
531
+ /* Lane timeline view */
532
+ .lanes {
533
+ display: flex;
534
+ flex-direction: column;
535
+ gap: 6px;
536
+ }
537
+
538
+ .lane {
539
+ align-items: stretch;
540
+ display: flex;
541
+ gap: 8px;
542
+ }
543
+
544
+ .laneLabel {
545
+ flex: 0 0 120px;
546
+ font-size: 13px;
547
+ font-weight: 600;
548
+ padding-top: 4px;
549
+ }
550
+
551
+ .laneTrack {
552
+ display: grid;
553
+ flex: 1;
554
+ grid-auto-columns: 1fr;
555
+ grid-auto-flow: column;
556
+ }
557
+
558
+ .laneCell {
559
+ border: 1px solid var(--theme-elevation-100);
560
+ min-height: 32px;
561
+ }
562
+
563
+ .laneHeader {
564
+ align-items: stretch;
565
+ display: flex;
566
+ gap: 8px;
567
+ }
568
+
569
+ .laneTime {
570
+ border-left: 1px solid var(--theme-elevation-100);
571
+ color: var(--theme-elevation-500);
572
+ font-size: 10px;
573
+ padding: 2px 0 2px 3px;
574
+ }
575
+
576
+ .hint {
577
+ color: var(--theme-elevation-400);
578
+ font-size: 12px;
579
+ }
580
+
581
+ /* Multi-resource item badges inside event blocks */
582
+ .eventItemExpanded {
583
+ overflow: visible;
584
+ white-space: normal;
585
+ }
586
+
587
+ .itemBadges {
588
+ display: flex;
589
+ flex-wrap: wrap;
590
+ gap: 3px;
591
+ margin-top: 2px;
592
+ }
593
+
594
+ .itemBadge {
595
+ /* Events have fixed light status backgrounds, so the badge must use
596
+ theme-independent colors — a subtle dark overlay + the event's own (dark)
597
+ text — to stay readable in both light and dark mode. */
598
+ background: rgba(0, 0, 0, 0.08);
599
+ border-radius: 3px;
600
+ color: inherit;
601
+ font-size: 9px;
602
+ padding: 1px 4px;
603
+ }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ type LaneResource = {
3
+ id: string;
4
+ name: string;
5
+ };
6
+ export declare function LaneTimelineView({ apiBase, day, onBook, resources, }: {
7
+ apiBase: string;
8
+ day: Date;
9
+ onBook: (resourceId: string, startIso: string) => void;
10
+ resources: LaneResource[];
11
+ }): React.JSX.Element;
12
+ export {};
@@ -0,0 +1,116 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useTranslation } from '@payloadcms/ui';
4
+ import React from 'react';
5
+ import { computeSlotStates } from '../../utilities/computeSlotStates.js';
6
+ import { localDayKey } from '../../utilities/slotUtils.js';
7
+ import styles from './CalendarView.module.css';
8
+ import { useResourceAvailability } from './useResourceAvailability.js';
9
+ const HOUR_START = 7;
10
+ const HOUR_END = 20;
11
+ const SLOT_STATE_KEYS = {
12
+ free: 'reservation:slotFree',
13
+ full: 'reservation:slotFull',
14
+ 'off-shift': 'reservation:slotOffShift',
15
+ 'time-off': 'reservation:slotTimeOff'
16
+ };
17
+ function Lane({ apiBase, day, onBook, resource }) {
18
+ const { t: _t } = useTranslation();
19
+ const t = _t;
20
+ const dayStart = new Date(day);
21
+ dayStart.setHours(HOUR_START, 0, 0, 0);
22
+ const dayEnd = new Date(day);
23
+ dayEnd.setHours(HOUR_END, 0, 0, 0);
24
+ const { data } = useResourceAvailability(apiBase, resource.id, dayStart, dayEnd);
25
+ const isoDay = localDayKey(day);
26
+ const dayAvail = data?.days.find((d)=>d.date === isoDay);
27
+ const slots = dayAvail ? computeSlotStates({
28
+ busy: data.busy,
29
+ capacityMode: data.capacityMode,
30
+ dayEnd,
31
+ dayStart,
32
+ quantity: data.quantity,
33
+ requiredPools: data.requiredPools,
34
+ shiftWindows: dayAvail.shiftWindows,
35
+ step: 60,
36
+ timeOff: dayAvail.timeOff
37
+ }) : [];
38
+ return /*#__PURE__*/ _jsxs("div", {
39
+ className: styles.lane,
40
+ children: [
41
+ /*#__PURE__*/ _jsx("div", {
42
+ className: styles.laneLabel,
43
+ children: resource.name
44
+ }),
45
+ /*#__PURE__*/ _jsx("div", {
46
+ className: styles.laneTrack,
47
+ children: slots.map((s)=>{
48
+ const cls = s.state === 'off-shift' ? styles.slotOffShift : s.state === 'time-off' ? styles.slotTimeOff : s.state === 'full' ? styles.slotFull : styles.slotFree;
49
+ const isFree = s.state === 'free';
50
+ const slotLabel = `${s.start.toLocaleTimeString()} — ${t(SLOT_STATE_KEYS[s.state])}`;
51
+ return isFree ? /*#__PURE__*/ _jsx("div", {
52
+ "aria-label": slotLabel,
53
+ className: `${styles.laneCell} ${cls}`,
54
+ onClick: ()=>onBook(resource.id, s.start.toISOString()),
55
+ onKeyDown: (e)=>{
56
+ if (e.key === 'Enter' || e.key === ' ') {
57
+ e.preventDefault();
58
+ onBook(resource.id, s.start.toISOString());
59
+ }
60
+ },
61
+ role: "button",
62
+ tabIndex: 0,
63
+ title: slotLabel
64
+ }, s.start.toISOString()) : /*#__PURE__*/ _jsx("div", {
65
+ className: `${styles.laneCell} ${cls}`,
66
+ title: slotLabel
67
+ }, s.start.toISOString());
68
+ })
69
+ })
70
+ ]
71
+ });
72
+ }
73
+ export function LaneTimelineView({ apiBase, day, onBook, resources }) {
74
+ const { t: _t } = useTranslation();
75
+ const t = _t;
76
+ if (resources.length === 0) {
77
+ return /*#__PURE__*/ _jsx("p", {
78
+ className: styles.hint,
79
+ children: t('reservation:laneNoResources')
80
+ });
81
+ }
82
+ const hours = Array.from({
83
+ length: HOUR_END - HOUR_START
84
+ }, (_, i)=>HOUR_START + i);
85
+ return /*#__PURE__*/ _jsxs("div", {
86
+ className: styles.lanes,
87
+ children: [
88
+ /*#__PURE__*/ _jsxs("div", {
89
+ className: styles.laneHeader,
90
+ children: [
91
+ /*#__PURE__*/ _jsx("div", {
92
+ className: styles.laneLabel
93
+ }),
94
+ /*#__PURE__*/ _jsx("div", {
95
+ className: styles.laneTrack,
96
+ children: hours.map((h)=>/*#__PURE__*/ _jsxs("div", {
97
+ className: styles.laneTime,
98
+ children: [
99
+ String(h).padStart(2, '0'),
100
+ ":00"
101
+ ]
102
+ }, h))
103
+ })
104
+ ]
105
+ }),
106
+ resources.map((r)=>/*#__PURE__*/ _jsx(Lane, {
107
+ apiBase: apiBase,
108
+ day: day,
109
+ onBook: onBook,
110
+ resource: r
111
+ }, r.id))
112
+ ]
113
+ });
114
+ }
115
+
116
+ //# sourceMappingURL=LaneTimelineView.js.map
@@ -0,0 +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 { localDayKey } from '../../utilities/slotUtils.js'\nimport styles from './CalendarView.module.css'\nimport { useResourceAvailability } from './useResourceAvailability.js'\n\ntype LaneResource = { id: string; name: string }\n\nconst HOUR_START = 7\nconst HOUR_END = 20\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 onBook,\n resource,\n}: {\n apiBase: string\n day: Date\n onBook: (resourceId: string, startIso: string) => void\n resource: LaneResource\n}) {\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n\n const dayStart = new Date(day)\n dayStart.setHours(HOUR_START, 0, 0, 0)\n const dayEnd = new Date(day)\n dayEnd.setHours(HOUR_END, 0, 0, 0)\n\n const { data } = useResourceAvailability(apiBase, resource.id, dayStart, dayEnd)\n const isoDay = localDayKey(day)\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 === 'full'\n ? styles.slotFull\n : styles.slotFree\n const isFree = s.state === 'free'\n const slotLabel = `${s.start.toLocaleTimeString()} — ${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 onBook,\n resources,\n}: {\n apiBase: string\n day: Date\n onBook: (resourceId: string, startIso: string) => void\n resources: LaneResource[]\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: HOUR_END - HOUR_START }, (_, i) => HOUR_START + 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 apiBase={apiBase} day={day} key={r.id} onBook={onBook} resource={r} />\n ))}\n </div>\n )\n}\n"],"names":["useTranslation","React","computeSlotStates","localDayKey","styles","useResourceAvailability","HOUR_START","HOUR_END","SLOT_STATE_KEYS","free","full","Lane","apiBase","day","onBook","resource","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","slotFull","slotFree","isFree","slotLabel","start","toLocaleTimeString","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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"}