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,508 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useConfig, useDocumentDrawer, useTranslation } from '@payloadcms/ui';
4
+ import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
+ import styles from './CalendarView.module.css';
6
+ const STATUS_CLASS_MAP = {
7
+ cancelled: styles.statusCancelled,
8
+ completed: styles.statusCompleted,
9
+ confirmed: styles.statusConfirmed,
10
+ 'no-show': styles.statusNoShow,
11
+ pending: styles.statusPending
12
+ };
13
+ const STATUS_COLORS = {
14
+ cancelled: '#e5e7eb',
15
+ completed: '#d1fae5',
16
+ confirmed: '#dbeafe',
17
+ 'no-show': '#fee2e2',
18
+ pending: '#fef3c7'
19
+ };
20
+ export const CalendarView = ()=>{
21
+ const { config } = useConfig();
22
+ const { t: _t } = useTranslation();
23
+ const t = _t;
24
+ const STATUS_LABELS = useMemo(()=>({
25
+ cancelled: t('reservation:statusCancelled'),
26
+ completed: t('reservation:statusCompleted'),
27
+ confirmed: t('reservation:statusConfirmed'),
28
+ 'no-show': t('reservation:statusNoShowLabel'),
29
+ pending: t('reservation:statusPending')
30
+ }), [
31
+ t
32
+ ]);
33
+ const slugs = config.admin?.custom?.reservationSlugs;
34
+ const reservationSlug = slugs?.reservations ?? 'reservations';
35
+ const [currentDate, setCurrentDate] = useState(()=>new Date());
36
+ const [viewMode, setViewMode] = useState('month');
37
+ const [reservations, setReservations] = useState([]);
38
+ const [loading, setLoading] = useState(true);
39
+ const [drawerDocId, setDrawerDocId] = useState(null);
40
+ const [initialData, setInitialData] = useState(undefined);
41
+ const [DocumentDrawer, , { openDrawer }] = useDocumentDrawer({
42
+ id: drawerDocId ?? undefined,
43
+ collectionSlug: reservationSlug
44
+ });
45
+ const pendingDrawerOpen = useRef(false);
46
+ useEffect(()=>{
47
+ if (pendingDrawerOpen.current) {
48
+ pendingDrawerOpen.current = false;
49
+ openDrawer();
50
+ }
51
+ });
52
+ const { rangeEnd, rangeStart } = useMemo(()=>{
53
+ const start = new Date(currentDate);
54
+ const end = new Date(currentDate);
55
+ if (viewMode === 'month') {
56
+ start.setDate(1);
57
+ start.setDate(start.getDate() - start.getDay());
58
+ end.setMonth(end.getMonth() + 1, 0);
59
+ end.setDate(end.getDate() + (6 - end.getDay()));
60
+ } else if (viewMode === 'week') {
61
+ const dayOfWeek = start.getDay();
62
+ start.setDate(start.getDate() - dayOfWeek);
63
+ end.setDate(start.getDate() + 6);
64
+ }
65
+ start.setHours(0, 0, 0, 0);
66
+ end.setHours(23, 59, 59, 999);
67
+ return {
68
+ rangeEnd: end,
69
+ rangeStart: start
70
+ };
71
+ }, [
72
+ currentDate,
73
+ viewMode
74
+ ]);
75
+ const fetchReservations = useCallback(async ()=>{
76
+ setLoading(true);
77
+ try {
78
+ const apiUrl = `${config.serverURL ?? ''}${config.routes.api}/${reservationSlug}`;
79
+ const params = new URLSearchParams({
80
+ depth: '1',
81
+ limit: '500',
82
+ sort: 'startTime',
83
+ 'where[startTime][greater_than_equal]': rangeStart.toISOString(),
84
+ 'where[startTime][less_than_equal]': rangeEnd.toISOString()
85
+ });
86
+ const response = await fetch(`${apiUrl}?${params}`);
87
+ const result = await response.json();
88
+ setReservations(result.docs ?? []);
89
+ } catch {
90
+ setReservations([]);
91
+ }
92
+ setLoading(false);
93
+ }, [
94
+ rangeStart,
95
+ rangeEnd,
96
+ config.routes.api,
97
+ config.serverURL,
98
+ reservationSlug
99
+ ]);
100
+ useEffect(()=>{
101
+ void fetchReservations();
102
+ }, [
103
+ fetchReservations
104
+ ]);
105
+ const handleEventClick = useCallback((e, id)=>{
106
+ e.stopPropagation();
107
+ setDrawerDocId(id);
108
+ setInitialData(undefined);
109
+ pendingDrawerOpen.current = true;
110
+ }, []);
111
+ const handleEventKeyDown = useCallback((e, id)=>{
112
+ if (e.key === 'Enter' || e.key === ' ') {
113
+ e.preventDefault();
114
+ e.stopPropagation();
115
+ setDrawerDocId(id);
116
+ setInitialData(undefined);
117
+ pendingDrawerOpen.current = true;
118
+ }
119
+ }, []);
120
+ const handleCreateNew = useCallback(()=>{
121
+ setDrawerDocId(null);
122
+ setInitialData(undefined);
123
+ pendingDrawerOpen.current = true;
124
+ }, []);
125
+ const handleDateClick = useCallback((date)=>{
126
+ setDrawerDocId(null);
127
+ setInitialData({
128
+ startTime: date.toISOString()
129
+ });
130
+ pendingDrawerOpen.current = true;
131
+ }, []);
132
+ const navigate = useCallback((direction)=>{
133
+ setCurrentDate((prev)=>{
134
+ const next = new Date(prev);
135
+ if (viewMode === 'month') {
136
+ next.setMonth(next.getMonth() + direction);
137
+ } else if (viewMode === 'week') {
138
+ next.setDate(next.getDate() + 7 * direction);
139
+ } else {
140
+ next.setDate(next.getDate() + direction);
141
+ }
142
+ return next;
143
+ });
144
+ }, [
145
+ viewMode
146
+ ]);
147
+ const goToToday = useCallback(()=>setCurrentDate(new Date()), []);
148
+ const getResName = (field)=>{
149
+ if (!field) {
150
+ return '';
151
+ }
152
+ if (typeof field === 'string') {
153
+ return '';
154
+ }
155
+ return field.name ?? '';
156
+ };
157
+ const getEventLabel = (r, compact)=>{
158
+ const time = new Date(r.startTime).toLocaleTimeString([], {
159
+ hour: '2-digit',
160
+ minute: '2-digit'
161
+ });
162
+ const serviceName = getResName(r.service);
163
+ if (compact) {
164
+ return `${time} ${serviceName}`.trim();
165
+ }
166
+ const customerName = getResName(r.customer);
167
+ const parts = [
168
+ time,
169
+ serviceName,
170
+ customerName
171
+ ].filter(Boolean);
172
+ return parts.join(' - ');
173
+ };
174
+ const getEventTooltip = (r)=>{
175
+ const serviceName = getResName(r.service) || t('reservation:calendarUnknownService');
176
+ const startStr = new Date(r.startTime).toLocaleTimeString([], {
177
+ hour: '2-digit',
178
+ minute: '2-digit'
179
+ });
180
+ const endStr = r.endTime ? new Date(r.endTime).toLocaleTimeString([], {
181
+ hour: '2-digit',
182
+ minute: '2-digit'
183
+ }) : '?';
184
+ const customerName = getResName(r.customer) || t('reservation:calendarUnknownCustomer');
185
+ const resourceName = getResName(r.resource) || t('reservation:calendarUnknownResource');
186
+ const status = STATUS_LABELS[r.status] ?? r.status;
187
+ return `${serviceName}\n${startStr} - ${endStr}\n${t('reservation:tooltipCustomer')} ${customerName}\n${t('reservation:tooltipResource')} ${resourceName}\n${t('reservation:tooltipStatus')} ${status}`;
188
+ };
189
+ const renderEventItem = (r, compact)=>/*#__PURE__*/ _jsx("div", {
190
+ className: `${styles.eventItem} ${STATUS_CLASS_MAP[r.status] ?? ''}`,
191
+ onClick: (e)=>handleEventClick(e, r.id),
192
+ onKeyDown: (e)=>handleEventKeyDown(e, r.id),
193
+ role: "button",
194
+ tabIndex: 0,
195
+ title: getEventTooltip(r),
196
+ children: getEventLabel(r, compact)
197
+ }, r.id);
198
+ const renderStatusLegend = ()=>/*#__PURE__*/ _jsx("div", {
199
+ className: styles.statusLegend,
200
+ children: Object.entries(STATUS_LABELS).map(([key, label])=>/*#__PURE__*/ _jsxs("div", {
201
+ className: styles.legendItem,
202
+ children: [
203
+ /*#__PURE__*/ _jsx("span", {
204
+ className: styles.legendDot,
205
+ style: {
206
+ background: STATUS_COLORS[key]
207
+ }
208
+ }),
209
+ label
210
+ ]
211
+ }, key))
212
+ });
213
+ const renderCurrentTimeLine = (cellDate, cellHour)=>{
214
+ const now = new Date();
215
+ if (now.getFullYear() !== cellDate.getFullYear() || now.getMonth() !== cellDate.getMonth() || now.getDate() !== cellDate.getDate() || now.getHours() !== cellHour) {
216
+ return null;
217
+ }
218
+ const topPercent = now.getMinutes() / 60 * 100;
219
+ return /*#__PURE__*/ _jsx("div", {
220
+ className: styles.currentTimeLine,
221
+ style: {
222
+ top: `${topPercent}%`
223
+ }
224
+ });
225
+ };
226
+ const renderMonthView = ()=>{
227
+ const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1);
228
+ const startDay = new Date(firstDay);
229
+ startDay.setDate(startDay.getDate() - startDay.getDay());
230
+ const days = [];
231
+ const d = new Date(startDay);
232
+ for(let i = 0; i < 42; i++){
233
+ days.push(new Date(d));
234
+ d.setDate(d.getDate() + 1);
235
+ }
236
+ const today = new Date();
237
+ const todayStr = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`;
238
+ return /*#__PURE__*/ _jsxs("div", {
239
+ className: styles.monthGrid,
240
+ children: [
241
+ [
242
+ t('reservation:dayShortSun'),
243
+ t('reservation:dayShortMon'),
244
+ t('reservation:dayShortTue'),
245
+ t('reservation:dayShortWed'),
246
+ t('reservation:dayShortThu'),
247
+ t('reservation:dayShortFri'),
248
+ t('reservation:dayShortSat')
249
+ ].map((d)=>/*#__PURE__*/ _jsx("div", {
250
+ className: styles.dayHeader,
251
+ children: d
252
+ }, d)),
253
+ days.map((day, i)=>{
254
+ const dayStr = `${day.getFullYear()}-${day.getMonth()}-${day.getDate()}`;
255
+ const isToday = dayStr === todayStr;
256
+ const isOtherMonth = day.getMonth() !== currentDate.getMonth();
257
+ const dayReservations = reservations.filter((r)=>{
258
+ const rDate = new Date(r.startTime);
259
+ return rDate.getFullYear() === day.getFullYear() && rDate.getMonth() === day.getMonth() && rDate.getDate() === day.getDate();
260
+ });
261
+ const clickDate = new Date(day);
262
+ clickDate.setHours(9, 0, 0, 0);
263
+ return /*#__PURE__*/ _jsxs("div", {
264
+ className: `${styles.dayCell} ${isOtherMonth ? styles.dayCellOtherMonth : ''} ${isToday ? styles.dayCellToday : ''}`,
265
+ onClick: ()=>handleDateClick(clickDate),
266
+ onKeyDown: (e)=>{
267
+ if (e.key === 'Enter' || e.key === ' ') {
268
+ e.preventDefault();
269
+ handleDateClick(clickDate);
270
+ }
271
+ },
272
+ role: "button",
273
+ tabIndex: 0,
274
+ children: [
275
+ /*#__PURE__*/ _jsx("div", {
276
+ className: styles.dayNumber,
277
+ children: day.getDate()
278
+ }),
279
+ dayReservations.map((r)=>renderEventItem(r, true))
280
+ ]
281
+ }, i);
282
+ })
283
+ ]
284
+ });
285
+ };
286
+ const renderWeekView = ()=>{
287
+ const startOfWeek = new Date(currentDate);
288
+ startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay());
289
+ startOfWeek.setHours(0, 0, 0, 0);
290
+ const weekDays = [];
291
+ for(let i = 0; i < 7; i++){
292
+ const d = new Date(startOfWeek);
293
+ d.setDate(d.getDate() + i);
294
+ weekDays.push(d);
295
+ }
296
+ const hours = Array.from({
297
+ length: 12
298
+ }, (_, i)=>i + 7);
299
+ return /*#__PURE__*/ _jsxs("div", {
300
+ className: styles.weekView,
301
+ children: [
302
+ /*#__PURE__*/ _jsx("div", {
303
+ className: styles.dayHeader
304
+ }),
305
+ weekDays.map((d, i)=>/*#__PURE__*/ _jsx("div", {
306
+ className: styles.dayHeader,
307
+ children: d.toLocaleDateString([], {
308
+ day: 'numeric',
309
+ month: 'numeric',
310
+ weekday: 'short'
311
+ })
312
+ }, i)),
313
+ hours.map((hour)=>/*#__PURE__*/ _jsxs(Fragment, {
314
+ children: [
315
+ /*#__PURE__*/ _jsxs("div", {
316
+ className: styles.timeLabel,
317
+ children: [
318
+ hour.toString().padStart(2, '0'),
319
+ ":00"
320
+ ]
321
+ }),
322
+ weekDays.map((day, di)=>{
323
+ const cellReservations = reservations.filter((r)=>{
324
+ const rDate = new Date(r.startTime);
325
+ return rDate.getFullYear() === day.getFullYear() && rDate.getMonth() === day.getMonth() && rDate.getDate() === day.getDate() && rDate.getHours() === hour;
326
+ });
327
+ const clickDate = new Date(day);
328
+ clickDate.setHours(hour, 0, 0, 0);
329
+ return /*#__PURE__*/ _jsxs("div", {
330
+ className: styles.weekCell,
331
+ onClick: ()=>handleDateClick(clickDate),
332
+ onKeyDown: (e)=>{
333
+ if (e.key === 'Enter' || e.key === ' ') {
334
+ e.preventDefault();
335
+ handleDateClick(clickDate);
336
+ }
337
+ },
338
+ role: "button",
339
+ tabIndex: 0,
340
+ children: [
341
+ renderCurrentTimeLine(day, hour),
342
+ cellReservations.map((r)=>renderEventItem(r, false))
343
+ ]
344
+ }, `cell-${hour}-${di}`);
345
+ })
346
+ ]
347
+ }, `row-${hour}`))
348
+ ]
349
+ });
350
+ };
351
+ const renderDayView = ()=>{
352
+ const hours = Array.from({
353
+ length: 14
354
+ }, (_, i)=>i + 7);
355
+ return /*#__PURE__*/ _jsx("div", {
356
+ className: styles.dayView,
357
+ children: hours.map((hour)=>{
358
+ const hourReservations = reservations.filter((r)=>{
359
+ const rDate = new Date(r.startTime);
360
+ return rDate.getFullYear() === currentDate.getFullYear() && rDate.getMonth() === currentDate.getMonth() && rDate.getDate() === currentDate.getDate() && rDate.getHours() === hour;
361
+ });
362
+ const clickDate = new Date(currentDate);
363
+ clickDate.setHours(hour, 0, 0, 0);
364
+ return /*#__PURE__*/ _jsxs(Fragment, {
365
+ children: [
366
+ /*#__PURE__*/ _jsxs("div", {
367
+ className: styles.timeLabel,
368
+ children: [
369
+ hour.toString().padStart(2, '0'),
370
+ ":00"
371
+ ]
372
+ }),
373
+ /*#__PURE__*/ _jsxs("div", {
374
+ className: styles.dayViewCell,
375
+ onClick: ()=>handleDateClick(clickDate),
376
+ onKeyDown: (e)=>{
377
+ if (e.key === 'Enter' || e.key === ' ') {
378
+ e.preventDefault();
379
+ handleDateClick(clickDate);
380
+ }
381
+ },
382
+ role: "button",
383
+ tabIndex: 0,
384
+ children: [
385
+ renderCurrentTimeLine(currentDate, hour),
386
+ hourReservations.map((r)=>renderEventItem(r, false))
387
+ ]
388
+ })
389
+ ]
390
+ }, `row-${hour}`);
391
+ })
392
+ });
393
+ };
394
+ const dateLabel = useMemo(()=>{
395
+ if (viewMode === 'month') {
396
+ return currentDate.toLocaleDateString([], {
397
+ month: 'long',
398
+ year: 'numeric'
399
+ });
400
+ }
401
+ if (viewMode === 'week') {
402
+ const startOfWeek = new Date(currentDate);
403
+ startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay());
404
+ const endOfWeek = new Date(startOfWeek);
405
+ endOfWeek.setDate(endOfWeek.getDate() + 6);
406
+ return `${startOfWeek.toLocaleDateString([], {
407
+ day: 'numeric',
408
+ month: 'short'
409
+ })} - ${endOfWeek.toLocaleDateString([], {
410
+ day: 'numeric',
411
+ month: 'short',
412
+ year: 'numeric'
413
+ })}`;
414
+ }
415
+ return currentDate.toLocaleDateString([], {
416
+ day: 'numeric',
417
+ month: 'long',
418
+ weekday: 'long',
419
+ year: 'numeric'
420
+ });
421
+ }, [
422
+ currentDate,
423
+ viewMode
424
+ ]);
425
+ if (loading) {
426
+ return /*#__PURE__*/ _jsx("div", {
427
+ className: styles.loading,
428
+ children: t('reservation:calendarLoading')
429
+ });
430
+ }
431
+ return /*#__PURE__*/ _jsxs("div", {
432
+ className: styles.wrapper,
433
+ children: [
434
+ /*#__PURE__*/ _jsxs("div", {
435
+ className: styles.header,
436
+ children: [
437
+ /*#__PURE__*/ _jsxs("div", {
438
+ className: styles.navButtons,
439
+ children: [
440
+ /*#__PURE__*/ _jsx("button", {
441
+ className: styles.navButton,
442
+ onClick: ()=>navigate(-1),
443
+ type: "button",
444
+ children: "←"
445
+ }),
446
+ /*#__PURE__*/ _jsx("button", {
447
+ className: styles.navButton,
448
+ onClick: goToToday,
449
+ type: "button",
450
+ children: t('reservation:calendarToday')
451
+ }),
452
+ /*#__PURE__*/ _jsx("button", {
453
+ className: styles.navButton,
454
+ onClick: ()=>navigate(1),
455
+ type: "button",
456
+ children: "→"
457
+ }),
458
+ /*#__PURE__*/ _jsx("span", {
459
+ className: styles.currentDate,
460
+ children: dateLabel
461
+ })
462
+ ]
463
+ }),
464
+ /*#__PURE__*/ _jsxs("div", {
465
+ className: styles.viewToggle,
466
+ children: [
467
+ /*#__PURE__*/ _jsx("button", {
468
+ className: styles.createButton,
469
+ onClick: handleCreateNew,
470
+ type: "button",
471
+ children: t('reservation:calendarCreateNew')
472
+ }),
473
+ [
474
+ {
475
+ key: 'month',
476
+ label: t('reservation:calendarMonth')
477
+ },
478
+ {
479
+ key: 'week',
480
+ label: t('reservation:calendarWeek')
481
+ },
482
+ {
483
+ key: 'day',
484
+ label: t('reservation:calendarDay')
485
+ }
486
+ ].map(({ key, label })=>/*#__PURE__*/ _jsx("button", {
487
+ className: `${styles.viewToggleButton} ${viewMode === key ? styles.viewToggleButtonActive : ''}`,
488
+ onClick: ()=>setViewMode(key),
489
+ type: "button",
490
+ children: label
491
+ }, key))
492
+ ]
493
+ })
494
+ ]
495
+ }),
496
+ renderStatusLegend(),
497
+ viewMode === 'month' && renderMonthView(),
498
+ viewMode === 'week' && renderWeekView(),
499
+ viewMode === 'day' && renderDayView(),
500
+ /*#__PURE__*/ _jsx(DocumentDrawer, {
501
+ initialData: initialData,
502
+ onSave: fetchReservations
503
+ })
504
+ ]
505
+ });
506
+ };
507
+
508
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/CalendarView/index.tsx"],"sourcesContent":["'use client'\nimport type { AdminViewServerProps } from 'payload'\n\nimport { useConfig, useDocumentDrawer, useTranslation } from '@payloadcms/ui'\nimport React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'\n\nimport type { PluginT } from '../../translations/index.js'\n\nimport styles from './CalendarView.module.css'\n\ntype ViewMode = 'day' | 'month' | 'week'\n\ntype Reservation = {\n customer?: { name?: string } | string\n endTime?: string\n id: string\n resource?: { name?: string } | string\n service?: { name?: string } | string\n startTime: string\n status: string\n}\n\nconst STATUS_CLASS_MAP: Record<string, string> = {\n cancelled: styles.statusCancelled,\n completed: styles.statusCompleted,\n confirmed: styles.statusConfirmed,\n 'no-show': styles.statusNoShow,\n pending: styles.statusPending,\n}\n\nconst STATUS_COLORS: Record<string, string> = {\n cancelled: '#e5e7eb',\n completed: '#d1fae5',\n confirmed: '#dbeafe',\n 'no-show': '#fee2e2',\n pending: '#fef3c7',\n}\n\nexport const CalendarView: React.FC<AdminViewServerProps> = () => {\n const { config } = useConfig()\n const { t: _t } = useTranslation()\n const t = _t as PluginT\n\n const STATUS_LABELS = useMemo<Record<string, string>>(\n () => ({\n cancelled: t('reservation:statusCancelled'),\n completed: t('reservation:statusCompleted'),\n confirmed: t('reservation:statusConfirmed'),\n 'no-show': t('reservation:statusNoShowLabel'),\n pending: t('reservation:statusPending'),\n }),\n [t],\n )\n const slugs = config.admin?.custom?.reservationSlugs\n const reservationSlug = slugs?.reservations ?? 'reservations'\n\n const [currentDate, setCurrentDate] = useState(() => new Date())\n const [viewMode, setViewMode] = useState<ViewMode>('month')\n const [reservations, setReservations] = useState<Reservation[]>([])\n const [loading, setLoading] = useState(true)\n const [drawerDocId, setDrawerDocId] = useState<null | string>(null)\n const [initialData, setInitialData] = useState<Record<string, unknown> | undefined>(undefined)\n\n const [DocumentDrawer, , { openDrawer }] = useDocumentDrawer({\n id: drawerDocId ?? undefined,\n collectionSlug: reservationSlug,\n })\n\n const pendingDrawerOpen = useRef(false)\n\n useEffect(() => {\n if (pendingDrawerOpen.current) {\n pendingDrawerOpen.current = false\n openDrawer()\n }\n })\n\n const { rangeEnd, rangeStart } = useMemo(() => {\n const start = new Date(currentDate)\n const end = new Date(currentDate)\n\n if (viewMode === 'month') {\n start.setDate(1)\n start.setDate(start.getDate() - start.getDay())\n end.setMonth(end.getMonth() + 1, 0)\n end.setDate(end.getDate() + (6 - end.getDay()))\n } else if (viewMode === 'week') {\n const dayOfWeek = start.getDay()\n start.setDate(start.getDate() - dayOfWeek)\n end.setDate(start.getDate() + 6)\n }\n start.setHours(0, 0, 0, 0)\n end.setHours(23, 59, 59, 999)\n\n return { rangeEnd: end, rangeStart: start }\n }, [currentDate, viewMode])\n\n const fetchReservations = useCallback(async () => {\n setLoading(true)\n try {\n const apiUrl = `${config.serverURL ?? ''}${config.routes.api}/${reservationSlug}`\n const params = new URLSearchParams({\n depth: '1',\n limit: '500',\n sort: 'startTime',\n 'where[startTime][greater_than_equal]': rangeStart.toISOString(),\n 'where[startTime][less_than_equal]': rangeEnd.toISOString(),\n })\n const response = await fetch(`${apiUrl}?${params}`)\n const result = await response.json()\n setReservations(result.docs ?? [])\n } catch {\n setReservations([])\n }\n setLoading(false)\n }, [rangeStart, rangeEnd, config.routes.api, config.serverURL, reservationSlug])\n\n useEffect(() => {\n void fetchReservations()\n }, [fetchReservations])\n\n const handleEventClick = useCallback((e: React.MouseEvent, id: string) => {\n e.stopPropagation()\n setDrawerDocId(id)\n setInitialData(undefined)\n pendingDrawerOpen.current = true\n }, [])\n\n const handleEventKeyDown = useCallback((e: React.KeyboardEvent, id: string) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n e.stopPropagation()\n setDrawerDocId(id)\n setInitialData(undefined)\n pendingDrawerOpen.current = true\n }\n }, [])\n\n const handleCreateNew = useCallback(() => {\n setDrawerDocId(null)\n setInitialData(undefined)\n pendingDrawerOpen.current = true\n }, [])\n\n const handleDateClick = useCallback((date: Date) => {\n setDrawerDocId(null)\n setInitialData({ startTime: date.toISOString() })\n pendingDrawerOpen.current = true\n }, [])\n\n const navigate = useCallback(\n (direction: -1 | 1) => {\n setCurrentDate((prev) => {\n const next = new Date(prev)\n if (viewMode === 'month') {\n next.setMonth(next.getMonth() + direction)\n } else if (viewMode === 'week') {\n next.setDate(next.getDate() + 7 * direction)\n } else {\n next.setDate(next.getDate() + direction)\n }\n return next\n })\n },\n [viewMode],\n )\n\n const goToToday = useCallback(() => setCurrentDate(new Date()), [])\n\n const getResName = (field: { name?: string } | string | undefined): string => {\n if (!field) {return ''}\n if (typeof field === 'string') {return ''}\n return field.name ?? ''\n }\n\n const getEventLabel = (r: Reservation, compact: boolean) => {\n const time = new Date(r.startTime).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })\n const serviceName = getResName(r.service)\n if (compact) {\n return `${time} ${serviceName}`.trim()\n }\n const customerName = getResName(r.customer)\n const parts = [time, serviceName, customerName].filter(Boolean)\n return parts.join(' - ')\n }\n\n const getEventTooltip = (r: Reservation): string => {\n const serviceName = getResName(r.service) || t('reservation:calendarUnknownService')\n const startStr = new Date(r.startTime).toLocaleTimeString([], {\n hour: '2-digit',\n minute: '2-digit',\n })\n const endStr = r.endTime\n ? new Date(r.endTime).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n : '?'\n const customerName = getResName(r.customer) || t('reservation:calendarUnknownCustomer')\n const resourceName = getResName(r.resource) || t('reservation:calendarUnknownResource')\n const status = STATUS_LABELS[r.status] ?? r.status\n return `${serviceName}\\n${startStr} - ${endStr}\\n${t('reservation:tooltipCustomer')} ${customerName}\\n${t('reservation:tooltipResource')} ${resourceName}\\n${t('reservation:tooltipStatus')} ${status}`\n }\n\n const renderEventItem = (r: Reservation, compact: boolean) => (\n <div\n className={`${styles.eventItem} ${STATUS_CLASS_MAP[r.status] ?? ''}`}\n key={r.id}\n onClick={(e) => handleEventClick(e, r.id)}\n onKeyDown={(e) => handleEventKeyDown(e, r.id)}\n role=\"button\"\n tabIndex={0}\n title={getEventTooltip(r)}\n >\n {getEventLabel(r, compact)}\n </div>\n )\n\n const renderStatusLegend = () => (\n <div className={styles.statusLegend}>\n {Object.entries(STATUS_LABELS).map(([key, label]) => (\n <div className={styles.legendItem} key={key}>\n <span className={styles.legendDot} style={{ background: STATUS_COLORS[key] }} />\n {label}\n </div>\n ))}\n </div>\n )\n\n const renderCurrentTimeLine = (cellDate: Date, cellHour: number) => {\n const now = new Date()\n if (\n now.getFullYear() !== cellDate.getFullYear() ||\n now.getMonth() !== cellDate.getMonth() ||\n now.getDate() !== cellDate.getDate() ||\n now.getHours() !== cellHour\n ) {\n return null\n }\n const topPercent = (now.getMinutes() / 60) * 100\n return <div className={styles.currentTimeLine} style={{ top: `${topPercent}%` }} />\n }\n\n const renderMonthView = () => {\n const firstDay = new Date(currentDate.getFullYear(), currentDate.getMonth(), 1)\n const startDay = new Date(firstDay)\n startDay.setDate(startDay.getDate() - startDay.getDay())\n\n const days: Date[] = []\n const d = new Date(startDay)\n for (let i = 0; i < 42; i++) {\n days.push(new Date(d))\n d.setDate(d.getDate() + 1)\n }\n\n const today = new Date()\n const todayStr = `${today.getFullYear()}-${today.getMonth()}-${today.getDate()}`\n\n return (\n <div className={styles.monthGrid}>\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 ].map((d) => (\n <div className={styles.dayHeader} key={d}>\n {d}\n </div>\n ))}\n {days.map((day, i) => {\n const dayStr = `${day.getFullYear()}-${day.getMonth()}-${day.getDate()}`\n const isToday = dayStr === todayStr\n const isOtherMonth = day.getMonth() !== currentDate.getMonth()\n const dayReservations = reservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n rDate.getFullYear() === day.getFullYear() &&\n rDate.getMonth() === day.getMonth() &&\n rDate.getDate() === day.getDate()\n )\n })\n\n const clickDate = new Date(day)\n clickDate.setHours(9, 0, 0, 0)\n\n return (\n <div\n className={`${styles.dayCell} ${isOtherMonth ? styles.dayCellOtherMonth : ''} ${isToday ? styles.dayCellToday : ''}`}\n key={i}\n onClick={() => handleDateClick(clickDate)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleDateClick(clickDate)\n }\n }}\n role=\"button\"\n tabIndex={0}\n >\n <div className={styles.dayNumber}>{day.getDate()}</div>\n {dayReservations.map((r) => renderEventItem(r, true))}\n </div>\n )\n })}\n </div>\n )\n }\n\n const renderWeekView = () => {\n const startOfWeek = new Date(currentDate)\n startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay())\n startOfWeek.setHours(0, 0, 0, 0)\n\n const weekDays: Date[] = []\n for (let i = 0; i < 7; i++) {\n const d = new Date(startOfWeek)\n d.setDate(d.getDate() + i)\n weekDays.push(d)\n }\n\n const hours = Array.from({ length: 12 }, (_, i) => i + 7)\n\n return (\n <div className={styles.weekView}>\n <div className={styles.dayHeader} />\n {weekDays.map((d, i) => (\n <div className={styles.dayHeader} key={i}>\n {d.toLocaleDateString([], { day: 'numeric', month: 'numeric', weekday: 'short' })}\n </div>\n ))}\n {hours.map((hour) => (\n <Fragment key={`row-${hour}`}>\n <div className={styles.timeLabel}>\n {hour.toString().padStart(2, '0')}:00\n </div>\n {weekDays.map((day, di) => {\n const cellReservations = reservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n rDate.getFullYear() === day.getFullYear() &&\n rDate.getMonth() === day.getMonth() &&\n rDate.getDate() === day.getDate() &&\n rDate.getHours() === hour\n )\n })\n const clickDate = new Date(day)\n clickDate.setHours(hour, 0, 0, 0)\n return (\n <div\n className={styles.weekCell}\n key={`cell-${hour}-${di}`}\n onClick={() => handleDateClick(clickDate)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleDateClick(clickDate)\n }\n }}\n role=\"button\"\n tabIndex={0}\n >\n {renderCurrentTimeLine(day, hour)}\n {cellReservations.map((r) => renderEventItem(r, false))}\n </div>\n )\n })}\n </Fragment>\n ))}\n </div>\n )\n }\n\n const renderDayView = () => {\n const hours = Array.from({ length: 14 }, (_, i) => i + 7)\n\n return (\n <div className={styles.dayView}>\n {hours.map((hour) => {\n const hourReservations = reservations.filter((r) => {\n const rDate = new Date(r.startTime)\n return (\n rDate.getFullYear() === currentDate.getFullYear() &&\n rDate.getMonth() === currentDate.getMonth() &&\n rDate.getDate() === currentDate.getDate() &&\n rDate.getHours() === hour\n )\n })\n const clickDate = new Date(currentDate)\n clickDate.setHours(hour, 0, 0, 0)\n return (\n <Fragment key={`row-${hour}`}>\n <div className={styles.timeLabel}>\n {hour.toString().padStart(2, '0')}:00\n </div>\n <div\n className={styles.dayViewCell}\n onClick={() => handleDateClick(clickDate)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault()\n handleDateClick(clickDate)\n }\n }}\n role=\"button\"\n tabIndex={0}\n >\n {renderCurrentTimeLine(currentDate, hour)}\n {hourReservations.map((r) => renderEventItem(r, false))}\n </div>\n </Fragment>\n )\n })}\n </div>\n )\n }\n\n const dateLabel = useMemo(() => {\n if (viewMode === 'month') {\n return currentDate.toLocaleDateString([], { month: 'long', year: 'numeric' })\n }\n if (viewMode === 'week') {\n const startOfWeek = new Date(currentDate)\n startOfWeek.setDate(startOfWeek.getDate() - startOfWeek.getDay())\n const endOfWeek = new Date(startOfWeek)\n endOfWeek.setDate(endOfWeek.getDate() + 6)\n return `${startOfWeek.toLocaleDateString([], { day: 'numeric', month: 'short' })} - ${endOfWeek.toLocaleDateString([], { day: 'numeric', month: 'short', year: 'numeric' })}`\n }\n return currentDate.toLocaleDateString([], {\n day: 'numeric',\n month: 'long',\n weekday: 'long',\n year: 'numeric',\n })\n }, [currentDate, viewMode])\n\n if (loading) {\n return <div className={styles.loading}>{t('reservation:calendarLoading')}</div>\n }\n\n return (\n <div className={styles.wrapper}>\n <div className={styles.header}>\n <div className={styles.navButtons}>\n <button className={styles.navButton} onClick={() => navigate(-1)} type=\"button\">\n &larr;\n </button>\n <button className={styles.navButton} onClick={goToToday} type=\"button\">\n {t('reservation:calendarToday')}\n </button>\n <button className={styles.navButton} onClick={() => navigate(1)} type=\"button\">\n &rarr;\n </button>\n <span className={styles.currentDate}>{dateLabel}</span>\n </div>\n <div className={styles.viewToggle}>\n <button className={styles.createButton} onClick={handleCreateNew} type=\"button\">\n {t('reservation:calendarCreateNew')}\n </button>\n {([\n { key: 'month' as ViewMode, label: t('reservation:calendarMonth') },\n { key: 'week' as ViewMode, label: t('reservation:calendarWeek') },\n { key: 'day' as ViewMode, label: t('reservation:calendarDay') },\n ]).map(({ key, label }) => (\n <button\n className={`${styles.viewToggleButton} ${viewMode === key ? styles.viewToggleButtonActive : ''}`}\n key={key}\n onClick={() => setViewMode(key)}\n type=\"button\"\n >\n {label}\n </button>\n ))}\n </div>\n </div>\n {renderStatusLegend()}\n {viewMode === 'month' && renderMonthView()}\n {viewMode === 'week' && renderWeekView()}\n {viewMode === 'day' && renderDayView()}\n <DocumentDrawer initialData={initialData} onSave={fetchReservations} />\n </div>\n )\n}\n"],"names":["useConfig","useDocumentDrawer","useTranslation","React","Fragment","useCallback","useEffect","useMemo","useRef","useState","styles","STATUS_CLASS_MAP","cancelled","statusCancelled","completed","statusCompleted","confirmed","statusConfirmed","statusNoShow","pending","statusPending","STATUS_COLORS","CalendarView","config","t","_t","STATUS_LABELS","slugs","admin","custom","reservationSlugs","reservationSlug","reservations","currentDate","setCurrentDate","Date","viewMode","setViewMode","setReservations","loading","setLoading","drawerDocId","setDrawerDocId","initialData","setInitialData","undefined","DocumentDrawer","openDrawer","id","collectionSlug","pendingDrawerOpen","current","rangeEnd","rangeStart","start","end","setDate","getDate","getDay","setMonth","getMonth","dayOfWeek","setHours","fetchReservations","apiUrl","serverURL","routes","api","params","URLSearchParams","depth","limit","sort","toISOString","response","fetch","result","json","docs","handleEventClick","e","stopPropagation","handleEventKeyDown","key","preventDefault","handleCreateNew","handleDateClick","date","startTime","navigate","direction","prev","next","goToToday","getResName","field","name","getEventLabel","r","compact","time","toLocaleTimeString","hour","minute","serviceName","service","trim","customerName","customer","parts","filter","Boolean","join","getEventTooltip","startStr","endStr","endTime","resourceName","resource","status","renderEventItem","div","className","eventItem","onClick","onKeyDown","role","tabIndex","title","renderStatusLegend","statusLegend","Object","entries","map","label","legendItem","span","legendDot","style","background","renderCurrentTimeLine","cellDate","cellHour","now","getFullYear","getHours","topPercent","getMinutes","currentTimeLine","top","renderMonthView","firstDay","startDay","days","d","i","push","today","todayStr","monthGrid","dayHeader","day","dayStr","isToday","isOtherMonth","dayReservations","rDate","clickDate","dayCell","dayCellOtherMonth","dayCellToday","dayNumber","renderWeekView","startOfWeek","weekDays","hours","Array","from","length","_","weekView","toLocaleDateString","month","weekday","timeLabel","toString","padStart","di","cellReservations","weekCell","renderDayView","dayView","hourReservations","dayViewCell","dateLabel","year","endOfWeek","wrapper","header","navButtons","button","navButton","type","viewToggle","createButton","viewToggleButton","viewToggleButtonActive","onSave"],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ .wrapper {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 16px;
5
+ }
6
+
7
+ .title {
8
+ margin: 0;
9
+ font-size: 1.25rem;
10
+ }
11
+
12
+ .statsGrid {
13
+ display: grid;
14
+ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
15
+ gap: 12px;
16
+ }
17
+
18
+ .statCard {
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ padding: 12px;
23
+ border: 1px solid var(--theme-elevation-100);
24
+ border-radius: 4px;
25
+ }
26
+
27
+ .statValue {
28
+ font-size: 1.75rem;
29
+ font-weight: 700;
30
+ line-height: 1;
31
+ }
32
+
33
+ .statLabel {
34
+ font-size: 0.75rem;
35
+ color: var(--theme-elevation-500);
36
+ margin-top: 4px;
37
+ text-transform: uppercase;
38
+ }
39
+
40
+ .nextAppointment {
41
+ padding: 12px;
42
+ background: var(--theme-elevation-50);
43
+ border-radius: 4px;
44
+ }
45
+
46
+ .nextAppointment p {
47
+ margin: 4px 0;
48
+ }
49
+
50
+ .noData {
51
+ color: var(--theme-elevation-400);
52
+ font-style: italic;
53
+ }
@@ -0,0 +1,2 @@
1
+ import type { WidgetServerProps } from 'payload';
2
+ export declare const DashboardWidgetServer: (props: WidgetServerProps) => Promise<import("react").JSX.Element | null>;