payload-reserve 1.4.0 → 1.6.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 (111) 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/AvailabilityOverview/index.js +29 -10
  12. package/dist/components/AvailabilityOverview/index.js.map +1 -1
  13. package/dist/components/AvailabilityTimeField/AvailabilityTimeField.module.css +7 -0
  14. package/dist/components/AvailabilityTimeField/index.d.ts +2 -0
  15. package/dist/components/AvailabilityTimeField/index.js +109 -0
  16. package/dist/components/AvailabilityTimeField/index.js.map +1 -0
  17. package/dist/components/CalendarView/CalendarView.module.css +114 -0
  18. package/dist/components/CalendarView/LaneTimelineView.d.ts +12 -0
  19. package/dist/components/CalendarView/LaneTimelineView.js +116 -0
  20. package/dist/components/CalendarView/LaneTimelineView.js.map +1 -0
  21. package/dist/components/CalendarView/index.js +244 -31
  22. package/dist/components/CalendarView/index.js.map +1 -1
  23. package/dist/components/CalendarView/useResourceAvailability.d.ts +9 -0
  24. package/dist/components/CalendarView/useResourceAvailability.js +40 -0
  25. package/dist/components/CalendarView/useResourceAvailability.js.map +1 -0
  26. package/dist/components/DashboardWidget/DashboardWidgetServer.js +20 -6
  27. package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
  28. package/dist/defaults.d.ts +3 -0
  29. package/dist/defaults.js +57 -0
  30. package/dist/defaults.js.map +1 -1
  31. package/dist/endpoints/cancelBooking.js +34 -21
  32. package/dist/endpoints/cancelBooking.js.map +1 -1
  33. package/dist/endpoints/checkAvailability.js +16 -1
  34. package/dist/endpoints/checkAvailability.js.map +1 -1
  35. package/dist/endpoints/createBooking.js +4 -1
  36. package/dist/endpoints/createBooking.js.map +1 -1
  37. package/dist/endpoints/customerSearch.js +24 -5
  38. package/dist/endpoints/customerSearch.js.map +1 -1
  39. package/dist/endpoints/getSlots.js +16 -1
  40. package/dist/endpoints/getSlots.js.map +1 -1
  41. package/dist/endpoints/resourceAvailability.d.ts +43 -0
  42. package/dist/endpoints/resourceAvailability.js +214 -0
  43. package/dist/endpoints/resourceAvailability.js.map +1 -0
  44. package/dist/exports/client.d.ts +1 -0
  45. package/dist/exports/client.js +1 -0
  46. package/dist/exports/client.js.map +1 -1
  47. package/dist/hooks/reservations/calculateEndTime.js +21 -1
  48. package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
  49. package/dist/hooks/reservations/expandRequiredResources.d.ts +9 -0
  50. package/dist/hooks/reservations/expandRequiredResources.js +81 -0
  51. package/dist/hooks/reservations/expandRequiredResources.js.map +1 -0
  52. package/dist/hooks/reservations/validateGuestBooking.d.ts +3 -0
  53. package/dist/hooks/reservations/validateGuestBooking.js +93 -0
  54. package/dist/hooks/reservations/validateGuestBooking.js.map +1 -0
  55. package/dist/hooks/reservations/validateStatusTransition.js +4 -2
  56. package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
  57. package/dist/hooks/users/provisionStaffResource.d.ts +15 -0
  58. package/dist/hooks/users/provisionStaffResource.js +88 -0
  59. package/dist/hooks/users/provisionStaffResource.js.map +1 -0
  60. package/dist/index.d.ts +5 -1
  61. package/dist/index.js +4 -0
  62. package/dist/index.js.map +1 -1
  63. package/dist/plugin.js +20 -2
  64. package/dist/plugin.js.map +1 -1
  65. package/dist/services/AvailabilityService.d.ts +2 -1
  66. package/dist/services/AvailabilityService.js +86 -60
  67. package/dist/services/AvailabilityService.js.map +1 -1
  68. package/dist/translations/ar.json +156 -0
  69. package/dist/translations/de.json +156 -0
  70. package/dist/translations/en.json +32 -1
  71. package/dist/translations/es.json +156 -0
  72. package/dist/translations/fa.json +156 -0
  73. package/dist/translations/fr.json +156 -0
  74. package/dist/translations/hi.json +156 -0
  75. package/dist/translations/id.json +156 -0
  76. package/dist/translations/index.js +44 -0
  77. package/dist/translations/index.js.map +1 -1
  78. package/dist/translations/pl.json +156 -0
  79. package/dist/translations/ru.json +156 -0
  80. package/dist/translations/tr.json +156 -0
  81. package/dist/translations/zh.json +156 -0
  82. package/dist/types.d.ts +57 -0
  83. package/dist/types.js.map +1 -1
  84. package/dist/utilities/computeSlotStates.d.ts +39 -0
  85. package/dist/utilities/computeSlotStates.js +49 -0
  86. package/dist/utilities/computeSlotStates.js.map +1 -0
  87. package/dist/utilities/guestBooking.d.ts +10 -0
  88. package/dist/utilities/guestBooking.js +16 -0
  89. package/dist/utilities/guestBooking.js.map +1 -0
  90. package/dist/utilities/resolveRequiredResources.d.ts +8 -0
  91. package/dist/utilities/resolveRequiredResources.js +27 -0
  92. package/dist/utilities/resolveRequiredResources.js.map +1 -0
  93. package/dist/utilities/scheduleUtils.d.ts +3 -0
  94. package/dist/utilities/scheduleUtils.js +5 -3
  95. package/dist/utilities/scheduleUtils.js.map +1 -1
  96. package/dist/utilities/selectOptions.d.ts +8 -0
  97. package/dist/utilities/selectOptions.js +11 -0
  98. package/dist/utilities/selectOptions.js.map +1 -0
  99. package/dist/utilities/slotUtils.d.ts +19 -0
  100. package/dist/utilities/slotUtils.js +28 -0
  101. package/dist/utilities/slotUtils.js.map +1 -1
  102. package/dist/utilities/tenantFilter.d.ts +25 -0
  103. package/dist/utilities/tenantFilter.js +56 -0
  104. package/dist/utilities/tenantFilter.js.map +1 -0
  105. package/dist/utilities/useTenantFilter.d.ts +6 -0
  106. package/dist/utilities/useTenantFilter.js +28 -0
  107. package/dist/utilities/useTenantFilter.js.map +1 -0
  108. package/dist/utilities/userRoles.d.ts +20 -0
  109. package/dist/utilities/userRoles.js +32 -0
  110. package/dist/utilities/userRoles.js.map +1 -0
  111. package/package.json +3 -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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"}
