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.
Files changed (70) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1145 -0
  3. package/dist/collections/Reservations.d.ts +3 -0
  4. package/dist/collections/Reservations.js +124 -0
  5. package/dist/collections/Reservations.js.map +1 -0
  6. package/dist/collections/Resources.d.ts +3 -0
  7. package/dist/collections/Resources.js +53 -0
  8. package/dist/collections/Resources.js.map +1 -0
  9. package/dist/collections/Schedules.d.ts +3 -0
  10. package/dist/collections/Schedules.js +182 -0
  11. package/dist/collections/Schedules.js.map +1 -0
  12. package/dist/collections/Services.d.ts +3 -0
  13. package/dist/collections/Services.js +75 -0
  14. package/dist/collections/Services.js.map +1 -0
  15. package/dist/components/AvailabilityOverview/AvailabilityOverview.module.css +103 -0
  16. package/dist/components/AvailabilityOverview/index.d.ts +2 -0
  17. package/dist/components/AvailabilityOverview/index.js +277 -0
  18. package/dist/components/AvailabilityOverview/index.js.map +1 -0
  19. package/dist/components/CalendarView/CalendarView.module.css +283 -0
  20. package/dist/components/CalendarView/index.d.ts +3 -0
  21. package/dist/components/CalendarView/index.js +508 -0
  22. package/dist/components/CalendarView/index.js.map +1 -0
  23. package/dist/components/DashboardWidget/DashboardWidget.module.css +53 -0
  24. package/dist/components/DashboardWidget/DashboardWidgetServer.d.ts +2 -0
  25. package/dist/components/DashboardWidget/DashboardWidgetServer.js +126 -0
  26. package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -0
  27. package/dist/defaults.d.ts +12 -0
  28. package/dist/defaults.js +29 -0
  29. package/dist/defaults.js.map +1 -0
  30. package/dist/exports/client.d.ts +2 -0
  31. package/dist/exports/client.js +4 -0
  32. package/dist/exports/client.js.map +1 -0
  33. package/dist/exports/rsc.d.ts +1 -0
  34. package/dist/exports/rsc.js +3 -0
  35. package/dist/exports/rsc.js.map +1 -0
  36. package/dist/hooks/index.d.ts +4 -0
  37. package/dist/hooks/index.js +6 -0
  38. package/dist/hooks/index.js.map +1 -0
  39. package/dist/hooks/reservations/calculateEndTime.d.ts +3 -0
  40. package/dist/hooks/reservations/calculateEndTime.js +22 -0
  41. package/dist/hooks/reservations/calculateEndTime.js.map +1 -0
  42. package/dist/hooks/reservations/validateCancellation.d.ts +3 -0
  43. package/dist/hooks/reservations/validateCancellation.js +38 -0
  44. package/dist/hooks/reservations/validateCancellation.js.map +1 -0
  45. package/dist/hooks/reservations/validateConflicts.d.ts +3 -0
  46. package/dist/hooks/reservations/validateConflicts.js +86 -0
  47. package/dist/hooks/reservations/validateConflicts.js.map +1 -0
  48. package/dist/hooks/reservations/validateStatusTransition.d.ts +2 -0
  49. package/dist/hooks/reservations/validateStatusTransition.js +54 -0
  50. package/dist/hooks/reservations/validateStatusTransition.js.map +1 -0
  51. package/dist/index.d.ts +2 -0
  52. package/dist/index.js +3 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/plugin.d.ts +3 -0
  55. package/dist/plugin.js +106 -0
  56. package/dist/plugin.js.map +1 -0
  57. package/dist/translations/en.json +86 -0
  58. package/dist/translations/index.d.ts +3 -0
  59. package/dist/translations/index.js +8 -0
  60. package/dist/translations/index.js.map +1 -0
  61. package/dist/types.d.ts +51 -0
  62. package/dist/types.js +16 -0
  63. package/dist/types.js.map +1 -0
  64. package/dist/utilities/scheduleUtils.d.ts +54 -0
  65. package/dist/utilities/scheduleUtils.js +87 -0
  66. package/dist/utilities/scheduleUtils.js.map +1 -0
  67. package/dist/utilities/slotUtils.d.ts +21 -0
  68. package/dist/utilities/slotUtils.js +28 -0
  69. package/dist/utilities/slotUtils.js.map +1 -0
  70. 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 &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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"}
@@ -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
+ }
@@ -0,0 +1,3 @@
1
+ import type { AdminViewServerProps } from 'payload';
2
+ import React from 'react';
3
+ export declare const CalendarView: React.FC<AdminViewServerProps>;