payload-reserve 1.6.0 → 2.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/README.md +40 -3
- package/dist/collections/Reservations.js +19 -7
- package/dist/collections/Reservations.js.map +1 -1
- package/dist/collections/Resources.js +11 -8
- package/dist/collections/Resources.js.map +1 -1
- package/dist/collections/Schedules.js +12 -6
- package/dist/collections/Schedules.js.map +1 -1
- package/dist/collections/Services.js +19 -10
- package/dist/collections/Services.js.map +1 -1
- package/dist/components/AvailabilityOverview/index.js +43 -18
- package/dist/components/AvailabilityOverview/index.js.map +1 -1
- package/dist/components/CalendarView/CalendarView.module.css +9 -0
- package/dist/components/CalendarView/LaneTimelineView.d.ts +4 -1
- package/dist/components/CalendarView/LaneTimelineView.js +17 -12
- package/dist/components/CalendarView/LaneTimelineView.js.map +1 -1
- package/dist/components/CalendarView/index.js +134 -44
- package/dist/components/CalendarView/index.js.map +1 -1
- package/dist/components/CustomerField/index.js +8 -3
- package/dist/components/CustomerField/index.js.map +1 -1
- package/dist/components/DashboardWidget/DashboardWidgetServer.js +79 -17
- package/dist/components/DashboardWidget/DashboardWidgetServer.js.map +1 -1
- package/dist/defaults.js +42 -8
- package/dist/defaults.js.map +1 -1
- package/dist/endpoints/cancelBooking.js +1 -1
- package/dist/endpoints/cancelBooking.js.map +1 -1
- package/dist/endpoints/checkAvailability.js +56 -7
- package/dist/endpoints/checkAvailability.js.map +1 -1
- package/dist/endpoints/createBooking.js +19 -10
- package/dist/endpoints/createBooking.js.map +1 -1
- package/dist/endpoints/customerSearch.js +5 -2
- package/dist/endpoints/customerSearch.js.map +1 -1
- package/dist/endpoints/getSlots.js +56 -7
- package/dist/endpoints/getSlots.js.map +1 -1
- package/dist/endpoints/resourceAvailability.d.ts +2 -1
- package/dist/endpoints/resourceAvailability.js +85 -25
- package/dist/endpoints/resourceAvailability.js.map +1 -1
- package/dist/hooks/reservations/calculateEndTime.js +48 -20
- package/dist/hooks/reservations/calculateEndTime.js.map +1 -1
- package/dist/hooks/reservations/enforceCustomerOwnership.d.ts +11 -0
- package/dist/hooks/reservations/enforceCustomerOwnership.js +30 -0
- package/dist/hooks/reservations/enforceCustomerOwnership.js.map +1 -0
- package/dist/hooks/reservations/onStatusChange.js +10 -4
- package/dist/hooks/reservations/onStatusChange.js.map +1 -1
- package/dist/hooks/reservations/validateCancellation.js +3 -2
- package/dist/hooks/reservations/validateCancellation.js.map +1 -1
- package/dist/hooks/reservations/validateConflicts.js +23 -4
- package/dist/hooks/reservations/validateConflicts.js.map +1 -1
- package/dist/hooks/reservations/validateGuestBooking.js +3 -4
- package/dist/hooks/reservations/validateGuestBooking.js.map +1 -1
- package/dist/hooks/reservations/validateStatusTransition.js +2 -2
- package/dist/hooks/reservations/validateStatusTransition.js.map +1 -1
- package/dist/hooks/users/provisionStaffResource.js +5 -8
- package/dist/hooks/users/provisionStaffResource.js.map +1 -1
- package/dist/plugin.js +81 -13
- package/dist/plugin.js.map +1 -1
- package/dist/services/AvailabilityService.d.ts +54 -2
- package/dist/services/AvailabilityService.js +180 -46
- package/dist/services/AvailabilityService.js.map +1 -1
- package/dist/translations/ar.json +1 -0
- package/dist/translations/de.json +1 -0
- package/dist/translations/en.json +1 -0
- package/dist/translations/es.json +1 -0
- package/dist/translations/fa.json +1 -0
- package/dist/translations/fr.json +1 -0
- package/dist/translations/hi.json +1 -0
- package/dist/translations/id.json +1 -0
- package/dist/translations/pl.json +1 -0
- package/dist/translations/ru.json +1 -0
- package/dist/translations/tr.json +1 -0
- package/dist/translations/zh.json +1 -0
- package/dist/types.d.ts +39 -1
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -1
- package/dist/utilities/collectionOverrides.d.ts +14 -0
- package/dist/utilities/collectionOverrides.js +47 -0
- package/dist/utilities/collectionOverrides.js.map +1 -0
- package/dist/utilities/ownerAccess.d.ts +6 -0
- package/dist/utilities/ownerAccess.js +25 -12
- package/dist/utilities/ownerAccess.js.map +1 -1
- package/dist/utilities/reservationChanges.d.ts +17 -0
- package/dist/utilities/reservationChanges.js +88 -0
- package/dist/utilities/reservationChanges.js.map +1 -0
- package/dist/utilities/scheduleUtils.d.ts +14 -8
- package/dist/utilities/scheduleUtils.js +26 -19
- package/dist/utilities/scheduleUtils.js.map +1 -1
- package/dist/utilities/timezoneUtils.d.ts +39 -0
- package/dist/utilities/timezoneUtils.js +134 -0
- package/dist/utilities/timezoneUtils.js.map +1 -0
- package/package.json +1 -1
|
@@ -4,7 +4,7 @@ import { useConfig, useDocumentDrawer, useTranslation } from '@payloadcms/ui';
|
|
|
4
4
|
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
5
5
|
import { computeSlotStates } from '../../utilities/computeSlotStates.js';
|
|
6
6
|
import { statusToI18nKey } from '../../utilities/i18nUtils.js';
|
|
7
|
-
import {
|
|
7
|
+
import { getDayKeyInTimezone, getHourInTimezone } from '../../utilities/timezoneUtils.js';
|
|
8
8
|
import { useTenantFilter } from '../../utilities/useTenantFilter.js';
|
|
9
9
|
import styles from './CalendarView.module.css';
|
|
10
10
|
import { LaneTimelineView } from './LaneTimelineView.js';
|
|
@@ -33,6 +33,38 @@ const CUSTOM_STATUS_PALETTE = [
|
|
|
33
33
|
'#fca5a5',
|
|
34
34
|
'#fdba74'
|
|
35
35
|
];
|
|
36
|
+
// Safe ceiling for list fetches; when totalDocs exceeds this we surface a
|
|
37
|
+
// "showing N of M" notice rather than silently truncating (review D9).
|
|
38
|
+
const MAX_LIST_LIMIT = 2000;
|
|
39
|
+
// Default visible-hour window for the week/day/lane grids; the actual window
|
|
40
|
+
// expands to include any booking outside it so nothing is hidden (review D8).
|
|
41
|
+
const DEFAULT_HOUR_START = 7;
|
|
42
|
+
const DEFAULT_HOUR_END = 20;
|
|
43
|
+
/**
|
|
44
|
+
* Visible-hour window covering `reservations` (in `timeZone`), never narrower
|
|
45
|
+
* than the default business window. Every booking's start hour gets a row, so a
|
|
46
|
+
* booking outside 7–20 is shown rather than silently dropped, and all three
|
|
47
|
+
* time views share one window.
|
|
48
|
+
*/ function computeHourWindow(reservations, timeZone) {
|
|
49
|
+
let startHour = DEFAULT_HOUR_START;
|
|
50
|
+
let endHour = DEFAULT_HOUR_END;
|
|
51
|
+
for (const r of reservations){
|
|
52
|
+
if (!r.startTime) {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const sh = getHourInTimezone(new Date(r.startTime), timeZone);
|
|
56
|
+
startHour = Math.min(startHour, sh);
|
|
57
|
+
endHour = Math.max(endHour, sh + 1);
|
|
58
|
+
if (r.endTime) {
|
|
59
|
+
// round the ending hour up so a slot that ends mid-hour still has a row
|
|
60
|
+
endHour = Math.max(endHour, getHourInTimezone(new Date(r.endTime), timeZone) + 1);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
endHour: Math.min(endHour, 24),
|
|
65
|
+
startHour: Math.max(startHour, 0)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
36
68
|
export const CalendarView = ()=>{
|
|
37
69
|
const { config } = useConfig();
|
|
38
70
|
const { t: _t } = useTranslation();
|
|
@@ -44,6 +76,7 @@ export const CalendarView = ()=>{
|
|
|
44
76
|
const resourceSlug = slugs?.resources ?? 'resources';
|
|
45
77
|
const reservationTenantParams = useTenantFilter(reservationSlug);
|
|
46
78
|
const resourceTenantParams = useTenantFilter(resourceSlug);
|
|
79
|
+
const reservationTimezone = config.admin?.custom?.reservationTimezone ?? 'UTC';
|
|
47
80
|
const statusMachine = config.admin?.custom?.reservationStatusMachine;
|
|
48
81
|
// The initial/pending status (what "pending" view shows)
|
|
49
82
|
const defaultStatus = statusMachine?.defaultStatus ?? 'pending';
|
|
@@ -108,6 +141,11 @@ export const CalendarView = ()=>{
|
|
|
108
141
|
const [viewMode, setViewMode] = useState('month');
|
|
109
142
|
const [reservations, setReservations] = useState([]);
|
|
110
143
|
const [loading, setLoading] = useState(true);
|
|
144
|
+
// { shown, total } when a fetch hit its cap, else null — drives a non-silent notice (D9)
|
|
145
|
+
const [truncation, setTruncation] = useState(null);
|
|
146
|
+
// Monotonic request counters so a slow earlier fetch can't overwrite a newer one (D5)
|
|
147
|
+
const reservationsSeq = useRef(0);
|
|
148
|
+
const pendingSeq = useRef(0);
|
|
111
149
|
const [drawerDocId, setDrawerDocId] = useState(null);
|
|
112
150
|
const [initialData, setInitialData] = useState(undefined);
|
|
113
151
|
// Resource filter state
|
|
@@ -166,8 +204,10 @@ export const CalendarView = ()=>{
|
|
|
166
204
|
if (viewMode === 'month') {
|
|
167
205
|
start.setDate(1);
|
|
168
206
|
start.setDate(start.getDate() - start.getDay());
|
|
169
|
-
|
|
170
|
-
|
|
207
|
+
// The grid always renders 42 cells (6 weeks) from `start`; fetch the same
|
|
208
|
+
// span so trailing weeks aren't silently empty (review D1).
|
|
209
|
+
end.setTime(start.getTime());
|
|
210
|
+
end.setDate(start.getDate() + 41);
|
|
171
211
|
} else if (viewMode === 'week') {
|
|
172
212
|
const dayOfWeek = start.getDay();
|
|
173
213
|
start.setDate(start.getDate() - dayOfWeek);
|
|
@@ -186,11 +226,12 @@ export const CalendarView = ()=>{
|
|
|
186
226
|
// Availability data for the selected resource (null when no resource selected — grid unshaded)
|
|
187
227
|
const { data: availability } = useResourceAvailability(apiBase, selectedResourceId || undefined, rangeStart, rangeEnd);
|
|
188
228
|
const fetchReservations = useCallback(async ()=>{
|
|
229
|
+
const seq = ++reservationsSeq.current;
|
|
189
230
|
setLoading(true);
|
|
190
231
|
try {
|
|
191
232
|
const params = new URLSearchParams({
|
|
192
233
|
depth: '1',
|
|
193
|
-
limit:
|
|
234
|
+
limit: String(MAX_LIST_LIMIT),
|
|
194
235
|
sort: 'startTime',
|
|
195
236
|
'where[startTime][greater_than_equal]': rangeStart.toISOString(),
|
|
196
237
|
'where[startTime][less_than_equal]': rangeEnd.toISOString(),
|
|
@@ -198,11 +239,25 @@ export const CalendarView = ()=>{
|
|
|
198
239
|
});
|
|
199
240
|
const response = await fetch(`${apiUrl}?${params}`);
|
|
200
241
|
const result = await response.json();
|
|
201
|
-
|
|
242
|
+
if (seq !== reservationsSeq.current) {
|
|
243
|
+
return;
|
|
244
|
+
} // a newer fetch superseded this one
|
|
245
|
+
const docs = result.docs ?? [];
|
|
246
|
+
setReservations(docs);
|
|
247
|
+
const total = result.totalDocs ?? docs.length;
|
|
248
|
+
setTruncation(total > docs.length ? {
|
|
249
|
+
shown: docs.length,
|
|
250
|
+
total
|
|
251
|
+
} : null);
|
|
202
252
|
} catch {
|
|
253
|
+
if (seq !== reservationsSeq.current) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
203
256
|
setReservations([]);
|
|
204
257
|
}
|
|
205
|
-
|
|
258
|
+
if (seq === reservationsSeq.current) {
|
|
259
|
+
setLoading(false);
|
|
260
|
+
}
|
|
206
261
|
}, [
|
|
207
262
|
rangeStart,
|
|
208
263
|
rangeEnd,
|
|
@@ -217,8 +272,11 @@ export const CalendarView = ()=>{
|
|
|
217
272
|
// Fetch pending count (always, for badge) — uses defaultStatus from config
|
|
218
273
|
const fetchPendingCount = useCallback(async ()=>{
|
|
219
274
|
try {
|
|
275
|
+
// limit:1 + depth:0 returns totalDocs (the full count) without downloading
|
|
276
|
+
// every pending doc — limit:0 in Payload means "no limit" (review D9).
|
|
220
277
|
const params = new URLSearchParams({
|
|
221
|
-
|
|
278
|
+
depth: '0',
|
|
279
|
+
limit: '1',
|
|
222
280
|
'where[status][equals]': defaultStatus,
|
|
223
281
|
...reservationTenantParams
|
|
224
282
|
});
|
|
@@ -240,18 +298,25 @@ export const CalendarView = ()=>{
|
|
|
240
298
|
]);
|
|
241
299
|
// Fetch pending reservations when tab is active — uses defaultStatus from config
|
|
242
300
|
const fetchPendingReservations = useCallback(async ()=>{
|
|
301
|
+
const seq = ++pendingSeq.current;
|
|
243
302
|
try {
|
|
244
303
|
const params = new URLSearchParams({
|
|
245
304
|
depth: '1',
|
|
246
|
-
limit:
|
|
305
|
+
limit: String(MAX_LIST_LIMIT),
|
|
247
306
|
sort: 'startTime',
|
|
248
307
|
'where[status][equals]': defaultStatus,
|
|
249
308
|
...reservationTenantParams
|
|
250
309
|
});
|
|
251
310
|
const response = await fetch(`${apiUrl}?${params}`);
|
|
252
311
|
const result = await response.json();
|
|
312
|
+
if (seq !== pendingSeq.current) {
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
253
315
|
setPendingReservations(result.docs ?? []);
|
|
254
316
|
} catch {
|
|
317
|
+
if (seq !== pendingSeq.current) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
255
320
|
setPendingReservations([]);
|
|
256
321
|
}
|
|
257
322
|
}, [
|
|
@@ -540,7 +605,8 @@ export const CalendarView = ()=>{
|
|
|
540
605
|
const getEventLabel = (r, compact)=>{
|
|
541
606
|
const time = new Date(r.startTime).toLocaleTimeString([], {
|
|
542
607
|
hour: '2-digit',
|
|
543
|
-
minute: '2-digit'
|
|
608
|
+
minute: '2-digit',
|
|
609
|
+
timeZone: reservationTimezone
|
|
544
610
|
});
|
|
545
611
|
const serviceName = getResName(r.service);
|
|
546
612
|
if (compact) {
|
|
@@ -571,11 +637,13 @@ export const CalendarView = ()=>{
|
|
|
571
637
|
const serviceName = getResName(r.service) || t('reservation:calendarUnknownService');
|
|
572
638
|
const startStr = new Date(r.startTime).toLocaleTimeString([], {
|
|
573
639
|
hour: '2-digit',
|
|
574
|
-
minute: '2-digit'
|
|
640
|
+
minute: '2-digit',
|
|
641
|
+
timeZone: reservationTimezone
|
|
575
642
|
});
|
|
576
643
|
const endStr = r.endTime ? new Date(r.endTime).toLocaleTimeString([], {
|
|
577
644
|
hour: '2-digit',
|
|
578
|
-
minute: '2-digit'
|
|
645
|
+
minute: '2-digit',
|
|
646
|
+
timeZone: reservationTimezone
|
|
579
647
|
}) : '?';
|
|
580
648
|
const customerName = getCustomerName(r.customer) || t('reservation:calendarUnknownCustomer');
|
|
581
649
|
const resourceNames = getResourceNames(r);
|
|
@@ -664,7 +732,7 @@ export const CalendarView = ()=>{
|
|
|
664
732
|
d.setDate(d.getDate() + 1);
|
|
665
733
|
}
|
|
666
734
|
const today = new Date();
|
|
667
|
-
const todayStr =
|
|
735
|
+
const todayStr = getDayKeyInTimezone(today, reservationTimezone);
|
|
668
736
|
return /*#__PURE__*/ _jsxs("div", {
|
|
669
737
|
className: styles.monthGrid,
|
|
670
738
|
children: [
|
|
@@ -681,12 +749,12 @@ export const CalendarView = ()=>{
|
|
|
681
749
|
children: d
|
|
682
750
|
}, d)),
|
|
683
751
|
days.map((day, i)=>{
|
|
684
|
-
const
|
|
685
|
-
const isToday =
|
|
686
|
-
const isOtherMonth =
|
|
752
|
+
const dayKey = getDayKeyInTimezone(day, reservationTimezone);
|
|
753
|
+
const isToday = dayKey === todayStr;
|
|
754
|
+
const isOtherMonth = dayKey.slice(0, 7) !== getDayKeyInTimezone(currentDate, reservationTimezone).slice(0, 7);
|
|
687
755
|
const dayReservations = filteredReservations.filter((r)=>{
|
|
688
|
-
const
|
|
689
|
-
return
|
|
756
|
+
const rKey = getDayKeyInTimezone(new Date(r.startTime), reservationTimezone);
|
|
757
|
+
return rKey === dayKey;
|
|
690
758
|
});
|
|
691
759
|
const clickDate = new Date(day);
|
|
692
760
|
clickDate.setHours(9, 0, 0, 0);
|
|
@@ -723,17 +791,19 @@ export const CalendarView = ()=>{
|
|
|
723
791
|
d.setDate(d.getDate() + i);
|
|
724
792
|
weekDays.push(d);
|
|
725
793
|
}
|
|
794
|
+
// Visible-hour window derived from the week's bookings (review D8)
|
|
795
|
+
const weekReservations = filteredReservations.filter((r)=>{
|
|
796
|
+
const k = getDayKeyInTimezone(new Date(r.startTime), reservationTimezone);
|
|
797
|
+
return weekDays.some((d)=>getDayKeyInTimezone(d, reservationTimezone) === k);
|
|
798
|
+
});
|
|
799
|
+
const { endHour: gridEndHour, startHour: gridStartHour } = computeHourWindow(weekReservations, reservationTimezone);
|
|
726
800
|
const hours = Array.from({
|
|
727
|
-
length:
|
|
728
|
-
}, (_, i)=>i +
|
|
729
|
-
// Grid bounds: hour 7 to hour 19 (end of last slot), step 60 min
|
|
730
|
-
const gridStartHour = 7;
|
|
731
|
-
const gridEndHour = gridStartHour + hours.length // 19
|
|
732
|
-
;
|
|
801
|
+
length: gridEndHour - gridStartHour
|
|
802
|
+
}, (_, i)=>i + gridStartHour);
|
|
733
803
|
const gridStep = 60;
|
|
734
804
|
// Build per-day slot-state maps when a resource is selected
|
|
735
805
|
const daySlotMaps = availability ? new Map(weekDays.map((day)=>{
|
|
736
|
-
const isoDay =
|
|
806
|
+
const isoDay = getDayKeyInTimezone(day, reservationTimezone);
|
|
737
807
|
const dayAvail = availability.days.find((d)=>d.date === isoDay);
|
|
738
808
|
const dayStart = new Date(day);
|
|
739
809
|
dayStart.setHours(gridStartHour, 0, 0, 0);
|
|
@@ -771,6 +841,7 @@ export const CalendarView = ()=>{
|
|
|
771
841
|
children: d.toLocaleDateString([], {
|
|
772
842
|
day: 'numeric',
|
|
773
843
|
month: 'numeric',
|
|
844
|
+
timeZone: reservationTimezone,
|
|
774
845
|
weekday: 'short'
|
|
775
846
|
})
|
|
776
847
|
}, i)),
|
|
@@ -784,14 +855,14 @@ export const CalendarView = ()=>{
|
|
|
784
855
|
]
|
|
785
856
|
}),
|
|
786
857
|
weekDays.map((day, di)=>{
|
|
858
|
+
const isoDay = getDayKeyInTimezone(day, reservationTimezone);
|
|
787
859
|
const cellReservations = filteredReservations.filter((r)=>{
|
|
788
860
|
const rDate = new Date(r.startTime);
|
|
789
|
-
return rDate
|
|
861
|
+
return getDayKeyInTimezone(rDate, reservationTimezone) === isoDay && getHourInTimezone(rDate, reservationTimezone) === hour;
|
|
790
862
|
});
|
|
791
863
|
const clickDate = new Date(day);
|
|
792
864
|
clickDate.setHours(hour, 0, 0, 0);
|
|
793
865
|
// Slot state (only when a resource is selected)
|
|
794
|
-
const isoDay = localDayKey(day);
|
|
795
866
|
const slotMap = daySlotMaps?.get(isoDay);
|
|
796
867
|
const slotInfo = slotMap?.get(clickDate.toISOString()) ?? null;
|
|
797
868
|
// Derive cell CSS class and interactivity based on slot state
|
|
@@ -856,18 +927,18 @@ export const CalendarView = ()=>{
|
|
|
856
927
|
});
|
|
857
928
|
};
|
|
858
929
|
const renderDayView = ()=>{
|
|
930
|
+
// Build slot-state map for the current day when a resource is selected
|
|
931
|
+
const currentDayKey = getDayKeyInTimezone(currentDate, reservationTimezone);
|
|
932
|
+
// Visible-hour window derived from this day's bookings (review D8)
|
|
933
|
+
const dayReservations = filteredReservations.filter((r)=>getDayKeyInTimezone(new Date(r.startTime), reservationTimezone) === currentDayKey);
|
|
934
|
+
const { endHour: gridEndHour, startHour: gridStartHour } = computeHourWindow(dayReservations, reservationTimezone);
|
|
859
935
|
const hours = Array.from({
|
|
860
|
-
length:
|
|
861
|
-
}, (_, i)=>i +
|
|
862
|
-
// Grid bounds: hour 7 to hour 21 (end of last slot), step 60 min
|
|
863
|
-
const gridStartHour = 7;
|
|
864
|
-
const gridEndHour = gridStartHour + hours.length // 21
|
|
865
|
-
;
|
|
936
|
+
length: gridEndHour - gridStartHour
|
|
937
|
+
}, (_, i)=>i + gridStartHour);
|
|
866
938
|
const gridStep = 60;
|
|
867
|
-
// Build slot-state map for the current day when a resource is selected
|
|
868
939
|
let daySlotMap = null;
|
|
869
940
|
if (availability) {
|
|
870
|
-
const isoDay =
|
|
941
|
+
const isoDay = currentDayKey;
|
|
871
942
|
const dayAvail = availability.days.find((d)=>d.date === isoDay);
|
|
872
943
|
const dayStart = new Date(currentDate);
|
|
873
944
|
dayStart.setHours(gridStartHour, 0, 0, 0);
|
|
@@ -894,7 +965,7 @@ export const CalendarView = ()=>{
|
|
|
894
965
|
children: hours.map((hour)=>{
|
|
895
966
|
const hourReservations = filteredReservations.filter((r)=>{
|
|
896
967
|
const rDate = new Date(r.startTime);
|
|
897
|
-
return rDate
|
|
968
|
+
return getDayKeyInTimezone(rDate, reservationTimezone) === currentDayKey && getHourInTimezone(rDate, reservationTimezone) === hour;
|
|
898
969
|
});
|
|
899
970
|
const clickDate = new Date(currentDate);
|
|
900
971
|
clickDate.setHours(hour, 0, 0, 0);
|
|
@@ -918,8 +989,7 @@ export const CalendarView = ()=>{
|
|
|
918
989
|
}
|
|
919
990
|
}
|
|
920
991
|
// Time-off label
|
|
921
|
-
const
|
|
922
|
-
const dayAvail = availability?.days.find((d)=>d.date === isoDay);
|
|
992
|
+
const dayAvail = availability?.days.find((d)=>d.date === currentDayKey);
|
|
923
993
|
const timeOffEntry = slotInfo?.state === 'time-off' ? dayAvail?.timeOff.find((to)=>new Date(to.start) <= clickDate && clickDate < new Date(to.end)) : undefined;
|
|
924
994
|
const timeOffLabel = timeOffEntry?.type ?? timeOffEntry?.reason ?? null;
|
|
925
995
|
const handleClick = isNonInteractive ? undefined : availability ? ()=>handleSlotClick(clickDate.toISOString()) : ()=>handleDateClick(clickDate);
|
|
@@ -1003,6 +1073,7 @@ export const CalendarView = ()=>{
|
|
|
1003
1073
|
hour: '2-digit',
|
|
1004
1074
|
minute: '2-digit',
|
|
1005
1075
|
month: 'short',
|
|
1076
|
+
timeZone: reservationTimezone,
|
|
1006
1077
|
year: 'numeric'
|
|
1007
1078
|
});
|
|
1008
1079
|
};
|
|
@@ -1159,21 +1230,25 @@ export const CalendarView = ()=>{
|
|
|
1159
1230
|
endOfWeek.setDate(endOfWeek.getDate() + 6);
|
|
1160
1231
|
return `${startOfWeek.toLocaleDateString([], {
|
|
1161
1232
|
day: 'numeric',
|
|
1162
|
-
month: 'short'
|
|
1233
|
+
month: 'short',
|
|
1234
|
+
timeZone: reservationTimezone
|
|
1163
1235
|
})} - ${endOfWeek.toLocaleDateString([], {
|
|
1164
1236
|
day: 'numeric',
|
|
1165
1237
|
month: 'short',
|
|
1238
|
+
timeZone: reservationTimezone,
|
|
1166
1239
|
year: 'numeric'
|
|
1167
1240
|
})}`;
|
|
1168
1241
|
}
|
|
1169
1242
|
return currentDate.toLocaleDateString([], {
|
|
1170
1243
|
day: 'numeric',
|
|
1171
1244
|
month: 'long',
|
|
1245
|
+
timeZone: reservationTimezone,
|
|
1172
1246
|
weekday: 'long',
|
|
1173
1247
|
year: 'numeric'
|
|
1174
1248
|
});
|
|
1175
1249
|
}, [
|
|
1176
1250
|
currentDate,
|
|
1251
|
+
reservationTimezone,
|
|
1177
1252
|
viewMode
|
|
1178
1253
|
]);
|
|
1179
1254
|
const handleDrawerSave = useCallback(()=>{
|
|
@@ -1269,6 +1344,14 @@ export const CalendarView = ()=>{
|
|
|
1269
1344
|
]
|
|
1270
1345
|
}),
|
|
1271
1346
|
viewMode !== 'pending' && renderStatusLegend(),
|
|
1347
|
+
viewMode !== 'pending' && truncation && /*#__PURE__*/ _jsx("div", {
|
|
1348
|
+
className: styles.truncationNotice,
|
|
1349
|
+
role: "status",
|
|
1350
|
+
children: t('reservation:calendarShowingNofM', {
|
|
1351
|
+
shown: String(truncation.shown),
|
|
1352
|
+
total: String(truncation.total)
|
|
1353
|
+
})
|
|
1354
|
+
}),
|
|
1272
1355
|
resources.length > 1 && /*#__PURE__*/ _jsx("div", {
|
|
1273
1356
|
className: styles.filterBar,
|
|
1274
1357
|
children: /*#__PURE__*/ _jsxs("select", {
|
|
@@ -1296,12 +1379,19 @@ export const CalendarView = ()=>{
|
|
|
1296
1379
|
viewMode === 'month' && renderMonthView(),
|
|
1297
1380
|
viewMode === 'week' && renderWeekView(),
|
|
1298
1381
|
viewMode === 'day' && renderDayView(),
|
|
1299
|
-
viewMode === 'lanes' &&
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1382
|
+
viewMode === 'lanes' && (()=>{
|
|
1383
|
+
const laneDayKey = getDayKeyInTimezone(currentDate, reservationTimezone);
|
|
1384
|
+
const { endHour, startHour } = computeHourWindow(filteredReservations.filter((r)=>getDayKeyInTimezone(new Date(r.startTime), reservationTimezone) === laneDayKey), reservationTimezone);
|
|
1385
|
+
return /*#__PURE__*/ _jsx(LaneTimelineView, {
|
|
1386
|
+
apiBase: apiBase,
|
|
1387
|
+
day: currentDate,
|
|
1388
|
+
endHour: endHour,
|
|
1389
|
+
onBook: handleLaneBook,
|
|
1390
|
+
resources: selectedResourceId ? resources.filter((r)=>r.id === selectedResourceId) : resources,
|
|
1391
|
+
startHour: startHour,
|
|
1392
|
+
timeZone: reservationTimezone
|
|
1393
|
+
});
|
|
1394
|
+
})()
|
|
1305
1395
|
]
|
|
1306
1396
|
}),
|
|
1307
1397
|
viewMode === 'pending' && renderPendingView(),
|