payload-reserve 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +1145 -0
- package/dist/collections/Reservations.d.ts +3 -0
- package/dist/collections/Reservations.js +124 -0
- package/dist/collections/Reservations.js.map +1 -0
- package/dist/collections/Resources.d.ts +3 -0
- package/dist/collections/Resources.js +53 -0
- package/dist/collections/Resources.js.map +1 -0
- package/dist/collections/Schedules.d.ts +3 -0
- package/dist/collections/Schedules.js +182 -0
- package/dist/collections/Schedules.js.map +1 -0
- package/dist/collections/Services.d.ts +3 -0
- package/dist/collections/Services.js +75 -0
- package/dist/collections/Services.js.map +1 -0
- package/dist/components/AvailabilityOverview/AvailabilityOverview.module.css +103 -0
- package/dist/components/AvailabilityOverview/index.d.ts +2 -0
- package/dist/components/AvailabilityOverview/index.js +277 -0
- package/dist/components/AvailabilityOverview/index.js.map +1 -0
- package/dist/components/CalendarView/CalendarView.module.css +283 -0
- package/dist/components/CalendarView/index.d.ts +3 -0
- package/dist/components/CalendarView/index.js +508 -0
- package/dist/components/CalendarView/index.js.map +1 -0
- package/dist/components/DashboardWidget/DashboardWidget.module.css +53 -0
- package/dist/components/DashboardWidget/DashboardWidgetServer.d.ts +2 -0
- package/dist/components/DashboardWidget/DashboardWidgetServer.js +126 -0
- package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -0
- package/dist/defaults.d.ts +12 -0
- package/dist/defaults.js +29 -0
- package/dist/defaults.js.map +1 -0
- package/dist/exports/client.d.ts +2 -0
- package/dist/exports/client.js +4 -0
- package/dist/exports/client.js.map +1 -0
- package/dist/exports/rsc.d.ts +1 -0
- package/dist/exports/rsc.js +3 -0
- package/dist/exports/rsc.js.map +1 -0
- package/dist/hooks/index.d.ts +4 -0
- package/dist/hooks/index.js +6 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/reservations/calculateEndTime.d.ts +3 -0
- package/dist/hooks/reservations/calculateEndTime.js +22 -0
- package/dist/hooks/reservations/calculateEndTime.js.map +1 -0
- package/dist/hooks/reservations/validateCancellation.d.ts +3 -0
- package/dist/hooks/reservations/validateCancellation.js +38 -0
- package/dist/hooks/reservations/validateCancellation.js.map +1 -0
- package/dist/hooks/reservations/validateConflicts.d.ts +3 -0
- package/dist/hooks/reservations/validateConflicts.js +86 -0
- package/dist/hooks/reservations/validateConflicts.js.map +1 -0
- package/dist/hooks/reservations/validateStatusTransition.d.ts +2 -0
- package/dist/hooks/reservations/validateStatusTransition.js +54 -0
- package/dist/hooks/reservations/validateStatusTransition.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +3 -0
- package/dist/plugin.js +106 -0
- package/dist/plugin.js.map +1 -0
- package/dist/translations/en.json +86 -0
- package/dist/translations/index.d.ts +3 -0
- package/dist/translations/index.js +8 -0
- package/dist/translations/index.js.map +1 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.js +16 -0
- package/dist/types.js.map +1 -0
- package/dist/utilities/scheduleUtils.d.ts +54 -0
- package/dist/utilities/scheduleUtils.js +87 -0
- package/dist/utilities/scheduleUtils.js.map +1 -0
- package/dist/utilities/slotUtils.d.ts +21 -0
- package/dist/utilities/slotUtils.js +28 -0
- package/dist/utilities/slotUtils.js.map +1 -0
- package/package.json +108 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useConfig, useTranslation } from '@payloadcms/ui';
|
|
4
|
+
import { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
|
|
5
|
+
import styles from './AvailabilityOverview.module.css';
|
|
6
|
+
const DAY_MAP = {
|
|
7
|
+
fri: 5,
|
|
8
|
+
mon: 1,
|
|
9
|
+
sat: 6,
|
|
10
|
+
sun: 0,
|
|
11
|
+
thu: 4,
|
|
12
|
+
tue: 2,
|
|
13
|
+
wed: 3
|
|
14
|
+
};
|
|
15
|
+
export const AvailabilityOverview = ()=>{
|
|
16
|
+
const { config } = useConfig();
|
|
17
|
+
const { t: _t } = useTranslation();
|
|
18
|
+
const t = _t;
|
|
19
|
+
const slugs = config.admin?.custom?.reservationSlugs;
|
|
20
|
+
const DAY_NAMES = useMemo(()=>[
|
|
21
|
+
t('reservation:dayShortSun'),
|
|
22
|
+
t('reservation:dayShortMon'),
|
|
23
|
+
t('reservation:dayShortTue'),
|
|
24
|
+
t('reservation:dayShortWed'),
|
|
25
|
+
t('reservation:dayShortThu'),
|
|
26
|
+
t('reservation:dayShortFri'),
|
|
27
|
+
t('reservation:dayShortSat')
|
|
28
|
+
], [
|
|
29
|
+
t
|
|
30
|
+
]);
|
|
31
|
+
const [weekStart, setWeekStart] = useState(()=>{
|
|
32
|
+
const now = new Date();
|
|
33
|
+
const d = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
34
|
+
d.setDate(d.getDate() - d.getDay());
|
|
35
|
+
return d;
|
|
36
|
+
});
|
|
37
|
+
const [resources, setResources] = useState([]);
|
|
38
|
+
const [schedules, setSchedules] = useState([]);
|
|
39
|
+
const [reservations, setReservations] = useState([]);
|
|
40
|
+
const [loading, setLoading] = useState(true);
|
|
41
|
+
const weekDays = useMemo(()=>{
|
|
42
|
+
return Array.from({
|
|
43
|
+
length: 7
|
|
44
|
+
}, (_, i)=>{
|
|
45
|
+
const d = new Date(weekStart);
|
|
46
|
+
d.setDate(d.getDate() + i);
|
|
47
|
+
return d;
|
|
48
|
+
});
|
|
49
|
+
}, [
|
|
50
|
+
weekStart
|
|
51
|
+
]);
|
|
52
|
+
const weekEnd = useMemo(()=>{
|
|
53
|
+
const d = new Date(weekStart);
|
|
54
|
+
d.setDate(d.getDate() + 6);
|
|
55
|
+
d.setHours(23, 59, 59, 999);
|
|
56
|
+
return d;
|
|
57
|
+
}, [
|
|
58
|
+
weekStart
|
|
59
|
+
]);
|
|
60
|
+
useEffect(()=>{
|
|
61
|
+
if (!slugs) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
const fetchData = async ()=>{
|
|
65
|
+
setLoading(true);
|
|
66
|
+
const apiBase = `${config.serverURL ?? ''}${config.routes.api}`;
|
|
67
|
+
try {
|
|
68
|
+
const [resourcesRes, schedulesRes, reservationsRes] = await Promise.all([
|
|
69
|
+
fetch(`${apiBase}/${slugs.resources}?where[active][equals]=true&limit=100`),
|
|
70
|
+
fetch(`${apiBase}/${slugs.schedules}?where[active][equals]=true&limit=500`),
|
|
71
|
+
fetch(`${apiBase}/${slugs.reservations}?${new URLSearchParams({
|
|
72
|
+
depth: '0',
|
|
73
|
+
limit: '500',
|
|
74
|
+
'where[startTime][greater_than_equal]': weekStart.toISOString(),
|
|
75
|
+
'where[startTime][less_than_equal]': weekEnd.toISOString(),
|
|
76
|
+
'where[status][not_in]': 'cancelled,no-show'
|
|
77
|
+
})}`)
|
|
78
|
+
]);
|
|
79
|
+
const [rData, sData, resData] = await Promise.all([
|
|
80
|
+
resourcesRes.json(),
|
|
81
|
+
schedulesRes.json(),
|
|
82
|
+
reservationsRes.json()
|
|
83
|
+
]);
|
|
84
|
+
setResources(rData.docs ?? []);
|
|
85
|
+
setSchedules(sData.docs ?? []);
|
|
86
|
+
setReservations(resData.docs ?? []);
|
|
87
|
+
} catch {
|
|
88
|
+
setResources([]);
|
|
89
|
+
setSchedules([]);
|
|
90
|
+
setReservations([]);
|
|
91
|
+
}
|
|
92
|
+
setLoading(false);
|
|
93
|
+
};
|
|
94
|
+
void fetchData();
|
|
95
|
+
}, [
|
|
96
|
+
weekStart,
|
|
97
|
+
weekEnd,
|
|
98
|
+
config.routes.api,
|
|
99
|
+
config.serverURL,
|
|
100
|
+
slugs
|
|
101
|
+
]);
|
|
102
|
+
const navigateWeek = useCallback((direction)=>{
|
|
103
|
+
setWeekStart((prev)=>{
|
|
104
|
+
const next = new Date(prev);
|
|
105
|
+
next.setDate(next.getDate() + 7 * direction);
|
|
106
|
+
return next;
|
|
107
|
+
});
|
|
108
|
+
}, []);
|
|
109
|
+
const goToThisWeek = useCallback(()=>{
|
|
110
|
+
const now = new Date();
|
|
111
|
+
const d = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
112
|
+
d.setDate(d.getDate() - d.getDay());
|
|
113
|
+
setWeekStart(d);
|
|
114
|
+
}, []);
|
|
115
|
+
const getResourceId = (r)=>typeof r === 'object' ? r.id : r;
|
|
116
|
+
const getSlotsForResourceDay = (resourceId, day)=>{
|
|
117
|
+
const resourceSchedules = schedules.filter((s)=>getResourceId(s.resource) === resourceId);
|
|
118
|
+
const dateStr = day.toISOString().split('T')[0];
|
|
119
|
+
const dayOfWeek = day.getDay();
|
|
120
|
+
const slots = [];
|
|
121
|
+
for (const schedule of resourceSchedules){
|
|
122
|
+
// Check for exceptions
|
|
123
|
+
const exception = schedule.exceptions?.find((e)=>{
|
|
124
|
+
const excDate = new Date(e.date).toISOString().split('T')[0];
|
|
125
|
+
return excDate === dateStr;
|
|
126
|
+
});
|
|
127
|
+
if (exception) {
|
|
128
|
+
slots.push({
|
|
129
|
+
type: 'exception',
|
|
130
|
+
label: exception.reason || t('reservation:availabilityUnavailable')
|
|
131
|
+
});
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (schedule.scheduleType === 'recurring') {
|
|
135
|
+
for (const slot of schedule.recurringSlots ?? []){
|
|
136
|
+
if (DAY_MAP[slot.day] === dayOfWeek) {
|
|
137
|
+
slots.push({
|
|
138
|
+
type: 'available',
|
|
139
|
+
label: `${slot.startTime}-${slot.endTime}`
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
} else if (schedule.scheduleType === 'manual') {
|
|
144
|
+
for (const slot of schedule.manualSlots ?? []){
|
|
145
|
+
const slotDate = new Date(slot.date).toISOString().split('T')[0];
|
|
146
|
+
if (slotDate === dateStr) {
|
|
147
|
+
slots.push({
|
|
148
|
+
type: 'available',
|
|
149
|
+
label: `${slot.startTime}-${slot.endTime}`
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return slots;
|
|
156
|
+
};
|
|
157
|
+
const getBookingsForResourceDay = (resourceId, day)=>{
|
|
158
|
+
return reservations.filter((r)=>{
|
|
159
|
+
const rDate = new Date(r.startTime);
|
|
160
|
+
return getResourceId(r.resource) === resourceId && rDate.getFullYear() === day.getFullYear() && rDate.getMonth() === day.getMonth() && rDate.getDate() === day.getDate();
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
if (!slugs) {
|
|
164
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
165
|
+
className: styles.noResources,
|
|
166
|
+
children: t('reservation:availabilityNotConfigured')
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
if (loading) {
|
|
170
|
+
return /*#__PURE__*/ _jsx("div", {
|
|
171
|
+
className: styles.loading,
|
|
172
|
+
children: t('reservation:availabilityLoading')
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
const weekLabel = `${weekDays[0].toLocaleDateString([], {
|
|
176
|
+
day: 'numeric',
|
|
177
|
+
month: 'short'
|
|
178
|
+
})} - ${weekDays[6].toLocaleDateString([], {
|
|
179
|
+
day: 'numeric',
|
|
180
|
+
month: 'short',
|
|
181
|
+
year: 'numeric'
|
|
182
|
+
})}`;
|
|
183
|
+
const gridColumns = `150px repeat(7, 1fr)`;
|
|
184
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
185
|
+
className: styles.wrapper,
|
|
186
|
+
children: [
|
|
187
|
+
/*#__PURE__*/ _jsx("h2", {
|
|
188
|
+
className: styles.title,
|
|
189
|
+
children: t('reservation:availabilityTitle')
|
|
190
|
+
}),
|
|
191
|
+
/*#__PURE__*/ _jsxs("div", {
|
|
192
|
+
className: styles.navigation,
|
|
193
|
+
children: [
|
|
194
|
+
/*#__PURE__*/ _jsx("button", {
|
|
195
|
+
className: styles.navButton,
|
|
196
|
+
onClick: ()=>navigateWeek(-1),
|
|
197
|
+
type: "button",
|
|
198
|
+
children: "←"
|
|
199
|
+
}),
|
|
200
|
+
/*#__PURE__*/ _jsx("button", {
|
|
201
|
+
className: styles.navButton,
|
|
202
|
+
onClick: goToThisWeek,
|
|
203
|
+
type: "button",
|
|
204
|
+
children: t('reservation:availabilityThisWeek')
|
|
205
|
+
}),
|
|
206
|
+
/*#__PURE__*/ _jsx("button", {
|
|
207
|
+
className: styles.navButton,
|
|
208
|
+
onClick: ()=>navigateWeek(1),
|
|
209
|
+
type: "button",
|
|
210
|
+
children: "→"
|
|
211
|
+
}),
|
|
212
|
+
/*#__PURE__*/ _jsx("span", {
|
|
213
|
+
className: styles.weekLabel,
|
|
214
|
+
children: weekLabel
|
|
215
|
+
})
|
|
216
|
+
]
|
|
217
|
+
}),
|
|
218
|
+
resources.length === 0 ? /*#__PURE__*/ _jsx("div", {
|
|
219
|
+
className: styles.noResources,
|
|
220
|
+
children: t('reservation:availabilityNoResources')
|
|
221
|
+
}) : /*#__PURE__*/ _jsxs("div", {
|
|
222
|
+
className: styles.grid,
|
|
223
|
+
style: {
|
|
224
|
+
gridTemplateColumns: gridColumns
|
|
225
|
+
},
|
|
226
|
+
children: [
|
|
227
|
+
/*#__PURE__*/ _jsx("div", {
|
|
228
|
+
className: styles.headerCell,
|
|
229
|
+
children: t('reservation:availabilityResource')
|
|
230
|
+
}),
|
|
231
|
+
weekDays.map((day, i)=>/*#__PURE__*/ _jsxs("div", {
|
|
232
|
+
className: styles.headerCell,
|
|
233
|
+
children: [
|
|
234
|
+
DAY_NAMES[day.getDay()],
|
|
235
|
+
" ",
|
|
236
|
+
day.getDate()
|
|
237
|
+
]
|
|
238
|
+
}, i)),
|
|
239
|
+
resources.map((resource)=>/*#__PURE__*/ _jsxs(Fragment, {
|
|
240
|
+
children: [
|
|
241
|
+
/*#__PURE__*/ _jsx("div", {
|
|
242
|
+
className: styles.resourceName,
|
|
243
|
+
children: resource.name
|
|
244
|
+
}),
|
|
245
|
+
weekDays.map((day, di)=>{
|
|
246
|
+
const slots = getSlotsForResourceDay(resource.id, day);
|
|
247
|
+
const bookings = getBookingsForResourceDay(resource.id, day);
|
|
248
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
249
|
+
className: styles.cell,
|
|
250
|
+
children: [
|
|
251
|
+
slots.map((slot, si)=>/*#__PURE__*/ _jsx("div", {
|
|
252
|
+
className: slot.type === 'exception' ? styles.slotException : styles.slotAvailable,
|
|
253
|
+
children: slot.label
|
|
254
|
+
}, `slot-${si}`)),
|
|
255
|
+
bookings.map((b)=>/*#__PURE__*/ _jsxs("div", {
|
|
256
|
+
className: styles.slotBooked,
|
|
257
|
+
children: [
|
|
258
|
+
new Date(b.startTime).toLocaleTimeString([], {
|
|
259
|
+
hour: '2-digit',
|
|
260
|
+
minute: '2-digit'
|
|
261
|
+
}),
|
|
262
|
+
' ',
|
|
263
|
+
t('reservation:availabilityBooked')
|
|
264
|
+
]
|
|
265
|
+
}, b.id))
|
|
266
|
+
]
|
|
267
|
+
}, `cell-${resource.id}-${di}`);
|
|
268
|
+
})
|
|
269
|
+
]
|
|
270
|
+
}, resource.id))
|
|
271
|
+
]
|
|
272
|
+
})
|
|
273
|
+
]
|
|
274
|
+
});
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
.wrapper {
|
|
2
|
+
padding: 20px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.header {
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: center;
|
|
8
|
+
justify-content: space-between;
|
|
9
|
+
margin-bottom: 16px;
|
|
10
|
+
flex-wrap: wrap;
|
|
11
|
+
gap: 8px;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.navButtons {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
gap: 8px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
.navButton {
|
|
21
|
+
background: transparent;
|
|
22
|
+
border: none;
|
|
23
|
+
border-radius: 3px;
|
|
24
|
+
box-shadow: inset 0 0 0 1px var(--theme-elevation-250);
|
|
25
|
+
padding: 6px 12px;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
font-family: inherit;
|
|
28
|
+
font-size: 0.875rem;
|
|
29
|
+
color: var(--theme-text);
|
|
30
|
+
transition: box-shadow 100ms cubic-bezier(0, 0.2, 0.2, 1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.navButton:hover {
|
|
34
|
+
box-shadow: inset 0 0 0 1px var(--theme-elevation-400);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.navButton:focus-visible {
|
|
38
|
+
outline: 2px solid var(--theme-text);
|
|
39
|
+
outline-offset: 2px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.currentDate {
|
|
43
|
+
font-size: 1.125rem;
|
|
44
|
+
font-weight: 600;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.viewToggle {
|
|
48
|
+
display: flex;
|
|
49
|
+
gap: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.createButton {
|
|
53
|
+
background: var(--theme-elevation-800);
|
|
54
|
+
color: var(--theme-elevation-0);
|
|
55
|
+
border: none;
|
|
56
|
+
border-radius: 3px;
|
|
57
|
+
padding: 6px 12px;
|
|
58
|
+
cursor: pointer;
|
|
59
|
+
font-family: inherit;
|
|
60
|
+
font-size: 0.75rem;
|
|
61
|
+
font-weight: 600;
|
|
62
|
+
margin-right: 8px;
|
|
63
|
+
transition: background-color 100ms cubic-bezier(0, 0.2, 0.2, 1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.createButton:hover {
|
|
67
|
+
background: var(--theme-elevation-600);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.createButton:focus-visible {
|
|
71
|
+
outline: 2px solid var(--theme-text);
|
|
72
|
+
outline-offset: 2px;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
.viewToggleButton {
|
|
76
|
+
background: var(--theme-elevation-150);
|
|
77
|
+
color: var(--theme-elevation-800);
|
|
78
|
+
border: none;
|
|
79
|
+
border-radius: 0;
|
|
80
|
+
padding: 6px 12px;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
font-family: inherit;
|
|
83
|
+
font-size: 0.75rem;
|
|
84
|
+
transition: background-color 100ms cubic-bezier(0, 0.2, 0.2, 1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.createButton + .viewToggleButton {
|
|
88
|
+
border-radius: 3px 0 0 3px;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.viewToggleButton + .viewToggleButton {
|
|
92
|
+
box-shadow: inset 1px 0 0 var(--theme-elevation-200);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.viewToggleButton:last-child {
|
|
96
|
+
border-radius: 0 3px 3px 0;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.viewToggleButton:hover {
|
|
100
|
+
background: var(--theme-elevation-100);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.viewToggleButton.viewToggleButtonActive {
|
|
104
|
+
background: var(--theme-elevation-800);
|
|
105
|
+
color: var(--theme-elevation-0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.viewToggleButton.viewToggleButtonActive:hover {
|
|
109
|
+
background: var(--theme-elevation-800);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.viewToggleButton:focus-visible {
|
|
113
|
+
outline: 2px solid var(--theme-text);
|
|
114
|
+
outline-offset: 2px;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.monthGrid {
|
|
118
|
+
display: grid;
|
|
119
|
+
grid-template-columns: repeat(7, 1fr);
|
|
120
|
+
gap: 1px;
|
|
121
|
+
background: var(--theme-elevation-150);
|
|
122
|
+
border: 1px solid var(--theme-elevation-150);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.dayHeader {
|
|
126
|
+
padding: 8px 4px;
|
|
127
|
+
text-align: center;
|
|
128
|
+
font-size: 0.75rem;
|
|
129
|
+
font-weight: 600;
|
|
130
|
+
text-transform: uppercase;
|
|
131
|
+
background: var(--theme-elevation-50);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.dayCell {
|
|
135
|
+
min-height: 80px;
|
|
136
|
+
padding: 4px;
|
|
137
|
+
background: var(--theme-bg);
|
|
138
|
+
vertical-align: top;
|
|
139
|
+
cursor: pointer;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.dayCell:hover {
|
|
143
|
+
background: var(--theme-elevation-50);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.dayCellOtherMonth {
|
|
147
|
+
opacity: 0.4;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.dayCellToday {
|
|
151
|
+
background: var(--theme-elevation-50);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.dayNumber {
|
|
155
|
+
font-size: 0.75rem;
|
|
156
|
+
font-weight: 600;
|
|
157
|
+
margin-bottom: 2px;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.eventItem {
|
|
161
|
+
font-size: 0.625rem;
|
|
162
|
+
padding: 2px 4px;
|
|
163
|
+
margin-bottom: 1px;
|
|
164
|
+
border-radius: 2px;
|
|
165
|
+
cursor: pointer;
|
|
166
|
+
overflow: hidden;
|
|
167
|
+
text-overflow: ellipsis;
|
|
168
|
+
white-space: nowrap;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.eventItem:hover {
|
|
172
|
+
opacity: 0.8;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.statusPending {
|
|
176
|
+
background: #fef3c7;
|
|
177
|
+
color: #92400e;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.statusConfirmed {
|
|
181
|
+
background: #dbeafe;
|
|
182
|
+
color: #1e40af;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.statusCompleted {
|
|
186
|
+
background: #d1fae5;
|
|
187
|
+
color: #065f46;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.statusCancelled {
|
|
191
|
+
background: #e5e7eb;
|
|
192
|
+
color: #6b7280;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.statusNoShow {
|
|
196
|
+
background: #fee2e2;
|
|
197
|
+
color: #991b1b;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.weekView {
|
|
201
|
+
display: grid;
|
|
202
|
+
grid-template-columns: 60px repeat(7, 1fr);
|
|
203
|
+
gap: 1px;
|
|
204
|
+
background: var(--theme-elevation-150);
|
|
205
|
+
border: 1px solid var(--theme-elevation-150);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.timeLabel {
|
|
209
|
+
padding: 4px;
|
|
210
|
+
font-size: 0.625rem;
|
|
211
|
+
text-align: right;
|
|
212
|
+
background: var(--theme-bg);
|
|
213
|
+
min-height: 40px;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.weekCell {
|
|
217
|
+
padding: 2px;
|
|
218
|
+
background: var(--theme-bg);
|
|
219
|
+
min-height: 40px;
|
|
220
|
+
position: relative;
|
|
221
|
+
cursor: pointer;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
.weekCell:hover {
|
|
225
|
+
background: var(--theme-elevation-50);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
.dayView {
|
|
229
|
+
display: grid;
|
|
230
|
+
grid-template-columns: 60px 1fr;
|
|
231
|
+
gap: 1px;
|
|
232
|
+
background: var(--theme-elevation-150);
|
|
233
|
+
border: 1px solid var(--theme-elevation-150);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.dayViewCell {
|
|
237
|
+
padding: 4px;
|
|
238
|
+
background: var(--theme-bg);
|
|
239
|
+
min-height: 48px;
|
|
240
|
+
position: relative;
|
|
241
|
+
cursor: pointer;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.dayViewCell:hover {
|
|
245
|
+
background: var(--theme-elevation-50);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
.loading {
|
|
249
|
+
text-align: center;
|
|
250
|
+
padding: 40px;
|
|
251
|
+
color: var(--theme-elevation-400);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
.statusLegend {
|
|
255
|
+
display: flex;
|
|
256
|
+
gap: 12px;
|
|
257
|
+
margin-bottom: 12px;
|
|
258
|
+
flex-wrap: wrap;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
.legendItem {
|
|
262
|
+
display: flex;
|
|
263
|
+
align-items: center;
|
|
264
|
+
gap: 4px;
|
|
265
|
+
font-size: 0.75rem;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.legendDot {
|
|
269
|
+
width: 10px;
|
|
270
|
+
height: 10px;
|
|
271
|
+
border-radius: 50%;
|
|
272
|
+
flex-shrink: 0;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.currentTimeLine {
|
|
276
|
+
position: absolute;
|
|
277
|
+
left: 0;
|
|
278
|
+
right: 0;
|
|
279
|
+
height: 2px;
|
|
280
|
+
background: #ef4444;
|
|
281
|
+
z-index: 2;
|
|
282
|
+
pointer-events: none;
|
|
283
|
+
}
|