@marco.colia/react-fast-scheduler 2.2.1 → 2.2.2
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/dist/index.cjs +1 -1433
- package/dist/index.js +1 -1410
- package/package.json +1 -1
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,1410 +1 @@
|
|
|
1
|
-
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import { format, startOfMonth, startOfWeek, endOfWeek, endOfMonth, addDays, addMonths, isSameDay, isToday, isSameMonth } from 'date-fns';
|
|
3
|
-
import * as React2 from 'react';
|
|
4
|
-
|
|
5
|
-
// src/components/defaultRenderAppointment/index.tsx
|
|
6
|
-
function defaultRenderAppointment({
|
|
7
|
-
appointment,
|
|
8
|
-
isDropInvalid,
|
|
9
|
-
onPointerDown,
|
|
10
|
-
onResizePointerDown,
|
|
11
|
-
appointmentAppearance,
|
|
12
|
-
appointmentBackgroundColor
|
|
13
|
-
}) {
|
|
14
|
-
const rawMaybe = appointment.raw;
|
|
15
|
-
const description = typeof rawMaybe.description === "string" ? rawMaybe.description : void 0;
|
|
16
|
-
const legacyClassName = typeof appointmentBackgroundColor === "string" ? appointmentBackgroundColor : void 0;
|
|
17
|
-
const backgroundClassName = appointmentAppearance?.className ?? legacyClassName ?? "bg-muted/40 dark:bg-muted/30";
|
|
18
|
-
const invalidStateClassName = isDropInvalid ? "border-red-500 bg-red-100/80 text-red-950 ring-1 ring-red-500/60 dark:bg-red-950/40 dark:text-red-100" : "";
|
|
19
|
-
const visualStateClassName = appointment.visualState === "ghost" ? "cursor-grab border-dashed opacity-35" : appointment.visualState === "dragging" ? "cursor-grabbing opacity-55 shadow-xl ring-1 ring-border/70" : "cursor-grab";
|
|
20
|
-
const className = `relative h-full overflow-hidden rounded-md border border-border p-2 pb-5 text-foreground ${backgroundClassName} ${invalidStateClassName} ${visualStateClassName}`;
|
|
21
|
-
return /* @__PURE__ */ jsxs(
|
|
22
|
-
"div",
|
|
23
|
-
{
|
|
24
|
-
onPointerDown,
|
|
25
|
-
className,
|
|
26
|
-
children: [
|
|
27
|
-
isDropInvalid ? /* @__PURE__ */ jsx("div", { className: "absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-red-600 text-[10px] font-bold text-white", children: "X" }) : null,
|
|
28
|
-
/* @__PURE__ */ jsx("div", { className: "text-xs font-semibold", children: appointment.title }),
|
|
29
|
-
description ? /* @__PURE__ */ jsx("div", { className: "mt-1 text-[10px] font-medium text-muted-foreground", children: description }) : null,
|
|
30
|
-
/* @__PURE__ */ jsx(
|
|
31
|
-
"div",
|
|
32
|
-
{
|
|
33
|
-
role: "button",
|
|
34
|
-
"aria-label": "Resize appointment",
|
|
35
|
-
onPointerDown: (event) => {
|
|
36
|
-
if (appointment.visualState === "ghost") {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
event.stopPropagation();
|
|
40
|
-
onResizePointerDown(event);
|
|
41
|
-
},
|
|
42
|
-
className: "absolute inset-x-1 bottom-1 h-2 cursor-ns-resize rounded-full bg-border/90 transition-colors hover:bg-border"
|
|
43
|
-
}
|
|
44
|
-
)
|
|
45
|
-
]
|
|
46
|
-
}
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// src/components/constants.ts
|
|
51
|
-
var STEP_MIN = 15;
|
|
52
|
-
var MIN_EVENT_MIN = 15;
|
|
53
|
-
var PX_PER_MIN = 2;
|
|
54
|
-
var GUTTER_W = 72;
|
|
55
|
-
var TOP_PAD = 12;
|
|
56
|
-
var BOTTOM_PAD = 24;
|
|
57
|
-
var RESOURCE_MIN_W = 420;
|
|
58
|
-
function isNameLike(resource) {
|
|
59
|
-
if (!resource || typeof resource !== "object") {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
const maybe = resource;
|
|
63
|
-
return typeof maybe.firstName === "string" || typeof maybe.lastName === "string";
|
|
64
|
-
}
|
|
65
|
-
var DEFAULT_APPOINTMENT_COLOR_TOKEN_CLASS_MAP = {
|
|
66
|
-
slate: "bg-slate-100 dark:bg-slate-900/40",
|
|
67
|
-
gray: "bg-gray-100 dark:bg-gray-900/40",
|
|
68
|
-
zinc: "bg-zinc-100 dark:bg-zinc-900/40",
|
|
69
|
-
neutral: "bg-neutral-100 dark:bg-neutral-900/40",
|
|
70
|
-
stone: "bg-stone-100 dark:bg-stone-900/40",
|
|
71
|
-
red: "bg-red-100 dark:bg-red-950/40",
|
|
72
|
-
orange: "bg-orange-100 dark:bg-orange-950/40",
|
|
73
|
-
amber: "bg-amber-100 dark:bg-amber-950/40",
|
|
74
|
-
yellow: "bg-yellow-100 dark:bg-yellow-950/40",
|
|
75
|
-
lime: "bg-lime-100 dark:bg-lime-950/40",
|
|
76
|
-
green: "bg-green-100 dark:bg-green-950/40",
|
|
77
|
-
emerald: "bg-emerald-100 dark:bg-emerald-950/40",
|
|
78
|
-
teal: "bg-teal-100 dark:bg-teal-950/40",
|
|
79
|
-
cyan: "bg-cyan-100 dark:bg-cyan-950/40",
|
|
80
|
-
sky: "bg-sky-100 dark:bg-sky-950/40",
|
|
81
|
-
blue: "bg-blue-100 dark:bg-blue-950/40",
|
|
82
|
-
indigo: "bg-indigo-100 dark:bg-indigo-950/40",
|
|
83
|
-
violet: "bg-violet-100 dark:bg-violet-950/40",
|
|
84
|
-
purple: "bg-purple-100 dark:bg-purple-950/40",
|
|
85
|
-
fuchsia: "bg-fuchsia-100 dark:bg-fuchsia-950/40",
|
|
86
|
-
pink: "bg-pink-100 dark:bg-pink-950/40",
|
|
87
|
-
rose: "bg-rose-100 dark:bg-rose-950/40"
|
|
88
|
-
};
|
|
89
|
-
function clamp(n, min, max) {
|
|
90
|
-
return Math.max(min, Math.min(max, n));
|
|
91
|
-
}
|
|
92
|
-
function snap(mins, stepMin = STEP_MIN) {
|
|
93
|
-
return Math.round(mins / stepMin) * stepMin;
|
|
94
|
-
}
|
|
95
|
-
function parseHHMM(hhmm) {
|
|
96
|
-
const [h, m] = hhmm.split(":").map(Number);
|
|
97
|
-
return h * 60 + m;
|
|
98
|
-
}
|
|
99
|
-
function fullName(resource) {
|
|
100
|
-
return `${resource.firstName ?? ""} ${resource.lastName ?? ""}`.trim();
|
|
101
|
-
}
|
|
102
|
-
function dayISO(date) {
|
|
103
|
-
return format(date, "yyyy-MM-dd");
|
|
104
|
-
}
|
|
105
|
-
function shiftDays(date, delta) {
|
|
106
|
-
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + delta);
|
|
107
|
-
}
|
|
108
|
-
function dateAtMinute(selectedDate, dayStartAbs, minuteFromStart) {
|
|
109
|
-
const base = new Date(
|
|
110
|
-
selectedDate.getFullYear(),
|
|
111
|
-
selectedDate.getMonth(),
|
|
112
|
-
selectedDate.getDate(),
|
|
113
|
-
0,
|
|
114
|
-
0,
|
|
115
|
-
0,
|
|
116
|
-
0
|
|
117
|
-
);
|
|
118
|
-
const abs = dayStartAbs + minuteFromStart;
|
|
119
|
-
const hh = Math.floor(abs / 60);
|
|
120
|
-
const mm = abs % 60;
|
|
121
|
-
base.setHours(hh, mm, 0, 0);
|
|
122
|
-
return base;
|
|
123
|
-
}
|
|
124
|
-
function minutesFromDayStart(dayStartAbs, date) {
|
|
125
|
-
return date.getHours() * 60 + date.getMinutes() - dayStartAbs;
|
|
126
|
-
}
|
|
127
|
-
function overlaps(a, b) {
|
|
128
|
-
return a.startMin < b.endMin && b.startMin < a.endMin;
|
|
129
|
-
}
|
|
130
|
-
function layoutOverlaps(blocks) {
|
|
131
|
-
const sorted = [...blocks].sort((a, b) => a.startMin - b.startMin || a.endMin - b.endMin);
|
|
132
|
-
const groups = [];
|
|
133
|
-
let active = [];
|
|
134
|
-
let current = [];
|
|
135
|
-
for (const block of sorted) {
|
|
136
|
-
active = active.filter((item) => item.endMin > block.startMin);
|
|
137
|
-
const conflicts = active.some((item) => overlaps(item, block));
|
|
138
|
-
if (!conflicts && current.length > 0) {
|
|
139
|
-
groups.push(current);
|
|
140
|
-
current = [];
|
|
141
|
-
}
|
|
142
|
-
current.push(block);
|
|
143
|
-
active.push(block);
|
|
144
|
-
}
|
|
145
|
-
if (current.length) {
|
|
146
|
-
groups.push(current);
|
|
147
|
-
}
|
|
148
|
-
const output = [];
|
|
149
|
-
for (const group of groups) {
|
|
150
|
-
const sortedGroup = [...group].sort((a, b) => a.startMin - b.startMin || a.endMin - b.endMin);
|
|
151
|
-
const laneEnd = [];
|
|
152
|
-
const laneOf = /* @__PURE__ */ new Map();
|
|
153
|
-
for (const block of sortedGroup) {
|
|
154
|
-
let placed = false;
|
|
155
|
-
for (let i = 0; i < laneEnd.length; i++) {
|
|
156
|
-
if (laneEnd[i] <= block.startMin) {
|
|
157
|
-
laneEnd[i] = block.endMin;
|
|
158
|
-
laneOf.set(block.id, i);
|
|
159
|
-
placed = true;
|
|
160
|
-
break;
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
if (!placed) {
|
|
164
|
-
laneEnd.push(block.endMin);
|
|
165
|
-
laneOf.set(block.id, laneEnd.length - 1);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const lanes = laneEnd.length;
|
|
169
|
-
for (const block of sortedGroup) {
|
|
170
|
-
output.push({ ...block, lane: laneOf.get(block.id) ?? 0, lanes });
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
return output;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// src/utils/scheduler-data.utils.ts
|
|
177
|
-
function buildResourceMap({
|
|
178
|
-
fallback,
|
|
179
|
-
getValue,
|
|
180
|
-
resources,
|
|
181
|
-
toKey
|
|
182
|
-
}) {
|
|
183
|
-
const map = /* @__PURE__ */ new Map();
|
|
184
|
-
for (const resource of resources) {
|
|
185
|
-
map.set(toKey(resource), getValue(resource) ?? fallback);
|
|
186
|
-
}
|
|
187
|
-
return map;
|
|
188
|
-
}
|
|
189
|
-
function normalizeAppointments({
|
|
190
|
-
adapter,
|
|
191
|
-
appointments,
|
|
192
|
-
dayMinutes,
|
|
193
|
-
dayStartAbs,
|
|
194
|
-
selectedISO
|
|
195
|
-
}) {
|
|
196
|
-
return appointments.map((appointment) => {
|
|
197
|
-
const start = new Date(adapter.getStart(appointment));
|
|
198
|
-
const end = new Date(adapter.getEnd(appointment));
|
|
199
|
-
return {
|
|
200
|
-
raw: appointment,
|
|
201
|
-
id: adapter.getId(appointment),
|
|
202
|
-
resourceId: adapter.getResourceId(appointment),
|
|
203
|
-
start,
|
|
204
|
-
end,
|
|
205
|
-
startMin: clamp(minutesFromDayStart(dayStartAbs, start), 0, dayMinutes),
|
|
206
|
-
endMin: clamp(minutesFromDayStart(dayStartAbs, end), 0, dayMinutes),
|
|
207
|
-
title: adapter.getTitle(appointment),
|
|
208
|
-
renderKey: String(adapter.getId(appointment)),
|
|
209
|
-
visualState: "normal"
|
|
210
|
-
};
|
|
211
|
-
}).filter((appointment) => dayISO(appointment.start) === selectedISO);
|
|
212
|
-
}
|
|
213
|
-
function buildAppointmentMap(appointments) {
|
|
214
|
-
return new Map(appointments.map((appointment) => [appointment.id, appointment]));
|
|
215
|
-
}
|
|
216
|
-
function buildPresentationAppointments(appointments, drag, selectedDate, dayStartAbs) {
|
|
217
|
-
if (drag.kind === "none") {
|
|
218
|
-
return appointments;
|
|
219
|
-
}
|
|
220
|
-
if (drag.kind === "move") {
|
|
221
|
-
return appointments;
|
|
222
|
-
}
|
|
223
|
-
return appointments.map((appointment) => {
|
|
224
|
-
if (appointment.id !== drag.appointmentId) {
|
|
225
|
-
return appointment;
|
|
226
|
-
}
|
|
227
|
-
return {
|
|
228
|
-
...appointment,
|
|
229
|
-
resourceId: drag.resourceId,
|
|
230
|
-
startMin: drag.startMin,
|
|
231
|
-
endMin: drag.endMin,
|
|
232
|
-
start: dateAtMinute(selectedDate, dayStartAbs, drag.startMin),
|
|
233
|
-
end: dateAtMinute(selectedDate, dayStartAbs, drag.endMin),
|
|
234
|
-
visualState: "dragging"
|
|
235
|
-
};
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
function buildLaidOutByResource(resources, appointments) {
|
|
239
|
-
const appointmentsByResource = /* @__PURE__ */ new Map();
|
|
240
|
-
for (const appointment of appointments) {
|
|
241
|
-
const current = appointmentsByResource.get(appointment.resourceId) ?? [];
|
|
242
|
-
current.push(appointment);
|
|
243
|
-
appointmentsByResource.set(appointment.resourceId, current);
|
|
244
|
-
}
|
|
245
|
-
const result = /* @__PURE__ */ new Map();
|
|
246
|
-
for (const resource of resources) {
|
|
247
|
-
result.set(resource.id, buildLaidOutAppointments(appointmentsByResource.get(resource.id) ?? []));
|
|
248
|
-
}
|
|
249
|
-
return result;
|
|
250
|
-
}
|
|
251
|
-
function buildLaidOutAppointments(appointments) {
|
|
252
|
-
return layoutOverlaps(
|
|
253
|
-
appointments.map((appointment) => ({
|
|
254
|
-
...appointment,
|
|
255
|
-
appointmentId: appointment.id,
|
|
256
|
-
id: appointment.renderKey
|
|
257
|
-
}))
|
|
258
|
-
).map(({ appointmentId, ...appointment }) => ({
|
|
259
|
-
...appointment,
|
|
260
|
-
id: appointmentId
|
|
261
|
-
}));
|
|
262
|
-
}
|
|
263
|
-
function buildAppointmentAppearanceByResource(resources, resourceAppointmentClassMap, getResourceAppointmentAppearance, appointmentColorTokenClassByResource, appointmentBgByResource) {
|
|
264
|
-
const map = /* @__PURE__ */ new Map();
|
|
265
|
-
for (const resource of resources) {
|
|
266
|
-
const classFromIdMap = resourceAppointmentClassMap?.[String(resource.id)];
|
|
267
|
-
if (classFromIdMap) {
|
|
268
|
-
map.set(resource.id, { className: classFromIdMap });
|
|
269
|
-
continue;
|
|
270
|
-
}
|
|
271
|
-
const appearance = getResourceAppointmentAppearance?.(resource);
|
|
272
|
-
if (appearance) {
|
|
273
|
-
map.set(resource.id, appearance);
|
|
274
|
-
continue;
|
|
275
|
-
}
|
|
276
|
-
const tokenClass = appointmentColorTokenClassByResource.get(resource.id);
|
|
277
|
-
if (tokenClass) {
|
|
278
|
-
map.set(resource.id, { className: tokenClass });
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
const legacyBackground = appointmentBgByResource.get(resource.id);
|
|
282
|
-
map.set(resource.id, legacyBackground ? { className: legacyBackground } : void 0);
|
|
283
|
-
}
|
|
284
|
-
return map;
|
|
285
|
-
}
|
|
286
|
-
function useSchedulerBaseData({
|
|
287
|
-
adapter,
|
|
288
|
-
appointments,
|
|
289
|
-
dayEnd = "18:00",
|
|
290
|
-
dayStart = "09:00",
|
|
291
|
-
resources,
|
|
292
|
-
selectedDate
|
|
293
|
-
}) {
|
|
294
|
-
const dayStartAbs = React2.useMemo(() => parseHHMM(dayStart), [dayStart]);
|
|
295
|
-
const dayEndAbs = React2.useMemo(() => parseHHMM(dayEnd), [dayEnd]);
|
|
296
|
-
const dayMinutes = React2.useMemo(
|
|
297
|
-
() => Math.max(0, dayEndAbs - dayStartAbs),
|
|
298
|
-
[dayEndAbs, dayStartAbs]
|
|
299
|
-
);
|
|
300
|
-
const gridHeight = React2.useMemo(
|
|
301
|
-
() => TOP_PAD + dayMinutes * PX_PER_MIN + BOTTOM_PAD,
|
|
302
|
-
[dayMinutes]
|
|
303
|
-
);
|
|
304
|
-
const gridMinWidth = React2.useMemo(
|
|
305
|
-
() => GUTTER_W + Math.max(resources.length, 1) * RESOURCE_MIN_W,
|
|
306
|
-
[resources.length]
|
|
307
|
-
);
|
|
308
|
-
const selectedISO = React2.useMemo(() => dayISO(selectedDate), [selectedDate]);
|
|
309
|
-
const renderAppts = React2.useMemo(() => {
|
|
310
|
-
return normalizeAppointments({
|
|
311
|
-
adapter,
|
|
312
|
-
appointments,
|
|
313
|
-
dayMinutes,
|
|
314
|
-
dayStartAbs,
|
|
315
|
-
selectedISO
|
|
316
|
-
});
|
|
317
|
-
}, [adapter, appointments, dayMinutes, dayStartAbs, selectedISO]);
|
|
318
|
-
const appointmentMap = React2.useMemo(() => buildAppointmentMap(renderAppts), [renderAppts]);
|
|
319
|
-
return {
|
|
320
|
-
appointmentMap,
|
|
321
|
-
dayEndAbs,
|
|
322
|
-
dayMinutes,
|
|
323
|
-
dayStartAbs,
|
|
324
|
-
gridHeight,
|
|
325
|
-
gridMinWidth,
|
|
326
|
-
renderAppts
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// src/hooks/useSchedulerInteractions.helpers.ts
|
|
331
|
-
var DRAG_START_THRESHOLD_PX = 4;
|
|
332
|
-
var updateMovePreviewPosition = (setMovePreview, pointer) => {
|
|
333
|
-
setMovePreview(
|
|
334
|
-
(current) => current ? {
|
|
335
|
-
...current,
|
|
336
|
-
clientX: pointer.clientX,
|
|
337
|
-
clientY: pointer.clientY
|
|
338
|
-
} : current
|
|
339
|
-
);
|
|
340
|
-
};
|
|
341
|
-
var startPendingDragIfThresholdPassed = (args) => {
|
|
342
|
-
const { dragRef, pendingDragRef, pointer, setDrag, setMovePreview, suppressClickRef } = args;
|
|
343
|
-
const pending = pendingDragRef.current;
|
|
344
|
-
if (!pending || pending.drag.pointerId !== pointer.pointerId) {
|
|
345
|
-
return;
|
|
346
|
-
}
|
|
347
|
-
const deltaX = pointer.clientX - pending.startClientX;
|
|
348
|
-
const deltaY = pointer.clientY - pending.startClientY;
|
|
349
|
-
if (Math.hypot(deltaX, deltaY) < DRAG_START_THRESHOLD_PX) {
|
|
350
|
-
return;
|
|
351
|
-
}
|
|
352
|
-
suppressClickRef.current = true;
|
|
353
|
-
dragRef.current = pending.drag;
|
|
354
|
-
setDrag(pending.drag);
|
|
355
|
-
if (pending.drag.kind === "move") {
|
|
356
|
-
setMovePreview({
|
|
357
|
-
appointment: pending.appointment,
|
|
358
|
-
clientX: pointer.clientX,
|
|
359
|
-
clientY: pointer.clientY,
|
|
360
|
-
width: pending.width,
|
|
361
|
-
height: pending.height,
|
|
362
|
-
offsetX: pending.offsetX,
|
|
363
|
-
offsetY: pending.offsetY
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
pendingDragRef.current = null;
|
|
367
|
-
};
|
|
368
|
-
var buildNextMoveDrag = (drag, pointer, dayMinutes, pointerToResourceId, pointerToMin) => {
|
|
369
|
-
const nextResourceId = pointerToResourceId(pointer.clientX, drag.resourceId);
|
|
370
|
-
const pointerMin = pointerToMin(nextResourceId, pointer.clientY);
|
|
371
|
-
const rawStart = pointerMin - drag.offsetMin;
|
|
372
|
-
const nextStartMin = clamp(snap(rawStart), 0, dayMinutes - drag.durationMin);
|
|
373
|
-
const nextEndMin = nextStartMin + drag.durationMin;
|
|
374
|
-
if (nextStartMin === drag.startMin && nextEndMin === drag.endMin && nextResourceId === drag.resourceId) {
|
|
375
|
-
return null;
|
|
376
|
-
}
|
|
377
|
-
return {
|
|
378
|
-
...drag,
|
|
379
|
-
resourceId: nextResourceId,
|
|
380
|
-
startMin: nextStartMin,
|
|
381
|
-
endMin: nextEndMin
|
|
382
|
-
};
|
|
383
|
-
};
|
|
384
|
-
var buildNextResizeDrag = (drag, pointer, dayMinutes, pointerToMin) => {
|
|
385
|
-
const pointerMin = pointerToMin(drag.resourceId, pointer.clientY);
|
|
386
|
-
const nextEndMin = clamp(snap(pointerMin), drag.startMin + MIN_EVENT_MIN, dayMinutes);
|
|
387
|
-
if (nextEndMin === drag.endMin) {
|
|
388
|
-
return null;
|
|
389
|
-
}
|
|
390
|
-
return {
|
|
391
|
-
...drag,
|
|
392
|
-
endMin: nextEndMin
|
|
393
|
-
};
|
|
394
|
-
};
|
|
395
|
-
var processPointerFrame = (args) => {
|
|
396
|
-
const {
|
|
397
|
-
dayMinutes,
|
|
398
|
-
dragRef,
|
|
399
|
-
latestPointerRef,
|
|
400
|
-
pendingDragRef,
|
|
401
|
-
pointerToMin,
|
|
402
|
-
pointerToResourceId,
|
|
403
|
-
setDrag,
|
|
404
|
-
setMovePreview,
|
|
405
|
-
suppressClickRef
|
|
406
|
-
} = args;
|
|
407
|
-
const pointer = latestPointerRef.current;
|
|
408
|
-
if (!pointer) {
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
const currentDrag = dragRef.current;
|
|
412
|
-
if (currentDrag.kind === "none" && pendingDragRef.current) {
|
|
413
|
-
startPendingDragIfThresholdPassed({
|
|
414
|
-
pointer,
|
|
415
|
-
pendingDragRef,
|
|
416
|
-
setDrag,
|
|
417
|
-
setMovePreview,
|
|
418
|
-
dragRef,
|
|
419
|
-
suppressClickRef
|
|
420
|
-
});
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
if (currentDrag.kind === "none") {
|
|
424
|
-
return;
|
|
425
|
-
}
|
|
426
|
-
if (currentDrag.kind === "move") {
|
|
427
|
-
updateMovePreviewPosition(setMovePreview, pointer);
|
|
428
|
-
const nextDrag2 = buildNextMoveDrag(
|
|
429
|
-
currentDrag,
|
|
430
|
-
pointer,
|
|
431
|
-
dayMinutes,
|
|
432
|
-
pointerToResourceId,
|
|
433
|
-
pointerToMin
|
|
434
|
-
);
|
|
435
|
-
if (nextDrag2) {
|
|
436
|
-
suppressClickRef.current = true;
|
|
437
|
-
dragRef.current = nextDrag2;
|
|
438
|
-
setDrag(nextDrag2);
|
|
439
|
-
}
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
const nextDrag = buildNextResizeDrag(currentDrag, pointer, dayMinutes, pointerToMin);
|
|
443
|
-
if (nextDrag) {
|
|
444
|
-
suppressClickRef.current = true;
|
|
445
|
-
dragRef.current = nextDrag;
|
|
446
|
-
setDrag(nextDrag);
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
var finishDrag = (args) => {
|
|
450
|
-
const {
|
|
451
|
-
dragRef,
|
|
452
|
-
frameRef,
|
|
453
|
-
latestPointerRef,
|
|
454
|
-
pendingDragRef,
|
|
455
|
-
persistDrag,
|
|
456
|
-
setDrag,
|
|
457
|
-
setMovePreview,
|
|
458
|
-
suppressClickRef
|
|
459
|
-
} = args;
|
|
460
|
-
if (frameRef.current !== null) {
|
|
461
|
-
window.cancelAnimationFrame(frameRef.current);
|
|
462
|
-
frameRef.current = null;
|
|
463
|
-
}
|
|
464
|
-
latestPointerRef.current = null;
|
|
465
|
-
pendingDragRef.current = null;
|
|
466
|
-
setMovePreview(null);
|
|
467
|
-
const current = dragRef.current;
|
|
468
|
-
if (current.kind === "none") {
|
|
469
|
-
return;
|
|
470
|
-
}
|
|
471
|
-
setDrag({ kind: "none" });
|
|
472
|
-
dragRef.current = { kind: "none" };
|
|
473
|
-
if (suppressClickRef.current) {
|
|
474
|
-
void persistDrag(current);
|
|
475
|
-
}
|
|
476
|
-
};
|
|
477
|
-
|
|
478
|
-
// src/hooks/useSchedulerInteractions.ts
|
|
479
|
-
var useSchedulerInteractions = ({
|
|
480
|
-
appointmentMap,
|
|
481
|
-
dayMinutes,
|
|
482
|
-
dayStartAbs,
|
|
483
|
-
onAppointmentChange,
|
|
484
|
-
onPersistMoveResize,
|
|
485
|
-
renderAppts,
|
|
486
|
-
resources,
|
|
487
|
-
selectedDate
|
|
488
|
-
}) => {
|
|
489
|
-
const [drag, setDrag] = React2.useState({ kind: "none" });
|
|
490
|
-
const [movePreview, setMovePreview] = React2.useState(
|
|
491
|
-
null
|
|
492
|
-
);
|
|
493
|
-
const colRefs = React2.useRef({});
|
|
494
|
-
const suppressClickRef = React2.useRef(false);
|
|
495
|
-
const dragRef = React2.useRef({ kind: "none" });
|
|
496
|
-
const pendingDragRef = React2.useRef(null);
|
|
497
|
-
const latestPointerRef = React2.useRef(null);
|
|
498
|
-
const frameRef = React2.useRef(null);
|
|
499
|
-
React2.useEffect(() => {
|
|
500
|
-
dragRef.current = drag;
|
|
501
|
-
}, [drag]);
|
|
502
|
-
React2.useEffect(() => {
|
|
503
|
-
return () => {
|
|
504
|
-
if (frameRef.current !== null) {
|
|
505
|
-
window.cancelAnimationFrame(frameRef.current);
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
}, []);
|
|
509
|
-
const pointerToMin = React2.useCallback(
|
|
510
|
-
(resourceId, clientY) => {
|
|
511
|
-
const col = colRefs.current[String(resourceId)];
|
|
512
|
-
if (!col) {
|
|
513
|
-
return 0;
|
|
514
|
-
}
|
|
515
|
-
const y = clientY - col.getBoundingClientRect().top - TOP_PAD;
|
|
516
|
-
return clamp(snap(y / PX_PER_MIN), 0, dayMinutes);
|
|
517
|
-
},
|
|
518
|
-
[dayMinutes]
|
|
519
|
-
);
|
|
520
|
-
const pointerToResourceId = React2.useCallback(
|
|
521
|
-
(clientX, fallbackResourceId) => {
|
|
522
|
-
for (const resource of resources) {
|
|
523
|
-
const col = colRefs.current[String(resource.id)];
|
|
524
|
-
if (!col) {
|
|
525
|
-
continue;
|
|
526
|
-
}
|
|
527
|
-
const rect = col.getBoundingClientRect();
|
|
528
|
-
if (clientX >= rect.left && clientX <= rect.right) {
|
|
529
|
-
return resource.id;
|
|
530
|
-
}
|
|
531
|
-
}
|
|
532
|
-
return fallbackResourceId;
|
|
533
|
-
},
|
|
534
|
-
[resources]
|
|
535
|
-
);
|
|
536
|
-
const hasOverlap = React2.useCallback(
|
|
537
|
-
(appointmentId, resourceId, startMin, endMin) => {
|
|
538
|
-
const events = renderAppts.filter(
|
|
539
|
-
(appointment) => appointment.resourceId === resourceId && appointment.id !== appointmentId
|
|
540
|
-
);
|
|
541
|
-
return events.some(
|
|
542
|
-
(appointment) => overlaps(appointment, { startMin, endMin })
|
|
543
|
-
);
|
|
544
|
-
},
|
|
545
|
-
[renderAppts]
|
|
546
|
-
);
|
|
547
|
-
const isDropInvalid = React2.useMemo(() => {
|
|
548
|
-
if (drag.kind === "none") {
|
|
549
|
-
return false;
|
|
550
|
-
}
|
|
551
|
-
return hasOverlap(drag.appointmentId, drag.resourceId, drag.startMin, drag.endMin);
|
|
552
|
-
}, [drag, hasOverlap]);
|
|
553
|
-
const persistDrag = React2.useCallback(
|
|
554
|
-
async (state) => {
|
|
555
|
-
const target = appointmentMap.get(state.appointmentId);
|
|
556
|
-
if (!target) {
|
|
557
|
-
return;
|
|
558
|
-
}
|
|
559
|
-
if (hasOverlap(state.appointmentId, state.resourceId, state.startMin, state.endMin)) {
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
const next = {
|
|
563
|
-
resourceId: state.resourceId,
|
|
564
|
-
start: dateAtMinute(selectedDate, dayStartAbs, state.startMin),
|
|
565
|
-
end: dateAtMinute(selectedDate, dayStartAbs, state.endMin)
|
|
566
|
-
};
|
|
567
|
-
if (onAppointmentChange) {
|
|
568
|
-
await onAppointmentChange({
|
|
569
|
-
kind: state.kind,
|
|
570
|
-
appointment: target.raw,
|
|
571
|
-
previous: {
|
|
572
|
-
resourceId: target.resourceId,
|
|
573
|
-
start: target.start,
|
|
574
|
-
end: target.end
|
|
575
|
-
},
|
|
576
|
-
next
|
|
577
|
-
});
|
|
578
|
-
return;
|
|
579
|
-
}
|
|
580
|
-
if (onPersistMoveResize) {
|
|
581
|
-
await onPersistMoveResize({
|
|
582
|
-
appointment: target.raw,
|
|
583
|
-
resourceId: next.resourceId,
|
|
584
|
-
start: next.start,
|
|
585
|
-
end: next.end
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
|
-
},
|
|
589
|
-
[
|
|
590
|
-
appointmentMap,
|
|
591
|
-
dayStartAbs,
|
|
592
|
-
hasOverlap,
|
|
593
|
-
onAppointmentChange,
|
|
594
|
-
onPersistMoveResize,
|
|
595
|
-
selectedDate
|
|
596
|
-
]
|
|
597
|
-
);
|
|
598
|
-
const onApptPointerDown = React2.useCallback(
|
|
599
|
-
(event, appointment) => {
|
|
600
|
-
if (event.button !== 0) {
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
event.stopPropagation();
|
|
604
|
-
suppressClickRef.current = false;
|
|
605
|
-
const rect = event.currentTarget.getBoundingClientRect();
|
|
606
|
-
const pointerMin = pointerToMin(appointment.resourceId, event.clientY);
|
|
607
|
-
pendingDragRef.current = {
|
|
608
|
-
drag: {
|
|
609
|
-
kind: "move",
|
|
610
|
-
appointmentId: appointment.id,
|
|
611
|
-
pointerId: event.pointerId,
|
|
612
|
-
resourceId: appointment.resourceId,
|
|
613
|
-
durationMin: Math.max(MIN_EVENT_MIN, appointment.endMin - appointment.startMin),
|
|
614
|
-
offsetMin: pointerMin - appointment.startMin,
|
|
615
|
-
startMin: appointment.startMin,
|
|
616
|
-
endMin: appointment.endMin
|
|
617
|
-
},
|
|
618
|
-
appointment,
|
|
619
|
-
startClientX: event.clientX,
|
|
620
|
-
startClientY: event.clientY,
|
|
621
|
-
width: rect.width,
|
|
622
|
-
height: rect.height,
|
|
623
|
-
offsetX: event.clientX - rect.left,
|
|
624
|
-
offsetY: event.clientY - rect.top
|
|
625
|
-
};
|
|
626
|
-
},
|
|
627
|
-
[pointerToMin]
|
|
628
|
-
);
|
|
629
|
-
const onResizePointerDown = React2.useCallback(
|
|
630
|
-
(event, appointment) => {
|
|
631
|
-
if (event.button !== 0) {
|
|
632
|
-
return;
|
|
633
|
-
}
|
|
634
|
-
event.stopPropagation();
|
|
635
|
-
suppressClickRef.current = false;
|
|
636
|
-
pendingDragRef.current = {
|
|
637
|
-
drag: {
|
|
638
|
-
kind: "resize",
|
|
639
|
-
appointmentId: appointment.id,
|
|
640
|
-
pointerId: event.pointerId,
|
|
641
|
-
resourceId: appointment.resourceId,
|
|
642
|
-
startMin: appointment.startMin,
|
|
643
|
-
endMin: appointment.endMin
|
|
644
|
-
},
|
|
645
|
-
appointment,
|
|
646
|
-
startClientX: event.clientX,
|
|
647
|
-
startClientY: event.clientY,
|
|
648
|
-
width: 0,
|
|
649
|
-
height: 0,
|
|
650
|
-
offsetX: 0,
|
|
651
|
-
offsetY: 0
|
|
652
|
-
};
|
|
653
|
-
},
|
|
654
|
-
[]
|
|
655
|
-
);
|
|
656
|
-
const onGlobalPointerMove = React2.useCallback(
|
|
657
|
-
(event) => {
|
|
658
|
-
latestPointerRef.current = {
|
|
659
|
-
clientX: event.clientX,
|
|
660
|
-
clientY: event.clientY,
|
|
661
|
-
pointerId: event.pointerId
|
|
662
|
-
};
|
|
663
|
-
if (frameRef.current !== null) {
|
|
664
|
-
return;
|
|
665
|
-
}
|
|
666
|
-
frameRef.current = window.requestAnimationFrame(() => {
|
|
667
|
-
frameRef.current = null;
|
|
668
|
-
processPointerFrame({
|
|
669
|
-
dayMinutes,
|
|
670
|
-
dragRef,
|
|
671
|
-
latestPointerRef,
|
|
672
|
-
pendingDragRef,
|
|
673
|
-
pointerToMin,
|
|
674
|
-
pointerToResourceId,
|
|
675
|
-
setDrag,
|
|
676
|
-
setMovePreview,
|
|
677
|
-
suppressClickRef
|
|
678
|
-
});
|
|
679
|
-
});
|
|
680
|
-
},
|
|
681
|
-
[dayMinutes, pointerToMin, pointerToResourceId]
|
|
682
|
-
);
|
|
683
|
-
const onGlobalPointerUp = React2.useCallback(() => {
|
|
684
|
-
finishDrag({
|
|
685
|
-
dragRef,
|
|
686
|
-
frameRef,
|
|
687
|
-
latestPointerRef,
|
|
688
|
-
pendingDragRef,
|
|
689
|
-
persistDrag,
|
|
690
|
-
setDrag,
|
|
691
|
-
setMovePreview,
|
|
692
|
-
suppressClickRef
|
|
693
|
-
});
|
|
694
|
-
}, [persistDrag]);
|
|
695
|
-
return {
|
|
696
|
-
colRefs,
|
|
697
|
-
drag,
|
|
698
|
-
isDropInvalid,
|
|
699
|
-
movePreview,
|
|
700
|
-
onApptPointerDown,
|
|
701
|
-
onGlobalPointerMove,
|
|
702
|
-
onGlobalPointerUp,
|
|
703
|
-
onResizePointerDown,
|
|
704
|
-
suppressClickRef
|
|
705
|
-
};
|
|
706
|
-
};
|
|
707
|
-
function useSchedulerPresentationData({
|
|
708
|
-
appointmentColorTokenClassMap,
|
|
709
|
-
dayStartAbs,
|
|
710
|
-
drag,
|
|
711
|
-
getResourceAppointmentAppearance,
|
|
712
|
-
getResourceAppointmentBackground,
|
|
713
|
-
getResourceAppointmentColorToken,
|
|
714
|
-
renderAppts,
|
|
715
|
-
resourceAppointmentClassMap,
|
|
716
|
-
resources,
|
|
717
|
-
selectedDate
|
|
718
|
-
}) {
|
|
719
|
-
const effectiveAppts = React2.useMemo(() => {
|
|
720
|
-
return buildPresentationAppointments(renderAppts, drag, selectedDate, dayStartAbs);
|
|
721
|
-
}, [dayStartAbs, drag, renderAppts, selectedDate]);
|
|
722
|
-
const baseLaidOutByResource = React2.useMemo(() => {
|
|
723
|
-
return buildLaidOutByResource(resources, renderAppts);
|
|
724
|
-
}, [renderAppts, resources]);
|
|
725
|
-
const laidOutByResource = React2.useMemo(() => {
|
|
726
|
-
if (drag.kind !== "resize") {
|
|
727
|
-
return baseLaidOutByResource;
|
|
728
|
-
}
|
|
729
|
-
const next = new Map(baseLaidOutByResource);
|
|
730
|
-
const nextAppointmentsForResource = effectiveAppts.filter(
|
|
731
|
-
(appointment) => appointment.resourceId === drag.resourceId
|
|
732
|
-
);
|
|
733
|
-
next.set(drag.resourceId, buildLaidOutAppointments(nextAppointmentsForResource));
|
|
734
|
-
return next;
|
|
735
|
-
}, [baseLaidOutByResource, drag, effectiveAppts]);
|
|
736
|
-
const appointmentBgByResource = React2.useMemo(() => {
|
|
737
|
-
if (!getResourceAppointmentBackground) {
|
|
738
|
-
return /* @__PURE__ */ new Map();
|
|
739
|
-
}
|
|
740
|
-
return buildResourceMap({
|
|
741
|
-
fallback: void 0,
|
|
742
|
-
getValue: getResourceAppointmentBackground,
|
|
743
|
-
resources,
|
|
744
|
-
toKey: (resource) => resource.id
|
|
745
|
-
});
|
|
746
|
-
}, [getResourceAppointmentBackground, resources]);
|
|
747
|
-
const appointmentColorTokenClassByResource = React2.useMemo(() => {
|
|
748
|
-
if (!getResourceAppointmentColorToken) {
|
|
749
|
-
return /* @__PURE__ */ new Map();
|
|
750
|
-
}
|
|
751
|
-
const mergedClassMap = {
|
|
752
|
-
...DEFAULT_APPOINTMENT_COLOR_TOKEN_CLASS_MAP,
|
|
753
|
-
...appointmentColorTokenClassMap ?? {}
|
|
754
|
-
};
|
|
755
|
-
return buildResourceMap({
|
|
756
|
-
fallback: void 0,
|
|
757
|
-
getValue: (resource) => {
|
|
758
|
-
const token = getResourceAppointmentColorToken(resource);
|
|
759
|
-
return token ? mergedClassMap[token] : void 0;
|
|
760
|
-
},
|
|
761
|
-
resources,
|
|
762
|
-
toKey: (resource) => resource.id
|
|
763
|
-
});
|
|
764
|
-
}, [appointmentColorTokenClassMap, getResourceAppointmentColorToken, resources]);
|
|
765
|
-
const appointmentAppearanceByResource = React2.useMemo(() => {
|
|
766
|
-
return buildAppointmentAppearanceByResource(
|
|
767
|
-
resources,
|
|
768
|
-
resourceAppointmentClassMap,
|
|
769
|
-
getResourceAppointmentAppearance,
|
|
770
|
-
appointmentColorTokenClassByResource,
|
|
771
|
-
appointmentBgByResource
|
|
772
|
-
);
|
|
773
|
-
}, [
|
|
774
|
-
appointmentBgByResource,
|
|
775
|
-
appointmentColorTokenClassByResource,
|
|
776
|
-
getResourceAppointmentAppearance,
|
|
777
|
-
resourceAppointmentClassMap,
|
|
778
|
-
resources
|
|
779
|
-
]);
|
|
780
|
-
return {
|
|
781
|
-
appointmentAppearanceByResource,
|
|
782
|
-
appointmentBgByResource,
|
|
783
|
-
effectiveAppts,
|
|
784
|
-
laidOutByResource
|
|
785
|
-
};
|
|
786
|
-
}
|
|
787
|
-
function Gutter({ dayMinutes, dayStartAbs, gridHeight }) {
|
|
788
|
-
return /* @__PURE__ */ jsx(
|
|
789
|
-
"div",
|
|
790
|
-
{
|
|
791
|
-
className: "rfs-times border-border bg-card",
|
|
792
|
-
style: { width: GUTTER_W, height: gridHeight },
|
|
793
|
-
children: Array.from({ length: Math.floor(dayMinutes / 60) + 1 }).map((_, i) => /* @__PURE__ */ jsxs(
|
|
794
|
-
"div",
|
|
795
|
-
{
|
|
796
|
-
className: "rfs-time-tick text-muted-foreground",
|
|
797
|
-
style: { top: TOP_PAD + i * 60 * PX_PER_MIN },
|
|
798
|
-
children: [
|
|
799
|
-
String(Math.floor((dayStartAbs + i * 60) / 60)).padStart(2, "0"),
|
|
800
|
-
":",
|
|
801
|
-
String((dayStartAbs + i * 60) % 60).padStart(2, "0")
|
|
802
|
-
]
|
|
803
|
-
},
|
|
804
|
-
`tick-${i}`
|
|
805
|
-
))
|
|
806
|
-
}
|
|
807
|
-
);
|
|
808
|
-
}
|
|
809
|
-
function defaultRenderResourceHeader({ resource }) {
|
|
810
|
-
return /* @__PURE__ */ jsx("strong", { children: resource.label });
|
|
811
|
-
}
|
|
812
|
-
function Header({ resources, renderResourceHeader }) {
|
|
813
|
-
return /* @__PURE__ */ jsxs("div", { className: "rfs-header border-border bg-card", children: [
|
|
814
|
-
/* @__PURE__ */ jsx(
|
|
815
|
-
"div",
|
|
816
|
-
{
|
|
817
|
-
className: "rfs-gutter border-border",
|
|
818
|
-
style: { width: GUTTER_W }
|
|
819
|
-
}
|
|
820
|
-
),
|
|
821
|
-
resources.map((resource) => /* @__PURE__ */ jsxs(
|
|
822
|
-
"div",
|
|
823
|
-
{
|
|
824
|
-
className: "rfs-resource-header border-border bg-card text-foreground",
|
|
825
|
-
style: { minWidth: RESOURCE_MIN_W, flex: 1 },
|
|
826
|
-
children: [
|
|
827
|
-
(renderResourceHeader ?? ((r) => defaultRenderResourceHeader({ resource: r })))(
|
|
828
|
-
resource
|
|
829
|
-
),
|
|
830
|
-
isNameLike(resource) ? /* @__PURE__ */ jsx("div", { className: "rfs-resource-subtitle text-muted-foreground", children: fullName(resource) }) : null
|
|
831
|
-
]
|
|
832
|
-
},
|
|
833
|
-
String(resource.id)
|
|
834
|
-
))
|
|
835
|
-
] });
|
|
836
|
-
}
|
|
837
|
-
var ResourceColInner = ({
|
|
838
|
-
resource,
|
|
839
|
-
colRefs,
|
|
840
|
-
gridHeight,
|
|
841
|
-
dayMinutes,
|
|
842
|
-
appointments,
|
|
843
|
-
appointmentAppearance,
|
|
844
|
-
appointmentBg,
|
|
845
|
-
isDropInvalid,
|
|
846
|
-
renderAppointment,
|
|
847
|
-
onApptPointerDown,
|
|
848
|
-
onResizePointerDown,
|
|
849
|
-
drag,
|
|
850
|
-
suppressClickRef
|
|
851
|
-
}) => {
|
|
852
|
-
return /* @__PURE__ */ jsx(
|
|
853
|
-
"div",
|
|
854
|
-
{
|
|
855
|
-
className: "rfs-col border-border bg-card",
|
|
856
|
-
style: { minWidth: RESOURCE_MIN_W, flex: 1 },
|
|
857
|
-
children: /* @__PURE__ */ jsxs(
|
|
858
|
-
"div",
|
|
859
|
-
{
|
|
860
|
-
ref: (el) => {
|
|
861
|
-
colRefs.current[String(resource.id)] = el;
|
|
862
|
-
},
|
|
863
|
-
className: "rfs-col-inner bg-card",
|
|
864
|
-
style: { height: gridHeight },
|
|
865
|
-
children: [
|
|
866
|
-
Array.from({ length: Math.floor(dayMinutes / 60) + 1 }).map((_, i) => /* @__PURE__ */ jsx(
|
|
867
|
-
"div",
|
|
868
|
-
{
|
|
869
|
-
className: "rfs-hour-line border-border/70",
|
|
870
|
-
style: { top: TOP_PAD + i * 60 * PX_PER_MIN }
|
|
871
|
-
},
|
|
872
|
-
`major-${i}`
|
|
873
|
-
)),
|
|
874
|
-
Array.from({ length: Math.floor(dayMinutes / STEP_MIN) + 1 }).map((_, i) => /* @__PURE__ */ jsx(
|
|
875
|
-
"div",
|
|
876
|
-
{
|
|
877
|
-
className: "rfs-slot-line border-border/50",
|
|
878
|
-
style: { top: TOP_PAD + i * STEP_MIN * PX_PER_MIN }
|
|
879
|
-
},
|
|
880
|
-
`minor-${i}`
|
|
881
|
-
)),
|
|
882
|
-
appointments.map((appointment) => {
|
|
883
|
-
const laneWidthPct = 100 / appointment.lanes;
|
|
884
|
-
const top = TOP_PAD + appointment.startMin * PX_PER_MIN;
|
|
885
|
-
const height = Math.max(
|
|
886
|
-
MIN_EVENT_MIN * PX_PER_MIN,
|
|
887
|
-
(appointment.endMin - appointment.startMin) * PX_PER_MIN
|
|
888
|
-
);
|
|
889
|
-
return /* @__PURE__ */ jsx(
|
|
890
|
-
"div",
|
|
891
|
-
{
|
|
892
|
-
className: `rfs-card-slot ${appointment.visualState === "ghost" ? "rfs-card-slot--ghost" : ""} ${appointment.visualState === "dragging" ? "rfs-card-slot--dragging" : ""}`,
|
|
893
|
-
style: {
|
|
894
|
-
top,
|
|
895
|
-
left: `${laneWidthPct * appointment.lane}%`,
|
|
896
|
-
width: `${laneWidthPct}%`,
|
|
897
|
-
height
|
|
898
|
-
},
|
|
899
|
-
children: (renderAppointment ?? defaultRenderAppointment)({
|
|
900
|
-
appointment,
|
|
901
|
-
isDropInvalid: isDropInvalid && drag.kind === "resize" && appointment.id === drag.appointmentId,
|
|
902
|
-
onPointerDown: (event) => onApptPointerDown(event, appointment),
|
|
903
|
-
onResizePointerDown: (event) => onResizePointerDown(event, appointment),
|
|
904
|
-
appointmentAppearance,
|
|
905
|
-
appointmentBackgroundColor: appointmentBg,
|
|
906
|
-
drag,
|
|
907
|
-
suppressClickRef
|
|
908
|
-
})
|
|
909
|
-
},
|
|
910
|
-
appointment.renderKey
|
|
911
|
-
);
|
|
912
|
-
})
|
|
913
|
-
]
|
|
914
|
-
}
|
|
915
|
-
)
|
|
916
|
-
}
|
|
917
|
-
);
|
|
918
|
-
};
|
|
919
|
-
|
|
920
|
-
// src/components/scheduler/parts/ResourceCol.tsx
|
|
921
|
-
var areResourceColsEqual = (prev, next) => [
|
|
922
|
-
prev.resource === next.resource,
|
|
923
|
-
prev.colRefs === next.colRefs,
|
|
924
|
-
prev.gridHeight === next.gridHeight,
|
|
925
|
-
prev.dayMinutes === next.dayMinutes,
|
|
926
|
-
prev.appointments === next.appointments,
|
|
927
|
-
prev.appointmentAppearance === next.appointmentAppearance,
|
|
928
|
-
prev.appointmentBg === next.appointmentBg,
|
|
929
|
-
prev.isDropInvalid === next.isDropInvalid,
|
|
930
|
-
prev.renderAppointment === next.renderAppointment,
|
|
931
|
-
prev.onApptPointerDown === next.onApptPointerDown,
|
|
932
|
-
prev.onResizePointerDown === next.onResizePointerDown,
|
|
933
|
-
prev.drag === next.drag,
|
|
934
|
-
prev.suppressClickRef === next.suppressClickRef
|
|
935
|
-
].every(Boolean);
|
|
936
|
-
var ResourceCol = React2.memo(
|
|
937
|
-
ResourceColInner,
|
|
938
|
-
areResourceColsEqual
|
|
939
|
-
);
|
|
940
|
-
|
|
941
|
-
// src/lib/cn.ts
|
|
942
|
-
function cn(...inputs) {
|
|
943
|
-
return inputs.filter(Boolean).join(" ");
|
|
944
|
-
}
|
|
945
|
-
var variantClasses = {
|
|
946
|
-
default: "bg-primary text-primary-foreground hover:bg-primary/90",
|
|
947
|
-
outline: "border border-border bg-card text-foreground hover:bg-muted",
|
|
948
|
-
ghost: "text-foreground hover:bg-muted"
|
|
949
|
-
};
|
|
950
|
-
var sizeClasses = {
|
|
951
|
-
default: "h-9 px-4 py-2",
|
|
952
|
-
sm: "h-8 px-3 text-xs",
|
|
953
|
-
icon: "h-8 w-8"
|
|
954
|
-
};
|
|
955
|
-
var Button = React2.forwardRef(function Button2({ className, variant = "default", size = "default", type = "button", ...props }, ref) {
|
|
956
|
-
return /* @__PURE__ */ jsx(
|
|
957
|
-
"button",
|
|
958
|
-
{
|
|
959
|
-
ref,
|
|
960
|
-
type,
|
|
961
|
-
className: cn(
|
|
962
|
-
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
|
963
|
-
variantClasses[variant],
|
|
964
|
-
sizeClasses[size],
|
|
965
|
-
className
|
|
966
|
-
),
|
|
967
|
-
...props
|
|
968
|
-
}
|
|
969
|
-
);
|
|
970
|
-
});
|
|
971
|
-
function Calendar({
|
|
972
|
-
selected,
|
|
973
|
-
onSelect
|
|
974
|
-
}) {
|
|
975
|
-
const [month, setMonth] = React2.useState(selected ?? /* @__PURE__ */ new Date());
|
|
976
|
-
React2.useEffect(() => {
|
|
977
|
-
if (selected) {
|
|
978
|
-
setMonth(selected);
|
|
979
|
-
}
|
|
980
|
-
}, [selected]);
|
|
981
|
-
const monthStart = startOfMonth(month);
|
|
982
|
-
const calendarStart = startOfWeek(monthStart, { weekStartsOn: 1 });
|
|
983
|
-
const calendarEnd = endOfWeek(endOfMonth(month), { weekStartsOn: 1 });
|
|
984
|
-
const days = [];
|
|
985
|
-
for (let day = calendarStart; day <= calendarEnd; day = addDays(day, 1)) {
|
|
986
|
-
days.push(day);
|
|
987
|
-
}
|
|
988
|
-
return /* @__PURE__ */ jsxs("div", { className: "w-72 rounded-md bg-popover", children: [
|
|
989
|
-
/* @__PURE__ */ jsxs("div", { className: "mb-2 flex items-center justify-between", children: [
|
|
990
|
-
/* @__PURE__ */ jsx(
|
|
991
|
-
Button,
|
|
992
|
-
{
|
|
993
|
-
variant: "ghost",
|
|
994
|
-
size: "icon",
|
|
995
|
-
"aria-label": "Previous month",
|
|
996
|
-
onClick: () => setMonth(addMonths(month, -1)),
|
|
997
|
-
children: "<"
|
|
998
|
-
}
|
|
999
|
-
),
|
|
1000
|
-
/* @__PURE__ */ jsx("div", { className: "text-sm font-medium", children: format(month, "MMMM yyyy") }),
|
|
1001
|
-
/* @__PURE__ */ jsx(
|
|
1002
|
-
Button,
|
|
1003
|
-
{
|
|
1004
|
-
variant: "ghost",
|
|
1005
|
-
size: "icon",
|
|
1006
|
-
"aria-label": "Next month",
|
|
1007
|
-
onClick: () => setMonth(addMonths(month, 1)),
|
|
1008
|
-
children: ">"
|
|
1009
|
-
}
|
|
1010
|
-
)
|
|
1011
|
-
] }),
|
|
1012
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-1 text-center text-xs text-muted-foreground", children: ["Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"].map((label) => /* @__PURE__ */ jsx(
|
|
1013
|
-
"div",
|
|
1014
|
-
{
|
|
1015
|
-
className: "py-1",
|
|
1016
|
-
children: label
|
|
1017
|
-
},
|
|
1018
|
-
label
|
|
1019
|
-
)) }),
|
|
1020
|
-
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 gap-1", children: days.map((day) => {
|
|
1021
|
-
const isSelected = selected ? isSameDay(day, selected) : false;
|
|
1022
|
-
return /* @__PURE__ */ jsx(
|
|
1023
|
-
Button,
|
|
1024
|
-
{
|
|
1025
|
-
variant: isSelected ? "default" : "ghost",
|
|
1026
|
-
size: "icon",
|
|
1027
|
-
className: cn(
|
|
1028
|
-
"text-xs",
|
|
1029
|
-
!isSameMonth(day, month) && "text-muted-foreground opacity-60",
|
|
1030
|
-
isToday(day) && !isSelected && "border border-border"
|
|
1031
|
-
),
|
|
1032
|
-
onClick: () => onSelect?.(day),
|
|
1033
|
-
children: format(day, "d")
|
|
1034
|
-
},
|
|
1035
|
-
day.toISOString()
|
|
1036
|
-
);
|
|
1037
|
-
}) })
|
|
1038
|
-
] });
|
|
1039
|
-
}
|
|
1040
|
-
var PopoverContext = React2.createContext(null);
|
|
1041
|
-
function usePopoverContext() {
|
|
1042
|
-
const context = React2.useContext(PopoverContext);
|
|
1043
|
-
if (!context) {
|
|
1044
|
-
throw new Error("Popover components must be used within Popover.");
|
|
1045
|
-
}
|
|
1046
|
-
return context;
|
|
1047
|
-
}
|
|
1048
|
-
function Popover({
|
|
1049
|
-
children,
|
|
1050
|
-
open: controlledOpen,
|
|
1051
|
-
onOpenChange
|
|
1052
|
-
}) {
|
|
1053
|
-
const [uncontrolledOpen, setUncontrolledOpen] = React2.useState(false);
|
|
1054
|
-
const triggerRef = React2.useRef(null);
|
|
1055
|
-
const contentRef = React2.useRef(null);
|
|
1056
|
-
const open = controlledOpen ?? uncontrolledOpen;
|
|
1057
|
-
const setOpen = React2.useCallback(
|
|
1058
|
-
(value) => {
|
|
1059
|
-
const nextValue = typeof value === "function" ? value(open) : value;
|
|
1060
|
-
if (controlledOpen === void 0) {
|
|
1061
|
-
setUncontrolledOpen(nextValue);
|
|
1062
|
-
}
|
|
1063
|
-
onOpenChange?.(nextValue);
|
|
1064
|
-
},
|
|
1065
|
-
[controlledOpen, onOpenChange, open]
|
|
1066
|
-
);
|
|
1067
|
-
React2.useEffect(() => {
|
|
1068
|
-
if (!open) {
|
|
1069
|
-
return void 0;
|
|
1070
|
-
}
|
|
1071
|
-
const handlePointerDown = (event) => {
|
|
1072
|
-
const target = event.target;
|
|
1073
|
-
if (triggerRef.current?.contains(target) || contentRef.current?.contains(target)) {
|
|
1074
|
-
return;
|
|
1075
|
-
}
|
|
1076
|
-
setOpen(false);
|
|
1077
|
-
};
|
|
1078
|
-
const handleKeyDown = (event) => {
|
|
1079
|
-
if (event.key === "Escape") {
|
|
1080
|
-
setOpen(false);
|
|
1081
|
-
}
|
|
1082
|
-
};
|
|
1083
|
-
document.addEventListener("mousedown", handlePointerDown);
|
|
1084
|
-
document.addEventListener("keydown", handleKeyDown);
|
|
1085
|
-
return () => {
|
|
1086
|
-
document.removeEventListener("mousedown", handlePointerDown);
|
|
1087
|
-
document.removeEventListener("keydown", handleKeyDown);
|
|
1088
|
-
};
|
|
1089
|
-
}, [open, setOpen]);
|
|
1090
|
-
return /* @__PURE__ */ jsx(PopoverContext.Provider, { value: { open, setOpen, triggerRef, contentRef }, children: /* @__PURE__ */ jsx("div", { className: "relative inline-flex", children }) });
|
|
1091
|
-
}
|
|
1092
|
-
function PopoverTrigger({
|
|
1093
|
-
children,
|
|
1094
|
-
asChild = false
|
|
1095
|
-
}) {
|
|
1096
|
-
const { open, setOpen, triggerRef } = usePopoverContext();
|
|
1097
|
-
if (asChild && React2.isValidElement(children)) {
|
|
1098
|
-
const child = children;
|
|
1099
|
-
return React2.cloneElement(child, {
|
|
1100
|
-
ref: triggerRef,
|
|
1101
|
-
"aria-expanded": open,
|
|
1102
|
-
onClick: (event) => {
|
|
1103
|
-
child.props.onClick?.(event);
|
|
1104
|
-
setOpen((current) => !current);
|
|
1105
|
-
}
|
|
1106
|
-
});
|
|
1107
|
-
}
|
|
1108
|
-
return /* @__PURE__ */ jsx(
|
|
1109
|
-
"button",
|
|
1110
|
-
{
|
|
1111
|
-
ref: triggerRef,
|
|
1112
|
-
type: "button",
|
|
1113
|
-
"aria-expanded": open,
|
|
1114
|
-
onClick: () => setOpen((current) => !current),
|
|
1115
|
-
children
|
|
1116
|
-
}
|
|
1117
|
-
);
|
|
1118
|
-
}
|
|
1119
|
-
function PopoverContent({
|
|
1120
|
-
children,
|
|
1121
|
-
className,
|
|
1122
|
-
align = "start"
|
|
1123
|
-
}) {
|
|
1124
|
-
const { open, contentRef } = usePopoverContext();
|
|
1125
|
-
if (!open) {
|
|
1126
|
-
return null;
|
|
1127
|
-
}
|
|
1128
|
-
return /* @__PURE__ */ jsx(
|
|
1129
|
-
"div",
|
|
1130
|
-
{
|
|
1131
|
-
ref: contentRef,
|
|
1132
|
-
className: cn(
|
|
1133
|
-
"absolute top-full z-50 mt-2 min-w-72 rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-md outline-none",
|
|
1134
|
-
align === "start" && "left-0",
|
|
1135
|
-
align === "center" && "left-1/2 -translate-x-1/2",
|
|
1136
|
-
align === "end" && "right-0",
|
|
1137
|
-
className
|
|
1138
|
-
),
|
|
1139
|
-
children
|
|
1140
|
-
}
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
|
-
function defaultRenderDatePicker({
|
|
1144
|
-
selectedDate,
|
|
1145
|
-
onSelectedDateChange
|
|
1146
|
-
}) {
|
|
1147
|
-
return /* @__PURE__ */ jsxs(Popover, { children: [
|
|
1148
|
-
/* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
1149
|
-
Button,
|
|
1150
|
-
{
|
|
1151
|
-
variant: "outline",
|
|
1152
|
-
size: "sm",
|
|
1153
|
-
className: "min-w-40 justify-start text-left font-normal",
|
|
1154
|
-
children: format(selectedDate, "PPP")
|
|
1155
|
-
}
|
|
1156
|
-
) }),
|
|
1157
|
-
/* @__PURE__ */ jsx(
|
|
1158
|
-
PopoverContent,
|
|
1159
|
-
{
|
|
1160
|
-
className: "w-auto p-2",
|
|
1161
|
-
align: "center",
|
|
1162
|
-
children: /* @__PURE__ */ jsx(
|
|
1163
|
-
Calendar,
|
|
1164
|
-
{
|
|
1165
|
-
selected: selectedDate,
|
|
1166
|
-
onSelect: onSelectedDateChange
|
|
1167
|
-
}
|
|
1168
|
-
)
|
|
1169
|
-
}
|
|
1170
|
-
)
|
|
1171
|
-
] });
|
|
1172
|
-
}
|
|
1173
|
-
function defaultRenderToolbar({
|
|
1174
|
-
goToPreviousDay,
|
|
1175
|
-
goToNextDay,
|
|
1176
|
-
defaultDatePicker,
|
|
1177
|
-
prevButtonLabel = "Prev",
|
|
1178
|
-
nextButtonLabel = "Next"
|
|
1179
|
-
}) {
|
|
1180
|
-
return /* @__PURE__ */ jsx("div", { className: "rfs-toolbar", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-border bg-card p-1 shadow-xs", children: [
|
|
1181
|
-
/* @__PURE__ */ jsx(
|
|
1182
|
-
Button,
|
|
1183
|
-
{
|
|
1184
|
-
variant: "outline",
|
|
1185
|
-
size: "sm",
|
|
1186
|
-
onClick: goToPreviousDay,
|
|
1187
|
-
children: prevButtonLabel
|
|
1188
|
-
}
|
|
1189
|
-
),
|
|
1190
|
-
defaultDatePicker,
|
|
1191
|
-
/* @__PURE__ */ jsx(
|
|
1192
|
-
Button,
|
|
1193
|
-
{
|
|
1194
|
-
variant: "outline",
|
|
1195
|
-
size: "sm",
|
|
1196
|
-
onClick: goToNextDay,
|
|
1197
|
-
children: nextButtonLabel
|
|
1198
|
-
}
|
|
1199
|
-
)
|
|
1200
|
-
] }) });
|
|
1201
|
-
}
|
|
1202
|
-
function Toolbar({
|
|
1203
|
-
selectedDate,
|
|
1204
|
-
onSelectedDateChange,
|
|
1205
|
-
prevButtonLabel,
|
|
1206
|
-
nextButtonLabel,
|
|
1207
|
-
renderToolbar,
|
|
1208
|
-
renderDatePicker
|
|
1209
|
-
}) {
|
|
1210
|
-
const goToPreviousDay = () => onSelectedDateChange(shiftDays(selectedDate, -1));
|
|
1211
|
-
const goToNextDay = () => onSelectedDateChange(shiftDays(selectedDate, 1));
|
|
1212
|
-
const defaultDatePicker = (renderDatePicker ?? defaultRenderDatePicker)({
|
|
1213
|
-
selectedDate,
|
|
1214
|
-
onSelectedDateChange
|
|
1215
|
-
});
|
|
1216
|
-
if (renderToolbar) {
|
|
1217
|
-
return /* @__PURE__ */ jsx(Fragment, { children: renderToolbar({
|
|
1218
|
-
selectedDate,
|
|
1219
|
-
onSelectedDateChange,
|
|
1220
|
-
goToPreviousDay,
|
|
1221
|
-
goToNextDay,
|
|
1222
|
-
defaultDatePicker
|
|
1223
|
-
}) });
|
|
1224
|
-
}
|
|
1225
|
-
return /* @__PURE__ */ jsx(Fragment, { children: defaultRenderToolbar({
|
|
1226
|
-
goToPreviousDay,
|
|
1227
|
-
goToNextDay,
|
|
1228
|
-
defaultDatePicker,
|
|
1229
|
-
prevButtonLabel,
|
|
1230
|
-
nextButtonLabel
|
|
1231
|
-
}) });
|
|
1232
|
-
}
|
|
1233
|
-
var IDLE_DRAG = { kind: "none" };
|
|
1234
|
-
function Scheduler({
|
|
1235
|
-
resources,
|
|
1236
|
-
appointments,
|
|
1237
|
-
selectedDate,
|
|
1238
|
-
onSelectedDateChange,
|
|
1239
|
-
adapter,
|
|
1240
|
-
onPersistMoveResize,
|
|
1241
|
-
onAppointmentChange,
|
|
1242
|
-
renderResourceHeader,
|
|
1243
|
-
renderAppointment,
|
|
1244
|
-
resourceAppointmentClassMap,
|
|
1245
|
-
getResourceAppointmentAppearance,
|
|
1246
|
-
getResourceAppointmentColorToken,
|
|
1247
|
-
appointmentColorTokenClassMap,
|
|
1248
|
-
getResourceAppointmentBackground,
|
|
1249
|
-
renderToolbar,
|
|
1250
|
-
renderDatePicker,
|
|
1251
|
-
prevButtonLabel = "Prev",
|
|
1252
|
-
nextButtonLabel = "Next",
|
|
1253
|
-
dayStart = "09:00",
|
|
1254
|
-
dayEnd = "18:00"
|
|
1255
|
-
}) {
|
|
1256
|
-
const { appointmentMap, dayMinutes, dayStartAbs, gridHeight, gridMinWidth, renderAppts } = useSchedulerBaseData({ adapter, appointments, dayEnd, dayStart, resources, selectedDate });
|
|
1257
|
-
const {
|
|
1258
|
-
colRefs,
|
|
1259
|
-
drag,
|
|
1260
|
-
isDropInvalid,
|
|
1261
|
-
movePreview,
|
|
1262
|
-
onApptPointerDown,
|
|
1263
|
-
onGlobalPointerMove,
|
|
1264
|
-
onGlobalPointerUp,
|
|
1265
|
-
onResizePointerDown,
|
|
1266
|
-
suppressClickRef
|
|
1267
|
-
} = useSchedulerInteractions({
|
|
1268
|
-
appointmentMap,
|
|
1269
|
-
dayMinutes,
|
|
1270
|
-
dayStartAbs,
|
|
1271
|
-
onAppointmentChange,
|
|
1272
|
-
onPersistMoveResize,
|
|
1273
|
-
renderAppts,
|
|
1274
|
-
resources,
|
|
1275
|
-
selectedDate
|
|
1276
|
-
});
|
|
1277
|
-
const { appointmentAppearanceByResource, appointmentBgByResource, laidOutByResource } = useSchedulerPresentationData({
|
|
1278
|
-
appointmentColorTokenClassMap,
|
|
1279
|
-
dayStartAbs,
|
|
1280
|
-
drag,
|
|
1281
|
-
getResourceAppointmentAppearance,
|
|
1282
|
-
getResourceAppointmentBackground,
|
|
1283
|
-
getResourceAppointmentColorToken,
|
|
1284
|
-
renderAppts,
|
|
1285
|
-
resourceAppointmentClassMap,
|
|
1286
|
-
resources,
|
|
1287
|
-
selectedDate
|
|
1288
|
-
});
|
|
1289
|
-
const moveOverlay = React2.useMemo(() => {
|
|
1290
|
-
if (drag.kind !== "move" || !movePreview) {
|
|
1291
|
-
return null;
|
|
1292
|
-
}
|
|
1293
|
-
return {
|
|
1294
|
-
appointment: {
|
|
1295
|
-
...movePreview.appointment,
|
|
1296
|
-
resourceId: drag.resourceId,
|
|
1297
|
-
startMin: drag.startMin,
|
|
1298
|
-
endMin: drag.endMin,
|
|
1299
|
-
visualState: "ghost",
|
|
1300
|
-
renderKey: `${movePreview.appointment.renderKey}-overlay`,
|
|
1301
|
-
lane: 0,
|
|
1302
|
-
lanes: 1
|
|
1303
|
-
},
|
|
1304
|
-
appearance: appointmentAppearanceByResource.get(drag.resourceId),
|
|
1305
|
-
background: appointmentBgByResource.get(drag.resourceId),
|
|
1306
|
-
left: movePreview.clientX - movePreview.offsetX,
|
|
1307
|
-
top: movePreview.clientY - movePreview.offsetY,
|
|
1308
|
-
width: movePreview.width,
|
|
1309
|
-
height: movePreview.height
|
|
1310
|
-
};
|
|
1311
|
-
}, [appointmentAppearanceByResource, appointmentBgByResource, drag, movePreview]);
|
|
1312
|
-
return /* @__PURE__ */ jsxs(
|
|
1313
|
-
"section",
|
|
1314
|
-
{
|
|
1315
|
-
className: `rfs-root rounded-lg bg-background text-foreground ${drag.kind === "move" ? "rfs-root--drag-move" : ""} ${drag.kind === "resize" ? "rfs-root--drag-resize" : ""}`,
|
|
1316
|
-
onPointerMove: onGlobalPointerMove,
|
|
1317
|
-
onPointerUp: onGlobalPointerUp,
|
|
1318
|
-
onPointerCancel: onGlobalPointerUp,
|
|
1319
|
-
children: [
|
|
1320
|
-
/* @__PURE__ */ jsx(
|
|
1321
|
-
Toolbar,
|
|
1322
|
-
{
|
|
1323
|
-
selectedDate,
|
|
1324
|
-
onSelectedDateChange,
|
|
1325
|
-
prevButtonLabel,
|
|
1326
|
-
nextButtonLabel,
|
|
1327
|
-
renderToolbar,
|
|
1328
|
-
renderDatePicker
|
|
1329
|
-
}
|
|
1330
|
-
),
|
|
1331
|
-
/* @__PURE__ */ jsx("div", { className: "rfs-shell border-border bg-card", children: /* @__PURE__ */ jsxs("div", { style: { minWidth: gridMinWidth }, children: [
|
|
1332
|
-
/* @__PURE__ */ jsx(
|
|
1333
|
-
Header,
|
|
1334
|
-
{
|
|
1335
|
-
resources,
|
|
1336
|
-
renderResourceHeader
|
|
1337
|
-
}
|
|
1338
|
-
),
|
|
1339
|
-
/* @__PURE__ */ jsxs("div", { className: "rfs-body", children: [
|
|
1340
|
-
/* @__PURE__ */ jsx(
|
|
1341
|
-
Gutter,
|
|
1342
|
-
{
|
|
1343
|
-
dayMinutes,
|
|
1344
|
-
dayStartAbs,
|
|
1345
|
-
gridHeight
|
|
1346
|
-
}
|
|
1347
|
-
),
|
|
1348
|
-
/* @__PURE__ */ jsx("div", { className: "rfs-grid", children: resources.map((resource) => /* @__PURE__ */ jsx(
|
|
1349
|
-
ResourceCol,
|
|
1350
|
-
{
|
|
1351
|
-
resource,
|
|
1352
|
-
colRefs,
|
|
1353
|
-
gridHeight,
|
|
1354
|
-
dayMinutes,
|
|
1355
|
-
appointments: laidOutByResource.get(resource.id) ?? [],
|
|
1356
|
-
appointmentAppearance: appointmentAppearanceByResource.get(resource.id),
|
|
1357
|
-
appointmentBg: appointmentBgByResource.get(resource.id),
|
|
1358
|
-
isDropInvalid,
|
|
1359
|
-
renderAppointment,
|
|
1360
|
-
onApptPointerDown,
|
|
1361
|
-
onResizePointerDown,
|
|
1362
|
-
drag: drag.kind === "resize" && drag.resourceId === resource.id ? drag : IDLE_DRAG,
|
|
1363
|
-
suppressClickRef
|
|
1364
|
-
},
|
|
1365
|
-
String(resource.id)
|
|
1366
|
-
)) })
|
|
1367
|
-
] })
|
|
1368
|
-
] }) }),
|
|
1369
|
-
moveOverlay ? /* @__PURE__ */ jsx(
|
|
1370
|
-
"div",
|
|
1371
|
-
{
|
|
1372
|
-
className: "rfs-drag-overlay",
|
|
1373
|
-
style: {
|
|
1374
|
-
left: moveOverlay.left,
|
|
1375
|
-
top: moveOverlay.top,
|
|
1376
|
-
width: moveOverlay.width,
|
|
1377
|
-
height: moveOverlay.height
|
|
1378
|
-
},
|
|
1379
|
-
children: (renderAppointment ?? defaultRenderAppointment)({
|
|
1380
|
-
appointment: moveOverlay.appointment,
|
|
1381
|
-
isDropInvalid,
|
|
1382
|
-
onPointerDown: () => void 0,
|
|
1383
|
-
onResizePointerDown: () => void 0,
|
|
1384
|
-
appointmentAppearance: moveOverlay.appearance,
|
|
1385
|
-
appointmentBackgroundColor: moveOverlay.background,
|
|
1386
|
-
drag,
|
|
1387
|
-
suppressClickRef
|
|
1388
|
-
})
|
|
1389
|
-
}
|
|
1390
|
-
) : null
|
|
1391
|
-
]
|
|
1392
|
-
}
|
|
1393
|
-
);
|
|
1394
|
-
}
|
|
1395
|
-
|
|
1396
|
-
// src/utils/scheduler-appointment-change.utils.ts
|
|
1397
|
-
function applySchedulerAppointmentChange(appointments, change, options) {
|
|
1398
|
-
return appointments.map((item) => {
|
|
1399
|
-
if (options.getId(item) !== options.getId(change.appointment)) {
|
|
1400
|
-
return item;
|
|
1401
|
-
}
|
|
1402
|
-
const withResource = options.setResourceId(item, change.next.resourceId);
|
|
1403
|
-
const withStart = options.setStart(withResource, change.next.start);
|
|
1404
|
-
return options.setEnd(withStart, change.next.end);
|
|
1405
|
-
});
|
|
1406
|
-
}
|
|
1407
|
-
|
|
1408
|
-
export { Scheduler, applySchedulerAppointmentChange };
|
|
1409
|
-
//# sourceMappingURL=index.js.map
|
|
1410
|
-
//# sourceMappingURL=index.js.map
|
|
1
|
+
import {jsx,jsxs,Fragment}from'react/jsx-runtime';import {format,startOfMonth,startOfWeek,endOfWeek,endOfMonth,addDays,addMonths,isSameDay,isToday,isSameMonth}from'date-fns';import*as h from'react';function G({appointment:e,isDropInvalid:t,onPointerDown:r,onResizePointerDown:s,appointmentAppearance:o,appointmentBackgroundColor:i}){let c=e.raw,a=typeof c.description=="string"?c.description:void 0,n=typeof i=="string"?i:void 0,d=o?.className??n??"bg-muted/40 dark:bg-muted/30",f=t?"border-red-500 bg-red-100/80 text-red-950 ring-1 ring-red-500/60 dark:bg-red-950/40 dark:text-red-100":"",p=e.visualState==="ghost"?"cursor-grab border-dashed opacity-35":e.visualState==="dragging"?"cursor-grabbing opacity-55 shadow-xl ring-1 ring-border/70":"cursor-grab",R=`relative h-full overflow-hidden rounded-md border border-border p-2 pb-5 text-foreground ${d} ${f} ${p}`;return jsxs("div",{onPointerDown:r,className:R,children:[t?jsx("div",{className:"absolute right-1 top-1 flex h-4 w-4 items-center justify-center rounded-full bg-red-600 text-[10px] font-bold text-white",children:"X"}):null,jsx("div",{className:"text-xs font-semibold",children:e.title}),a?jsx("div",{className:"mt-1 text-[10px] font-medium text-muted-foreground",children:a}):null,jsx("div",{role:"button","aria-label":"Resize appointment",onPointerDown:l=>{e.visualState!=="ghost"&&(l.stopPropagation(),s(l));},className:"absolute inset-x-1 bottom-1 h-2 cursor-ns-resize rounded-full bg-border/90 transition-colors hover:bg-border"})]})}function me(e){if(!e||typeof e!="object")return false;let t=e;return typeof t.firstName=="string"||typeof t.lastName=="string"}var fe={slate:"bg-slate-100 dark:bg-slate-900/40",gray:"bg-gray-100 dark:bg-gray-900/40",zinc:"bg-zinc-100 dark:bg-zinc-900/40",neutral:"bg-neutral-100 dark:bg-neutral-900/40",stone:"bg-stone-100 dark:bg-stone-900/40",red:"bg-red-100 dark:bg-red-950/40",orange:"bg-orange-100 dark:bg-orange-950/40",amber:"bg-amber-100 dark:bg-amber-950/40",yellow:"bg-yellow-100 dark:bg-yellow-950/40",lime:"bg-lime-100 dark:bg-lime-950/40",green:"bg-green-100 dark:bg-green-950/40",emerald:"bg-emerald-100 dark:bg-emerald-950/40",teal:"bg-teal-100 dark:bg-teal-950/40",cyan:"bg-cyan-100 dark:bg-cyan-950/40",sky:"bg-sky-100 dark:bg-sky-950/40",blue:"bg-blue-100 dark:bg-blue-950/40",indigo:"bg-indigo-100 dark:bg-indigo-950/40",violet:"bg-violet-100 dark:bg-violet-950/40",purple:"bg-purple-100 dark:bg-purple-950/40",fuchsia:"bg-fuchsia-100 dark:bg-fuchsia-950/40",pink:"bg-pink-100 dark:bg-pink-950/40",rose:"bg-rose-100 dark:bg-rose-950/40"};function D(e,t,r){return Math.max(t,Math.min(r,e))}function X(e,t=15){return Math.round(e/t)*t}function ne(e){let[t,r]=e.split(":").map(Number);return t*60+r}function Re(e){return `${e.firstName??""} ${e.lastName??""}`.trim()}function K(e){return format(e,"yyyy-MM-dd")}function oe(e,t){return new Date(e.getFullYear(),e.getMonth(),e.getDate()+t)}function _(e,t,r){let s=new Date(e.getFullYear(),e.getMonth(),e.getDate(),0,0,0,0),o=t+r,i=Math.floor(o/60),c=o%60;return s.setHours(i,c,0,0),s}function se(e,t){return t.getHours()*60+t.getMinutes()-e}function ie(e,t){return e.startMin<t.endMin&&t.startMin<e.endMin}function ge(e){let t=[...e].sort((c,a)=>c.startMin-a.startMin||c.endMin-a.endMin),r=[],s=[],o=[];for(let c of t)s=s.filter(n=>n.endMin>c.startMin),!s.some(n=>ie(n,c))&&o.length>0&&(r.push(o),o=[]),o.push(c),s.push(c);o.length&&r.push(o);let i=[];for(let c of r){let a=[...c].sort((p,R)=>p.startMin-R.startMin||p.endMin-R.endMin),n=[],d=new Map;for(let p of a){let R=false;for(let l=0;l<n.length;l++)if(n[l]<=p.startMin){n[l]=p.endMin,d.set(p.id,l),R=true;break}R||(n.push(p.endMin),d.set(p.id,n.length-1));}let f=n.length;for(let p of a)i.push({...p,lane:d.get(p.id)??0,lanes:f});}return i}function ae({fallback:e,getValue:t,resources:r,toKey:s}){let o=new Map;for(let i of r)o.set(s(i),t(i)??e);return o}function he({adapter:e,appointments:t,dayMinutes:r,dayStartAbs:s,selectedISO:o}){return t.map(i=>{let c=new Date(e.getStart(i)),a=new Date(e.getEnd(i));return {raw:i,id:e.getId(i),resourceId:e.getResourceId(i),start:c,end:a,startMin:D(se(s,c),0,r),endMin:D(se(s,a),0,r),title:e.getTitle(i),renderKey:String(e.getId(i)),visualState:"normal"}}).filter(i=>K(i.start)===o)}function Te(e){return new Map(e.map(t=>[t.id,t]))}function be(e,t,r,s){return t.kind==="none"||t.kind==="move"?e:e.map(o=>o.id!==t.appointmentId?o:{...o,resourceId:t.resourceId,startMin:t.startMin,endMin:t.endMin,start:_(r,s,t.startMin),end:_(r,s,t.endMin),visualState:"dragging"})}function Se(e,t){let r=new Map;for(let o of t){let i=r.get(o.resourceId)??[];i.push(o),r.set(o.resourceId,i);}let s=new Map;for(let o of e)s.set(o.id,ce(r.get(o.id)??[]));return s}function ce(e){return ge(e.map(t=>({...t,appointmentId:t.id,id:t.renderKey}))).map(({appointmentId:t,...r})=>({...r,id:t}))}function Ie(e,t,r,s,o){let i=new Map;for(let c of e){let a=t?.[String(c.id)];if(a){i.set(c.id,{className:a});continue}let n=r?.(c);if(n){i.set(c.id,n);continue}let d=s.get(c.id);if(d){i.set(c.id,{className:d});continue}let f=o.get(c.id);i.set(c.id,f?{className:f}:void 0);}return i}function Ae({adapter:e,appointments:t,dayEnd:r="18:00",dayStart:s="09:00",resources:o,selectedDate:i}){let c=h.useMemo(()=>ne(s),[s]),a=h.useMemo(()=>ne(r),[r]),n=h.useMemo(()=>Math.max(0,a-c),[a,c]),d=h.useMemo(()=>12+n*2+24,[n]),f=h.useMemo(()=>72+Math.max(o.length,1)*420,[o.length]),p=h.useMemo(()=>K(i),[i]),R=h.useMemo(()=>he({adapter:e,appointments:t,dayMinutes:n,dayStartAbs:c,selectedISO:p}),[e,t,n,c,p]);return {appointmentMap:h.useMemo(()=>Te(R),[R]),dayEndAbs:a,dayMinutes:n,dayStartAbs:c,gridHeight:d,gridMinWidth:f,renderAppts:R}}var ot=4,st=(e,t)=>{e(r=>r&&{...r,clientX:t.clientX,clientY:t.clientY});},it=e=>{let{dragRef:t,pendingDragRef:r,pointer:s,setDrag:o,setMovePreview:i,suppressClickRef:c}=e,a=r.current;if(!a||a.drag.pointerId!==s.pointerId)return;let n=s.clientX-a.startClientX,d=s.clientY-a.startClientY;Math.hypot(n,d)<ot||(c.current=true,t.current=a.drag,o(a.drag),a.drag.kind==="move"&&i({appointment:a.appointment,clientX:s.clientX,clientY:s.clientY,width:a.width,height:a.height,offsetX:a.offsetX,offsetY:a.offsetY}),r.current=null);},at=(e,t,r,s,o)=>{let i=s(t.clientX,e.resourceId),a=o(i,t.clientY)-e.offsetMin,n=D(X(a),0,r-e.durationMin),d=n+e.durationMin;return n===e.startMin&&d===e.endMin&&i===e.resourceId?null:{...e,resourceId:i,startMin:n,endMin:d}},ct=(e,t,r,s)=>{let o=s(e.resourceId,t.clientY),i=D(X(o),e.startMin+15,r);return i===e.endMin?null:{...e,endMin:i}},Pe=e=>{let{dayMinutes:t,dragRef:r,latestPointerRef:s,pendingDragRef:o,pointerToMin:i,pointerToResourceId:c,setDrag:a,setMovePreview:n,suppressClickRef:d}=e,f=s.current;if(!f)return;let p=r.current;if(p.kind==="none"&&o.current){it({pointer:f,pendingDragRef:o,setDrag:a,setMovePreview:n,dragRef:r,suppressClickRef:d});return}if(p.kind==="none")return;if(p.kind==="move"){st(n,f);let l=at(p,f,t,c,i);l&&(d.current=true,r.current=l,a(l));return}let R=ct(p,f,t,i);R&&(d.current=true,r.current=R,a(R));},xe=e=>{let{dragRef:t,frameRef:r,latestPointerRef:s,pendingDragRef:o,persistDrag:i,setDrag:c,setMovePreview:a,suppressClickRef:n}=e;r.current!==null&&(window.cancelAnimationFrame(r.current),r.current=null),s.current=null,o.current=null,a(null);let d=t.current;d.kind!=="none"&&(c({kind:"none"}),t.current={kind:"none"},n.current&&i(d));};var De=({appointmentMap:e,dayMinutes:t,dayStartAbs:r,onAppointmentChange:s,onPersistMoveResize:o,renderAppts:i,resources:c,selectedDate:a})=>{let[n,d]=h.useState({kind:"none"}),[f,p]=h.useState(null),R=h.useRef({}),l=h.useRef(false),b=h.useRef({kind:"none"}),A=h.useRef(null),I=h.useRef(null),T=h.useRef(null);h.useEffect(()=>{b.current=n;},[n]),h.useEffect(()=>()=>{T.current!==null&&window.cancelAnimationFrame(T.current);},[]);let M=h.useCallback((u,m)=>{let g=R.current[String(u)];if(!g)return 0;let y=m-g.getBoundingClientRect().top-12;return D(X(y/2),0,t)},[t]),V=h.useCallback((u,m)=>{for(let g of c){let y=R.current[String(g.id)];if(!y)continue;let Y=y.getBoundingClientRect();if(u>=Y.left&&u<=Y.right)return g.id}return m},[c]),E=h.useCallback((u,m,g,y)=>i.filter(k=>k.resourceId===m&&k.id!==u).some(k=>ie(k,{startMin:g,endMin:y})),[i]),z=h.useMemo(()=>n.kind==="none"?false:E(n.appointmentId,n.resourceId,n.startMin,n.endMin),[n,E]),O=h.useCallback(async u=>{let m=e.get(u.appointmentId);if(!m||E(u.appointmentId,u.resourceId,u.startMin,u.endMin))return;let g={resourceId:u.resourceId,start:_(a,r,u.startMin),end:_(a,r,u.endMin)};if(s){await s({kind:u.kind,appointment:m.raw,previous:{resourceId:m.resourceId,start:m.start,end:m.end},next:g});return}o&&await o({appointment:m.raw,resourceId:g.resourceId,start:g.start,end:g.end});},[e,r,E,s,o,a]),$=h.useCallback((u,m)=>{if(u.button!==0)return;u.stopPropagation(),l.current=false;let g=u.currentTarget.getBoundingClientRect(),y=M(m.resourceId,u.clientY);A.current={drag:{kind:"move",appointmentId:m.id,pointerId:u.pointerId,resourceId:m.resourceId,durationMin:Math.max(15,m.endMin-m.startMin),offsetMin:y-m.startMin,startMin:m.startMin,endMin:m.endMin},appointment:m,startClientX:u.clientX,startClientY:u.clientY,width:g.width,height:g.height,offsetX:u.clientX-g.left,offsetY:u.clientY-g.top};},[M]),j=h.useCallback((u,m)=>{u.button===0&&(u.stopPropagation(),l.current=false,A.current={drag:{kind:"resize",appointmentId:m.id,pointerId:u.pointerId,resourceId:m.resourceId,startMin:m.startMin,endMin:m.endMin},appointment:m,startClientX:u.clientX,startClientY:u.clientY,width:0,height:0,offsetX:0,offsetY:0});},[]),F=h.useCallback(u=>{I.current={clientX:u.clientX,clientY:u.clientY,pointerId:u.pointerId},T.current===null&&(T.current=window.requestAnimationFrame(()=>{T.current=null,Pe({dayMinutes:t,dragRef:b,latestPointerRef:I,pendingDragRef:A,pointerToMin:M,pointerToResourceId:V,setDrag:d,setMovePreview:p,suppressClickRef:l});}));},[t,M,V]),ee=h.useCallback(()=>{xe({dragRef:b,frameRef:T,latestPointerRef:I,pendingDragRef:A,persistDrag:O,setDrag:d,setMovePreview:p,suppressClickRef:l});},[O]);return {colRefs:R,drag:n,isDropInvalid:z,movePreview:f,onApptPointerDown:$,onGlobalPointerMove:F,onGlobalPointerUp:ee,onResizePointerDown:j,suppressClickRef:l}};function ke({appointmentColorTokenClassMap:e,dayStartAbs:t,drag:r,getResourceAppointmentAppearance:s,getResourceAppointmentBackground:o,getResourceAppointmentColorToken:i,renderAppts:c,resourceAppointmentClassMap:a,resources:n,selectedDate:d}){let f=h.useMemo(()=>be(c,r,d,t),[t,r,c,d]),p=h.useMemo(()=>Se(n,c),[c,n]),R=h.useMemo(()=>{if(r.kind!=="resize")return p;let I=new Map(p),T=f.filter(M=>M.resourceId===r.resourceId);return I.set(r.resourceId,ce(T)),I},[p,r,f]),l=h.useMemo(()=>o?ae({fallback:void 0,getValue:o,resources:n,toKey:I=>I.id}):new Map,[o,n]),b=h.useMemo(()=>{if(!i)return new Map;let I={...fe,...e??{}};return ae({fallback:void 0,getValue:T=>{let M=i(T);return M?I[M]:void 0},resources:n,toKey:T=>T.id})},[e,i,n]);return {appointmentAppearanceByResource:h.useMemo(()=>Ie(n,a,s,b,l),[l,b,s,a,n]),appointmentBgByResource:l,effectiveAppts:f,laidOutByResource:R}}function we({dayMinutes:e,dayStartAbs:t,gridHeight:r}){return jsx("div",{className:"rfs-times border-border bg-card",style:{width:72,height:r},children:Array.from({length:Math.floor(e/60)+1}).map((s,o)=>jsxs("div",{className:"rfs-time-tick text-muted-foreground",style:{top:12+o*60*2},children:[String(Math.floor((t+o*60)/60)).padStart(2,"0"),":",String((t+o*60)%60).padStart(2,"0")]},`tick-${o}`))})}function Ne({resource:e}){return jsx("strong",{children:e.label})}function Ee({resources:e,renderResourceHeader:t}){return jsxs("div",{className:"rfs-header border-border bg-card",children:[jsx("div",{className:"rfs-gutter border-border",style:{width:72}}),e.map(r=>jsxs("div",{className:"rfs-resource-header border-border bg-card text-foreground",style:{minWidth:420,flex:1},children:[(t??(s=>Ne({resource:s})))(r),me(r)?jsx("div",{className:"rfs-resource-subtitle text-muted-foreground",children:Re(r)}):null]},String(r.id)))]})}var Oe=({resource:e,colRefs:t,gridHeight:r,dayMinutes:s,appointments:o,appointmentAppearance:i,appointmentBg:c,isDropInvalid:a,renderAppointment:n,onApptPointerDown:d,onResizePointerDown:f,drag:p,suppressClickRef:R})=>jsx("div",{className:"rfs-col border-border bg-card",style:{minWidth:420,flex:1},children:jsxs("div",{ref:l=>{t.current[String(e.id)]=l;},className:"rfs-col-inner bg-card",style:{height:r},children:[Array.from({length:Math.floor(s/60)+1}).map((l,b)=>jsx("div",{className:"rfs-hour-line border-border/70",style:{top:12+b*60*2}},`major-${b}`)),Array.from({length:Math.floor(s/15)+1}).map((l,b)=>jsx("div",{className:"rfs-slot-line border-border/50",style:{top:12+b*15*2}},`minor-${b}`)),o.map(l=>{let b=100/l.lanes,A=12+l.startMin*2,I=Math.max(30,(l.endMin-l.startMin)*2);return jsx("div",{className:`rfs-card-slot ${l.visualState==="ghost"?"rfs-card-slot--ghost":""} ${l.visualState==="dragging"?"rfs-card-slot--dragging":""}`,style:{top:A,left:`${b*l.lane}%`,width:`${b}%`,height:I},children:(n??G)({appointment:l,isDropInvalid:a&&p.kind==="resize"&&l.id===p.appointmentId,onPointerDown:T=>d(T,l),onResizePointerDown:T=>f(T,l),appointmentAppearance:i,appointmentBackgroundColor:c,drag:p,suppressClickRef:R})},l.renderKey)})]})});var mt=(e,t)=>[e.resource===t.resource,e.colRefs===t.colRefs,e.gridHeight===t.gridHeight,e.dayMinutes===t.dayMinutes,e.appointments===t.appointments,e.appointmentAppearance===t.appointmentAppearance,e.appointmentBg===t.appointmentBg,e.isDropInvalid===t.isDropInvalid,e.renderAppointment===t.renderAppointment,e.onApptPointerDown===t.onApptPointerDown,e.onResizePointerDown===t.onResizePointerDown,e.drag===t.drag,e.suppressClickRef===t.suppressClickRef].every(Boolean),_e=h.memo(Oe,mt);function H(...e){return e.filter(Boolean).join(" ")}var ft={default:"bg-primary text-primary-foreground hover:bg-primary/90",outline:"border border-border bg-card text-foreground hover:bg-muted",ghost:"text-foreground hover:bg-muted"},Rt={default:"h-9 px-4 py-2",sm:"h-8 px-3 text-xs",icon:"h-8 w-8"},P=h.forwardRef(function({className:t,variant:r="default",size:s="default",type:o="button",...i},c){return jsx("button",{ref:c,type:o,className:H("inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",ft[r],Rt[s],t),...i})});function Ue({selected:e,onSelect:t}){let[r,s]=h.useState(e??new Date);h.useEffect(()=>{e&&s(e);},[e]);let o=startOfMonth(r),i=startOfWeek(o,{weekStartsOn:1}),c=endOfWeek(endOfMonth(r),{weekStartsOn:1}),a=[];for(let n=i;n<=c;n=addDays(n,1))a.push(n);return jsxs("div",{className:"w-72 rounded-md bg-popover",children:[jsxs("div",{className:"mb-2 flex items-center justify-between",children:[jsx(P,{variant:"ghost",size:"icon","aria-label":"Previous month",onClick:()=>s(addMonths(r,-1)),children:"<"}),jsx("div",{className:"text-sm font-medium",children:format(r,"MMMM yyyy")}),jsx(P,{variant:"ghost",size:"icon","aria-label":"Next month",onClick:()=>s(addMonths(r,1)),children:">"})]}),jsx("div",{className:"grid grid-cols-7 gap-1 text-center text-xs text-muted-foreground",children:["Mo","Tu","We","Th","Fr","Sa","Su"].map(n=>jsx("div",{className:"py-1",children:n},n))}),jsx("div",{className:"grid grid-cols-7 gap-1",children:a.map(n=>{let d=e?isSameDay(n,e):false;return jsx(P,{variant:d?"default":"ghost",size:"icon",className:H("text-xs",!isSameMonth(n,r)&&"text-muted-foreground opacity-60",isToday(n)&&!d&&"border border-border"),onClick:()=>t?.(n),children:format(n,"d")},n.toISOString())})})]})}var Ve=h.createContext(null);function $e(){let e=h.useContext(Ve);if(!e)throw new Error("Popover components must be used within Popover.");return e}function Fe({children:e,open:t,onOpenChange:r}){let[s,o]=h.useState(false),i=h.useRef(null),c=h.useRef(null),a=t??s,n=h.useCallback(d=>{let f=typeof d=="function"?d(a):d;t===void 0&&o(f),r?.(f);},[t,r,a]);return h.useEffect(()=>{if(!a)return;let d=p=>{let R=p.target;i.current?.contains(R)||c.current?.contains(R)||n(false);},f=p=>{p.key==="Escape"&&n(false);};return document.addEventListener("mousedown",d),document.addEventListener("keydown",f),()=>{document.removeEventListener("mousedown",d),document.removeEventListener("keydown",f);}},[a,n]),jsx(Ve.Provider,{value:{open:a,setOpen:n,triggerRef:i,contentRef:c},children:jsx("div",{className:"relative inline-flex",children:e})})}function We({children:e,asChild:t=false}){let{open:r,setOpen:s,triggerRef:o}=$e();if(t&&h.isValidElement(e)){let i=e;return h.cloneElement(i,{ref:o,"aria-expanded":r,onClick:c=>{i.props.onClick?.(c),s(a=>!a);}})}return jsx("button",{ref:o,type:"button","aria-expanded":r,onClick:()=>s(i=>!i),children:e})}function Ge({children:e,className:t,align:r="start"}){let{open:s,contentRef:o}=$e();return s?jsx("div",{ref:o,className:H("absolute top-full z-50 mt-2 min-w-72 rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-md outline-none",r==="start"&&"left-0",r==="center"&&"left-1/2 -translate-x-1/2",r==="end"&&"right-0",t),children:e}):null}function Ke({selectedDate:e,onSelectedDateChange:t}){return jsxs(Fe,{children:[jsx(We,{asChild:true,children:jsx(P,{variant:"outline",size:"sm",className:"min-w-40 justify-start text-left font-normal",children:format(e,"PPP")})}),jsx(Ge,{className:"w-auto p-2",align:"center",children:jsx(Ue,{selected:e,onSelect:t})})]})}function qe({goToPreviousDay:e,goToNextDay:t,defaultDatePicker:r,prevButtonLabel:s="Prev",nextButtonLabel:o="Next"}){return jsx("div",{className:"rfs-toolbar",children:jsxs("div",{className:"flex items-center gap-2 rounded-lg border border-border bg-card p-1 shadow-xs",children:[jsx(P,{variant:"outline",size:"sm",onClick:e,children:s}),r,jsx(P,{variant:"outline",size:"sm",onClick:t,children:o})]})})}function Ze({selectedDate:e,onSelectedDateChange:t,prevButtonLabel:r,nextButtonLabel:s,renderToolbar:o,renderDatePicker:i}){let c=()=>t(oe(e,-1)),a=()=>t(oe(e,1)),n=(i??Ke)({selectedDate:e,onSelectedDateChange:t});return o?jsx(Fragment,{children:o({selectedDate:e,onSelectedDateChange:t,goToPreviousDay:c,goToNextDay:a,defaultDatePicker:n})}):jsx(Fragment,{children:qe({goToPreviousDay:c,goToNextDay:a,defaultDatePicker:n,prevButtonLabel:r,nextButtonLabel:s})})}var Dt={kind:"none"};function kt({resources:e,appointments:t,selectedDate:r,onSelectedDateChange:s,adapter:o,onPersistMoveResize:i,onAppointmentChange:c,renderResourceHeader:a,renderAppointment:n,resourceAppointmentClassMap:d,getResourceAppointmentAppearance:f,getResourceAppointmentColorToken:p,appointmentColorTokenClassMap:R,getResourceAppointmentBackground:l,renderToolbar:b,renderDatePicker:A,prevButtonLabel:I="Prev",nextButtonLabel:T="Next",dayStart:M="09:00",dayEnd:V="18:00"}){let{appointmentMap:E,dayMinutes:z,dayStartAbs:O,gridHeight:$,gridMinWidth:j,renderAppts:F}=Ae({adapter:o,appointments:t,dayEnd:V,dayStart:M,resources:e,selectedDate:r}),{colRefs:ee,drag:u,isDropInvalid:m,movePreview:g,onApptPointerDown:y,onGlobalPointerMove:Y,onGlobalPointerUp:k,onResizePointerDown:et,suppressClickRef:le}=De({appointmentMap:E,dayMinutes:z,dayStartAbs:O,onAppointmentChange:c,onPersistMoveResize:i,renderAppts:F,resources:e,selectedDate:r}),{appointmentAppearanceByResource:te,appointmentBgByResource:re,laidOutByResource:tt}=ke({appointmentColorTokenClassMap:R,dayStartAbs:O,drag:u,getResourceAppointmentAppearance:f,getResourceAppointmentBackground:l,getResourceAppointmentColorToken:p,renderAppts:F,resourceAppointmentClassMap:d,resources:e,selectedDate:r}),x=h.useMemo(()=>u.kind!=="move"||!g?null:{appointment:{...g.appointment,resourceId:u.resourceId,startMin:u.startMin,endMin:u.endMin,visualState:"ghost",renderKey:`${g.appointment.renderKey}-overlay`,lane:0,lanes:1},appearance:te.get(u.resourceId),background:re.get(u.resourceId),left:g.clientX-g.offsetX,top:g.clientY-g.offsetY,width:g.width,height:g.height},[te,re,u,g]);return jsxs("section",{className:`rfs-root rounded-lg bg-background text-foreground ${u.kind==="move"?"rfs-root--drag-move":""} ${u.kind==="resize"?"rfs-root--drag-resize":""}`,onPointerMove:Y,onPointerUp:k,onPointerCancel:k,children:[jsx(Ze,{selectedDate:r,onSelectedDateChange:s,prevButtonLabel:I,nextButtonLabel:T,renderToolbar:b,renderDatePicker:A}),jsx("div",{className:"rfs-shell border-border bg-card",children:jsxs("div",{style:{minWidth:j},children:[jsx(Ee,{resources:e,renderResourceHeader:a}),jsxs("div",{className:"rfs-body",children:[jsx(we,{dayMinutes:z,dayStartAbs:O,gridHeight:$}),jsx("div",{className:"rfs-grid",children:e.map(L=>jsx(_e,{resource:L,colRefs:ee,gridHeight:$,dayMinutes:z,appointments:tt.get(L.id)??[],appointmentAppearance:te.get(L.id),appointmentBg:re.get(L.id),isDropInvalid:m,renderAppointment:n,onApptPointerDown:y,onResizePointerDown:et,drag:u.kind==="resize"&&u.resourceId===L.id?u:Dt,suppressClickRef:le},String(L.id)))})]})]})}),x?jsx("div",{className:"rfs-drag-overlay",style:{left:x.left,top:x.top,width:x.width,height:x.height},children:(n??G)({appointment:x.appointment,isDropInvalid:m,onPointerDown:()=>{},onResizePointerDown:()=>{},appointmentAppearance:x.appearance,appointmentBackgroundColor:x.background,drag:u,suppressClickRef:le})}):null]})}function wt(e,t,r){return e.map(s=>{if(r.getId(s)!==r.getId(t.appointment))return s;let o=r.setResourceId(s,t.next.resourceId),i=r.setStart(o,t.next.start);return r.setEnd(i,t.next.end)})}export{kt as Scheduler,wt as applySchedulerAppointmentChange};
|