payload-reserve 1.6.0 → 2.1.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 +55 -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 +76 -18
  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 +166 -44
  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 +91 -18
  21. package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
  22. package/dist/defaults.js +44 -9
  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/effectiveTimezone.d.ts +13 -0
  33. package/dist/endpoints/effectiveTimezone.js +41 -0
  34. package/dist/endpoints/effectiveTimezone.js.map +1 -0
  35. package/dist/endpoints/getSlots.js +56 -7
  36. package/dist/endpoints/getSlots.js.map +1 -1
  37. package/dist/endpoints/resourceAvailability.d.ts +4 -1
  38. package/dist/endpoints/resourceAvailability.js +102 -26
  39. package/dist/endpoints/resourceAvailability.js.map +1 -1
  40. package/dist/hooks/reservations/calculateEndTime.js +48 -20
  41. package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
  42. package/dist/hooks/reservations/enforceCustomerOwnership.d.ts +11 -0
  43. package/dist/hooks/reservations/enforceCustomerOwnership.js +30 -0
  44. package/dist/hooks/reservations/enforceCustomerOwnership.js.map +1 -0
  45. package/dist/hooks/reservations/onStatusChange.js +10 -4
  46. package/dist/hooks/reservations/onStatusChange.js.map +1 -1
  47. package/dist/hooks/reservations/validateCancellation.js +3 -2
  48. package/dist/hooks/reservations/validateCancellation.js.map +1 -1
  49. package/dist/hooks/reservations/validateConflicts.js +23 -4
  50. package/dist/hooks/reservations/validateConflicts.js.map +1 -1
  51. package/dist/hooks/reservations/validateGuestBooking.js +3 -4
  52. package/dist/hooks/reservations/validateGuestBooking.js.map +1 -1
  53. package/dist/hooks/reservations/validateStatusTransition.js +2 -2
  54. package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
  55. package/dist/hooks/users/provisionStaffResource.js +5 -8
  56. package/dist/hooks/users/provisionStaffResource.js.map +1 -1
  57. package/dist/plugin.js +83 -14
  58. package/dist/plugin.js.map +1 -1
  59. package/dist/services/AvailabilityService.d.ts +54 -2
  60. package/dist/services/AvailabilityService.js +180 -46
  61. package/dist/services/AvailabilityService.js.map +1 -1
  62. package/dist/translations/ar.json +1 -0
  63. package/dist/translations/de.json +1 -0
  64. package/dist/translations/en.json +1 -0
  65. package/dist/translations/es.json +1 -0
  66. package/dist/translations/fa.json +1 -0
  67. package/dist/translations/fr.json +1 -0
  68. package/dist/translations/hi.json +1 -0
  69. package/dist/translations/id.json +1 -0
  70. package/dist/translations/pl.json +1 -0
  71. package/dist/translations/ru.json +1 -0
  72. package/dist/translations/tr.json +1 -0
  73. package/dist/translations/zh.json +1 -0
  74. package/dist/types.d.ts +46 -1
  75. package/dist/types.js +2 -0
  76. package/dist/types.js.map +1 -1
  77. package/dist/utilities/collectionOverrides.d.ts +14 -0
  78. package/dist/utilities/collectionOverrides.js +47 -0
  79. package/dist/utilities/collectionOverrides.js.map +1 -0
  80. package/dist/utilities/ownerAccess.d.ts +6 -0
  81. package/dist/utilities/ownerAccess.js +25 -12
  82. package/dist/utilities/ownerAccess.js.map +1 -1
  83. package/dist/utilities/reservationChanges.d.ts +17 -0
  84. package/dist/utilities/reservationChanges.js +88 -0
  85. package/dist/utilities/reservationChanges.js.map +1 -0
  86. package/dist/utilities/scheduleUtils.d.ts +14 -8
  87. package/dist/utilities/scheduleUtils.js +26 -19
  88. package/dist/utilities/scheduleUtils.js.map +1 -1
  89. package/dist/utilities/tenantTimezone.d.ts +41 -0
  90. package/dist/utilities/tenantTimezone.js +77 -0
  91. package/dist/utilities/tenantTimezone.js.map +1 -0
  92. package/dist/utilities/timezoneUtils.d.ts +44 -0
  93. package/dist/utilities/timezoneUtils.js +146 -0
  94. package/dist/utilities/timezoneUtils.js.map +1 -0
  95. package/package.json +1 -1
