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,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 ←\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 →\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
|
+
}
|