payload-reserve 1.5.0 → 2.0.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 (95) hide show
  1. package/README.md +40 -3
  2. package/dist/collections/Reservations.js +19 -7
  3. package/dist/collections/Reservations.js.map +1 -1
  4. package/dist/collections/Resources.js +11 -8
  5. package/dist/collections/Resources.js.map +1 -1
  6. package/dist/collections/Schedules.js +12 -6
  7. package/dist/collections/Schedules.js.map +1 -1
  8. package/dist/collections/Services.js +19 -10
  9. package/dist/collections/Services.js.map +1 -1
  10. package/dist/components/AvailabilityOverview/index.js +70 -26
  11. package/dist/components/AvailabilityOverview/index.js.map +1 -1
  12. package/dist/components/CalendarView/CalendarView.module.css +9 -0
  13. package/dist/components/CalendarView/LaneTimelineView.d.ts +4 -1
  14. package/dist/components/CalendarView/LaneTimelineView.js +17 -12
  15. package/dist/components/CalendarView/LaneTimelineView.js.map +1 -1
  16. package/dist/components/CalendarView/index.js +154 -53
  17. package/dist/components/CalendarView/index.js.map +1 -1
  18. package/dist/components/CustomerField/index.js +8 -3
  19. package/dist/components/CustomerField/index.js.map +1 -1
  20. package/dist/components/DashboardWidget/DashboardWidgetServer.js +97 -21
  21. package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
  22. package/dist/defaults.js +46 -8
  23. package/dist/defaults.js.map +1 -1
  24. package/dist/endpoints/cancelBooking.js +1 -1
  25. package/dist/endpoints/cancelBooking.js.map +1 -1
  26. package/dist/endpoints/checkAvailability.js +56 -7
  27. package/dist/endpoints/checkAvailability.js.map +1 -1
  28. package/dist/endpoints/createBooking.js +19 -10
  29. package/dist/endpoints/createBooking.js.map +1 -1
  30. package/dist/endpoints/customerSearch.js +5 -2
  31. package/dist/endpoints/customerSearch.js.map +1 -1
  32. package/dist/endpoints/getSlots.js +56 -7
  33. package/dist/endpoints/getSlots.js.map +1 -1
  34. package/dist/endpoints/resourceAvailability.d.ts +2 -1
  35. package/dist/endpoints/resourceAvailability.js +85 -25
  36. package/dist/endpoints/resourceAvailability.js.map +1 -1
  37. package/dist/hooks/reservations/calculateEndTime.js +48 -20
  38. package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
  39. package/dist/hooks/reservations/enforceCustomerOwnership.d.ts +11 -0
  40. package/dist/hooks/reservations/enforceCustomerOwnership.js +30 -0
  41. package/dist/hooks/reservations/enforceCustomerOwnership.js.map +1 -0
  42. package/dist/hooks/reservations/onStatusChange.js +10 -4
  43. package/dist/hooks/reservations/onStatusChange.js.map +1 -1
  44. package/dist/hooks/reservations/validateCancellation.js +3 -2
  45. package/dist/hooks/reservations/validateCancellation.js.map +1 -1
  46. package/dist/hooks/reservations/validateConflicts.js +23 -4
  47. package/dist/hooks/reservations/validateConflicts.js.map +1 -1
  48. package/dist/hooks/reservations/validateGuestBooking.js +3 -4
  49. package/dist/hooks/reservations/validateGuestBooking.js.map +1 -1
  50. package/dist/hooks/reservations/validateStatusTransition.js +2 -2
  51. package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
  52. package/dist/hooks/users/provisionStaffResource.js +5 -8
  53. package/dist/hooks/users/provisionStaffResource.js.map +1 -1
  54. package/dist/plugin.js +82 -13
  55. package/dist/plugin.js.map +1 -1
  56. package/dist/services/AvailabilityService.d.ts +54 -2
  57. package/dist/services/AvailabilityService.js +180 -46
  58. package/dist/services/AvailabilityService.js.map +1 -1
  59. package/dist/translations/ar.json +1 -0
  60. package/dist/translations/de.json +1 -0
  61. package/dist/translations/en.json +1 -0
  62. package/dist/translations/es.json +1 -0
  63. package/dist/translations/fa.json +1 -0
  64. package/dist/translations/fr.json +1 -0
  65. package/dist/translations/hi.json +1 -0
  66. package/dist/translations/id.json +1 -0
  67. package/dist/translations/pl.json +1 -0
  68. package/dist/translations/ru.json +1 -0
  69. package/dist/translations/tr.json +1 -0
  70. package/dist/translations/zh.json +1 -0
  71. package/dist/types.d.ts +50 -1
  72. package/dist/types.js +2 -0
  73. package/dist/types.js.map +1 -1
  74. package/dist/utilities/collectionOverrides.d.ts +14 -0
  75. package/dist/utilities/collectionOverrides.js +47 -0
  76. package/dist/utilities/collectionOverrides.js.map +1 -0
  77. package/dist/utilities/ownerAccess.d.ts +6 -0
  78. package/dist/utilities/ownerAccess.js +25 -12
  79. package/dist/utilities/ownerAccess.js.map +1 -1
  80. package/dist/utilities/reservationChanges.d.ts +17 -0
  81. package/dist/utilities/reservationChanges.js +88 -0
  82. package/dist/utilities/reservationChanges.js.map +1 -0
  83. package/dist/utilities/scheduleUtils.d.ts +14 -8
  84. package/dist/utilities/scheduleUtils.js +26 -19
  85. package/dist/utilities/scheduleUtils.js.map +1 -1
  86. package/dist/utilities/tenantFilter.d.ts +25 -0
  87. package/dist/utilities/tenantFilter.js +56 -0
  88. package/dist/utilities/tenantFilter.js.map +1 -0
  89. package/dist/utilities/timezoneUtils.d.ts +39 -0
  90. package/dist/utilities/timezoneUtils.js +134 -0
  91. package/dist/utilities/timezoneUtils.js.map +1 -0
  92. package/dist/utilities/useTenantFilter.d.ts +6 -0
  93. package/dist/utilities/useTenantFilter.js +28 -0
  94. package/dist/utilities/useTenantFilter.js.map +1 -0
  95. package/package.json +2 -1
@@ -2,6 +2,8 @@
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 { getDayKeyInTimezone, getDayOfWeekFromDayKey } from '../../utilities/timezoneUtils.js';
6
+ import { useTenantFilter } from '../../utilities/useTenantFilter.js';
5
7
  import styles from './AvailabilityOverview.module.css';