@@ -2,6 +2,7 @@
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useConfig, useTranslation } from '@payloadcms/ui';
4
4
  import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
5
+ import { useTenantFilter } from '../../utilities/useTenantFilter.js';
5
6
  import styles from './AvailabilityOverview.module.css';
6
7
  const DAY_MAP = {
7
8
  fri: 5,
@@ -31,6 +32,9 @@ export const AvailabilityOverview = ()=>{
31
32
  'pending',
32
33
  'confirmed'
33
34
  ];
35
+ const resourcesTenantParams = useTenantFilter(slugs?.resources ?? 'resources');
36
+ const schedulesTenantParams = useTenantFilter(slugs?.schedules ?? 'schedules');
37
+ const reservationsTenantParams = useTenantFilter(slugs?.reservations ?? 'reservations');
34
38
  const DAY_NAMES = useMemo(()=>[
35
39
  t('reservation:dayShortSun'),
36
40
  t('reservation:dayShortMon'),
@@ -83,16 +87,28 @@ export const AvailabilityOverview = ()=>{
83
87
  // Payload's REST API accepts a comma-separated list.
84
88
  const blockingIn = blockingStatuses.join(',');
85
89
  try {
90
+ const resourcesParams = new URLSearchParams({
91
+ limit: '100',
92
+ 'where[active][equals]': 'true',
93
+ ...resourcesTenantParams
94
+ });
95
+ const schedulesParams = new URLSearchParams({
96
+ limit: '500',
97
+ 'where[active][equals]': 'true',
98
+ ...schedulesTenantParams
99
+ });
100
+ const reservationsParams = new URLSearchParams({
101
+ depth: '0',
102
+ limit: '500',
103
+ 'where[startTime][greater_than_equal]': weekStart.toISOString(),
104
+ 'where[startTime][less_than_equal]': weekEnd.toISOString(),
105
+ 'where[status][in]': blockingIn,
106
+ ...reservationsTenantParams
107
+ });
86
108
  const [resourcesRes, schedulesRes, reservationsRes] = await Promise.all([
87
- fetch(`${apiBase}/${slugs.resources}?where[active][equals]=true&limit=100`),
88
- fetch(`${apiBase}/${slugs.schedules}?where[active][equals]=true&limit=500`),
89
- fetch(`${apiBase}/${slugs.reservations}?${new URLSearchParams({
90
- depth: '0',
91
- limit: '500',
92
- 'where[startTime][greater_than_equal]': weekStart.toISOString(),
93
- 'where[startTime][less_than_equal]': weekEnd.toISOString(),
94
- 'where[status][in]': blockingIn
95
- })}`)
109
+ fetch(`${apiBase}/${slugs.resources}?${resourcesParams}`),
110
+ fetch(`${apiBase}/${slugs.schedules}?${schedulesParams}`),
111
+ fetch(`${apiBase}/${slugs.reservations}?${reservationsParams}`)
96
112
  ]);
97
113
  const [rData, sData, resData] = await Promise.all([
98
114
  resourcesRes.json(),
@@ -119,7 +135,10 @@ export const AvailabilityOverview = ()=>{
119
135
  config.routes.api,
120
136
  config.serverURL,
121
137
  slugs,
122
- blockingStatuses.join(',')
138
+ blockingStatuses.join(','),
139
+ resourcesTenantParams,
140
+ schedulesTenantParams,
141
+ reservationsTenantParams
123
142
  ]);
124
143
  const navigateWeek = useCallback((direction)=>{
125
144
  setWeekStart((prev)=>{
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/AvailabilityOverview/index.tsx"],"sourcesContent":["'use client'\nimport type { AdminViewServerProps } from 'payload'\n\nimport { useConfig, useTranslation } from '@payloadcms/ui'\nimport { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\n\nimport styles from './AvailabilityOverview.module.css'\n\ntype Resource = {\n active?: boolean\n capacityMode?: 'per-guest' | 'per-reservation'\n id: string\n name: string\n quantity?: number\n}\n\ntype Schedule = {\n active?: boolean\n exceptions?: Array<{ date: string; reason?: string }>\n id: string\n manualSlots?: Array<{ date: string; endTime: string; startTime: string }>\n recurringSlots?: Array<{ day: string; endTime: string; startTime: string }>\n resource: { id: string } | string\n scheduleType: 'manual' | 'recurring'\n}\n\ntype Reservation = {\n endTime?: string\n id: string\n resource: { id: string } | string\n startTime: string\n status: string\n}\n\nconst DAY_MAP: Record<string, number> = {\n fri: 5,\n mon: 1,\n sat: 6,\n sun: 0,\n thu: 4,\n tue: 2,\n wed: 3,\n}\n\n/** Return the CSS class for a capacity badge based on utilization ratio. */\nfunction capacityClass(booked: number, total: number): string {\n if (booked >= total) {return styles.slotCapacityFull}\n if (booked / total >= 0.5) {return styles.slotCapacityMid}\n return styles.slotCapacityLow\n}\n\nexport const AvailabilityOverview: React.FC<AdminViewServerProps> = () => {\n const { config } = useConfig()\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n const slugs = config.admin?.custom?.reservationSlugs\n const statusMachine = config.admin?.custom?.reservationStatusMachine\n const blockingStatuses: string[] = statusMachine?.blockingStatuses ?? ['pending', 'confirmed']\n\n const DAY_NAMES = useMemo(\n () => [\n t('reservation:dayShortSun'),\n t('reservation:dayShortMon'),\n t('reservation:dayShortTue'),\n t('reservation:dayShortWed'),\n t('reservation:dayShortThu'),\n t('reservation:dayShortFri'),\n t('reservation:dayShortSat'),\n ],\n [t],\n )\n\n const [weekStart, setWeekStart] = useState(() => {\n const now = new Date()\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n d.setDate(d.getDate() - d.getDay())\n return d\n })\n\n const [resources, setResources] = useState<Resource[]>([])\n const [schedules, setSchedules] = useState<Schedule[]>([])\n const [reservations, setReservations] = useState<Reservation[]>([])\n const [loading, setLoading] = useState(true)\n\n const weekDays = useMemo(() => {\n return Array.from({ length: 7 }, (_, i) => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + i)\n return d\n })\n }, [weekStart])\n\n const weekEnd = useMemo(() => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + 6)\n d.setHours(23, 59, 59, 999)\n return d\n }, [weekStart])\n\n useEffect(() => {\n if (!slugs) {return}\n\n const fetchData = async () => {\n setLoading(true)\n const apiBase = `${config.serverURL ?? ''}${config.routes.api}`\n\n // Build a query that fetches only blocking-status reservations so the\n // component doesn't need to filter client-side. The `in` operator on\n // Payload's REST API accepts a comma-separated list.\n const blockingIn = blockingStatuses.join(',')\n\n try {\n const [resourcesRes, schedulesRes, reservationsRes] = await Promise.all([\n fetch(`${apiBase}/${slugs.resources}?where[active][equals]=true&limit=100`),\n fetch(`${apiBase}/${slugs.schedules}?where[active][equals]=true&limit=500`),\n fetch(\n `${apiBase}/${slugs.reservations}?${new URLSearchParams({\n depth: '0',\n limit: '500',\n 'where[startTime][greater_than_equal]': weekStart.toISOString(),\n 'where[startTime][less_than_equal]': weekEnd.toISOString(),\n 'where[status][in]': blockingIn,\n })}`,\n ),\n ])\n\n const [rData, sData, resData] = await Promise.all([\n resourcesRes.json(),\n schedulesRes.json(),\n reservationsRes.json(),\n ])\n\n setResources(rData.docs ?? [])\n setSchedules(sData.docs ?? [])\n setReservations(resData.docs ?? [])\n } catch {\n setResources([])\n setSchedules([])\n setReservations([])\n }\n setLoading(false)\n }\n\n void fetchData()\n // blockingStatuses is derived from config which is stable; stringify to\n // avoid object-reference churn causing infinite loops.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [weekStart, weekEnd, config.routes.api, config.serverURL, slugs, blockingStatuses.join(',')])\n\n const navigateWeek = useCallback((direction: -1 | 1) => {\n setWeekStart((prev) => {\n const next = new Date(prev)\n next.setDate(next.getDate() + 7 * direction)\n return next\n })\n }, [])\n\n const goToThisWeek = useCallback(() => {\n const now = new Date()\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n d.setDate(d.getDate() - d.getDay())\n setWeekStart(d)\n }, [])\n\n const getResourceId = (r: { id: string } | string) =>\n typeof r === 'object' ? r.id : r\n\n const getSlotsForResourceDay = (resourceId: string, day: Date) => {\n const resourceSchedules = schedules.filter(\n (s) => getResourceId(s.resource) === resourceId,\n )\n const dateStr = day.toISOString().split('T')[0]\n const dayOfWeek = day.getDay()\n\n const slots: Array<{ label: string; type: 'available' | 'exception' }> = []\n\n for (const schedule of resourceSchedules) {\n // Check for exceptions\n const exception = schedule.exceptions?.find((e) => {\n const excDate = new Date(e.date).toISOString().split('T')[0]\n return excDate === dateStr\n })\n\n if (exception) {\n slots.push({\n type: 'exception',\n label: exception.reason || t('reservation:availabilityUnavailable'),\n })\n continue\n }\n\n if (schedule.scheduleType === 'recurring') {\n for (const slot of schedule.recurringSlots ?? []) {\n if (DAY_MAP[slot.day] === dayOfWeek) {\n slots.push({\n type: 'available',\n label: `${slot.startTime}-${slot.endTime}`,\n })\n }\n }\n } else if (schedule.scheduleType === 'manual') {\n for (const slot of schedule.manualSlots ?? []) {\n const slotDate = new Date(slot.date).toISOString().split('T')[0]\n if (slotDate === dateStr) {\n slots.push({\n type: 'available',\n label: `${slot.startTime}-${slot.endTime}`,\n })\n }\n }\n }\n }\n\n return slots\n }\n\n /** Returns all blocking-status reservations for a resource on a given day. */\n const getBookingsForResourceDay = (resourceId: string, day: Date) => {\n return reservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n getResourceId(r.resource) === resourceId &&\n rDate.getFullYear() === day.getFullYear() &&\n rDate.getMonth() === day.getMonth() &&\n rDate.getDate() === day.getDate()\n )\n })\n }\n\n if (!slugs) {\n return <div className={styles.noResources}>{t('reservation:availabilityNotConfigured')}</div>\n }\n\n if (loading) {\n return <div className={styles.loading}>{t('reservation:availabilityLoading')}</div>\n }\n\n const weekLabel = `${weekDays[0].toLocaleDateString([], { day: 'numeric', month: 'short' })} - ${weekDays[6].toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' })}`\n\n const gridColumns = `150px repeat(7, 1fr)`\n\n return (\n <div className={styles.wrapper}>\n <h2 className={styles.title}>{t('reservation:availabilityTitle')}</h2>\n <div className={styles.navigation}>\n <button className={styles.navButton} onClick={() => navigateWeek(-1)} type=\"button\">\n &larr;\n </button>\n <button className={styles.navButton} onClick={goToThisWeek} type=\"button\">\n {t('reservation:availabilityThisWeek')}\n </button>\n <button className={styles.navButton} onClick={() => navigateWeek(1)} type=\"button\">\n &rarr;\n </button>\n <span className={styles.weekLabel}>{weekLabel}</span>\n </div>\n\n {resources.length === 0 ? (\n <div className={styles.noResources}>{t('reservation:availabilityNoResources')}</div>\n ) : (\n <div className={styles.grid} style={{ gridTemplateColumns: gridColumns }}>\n {/* Header row */}\n <div className={styles.headerCell}>{t('reservation:availabilityResource')}</div>\n {weekDays.map((day, i) => (\n <div className={styles.headerCell} key={i}>\n {DAY_NAMES[day.getDay()]} {day.getDate()}\n </div>\n ))}\n\n {/* Resource rows */}\n {resources.map((resource) => {\n const quantity = resource.quantity ?? 1\n return (\n <Fragment key={resource.id}>\n <div className={styles.resourceName}>\n {resource.name}\n {quantity > 1 && (\n <span style={{ fontWeight: 400, marginLeft: 4, opacity: 0.6 }}>\n {' '}(&times;{quantity})\n </span>\n )}\n </div>\n {weekDays.map((day, di) => {\n const slots = getSlotsForResourceDay(resource.id, day)\n const bookings = getBookingsForResourceDay(resource.id, day)\n const bookedCount = bookings.length\n\n return (\n <div className={styles.cell} key={`cell-${resource.id}-${di}`}>\n {slots.map((slot, si) => (\n <div\n className={\n slot.type === 'exception'\n ? styles.slotException\n : styles.slotAvailable\n }\n key={`slot-${si}`}\n >\n {slot.label}\n </div>\n ))}\n {quantity > 1 ? (\n /* Multi-unit resource: show X/Y booked with graduated color */\n bookedCount > 0 && (\n <div\n className={capacityClass(bookedCount, quantity)}\n title={t('reservation:availabilityXofYBooked', {\n booked: bookedCount,\n total: quantity,\n })}\n >\n {t('reservation:availabilityXofYBooked', {\n booked: bookedCount,\n total: quantity,\n })}\n </div>\n )\n ) : (\n /* Single-unit resource: show individual booking times */\n bookings.map((b) => (\n <div className={styles.slotBooked} key={b.id}>\n {new Date(b.startTime).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })}{' '}\n {t('reservation:availabilityBooked')}\n </div>\n ))\n )}\n </div>\n )\n })}\n </Fragment>\n )\n })}\n </div>\n )}\n </div>\n )\n}\n"],"names":["useConfig","useTranslation","Fragment","useCallback","useEffect","useMemo","useState","styles","DAY_MAP","fri","mon","sat","sun","thu","tue","wed","capacityClass","booked","total","slotCapacityFull","slotCapacityMid","slotCapacityLow","AvailabilityOverview","config","t","_t","slugs","admin","custom","reservationSlugs","statusMachine","reservationStatusMachine","blockingStatuses","DAY_NAMES","weekStart","setWeekStart","now","Date","d","getFullYear","getMonth","getDate","setDate","getDay","resources","setResources","schedules","setSchedules","reservations","setReservations","loading","setLoading","weekDays","Array","from","length","_","i","weekEnd","setHours","fetchData","apiBase","serverURL","routes","api","blockingIn","join","resourcesRes","schedulesRes","reservationsRes","Promise","all","fetch","URLSearchParams","depth","limit","toISOString","rData","sData","resData","json","docs","navigateWeek","direction","prev","next","goToThisWeek","getResourceId","r","id","getSlotsForResourceDay","resourceId","day","resourceSchedules","filter","s","resource","dateStr","split","dayOfWeek","slots","schedule","exception","exceptions","find","e","excDate","date","push","type","label","reason","scheduleType","slot","recurringSlots","startTime","endTime","manualSlots","slotDate","getBookingsForResourceDay","rDate","div","className","noResources","weekLabel","toLocaleDateString","month","year","gridColumns","wrapper","h2","title","navigation","button","navButton","onClick","span","grid","style","gridTemplateColumns","headerCell","map","quantity","resourceName","name","fontWeight","marginLeft","opacity","di","bookings","bookedCount","cell","si","slotException","slotAvailable","b","slotBooked","toLocaleTimeString","hour","minute"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/components/AvailabilityOverview/index.tsx"],"sourcesContent":["'use client'\nimport type { AdminViewServerProps } from 'payload'\n\nimport { useConfig, useTranslation } from '@payloadcms/ui'\nimport { Fragment, useCallback, useEffect, useMemo, useState } from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\n\nimport { useTenantFilter } from '../../utilities/useTenantFilter.js'\nimport styles from './AvailabilityOverview.module.css'\n\ntype Resource = {\n active?: boolean\n capacityMode?: 'per-guest' | 'per-reservation'\n id: string\n name: string\n quantity?: number\n}\n\ntype Schedule = {\n active?: boolean\n exceptions?: Array<{ date: string; reason?: string }>\n id: string\n manualSlots?: Array<{ date: string; endTime: string; startTime: string }>\n recurringSlots?: Array<{ day: string; endTime: string; startTime: string }>\n resource: { id: string } | string\n scheduleType: 'manual' | 'recurring'\n}\n\ntype Reservation = {\n endTime?: string\n id: string\n resource: { id: string } | string\n startTime: string\n status: string\n}\n\nconst DAY_MAP: Record<string, number> = {\n fri: 5,\n mon: 1,\n sat: 6,\n sun: 0,\n thu: 4,\n tue: 2,\n wed: 3,\n}\n\n/** Return the CSS class for a capacity badge based on utilization ratio. */\nfunction capacityClass(booked: number, total: number): string {\n if (booked >= total) {return styles.slotCapacityFull}\n if (booked / total >= 0.5) {return styles.slotCapacityMid}\n return styles.slotCapacityLow\n}\n\nexport const AvailabilityOverview: React.FC<AdminViewServerProps> = () => {\n const { config } = useConfig()\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n const slugs = config.admin?.custom?.reservationSlugs\n const statusMachine = config.admin?.custom?.reservationStatusMachine\n const blockingStatuses: string[] = statusMachine?.blockingStatuses ?? ['pending', 'confirmed']\n\n const resourcesTenantParams = useTenantFilter(slugs?.resources ?? 'resources')\n const schedulesTenantParams = useTenantFilter(slugs?.schedules ?? 'schedules')\n const reservationsTenantParams = useTenantFilter(slugs?.reservations ?? 'reservations')\n\n const DAY_NAMES = useMemo(\n () => [\n t('reservation:dayShortSun'),\n t('reservation:dayShortMon'),\n t('reservation:dayShortTue'),\n t('reservation:dayShortWed'),\n t('reservation:dayShortThu'),\n t('reservation:dayShortFri'),\n t('reservation:dayShortSat'),\n ],\n [t],\n )\n\n const [weekStart, setWeekStart] = useState(() => {\n const now = new Date()\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n d.setDate(d.getDate() - d.getDay())\n return d\n })\n\n const [resources, setResources] = useState<Resource[]>([])\n const [schedules, setSchedules] = useState<Schedule[]>([])\n const [reservations, setReservations] = useState<Reservation[]>([])\n const [loading, setLoading] = useState(true)\n\n const weekDays = useMemo(() => {\n return Array.from({ length: 7 }, (_, i) => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + i)\n return d\n })\n }, [weekStart])\n\n const weekEnd = useMemo(() => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + 6)\n d.setHours(23, 59, 59, 999)\n return d\n }, [weekStart])\n\n useEffect(() => {\n if (!slugs) {return}\n\n const fetchData = async () => {\n setLoading(true)\n const apiBase = `${config.serverURL ?? ''}${config.routes.api}`\n\n // Build a query that fetches only blocking-status reservations so the\n // component doesn't need to filter client-side. The `in` operator on\n // Payload's REST API accepts a comma-separated list.\n const blockingIn = blockingStatuses.join(',')\n\n try {\n const resourcesParams = new URLSearchParams({\n limit: '100',\n 'where[active][equals]': 'true',\n ...resourcesTenantParams,\n })\n const schedulesParams = new URLSearchParams({\n limit: '500',\n 'where[active][equals]': 'true',\n ...schedulesTenantParams,\n })\n const reservationsParams = new URLSearchParams({\n depth: '0',\n limit: '500',\n 'where[startTime][greater_than_equal]': weekStart.toISOString(),\n 'where[startTime][less_than_equal]': weekEnd.toISOString(),\n 'where[status][in]': blockingIn,\n ...reservationsTenantParams,\n })\n const [resourcesRes, schedulesRes, reservationsRes] = await Promise.all([\n fetch(`${apiBase}/${slugs.resources}?${resourcesParams}`),\n fetch(`${apiBase}/${slugs.schedules}?${schedulesParams}`),\n fetch(`${apiBase}/${slugs.reservations}?${reservationsParams}`),\n ])\n\n const [rData, sData, resData] = await Promise.all([\n resourcesRes.json(),\n schedulesRes.json(),\n reservationsRes.json(),\n ])\n\n setResources(rData.docs ?? [])\n setSchedules(sData.docs ?? [])\n setReservations(resData.docs ?? [])\n } catch {\n setResources([])\n setSchedules([])\n setReservations([])\n }\n setLoading(false)\n }\n\n void fetchData()\n // blockingStatuses is derived from config which is stable; stringify to\n // avoid object-reference churn causing infinite loops.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [weekStart, weekEnd, config.routes.api, config.serverURL, slugs, blockingStatuses.join(','), resourcesTenantParams, schedulesTenantParams, reservationsTenantParams])\n\n const navigateWeek = useCallback((direction: -1 | 1) => {\n setWeekStart((prev) => {\n const next = new Date(prev)\n next.setDate(next.getDate() + 7 * direction)\n return next\n })\n }, [])\n\n const goToThisWeek = useCallback(() => {\n const now = new Date()\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n d.setDate(d.getDate() - d.getDay())\n setWeekStart(d)\n }, [])\n\n const getResourceId = (r: { id: string } | string) =>\n typeof r === 'object' ? r.id : r\n\n const getSlotsForResourceDay = (resourceId: string, day: Date) => {\n const resourceSchedules = schedules.filter(\n (s) => getResourceId(s.resource) === resourceId,\n )\n const dateStr = day.toISOString().split('T')[0]\n const dayOfWeek = day.getDay()\n\n const slots: Array<{ label: string; type: 'available' | 'exception' }> = []\n\n for (const schedule of resourceSchedules) {\n // Check for exceptions\n const exception = schedule.exceptions?.find((e) => {\n const excDate = new Date(e.date).toISOString().split('T')[0]\n return excDate === dateStr\n })\n\n if (exception) {\n slots.push({\n type: 'exception',\n label: exception.reason || t('reservation:availabilityUnavailable'),\n })\n continue\n }\n\n if (schedule.scheduleType === 'recurring') {\n for (const slot of schedule.recurringSlots ?? []) {\n if (DAY_MAP[slot.day] === dayOfWeek) {\n slots.push({\n type: 'available',\n label: `${slot.startTime}-${slot.endTime}`,\n })\n }\n }\n } else if (schedule.scheduleType === 'manual') {\n for (const slot of schedule.manualSlots ?? []) {\n const slotDate = new Date(slot.date).toISOString().split('T')[0]\n if (slotDate === dateStr) {\n slots.push({\n type: 'available',\n label: `${slot.startTime}-${slot.endTime}`,\n })\n }\n }\n }\n }\n\n return slots\n }\n\n /** Returns all blocking-status reservations for a resource on a given day. */\n const getBookingsForResourceDay = (resourceId: string, day: Date) => {\n return reservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n getResourceId(r.resource) === resourceId &&\n rDate.getFullYear() === day.getFullYear() &&\n rDate.getMonth() === day.getMonth() &&\n rDate.getDate() === day.getDate()\n )\n })\n }\n\n if (!slugs) {\n return <div className={styles.noResources}>{t('reservation:availabilityNotConfigured')}</div>\n }\n\n if (loading) {\n return <div className={styles.loading}>{t('reservation:availabilityLoading')}</div>\n }\n\n const weekLabel = `${weekDays[0].toLocaleDateString([], { day: 'numeric', month: 'short' })} - ${weekDays[6].toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' })}`\n\n const gridColumns = `150px repeat(7, 1fr)`\n\n return (\n <div className={styles.wrapper}>\n <h2 className={styles.title}>{t('reservation:availabilityTitle')}</h2>\n <div className={styles.navigation}>\n <button className={styles.navButton} onClick={() => navigateWeek(-1)} type=\"button\">\n &larr;\n </button>\n <button className={styles.navButton} onClick={goToThisWeek} type=\"button\">\n {t('reservation:availabilityThisWeek')}\n </button>\n <button className={styles.navButton} onClick={() => navigateWeek(1)} type=\"button\">\n &rarr;\n </button>\n <span className={styles.weekLabel}>{weekLabel}</span>\n </div>\n\n {resources.length === 0 ? (\n <div className={styles.noResources}>{t('reservation:availabilityNoResources')}</div>\n ) : (\n <div className={styles.grid} style={{ gridTemplateColumns: gridColumns }}>\n {/* Header row */}\n <div className={styles.headerCell}>{t('reservation:availabilityResource')}</div>\n {weekDays.map((day, i) => (\n <div className={styles.headerCell} key={i}>\n {DAY_NAMES[day.getDay()]} {day.getDate()}\n </div>\n ))}\n\n {/* Resource rows */}\n {resources.map((resource) => {\n const quantity = resource.quantity ?? 1\n return (\n <Fragment key={resource.id}>\n <div className={styles.resourceName}>\n {resource.name}\n {quantity > 1 && (\n <span style={{ fontWeight: 400, marginLeft: 4, opacity: 0.6 }}>\n {' '}(&times;{quantity})\n </span>\n )}\n </div>\n {weekDays.map((day, di) => {\n const slots = getSlotsForResourceDay(resource.id, day)\n const bookings = getBookingsForResourceDay(resource.id, day)\n const bookedCount = bookings.length\n\n return (\n <div className={styles.cell} key={`cell-${resource.id}-${di}`}>\n {slots.map((slot, si) => (\n <div\n className={\n slot.type === 'exception'\n ? styles.slotException\n : styles.slotAvailable\n }\n key={`slot-${si}`}\n >\n {slot.label}\n </div>\n ))}\n {quantity > 1 ? (\n /* Multi-unit resource: show X/Y booked with graduated color */\n bookedCount > 0 && (\n <div\n className={capacityClass(bookedCount, quantity)}\n title={t('reservation:availabilityXofYBooked', {\n booked: bookedCount,\n total: quantity,\n })}\n >\n {t('reservation:availabilityXofYBooked', {\n booked: bookedCount,\n total: quantity,\n })}\n </div>\n )\n ) : (\n /* Single-unit resource: show individual booking times */\n bookings.map((b) => (\n <div className={styles.slotBooked} key={b.id}>\n {new Date(b.startTime).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })}{' '}\n {t('reservation:availabilityBooked')}\n </div>\n ))\n )}\n </div>\n )\n })}\n </Fragment>\n )\n })}\n </div>\n )}\n </div>\n )\n}\n"],"names":["useConfig","useTranslation","Fragment","useCallback","useEffect","useMemo","useState","useTenantFilter","styles","DAY_MAP","fri","mon","sat","sun","thu","tue","wed","capacityClass","booked","total","slotCapacityFull","slotCapacityMid","slotCapacityLow","AvailabilityOverview","config","t","_t","slugs","admin","custom","reservationSlugs","statusMachine","reservationStatusMachine","blockingStatuses","resourcesTenantParams","resources","schedulesTenantParams","schedules","reservationsTenantParams","reservations","DAY_NAMES","weekStart","setWeekStart","now","Date","d","getFullYear","getMonth","getDate","setDate","getDay","setResources","setSchedules","setReservations","loading","setLoading","weekDays","Array","from","length","_","i","weekEnd","setHours","fetchData","apiBase","serverURL","routes","api","blockingIn","join","resourcesParams","URLSearchParams","limit","schedulesParams","reservationsParams","depth","toISOString","resourcesRes","schedulesRes","reservationsRes","Promise","all","fetch","rData","sData","resData","json","docs","navigateWeek","direction","prev","next","goToThisWeek","getResourceId","r","id","getSlotsForResourceDay","resourceId","day","resourceSchedules","filter","s","resource","dateStr","split","dayOfWeek","slots","schedule","exception","exceptions","find","e","excDate","date","push","type","label","reason","scheduleType","slot","recurringSlots","startTime","endTime","manualSlots","slotDate","getBookingsForResourceDay","rDate","div","className","noResources","weekLabel","toLocaleDateString","month","year","gridColumns","wrapper","h2","title","navigation","button","navButton","onClick","span","grid","style","gridTemplateColumns","headerCell","map","quantity","resourceName","name","fontWeight","marginLeft","opacity","di","bookings","bookedCount","cell","si","slotException","slotAvailable","b","slotBooked","toLocaleTimeString","hour","minute"],"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 {};