payload-reserve 1.0.2 → 1.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 (81) hide show
  1. package/README.md +61 -1141
  2. package/dist/collections/Customers.d.ts +3 -0
  3. package/dist/collections/Customers.js +58 -0
  4. package/dist/collections/Customers.js.map +1 -0
  5. package/dist/collections/Reservations.js +129 -35
  6. package/dist/collections/Reservations.js.map +1 -1
  7. package/dist/collections/Resources.js +70 -2
  8. package/dist/collections/Resources.js.map +1 -1
  9. package/dist/collections/Schedules.js +4 -1
  10. package/dist/collections/Schedules.js.map +1 -1
  11. package/dist/collections/Services.js +59 -2
  12. package/dist/collections/Services.js.map +1 -1
  13. package/dist/components/AvailabilityOverview/AvailabilityOverview.module.css +31 -0
  14. package/dist/components/AvailabilityOverview/index.js +59 -8
  15. package/dist/components/AvailabilityOverview/index.js.map +1 -1
  16. package/dist/components/CalendarView/CalendarView.module.css +171 -0
  17. package/dist/components/CalendarView/index.js +547 -38
  18. package/dist/components/CalendarView/index.js.map +1 -1
  19. package/dist/components/CustomerField/index.js +24 -10
  20. package/dist/components/CustomerField/index.js.map +1 -1
  21. package/dist/components/DashboardWidget/DashboardWidgetServer.js +21 -11
  22. package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
  23. package/dist/defaults.d.ts +1 -2
  24. package/dist/defaults.js +22 -4
  25. package/dist/defaults.js.map +1 -1
  26. package/dist/endpoints/cancelBooking.d.ts +3 -0
  27. package/dist/endpoints/cancelBooking.js +37 -0
  28. package/dist/endpoints/cancelBooking.js.map +1 -0
  29. package/dist/endpoints/checkAvailability.d.ts +3 -0
  30. package/dist/endpoints/checkAvailability.js +37 -0
  31. package/dist/endpoints/checkAvailability.js.map +1 -0
  32. package/dist/endpoints/createBooking.d.ts +3 -0
  33. package/dist/endpoints/createBooking.js +32 -0
  34. package/dist/endpoints/createBooking.js.map +1 -0
  35. package/dist/endpoints/customerSearch.js +76 -58
  36. package/dist/endpoints/customerSearch.js.map +1 -1
  37. package/dist/endpoints/getSlots.d.ts +3 -0
  38. package/dist/endpoints/getSlots.js +51 -0
  39. package/dist/endpoints/getSlots.js.map +1 -0
  40. package/dist/hooks/index.d.ts +2 -0
  41. package/dist/hooks/index.js +2 -0
  42. package/dist/hooks/index.js.map +1 -1
  43. package/dist/hooks/reservations/calculateEndTime.js +75 -9
  44. package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
  45. package/dist/hooks/reservations/checkIdempotency.d.ts +3 -0
  46. package/dist/hooks/reservations/checkIdempotency.js +32 -0
  47. package/dist/hooks/reservations/checkIdempotency.js.map +1 -0
  48. package/dist/hooks/reservations/onStatusChange.d.ts +3 -0
  49. package/dist/hooks/reservations/onStatusChange.js +41 -0
  50. package/dist/hooks/reservations/onStatusChange.js.map +1 -0
  51. package/dist/hooks/reservations/validateCancellation.js +1 -1
  52. package/dist/hooks/reservations/validateCancellation.js.map +1 -1
  53. package/dist/hooks/reservations/validateConflicts.js +34 -56
  54. package/dist/hooks/reservations/validateConflicts.js.map +1 -1
  55. package/dist/hooks/reservations/validateStatusTransition.d.ts +2 -1
  56. package/dist/hooks/reservations/validateStatusTransition.js +29 -6
  57. package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
  58. package/dist/index.d.ts +5 -1
  59. package/dist/index.js +3 -0
  60. package/dist/index.js.map +1 -1
  61. package/dist/plugin.js +49 -54
  62. package/dist/plugin.js.map +1 -1
  63. package/dist/services/AvailabilityService.d.ts +57 -0
  64. package/dist/services/AvailabilityService.js +232 -0
  65. package/dist/services/AvailabilityService.js.map +1 -0
  66. package/dist/services/index.d.ts +1 -0
  67. package/dist/services/index.js +3 -0
  68. package/dist/services/index.js.map +1 -0
  69. package/dist/translations/en.json +33 -1
  70. package/dist/translations/index.d.ts +5 -0
  71. package/dist/translations/index.js.map +1 -1
  72. package/dist/types.d.ts +84 -6
  73. package/dist/types.js +29 -9
  74. package/dist/types.js.map +1 -1
  75. package/dist/utilities/ownerAccess.d.ts +24 -0
  76. package/dist/utilities/ownerAccess.js +128 -0
  77. package/dist/utilities/ownerAccess.js.map +1 -0
  78. package/dist/utilities/resolveReservationItems.d.ts +18 -0
  79. package/dist/utilities/resolveReservationItems.js +45 -0
  80. package/dist/utilities/resolveReservationItems.js.map +1 -0
  81. package/package.json +12 -9