6
8
  const DAY_MAP = {
7
9
  fri: 5,
@@ -31,6 +33,10 @@ export const AvailabilityOverview = ()=>{
31
33
  'pending',
32
34
  'confirmed'
33
35
  ];
36
+ const reservationTimezone = config.admin?.custom?.reservationTimezone ?? 'UTC';
37
+ const resourcesTenantParams = useTenantFilter(slugs?.resources ?? 'resources');
38
+ const schedulesTenantParams = useTenantFilter(slugs?.schedules ?? 'schedules');
39
+ const reservationsTenantParams = useTenantFilter(slugs?.reservations ?? 'reservations');
34
40
  const DAY_NAMES = useMemo(()=>[
35
41
  t('reservation:dayShortSun'),
36
42
  t('reservation:dayShortMon'),
@@ -52,6 +58,8 @@ export const AvailabilityOverview = ()=>{
52
58
  const [schedules, setSchedules] = useState([]);
53
59
  const [reservations, setReservations] = useState([]);
54
60
  const [loading, setLoading] = useState(true);
61
+ // Cells are browser-local midnights by design; every key derived from them
62
+ // (day keys, weekday matching) is computed in the business timezone.
55
63
  const weekDays = useMemo(()=>{
56
64
  return Array.from({
57
65
  length: 7
@@ -75,6 +83,8 @@ export const AvailabilityOverview = ()=>{
75
83
  if (!slugs) {
76
84
  return;
77
85
  }
86
+ // Ignore a slow earlier fetch if the week changed before it resolved (D5)
87
+ let stale = false;
78
88
  const fetchData = async ()=>{
79
89
  setLoading(true);
80
90
  const apiBase = `${config.serverURL ?? ''}${config.routes.api}`;
@@ -83,33 +93,56 @@ export const AvailabilityOverview = ()=>{
83
93
  // Payload's REST API accepts a comma-separated list.
84
94
  const blockingIn = blockingStatuses.join(',');
85
95
  try {
96
+ const resourcesParams = new URLSearchParams({
97
+ limit: '100',
98
+ 'where[active][equals]': 'true',
99
+ ...resourcesTenantParams
100
+ });
101
+ const schedulesParams = new URLSearchParams({
102
+ limit: '1000',
103
+ 'where[active][equals]': 'true',
104
+ ...schedulesTenantParams
105
+ });
106
+ const reservationsParams = new URLSearchParams({
107
+ depth: '0',
108
+ limit: '2000',
109
+ 'where[startTime][greater_than_equal]': weekStart.toISOString(),
110
+ 'where[startTime][less_than_equal]': weekEnd.toISOString(),
111
+ 'where[status][in]': blockingIn,
112
+ ...reservationsTenantParams
113
+ });
86
114
  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
- })}`)
115
+ fetch(`${apiBase}/${slugs.resources}?${resourcesParams}`),
116
+ fetch(`${apiBase}/${slugs.schedules}?${schedulesParams}`),
117
+ fetch(`${apiBase}/${slugs.reservations}?${reservationsParams}`)
96
118
  ]);
97
119
  const [rData, sData, resData] = await Promise.all([
98
120
  resourcesRes.json(),
99
121
  schedulesRes.json(),
100
122
  reservationsRes.json()
101
123
  ]);
124
+ if (stale) {
125
+ return;
126
+ }
102
127
  setResources(rData.docs ?? []);
103
128
  setSchedules(sData.docs ?? []);
104
129
  setReservations(resData.docs ?? []);
105
130
  } catch {
131
+ if (stale) {
132
+ return;
133
+ }
106
134
  setResources([]);
107
135
  setSchedules([]);
108
136
  setReservations([]);
109
137
  }
110
- setLoading(false);
138
+ if (!stale) {
139
+ setLoading(false);
140
+ }
111
141
  };
112
142
  void fetchData();
143
+ return ()=>{
144
+ stale = true;
145
+ };
113
146
  // blockingStatuses is derived from config which is stable; stringify to
114
147
  // avoid object-reference churn causing infinite loops.
115
148
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -119,7 +152,10 @@ export const AvailabilityOverview = ()=>{
119
152
  config.routes.api,
120
153
  config.serverURL,
121
154
  slugs,
122
- blockingStatuses.join(',')
155
+ blockingStatuses.join(','),
156
+ resourcesTenantParams,
157
+ schedulesTenantParams,
158
+ reservationsTenantParams
123
159
  ]);
124
160
  const navigateWeek = useCallback((direction)=>{
125
161
  setWeekStart((prev)=>{
@@ -137,14 +173,16 @@ export const AvailabilityOverview = ()=>{
137
173
  const getResourceId = (r)=>typeof r === 'object' ? r.id : r;
138
174
  const getSlotsForResourceDay = (resourceId, day)=>{
139
175
  const resourceSchedules = schedules.filter((s)=>getResourceId(s.resource) === resourceId);
140
- const dateStr = day.toISOString().split('T')[0];
141
- const dayOfWeek = day.getDay();
176
+ const dateStr = getDayKeyInTimezone(day, reservationTimezone);
177
+ const dayOfWeek = getDayOfWeekFromDayKey(dateStr);
142
178
  const slots = [];
143
179
  for (const schedule of resourceSchedules){
144
- // Check for exceptions
180
+ // Check for exceptions — match the full [date, endDate] range inclusively
181
+ // (review D4), keyed in the business timezone, like the server does.
145
182
  const exception = schedule.exceptions?.find((e)=>{
146
- const excDate = new Date(e.date).toISOString().split('T')[0];
147
- return excDate === dateStr;
183
+ const start = getDayKeyInTimezone(new Date(e.date), reservationTimezone);
184
+ const end = e.endDate ? getDayKeyInTimezone(new Date(e.endDate), reservationTimezone) : start;
185
+ return dateStr >= start && dateStr <= end;
148
186
  });
149
187
  if (exception) {
150
188
  slots.push({
@@ -155,7 +193,7 @@ export const AvailabilityOverview = ()=>{
155
193
  }
156
194
  if (schedule.scheduleType === 'recurring') {
157
195
  for (const slot of schedule.recurringSlots ?? []){
158
- if (DAY_MAP[slot.day] === dayOfWeek) {
196
+ if (slot.day === dayOfWeek) {
159
197
  slots.push({
160
198
  type: 'available',
161
199
  label: `${slot.startTime}-${slot.endTime}`
@@ -164,7 +202,7 @@ export const AvailabilityOverview = ()=>{
164
202
  }
165
203
  } else if (schedule.scheduleType === 'manual') {
166
204
  for (const slot of schedule.manualSlots ?? []){
167
- const slotDate = new Date(slot.date).toISOString().split('T')[0];
205
+ const slotDate = getDayKeyInTimezone(new Date(slot.date), reservationTimezone);
168
206
  if (slotDate === dateStr) {
169
207
  slots.push({
170
208
  type: 'available',
@@ -177,9 +215,9 @@ export const AvailabilityOverview = ()=>{
177
215
  return slots;
178
216
  };
179
217
  /** Returns all blocking-status reservations for a resource on a given day. */ const getBookingsForResourceDay = (resourceId, day)=>{
218
+ const dayKey = getDayKeyInTimezone(day, reservationTimezone);
180
219
  return reservations.filter((r)=>{
181
- const rDate = new Date(r.startTime);
182
- return getResourceId(r.resource) === resourceId && rDate.getFullYear() === day.getFullYear() && rDate.getMonth() === day.getMonth() && rDate.getDate() === day.getDate();
220
+ return getResourceId(r.resource) === resourceId && getDayKeyInTimezone(new Date(r.startTime), reservationTimezone) === dayKey;
183
221
  });
184
222
  };
185
223
  if (!slugs) {
@@ -196,10 +234,12 @@ export const AvailabilityOverview = ()=>{
196
234
  }
197
235
  const weekLabel = `${weekDays[0].toLocaleDateString([], {
198
236
  day: 'numeric',
199
- month: 'short'
237
+ month: 'short',
238
+ timeZone: reservationTimezone
200
239
  })} - ${weekDays[6].toLocaleDateString([], {
201
240
  day: 'numeric',
202
241
  month: 'short',
242
+ timeZone: reservationTimezone,
203
243
  year: 'numeric'
204
244
  })}`;
205
245
  const gridColumns = `150px repeat(7, 1fr)`;
@@ -250,14 +290,17 @@ export const AvailabilityOverview = ()=>{
250
290
  className: styles.headerCell,
251
291
  children: t('reservation:availabilityResource')
252
292
  }),
253
- weekDays.map((day, i)=>/*#__PURE__*/ _jsxs("div", {
293
+ weekDays.map((day, i)=>{
294
+ const dayKey = getDayKeyInTimezone(day, reservationTimezone);
295
+ return /*#__PURE__*/ _jsxs("div", {
254
296
  className: styles.headerCell,
255
297
  children: [
256
- DAY_NAMES[day.getDay()],
298
+ DAY_NAMES[DAY_MAP[getDayOfWeekFromDayKey(dayKey)]],
257
299
  " ",
258
- day.getDate()
300
+ Number(dayKey.slice(8, 10))
259
301
  ]
260
- }, i)),
302
+ }, i);
303
+ }),
261
304
  resources.map((resource)=>{
262
305
  const quantity = resource.quantity ?? 1;
263
306
  return /*#__PURE__*/ _jsxs(Fragment, {
@@ -307,7 +350,8 @@ export const AvailabilityOverview = ()=>{
307
350
  children: [
308
351
  new Date(b.startTime).toLocaleTimeString([], {
309
352
  hour: '2-digit',
310
- minute: '2-digit'
353
+ minute: '2-digit',
354
+ timeZone: reservationTimezone
311
355
  }),
312
356
  ' ',
313
357
  t('reservation:availabilityBooked')
@@ -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":"AAAA;;AAGA,SAASA,SAAS,EAAEC,cAAc,QAAQ,iBAAgB;AAC1D,SAASC,QAAQ,EAAEC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAI3E,OAAOC,YAAY,oCAAmC;AA4BtD,MAAMC,UAAkC;IACtCC,KAAK;IACLC,KAAK;IACLC,KAAK;IACLC,KAAK;IACLC,KAAK;IACLC,KAAK;IACLC,KAAK;AACP;AAEA,0EAA0E,GAC1E,SAASC,cAAcC,MAAc,EAAEC,KAAa;IAClD,IAAID,UAAUC,OAAO;QAAC,OAAOX,OAAOY,gBAAgB;IAAA;IACpD,IAAIF,SAASC,SAAS,KAAK;QAAC,OAAOX,OAAOa,eAAe;IAAA;IACzD,OAAOb,OAAOc,eAAe;AAC/B;AAEA,OAAO,MAAMC,uBAAuD;IAClE,MAAM,EAAEC,MAAM,EAAE,GAAGvB;IACnB,MAAM,EAAEwB,GAAGC,EAAE,EAAE,GAAGxB;IAClB,MAAMuB,IAAIC;IACV,MAAMC,QAAQH,OAAOI,KAAK,EAAEC,QAAQC;IACpC,MAAMC,gBAAgBP,OAAOI,KAAK,EAAEC,QAAQG;IAC5C,MAAMC,mBAA6BF,eAAeE,oBAAoB;QAAC;QAAW;KAAY;IAE9F,MAAMC,YAAY5B,QAChB,IAAM;YACJmB,EAAE;YACFA,EAAE;YACFA,EAAE;YACFA,EAAE;YACFA,EAAE;YACFA,EAAE;YACFA,EAAE;SACH,EACD;QAACA;KAAE;IAGL,MAAM,CAACU,WAAWC,aAAa,GAAG7B,SAAS;QACzC,MAAM8B,MAAM,IAAIC;QAChB,MAAMC,IAAI,IAAID,KAAKD,IAAIG,WAAW,IAAIH,IAAII,QAAQ,IAAIJ,IAAIK,OAAO;QACjEH,EAAEI,OAAO,CAACJ,EAAEG,OAAO,KAAKH,EAAEK,MAAM;QAChC,OAAOL;IACT;IAEA,MAAM,CAACM,WAAWC,aAAa,GAAGvC,SAAqB,EAAE;IACzD,MAAM,CAACwC,WAAWC,aAAa,GAAGzC,SAAqB,EAAE;IACzD,MAAM,CAAC0C,cAAcC,gBAAgB,GAAG3C,SAAwB,EAAE;IAClE,MAAM,CAAC4C,SAASC,WAAW,GAAG7C,SAAS;IAEvC,MAAM8C,WAAW/C,QAAQ;QACvB,OAAOgD,MAAMC,IAAI,CAAC;YAAEC,QAAQ;QAAE,GAAG,CAACC,GAAGC;YACnC,MAAMnB,IAAI,IAAID,KAAKH;YACnBI,EAAEI,OAAO,CAACJ,EAAEG,OAAO,KAAKgB;YACxB,OAAOnB;QACT;IACF,GAAG;QAACJ;KAAU;IAEd,MAAMwB,UAAUrD,QAAQ;QACtB,MAAMiC,IAAI,IAAID,KAAKH;QACnBI,EAAEI,OAAO,CAACJ,EAAEG,OAAO,KAAK;QACxBH,EAAEqB,QAAQ,CAAC,IAAI,IAAI,IAAI;QACvB,OAAOrB;IACT,GAAG;QAACJ;KAAU;IAEd9B,UAAU;QACR,IAAI,CAACsB,OAAO;YAAC;QAAM;QAEnB,MAAMkC,YAAY;YAChBT,WAAW;YACX,MAAMU,UAAU,GAAGtC,OAAOuC,SAAS,IAAI,KAAKvC,OAAOwC,MAAM,CAACC,GAAG,EAAE;YAE/D,sEAAsE;YACtE,qEAAqE;YACrE,qDAAqD;YACrD,MAAMC,aAAajC,iBAAiBkC,IAAI,CAAC;YAEzC,IAAI;gBACF,MAAM,CAACC,cAAcC,cAAcC,gBAAgB,GAAG,MAAMC,QAAQC,GAAG,CAAC;oBACtEC,MAAM,GAAGX,QAAQ,CAAC,EAAEnC,MAAMkB,SAAS,CAAC,qCAAqC,CAAC;oBAC1E4B,MAAM,GAAGX,QAAQ,CAAC,EAAEnC,MAAMoB,SAAS,CAAC,qCAAqC,CAAC;oBAC1E0B,MACE,GAAGX,QAAQ,CAAC,EAAEnC,MAAMsB,YAAY,CAAC,CAAC,EAAE,IAAIyB,gBAAgB;wBACtDC,OAAO;wBACPC,OAAO;wBACP,wCAAwCzC,UAAU0C,WAAW;wBAC7D,qCAAqClB,QAAQkB,WAAW;wBACxD,qBAAqBX;oBACvB,IAAI;iBAEP;gBAED,MAAM,CAACY,OAAOC,OAAOC,QAAQ,GAAG,MAAMT,QAAQC,GAAG,CAAC;oBAChDJ,aAAaa,IAAI;oBACjBZ,aAAaY,IAAI;oBACjBX,gBAAgBW,IAAI;iBACrB;gBAEDnC,aAAagC,MAAMI,IAAI,IAAI,EAAE;gBAC7BlC,aAAa+B,MAAMG,IAAI,IAAI,EAAE;gBAC7BhC,gBAAgB8B,QAAQE,IAAI,IAAI,EAAE;YACpC,EAAE,OAAM;gBACNpC,aAAa,EAAE;gBACfE,aAAa,EAAE;gBACfE,gBAAgB,EAAE;YACpB;YACAE,WAAW;QACb;QAEA,KAAKS;IACL,wEAAwE;IACxE,uDAAuD;IACvD,uDAAuD;IACzD,GAAG;QAAC1B;QAAWwB;QAASnC,OAAOwC,MAAM,CAACC,GAAG;QAAEzC,OAAOuC,SAAS;QAAEpC;QAAOM,iBAAiBkC,IAAI,CAAC;KAAK;IAE/F,MAAMgB,eAAe/E,YAAY,CAACgF;QAChChD,aAAa,CAACiD;YACZ,MAAMC,OAAO,IAAIhD,KAAK+C;YACtBC,KAAK3C,OAAO,CAAC2C,KAAK5C,OAAO,KAAK,IAAI0C;YAClC,OAAOE;QACT;IACF,GAAG,EAAE;IAEL,MAAMC,eAAenF,YAAY;QAC/B,MAAMiC,MAAM,IAAIC;QAChB,MAAMC,IAAI,IAAID,KAAKD,IAAIG,WAAW,IAAIH,IAAII,QAAQ,IAAIJ,IAAIK,OAAO;QACjEH,EAAEI,OAAO,CAACJ,EAAEG,OAAO,KAAKH,EAAEK,MAAM;QAChCR,aAAaG;IACf,GAAG,EAAE;IAEL,MAAMiD,gBAAgB,CAACC,IACrB,OAAOA,MAAM,WAAWA,EAAEC,EAAE,GAAGD;IAEjC,MAAME,yBAAyB,CAACC,YAAoBC;QAClD,MAAMC,oBAAoB/C,UAAUgD,MAAM,CACxC,CAACC,IAAMR,cAAcQ,EAAEC,QAAQ,MAAML;QAEvC,MAAMM,UAAUL,IAAIhB,WAAW,GAAGsB,KAAK,CAAC,IAAI,CAAC,EAAE;QAC/C,MAAMC,YAAYP,IAAIjD,MAAM;QAE5B,MAAMyD,QAAmE,EAAE;QAE3E,KAAK,MAAMC,YAAYR,kBAAmB;YACxC,uBAAuB;YACvB,MAAMS,YAAYD,SAASE,UAAU,EAAEC,KAAK,CAACC;gBAC3C,MAAMC,UAAU,IAAIrE,KAAKoE,EAAEE,IAAI,EAAE/B,WAAW,GAAGsB,KAAK,CAAC,IAAI,CAAC,EAAE;gBAC5D,OAAOQ,YAAYT;YACrB;YAEA,IAAIK,WAAW;gBACbF,MAAMQ,IAAI,CAAC;oBACTC,MAAM;oBACNC,OAAOR,UAAUS,MAAM,IAAIvF,EAAE;gBAC/B;gBACA;YACF;YAEA,IAAI6E,SAASW,YAAY,KAAK,aAAa;gBACzC,KAAK,MAAMC,QAAQZ,SAASa,cAAc,IAAI,EAAE,CAAE;oBAChD,IAAI1G,OAAO,CAACyG,KAAKrB,GAAG,CAAC,KAAKO,WAAW;wBACnCC,MAAMQ,IAAI,CAAC;4BACTC,MAAM;4BACNC,OAAO,GAAGG,KAAKE,SAAS,CAAC,CAAC,EAAEF,KAAKG,OAAO,EAAE;wBAC5C;oBACF;gBACF;YACF,OAAO,IAAIf,SAASW,YAAY,KAAK,UAAU;gBAC7C,KAAK,MAAMC,QAAQZ,SAASgB,WAAW,IAAI,EAAE,CAAE;oBAC7C,MAAMC,WAAW,IAAIjF,KAAK4E,KAAKN,IAAI,EAAE/B,WAAW,GAAGsB,KAAK,CAAC,IAAI,CAAC,EAAE;oBAChE,IAAIoB,aAAarB,SAAS;wBACxBG,MAAMQ,IAAI,CAAC;4BACTC,MAAM;4BACNC,OAAO,GAAGG,KAAKE,SAAS,CAAC,CAAC,EAAEF,KAAKG,OAAO,EAAE;wBAC5C;oBACF;gBACF;YACF;QACF;QAEA,OAAOhB;IACT;IAEA,4EAA4E,GAC5E,MAAMmB,4BAA4B,CAAC5B,YAAoBC;QACrD,OAAO5C,aAAa8C,MAAM,CAAC,CAACN;YAC1B,MAAMgC,QAAQ,IAAInF,KAAKmD,EAAE2B,SAAS;YAClC,OACE5B,cAAcC,EAAEQ,QAAQ,MAAML,cAC9B6B,MAAMjF,WAAW,OAAOqD,IAAIrD,WAAW,MACvCiF,MAAMhF,QAAQ,OAAOoD,IAAIpD,QAAQ,MACjCgF,MAAM/E,OAAO,OAAOmD,IAAInD,OAAO;QAEnC;IACF;IAEA,IAAI,CAACf,OAAO;QACV,qBAAO,KAAC+F;YAAIC,WAAWnH,OAAOoH,WAAW;sBAAGnG,EAAE;;IAChD;IAEA,IAAI0B,SAAS;QACX,qBAAO,KAACuE;YAAIC,WAAWnH,OAAO2C,OAAO;sBAAG1B,EAAE;;IAC5C;IAEA,MAAMoG,YAAY,GAAGxE,QAAQ,CAAC,EAAE,CAACyE,kBAAkB,CAAC,EAAE,EAAE;QAAEjC,KAAK;QAAWkC,OAAO;IAAQ,GAAG,GAAG,EAAE1E,QAAQ,CAAC,EAAE,CAACyE,kBAAkB,CAAC,EAAE,EAAE;QAAEjC,KAAK;QAAWkC,OAAO;QAASC,MAAM;IAAU,IAAI;IAE1L,MAAMC,cAAc,CAAC,oBAAoB,CAAC;IAE1C,qBACE,MAACP;QAAIC,WAAWnH,OAAO0H,OAAO;;0BAC5B,KAACC;gBAAGR,WAAWnH,OAAO4H,KAAK;0BAAG3G,EAAE;;0BAChC,MAACiG;gBAAIC,WAAWnH,OAAO6H,UAAU;;kCAC/B,KAACC;wBAAOX,WAAWnH,OAAO+H,SAAS;wBAAEC,SAAS,IAAMrD,aAAa,CAAC;wBAAI2B,MAAK;kCAAS;;kCAGpF,KAACwB;wBAAOX,WAAWnH,OAAO+H,SAAS;wBAAEC,SAASjD;wBAAcuB,MAAK;kCAC9DrF,EAAE;;kCAEL,KAAC6G;wBAAOX,WAAWnH,OAAO+H,SAAS;wBAAEC,SAAS,IAAMrD,aAAa;wBAAI2B,MAAK;kCAAS;;kCAGnF,KAAC2B;wBAAKd,WAAWnH,OAAOqH,SAAS;kCAAGA;;;;YAGrChF,UAAUW,MAAM,KAAK,kBACpB,KAACkE;gBAAIC,WAAWnH,OAAOoH,WAAW;0BAAGnG,EAAE;+BAEvC,MAACiG;gBAAIC,WAAWnH,OAAOkI,IAAI;gBAAEC,OAAO;oBAAEC,qBAAqBX;gBAAY;;kCAErE,KAACP;wBAAIC,WAAWnH,OAAOqI,UAAU;kCAAGpH,EAAE;;oBACrC4B,SAASyF,GAAG,CAAC,CAACjD,KAAKnC,kBAClB,MAACgE;4BAAIC,WAAWnH,OAAOqI,UAAU;;gCAC9B3G,SAAS,CAAC2D,IAAIjD,MAAM,GAAG;gCAAC;gCAAEiD,IAAInD,OAAO;;2BADAgB;oBAMzCb,UAAUiG,GAAG,CAAC,CAAC7C;wBACd,MAAM8C,WAAW9C,SAAS8C,QAAQ,IAAI;wBACtC,qBACE,MAAC5I;;8CACC,MAACuH;oCAAIC,WAAWnH,OAAOwI,YAAY;;wCAChC/C,SAASgD,IAAI;wCACbF,WAAW,mBACV,MAACN;4CAAKE,OAAO;gDAAEO,YAAY;gDAAKC,YAAY;gDAAGC,SAAS;4CAAI;;gDACzD;gDAAI;gDAASL;gDAAS;;;;;gCAI5B1F,SAASyF,GAAG,CAAC,CAACjD,KAAKwD;oCAClB,MAAMhD,QAAQV,uBAAuBM,SAASP,EAAE,EAAEG;oCAClD,MAAMyD,WAAW9B,0BAA0BvB,SAASP,EAAE,EAAEG;oCACxD,MAAM0D,cAAcD,SAAS9F,MAAM;oCAEnC,qBACE,MAACkE;wCAAIC,WAAWnH,OAAOgJ,IAAI;;4CACxBnD,MAAMyC,GAAG,CAAC,CAAC5B,MAAMuC,mBAChB,KAAC/B;oDACCC,WACET,KAAKJ,IAAI,KAAK,cACVtG,OAAOkJ,aAAa,GACpBlJ,OAAOmJ,aAAa;8DAIzBzC,KAAKH,KAAK;mDAFN,CAAC,KAAK,EAAE0C,IAAI;4CAKpBV,WAAW,IACV,6DAA6D,GAC7DQ,cAAc,mBACZ,KAAC7B;gDACCC,WAAW1G,cAAcsI,aAAaR;gDACtCX,OAAO3G,EAAE,sCAAsC;oDAC7CP,QAAQqI;oDACRpI,OAAO4H;gDACT;0DAECtH,EAAE,sCAAsC;oDACvCP,QAAQqI;oDACRpI,OAAO4H;gDACT;iDAIJ,uDAAuD,GACvDO,SAASR,GAAG,CAAC,CAACc,kBACZ,MAAClC;oDAAIC,WAAWnH,OAAOqJ,UAAU;;wDAC9B,IAAIvH,KAAKsH,EAAExC,SAAS,EAAE0C,kBAAkB,CAAC,EAAE,EAAE;4DAC5CC,MAAM;4DACNC,QAAQ;wDACV;wDAAI;wDACHvI,EAAE;;mDALmCmI,EAAElE,EAAE;;uCAhChB,CAAC,KAAK,EAAEO,SAASP,EAAE,CAAC,CAAC,EAAE2D,IAAI;gCA2CjE;;2BA1DapD,SAASP,EAAE;oBA6D9B;;;;;AAKV,EAAC"}
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 { getDayKeyInTimezone, getDayOfWeekFromDayKey } from '../../utilities/timezoneUtils.js'\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; endDate?: 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 const reservationTimezone: string = config.admin?.custom?.reservationTimezone ?? 'UTC'\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 // Cells are browser-local midnights by design; every key derived from them\n // (day keys, weekday matching) is computed in the business timezone.\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 // Ignore a slow earlier fetch if the week changed before it resolved (D5)\n let stale = false\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: '1000',\n 'where[active][equals]': 'true',\n ...schedulesTenantParams,\n })\n const reservationsParams = new URLSearchParams({\n depth: '0',\n limit: '2000',\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 if (stale) {return}\n setResources(rData.docs ?? [])\n setSchedules(sData.docs ?? [])\n setReservations(resData.docs ?? [])\n } catch {\n if (stale) {return}\n setResources([])\n setSchedules([])\n setReservations([])\n }\n if (!stale) {setLoading(false)}\n }\n\n void fetchData()\n return () => {\n stale = true\n }\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 = getDayKeyInTimezone(day, reservationTimezone)\n const dayOfWeek = getDayOfWeekFromDayKey(dateStr)\n\n const slots: Array<{ label: string; type: 'available' | 'exception' }> = []\n\n for (const schedule of resourceSchedules) {\n // Check for exceptions — match the full [date, endDate] range inclusively\n // (review D4), keyed in the business timezone, like the server does.\n const exception = schedule.exceptions?.find((e) => {\n const start = getDayKeyInTimezone(new Date(e.date), reservationTimezone)\n const end = e.endDate ? getDayKeyInTimezone(new Date(e.endDate), reservationTimezone) : start\n return dateStr >= start && dateStr <= end\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 (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 = getDayKeyInTimezone(new Date(slot.date), reservationTimezone)\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 const dayKey = getDayKeyInTimezone(day, reservationTimezone)\n return reservations.filter((r) => {\n return (\n getResourceId(r.resource) === resourceId &&\n getDayKeyInTimezone(new Date(r.startTime), reservationTimezone) === dayKey\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', timeZone: reservationTimezone })} - ${weekDays[6].toLocaleDateString([], { day: 'numeric', month: 'short', timeZone: reservationTimezone, 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 const dayKey = getDayKeyInTimezone(day, reservationTimezone)\n return (\n <div className={styles.headerCell} key={i}>\n {DAY_NAMES[DAY_MAP[getDayOfWeekFromDayKey(dayKey)]]} {Number(dayKey.slice(8, 10))}\n </div>\n )\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 timeZone: reservationTimezone,\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","getDayKeyInTimezone","getDayOfWeekFromDayKey","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","reservationTimezone","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","stale","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","dayOfWeek","slots","schedule","exception","exceptions","find","e","start","date","end","endDate","push","type","label","reason","scheduleType","slot","recurringSlots","startTime","endTime","manualSlots","slotDate","getBookingsForResourceDay","dayKey","div","className","noResources","weekLabel","toLocaleDateString","month","timeZone","year","gridColumns","wrapper","h2","title","navigation","button","navButton","onClick","span","grid","style","gridTemplateColumns","headerCell","map","Number","slice","quantity","resourceName","name","fontWeight","marginLeft","opacity","di","bookings","bookedCount","cell","si","slotException","slotAvailable","b","slotBooked","toLocaleTimeString","hour","minute"],"mappings":"AAAA;;AAGA,SAASA,SAAS,EAAEC,cAAc,QAAQ,iBAAgB;AAC1D,SAASC,QAAQ,EAAEC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,QAAQ,QAAQ,QAAO;AAI3E,SAASC,mBAAmB,EAAEC,sBAAsB,QAAQ,mCAAkC;AAC9F,SAASC,eAAe,QAAQ,qCAAoC;AACpE,OAAOC,YAAY,oCAAmC;AA4BtD,MAAMC,UAAkC;IACtCC,KAAK;IACLC,KAAK;IACLC,KAAK;IACLC,KAAK;IACLC,KAAK;IACLC,KAAK;IACLC,KAAK;AACP;AAEA,0EAA0E,GAC1E,SAASC,cAAcC,MAAc,EAAEC,KAAa;IAClD,IAAID,UAAUC,OAAO;QAAC,OAAOX,OAAOY,gBAAgB;IAAA;IACpD,IAAIF,SAASC,SAAS,KAAK;QAAC,OAAOX,OAAOa,eAAe;IAAA;IACzD,OAAOb,OAAOc,eAAe;AAC/B;AAEA,OAAO,MAAMC,uBAAuD;IAClE,MAAM,EAAEC,MAAM,EAAE,GAAG1B;IACnB,MAAM,EAAE2B,GAAGC,EAAE,EAAE,GAAG3B;IAClB,MAAM0B,IAAIC;IACV,MAAMC,QAAQH,OAAOI,KAAK,EAAEC,QAAQC;IACpC,MAAMC,gBAAgBP,OAAOI,KAAK,EAAEC,QAAQG;IAC5C,MAAMC,mBAA6BF,eAAeE,oBAAoB;QAAC;QAAW;KAAY;IAC9F,MAAMC,sBAA8BV,OAAOI,KAAK,EAAEC,QAAQK,uBAAuB;IAEjF,MAAMC,wBAAwB5B,gBAAgBoB,OAAOS,aAAa;IAClE,MAAMC,wBAAwB9B,gBAAgBoB,OAAOW,aAAa;IAClE,MAAMC,2BAA2BhC,gBAAgBoB,OAAOa,gBAAgB;IAExE,MAAMC,YAAYtC,QAChB,IAAM;YACJsB,EAAE;YACFA,EAAE;YACFA,EAAE;YACFA,EAAE;YACFA,EAAE;YACFA,EAAE;YACFA,EAAE;SACH,EACD;QAACA;KAAE;IAGL,MAAM,CAACiB,WAAWC,aAAa,GAAGvC,SAAS;QACzC,MAAMwC,MAAM,IAAIC;QAChB,MAAMC,IAAI,IAAID,KAAKD,IAAIG,WAAW,IAAIH,IAAII,QAAQ,IAAIJ,IAAIK,OAAO;QACjEH,EAAEI,OAAO,CAACJ,EAAEG,OAAO,KAAKH,EAAEK,MAAM;QAChC,OAAOL;IACT;IAEA,MAAM,CAACV,WAAWgB,aAAa,GAAGhD,SAAqB,EAAE;IACzD,MAAM,CAACkC,WAAWe,aAAa,GAAGjD,SAAqB,EAAE;IACzD,MAAM,CAACoC,cAAcc,gBAAgB,GAAGlD,SAAwB,EAAE;IAClE,MAAM,CAACmD,SAASC,WAAW,GAAGpD,SAAS;IAEvC,2EAA2E;IAC3E,qEAAqE;IACrE,MAAMqD,WAAWtD,QAAQ;QACvB,OAAOuD,MAAMC,IAAI,CAAC;YAAEC,QAAQ;QAAE,GAAG,CAACC,GAAGC;YACnC,MAAMhB,IAAI,IAAID,KAAKH;YACnBI,EAAEI,OAAO,CAACJ,EAAEG,OAAO,KAAKa;YACxB,OAAOhB;QACT;IACF,GAAG;QAACJ;KAAU;IAEd,MAAMqB,UAAU5D,QAAQ;QACtB,MAAM2C,IAAI,IAAID,KAAKH;QACnBI,EAAEI,OAAO,CAACJ,EAAEG,OAAO,KAAK;QACxBH,EAAEkB,QAAQ,CAAC,IAAI,IAAI,IAAI;QACvB,OAAOlB;IACT,GAAG;QAACJ;KAAU;IAEdxC,UAAU;QACR,IAAI,CAACyB,OAAO;YAAC;QAAM;QAEnB,0EAA0E;QAC1E,IAAIsC,QAAQ;QAEZ,MAAMC,YAAY;YAChBV,WAAW;YACX,MAAMW,UAAU,GAAG3C,OAAO4C,SAAS,IAAI,KAAK5C,OAAO6C,MAAM,CAACC,GAAG,EAAE;YAE/D,sEAAsE;YACtE,qEAAqE;YACrE,qDAAqD;YACrD,MAAMC,aAAatC,iBAAiBuC,IAAI,CAAC;YAEzC,IAAI;gBACF,MAAMC,kBAAkB,IAAIC,gBAAgB;oBAC1CC,OAAO;oBACP,yBAAyB;oBACzB,GAAGxC,qBAAqB;gBAC1B;gBACA,MAAMyC,kBAAkB,IAAIF,gBAAgB;oBAC1CC,OAAO;oBACP,yBAAyB;oBACzB,GAAGtC,qBAAqB;gBAC1B;gBACA,MAAMwC,qBAAqB,IAAIH,gBAAgB;oBAC7CI,OAAO;oBACPH,OAAO;oBACP,wCAAwCjC,UAAUqC,WAAW;oBAC7D,qCAAqChB,QAAQgB,WAAW;oBACxD,qBAAqBR;oBACrB,GAAGhC,wBAAwB;gBAC7B;gBACA,MAAM,CAACyC,cAAcC,cAAcC,gBAAgB,GAAG,MAAMC,QAAQC,GAAG,CAAC;oBACtEC,MAAM,GAAGlB,QAAQ,CAAC,EAAExC,MAAMS,SAAS,CAAC,CAAC,EAAEqC,iBAAiB;oBACxDY,MAAM,GAAGlB,QAAQ,CAAC,EAAExC,MAAMW,SAAS,CAAC,CAAC,EAAEsC,iBAAiB;oBACxDS,MAAM,GAAGlB,QAAQ,CAAC,EAAExC,MAAMa,YAAY,CAAC,CAAC,EAAEqC,oBAAoB;iBAC/D;gBAED,MAAM,CAACS,OAAOC,OAAOC,QAAQ,GAAG,MAAML,QAAQC,GAAG,CAAC;oBAChDJ,aAAaS,IAAI;oBACjBR,aAAaQ,IAAI;oBACjBP,gBAAgBO,IAAI;iBACrB;gBAED,IAAIxB,OAAO;oBAAC;gBAAM;gBAClBb,aAAakC,MAAMI,IAAI,IAAI,EAAE;gBAC7BrC,aAAakC,MAAMG,IAAI,IAAI,EAAE;gBAC7BpC,gBAAgBkC,QAAQE,IAAI,IAAI,EAAE;YACpC,EAAE,OAAM;gBACN,IAAIzB,OAAO;oBAAC;gBAAM;gBAClBb,aAAa,EAAE;gBACfC,aAAa,EAAE;gBACfC,gBAAgB,EAAE;YACpB;YACA,IAAI,CAACW,OAAO;gBAACT,WAAW;YAAM;QAChC;QAEA,KAAKU;QACL,OAAO;YACLD,QAAQ;QACV;IACA,wEAAwE;IACxE,uDAAuD;IACvD,uDAAuD;IACzD,GAAG;QAACvB;QAAWqB;QAASvC,OAAO6C,MAAM,CAACC,GAAG;QAAE9C,OAAO4C,SAAS;QAAEzC;QAAOM,iBAAiBuC,IAAI,CAAC;QAAMrC;QAAuBE;QAAuBE;KAAyB;IAEvK,MAAMoD,eAAe1F,YAAY,CAAC2F;QAChCjD,aAAa,CAACkD;YACZ,MAAMC,OAAO,IAAIjD,KAAKgD;YACtBC,KAAK5C,OAAO,CAAC4C,KAAK7C,OAAO,KAAK,IAAI2C;YAClC,OAAOE;QACT;IACF,GAAG,EAAE;IAEL,MAAMC,eAAe9F,YAAY;QAC/B,MAAM2C,MAAM,IAAIC;QAChB,MAAMC,IAAI,IAAID,KAAKD,IAAIG,WAAW,IAAIH,IAAII,QAAQ,IAAIJ,IAAIK,OAAO;QACjEH,EAAEI,OAAO,CAACJ,EAAEG,OAAO,KAAKH,EAAEK,MAAM;QAChCR,aAAaG;IACf,GAAG,EAAE;IAEL,MAAMkD,gBAAgB,CAACC,IACrB,OAAOA,MAAM,WAAWA,EAAEC,EAAE,GAAGD;IAEjC,MAAME,yBAAyB,CAACC,YAAoBC;QAClD,MAAMC,oBAAoBhE,UAAUiE,MAAM,CACxC,CAACC,IAAMR,cAAcQ,EAAEC,QAAQ,MAAML;QAEvC,MAAMM,UAAUrG,oBAAoBgG,KAAKnE;QACzC,MAAMyE,YAAYrG,uBAAuBoG;QAEzC,MAAME,QAAmE,EAAE;QAE3E,KAAK,MAAMC,YAAYP,kBAAmB;YACxC,0EAA0E;YAC1E,qEAAqE;YACrE,MAAMQ,YAAYD,SAASE,UAAU,EAAEC,KAAK,CAACC;gBAC3C,MAAMC,QAAQ7G,oBAAoB,IAAIwC,KAAKoE,EAAEE,IAAI,GAAGjF;gBACpD,MAAMkF,MAAMH,EAAEI,OAAO,GAAGhH,oBAAoB,IAAIwC,KAAKoE,EAAEI,OAAO,GAAGnF,uBAAuBgF;gBACxF,OAAOR,WAAWQ,SAASR,WAAWU;YACxC;YAEA,IAAIN,WAAW;gBACbF,MAAMU,IAAI,CAAC;oBACTC,MAAM;oBACNC,OAAOV,UAAUW,MAAM,IAAIhG,EAAE;gBAC/B;gBACA;YACF;YAEA,IAAIoF,SAASa,YAAY,KAAK,aAAa;gBACzC,KAAK,MAAMC,QAAQd,SAASe,cAAc,IAAI,EAAE,CAAE;oBAChD,IAAID,KAAKtB,GAAG,KAAKM,WAAW;wBAC1BC,MAAMU,IAAI,CAAC;4BACTC,MAAM;4BACNC,OAAO,GAAGG,KAAKE,SAAS,CAAC,CAAC,EAAEF,KAAKG,OAAO,EAAE;wBAC5C;oBACF;gBACF;YACF,OAAO,IAAIjB,SAASa,YAAY,KAAK,UAAU;gBAC7C,KAAK,MAAMC,QAAQd,SAASkB,WAAW,IAAI,EAAE,CAAE;oBAC7C,MAAMC,WAAW3H,oBAAoB,IAAIwC,KAAK8E,KAAKR,IAAI,GAAGjF;oBAC1D,IAAI8F,aAAatB,SAAS;wBACxBE,MAAMU,IAAI,CAAC;4BACTC,MAAM;4BACNC,OAAO,GAAGG,KAAKE,SAAS,CAAC,CAAC,EAAEF,KAAKG,OAAO,EAAE;wBAC5C;oBACF;gBACF;YACF;QACF;QAEA,OAAOlB;IACT;IAEA,4EAA4E,GAC5E,MAAMqB,4BAA4B,CAAC7B,YAAoBC;QACrD,MAAM6B,SAAS7H,oBAAoBgG,KAAKnE;QACxC,OAAOM,aAAa+D,MAAM,CAAC,CAACN;YAC1B,OACED,cAAcC,EAAEQ,QAAQ,MAAML,cAC9B/F,oBAAoB,IAAIwC,KAAKoD,EAAE4B,SAAS,GAAG3F,yBAAyBgG;QAExE;IACF;IAEA,IAAI,CAACvG,OAAO;QACV,qBAAO,KAACwG;YAAIC,WAAW5H,OAAO6H,WAAW;sBAAG5G,EAAE;;IAChD;IAEA,IAAI8B,SAAS;QACX,qBAAO,KAAC4E;YAAIC,WAAW5H,OAAO+C,OAAO;sBAAG9B,EAAE;;IAC5C;IAEA,MAAM6G,YAAY,GAAG7E,QAAQ,CAAC,EAAE,CAAC8E,kBAAkB,CAAC,EAAE,EAAE;QAAElC,KAAK;QAAWmC,OAAO;QAASC,UAAUvG;IAAoB,GAAG,GAAG,EAAEuB,QAAQ,CAAC,EAAE,CAAC8E,kBAAkB,CAAC,EAAE,EAAE;QAAElC,KAAK;QAAWmC,OAAO;QAASC,UAAUvG;QAAqBwG,MAAM;IAAU,IAAI;IAExP,MAAMC,cAAc,CAAC,oBAAoB,CAAC;IAE1C,qBACE,MAACR;QAAIC,WAAW5H,OAAOoI,OAAO;;0BAC5B,KAACC;gBAAGT,WAAW5H,OAAOsI,KAAK;0BAAGrH,EAAE;;0BAChC,MAAC0G;gBAAIC,WAAW5H,OAAOuI,UAAU;;kCAC/B,KAACC;wBAAOZ,WAAW5H,OAAOyI,SAAS;wBAAEC,SAAS,IAAMvD,aAAa,CAAC;wBAAI4B,MAAK;kCAAS;;kCAGpF,KAACyB;wBAAOZ,WAAW5H,OAAOyI,SAAS;wBAAEC,SAASnD;wBAAcwB,MAAK;kCAC9D9F,EAAE;;kCAEL,KAACuH;wBAAOZ,WAAW5H,OAAOyI,SAAS;wBAAEC,SAAS,IAAMvD,aAAa;wBAAI4B,MAAK;kCAAS;;kCAGnF,KAAC4B;wBAAKf,WAAW5H,OAAO8H,SAAS;kCAAGA;;;;YAGrClG,UAAUwB,MAAM,KAAK,kBACpB,KAACuE;gBAAIC,WAAW5H,OAAO6H,WAAW;0BAAG5G,EAAE;+BAEvC,MAAC0G;gBAAIC,WAAW5H,OAAO4I,IAAI;gBAAEC,OAAO;oBAAEC,qBAAqBX;gBAAY;;kCAErE,KAACR;wBAAIC,WAAW5H,OAAO+I,UAAU;kCAAG9H,EAAE;;oBACrCgC,SAAS+F,GAAG,CAAC,CAACnD,KAAKvC;wBAClB,MAAMoE,SAAS7H,oBAAoBgG,KAAKnE;wBACxC,qBACE,MAACiG;4BAAIC,WAAW5H,OAAO+I,UAAU;;gCAC9B9G,SAAS,CAAChC,OAAO,CAACH,uBAAuB4H,QAAQ,CAAC;gCAAC;gCAAEuB,OAAOvB,OAAOwB,KAAK,CAAC,GAAG;;2BADvC5F;oBAI5C;oBAGC1B,UAAUoH,GAAG,CAAC,CAAC/C;wBACd,MAAMkD,WAAWlD,SAASkD,QAAQ,IAAI;wBACtC,qBACE,MAAC3J;;8CACC,MAACmI;oCAAIC,WAAW5H,OAAOoJ,YAAY;;wCAChCnD,SAASoD,IAAI;wCACbF,WAAW,mBACV,MAACR;4CAAKE,OAAO;gDAAES,YAAY;gDAAKC,YAAY;gDAAGC,SAAS;4CAAI;;gDACzD;gDAAI;gDAASL;gDAAS;;;;;gCAI5BlG,SAAS+F,GAAG,CAAC,CAACnD,KAAK4D;oCAClB,MAAMrD,QAAQT,uBAAuBM,SAASP,EAAE,EAAEG;oCAClD,MAAM6D,WAAWjC,0BAA0BxB,SAASP,EAAE,EAAEG;oCACxD,MAAM8D,cAAcD,SAAStG,MAAM;oCAEnC,qBACE,MAACuE;wCAAIC,WAAW5H,OAAO4J,IAAI;;4CACxBxD,MAAM4C,GAAG,CAAC,CAAC7B,MAAM0C,mBAChB,KAAClC;oDACCC,WACET,KAAKJ,IAAI,KAAK,cACV/G,OAAO8J,aAAa,GACpB9J,OAAO+J,aAAa;8DAIzB5C,KAAKH,KAAK;mDAFN,CAAC,KAAK,EAAE6C,IAAI;4CAKpBV,WAAW,IACV,6DAA6D,GAC7DQ,cAAc,mBACZ,KAAChC;gDACCC,WAAWnH,cAAckJ,aAAaR;gDACtCb,OAAOrH,EAAE,sCAAsC;oDAC7CP,QAAQiJ;oDACRhJ,OAAOwI;gDACT;0DAEClI,EAAE,sCAAsC;oDACvCP,QAAQiJ;oDACRhJ,OAAOwI;gDACT;iDAIJ,uDAAuD,GACvDO,SAASV,GAAG,CAAC,CAACgB,kBACZ,MAACrC;oDAAIC,WAAW5H,OAAOiK,UAAU;;wDAC9B,IAAI5H,KAAK2H,EAAE3C,SAAS,EAAE6C,kBAAkB,CAAC,EAAE,EAAE;4DAC5CC,MAAM;4DACNC,QAAQ;4DACRnC,UAAUvG;wDACZ;wDAAI;wDACHT,EAAE;;mDANmC+I,EAAEtE,EAAE;;uCAhChB,CAAC,KAAK,EAAEO,SAASP,EAAE,CAAC,CAAC,EAAE+D,IAAI;gCA4CjE;;2BA3DaxD,SAASP,EAAE;oBA8D9B;;;;;AAKV,EAAC"}
@@ -461,6 +461,15 @@
461
461
  margin-bottom: 12px;
462
462
  }
463
463
 
464
+ .truncationNotice {
465
+ margin-bottom: 12px;
466
+ padding: 8px 12px;
467
+ border-radius: 4px;
468
+ background: var(--theme-warning-100, #fef3c7);
469
+ color: var(--theme-warning-700, #92400e);
470
+ font-size: 0.85rem;
471
+ }
472
+
464
473
  .resourceFilter {
465
474
  appearance: none;
466
475
  background: var(--theme-bg);
@@ -3,10 +3,13 @@ type LaneResource = {
3
3
  id: string;
4
4
  name: string;
5
5
  };
6
- export declare function LaneTimelineView({ apiBase, day, onBook, resources, }: {
6
+ export declare function LaneTimelineView({ apiBase, day, endHour, onBook, resources, startHour, timeZone, }: {
7
7
  apiBase: string;
8
8
  day: Date;
9
+ endHour: number;
9
10
  onBook: (resourceId: string, startIso: string) => void;
10
11
  resources: LaneResource[];
12
+ startHour: number;
13
+ timeZone: string;
11
14
  }): React.JSX.Element;
12
15
  export {};
@@ -3,26 +3,24 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useTranslation } from '@payloadcms/ui';
4
4
  import React from 'react';
5
5
  import { computeSlotStates } from '../../utilities/computeSlotStates.js';
6
- import { localDayKey } from '../../utilities/slotUtils.js';
6
+ import { getDayKeyInTimezone } from '../../utilities/timezoneUtils.js';
7
7
  import styles from './CalendarView.module.css';
8
8
  import { useResourceAvailability } from './useResourceAvailability.js';
9
- const HOUR_START = 7;
10
- const HOUR_END = 20;
11
9
  const SLOT_STATE_KEYS = {
12
10
  free: 'reservation:slotFree',
13
11
  full: 'reservation:slotFull',
14
12
  'off-shift': 'reservation:slotOffShift',
15
13
  'time-off': 'reservation:slotTimeOff'
16
14
  };
17
- function Lane({ apiBase, day, onBook, resource }) {
15
+ function Lane({ apiBase, day, endHour, onBook, resource, startHour, timeZone }) {
18
16
  const { t: _t } = useTranslation();
19
17
  const t = _t;
20
18
  const dayStart = new Date(day);
21
- dayStart.setHours(HOUR_START, 0, 0, 0);
19
+ dayStart.setHours(startHour, 0, 0, 0);
22
20
  const dayEnd = new Date(day);
23
- dayEnd.setHours(HOUR_END, 0, 0, 0);
21
+ dayEnd.setHours(endHour, 0, 0, 0);
24
22
  const { data } = useResourceAvailability(apiBase, resource.id, dayStart, dayEnd);
25
- const isoDay = localDayKey(day);
23
+ const isoDay = getDayKeyInTimezone(day, timeZone);
26
24
  const dayAvail = data?.days.find((d)=>d.date === isoDay);
27
25
  const slots = dayAvail ? computeSlotStates({
28
26
  busy: data.busy,
@@ -47,7 +45,11 @@ function Lane({ apiBase, day, onBook, resource }) {
47
45
  children: slots.map((s)=>{
48
46
  const cls = s.state === 'off-shift' ? styles.slotOffShift : s.state === 'time-off' ? styles.slotTimeOff : s.state === 'full' ? styles.slotFull : styles.slotFree;
49
47
  const isFree = s.state === 'free';
50
- const slotLabel = `${s.start.toLocaleTimeString()} — ${t(SLOT_STATE_KEYS[s.state])}`;
48
+ const slotLabel = `${s.start.toLocaleTimeString([], {
49
+ hour: '2-digit',
50
+ minute: '2-digit',
51
+ timeZone
52
+ })} — ${t(SLOT_STATE_KEYS[s.state])}`;
51
53
  return isFree ? /*#__PURE__*/ _jsx("div", {
52
54
  "aria-label": slotLabel,
53
55
  className: `${styles.laneCell} ${cls}`,
@@ -70,7 +72,7 @@ function Lane({ apiBase, day, onBook, resource }) {
70
72
  ]
71
73
  });
72
74
  }
73
- export function LaneTimelineView({ apiBase, day, onBook, resources }) {
75
+ export function LaneTimelineView({ apiBase, day, endHour, onBook, resources, startHour, timeZone }) {
74
76
  const { t: _t } = useTranslation();
75
77
  const t = _t;
76
78
  if (resources.length === 0) {
@@ -80,8 +82,8 @@ export function LaneTimelineView({ apiBase, day, onBook, resources }) {
80
82
  });
81
83
  }
82
84
  const hours = Array.from({
83
- length: HOUR_END - HOUR_START
84
- }, (_, i)=>HOUR_START + i);
85
+ length: endHour - startHour
86
+ }, (_, i)=>startHour + i);
85
87
  return /*#__PURE__*/ _jsxs("div", {
86
88
  className: styles.lanes,
87
89
  children: [
@@ -106,8 +108,11 @@ export function LaneTimelineView({ apiBase, day, onBook, resources }) {
106
108
  resources.map((r)=>/*#__PURE__*/ _jsx(Lane, {
107
109
  apiBase: apiBase,
108
110
  day: day,
111
+ endHour: endHour,
109
112
  onBook: onBook,
110
- resource: r
113
+ resource: r,
114
+ startHour: startHour,
115
+ timeZone: timeZone
111
116
  }, r.id))
112
117
  ]
113
118
  });
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/CalendarView/LaneTimelineView.tsx"],"sourcesContent":["'use client'\nimport { useTranslation } from '@payloadcms/ui'\nimport React from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { SlotState } from '../../utilities/computeSlotStates.js'\n\nimport { computeSlotStates } from '../../utilities/computeSlotStates.js'\nimport { 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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"}
1
+ {"version":3,"sources":["../../../src/components/CalendarView/LaneTimelineView.tsx"],"sourcesContent":["'use client'\nimport { useTranslation } from '@payloadcms/ui'\nimport React from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\nimport type { SlotState } from '../../utilities/computeSlotStates.js'\n\nimport { computeSlotStates } from '../../utilities/computeSlotStates.js'\nimport { getDayKeyInTimezone } from '../../utilities/timezoneUtils.js'\nimport styles from './CalendarView.module.css'\nimport { useResourceAvailability } from './useResourceAvailability.js'\n\ntype LaneResource = { id: string; name: string }\n\nconst SLOT_STATE_KEYS: Record<SlotState, string> = {\n free: 'reservation:slotFree',\n full: 'reservation:slotFull',\n 'off-shift': 'reservation:slotOffShift',\n 'time-off': 'reservation:slotTimeOff',\n}\n\nfunction Lane({\n apiBase,\n day,\n endHour,\n onBook,\n resource,\n startHour,\n timeZone,\n}: {\n apiBase: string\n day: Date\n endHour: number\n onBook: (resourceId: string, startIso: string) => void\n resource: LaneResource\n startHour: number\n timeZone: string\n}) {\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n\n const dayStart = new Date(day)\n dayStart.setHours(startHour, 0, 0, 0)\n const dayEnd = new Date(day)\n dayEnd.setHours(endHour, 0, 0, 0)\n\n const { data } = useResourceAvailability(apiBase, resource.id, dayStart, dayEnd)\n const isoDay = getDayKeyInTimezone(day, timeZone)\n const dayAvail = data?.days.find((d) => d.date === isoDay)\n\n const slots = dayAvail\n ? computeSlotStates({\n busy: data!.busy,\n capacityMode: data!.capacityMode,\n dayEnd,\n dayStart,\n quantity: data!.quantity,\n requiredPools: data!.requiredPools,\n shiftWindows: dayAvail.shiftWindows,\n step: 60,\n timeOff: dayAvail.timeOff,\n })\n : []\n\n return (\n <div className={styles.lane}>\n <div className={styles.laneLabel}>{resource.name}</div>\n <div className={styles.laneTrack}>\n {slots.map((s) => {\n const cls =\n s.state === 'off-shift'\n ? styles.slotOffShift\n : s.state === 'time-off'\n ? styles.slotTimeOff\n : s.state === 'full'\n ? styles.slotFull\n : styles.slotFree\n const isFree = s.state === 'free'\n const slotLabel = `${s.start.toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n timeZone,\n })} — ${t(SLOT_STATE_KEYS[s.state])}`\n return isFree ? (\n <div\n aria-label={slotLabel}\n className={`${styles.laneCell} ${cls}`}\n key={s.start.toISOString()}\n onClick={() => onBook(resource.id, s.start.toISOString())}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n onBook(resource.id, s.start.toISOString())\n }\n }}\n role=\"button\"\n tabIndex={0}\n title={slotLabel}\n />\n ) : (\n <div\n className={`${styles.laneCell} ${cls}`}\n key={s.start.toISOString()}\n title={slotLabel}\n />\n )\n })}\n </div>\n </div>\n )\n}\n\nexport function LaneTimelineView({\n apiBase,\n day,\n endHour,\n onBook,\n resources,\n startHour,\n timeZone,\n}: {\n apiBase: string\n day: Date\n endHour: number\n onBook: (resourceId: string, startIso: string) => void\n resources: LaneResource[]\n startHour: number\n timeZone: string\n}) {\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n if (resources.length === 0) {\n return <p className={styles.hint}>{t('reservation:laneNoResources')}</p>\n }\n const hours = Array.from({ length: endHour - startHour }, (_, i) => startHour + i)\n return (\n <div className={styles.lanes}>\n <div className={styles.laneHeader}>\n <div className={styles.laneLabel} />\n <div className={styles.laneTrack}>\n {hours.map((h) => (\n <div className={styles.laneTime} key={h}>\n {String(h).padStart(2, '0')}:00\n </div>\n ))}\n </div>\n </div>\n {resources.map((r) => (\n <Lane\n apiBase={apiBase}\n day={day}\n endHour={endHour}\n key={r.id}\n onBook={onBook}\n resource={r}\n startHour={startHour}\n timeZone={timeZone}\n />\n ))}\n </div>\n )\n}\n"],"names":["useTranslation","React","computeSlotStates","getDayKeyInTimezone","styles","useResourceAvailability","SLOT_STATE_KEYS","free","full","Lane","apiBase","day","endHour","onBook","resource","startHour","timeZone","t","_t","dayStart","Date","setHours","dayEnd","data","id","isoDay","dayAvail","days","find","d","date","slots","busy","capacityMode","quantity","requiredPools","shiftWindows","step","timeOff","div","className","lane","laneLabel","name","laneTrack","map","s","cls","state","slotOffShift","slotTimeOff","slotFull","slotFree","isFree","slotLabel","start","toLocaleTimeString","hour","minute","aria-label","laneCell","onClick","toISOString","onKeyDown","e","key","preventDefault","role","tabIndex","title","LaneTimelineView","resources","length","p","hint","hours","Array","from","_","i","lanes","laneHeader","h","laneTime","String","padStart","r"],"mappings":"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"}