@marco.colia/react-fast-scheduler 1.0.2 → 2.1.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 +73 -37
- package/dist/index.cjs +667 -292
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -18
- package/dist/index.d.ts +37 -18
- package/dist/index.js +666 -292
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
|
-
import * as React from 'react';
|
|
2
|
-
import { format } from 'date-fns';
|
|
3
1
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { format } from 'date-fns';
|
|
3
|
+
import * as React2 from 'react';
|
|
4
4
|
|
|
5
|
-
// src/components/
|
|
5
|
+
// src/components/defaultRenderAppointment/index.tsx
|
|
6
|
+
function defaultRenderAppointment({
|
|
7
|
+
appointment,
|
|
8
|
+
onPointerDown,
|
|
9
|
+
onResizePointerDown,
|
|
10
|
+
appointmentAppearance,
|
|
11
|
+
appointmentBackgroundColor
|
|
12
|
+
}) {
|
|
13
|
+
const rawMaybe = appointment.raw;
|
|
14
|
+
const description = typeof rawMaybe.description === "string" ? rawMaybe.description : void 0;
|
|
15
|
+
const legacyClassName = typeof appointmentBackgroundColor === "string" ? appointmentBackgroundColor : void 0;
|
|
16
|
+
const backgroundClassName = appointmentAppearance?.className ?? legacyClassName ?? "bg-muted/40 dark:bg-muted/30";
|
|
17
|
+
const className = `relative h-full cursor-grab overflow-hidden rounded-md border border-border p-2 pb-5 text-foreground ${backgroundClassName}`;
|
|
18
|
+
return /* @__PURE__ */ jsxs("div", { onPointerDown, className, children: [
|
|
19
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs font-semibold", children: appointment.title }),
|
|
20
|
+
description ? /* @__PURE__ */ jsx("div", { className: "mt-1 text-[10px] font-medium text-muted-foreground", children: description }) : null,
|
|
21
|
+
/* @__PURE__ */ jsx(
|
|
22
|
+
"div",
|
|
23
|
+
{
|
|
24
|
+
role: "button",
|
|
25
|
+
"aria-label": "Resize appointment",
|
|
26
|
+
onPointerDown: (event) => {
|
|
27
|
+
event.stopPropagation();
|
|
28
|
+
onResizePointerDown(event);
|
|
29
|
+
},
|
|
30
|
+
className: "absolute inset-x-1 bottom-1 h-2 cursor-ns-resize rounded-full bg-border/90 transition-colors hover:bg-border"
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
] });
|
|
34
|
+
}
|
|
6
35
|
|
|
7
36
|
// src/components/constants.ts
|
|
8
37
|
var STEP_MIN = 15;
|
|
@@ -12,26 +41,75 @@ var GUTTER_W = 72;
|
|
|
12
41
|
var TOP_PAD = 12;
|
|
13
42
|
var BOTTOM_PAD = 24;
|
|
14
43
|
var RESOURCE_MIN_W = 420;
|
|
15
|
-
|
|
16
|
-
|
|
44
|
+
|
|
45
|
+
// src/utils/scheduler-core.utils.ts
|
|
46
|
+
function isNameLike(resource) {
|
|
47
|
+
if (!resource || typeof resource !== "object") {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
const maybe = resource;
|
|
51
|
+
return typeof maybe.firstName === "string" || typeof maybe.lastName === "string";
|
|
52
|
+
}
|
|
53
|
+
function toInputDateValue(date) {
|
|
54
|
+
const year = date.getFullYear();
|
|
55
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
56
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
57
|
+
return `${year}-${month}-${day}`;
|
|
58
|
+
}
|
|
59
|
+
function fromInputDateValue(value) {
|
|
60
|
+
const [yearRaw, monthRaw, dayRaw] = value.split("-");
|
|
61
|
+
const year = Number(yearRaw);
|
|
62
|
+
const month = Number(monthRaw);
|
|
63
|
+
const day = Number(dayRaw);
|
|
64
|
+
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
return new Date(year, month - 1, day);
|
|
68
|
+
}
|
|
69
|
+
var DEFAULT_APPOINTMENT_COLOR_TOKEN_CLASS_MAP = {
|
|
70
|
+
slate: "bg-slate-100 dark:bg-slate-900/40",
|
|
71
|
+
gray: "bg-gray-100 dark:bg-gray-900/40",
|
|
72
|
+
zinc: "bg-zinc-100 dark:bg-zinc-900/40",
|
|
73
|
+
neutral: "bg-neutral-100 dark:bg-neutral-900/40",
|
|
74
|
+
stone: "bg-stone-100 dark:bg-stone-900/40",
|
|
75
|
+
red: "bg-red-100 dark:bg-red-950/40",
|
|
76
|
+
orange: "bg-orange-100 dark:bg-orange-950/40",
|
|
77
|
+
amber: "bg-amber-100 dark:bg-amber-950/40",
|
|
78
|
+
yellow: "bg-yellow-100 dark:bg-yellow-950/40",
|
|
79
|
+
lime: "bg-lime-100 dark:bg-lime-950/40",
|
|
80
|
+
green: "bg-green-100 dark:bg-green-950/40",
|
|
81
|
+
emerald: "bg-emerald-100 dark:bg-emerald-950/40",
|
|
82
|
+
teal: "bg-teal-100 dark:bg-teal-950/40",
|
|
83
|
+
cyan: "bg-cyan-100 dark:bg-cyan-950/40",
|
|
84
|
+
sky: "bg-sky-100 dark:bg-sky-950/40",
|
|
85
|
+
blue: "bg-blue-100 dark:bg-blue-950/40",
|
|
86
|
+
indigo: "bg-indigo-100 dark:bg-indigo-950/40",
|
|
87
|
+
violet: "bg-violet-100 dark:bg-violet-950/40",
|
|
88
|
+
purple: "bg-purple-100 dark:bg-purple-950/40",
|
|
89
|
+
fuchsia: "bg-fuchsia-100 dark:bg-fuchsia-950/40",
|
|
90
|
+
pink: "bg-pink-100 dark:bg-pink-950/40",
|
|
91
|
+
rose: "bg-rose-100 dark:bg-rose-950/40"
|
|
17
92
|
};
|
|
18
|
-
|
|
93
|
+
function clamp(n, min, max) {
|
|
94
|
+
return Math.max(min, Math.min(max, n));
|
|
95
|
+
}
|
|
96
|
+
function snap(mins, stepMin = STEP_MIN) {
|
|
19
97
|
return Math.round(mins / stepMin) * stepMin;
|
|
20
|
-
}
|
|
21
|
-
|
|
98
|
+
}
|
|
99
|
+
function parseHHMM(hhmm) {
|
|
22
100
|
const [h, m] = hhmm.split(":").map(Number);
|
|
23
101
|
return h * 60 + m;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
return `${
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return format(
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return new Date(
|
|
33
|
-
}
|
|
34
|
-
|
|
102
|
+
}
|
|
103
|
+
function fullName(resource) {
|
|
104
|
+
return `${resource.firstName ?? ""} ${resource.lastName ?? ""}`.trim();
|
|
105
|
+
}
|
|
106
|
+
function dayISO(date) {
|
|
107
|
+
return format(date, "yyyy-MM-dd");
|
|
108
|
+
}
|
|
109
|
+
function shiftDays(date, delta) {
|
|
110
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate() + delta);
|
|
111
|
+
}
|
|
112
|
+
function dateAtMinute(selectedDate, dayStartAbs, minuteFromStart) {
|
|
35
113
|
const base = new Date(
|
|
36
114
|
selectedDate.getFullYear(),
|
|
37
115
|
selectedDate.getMonth(),
|
|
@@ -46,225 +124,314 @@ var dateAtMinute = (selectedDate, dayStartAbs, minuteFromStart) => {
|
|
|
46
124
|
const mm = abs % 60;
|
|
47
125
|
base.setHours(hh, mm, 0, 0);
|
|
48
126
|
return base;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
|
|
127
|
+
}
|
|
128
|
+
function minutesFromDayStart(dayStartAbs, date) {
|
|
129
|
+
return date.getHours() * 60 + date.getMinutes() - dayStartAbs;
|
|
130
|
+
}
|
|
131
|
+
function overlaps(a, b) {
|
|
54
132
|
return a.startMin < b.endMin && b.startMin < a.endMin;
|
|
55
|
-
}
|
|
56
|
-
|
|
133
|
+
}
|
|
134
|
+
function layoutOverlaps(blocks) {
|
|
57
135
|
const sorted = [...blocks].sort((a, b) => a.startMin - b.startMin || a.endMin - b.endMin);
|
|
58
136
|
const groups = [];
|
|
59
137
|
let active = [];
|
|
60
138
|
let current = [];
|
|
61
|
-
for (const
|
|
62
|
-
active = active.filter((
|
|
63
|
-
const conflicts = active.some((
|
|
139
|
+
for (const block of sorted) {
|
|
140
|
+
active = active.filter((item) => item.endMin > block.startMin);
|
|
141
|
+
const conflicts = active.some((item) => overlaps(item, block));
|
|
64
142
|
if (!conflicts && current.length > 0) {
|
|
65
143
|
groups.push(current);
|
|
66
144
|
current = [];
|
|
67
145
|
}
|
|
68
|
-
current.push(
|
|
69
|
-
active.push(
|
|
146
|
+
current.push(block);
|
|
147
|
+
active.push(block);
|
|
70
148
|
}
|
|
71
149
|
if (current.length) {
|
|
72
150
|
groups.push(current);
|
|
73
151
|
}
|
|
74
|
-
const
|
|
152
|
+
const output = [];
|
|
75
153
|
for (const group of groups) {
|
|
76
|
-
const
|
|
154
|
+
const sortedGroup = [...group].sort((a, b) => a.startMin - b.startMin || a.endMin - b.endMin);
|
|
77
155
|
const laneEnd = [];
|
|
78
156
|
const laneOf = /* @__PURE__ */ new Map();
|
|
79
|
-
for (const
|
|
157
|
+
for (const block of sortedGroup) {
|
|
80
158
|
let placed = false;
|
|
81
159
|
for (let i = 0; i < laneEnd.length; i++) {
|
|
82
|
-
if (laneEnd[i] <=
|
|
83
|
-
laneEnd[i] =
|
|
84
|
-
laneOf.set(
|
|
160
|
+
if (laneEnd[i] <= block.startMin) {
|
|
161
|
+
laneEnd[i] = block.endMin;
|
|
162
|
+
laneOf.set(block.id, i);
|
|
85
163
|
placed = true;
|
|
86
164
|
break;
|
|
87
165
|
}
|
|
88
166
|
}
|
|
89
167
|
if (!placed) {
|
|
90
|
-
laneEnd.push(
|
|
91
|
-
laneOf.set(
|
|
168
|
+
laneEnd.push(block.endMin);
|
|
169
|
+
laneOf.set(block.id, laneEnd.length - 1);
|
|
92
170
|
}
|
|
93
171
|
}
|
|
94
172
|
const lanes = laneEnd.length;
|
|
95
|
-
for (const
|
|
96
|
-
|
|
173
|
+
for (const block of sortedGroup) {
|
|
174
|
+
output.push({ ...block, lane: laneOf.get(block.id) ?? 0, lanes });
|
|
97
175
|
}
|
|
98
176
|
}
|
|
99
|
-
return
|
|
100
|
-
};
|
|
101
|
-
function isNameLike(resource) {
|
|
102
|
-
if (!resource || typeof resource !== "object") {
|
|
103
|
-
return false;
|
|
104
|
-
}
|
|
105
|
-
const maybe = resource;
|
|
106
|
-
return typeof maybe.firstName === "string" || typeof maybe.lastName === "string";
|
|
177
|
+
return output;
|
|
107
178
|
}
|
|
108
|
-
function
|
|
179
|
+
function defaultRenderDatePicker({ selectedDate, onSelectedDateChange }) {
|
|
180
|
+
return /* @__PURE__ */ jsx(
|
|
181
|
+
"input",
|
|
182
|
+
{
|
|
183
|
+
type: "date",
|
|
184
|
+
value: toInputDateValue(selectedDate),
|
|
185
|
+
onChange: (event) => {
|
|
186
|
+
const nextDate = fromInputDateValue(event.target.value);
|
|
187
|
+
if (nextDate) {
|
|
188
|
+
onSelectedDateChange(nextDate);
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
className: "h-7 rounded-md border border-border bg-card px-2 text-xs text-foreground",
|
|
192
|
+
"aria-label": "Select date"
|
|
193
|
+
}
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
function defaultRenderResourceHeader({ resource }) {
|
|
197
|
+
return /* @__PURE__ */ jsx("strong", { children: resource.label });
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/utils/scheduler-data.utils.ts
|
|
201
|
+
function buildResourceMap({
|
|
202
|
+
fallback,
|
|
203
|
+
getValue,
|
|
109
204
|
resources,
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
205
|
+
toKey
|
|
206
|
+
}) {
|
|
207
|
+
const map = /* @__PURE__ */ new Map();
|
|
208
|
+
for (const resource of resources) {
|
|
209
|
+
map.set(toKey(resource), getValue(resource) ?? fallback);
|
|
210
|
+
}
|
|
211
|
+
return map;
|
|
212
|
+
}
|
|
213
|
+
function normalizeAppointments({
|
|
113
214
|
adapter,
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
getResourceAppointmentBackground,
|
|
119
|
-
renderDatePicker,
|
|
120
|
-
prevButtonLabel = "Prev",
|
|
121
|
-
nextButtonLabel = "Next",
|
|
122
|
-
dayStart = "09:00",
|
|
123
|
-
dayEnd = "18:00"
|
|
215
|
+
appointments,
|
|
216
|
+
dayMinutes,
|
|
217
|
+
dayStartAbs,
|
|
218
|
+
selectedISO
|
|
124
219
|
}) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
const appointmentMap = React.useMemo(() => {
|
|
151
|
-
return new Map(renderAppts.map((appointment) => [appointment.id, appointment]));
|
|
152
|
-
}, [renderAppts]);
|
|
153
|
-
const effectiveAppts = React.useMemo(() => {
|
|
154
|
-
if (drag.kind === "none") {
|
|
155
|
-
return renderAppts;
|
|
156
|
-
}
|
|
157
|
-
return renderAppts.map((appointment) => {
|
|
158
|
-
if (appointment.id !== drag.appointmentId) {
|
|
159
|
-
return appointment;
|
|
160
|
-
}
|
|
161
|
-
return {
|
|
162
|
-
...appointment,
|
|
163
|
-
resourceId: drag.resourceId,
|
|
164
|
-
startMin: drag.startMin,
|
|
165
|
-
endMin: drag.endMin,
|
|
166
|
-
start: dateAtMinute(selectedDate, dayStartAbs, drag.startMin),
|
|
167
|
-
end: dateAtMinute(selectedDate, dayStartAbs, drag.endMin)
|
|
168
|
-
};
|
|
169
|
-
});
|
|
170
|
-
}, [drag, renderAppts, selectedDate, dayStartAbs]);
|
|
171
|
-
const laidOutByResource = React.useMemo(() => {
|
|
172
|
-
const byResource = /* @__PURE__ */ new Map();
|
|
173
|
-
for (const resource of resources) {
|
|
174
|
-
const events = effectiveAppts.filter((appointment) => appointment.resourceId === resource.id);
|
|
175
|
-
byResource.set(resource.id, layoutOverlaps(events));
|
|
220
|
+
return appointments.map((appointment) => {
|
|
221
|
+
const start = new Date(adapter.getStart(appointment));
|
|
222
|
+
const end = new Date(adapter.getEnd(appointment));
|
|
223
|
+
return {
|
|
224
|
+
raw: appointment,
|
|
225
|
+
id: adapter.getId(appointment),
|
|
226
|
+
resourceId: adapter.getResourceId(appointment),
|
|
227
|
+
start,
|
|
228
|
+
end,
|
|
229
|
+
startMin: clamp(minutesFromDayStart(dayStartAbs, start), 0, dayMinutes),
|
|
230
|
+
endMin: clamp(minutesFromDayStart(dayStartAbs, end), 0, dayMinutes),
|
|
231
|
+
title: adapter.getTitle(appointment)
|
|
232
|
+
};
|
|
233
|
+
}).filter((appointment) => dayISO(appointment.start) === selectedISO);
|
|
234
|
+
}
|
|
235
|
+
function buildAppointmentMap(appointments) {
|
|
236
|
+
return new Map(appointments.map((appointment) => [appointment.id, appointment]));
|
|
237
|
+
}
|
|
238
|
+
function applyDragToAppointments(appointments, drag, selectedDate, dayStartAbs) {
|
|
239
|
+
if (drag.kind === "none") {
|
|
240
|
+
return appointments;
|
|
241
|
+
}
|
|
242
|
+
return appointments.map((appointment) => {
|
|
243
|
+
if (appointment.id !== drag.appointmentId) {
|
|
244
|
+
return appointment;
|
|
176
245
|
}
|
|
177
|
-
return
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
246
|
+
return {
|
|
247
|
+
...appointment,
|
|
248
|
+
resourceId: drag.resourceId,
|
|
249
|
+
startMin: drag.startMin,
|
|
250
|
+
endMin: drag.endMin,
|
|
251
|
+
start: dateAtMinute(selectedDate, dayStartAbs, drag.startMin),
|
|
252
|
+
end: dateAtMinute(selectedDate, dayStartAbs, drag.endMin)
|
|
253
|
+
};
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
function buildLaidOutByResource(resources, appointments) {
|
|
257
|
+
const appointmentsByResource = /* @__PURE__ */ new Map();
|
|
258
|
+
for (const appointment of appointments) {
|
|
259
|
+
const current = appointmentsByResource.get(appointment.resourceId) ?? [];
|
|
260
|
+
current.push(appointment);
|
|
261
|
+
appointmentsByResource.set(appointment.resourceId, current);
|
|
262
|
+
}
|
|
263
|
+
const result = /* @__PURE__ */ new Map();
|
|
264
|
+
for (const resource of resources) {
|
|
265
|
+
result.set(resource.id, layoutOverlaps(appointmentsByResource.get(resource.id) ?? []));
|
|
266
|
+
}
|
|
267
|
+
return result;
|
|
268
|
+
}
|
|
269
|
+
function buildAppointmentAppearanceByResource(resources, resourceAppointmentClassMap, getResourceAppointmentAppearance, appointmentColorTokenClassByResource, appointmentBgByResource) {
|
|
270
|
+
const map = /* @__PURE__ */ new Map();
|
|
271
|
+
for (const resource of resources) {
|
|
272
|
+
const classFromIdMap = resourceAppointmentClassMap?.[String(resource.id)];
|
|
273
|
+
if (classFromIdMap) {
|
|
274
|
+
map.set(resource.id, { className: classFromIdMap });
|
|
275
|
+
continue;
|
|
183
276
|
}
|
|
184
|
-
|
|
185
|
-
|
|
277
|
+
const appearance = getResourceAppointmentAppearance?.(resource);
|
|
278
|
+
if (appearance) {
|
|
279
|
+
map.set(resource.id, appearance);
|
|
280
|
+
continue;
|
|
186
281
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
if (!col) {
|
|
192
|
-
return 0;
|
|
282
|
+
const tokenClass = appointmentColorTokenClassByResource.get(resource.id);
|
|
283
|
+
if (tokenClass) {
|
|
284
|
+
map.set(resource.id, { className: tokenClass });
|
|
285
|
+
continue;
|
|
193
286
|
}
|
|
194
|
-
const
|
|
195
|
-
|
|
287
|
+
const legacyBackground = appointmentBgByResource.get(resource.id);
|
|
288
|
+
map.set(resource.id, legacyBackground ? { className: legacyBackground } : void 0);
|
|
289
|
+
}
|
|
290
|
+
return map;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// src/hooks/useSchedulerBaseData.ts
|
|
294
|
+
function useSchedulerBaseData({
|
|
295
|
+
adapter,
|
|
296
|
+
appointments,
|
|
297
|
+
dayEnd = "18:00",
|
|
298
|
+
dayStart = "09:00",
|
|
299
|
+
resources,
|
|
300
|
+
selectedDate
|
|
301
|
+
}) {
|
|
302
|
+
const dayStartAbs = React2.useMemo(() => parseHHMM(dayStart), [dayStart]);
|
|
303
|
+
const dayEndAbs = React2.useMemo(() => parseHHMM(dayEnd), [dayEnd]);
|
|
304
|
+
const dayMinutes = React2.useMemo(() => Math.max(0, dayEndAbs - dayStartAbs), [dayEndAbs, dayStartAbs]);
|
|
305
|
+
const gridHeight = React2.useMemo(() => TOP_PAD + dayMinutes * PX_PER_MIN + BOTTOM_PAD, [dayMinutes]);
|
|
306
|
+
const gridMinWidth = React2.useMemo(() => GUTTER_W + Math.max(resources.length, 1) * RESOURCE_MIN_W, [resources.length]);
|
|
307
|
+
const selectedISO = React2.useMemo(() => dayISO(selectedDate), [selectedDate]);
|
|
308
|
+
const renderAppts = React2.useMemo(() => {
|
|
309
|
+
return normalizeAppointments({
|
|
310
|
+
adapter,
|
|
311
|
+
appointments,
|
|
312
|
+
dayMinutes,
|
|
313
|
+
dayStartAbs,
|
|
314
|
+
selectedISO
|
|
315
|
+
});
|
|
316
|
+
}, [adapter, appointments, dayMinutes, dayStartAbs, selectedISO]);
|
|
317
|
+
const appointmentMap = React2.useMemo(() => buildAppointmentMap(renderAppts), [renderAppts]);
|
|
318
|
+
return {
|
|
319
|
+
appointmentMap,
|
|
320
|
+
dayEndAbs,
|
|
321
|
+
dayMinutes,
|
|
322
|
+
dayStartAbs,
|
|
323
|
+
gridHeight,
|
|
324
|
+
gridMinWidth,
|
|
325
|
+
renderAppts
|
|
196
326
|
};
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
327
|
+
}
|
|
328
|
+
function useSchedulerInteractions({
|
|
329
|
+
appointmentMap,
|
|
330
|
+
dayMinutes,
|
|
331
|
+
dayStartAbs,
|
|
332
|
+
onAppointmentChange,
|
|
333
|
+
onPersistMoveResize,
|
|
334
|
+
renderAppts,
|
|
335
|
+
resources,
|
|
336
|
+
selectedDate
|
|
337
|
+
}) {
|
|
338
|
+
const [drag, setDrag] = React2.useState({ kind: "none" });
|
|
339
|
+
const colRefs = React2.useRef({});
|
|
340
|
+
const suppressClickRef = React2.useRef(false);
|
|
341
|
+
const pointerToMin = React2.useCallback(
|
|
342
|
+
(resourceId, clientY) => {
|
|
343
|
+
const col = colRefs.current[String(resourceId)];
|
|
200
344
|
if (!col) {
|
|
201
|
-
|
|
345
|
+
return 0;
|
|
202
346
|
}
|
|
203
|
-
const
|
|
204
|
-
|
|
205
|
-
|
|
347
|
+
const y = clientY - col.getBoundingClientRect().top - TOP_PAD;
|
|
348
|
+
return clamp(snap(y / PX_PER_MIN), 0, dayMinutes);
|
|
349
|
+
},
|
|
350
|
+
[dayMinutes]
|
|
351
|
+
);
|
|
352
|
+
const pointerToResourceId = React2.useCallback(
|
|
353
|
+
(clientX, fallbackResourceId) => {
|
|
354
|
+
for (const resource of resources) {
|
|
355
|
+
const col = colRefs.current[String(resource.id)];
|
|
356
|
+
if (!col) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
const rect = col.getBoundingClientRect();
|
|
360
|
+
if (clientX >= rect.left && clientX <= rect.right) {
|
|
361
|
+
return resource.id;
|
|
362
|
+
}
|
|
206
363
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
364
|
+
return fallbackResourceId;
|
|
365
|
+
},
|
|
366
|
+
[resources]
|
|
367
|
+
);
|
|
368
|
+
const hasOverlap = React2.useCallback(
|
|
369
|
+
(appointmentId, resourceId, startMin, endMin) => {
|
|
370
|
+
const events = renderAppts.filter((appointment) => appointment.resourceId === resourceId && appointment.id !== appointmentId);
|
|
371
|
+
return events.some((appointment) => overlaps(appointment, { startMin, endMin }));
|
|
372
|
+
},
|
|
373
|
+
[renderAppts]
|
|
374
|
+
);
|
|
375
|
+
const persistDrag = React2.useCallback(
|
|
376
|
+
async (state) => {
|
|
377
|
+
const target = appointmentMap.get(state.appointmentId);
|
|
378
|
+
if (!target) {
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (hasOverlap(state.appointmentId, state.resourceId, state.startMin, state.endMin)) {
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
const next = {
|
|
385
|
+
resourceId: state.resourceId,
|
|
386
|
+
start: dateAtMinute(selectedDate, dayStartAbs, state.startMin),
|
|
387
|
+
end: dateAtMinute(selectedDate, dayStartAbs, state.endMin)
|
|
388
|
+
};
|
|
389
|
+
if (onAppointmentChange) {
|
|
390
|
+
await onAppointmentChange({
|
|
391
|
+
kind: state.kind,
|
|
392
|
+
appointment: target.raw,
|
|
393
|
+
previous: {
|
|
394
|
+
resourceId: target.resourceId,
|
|
395
|
+
start: target.start,
|
|
396
|
+
end: target.end
|
|
397
|
+
},
|
|
398
|
+
next
|
|
399
|
+
});
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (onPersistMoveResize) {
|
|
403
|
+
await onPersistMoveResize({
|
|
404
|
+
appointment: target.raw,
|
|
405
|
+
resourceId: next.resourceId,
|
|
406
|
+
start: next.start,
|
|
407
|
+
end: next.end
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
},
|
|
411
|
+
[appointmentMap, dayStartAbs, hasOverlap, onAppointmentChange, onPersistMoveResize, selectedDate]
|
|
412
|
+
);
|
|
413
|
+
const onApptPointerDown = React2.useCallback(
|
|
414
|
+
(event, appointment) => {
|
|
415
|
+
if (event.button !== 0) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
event.stopPropagation();
|
|
419
|
+
suppressClickRef.current = false;
|
|
420
|
+
const pointerMin = pointerToMin(appointment.resourceId, event.clientY);
|
|
421
|
+
setDrag({
|
|
422
|
+
kind: "move",
|
|
423
|
+
appointmentId: appointment.id,
|
|
424
|
+
pointerId: event.pointerId,
|
|
425
|
+
resourceId: appointment.resourceId,
|
|
426
|
+
durationMin: Math.max(MIN_EVENT_MIN, appointment.endMin - appointment.startMin),
|
|
427
|
+
offsetMin: pointerMin - appointment.startMin,
|
|
428
|
+
startMin: appointment.startMin,
|
|
429
|
+
endMin: appointment.endMin
|
|
246
430
|
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
event.stopPropagation();
|
|
254
|
-
suppressClickRef.current = false;
|
|
255
|
-
const pointerMin = pointerToMin(appointment.resourceId, event.clientY);
|
|
256
|
-
setDrag({
|
|
257
|
-
kind: "move",
|
|
258
|
-
appointmentId: appointment.id,
|
|
259
|
-
pointerId: event.pointerId,
|
|
260
|
-
resourceId: appointment.resourceId,
|
|
261
|
-
durationMin: Math.max(MIN_EVENT_MIN, appointment.endMin - appointment.startMin),
|
|
262
|
-
offsetMin: pointerMin - appointment.startMin,
|
|
263
|
-
startMin: appointment.startMin,
|
|
264
|
-
endMin: appointment.endMin
|
|
265
|
-
});
|
|
266
|
-
};
|
|
267
|
-
const onResizePointerDown = (event, appointment) => {
|
|
431
|
+
},
|
|
432
|
+
[pointerToMin]
|
|
433
|
+
);
|
|
434
|
+
const onResizePointerDown = React2.useCallback((event, appointment) => {
|
|
268
435
|
if (event.button !== 0) {
|
|
269
436
|
return;
|
|
270
437
|
}
|
|
@@ -278,39 +445,42 @@ function Scheduler({
|
|
|
278
445
|
startMin: appointment.startMin,
|
|
279
446
|
endMin: appointment.endMin
|
|
280
447
|
});
|
|
281
|
-
};
|
|
282
|
-
const onGlobalPointerMove = (
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
448
|
+
}, []);
|
|
449
|
+
const onGlobalPointerMove = React2.useCallback(
|
|
450
|
+
(event) => {
|
|
451
|
+
if (drag.kind === "none") {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (drag.kind === "move") {
|
|
455
|
+
const nextResourceId = pointerToResourceId(event.clientX, drag.resourceId);
|
|
456
|
+
const pointerMin2 = pointerToMin(nextResourceId, event.clientY);
|
|
457
|
+
const rawStart = pointerMin2 - drag.offsetMin;
|
|
458
|
+
const nextStartMin = clamp(snap(rawStart), 0, dayMinutes - drag.durationMin);
|
|
459
|
+
const nextEndMin2 = nextStartMin + drag.durationMin;
|
|
460
|
+
if (nextStartMin !== drag.startMin || nextEndMin2 !== drag.endMin || nextResourceId !== drag.resourceId) {
|
|
461
|
+
suppressClickRef.current = true;
|
|
462
|
+
setDrag({
|
|
463
|
+
...drag,
|
|
464
|
+
resourceId: nextResourceId,
|
|
465
|
+
startMin: nextStartMin,
|
|
466
|
+
endMin: nextEndMin2
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const pointerMin = pointerToMin(drag.resourceId, event.clientY);
|
|
472
|
+
const nextEndMin = clamp(snap(pointerMin), drag.startMin + MIN_EVENT_MIN, dayMinutes);
|
|
473
|
+
if (nextEndMin !== drag.endMin) {
|
|
293
474
|
suppressClickRef.current = true;
|
|
294
475
|
setDrag({
|
|
295
476
|
...drag,
|
|
296
|
-
|
|
297
|
-
startMin: nextStartMin,
|
|
298
|
-
endMin: nextEndMin2
|
|
477
|
+
endMin: nextEndMin
|
|
299
478
|
});
|
|
300
479
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (nextEndMin !== drag.endMin) {
|
|
306
|
-
suppressClickRef.current = true;
|
|
307
|
-
setDrag({
|
|
308
|
-
...drag,
|
|
309
|
-
endMin: nextEndMin
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
const onGlobalPointerUp = () => {
|
|
480
|
+
},
|
|
481
|
+
[dayMinutes, drag, pointerToMin, pointerToResourceId]
|
|
482
|
+
);
|
|
483
|
+
const onGlobalPointerUp = React2.useCallback(() => {
|
|
314
484
|
if (drag.kind === "none") {
|
|
315
485
|
return;
|
|
316
486
|
}
|
|
@@ -319,89 +489,281 @@ function Scheduler({
|
|
|
319
489
|
if (suppressClickRef.current) {
|
|
320
490
|
void persistDrag(current);
|
|
321
491
|
}
|
|
492
|
+
}, [drag, persistDrag]);
|
|
493
|
+
return {
|
|
494
|
+
colRefs,
|
|
495
|
+
drag,
|
|
496
|
+
onApptPointerDown,
|
|
497
|
+
onGlobalPointerMove,
|
|
498
|
+
onGlobalPointerUp,
|
|
499
|
+
onResizePointerDown,
|
|
500
|
+
suppressClickRef
|
|
322
501
|
};
|
|
502
|
+
}
|
|
503
|
+
function useSchedulerPresentationData({
|
|
504
|
+
appointmentColorTokenClassMap,
|
|
505
|
+
dayStartAbs,
|
|
506
|
+
drag,
|
|
507
|
+
getResourceAppointmentAppearance,
|
|
508
|
+
getResourceAppointmentBackground,
|
|
509
|
+
getResourceAppointmentColorToken,
|
|
510
|
+
renderAppts,
|
|
511
|
+
resourceAppointmentClassMap,
|
|
512
|
+
resources,
|
|
513
|
+
selectedDate
|
|
514
|
+
}) {
|
|
515
|
+
const effectiveAppts = React2.useMemo(() => {
|
|
516
|
+
return applyDragToAppointments(renderAppts, drag, selectedDate, dayStartAbs);
|
|
517
|
+
}, [dayStartAbs, drag, renderAppts, selectedDate]);
|
|
518
|
+
const laidOutByResource = React2.useMemo(() => {
|
|
519
|
+
return buildLaidOutByResource(resources, effectiveAppts);
|
|
520
|
+
}, [effectiveAppts, resources]);
|
|
521
|
+
const appointmentBgByResource = React2.useMemo(() => {
|
|
522
|
+
if (!getResourceAppointmentBackground) {
|
|
523
|
+
return /* @__PURE__ */ new Map();
|
|
524
|
+
}
|
|
525
|
+
return buildResourceMap({
|
|
526
|
+
fallback: void 0,
|
|
527
|
+
getValue: getResourceAppointmentBackground,
|
|
528
|
+
resources,
|
|
529
|
+
toKey: (resource) => resource.id
|
|
530
|
+
});
|
|
531
|
+
}, [getResourceAppointmentBackground, resources]);
|
|
532
|
+
const appointmentColorTokenClassByResource = React2.useMemo(() => {
|
|
533
|
+
if (!getResourceAppointmentColorToken) {
|
|
534
|
+
return /* @__PURE__ */ new Map();
|
|
535
|
+
}
|
|
536
|
+
const mergedClassMap = {
|
|
537
|
+
...DEFAULT_APPOINTMENT_COLOR_TOKEN_CLASS_MAP,
|
|
538
|
+
...appointmentColorTokenClassMap ?? {}
|
|
539
|
+
};
|
|
540
|
+
return buildResourceMap({
|
|
541
|
+
fallback: void 0,
|
|
542
|
+
getValue: (resource) => {
|
|
543
|
+
const token = getResourceAppointmentColorToken(resource);
|
|
544
|
+
return token ? mergedClassMap[token] : void 0;
|
|
545
|
+
},
|
|
546
|
+
resources,
|
|
547
|
+
toKey: (resource) => resource.id
|
|
548
|
+
});
|
|
549
|
+
}, [appointmentColorTokenClassMap, getResourceAppointmentColorToken, resources]);
|
|
550
|
+
const appointmentAppearanceByResource = React2.useMemo(() => {
|
|
551
|
+
return buildAppointmentAppearanceByResource(
|
|
552
|
+
resources,
|
|
553
|
+
resourceAppointmentClassMap,
|
|
554
|
+
getResourceAppointmentAppearance,
|
|
555
|
+
appointmentColorTokenClassByResource,
|
|
556
|
+
appointmentBgByResource
|
|
557
|
+
);
|
|
558
|
+
}, [
|
|
559
|
+
appointmentBgByResource,
|
|
560
|
+
appointmentColorTokenClassByResource,
|
|
561
|
+
getResourceAppointmentAppearance,
|
|
562
|
+
resourceAppointmentClassMap,
|
|
563
|
+
resources
|
|
564
|
+
]);
|
|
565
|
+
return {
|
|
566
|
+
appointmentAppearanceByResource,
|
|
567
|
+
appointmentBgByResource,
|
|
568
|
+
effectiveAppts,
|
|
569
|
+
laidOutByResource
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
function Scheduler({
|
|
573
|
+
resources,
|
|
574
|
+
appointments,
|
|
575
|
+
selectedDate,
|
|
576
|
+
onSelectedDateChange,
|
|
577
|
+
adapter,
|
|
578
|
+
onPersistMoveResize,
|
|
579
|
+
onAppointmentChange,
|
|
580
|
+
renderResourceHeader,
|
|
581
|
+
renderAppointment,
|
|
582
|
+
resourceAppointmentClassMap,
|
|
583
|
+
getResourceAppointmentAppearance,
|
|
584
|
+
getResourceAppointmentColorToken,
|
|
585
|
+
appointmentColorTokenClassMap,
|
|
586
|
+
getResourceAppointmentBackground,
|
|
587
|
+
renderDatePicker,
|
|
588
|
+
prevButtonLabel = "Prev",
|
|
589
|
+
nextButtonLabel = "Next",
|
|
590
|
+
dayStart = "09:00",
|
|
591
|
+
dayEnd = "18:00"
|
|
592
|
+
}) {
|
|
593
|
+
const {
|
|
594
|
+
appointmentMap,
|
|
595
|
+
dayMinutes,
|
|
596
|
+
dayStartAbs,
|
|
597
|
+
gridHeight,
|
|
598
|
+
gridMinWidth,
|
|
599
|
+
renderAppts
|
|
600
|
+
} = useSchedulerBaseData({
|
|
601
|
+
adapter,
|
|
602
|
+
appointments,
|
|
603
|
+
dayEnd,
|
|
604
|
+
dayStart,
|
|
605
|
+
resources,
|
|
606
|
+
selectedDate
|
|
607
|
+
});
|
|
608
|
+
const {
|
|
609
|
+
colRefs,
|
|
610
|
+
drag,
|
|
611
|
+
onApptPointerDown,
|
|
612
|
+
onGlobalPointerMove,
|
|
613
|
+
onGlobalPointerUp,
|
|
614
|
+
onResizePointerDown,
|
|
615
|
+
suppressClickRef
|
|
616
|
+
} = useSchedulerInteractions({
|
|
617
|
+
appointmentMap,
|
|
618
|
+
dayMinutes,
|
|
619
|
+
dayStartAbs,
|
|
620
|
+
onAppointmentChange,
|
|
621
|
+
onPersistMoveResize,
|
|
622
|
+
renderAppts,
|
|
623
|
+
resources,
|
|
624
|
+
selectedDate
|
|
625
|
+
});
|
|
626
|
+
const {
|
|
627
|
+
appointmentAppearanceByResource,
|
|
628
|
+
appointmentBgByResource,
|
|
629
|
+
laidOutByResource
|
|
630
|
+
} = useSchedulerPresentationData({
|
|
631
|
+
appointmentColorTokenClassMap,
|
|
632
|
+
dayStartAbs,
|
|
633
|
+
drag,
|
|
634
|
+
getResourceAppointmentAppearance,
|
|
635
|
+
getResourceAppointmentBackground,
|
|
636
|
+
getResourceAppointmentColorToken,
|
|
637
|
+
renderAppts,
|
|
638
|
+
resourceAppointmentClassMap,
|
|
639
|
+
resources,
|
|
640
|
+
selectedDate
|
|
641
|
+
});
|
|
642
|
+
const defaultResourceHeaderRenderer = (resource) => defaultRenderResourceHeader({ resource });
|
|
323
643
|
return /* @__PURE__ */ jsxs(
|
|
324
644
|
"section",
|
|
325
645
|
{
|
|
326
|
-
className: `rfs-root ${drag.kind === "move" ? "rfs-root--drag-move" : ""} ${drag.kind === "resize" ? "rfs-root--drag-resize" : ""}`,
|
|
646
|
+
className: `rfs-root rounded-lg bg-background text-foreground ${drag.kind === "move" ? "rfs-root--drag-move" : ""} ${drag.kind === "resize" ? "rfs-root--drag-resize" : ""}`,
|
|
327
647
|
onPointerMove: onGlobalPointerMove,
|
|
328
648
|
onPointerUp: onGlobalPointerUp,
|
|
329
649
|
onPointerCancel: onGlobalPointerUp,
|
|
330
650
|
children: [
|
|
331
651
|
/* @__PURE__ */ jsxs("div", { className: "rfs-toolbar", children: [
|
|
332
|
-
/* @__PURE__ */ jsx(
|
|
333
|
-
|
|
334
|
-
|
|
652
|
+
/* @__PURE__ */ jsx(
|
|
653
|
+
"button",
|
|
654
|
+
{
|
|
655
|
+
className: "rfs-btn border-border bg-card text-foreground hover:bg-muted",
|
|
656
|
+
type: "button",
|
|
657
|
+
onClick: () => onSelectedDateChange(shiftDays(selectedDate, -1)),
|
|
658
|
+
children: prevButtonLabel
|
|
659
|
+
}
|
|
660
|
+
),
|
|
661
|
+
(renderDatePicker ?? defaultRenderDatePicker)({ selectedDate, onSelectedDateChange }),
|
|
662
|
+
/* @__PURE__ */ jsx(
|
|
663
|
+
"button",
|
|
664
|
+
{
|
|
665
|
+
className: "rfs-btn border-border bg-card text-foreground hover:bg-muted",
|
|
666
|
+
type: "button",
|
|
667
|
+
onClick: () => onSelectedDateChange(shiftDays(selectedDate, 1)),
|
|
668
|
+
children: nextButtonLabel
|
|
669
|
+
}
|
|
670
|
+
)
|
|
335
671
|
] }),
|
|
336
|
-
/* @__PURE__ */ jsx("div", { className: "rfs-shell", children: /* @__PURE__ */ jsxs("div", { style: { minWidth: gridMinWidth }, children: [
|
|
337
|
-
/* @__PURE__ */ jsxs("div", { className: "rfs-header", children: [
|
|
338
|
-
/* @__PURE__ */ jsx("div", { className: "rfs-gutter", style: { width: GUTTER_W } }),
|
|
339
|
-
resources.map((resource) => /* @__PURE__ */ jsxs(
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
672
|
+
/* @__PURE__ */ jsx("div", { className: "rfs-shell border-border bg-card", children: /* @__PURE__ */ jsxs("div", { style: { minWidth: gridMinWidth }, children: [
|
|
673
|
+
/* @__PURE__ */ jsxs("div", { className: "rfs-header border-border bg-card", children: [
|
|
674
|
+
/* @__PURE__ */ jsx("div", { className: "rfs-gutter border-border", style: { width: GUTTER_W } }),
|
|
675
|
+
resources.map((resource) => /* @__PURE__ */ jsxs(
|
|
676
|
+
"div",
|
|
677
|
+
{
|
|
678
|
+
className: "rfs-resource-header border-border bg-card text-foreground",
|
|
679
|
+
style: { minWidth: RESOURCE_MIN_W, flex: 1 },
|
|
680
|
+
children: [
|
|
681
|
+
(renderResourceHeader ?? defaultResourceHeaderRenderer)(resource),
|
|
682
|
+
isNameLike(resource) ? /* @__PURE__ */ jsx("div", { className: "rfs-resource-subtitle text-muted-foreground", children: fullName(resource) }) : null
|
|
683
|
+
]
|
|
684
|
+
},
|
|
685
|
+
String(resource.id)
|
|
686
|
+
))
|
|
343
687
|
] }),
|
|
344
688
|
/* @__PURE__ */ jsxs("div", { className: "rfs-body", children: [
|
|
345
|
-
/* @__PURE__ */ jsx("div", { className: "rfs-times", style: { width: GUTTER_W, height: gridHeight }, children: Array.from({ length: Math.floor(dayMinutes / 60) + 1 }).map((_, i) => /* @__PURE__ */ jsxs(
|
|
346
|
-
String(Math.floor((dayStartAbs + i * 60) / 60)).padStart(2, "0"),
|
|
347
|
-
":",
|
|
348
|
-
String((dayStartAbs + i * 60) % 60).padStart(2, "0")
|
|
349
|
-
] }, `tick-${i}`)) }),
|
|
350
|
-
/* @__PURE__ */ jsx("div", { className: "rfs-grid", children: resources.map((resource) => /* @__PURE__ */ jsx("div", { className: "rfs-col", style: { minWidth: RESOURCE_MIN_W, flex: 1 }, children: /* @__PURE__ */ jsxs(
|
|
689
|
+
/* @__PURE__ */ jsx("div", { className: "rfs-times border-border bg-card", style: { width: GUTTER_W, height: gridHeight }, children: Array.from({ length: Math.floor(dayMinutes / 60) + 1 }).map((_, i) => /* @__PURE__ */ jsxs(
|
|
351
690
|
"div",
|
|
352
691
|
{
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
},
|
|
356
|
-
className: "rfs-col-inner",
|
|
357
|
-
style: { height: gridHeight },
|
|
692
|
+
className: "rfs-time-tick text-muted-foreground",
|
|
693
|
+
style: { top: TOP_PAD + i * 60 * PX_PER_MIN },
|
|
358
694
|
children: [
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
695
|
+
String(Math.floor((dayStartAbs + i * 60) / 60)).padStart(2, "0"),
|
|
696
|
+
":",
|
|
697
|
+
String((dayStartAbs + i * 60) % 60).padStart(2, "0")
|
|
698
|
+
]
|
|
699
|
+
},
|
|
700
|
+
`tick-${i}`
|
|
701
|
+
)) }),
|
|
702
|
+
/* @__PURE__ */ jsx("div", { className: "rfs-grid", children: resources.map((resource) => /* @__PURE__ */ jsx(
|
|
703
|
+
"div",
|
|
704
|
+
{
|
|
705
|
+
className: "rfs-col border-border bg-card",
|
|
706
|
+
style: { minWidth: RESOURCE_MIN_W, flex: 1 },
|
|
707
|
+
children: /* @__PURE__ */ jsxs(
|
|
708
|
+
"div",
|
|
709
|
+
{
|
|
710
|
+
ref: (el) => {
|
|
711
|
+
colRefs.current[String(resource.id)] = el;
|
|
372
712
|
},
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
"div",
|
|
382
|
-
{
|
|
383
|
-
className: "rfs-card-slot",
|
|
384
|
-
style: {
|
|
385
|
-
top,
|
|
386
|
-
left: `${laneWidthPct * appointment.lane}%`,
|
|
387
|
-
width: `${laneWidthPct}%`,
|
|
388
|
-
height
|
|
713
|
+
className: "rfs-col-inner bg-card",
|
|
714
|
+
style: { height: gridHeight },
|
|
715
|
+
children: [
|
|
716
|
+
Array.from({ length: Math.floor(dayMinutes / 60) + 1 }).map((_, i) => /* @__PURE__ */ jsx(
|
|
717
|
+
"div",
|
|
718
|
+
{
|
|
719
|
+
className: "rfs-hour-line border-border/70",
|
|
720
|
+
style: { top: TOP_PAD + i * 60 * PX_PER_MIN }
|
|
389
721
|
},
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
722
|
+
`major-${String(resource.id)}-${i}`
|
|
723
|
+
)),
|
|
724
|
+
Array.from({ length: Math.floor(dayMinutes / STEP_MIN) + 1 }).map((_, i) => /* @__PURE__ */ jsx(
|
|
725
|
+
"div",
|
|
726
|
+
{
|
|
727
|
+
className: "rfs-slot-line border-border/50",
|
|
728
|
+
style: { top: TOP_PAD + i * STEP_MIN * PX_PER_MIN }
|
|
729
|
+
},
|
|
730
|
+
`minor-${String(resource.id)}-${i}`
|
|
731
|
+
)),
|
|
732
|
+
(laidOutByResource.get(resource.id) ?? []).map((appointment) => {
|
|
733
|
+
const laneWidthPct = 100 / appointment.lanes;
|
|
734
|
+
const top = TOP_PAD + appointment.startMin * PX_PER_MIN;
|
|
735
|
+
const height = Math.max(MIN_EVENT_MIN * PX_PER_MIN, (appointment.endMin - appointment.startMin) * PX_PER_MIN);
|
|
736
|
+
const appointmentAppearance = appointmentAppearanceByResource.get(resource.id);
|
|
737
|
+
const appointmentBackgroundColor = appointmentBgByResource.get(resource.id);
|
|
738
|
+
return /* @__PURE__ */ jsx(
|
|
739
|
+
"div",
|
|
740
|
+
{
|
|
741
|
+
className: "rfs-card-slot",
|
|
742
|
+
style: {
|
|
743
|
+
top,
|
|
744
|
+
left: `${laneWidthPct * appointment.lane}%`,
|
|
745
|
+
width: `${laneWidthPct}%`,
|
|
746
|
+
height
|
|
747
|
+
},
|
|
748
|
+
children: (renderAppointment ?? defaultRenderAppointment)({
|
|
749
|
+
appointment,
|
|
750
|
+
onPointerDown: (event) => onApptPointerDown(event, appointment),
|
|
751
|
+
onResizePointerDown: (event) => onResizePointerDown(event, appointment),
|
|
752
|
+
appointmentAppearance,
|
|
753
|
+
appointmentBackgroundColor,
|
|
754
|
+
drag,
|
|
755
|
+
suppressClickRef
|
|
756
|
+
})
|
|
757
|
+
},
|
|
758
|
+
String(appointment.id)
|
|
759
|
+
);
|
|
760
|
+
})
|
|
761
|
+
]
|
|
762
|
+
}
|
|
763
|
+
)
|
|
764
|
+
},
|
|
765
|
+
String(resource.id)
|
|
766
|
+
)) })
|
|
405
767
|
] })
|
|
406
768
|
] }) })
|
|
407
769
|
]
|
|
@@ -409,6 +771,18 @@ function Scheduler({
|
|
|
409
771
|
);
|
|
410
772
|
}
|
|
411
773
|
|
|
412
|
-
|
|
774
|
+
// src/utils/scheduler-appointment-change.utils.ts
|
|
775
|
+
function applySchedulerAppointmentChange(appointments, change, options) {
|
|
776
|
+
return appointments.map((item) => {
|
|
777
|
+
if (options.getId(item) !== options.getId(change.appointment)) {
|
|
778
|
+
return item;
|
|
779
|
+
}
|
|
780
|
+
const withResource = options.setResourceId(item, change.next.resourceId);
|
|
781
|
+
const withStart = options.setStart(withResource, change.next.start);
|
|
782
|
+
return options.setEnd(withStart, change.next.end);
|
|
783
|
+
});
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
export { Scheduler, applySchedulerAppointmentChange };
|
|
413
787
|
//# sourceMappingURL=index.js.map
|
|
414
788
|
//# sourceMappingURL=index.js.map
|