@@ -79,6 +79,37 @@
79
79
  color: #1e40af;
80
80
  }
81
81
 
82
+ /* Graduated capacity colors for multi-unit resources */
83
+ /* Green: utilization < 50% */
84
+ .slotCapacityLow {
85
+ font-size: 0.625rem;
86
+ padding: 2px 4px;
87
+ margin-bottom: 1px;
88
+ border-radius: 2px;
89
+ background: #d1fae5;
90
+ color: #065f46;
91
+ }
92
+
93
+ /* Yellow: utilization 50–99% */
94
+ .slotCapacityMid {
95
+ font-size: 0.625rem;
96
+ padding: 2px 4px;
97
+ margin-bottom: 1px;
98
+ border-radius: 2px;
99
+ background: #fef3c7;
100
+ color: #92400e;
101
+ }
102
+
103
+ /* Red: utilization 100% (fully booked) */
104
+ .slotCapacityFull {
105
+ font-size: 0.625rem;
106
+ padding: 2px 4px;
107
+ margin-bottom: 1px;
108
+ border-radius: 2px;
109
+ background: #fee2e2;
110
+ color: #991b1b;
111
+ }
112
+
82
113
  .slotException {
83
114
  font-size: 0.625rem;
84
115
  padding: 2px 4px;
@@ -12,11 +12,25 @@ const DAY_MAP = {
12
12
  tue: 2,
13
13
  wed: 3
14
14
  };
15
+ /** Return the CSS class for a capacity badge based on utilization ratio. */ function capacityClass(booked, total) {
16
+ if (booked >= total) {
17
+ return styles.slotCapacityFull;
18
+ }
19
+ if (booked / total >= 0.5) {
20
+ return styles.slotCapacityMid;
21
+ }
22
+ return styles.slotCapacityLow;
23
+ }
15
24
  export const AvailabilityOverview = ()=>{
16
25
  const { config } = useConfig();
17
26
  const { t: _t } = useTranslation();
18
27
  const t = _t;
19
28
  const slugs = config.admin?.custom?.reservationSlugs;
29
+ const statusMachine = config.admin?.custom?.reservationStatusMachine;
30
+ const blockingStatuses = statusMachine?.blockingStatuses ?? [
31
+ 'pending',
32
+ 'confirmed'
33
+ ];
20
34
  const DAY_NAMES = useMemo(()=>[
21
35
  t('reservation:dayShortSun'),
22
36
  t('reservation:dayShortMon'),
@@ -64,6 +78,10 @@ export const AvailabilityOverview = ()=>{
64
78
  const fetchData = async ()=>{
65
79
  setLoading(true);
66
80
  const apiBase = `${config.serverURL ?? ''}${config.routes.api}`;
81
+ // Build a query that fetches only blocking-status reservations so the
82
+ // component doesn't need to filter client-side. The `in` operator on
83
+ // Payload's REST API accepts a comma-separated list.
84
+ const blockingIn = blockingStatuses.join(',');
67
85
  try {
68
86
  const [resourcesRes, schedulesRes, reservationsRes] = await Promise.all([
69
87
  fetch(`${apiBase}/${slugs.resources}?where[active][equals]=true&limit=100`),
@@ -73,7 +91,7 @@ export const AvailabilityOverview = ()=>{
73
91
  limit: '500',
74
92
  'where[startTime][greater_than_equal]': weekStart.toISOString(),
75
93
  'where[startTime][less_than_equal]': weekEnd.toISOString(),
76
- 'where[status][not_in]': 'cancelled,no-show'
94
+ 'where[status][in]': blockingIn
77
95
  })}`)
78
96
  ]);
79
97
  const [rData, sData, resData] = await Promise.all([
@@ -92,12 +110,16 @@ export const AvailabilityOverview = ()=>{
92
110
  setLoading(false);
93
111
  };
94
112
  void fetchData();
113
+ // blockingStatuses is derived from config which is stable; stringify to
114
+ // avoid object-reference churn causing infinite loops.
115
+ // eslint-disable-next-line react-hooks/exhaustive-deps
95
116
  }, [
96
117
  weekStart,
97
118
  weekEnd,
98
119
  config.routes.api,
99
120
  config.serverURL,
100
- slugs
121
+ slugs,
122
+ blockingStatuses.join(',')
101
123
  ]);
102
124
  const navigateWeek = useCallback((direction)=>{
103
125
  setWeekStart((prev)=>{
@@ -154,7 +176,7 @@ export const AvailabilityOverview = ()=>{
154
176
  }
155
177
  return slots;
156
178
  };
157
- const getBookingsForResourceDay = (resourceId, day)=>{
179
+ /** Returns all blocking-status reservations for a resource on a given day. */ const getBookingsForResourceDay = (resourceId, day)=>{
158
180
  return reservations.filter((r)=>{
159
181
  const rDate = new Date(r.startTime);
160
182
  return getResourceId(r.resource) === resourceId && rDate.getFullYear() === day.getFullYear() && rDate.getMonth() === day.getMonth() && rDate.getDate() === day.getDate();
@@ -236,15 +258,33 @@ export const AvailabilityOverview = ()=>{
236
258
  day.getDate()
237
259
  ]
238
260
  }, i)),
239
- resources.map((resource)=>/*#__PURE__*/ _jsxs(Fragment, {
261
+ resources.map((resource)=>{
262
+ const quantity = resource.quantity ?? 1;
263
+ return /*#__PURE__*/ _jsxs(Fragment, {
240
264
  children: [
241
- /*#__PURE__*/ _jsx("div", {
265
+ /*#__PURE__*/ _jsxs("div", {
242
266
  className: styles.resourceName,
243
- children: resource.name
267
+ children: [
268
+ resource.name,
269
+ quantity > 1 && /*#__PURE__*/ _jsxs("span", {
270
+ style: {
271
+ fontWeight: 400,
272
+ marginLeft: 4,
273
+ opacity: 0.6
274
+ },
275
+ children: [
276
+ ' ',
277
+ "(×",
278
+ quantity,
279
+ ")"
280
+ ]
281
+ })
282
+ ]
244
283
  }),
245
284
  weekDays.map((day, di)=>{
246
285
  const slots = getSlotsForResourceDay(resource.id, day);
247
286
  const bookings = getBookingsForResourceDay(resource.id, day);
287
+ const bookedCount = bookings.length;
248
288
  return /*#__PURE__*/ _jsxs("div", {
249
289
  className: styles.cell,
250
290
  children: [
@@ -252,7 +292,17 @@ export const AvailabilityOverview = ()=>{
252
292
  className: slot.type === 'exception' ? styles.slotException : styles.slotAvailable,
253
293
  children: slot.label
254
294
  }, `slot-${si}`)),
255
- bookings.map((b)=>/*#__PURE__*/ _jsxs("div", {
295
+ quantity > 1 ? /* Multi-unit resource: show X/Y booked with graduated color */ bookedCount > 0 && /*#__PURE__*/ _jsx("div", {
296
+ className: capacityClass(bookedCount, quantity),
297
+ title: t('reservation:availabilityXofYBooked', {
298
+ booked: bookedCount,
299
+ total: quantity
300
+ }),
301
+ children: t('reservation:availabilityXofYBooked', {
302
+ booked: bookedCount,
303
+ total: quantity
304
+ })
305
+ }) : /* Single-unit resource: show individual booking times */ bookings.map((b)=>/*#__PURE__*/ _jsxs("div", {
256
306
  className: styles.slotBooked,
257
307
  children: [
258
308
  new Date(b.startTime).toLocaleTimeString([], {
@@ -267,7 +317,8 @@ export const AvailabilityOverview = ()=>{
267
317
  }, `cell-${resource.id}-${di}`);
268
318
  })
269
319
  ]
270
- }, resource.id))
320
+ }, resource.id);
321
+ })
271
322
  ]
272
323
  })
273
324
  ]
@@ -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 id: string\n name: string\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\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\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 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][not_in]': 'cancelled,no-show',\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 }, [weekStart, weekEnd, config.routes.api, config.serverURL, slugs])\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 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 <Fragment key={resource.id}>\n <div className={styles.resourceName}>\n {resource.name}\n </div>\n {weekDays.map((day, di) => {\n const slots = getSlotsForResourceDay(resource.id, day)\n const bookings = getBookingsForResourceDay(resource.id, day)\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 {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 </div>\n )\n })}\n </Fragment>\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","AvailabilityOverview","config","t","_t","slugs","admin","custom","reservationSlugs","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","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","resourceName","name","di","bookings","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 styles from './AvailabilityOverview.module.css'\n\ntype Resource = {\n active?: boolean\n capacityMode?: 'per-guest' | 'per-reservation'\n id: string\n name: string\n quantity?: number\n}\n\ntype Schedule = {\n active?: boolean\n exceptions?: Array<{ date: string; reason?: string }>\n id: string\n manualSlots?: Array<{ date: string; endTime: string; startTime: string }>\n recurringSlots?: Array<{ day: string; endTime: string; startTime: string }>\n resource: { id: string } | string\n scheduleType: 'manual' | 'recurring'\n}\n\ntype Reservation = {\n endTime?: string\n id: string\n resource: { id: string } | string\n startTime: string\n status: string\n}\n\nconst DAY_MAP: Record<string, number> = {\n fri: 5,\n mon: 1,\n sat: 6,\n sun: 0,\n thu: 4,\n tue: 2,\n wed: 3,\n}\n\n/** Return the CSS class for a capacity badge based on utilization ratio. */\nfunction capacityClass(booked: number, total: number): string {\n if (booked >= total) {return styles.slotCapacityFull}\n if (booked / total >= 0.5) {return styles.slotCapacityMid}\n return styles.slotCapacityLow\n}\n\nexport const AvailabilityOverview: React.FC<AdminViewServerProps> = () => {\n const { config } = useConfig()\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n const slugs = config.admin?.custom?.reservationSlugs\n const statusMachine = config.admin?.custom?.reservationStatusMachine\n const blockingStatuses: string[] = statusMachine?.blockingStatuses ?? ['pending', 'confirmed']\n\n const DAY_NAMES = useMemo(\n () => [\n t('reservation:dayShortSun'),\n t('reservation:dayShortMon'),\n t('reservation:dayShortTue'),\n t('reservation:dayShortWed'),\n t('reservation:dayShortThu'),\n t('reservation:dayShortFri'),\n t('reservation:dayShortSat'),\n ],\n [t],\n )\n\n const [weekStart, setWeekStart] = useState(() => {\n const now = new Date()\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n d.setDate(d.getDate() - d.getDay())\n return d\n })\n\n const [resources, setResources] = useState<Resource[]>([])\n const [schedules, setSchedules] = useState<Schedule[]>([])\n const [reservations, setReservations] = useState<Reservation[]>([])\n const [loading, setLoading] = useState(true)\n\n const weekDays = useMemo(() => {\n return Array.from({ length: 7 }, (_, i) => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + i)\n return d\n })\n }, [weekStart])\n\n const weekEnd = useMemo(() => {\n const d = new Date(weekStart)\n d.setDate(d.getDate() + 6)\n d.setHours(23, 59, 59, 999)\n return d\n }, [weekStart])\n\n useEffect(() => {\n if (!slugs) {return}\n\n const fetchData = async () => {\n setLoading(true)\n const apiBase = `${config.serverURL ?? ''}${config.routes.api}`\n\n // Build a query that fetches only blocking-status reservations so the\n // component doesn't need to filter client-side. The `in` operator on\n // Payload's REST API accepts a comma-separated list.\n const blockingIn = blockingStatuses.join(',')\n\n try {\n const [resourcesRes, schedulesRes, reservationsRes] = await Promise.all([\n fetch(`${apiBase}/${slugs.resources}?where[active][equals]=true&limit=100`),\n fetch(`${apiBase}/${slugs.schedules}?where[active][equals]=true&limit=500`),\n fetch(\n `${apiBase}/${slugs.reservations}?${new URLSearchParams({\n depth: '0',\n limit: '500',\n 'where[startTime][greater_than_equal]': weekStart.toISOString(),\n 'where[startTime][less_than_equal]': weekEnd.toISOString(),\n 'where[status][in]': blockingIn,\n })}`,\n ),\n ])\n\n const [rData, sData, resData] = await Promise.all([\n resourcesRes.json(),\n schedulesRes.json(),\n reservationsRes.json(),\n ])\n\n setResources(rData.docs ?? [])\n setSchedules(sData.docs ?? [])\n setReservations(resData.docs ?? [])\n } catch {\n setResources([])\n setSchedules([])\n setReservations([])\n }\n setLoading(false)\n }\n\n void fetchData()\n // blockingStatuses is derived from config which is stable; stringify to\n // avoid object-reference churn causing infinite loops.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [weekStart, weekEnd, config.routes.api, config.serverURL, slugs, blockingStatuses.join(',')])\n\n const navigateWeek = useCallback((direction: -1 | 1) => {\n setWeekStart((prev) => {\n const next = new Date(prev)\n next.setDate(next.getDate() + 7 * direction)\n return next\n })\n }, [])\n\n const goToThisWeek = useCallback(() => {\n const now = new Date()\n const d = new Date(now.getFullYear(), now.getMonth(), now.getDate())\n d.setDate(d.getDate() - d.getDay())\n setWeekStart(d)\n }, [])\n\n const getResourceId = (r: { id: string } | string) =>\n typeof r === 'object' ? r.id : r\n\n const getSlotsForResourceDay = (resourceId: string, day: Date) => {\n const resourceSchedules = schedules.filter(\n (s) => getResourceId(s.resource) === resourceId,\n )\n const dateStr = day.toISOString().split('T')[0]\n const dayOfWeek = day.getDay()\n\n const slots: Array<{ label: string; type: 'available' | 'exception' }> = []\n\n for (const schedule of resourceSchedules) {\n // Check for exceptions\n const exception = schedule.exceptions?.find((e) => {\n const excDate = new Date(e.date).toISOString().split('T')[0]\n return excDate === dateStr\n })\n\n if (exception) {\n slots.push({\n type: 'exception',\n label: exception.reason || t('reservation:availabilityUnavailable'),\n })\n continue\n }\n\n if (schedule.scheduleType === 'recurring') {\n for (const slot of schedule.recurringSlots ?? []) {\n if (DAY_MAP[slot.day] === dayOfWeek) {\n slots.push({\n type: 'available',\n label: `${slot.startTime}-${slot.endTime}`,\n })\n }\n }\n } else if (schedule.scheduleType === 'manual') {\n for (const slot of schedule.manualSlots ?? []) {\n const slotDate = new Date(slot.date).toISOString().split('T')[0]\n if (slotDate === dateStr) {\n slots.push({\n type: 'available',\n label: `${slot.startTime}-${slot.endTime}`,\n })\n }\n }\n }\n }\n\n return slots\n }\n\n /** Returns all blocking-status reservations for a resource on a given day. */\n const getBookingsForResourceDay = (resourceId: string, day: Date) => {\n return reservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n getResourceId(r.resource) === resourceId &&\n rDate.getFullYear() === day.getFullYear() &&\n rDate.getMonth() === day.getMonth() &&\n rDate.getDate() === day.getDate()\n )\n })\n }\n\n if (!slugs) {\n return <div className={styles.noResources}>{t('reservation:availabilityNotConfigured')}</div>\n }\n\n if (loading) {\n return <div className={styles.loading}>{t('reservation:availabilityLoading')}</div>\n }\n\n const weekLabel = `${weekDays[0].toLocaleDateString([], { day: 'numeric', month: 'short' })} - ${weekDays[6].toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' })}`\n\n const gridColumns = `150px repeat(7, 1fr)`\n\n return (\n <div className={styles.wrapper}>\n <h2 className={styles.title}>{t('reservation:availabilityTitle')}</h2>\n <div className={styles.navigation}>\n <button className={styles.navButton} onClick={() => navigateWeek(-1)} type=\"button\">\n &larr;\n </button>\n <button className={styles.navButton} onClick={goToThisWeek} type=\"button\">\n {t('reservation:availabilityThisWeek')}\n </button>\n <button className={styles.navButton} onClick={() => navigateWeek(1)} type=\"button\">\n &rarr;\n </button>\n <span className={styles.weekLabel}>{weekLabel}</span>\n </div>\n\n {resources.length === 0 ? (\n <div className={styles.noResources}>{t('reservation:availabilityNoResources')}</div>\n ) : (\n <div className={styles.grid} style={{ gridTemplateColumns: gridColumns }}>\n {/* Header row */}\n <div className={styles.headerCell}>{t('reservation:availabilityResource')}</div>\n {weekDays.map((day, i) => (\n <div className={styles.headerCell} key={i}>\n {DAY_NAMES[day.getDay()]} {day.getDate()}\n </div>\n ))}\n\n {/* Resource rows */}\n {resources.map((resource) => {\n const quantity = resource.quantity ?? 1\n return (\n <Fragment key={resource.id}>\n <div className={styles.resourceName}>\n {resource.name}\n {quantity > 1 && (\n <span style={{ fontWeight: 400, marginLeft: 4, opacity: 0.6 }}>\n {' '}(&times;{quantity})\n </span>\n )}\n </div>\n {weekDays.map((day, di) => {\n const slots = getSlotsForResourceDay(resource.id, day)\n const bookings = getBookingsForResourceDay(resource.id, day)\n const bookedCount = bookings.length\n\n return (\n <div className={styles.cell} key={`cell-${resource.id}-${di}`}>\n {slots.map((slot, si) => (\n <div\n className={\n slot.type === 'exception'\n ? styles.slotException\n : styles.slotAvailable\n }\n key={`slot-${si}`}\n >\n {slot.label}\n </div>\n ))}\n {quantity > 1 ? (\n /* Multi-unit resource: show X/Y booked with graduated color */\n bookedCount > 0 && (\n <div\n className={capacityClass(bookedCount, quantity)}\n title={t('reservation:availabilityXofYBooked', {\n booked: bookedCount,\n total: quantity,\n })}\n >\n {t('reservation:availabilityXofYBooked', {\n booked: bookedCount,\n total: quantity,\n })}\n </div>\n )\n ) : (\n /* Single-unit resource: show individual booking times */\n bookings.map((b) => (\n <div className={styles.slotBooked} key={b.id}>\n {new Date(b.startTime).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })}{' '}\n {t('reservation:availabilityBooked')}\n </div>\n ))\n )}\n </div>\n )\n })}\n </Fragment>\n )\n })}\n </div>\n )}\n </div>\n )\n}\n"],"names":["useConfig","useTranslation","Fragment","useCallback","useEffect","useMemo","useState","styles","DAY_MAP","fri","mon","sat","sun","thu","tue","wed","capacityClass","booked","total","slotCapacityFull","slotCapacityMid","slotCapacityLow","AvailabilityOverview","config","t","_t","slugs","admin","custom","reservationSlugs","statusMachine","reservationStatusMachine","blockingStatuses","DAY_NAMES","weekStart","setWeekStart","now","Date","d","getFullYear","getMonth","getDate","setDate","getDay","resources","setResources","schedules","setSchedules","reservations","setReservations","loading","setLoading","weekDays","Array","from","length","_","i","weekEnd","setHours","fetchData","apiBase","serverURL","routes","api","blockingIn","join","resourcesRes","schedulesRes","reservationsRes","Promise","all","fetch","URLSearchParams","depth","limit","toISOString","rData","sData","resData","json","docs","navigateWeek","direction","prev","next","goToThisWeek","getResourceId","r","id","getSlotsForResourceDay","resourceId","day","resourceSchedules","filter","s","resource","dateStr","split","dayOfWeek","slots","schedule","exception","exceptions","find","e","excDate","date","push","type","label","reason","scheduleType","slot","recurringSlots","startTime","endTime","manualSlots","slotDate","getBookingsForResourceDay","rDate","div","className","noResources","weekLabel","toLocaleDateString","month","year","gridColumns","wrapper","h2","title","navigation","button","navButton","onClick","span","grid","style","gridTemplateColumns","headerCell","map","quantity","resourceName","name","fontWeight","marginLeft","opacity","di","bookings","bookedCount","cell","si","slotException","slotAvailable","b","slotBooked","toLocaleTimeString","hour","minute"],"mappings":"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"}
@@ -281,3 +281,174 @@
281
281
  z-index: 2;
282
282
  pointer-events: none;
283
283
  }
284
+
285
+ /* Pending badge on toggle button */
286
+ .pendingBadge {
287
+ display: inline-flex;
288
+ align-items: center;
289
+ justify-content: center;
290
+ min-width: 18px;
291
+ height: 18px;
292
+ padding: 0 5px;
293
+ margin-left: 6px;
294
+ border-radius: 9px;
295
+ font-size: 0.625rem;
296
+ font-weight: 700;
297
+ line-height: 1;
298
+ background: #ef4444;
299
+ color: #fff;
300
+ }
301
+
302
+ .viewToggleButtonActive .pendingBadge {
303
+ background: #f59e0b;
304
+ }
305
+
306
+ /* Pending view layout */
307
+ .pendingView {
308
+ margin-top: 4px;
309
+ }
310
+
311
+ .pendingToolbar {
312
+ display: flex;
313
+ align-items: center;
314
+ gap: 12px;
315
+ margin-bottom: 12px;
316
+ }
317
+
318
+ .selectAllLabel {
319
+ display: flex;
320
+ align-items: center;
321
+ gap: 6px;
322
+ font-size: 0.8125rem;
323
+ cursor: pointer;
324
+ user-select: none;
325
+ }
326
+
327
+ .bulkConfirmButton {
328
+ background: transparent;
329
+ border: none;
330
+ box-shadow: inset 0 0 0 1px #16a34a;
331
+ color: #16a34a;
332
+ border-radius: 3px;
333
+ padding: 5px 12px;
334
+ cursor: pointer;
335
+ font-family: inherit;
336
+ font-size: 0.75rem;
337
+ font-weight: 600;
338
+ transition: background-color 100ms ease, opacity 100ms ease;
339
+ }
340
+
341
+ .bulkConfirmButton:hover {
342
+ background: #f0fdf4;
343
+ }
344
+
345
+ .bulkConfirmButton:disabled {
346
+ opacity: 0.5;
347
+ cursor: not-allowed;
348
+ }
349
+
350
+ /* Feedback toast */
351
+ .feedbackToast {
352
+ padding: 8px 14px;
353
+ border-radius: 4px;
354
+ font-size: 0.8125rem;
355
+ font-weight: 500;
356
+ margin-bottom: 12px;
357
+ }
358
+
359
+ .feedbackSuccess {
360
+ background: #dcfce7;
361
+ color: #166534;
362
+ }
363
+
364
+ .feedbackError {
365
+ background: #fee2e2;
366
+ color: #991b1b;
367
+ }
368
+
369
+ /* Pending table */
370
+ .pendingTable {
371
+ width: 100%;
372
+ border-collapse: collapse;
373
+ font-size: 0.8125rem;
374
+ }
375
+
376
+ .pendingTh {
377
+ text-align: left;
378
+ padding: 8px 10px;
379
+ font-weight: 600;
380
+ font-size: 0.75rem;
381
+ text-transform: uppercase;
382
+ color: var(--theme-elevation-500);
383
+ border-bottom: 2px solid var(--theme-elevation-150);
384
+ }
385
+
386
+ .pendingRow {
387
+ border-bottom: 1px solid var(--theme-elevation-100);
388
+ transition: background-color 100ms ease;
389
+ }
390
+
391
+ .pendingRow:hover {
392
+ background: var(--theme-elevation-50);
393
+ }
394
+
395
+ .pendingTd {
396
+ padding: 8px 10px;
397
+ vertical-align: middle;
398
+ }
399
+
400
+ .pendingCustomerLink {
401
+ cursor: pointer;
402
+ color: var(--theme-text);
403
+ text-decoration: none;
404
+ }
405
+
406
+ .pendingCustomerLink:hover {
407
+ text-decoration: underline;
408
+ }
409
+
410
+ /* Action buttons */
411
+ .confirmButton,
412
+ .cancelButton {
413
+ background: transparent;
414
+ border: none;
415
+ border-radius: 3px;
416
+ padding: 4px 8px;
417
+ cursor: pointer;
418
+ font-size: 0.875rem;
419
+ line-height: 1;
420
+ transition: background-color 100ms ease, opacity 100ms ease;
421
+ }
422
+
423
+ .confirmButton {
424
+ box-shadow: inset 0 0 0 1px #16a34a;
425
+ color: #16a34a;
426
+ }
427
+
428
+ .confirmButton:hover {
429
+ background: #f0fdf4;
430
+ }
431
+
432
+ .cancelButton {
433
+ box-shadow: inset 0 0 0 1px #dc2626;
434
+ color: #dc2626;
435
+ margin-left: 4px;
436
+ }
437
+
438
+ .cancelButton:hover {
439
+ background: #fef2f2;
440
+ }
441
+
442
+ .confirmButton:disabled,
443
+ .cancelButton:disabled {
444
+ opacity: 0.5;
445
+ cursor: not-allowed;
446
+ }
447
+
448
+ /* Empty state */
449
+ .pendingEmpty {
450
+ text-align: center;
451
+ padding: 48px 20px;
452
+ color: var(--theme-elevation-400);
453
+ font-size: 0.9375rem;
454
+ }