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