@@ -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 { getDayKeyInTimezone, getDayOfWeekFromDayKey } from '../../utilities/timezoneUtils.js';
5
6
  import { useTenantFilter } from '../../utilities/useTenantFilter.js';
6
7
  import styles from './AvailabilityOverview.module.css';
7
8
  const DAY_MAP = {
@@ -32,9 +33,43 @@ export const AvailabilityOverview = ()=>{
32
33
  'pending',
33
34
  'confirmed'
34
35
  ];
36
+ // In multiTenant mode the business timezone is the SELECTED tenant's zone,
37
+ // resolved server-side from the tenant cookie; fall back to the static global
38
+ // zone until it resolves and for plain installs.
39
+ const staticReservationTimezone = config.admin?.custom?.reservationTimezone ?? 'UTC';
35
40
  const resourcesTenantParams = useTenantFilter(slugs?.resources ?? 'resources');
36
41
  const schedulesTenantParams = useTenantFilter(slugs?.schedules ?? 'schedules');
37
42
  const reservationsTenantParams = useTenantFilter(slugs?.reservations ?? 'reservations');
43
+ const [effectiveTimezone, setEffectiveTimezone] = useState(null);
44
+ const reservationTimezone = effectiveTimezone ?? staticReservationTimezone;
45
+ const tenantKey = JSON.stringify(reservationsTenantParams);
46
+ useEffect(()=>{
47
+ let cancelled = false;
48
+ const apiBase = `${config.serverURL ?? ''}${config.routes.api}`;
49
+ void (async ()=>{
50
+ try {
51
+ const res = await fetch(`${apiBase}/reserve/effective-timezone`, {
52
+ credentials: 'same-origin'
53
+ });
54
+ if (!res.ok) {
55
+ return;
56
+ }
57
+ const json = await res.json();
58
+ if (!cancelled && typeof json?.timeZone === 'string') {
59
+ setEffectiveTimezone(json.timeZone);
60
+ }
61
+ } catch {
62
+ // keep the static fallback
63
+ }
64
+ })();
65
+ return ()=>{
66
+ cancelled = true;
67
+ };
68
+ }, [
69
+ config.serverURL,
70
+ config.routes.api,
71
+ tenantKey
72
+ ]);
38
73
  const DAY_NAMES = useMemo(()=>[
39
74
  t('reservation:dayShortSun'),
40
75
  t('reservation:dayShortMon'),
@@ -56,6 +91,8 @@ export const AvailabilityOverview = ()=>{
56
91
  const [schedules, setSchedules] = useState([]);
57
92
  const [reservations, setReservations] = useState([]);
58
93
  const [loading, setLoading] = useState(true);
94
+ // Cells are browser-local midnights by design; every key derived from them
95
+ // (day keys, weekday matching) is computed in the business timezone.
59
96
  const weekDays = useMemo(()=>{
60
97
  return Array.from({
61
98
  length: 7
@@ -79,6 +116,8 @@ export const AvailabilityOverview = ()=>{
79
116
  if (!slugs) {
80
117
  return;
81
118
  }
119
+ // Ignore a slow earlier fetch if the week changed before it resolved (D5)
120
+ let stale = false;
82
121
  const fetchData = async ()=>{
83
122
  setLoading(true);
84
123
  const apiBase = `${config.serverURL ?? ''}${config.routes.api}`;
@@ -93,13 +132,13 @@ export const AvailabilityOverview = ()=>{
93
132
  ...resourcesTenantParams
94
133
  });
95
134
  const schedulesParams = new URLSearchParams({
96
- limit: '500',
135
+ limit: '1000',
97
136
  'where[active][equals]': 'true',
98
137
  ...schedulesTenantParams
99
138
  });
100
139
  const reservationsParams = new URLSearchParams({
101
140
  depth: '0',
102
- limit: '500',
141
+ limit: '2000',
103
142
  'where[startTime][greater_than_equal]': weekStart.toISOString(),
104
143
  'where[startTime][less_than_equal]': weekEnd.toISOString(),
105
144
  'where[status][in]': blockingIn,
@@ -115,17 +154,28 @@ export const AvailabilityOverview = ()=>{
115
154
  schedulesRes.json(),
116
155
  reservationsRes.json()
117
156
  ]);
157
+ if (stale) {
158
+ return;
159
+ }
118
160
  setResources(rData.docs ?? []);
119
161
  setSchedules(sData.docs ?? []);
120
162
  setReservations(resData.docs ?? []);
121
163
  } catch {
164
+ if (stale) {
165
+ return;
166
+ }
122
167
  setResources([]);
123
168
  setSchedules([]);
124
169
  setReservations([]);
125
170
  }
126
- setLoading(false);
171
+ if (!stale) {
172
+ setLoading(false);
173
+ }
127
174
  };
128
175
  void fetchData();
176
+ return ()=>{
177
+ stale = true;
178
+ };
129
179
  // blockingStatuses is derived from config which is stable; stringify to
130
180
  // avoid object-reference churn causing infinite loops.
131
181
  // eslint-disable-next-line react-hooks/exhaustive-deps
@@ -156,14 +206,16 @@ export const AvailabilityOverview = ()=>{
156
206
  const getResourceId = (r)=>typeof r === 'object' ? r.id : r;
157
207
  const getSlotsForResourceDay = (resourceId, day)=>{
158
208
  const resourceSchedules = schedules.filter((s)=>getResourceId(s.resource) === resourceId);
159
- const dateStr = day.toISOString().split('T')[0];
160
- const dayOfWeek = day.getDay();
209
+ const dateStr = getDayKeyInTimezone(day, reservationTimezone);
210
+ const dayOfWeek = getDayOfWeekFromDayKey(dateStr);
161
211
  const slots = [];
162
212
  for (const schedule of resourceSchedules){
163
- // Check for exceptions
213
+ // Check for exceptions — match the full [date, endDate] range inclusively
214
+ // (review D4), keyed in the business timezone, like the server does.
164
215
  const exception = schedule.exceptions?.find((e)=>{
165
- const excDate = new Date(e.date).toISOString().split('T')[0];
166
- return excDate === dateStr;
216
+ const start = getDayKeyInTimezone(new Date(e.date), reservationTimezone);
217
+ const end = e.endDate ? getDayKeyInTimezone(new Date(e.endDate), reservationTimezone) : start;
218
+ return dateStr >= start && dateStr <= end;
167
219
  });
168
220
  if (exception) {
169
221
  slots.push({
@@ -174,7 +226,7 @@ export const AvailabilityOverview = ()=>{
174
226
  }
175
227
  if (schedule.scheduleType === 'recurring') {
176
228
  for (const slot of schedule.recurringSlots ?? []){
177
- if (DAY_MAP[slot.day] === dayOfWeek) {
229
+ if (slot.day === dayOfWeek) {
178
230
  slots.push({
179
231
  type: 'available',
180
232
  label: `${slot.startTime}-${slot.endTime}`
@@ -183,7 +235,7 @@ export const AvailabilityOverview = ()=>{
183
235
  }
184
236
  } else if (schedule.scheduleType === 'manual') {
185
237
  for (const slot of schedule.manualSlots ?? []){
186
- const slotDate = new Date(slot.date).toISOString().split('T')[0];
238
+ const slotDate = getDayKeyInTimezone(new Date(slot.date), reservationTimezone);
187
239
  if (slotDate === dateStr) {
188
240
  slots.push({
189
241
  type: 'available',
@@ -196,9 +248,9 @@ export const AvailabilityOverview = ()=>{
196
248
  return slots;
197
249
  };
198
250
  /** Returns all blocking-status reservations for a resource on a given day. */ const getBookingsForResourceDay = (resourceId, day)=>{
251
+ const dayKey = getDayKeyInTimezone(day, reservationTimezone);
199
252
  return reservations.filter((r)=>{
200
- const rDate = new Date(r.startTime);
201
- return getResourceId(r.resource) === resourceId && rDate.getFullYear() === day.getFullYear() && rDate.getMonth() === day.getMonth() && rDate.getDate() === day.getDate();
253
+ return getResourceId(r.resource) === resourceId && getDayKeyInTimezone(new Date(r.startTime), reservationTimezone) === dayKey;
202
254
  });
203
255
  };
204
256
  if (!slugs) {
@@ -215,10 +267,12 @@ export const AvailabilityOverview = ()=>{
215
267
  }
216
268
  const weekLabel = `${weekDays[0].toLocaleDateString([], {
217
269
  day: 'numeric',
218
- month: 'short'
270
+ month: 'short',
271
+ timeZone: reservationTimezone
219
272
  })} - ${weekDays[6].toLocaleDateString([], {
220
273
  day: 'numeric',
221
274
  month: 'short',
275
+ timeZone: reservationTimezone,
222
276
  year: 'numeric'
223
277
  })}`;
224
278
  const gridColumns = `150px repeat(7, 1fr)`;
@@ -269,14 +323,17 @@ export const AvailabilityOverview = ()=>{
269
323
  className: styles.headerCell,
270
324
  children: t('reservation:availabilityResource')
271
325
  }),
272
- weekDays.map((day, i)=>/*#__PURE__*/ _jsxs("div", {
326
+ weekDays.map((day, i)=>{
327
+ const dayKey = getDayKeyInTimezone(day, reservationTimezone);
328
+ return /*#__PURE__*/ _jsxs("div", {
273
329
  className: styles.headerCell,
274
330
  children: [
275
- DAY_NAMES[day.getDay()],
331
+ DAY_NAMES[DAY_MAP[getDayOfWeekFromDayKey(dayKey)]],
276
332
  " ",
277
- day.getDate()
333
+ Number(dayKey.slice(8, 10))
278
334
  ]
279
- }, i)),
335
+ }, i);
336
+ }),
280
337
  resources.map((resource)=>{
281
338
  const quantity = resource.quantity ?? 1;
282
339
  return /*#__PURE__*/ _jsxs(Fragment, {
@@ -326,7 +383,8 @@ export const AvailabilityOverview = ()=>{
326
383
  children: [
327
384
  new Date(b.startTime).toLocaleTimeString([], {
328
385
  hour: '2-digit',
329
- minute: '2-digit'
386
+ minute: '2-digit',
387
+ timeZone: reservationTimezone
330
388
  }),
331
389
  ' ',
332
390
  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 { 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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"}
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 // In multiTenant mode the business timezone is the SELECTED tenant's zone,\n // resolved server-side from the tenant cookie; fall back to the static global\n // zone until it resolves and for plain installs.\n const staticReservationTimezone: 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 [effectiveTimezone, setEffectiveTimezone] = useState<null | string>(null)\n const reservationTimezone = effectiveTimezone ?? staticReservationTimezone\n\n const tenantKey = JSON.stringify(reservationsTenantParams)\n useEffect(() => {\n let cancelled = false\n const apiBase = `${config.serverURL ?? ''}${config.routes.api}`\n void (async () => {\n try {\n const res = await fetch(`${apiBase}/reserve/effective-timezone`, {\n credentials: 'same-origin',\n })\n if (!res.ok) {\n return\n }\n const json = await res.json()\n if (!cancelled && typeof json?.timeZone === 'string') {\n setEffectiveTimezone(json.timeZone)\n }\n } catch {\n // keep the static fallback\n }\n })()\n return () => {\n cancelled = true\n }\n \n }, [config.serverURL, config.routes.api, tenantKey])\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","staticReservationTimezone","reservationTimezone","resourcesTenantParams","resources","schedulesTenantParams","schedules","reservationsTenantParams","reservations","effectiveTimezone","setEffectiveTimezone","tenantKey","JSON","stringify","cancelled","apiBase","serverURL","routes","api","res","fetch","credentials","ok","json","timeZone","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","blockingIn","join","resourcesParams","URLSearchParams","limit","schedulesParams","reservationsParams","depth","toISOString","resourcesRes","schedulesRes","reservationsRes","Promise","all","rData","sData","resData","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","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":"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"}
@@ -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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"}