@marco.colia/react-fast-scheduler 2.0.0 → 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/LICENSE +22 -22
- package/README.md +251 -251
- package/dist/index.cjs +543 -357
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +542 -356
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +86 -86
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,311 +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
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
+
);
|
|
113
195
|
}
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
const year = Number(yearRaw);
|
|
117
|
-
const month = Number(monthRaw);
|
|
118
|
-
const day = Number(dayRaw);
|
|
119
|
-
if (!Number.isInteger(year) || !Number.isInteger(month) || !Number.isInteger(day)) {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
122
|
-
return new Date(year, month - 1, day);
|
|
196
|
+
function defaultRenderResourceHeader({ resource }) {
|
|
197
|
+
return /* @__PURE__ */ jsx("strong", { children: resource.label });
|
|
123
198
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
stone: "bg-stone-100 dark:bg-stone-900/40",
|
|
130
|
-
red: "bg-red-100 dark:bg-red-950/40",
|
|
131
|
-
orange: "bg-orange-100 dark:bg-orange-950/40",
|
|
132
|
-
amber: "bg-amber-100 dark:bg-amber-950/40",
|
|
133
|
-
yellow: "bg-yellow-100 dark:bg-yellow-950/40",
|
|
134
|
-
lime: "bg-lime-100 dark:bg-lime-950/40",
|
|
135
|
-
green: "bg-green-100 dark:bg-green-950/40",
|
|
136
|
-
emerald: "bg-emerald-100 dark:bg-emerald-950/40",
|
|
137
|
-
teal: "bg-teal-100 dark:bg-teal-950/40",
|
|
138
|
-
cyan: "bg-cyan-100 dark:bg-cyan-950/40",
|
|
139
|
-
sky: "bg-sky-100 dark:bg-sky-950/40",
|
|
140
|
-
blue: "bg-blue-100 dark:bg-blue-950/40",
|
|
141
|
-
indigo: "bg-indigo-100 dark:bg-indigo-950/40",
|
|
142
|
-
violet: "bg-violet-100 dark:bg-violet-950/40",
|
|
143
|
-
purple: "bg-purple-100 dark:bg-purple-950/40",
|
|
144
|
-
fuchsia: "bg-fuchsia-100 dark:bg-fuchsia-950/40",
|
|
145
|
-
pink: "bg-pink-100 dark:bg-pink-950/40",
|
|
146
|
-
rose: "bg-rose-100 dark:bg-rose-950/40"
|
|
147
|
-
};
|
|
148
|
-
function Scheduler({
|
|
199
|
+
|
|
200
|
+
// src/utils/scheduler-data.utils.ts
|
|
201
|
+
function buildResourceMap({
|
|
202
|
+
fallback,
|
|
203
|
+
getValue,
|
|
149
204
|
resources,
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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({
|
|
153
214
|
adapter,
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
resourceAppointmentClassMap,
|
|
159
|
-
getResourceAppointmentAppearance,
|
|
160
|
-
getResourceAppointmentColorToken,
|
|
161
|
-
appointmentColorTokenClassMap,
|
|
162
|
-
getResourceAppointmentBackground,
|
|
163
|
-
renderDatePicker,
|
|
164
|
-
prevButtonLabel = "Prev",
|
|
165
|
-
nextButtonLabel = "Next",
|
|
166
|
-
dayStart = "09:00",
|
|
167
|
-
dayEnd = "18:00"
|
|
215
|
+
appointments,
|
|
216
|
+
dayMinutes,
|
|
217
|
+
dayStartAbs,
|
|
218
|
+
selectedISO
|
|
168
219
|
}) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const appointmentMap = React.useMemo(() => {
|
|
195
|
-
return new Map(renderAppts.map((appointment) => [appointment.id, appointment]));
|
|
196
|
-
}, [renderAppts]);
|
|
197
|
-
const effectiveAppts = React.useMemo(() => {
|
|
198
|
-
if (drag.kind === "none") {
|
|
199
|
-
return renderAppts;
|
|
200
|
-
}
|
|
201
|
-
return renderAppts.map((appointment) => {
|
|
202
|
-
if (appointment.id !== drag.appointmentId) {
|
|
203
|
-
return appointment;
|
|
204
|
-
}
|
|
205
|
-
return {
|
|
206
|
-
...appointment,
|
|
207
|
-
resourceId: drag.resourceId,
|
|
208
|
-
startMin: drag.startMin,
|
|
209
|
-
endMin: drag.endMin,
|
|
210
|
-
start: dateAtMinute(selectedDate, dayStartAbs, drag.startMin),
|
|
211
|
-
end: dateAtMinute(selectedDate, dayStartAbs, drag.endMin)
|
|
212
|
-
};
|
|
213
|
-
});
|
|
214
|
-
}, [drag, renderAppts, selectedDate, dayStartAbs]);
|
|
215
|
-
const laidOutByResource = React.useMemo(() => {
|
|
216
|
-
const byResource = /* @__PURE__ */ new Map();
|
|
217
|
-
for (const resource of resources) {
|
|
218
|
-
const events = effectiveAppts.filter((appointment) => appointment.resourceId === resource.id);
|
|
219
|
-
byResource.set(resource.id, layoutOverlaps(events));
|
|
220
|
-
}
|
|
221
|
-
return byResource;
|
|
222
|
-
}, [effectiveAppts, resources]);
|
|
223
|
-
const appointmentBgByResource = React.useMemo(() => {
|
|
224
|
-
const map = /* @__PURE__ */ new Map();
|
|
225
|
-
if (!getResourceAppointmentBackground) {
|
|
226
|
-
return map;
|
|
227
|
-
}
|
|
228
|
-
for (const resource of resources) {
|
|
229
|
-
map.set(resource.id, getResourceAppointmentBackground(resource));
|
|
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;
|
|
230
245
|
}
|
|
231
|
-
return
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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)
|
|
238
253
|
};
|
|
239
|
-
|
|
240
|
-
|
|
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;
|
|
241
276
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
map.set(resource.id,
|
|
277
|
+
const appearance = getResourceAppointmentAppearance?.(resource);
|
|
278
|
+
if (appearance) {
|
|
279
|
+
map.set(resource.id, appearance);
|
|
280
|
+
continue;
|
|
245
281
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
282
|
+
const tokenClass = appointmentColorTokenClassByResource.get(resource.id);
|
|
283
|
+
if (tokenClass) {
|
|
284
|
+
map.set(resource.id, { className: tokenClass });
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
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
|
|
326
|
+
};
|
|
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)];
|
|
344
|
+
if (!col) {
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
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
|
+
}
|
|
255
363
|
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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;
|
|
260
380
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
map.set(resource.id, { className: tokenClass });
|
|
264
|
-
continue;
|
|
381
|
+
if (hasOverlap(state.appointmentId, state.resourceId, state.startMin, state.endMin)) {
|
|
382
|
+
return;
|
|
265
383
|
}
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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;
|
|
270
401
|
}
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
return 0;
|
|
279
|
-
}
|
|
280
|
-
const y = clientY - col.getBoundingClientRect().top - TOP_PAD;
|
|
281
|
-
return clamp(snap(y / PX_PER_MIN), 0, dayMinutes);
|
|
282
|
-
};
|
|
283
|
-
const pointerToResourceId = (clientX, fallbackResourceId) => {
|
|
284
|
-
for (const resource of resources) {
|
|
285
|
-
const col = colRefs.current[String(resource.id)];
|
|
286
|
-
if (!col) {
|
|
287
|
-
continue;
|
|
402
|
+
if (onPersistMoveResize) {
|
|
403
|
+
await onPersistMoveResize({
|
|
404
|
+
appointment: target.raw,
|
|
405
|
+
resourceId: next.resourceId,
|
|
406
|
+
start: next.start,
|
|
407
|
+
end: next.end
|
|
408
|
+
});
|
|
288
409
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
410
|
+
},
|
|
411
|
+
[appointmentMap, dayStartAbs, hasOverlap, onAppointmentChange, onPersistMoveResize, selectedDate]
|
|
412
|
+
);
|
|
413
|
+
const onApptPointerDown = React2.useCallback(
|
|
414
|
+
(event, appointment) => {
|
|
415
|
+
if (event.button !== 0) {
|
|
416
|
+
return;
|
|
292
417
|
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
if (hasOverlap(state.appointmentId, state.resourceId, state.startMin, state.endMin)) {
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
const next = {
|
|
309
|
-
resourceId: state.resourceId,
|
|
310
|
-
start: dateAtMinute(selectedDate, dayStartAbs, state.startMin),
|
|
311
|
-
end: dateAtMinute(selectedDate, dayStartAbs, state.endMin)
|
|
312
|
-
};
|
|
313
|
-
if (onAppointmentChange) {
|
|
314
|
-
await onAppointmentChange({
|
|
315
|
-
kind: state.kind,
|
|
316
|
-
appointment: target.raw,
|
|
317
|
-
previous: {
|
|
318
|
-
resourceId: target.resourceId,
|
|
319
|
-
start: target.start,
|
|
320
|
-
end: target.end
|
|
321
|
-
},
|
|
322
|
-
next
|
|
323
|
-
});
|
|
324
|
-
return;
|
|
325
|
-
}
|
|
326
|
-
if (onPersistMoveResize) {
|
|
327
|
-
await onPersistMoveResize({
|
|
328
|
-
appointment: target.raw,
|
|
329
|
-
resourceId: next.resourceId,
|
|
330
|
-
start: next.start,
|
|
331
|
-
end: next.end
|
|
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
|
|
332
430
|
});
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
event.stopPropagation();
|
|
340
|
-
suppressClickRef.current = false;
|
|
341
|
-
const pointerMin = pointerToMin(appointment.resourceId, event.clientY);
|
|
342
|
-
setDrag({
|
|
343
|
-
kind: "move",
|
|
344
|
-
appointmentId: appointment.id,
|
|
345
|
-
pointerId: event.pointerId,
|
|
346
|
-
resourceId: appointment.resourceId,
|
|
347
|
-
durationMin: Math.max(MIN_EVENT_MIN, appointment.endMin - appointment.startMin),
|
|
348
|
-
offsetMin: pointerMin - appointment.startMin,
|
|
349
|
-
startMin: appointment.startMin,
|
|
350
|
-
endMin: appointment.endMin
|
|
351
|
-
});
|
|
352
|
-
};
|
|
353
|
-
const onResizePointerDown = (event, appointment) => {
|
|
431
|
+
},
|
|
432
|
+
[pointerToMin]
|
|
433
|
+
);
|
|
434
|
+
const onResizePointerDown = React2.useCallback((event, appointment) => {
|
|
354
435
|
if (event.button !== 0) {
|
|
355
436
|
return;
|
|
356
437
|
}
|
|
@@ -364,39 +445,42 @@ function Scheduler({
|
|
|
364
445
|
startMin: appointment.startMin,
|
|
365
446
|
endMin: appointment.endMin
|
|
366
447
|
});
|
|
367
|
-
};
|
|
368
|
-
const onGlobalPointerMove = (
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
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) {
|
|
379
474
|
suppressClickRef.current = true;
|
|
380
475
|
setDrag({
|
|
381
476
|
...drag,
|
|
382
|
-
|
|
383
|
-
startMin: nextStartMin,
|
|
384
|
-
endMin: nextEndMin2
|
|
477
|
+
endMin: nextEndMin
|
|
385
478
|
});
|
|
386
479
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
if (nextEndMin !== drag.endMin) {
|
|
392
|
-
suppressClickRef.current = true;
|
|
393
|
-
setDrag({
|
|
394
|
-
...drag,
|
|
395
|
-
endMin: nextEndMin
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
const onGlobalPointerUp = () => {
|
|
480
|
+
},
|
|
481
|
+
[dayMinutes, drag, pointerToMin, pointerToResourceId]
|
|
482
|
+
);
|
|
483
|
+
const onGlobalPointerUp = React2.useCallback(() => {
|
|
400
484
|
if (drag.kind === "none") {
|
|
401
485
|
return;
|
|
402
486
|
}
|
|
@@ -405,55 +489,157 @@ function Scheduler({
|
|
|
405
489
|
if (suppressClickRef.current) {
|
|
406
490
|
void persistDrag(current);
|
|
407
491
|
}
|
|
492
|
+
}, [drag, persistDrag]);
|
|
493
|
+
return {
|
|
494
|
+
colRefs,
|
|
495
|
+
drag,
|
|
496
|
+
onApptPointerDown,
|
|
497
|
+
onGlobalPointerMove,
|
|
498
|
+
onGlobalPointerUp,
|
|
499
|
+
onResizePointerDown,
|
|
500
|
+
suppressClickRef
|
|
408
501
|
};
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
onResizePointerDown2(event);
|
|
432
|
-
},
|
|
433
|
-
className: "absolute inset-x-1 bottom-1 h-2 cursor-ns-resize rounded-full bg-border/90 transition-colors hover:bg-border"
|
|
434
|
-
}
|
|
435
|
-
)
|
|
436
|
-
] });
|
|
437
|
-
};
|
|
438
|
-
const defaultRenderDatePicker = ({
|
|
439
|
-
selectedDate: value,
|
|
440
|
-
onSelectedDateChange: onDateChange
|
|
441
|
-
}) => /* @__PURE__ */ jsx(
|
|
442
|
-
"input",
|
|
443
|
-
{
|
|
444
|
-
type: "date",
|
|
445
|
-
value: toInputDateValue(value),
|
|
446
|
-
onChange: (event) => {
|
|
447
|
-
const nextDate = fromInputDateValue(event.target.value);
|
|
448
|
-
if (nextDate) {
|
|
449
|
-
onDateChange(nextDate);
|
|
450
|
-
}
|
|
451
|
-
},
|
|
452
|
-
className: "h-7 rounded-md border border-border bg-card px-2 text-xs text-foreground",
|
|
453
|
-
"aria-label": "Select date"
|
|
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();
|
|
454
524
|
}
|
|
455
|
-
|
|
456
|
-
|
|
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 });
|
|
457
643
|
return /* @__PURE__ */ jsxs(
|
|
458
644
|
"section",
|
|
459
645
|
{
|
|
@@ -492,7 +678,7 @@ function Scheduler({
|
|
|
492
678
|
className: "rfs-resource-header border-border bg-card text-foreground",
|
|
493
679
|
style: { minWidth: RESOURCE_MIN_W, flex: 1 },
|
|
494
680
|
children: [
|
|
495
|
-
(renderResourceHeader ??
|
|
681
|
+
(renderResourceHeader ?? defaultResourceHeaderRenderer)(resource),
|
|
496
682
|
isNameLike(resource) ? /* @__PURE__ */ jsx("div", { className: "rfs-resource-subtitle text-muted-foreground", children: fullName(resource) }) : null
|
|
497
683
|
]
|
|
498
684
|
},
|
|
@@ -585,7 +771,7 @@ function Scheduler({
|
|
|
585
771
|
);
|
|
586
772
|
}
|
|
587
773
|
|
|
588
|
-
// src/appointment-change.ts
|
|
774
|
+
// src/utils/scheduler-appointment-change.utils.ts
|
|
589
775
|
function applySchedulerAppointmentChange(appointments, change, options) {
|
|
590
776
|
return appointments.map((item) => {
|
|
591
777
|
if (options.getId(item) !== options.getId(change.appointment)) {
|