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