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.
- package/README.md +61 -1141
- package/dist/collections/Customers.d.ts +3 -0
- package/dist/collections/Customers.js +58 -0
- package/dist/collections/Customers.js.map +1 -0
- package/dist/collections/Reservations.js +129 -35
- package/dist/collections/Reservations.js.map +1 -1
- package/dist/collections/Resources.js +70 -2
- package/dist/collections/Resources.js.map +1 -1
- package/dist/collections/Schedules.js +4 -1
- package/dist/collections/Schedules.js.map +1 -1
- package/dist/collections/Services.js +59 -2
- package/dist/collections/Services.js.map +1 -1
- package/dist/components/AvailabilityOverview/AvailabilityOverview.module.css +31 -0
- package/dist/components/AvailabilityOverview/index.js +59 -8
- package/dist/components/AvailabilityOverview/index.js.map +1 -1
- package/dist/components/CalendarView/CalendarView.module.css +171 -0
- package/dist/components/CalendarView/index.js +547 -38
- package/dist/components/CalendarView/index.js.map +1 -1
- package/dist/components/CustomerField/index.js +24 -10
- package/dist/components/CustomerField/index.js.map +1 -1
- package/dist/components/DashboardWidget/DashboardWidgetServer.js +21 -11
- package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
- package/dist/defaults.d.ts +1 -2
- package/dist/defaults.js +22 -4
- package/dist/defaults.js.map +1 -1
- package/dist/endpoints/cancelBooking.d.ts +3 -0
- package/dist/endpoints/cancelBooking.js +37 -0
- package/dist/endpoints/cancelBooking.js.map +1 -0
- package/dist/endpoints/checkAvailability.d.ts +3 -0
- package/dist/endpoints/checkAvailability.js +37 -0
- package/dist/endpoints/checkAvailability.js.map +1 -0
- package/dist/endpoints/createBooking.d.ts +3 -0
- package/dist/endpoints/createBooking.js +32 -0
- package/dist/endpoints/createBooking.js.map +1 -0
- package/dist/endpoints/customerSearch.js +76 -58
- package/dist/endpoints/customerSearch.js.map +1 -1
- package/dist/endpoints/getSlots.d.ts +3 -0
- package/dist/endpoints/getSlots.js +51 -0
- package/dist/endpoints/getSlots.js.map +1 -0
- package/dist/hooks/index.d.ts +2 -0
- package/dist/hooks/index.js +2 -0
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/reservations/calculateEndTime.js +75 -9
- package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
- package/dist/hooks/reservations/checkIdempotency.d.ts +3 -0
- package/dist/hooks/reservations/checkIdempotency.js +32 -0
- package/dist/hooks/reservations/checkIdempotency.js.map +1 -0
- package/dist/hooks/reservations/onStatusChange.d.ts +3 -0
- package/dist/hooks/reservations/onStatusChange.js +41 -0
- package/dist/hooks/reservations/onStatusChange.js.map +1 -0
- package/dist/hooks/reservations/validateCancellation.js +1 -1
- package/dist/hooks/reservations/validateCancellation.js.map +1 -1
- package/dist/hooks/reservations/validateConflicts.js +34 -56
- package/dist/hooks/reservations/validateConflicts.js.map +1 -1
- package/dist/hooks/reservations/validateStatusTransition.d.ts +2 -1
- package/dist/hooks/reservations/validateStatusTransition.js +29 -6
- package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/plugin.js +49 -54
- package/dist/plugin.js.map +1 -1
- package/dist/services/AvailabilityService.d.ts +57 -0
- package/dist/services/AvailabilityService.js +232 -0
- package/dist/services/AvailabilityService.js.map +1 -0
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +3 -0
- package/dist/services/index.js.map +1 -0
- package/dist/translations/en.json +33 -1
- package/dist/translations/index.d.ts +5 -0
- package/dist/translations/index.js.map +1 -1
- package/dist/types.d.ts +84 -6
- package/dist/types.js +29 -9
- package/dist/types.js.map +1 -1
- package/dist/utilities/ownerAccess.d.ts +24 -0
- package/dist/utilities/ownerAccess.js +128 -0
- package/dist/utilities/ownerAccess.js.map +1 -0
- package/dist/utilities/resolveReservationItems.d.ts +18 -0
- package/dist/utilities/resolveReservationItems.js +45 -0
- package/dist/utilities/resolveReservationItems.js.map +1 -0
- 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][
|
|
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)
|
|
261
|
+
resources.map((resource)=>{
|
|
262
|
+
const quantity = resource.quantity ?? 1;
|
|
263
|
+
return /*#__PURE__*/ _jsxs(Fragment, {
|
|
240
264
|
children: [
|
|
241
|
-
/*#__PURE__*/
|
|
265
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
242
266
|
className: styles.resourceName,
|
|
243
|
-
children:
|
|
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
|
-
|
|
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 ←\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 →\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 ←\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 →\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 {' '}(×{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
|
+
}
|