calendarkit-pro 1.2.1 → 1.3.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 +24 -2
- package/dist/index.d.mts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1024 -712
- package/dist/index.mjs +1001 -686
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/index.tsx
|
|
4
|
-
import React18, { useId, useState as useState6, useMemo as useMemo4 } from "react";
|
|
4
|
+
import React18, { useCallback as useCallback5, useId, useState as useState6, useMemo as useMemo4 } from "react";
|
|
5
5
|
import { DndContext, useSensor, useSensors, PointerSensor, DragOverlay } from "@dnd-kit/core";
|
|
6
6
|
import { createSnapModifier, restrictToWindowEdges } from "@dnd-kit/modifiers";
|
|
7
7
|
import { AnimatePresence as AnimatePresence3, motion as motion4 } from "framer-motion";
|
|
@@ -164,21 +164,26 @@ import { Plus, ChevronDown, Globe } from "lucide-react";
|
|
|
164
164
|
|
|
165
165
|
// src/components/MiniCalendar.tsx
|
|
166
166
|
import React3 from "react";
|
|
167
|
-
import { format as
|
|
167
|
+
import { format as format2, isSameMonth, isSameDay, isToday, addMonths, subMonths } from "date-fns";
|
|
168
168
|
|
|
169
|
-
// src/
|
|
170
|
-
import {
|
|
171
|
-
|
|
169
|
+
// src/core/week.ts
|
|
170
|
+
import { eachDayOfInterval, endOfMonth, endOfWeek, startOfMonth, startOfWeek } from "date-fns";
|
|
171
|
+
function resolveWeekStartsOn(locale, fallback) {
|
|
172
|
+
return locale?.options?.weekStartsOn ?? fallback;
|
|
173
|
+
}
|
|
174
|
+
function getWeekDays(date, weekStartsOn) {
|
|
175
|
+
return eachDayOfInterval({
|
|
176
|
+
start: startOfWeek(date, { weekStartsOn }),
|
|
177
|
+
end: endOfWeek(date, { weekStartsOn })
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function getMonthGrid(date, weekStartsOn) {
|
|
172
181
|
const monthStart = startOfMonth(date);
|
|
173
182
|
return eachDayOfInterval({
|
|
174
|
-
start: startOfWeek(monthStart, { weekStartsOn
|
|
175
|
-
end: endOfWeek(endOfMonth(monthStart), { weekStartsOn
|
|
183
|
+
start: startOfWeek(monthStart, { weekStartsOn }),
|
|
184
|
+
end: endOfWeek(endOfMonth(monthStart), { weekStartsOn })
|
|
176
185
|
});
|
|
177
|
-
}
|
|
178
|
-
var uses24HourClock = (locale) => {
|
|
179
|
-
if (!locale) return false;
|
|
180
|
-
return !/[ap]\.?m/i.test(format2(new Date(2e3, 0, 1, 13, 0), "p", { locale }));
|
|
181
|
-
};
|
|
186
|
+
}
|
|
182
187
|
|
|
183
188
|
// src/components/MiniCalendar.tsx
|
|
184
189
|
import { ChevronLeft as ChevronLeft2, ChevronRight as ChevronRight2 } from "lucide-react";
|
|
@@ -193,7 +198,7 @@ var MiniCalendar = ({
|
|
|
193
198
|
React3.useEffect(() => {
|
|
194
199
|
setViewDate(currentDate);
|
|
195
200
|
}, [currentDate]);
|
|
196
|
-
const weekStartsOn = locale
|
|
201
|
+
const weekStartsOn = resolveWeekStartsOn(locale, 0);
|
|
197
202
|
const days = React3.useMemo(
|
|
198
203
|
() => getMonthGrid(viewDate, weekStartsOn),
|
|
199
204
|
[viewDate, weekStartsOn]
|
|
@@ -218,7 +223,7 @@ var MiniCalendar = ({
|
|
|
218
223
|
onDateChange(day);
|
|
219
224
|
if (onViewChange) onViewChange("day");
|
|
220
225
|
};
|
|
221
|
-
return /* @__PURE__ */ React3.createElement("div", { className: cn("px-4 w-[260px]", className) }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center justify-between mb-4" }, /* @__PURE__ */ React3.createElement("span", { className: "text-sm font-semibold text-foreground capitalize" },
|
|
226
|
+
return /* @__PURE__ */ React3.createElement("div", { className: cn("px-4 w-[260px]", className) }, /* @__PURE__ */ React3.createElement("div", { className: "flex items-center justify-between mb-4" }, /* @__PURE__ */ React3.createElement("span", { className: "text-sm font-semibold text-foreground capitalize" }, format2(viewDate, "MMMM yyyy", { locale })), /* @__PURE__ */ React3.createElement("div", { className: "flex items-center bg-muted/40 rounded-lg p-0.5" }, /* @__PURE__ */ React3.createElement(
|
|
222
227
|
Button,
|
|
223
228
|
{
|
|
224
229
|
variant: "ghost",
|
|
@@ -255,7 +260,7 @@ var MiniCalendar = ({
|
|
|
255
260
|
!isSelected && isTodayDate && "bg-primary/10 text-primary ring-1 ring-primary/30"
|
|
256
261
|
)
|
|
257
262
|
},
|
|
258
|
-
|
|
263
|
+
format2(day, "d")
|
|
259
264
|
);
|
|
260
265
|
})));
|
|
261
266
|
};
|
|
@@ -263,6 +268,194 @@ var MiniCalendar = ({
|
|
|
263
268
|
// src/components/Sidebar.tsx
|
|
264
269
|
import { toZonedTime } from "date-fns-tz";
|
|
265
270
|
import { format as format4 } from "date-fns";
|
|
271
|
+
|
|
272
|
+
// src/core/format.ts
|
|
273
|
+
import { format as format3 } from "date-fns";
|
|
274
|
+
|
|
275
|
+
// src/locales/index.ts
|
|
276
|
+
var en = {
|
|
277
|
+
today: "Today",
|
|
278
|
+
month: "Month",
|
|
279
|
+
week: "Week",
|
|
280
|
+
day: "Day",
|
|
281
|
+
agenda: "Agenda",
|
|
282
|
+
resource: "Resources",
|
|
283
|
+
createEvent: "Create Event",
|
|
284
|
+
editEvent: "Edit Event",
|
|
285
|
+
delete: "Delete",
|
|
286
|
+
save: "Save",
|
|
287
|
+
cancel: "Cancel",
|
|
288
|
+
title: "Add title",
|
|
289
|
+
start: "Start",
|
|
290
|
+
end: "End",
|
|
291
|
+
allDay: "All Day",
|
|
292
|
+
description: "Description",
|
|
293
|
+
repeat: "Repeat",
|
|
294
|
+
noRepeat: "Does not repeat",
|
|
295
|
+
selectCalendar: "Select Calendar",
|
|
296
|
+
selectType: "Select Type",
|
|
297
|
+
daily: "Daily",
|
|
298
|
+
weekly: "Weekly",
|
|
299
|
+
monthly: "Monthly",
|
|
300
|
+
yearly: "Yearly",
|
|
301
|
+
search: "Search events...",
|
|
302
|
+
allTypes: "All Types",
|
|
303
|
+
timezone: "Time zone",
|
|
304
|
+
localTime: "Local Time",
|
|
305
|
+
create: "Create",
|
|
306
|
+
calendars: "Calendars",
|
|
307
|
+
event: "Event",
|
|
308
|
+
task: "Task",
|
|
309
|
+
appointmentSchedule: "Appointment schedule",
|
|
310
|
+
new: "New",
|
|
311
|
+
dateAndTime: "Date & Time",
|
|
312
|
+
whosJoining: "Who's joining?",
|
|
313
|
+
suggestedTimes: "Suggested times for guests",
|
|
314
|
+
viewSuggestions: "View suggestions",
|
|
315
|
+
whereWillItBe: "Where will it be?",
|
|
316
|
+
location: "Enter address",
|
|
317
|
+
descriptionAndAttachments: "Description",
|
|
318
|
+
dragAndDrop: "Drag & drop files or",
|
|
319
|
+
guests: "Guests",
|
|
320
|
+
addGuests: "Add guests",
|
|
321
|
+
addAttachment: "Upload files",
|
|
322
|
+
moreOptions: "More options",
|
|
323
|
+
doesNotRepeat: "Does not repeat",
|
|
324
|
+
locationHelpText: 'Enter a physical address (e.g. "123 Main St") to generate the map.',
|
|
325
|
+
notes: "Notes...",
|
|
326
|
+
guestCount: "guest",
|
|
327
|
+
guestsCount: "guests",
|
|
328
|
+
repeatFor: "Repeat for",
|
|
329
|
+
times: "times",
|
|
330
|
+
until: "Until",
|
|
331
|
+
endRepeat: "End repeat",
|
|
332
|
+
never: "Never",
|
|
333
|
+
afterOccurrences: "After occurrences",
|
|
334
|
+
onDate: "On date",
|
|
335
|
+
tomorrow: "Tomorrow",
|
|
336
|
+
edit: "Edit",
|
|
337
|
+
duplicate: "Duplicate",
|
|
338
|
+
eventSingular: "event",
|
|
339
|
+
eventPlural: "events",
|
|
340
|
+
allCaughtUp: "All caught up!",
|
|
341
|
+
noEventsScheduled: "No events scheduled",
|
|
342
|
+
noUpcoming: "Your schedule is clear",
|
|
343
|
+
noUpcomingHint: "No upcoming events to show. Create a new event to start planning."
|
|
344
|
+
};
|
|
345
|
+
var fr = {
|
|
346
|
+
today: "Aujourd'hui",
|
|
347
|
+
month: "Mois",
|
|
348
|
+
week: "Semaine",
|
|
349
|
+
day: "Jour",
|
|
350
|
+
agenda: "Agenda",
|
|
351
|
+
resource: "Ressources",
|
|
352
|
+
createEvent: "Cr\xE9er un \xE9v\xE9nement",
|
|
353
|
+
editEvent: "Modifier l'\xE9v\xE9nement",
|
|
354
|
+
delete: "Supprimer",
|
|
355
|
+
save: "Enregistrer",
|
|
356
|
+
cancel: "Annuler",
|
|
357
|
+
title: "Ajouter un titre",
|
|
358
|
+
start: "D\xE9but",
|
|
359
|
+
end: "Fin",
|
|
360
|
+
allDay: "Toute la journ\xE9e",
|
|
361
|
+
description: "Description",
|
|
362
|
+
repeat: "R\xE9p\xE9ter",
|
|
363
|
+
noRepeat: "Ne se r\xE9p\xE8te pas",
|
|
364
|
+
selectCalendar: "S\xE9lectionner un calendrier",
|
|
365
|
+
selectType: "S\xE9lectionner un type",
|
|
366
|
+
daily: "Quotidien",
|
|
367
|
+
weekly: "Hebdomadaire",
|
|
368
|
+
monthly: "Mensuel",
|
|
369
|
+
yearly: "Annuel",
|
|
370
|
+
search: "Rechercher...",
|
|
371
|
+
allTypes: "Tous les types",
|
|
372
|
+
timezone: "Fuseau horaire",
|
|
373
|
+
localTime: "Heure locale",
|
|
374
|
+
create: "Cr\xE9er",
|
|
375
|
+
calendars: "Calendriers",
|
|
376
|
+
event: "\xC9v\xE9nement",
|
|
377
|
+
task: "T\xE2che",
|
|
378
|
+
appointmentSchedule: "Planning de rendez-vous",
|
|
379
|
+
new: "Nouveau",
|
|
380
|
+
dateAndTime: "Date et heure",
|
|
381
|
+
whosJoining: "Qui participe ?",
|
|
382
|
+
suggestedTimes: "Horaires sugg\xE9r\xE9s",
|
|
383
|
+
viewSuggestions: "Voir les suggestions",
|
|
384
|
+
whereWillItBe: "O\xF9 aura lieu ?",
|
|
385
|
+
location: "Entrer une adresse",
|
|
386
|
+
descriptionAndAttachments: "Description",
|
|
387
|
+
dragAndDrop: "Glisser-d\xE9poser des fichiers ou",
|
|
388
|
+
guests: "Invit\xE9s",
|
|
389
|
+
addGuests: "Ajouter des invit\xE9s",
|
|
390
|
+
addAttachment: "T\xE9l\xE9charger des fichiers",
|
|
391
|
+
moreOptions: "Plus d'options",
|
|
392
|
+
doesNotRepeat: "Ne se r\xE9p\xE8te pas",
|
|
393
|
+
locationHelpText: 'Entrez une adresse physique (ex: "123 Rue Principale") pour g\xE9n\xE9rer la carte.',
|
|
394
|
+
notes: "Notes...",
|
|
395
|
+
guestCount: "invit\xE9",
|
|
396
|
+
guestsCount: "invit\xE9s",
|
|
397
|
+
repeatFor: "R\xE9p\xE9ter pendant",
|
|
398
|
+
times: "fois",
|
|
399
|
+
until: "Jusqu'au",
|
|
400
|
+
endRepeat: "Fin de r\xE9p\xE9tition",
|
|
401
|
+
never: "Jamais",
|
|
402
|
+
afterOccurrences: "Apr\xE8s occurrences",
|
|
403
|
+
onDate: "\xC0 la date",
|
|
404
|
+
tomorrow: "Demain",
|
|
405
|
+
edit: "Modifier",
|
|
406
|
+
duplicate: "Dupliquer",
|
|
407
|
+
eventSingular: "\xE9v\xE9nement",
|
|
408
|
+
eventPlural: "\xE9v\xE9nements",
|
|
409
|
+
allCaughtUp: "Tout est \xE0 jour !",
|
|
410
|
+
noEventsScheduled: "Aucun \xE9v\xE9nement pr\xE9vu",
|
|
411
|
+
noUpcoming: "Votre planning est libre",
|
|
412
|
+
noUpcomingHint: "Aucun \xE9v\xE9nement \xE0 venir. Cr\xE9ez un nouvel \xE9v\xE9nement pour commencer \xE0 planifier."
|
|
413
|
+
};
|
|
414
|
+
|
|
415
|
+
// src/core/format.ts
|
|
416
|
+
var clockCache = /* @__PURE__ */ new WeakMap();
|
|
417
|
+
function uses24HourClock(locale) {
|
|
418
|
+
if (!locale) return false;
|
|
419
|
+
let is24 = clockCache.get(locale);
|
|
420
|
+
if (is24 === void 0) {
|
|
421
|
+
is24 = !/[ap]\.?m/i.test(format3(new Date(2e3, 0, 1, 13, 0), "p", { locale }));
|
|
422
|
+
clockCache.set(locale, is24);
|
|
423
|
+
}
|
|
424
|
+
return is24;
|
|
425
|
+
}
|
|
426
|
+
var H24 = { hour: "H:mm", time: "H:mm", compact: "H:mm" };
|
|
427
|
+
var H12 = { hour: "h a", time: "h:mm a", compact: "h:mm" };
|
|
428
|
+
function timeFormats(locale) {
|
|
429
|
+
return uses24HourClock(locale) ? H24 : H12;
|
|
430
|
+
}
|
|
431
|
+
var MAX_FORMATTERS = 50;
|
|
432
|
+
var formatters = /* @__PURE__ */ new Map();
|
|
433
|
+
function getDateTimeFormat(locale, timeZone, options) {
|
|
434
|
+
const key = `${locale}|${timeZone ?? ""}|${JSON.stringify(options)}`;
|
|
435
|
+
let formatter = formatters.get(key);
|
|
436
|
+
if (formatter) {
|
|
437
|
+
formatters.delete(key);
|
|
438
|
+
} else {
|
|
439
|
+
formatter = new Intl.DateTimeFormat(locale, { ...options, timeZone });
|
|
440
|
+
if (formatters.size >= MAX_FORMATTERS) {
|
|
441
|
+
formatters.delete(formatters.keys().next().value);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
formatters.set(key, formatter);
|
|
445
|
+
return formatter;
|
|
446
|
+
}
|
|
447
|
+
function timeZoneAbbreviation(date, timeZone) {
|
|
448
|
+
try {
|
|
449
|
+
return getDateTimeFormat("en-US", timeZone, { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value ?? "";
|
|
450
|
+
} catch {
|
|
451
|
+
return "";
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function resolveTranslations(language, overrides) {
|
|
455
|
+
return { ...language === "fr" ? fr : en, ...overrides };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// src/components/Sidebar.tsx
|
|
266
459
|
var Sidebar = ({
|
|
267
460
|
currentDate,
|
|
268
461
|
onDateChange,
|
|
@@ -293,14 +486,7 @@ var Sidebar = ({
|
|
|
293
486
|
{ id: "3", label: "Tasks", color: "#6366f1", active: true }
|
|
294
487
|
];
|
|
295
488
|
const displayCalendars = calendars || defaultCalendars;
|
|
296
|
-
const getAcronym = (tz) =>
|
|
297
|
-
if (!tz || !now) return "LOC";
|
|
298
|
-
try {
|
|
299
|
-
return new Intl.DateTimeFormat("en-US", { timeZone: tz, timeZoneName: "short" }).formatToParts(now).find((part) => part.type === "timeZoneName")?.value || "";
|
|
300
|
-
} catch {
|
|
301
|
-
return "";
|
|
302
|
-
}
|
|
303
|
-
};
|
|
489
|
+
const getAcronym = (tz) => !tz || !now ? "LOC" : timeZoneAbbreviation(now, tz);
|
|
304
490
|
const timezones = [
|
|
305
491
|
{ value: "", label: translations?.localTime || "Local Time", acronym: "LOC" },
|
|
306
492
|
{ value: "UTC", label: "UTC", acronym: "UTC" },
|
|
@@ -325,10 +511,7 @@ var Sidebar = ({
|
|
|
325
511
|
try {
|
|
326
512
|
if (!tz.value) {
|
|
327
513
|
time = format4(now, "HH:mm");
|
|
328
|
-
|
|
329
|
-
acronym = new Intl.DateTimeFormat("en-US", { timeZoneName: "short" }).formatToParts(now).find((part) => part.type === "timeZoneName")?.value || "LOC";
|
|
330
|
-
} catch {
|
|
331
|
-
}
|
|
514
|
+
acronym = timeZoneAbbreviation(now) || "LOC";
|
|
332
515
|
} else {
|
|
333
516
|
const zDate = toZonedTime(now, tz.value);
|
|
334
517
|
time = format4(zDate, "HH:mm");
|
|
@@ -423,32 +606,198 @@ var Sidebar = ({
|
|
|
423
606
|
};
|
|
424
607
|
|
|
425
608
|
// src/views/MonthView.tsx
|
|
426
|
-
import React7, { useMemo
|
|
427
|
-
import { format as
|
|
428
|
-
|
|
609
|
+
import React7, { useMemo } from "react";
|
|
610
|
+
import { format as format7, isSameDay as isSameDay3, isSameMonth as isSameMonth2 } from "date-fns";
|
|
611
|
+
|
|
612
|
+
// src/core/events.ts
|
|
613
|
+
import { addDays, format as format6, startOfDay as startOfDay2 } from "date-fns";
|
|
614
|
+
|
|
615
|
+
// src/core/timezone.ts
|
|
616
|
+
import { format as format5, isSameDay as isSameDay2, startOfDay } from "date-fns";
|
|
617
|
+
import { fromZonedTime, toZonedTime as toZonedTime2 } from "date-fns-tz";
|
|
618
|
+
|
|
619
|
+
// src/core/warn.ts
|
|
620
|
+
var warned = /* @__PURE__ */ new Set();
|
|
621
|
+
function isProductionBuild() {
|
|
622
|
+
try {
|
|
623
|
+
return process.env.NODE_ENV === "production";
|
|
624
|
+
} catch {
|
|
625
|
+
return false;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
function warnOnce(key, message) {
|
|
629
|
+
if (isProductionBuild() || warned.has(key)) return;
|
|
630
|
+
warned.add(key);
|
|
631
|
+
console.warn(`[calendarkit-pro] ${message}`);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// src/core/timezone.ts
|
|
635
|
+
var validity = /* @__PURE__ */ new Map();
|
|
636
|
+
function resolveTimeZone(tz) {
|
|
637
|
+
if (!tz) return void 0;
|
|
638
|
+
let valid = validity.get(tz);
|
|
639
|
+
if (valid === void 0) {
|
|
640
|
+
try {
|
|
641
|
+
new Intl.DateTimeFormat("en-US", { timeZone: tz });
|
|
642
|
+
valid = true;
|
|
643
|
+
} catch {
|
|
644
|
+
valid = false;
|
|
645
|
+
}
|
|
646
|
+
validity.set(tz, valid);
|
|
647
|
+
}
|
|
648
|
+
if (!valid) {
|
|
649
|
+
warnOnce(`invalid-timezone:${tz}`, `Unknown timezone "${tz}", falling back to local time.`);
|
|
650
|
+
return void 0;
|
|
651
|
+
}
|
|
652
|
+
return tz;
|
|
653
|
+
}
|
|
654
|
+
function toDisplay(date, tz) {
|
|
655
|
+
const zone = resolveTimeZone(tz);
|
|
656
|
+
return zone ? toZonedTime2(date, zone) : date;
|
|
657
|
+
}
|
|
658
|
+
function fromDisplay(wall, tz) {
|
|
659
|
+
const zone = resolveTimeZone(tz);
|
|
660
|
+
return zone ? fromZonedTime(wall, zone) : new Date(wall);
|
|
661
|
+
}
|
|
662
|
+
function minutesSinceDisplayMidnight(date, tz) {
|
|
663
|
+
const wall = toDisplay(date, tz);
|
|
664
|
+
return wall.getHours() * 60 + wall.getMinutes();
|
|
665
|
+
}
|
|
666
|
+
function isSameDisplayDay(date, day, tz) {
|
|
667
|
+
return isSameDay2(toDisplay(date, tz), day);
|
|
668
|
+
}
|
|
669
|
+
function displayToday(tz, now = /* @__PURE__ */ new Date()) {
|
|
670
|
+
return startOfDay(toDisplay(now, tz));
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// src/core/events.ts
|
|
674
|
+
var toDate = (value) => value === void 0 || value === null ? void 0 : value instanceof Date ? value : new Date(value);
|
|
675
|
+
var isValidDate = (date) => !!date && !Number.isNaN(date.getTime());
|
|
676
|
+
var normalizedEvent = /* @__PURE__ */ new WeakMap();
|
|
677
|
+
var normalizedList = /* @__PURE__ */ new WeakMap();
|
|
678
|
+
function normalizeEvent(event) {
|
|
679
|
+
const cached = normalizedEvent.get(event);
|
|
680
|
+
if (cached !== void 0) return cached;
|
|
681
|
+
const start = toDate(event.start);
|
|
682
|
+
const end = toDate(event.end);
|
|
683
|
+
let result = null;
|
|
684
|
+
if (!isValidDate(start) || !isValidDate(end)) {
|
|
685
|
+
warnOnce(`invalid-date:${event.id}`, `Event "${event.id}" has an invalid start or end date and was skipped.`);
|
|
686
|
+
} else if (end.getTime() < start.getTime()) {
|
|
687
|
+
warnOnce(`end-before-start:${event.id}`, `Event "${event.id}" ends before it starts and was skipped.`);
|
|
688
|
+
} else {
|
|
689
|
+
const needsCopy = start !== event.start || end !== event.end || needsRecurrenceCopy(event);
|
|
690
|
+
result = needsCopy ? {
|
|
691
|
+
...event,
|
|
692
|
+
start,
|
|
693
|
+
end,
|
|
694
|
+
recurrence: event.recurrence && {
|
|
695
|
+
...event.recurrence,
|
|
696
|
+
until: toDate(event.recurrence.until),
|
|
697
|
+
exdates: event.recurrence.exdates?.map((d) => toDate(d))
|
|
698
|
+
}
|
|
699
|
+
} : event;
|
|
700
|
+
}
|
|
701
|
+
normalizedEvent.set(event, result);
|
|
702
|
+
return result;
|
|
703
|
+
}
|
|
704
|
+
function needsRecurrenceCopy(event) {
|
|
705
|
+
const r = event.recurrence;
|
|
706
|
+
if (!r) return false;
|
|
707
|
+
return r.until !== void 0 && !(r.until instanceof Date) || !!r.exdates?.some((d) => !(d instanceof Date));
|
|
708
|
+
}
|
|
709
|
+
function normalizeEvents(events) {
|
|
710
|
+
const cached = normalizedList.get(events);
|
|
711
|
+
if (cached) return cached;
|
|
712
|
+
const seen = /* @__PURE__ */ new Set();
|
|
713
|
+
const result = [];
|
|
714
|
+
for (const event of events) {
|
|
715
|
+
const normalized = normalizeEvent(event);
|
|
716
|
+
if (!normalized) continue;
|
|
717
|
+
if (seen.has(normalized.id)) {
|
|
718
|
+
warnOnce(`duplicate-id:${normalized.id}`, `Duplicate event id "${normalized.id}": ids must be unique.`);
|
|
719
|
+
}
|
|
720
|
+
seen.add(normalized.id);
|
|
721
|
+
result.push(normalized);
|
|
722
|
+
}
|
|
723
|
+
normalizedList.set(events, result);
|
|
724
|
+
return result;
|
|
725
|
+
}
|
|
726
|
+
var dayKey = (wall) => format6(wall, "yyyy-MM-dd");
|
|
727
|
+
function memoByZone(cache, events, tz, compute) {
|
|
728
|
+
let byZone = cache.get(events);
|
|
729
|
+
if (!byZone) {
|
|
730
|
+
byZone = /* @__PURE__ */ new Map();
|
|
731
|
+
cache.set(events, byZone);
|
|
732
|
+
}
|
|
733
|
+
const key = tz ?? "";
|
|
734
|
+
let value = byZone.get(key);
|
|
735
|
+
if (value === void 0) {
|
|
736
|
+
value = compute();
|
|
737
|
+
byZone.set(key, value);
|
|
738
|
+
}
|
|
739
|
+
return value;
|
|
740
|
+
}
|
|
741
|
+
var startDayCache = /* @__PURE__ */ new WeakMap();
|
|
742
|
+
function eventsByStartDay(events, tz) {
|
|
743
|
+
return memoByZone(startDayCache, events, tz, () => {
|
|
744
|
+
const map = /* @__PURE__ */ new Map();
|
|
745
|
+
for (const event of events) {
|
|
746
|
+
const key = dayKey(toDisplay(event.start, tz));
|
|
747
|
+
const bucket = map.get(key);
|
|
748
|
+
if (bucket) bucket.push(event);
|
|
749
|
+
else map.set(key, [event]);
|
|
750
|
+
}
|
|
751
|
+
return map;
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
var segmentCache = /* @__PURE__ */ new WeakMap();
|
|
755
|
+
function segmentsByDisplayDay(events, tz, range) {
|
|
756
|
+
const cacheKey = range ? `${tz ?? ""}|${range.start.getTime()}|${range.end.getTime()}` : tz;
|
|
757
|
+
return memoByZone(segmentCache, events, cacheKey, () => {
|
|
758
|
+
const map = /* @__PURE__ */ new Map();
|
|
759
|
+
for (const event of events) {
|
|
760
|
+
const wallStart = toDisplay(event.start, tz);
|
|
761
|
+
const rawEnd = toDisplay(event.end, tz);
|
|
762
|
+
const wallEnd = rawEnd < wallStart ? wallStart : rawEnd;
|
|
763
|
+
if (range && (wallEnd < range.start || wallStart >= range.end)) continue;
|
|
764
|
+
let day = startOfDay2(range && wallStart < range.start ? range.start : wallStart);
|
|
765
|
+
const stop = range && wallEnd > range.end ? range.end : wallEnd;
|
|
766
|
+
do {
|
|
767
|
+
const next = addDays(day, 1);
|
|
768
|
+
const start = wallStart > day ? wallStart : day;
|
|
769
|
+
const end = wallEnd < next ? wallEnd : next;
|
|
770
|
+
if (end > start || wallStart.getTime() === wallEnd.getTime()) {
|
|
771
|
+
const key = dayKey(day);
|
|
772
|
+
const bucket = map.get(key);
|
|
773
|
+
const segment = { event, start, end };
|
|
774
|
+
if (bucket) bucket.push(segment);
|
|
775
|
+
else map.set(key, [segment]);
|
|
776
|
+
}
|
|
777
|
+
day = next;
|
|
778
|
+
} while (day < stop);
|
|
779
|
+
}
|
|
780
|
+
return map;
|
|
781
|
+
});
|
|
782
|
+
}
|
|
429
783
|
|
|
430
784
|
// src/components/dnd/DraggableEvent.tsx
|
|
431
785
|
import React5 from "react";
|
|
432
786
|
import { useDraggable } from "@dnd-kit/core";
|
|
433
787
|
import { CSS } from "@dnd-kit/utilities";
|
|
434
|
-
var
|
|
788
|
+
var DraggableEventImpl = ({ event, dragId, grabOffsetMs, children, className, style: propStyle, ...props }) => {
|
|
435
789
|
const dragDisabled = isEventDragDisabled(event);
|
|
436
790
|
const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({
|
|
437
|
-
id: event.id,
|
|
438
|
-
data: { event },
|
|
791
|
+
id: dragId ?? event.id,
|
|
792
|
+
data: { event, grabOffsetMs },
|
|
439
793
|
disabled: dragDisabled
|
|
440
794
|
});
|
|
441
795
|
const style = {
|
|
442
796
|
...propStyle,
|
|
443
797
|
transform: CSS.Translate.toString(transform),
|
|
444
798
|
zIndex: isDragging ? 100 : propStyle?.zIndex,
|
|
445
|
-
//
|
|
446
|
-
// so that the DragOverlay (the "ghost") is the only thing the user focuses on.
|
|
447
|
-
// If we set opacity: 0, it disappears. If 0.1, it's a faint placeholder.
|
|
448
|
-
// The user asked for "Make the entire dragged item transparent".
|
|
449
|
-
// Usually this means the original item should be faded out while the drag overlay is moving.
|
|
799
|
+
// Hide the original while dragging: the DragOverlay ghost is what the user follows.
|
|
450
800
|
opacity: isDragging ? 0 : propStyle?.opacity
|
|
451
|
-
// Hiding original element completely
|
|
452
801
|
};
|
|
453
802
|
return /* @__PURE__ */ React5.createElement(
|
|
454
803
|
"div",
|
|
@@ -463,33 +812,56 @@ var DraggableEvent = ({ event, children, className, style: propStyle, ...props }
|
|
|
463
812
|
children
|
|
464
813
|
);
|
|
465
814
|
};
|
|
815
|
+
var DraggableEvent = React5.memo(DraggableEventImpl);
|
|
466
816
|
|
|
467
817
|
// src/components/dnd/DroppableCell.tsx
|
|
468
818
|
import React6 from "react";
|
|
469
819
|
import { useDroppable } from "@dnd-kit/core";
|
|
470
|
-
var
|
|
820
|
+
var QUARTER_HOVER = [
|
|
821
|
+
"hover:bg-blue-50/50 dark:hover:bg-blue-900/10",
|
|
822
|
+
"hover:bg-blue-50/80 dark:hover:bg-blue-900/20",
|
|
823
|
+
"hover:bg-blue-100/50 dark:hover:bg-blue-900/30",
|
|
824
|
+
"hover:bg-blue-100/80 dark:hover:bg-blue-900/40"
|
|
825
|
+
];
|
|
826
|
+
var QUARTER_ACTIVE = [
|
|
827
|
+
"bg-blue-50/50 dark:bg-blue-900/10 ring-2 ring-primary ring-inset",
|
|
828
|
+
"bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-primary ring-inset",
|
|
829
|
+
"bg-blue-100/50 dark:bg-blue-900/30 ring-2 ring-primary ring-inset",
|
|
830
|
+
"bg-blue-100/80 dark:bg-blue-900/40 ring-2 ring-primary ring-inset"
|
|
831
|
+
];
|
|
832
|
+
var quarterOf = (date) => Math.min(3, Math.floor(date.getMinutes() / 15));
|
|
833
|
+
var DroppableCellImpl = ({ id, date, resourceId, children, className, style, onClick, onSlotClick }) => {
|
|
471
834
|
const { isOver, setNodeRef } = useDroppable({
|
|
472
835
|
id,
|
|
473
836
|
data: { date, resourceId }
|
|
474
837
|
});
|
|
475
|
-
const
|
|
476
|
-
const quarterClass = minutes === 0 ? "hover:bg-blue-50/50 dark:hover:bg-blue-900/10" : minutes === 15 ? "hover:bg-blue-50/80 dark:hover:bg-blue-900/20" : minutes === 30 ? "hover:bg-blue-100/50 dark:hover:bg-blue-900/30" : "hover:bg-blue-100/80 dark:hover:bg-blue-900/40";
|
|
477
|
-
const activeQuarterClass = minutes === 0 ? "bg-blue-50/50 dark:bg-blue-900/10 ring-2 ring-primary ring-inset" : minutes === 15 ? "bg-blue-50/80 dark:bg-blue-900/20 ring-2 ring-primary ring-inset" : minutes === 30 ? "bg-blue-100/50 dark:bg-blue-900/30 ring-2 ring-primary ring-inset" : "bg-blue-100/80 dark:bg-blue-900/40 ring-2 ring-primary ring-inset";
|
|
838
|
+
const quarter = quarterOf(date);
|
|
478
839
|
return /* @__PURE__ */ React6.createElement(
|
|
479
840
|
"div",
|
|
480
841
|
{
|
|
481
842
|
ref: setNodeRef,
|
|
482
843
|
className: cn(
|
|
483
844
|
className,
|
|
484
|
-
|
|
485
|
-
isOver &&
|
|
845
|
+
QUARTER_HOVER[quarter],
|
|
846
|
+
isOver && QUARTER_ACTIVE[quarter],
|
|
847
|
+
onSlotClick && "cursor-pointer"
|
|
486
848
|
),
|
|
487
849
|
style,
|
|
488
|
-
onClick
|
|
850
|
+
onClick: onSlotClick ? () => onSlotClick(date, resourceId) : onClick
|
|
489
851
|
},
|
|
490
852
|
children
|
|
491
853
|
);
|
|
492
854
|
};
|
|
855
|
+
var shallowEqualStyle = (a, b) => {
|
|
856
|
+
if (a === b) return true;
|
|
857
|
+
if (!a || !b) return false;
|
|
858
|
+
const keys = Object.keys(a);
|
|
859
|
+
return keys.length === Object.keys(b).length && keys.every((key) => a[key] === b[key]);
|
|
860
|
+
};
|
|
861
|
+
var DroppableCell = React6.memo(
|
|
862
|
+
DroppableCellImpl,
|
|
863
|
+
(prev, next) => prev.id === next.id && prev.date.getTime() === next.date.getTime() && prev.resourceId === next.resourceId && prev.children === next.children && prev.className === next.className && prev.onClick === next.onClick && prev.onSlotClick === next.onSlotClick && shallowEqualStyle(prev.style, next.style)
|
|
864
|
+
);
|
|
493
865
|
|
|
494
866
|
// src/views/MonthView.tsx
|
|
495
867
|
var EventItem = React7.memo(({ event, onEventClick }) => /* @__PURE__ */ React7.createElement(DraggableEvent, { event }, /* @__PURE__ */ React7.createElement(
|
|
@@ -521,41 +893,21 @@ var MonthView = ({
|
|
|
521
893
|
timezone,
|
|
522
894
|
locale
|
|
523
895
|
}) => {
|
|
524
|
-
const weekStartsOn = locale
|
|
525
|
-
const days = useMemo(
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
);
|
|
529
|
-
const weekDays = useMemo(() => {
|
|
530
|
-
const start = startOfWeek2(currentDate, { weekStartsOn });
|
|
531
|
-
const end = endOfWeek2(currentDate, { weekStartsOn });
|
|
532
|
-
return eachDayOfInterval2({ start, end });
|
|
533
|
-
}, [currentDate, weekStartsOn]);
|
|
534
|
-
const getZonedDate = useCallback((date) => {
|
|
535
|
-
return timezone ? toZonedTime2(date, timezone) : date;
|
|
536
|
-
}, [timezone]);
|
|
537
|
-
const eventsByDay = useMemo(() => {
|
|
538
|
-
const map = /* @__PURE__ */ new Map();
|
|
539
|
-
events.forEach((event) => {
|
|
540
|
-
const zonedStart = getZonedDate(event.start);
|
|
541
|
-
const key = format5(zonedStart, "yyyy-MM-dd");
|
|
542
|
-
if (!map.has(key)) {
|
|
543
|
-
map.set(key, []);
|
|
544
|
-
}
|
|
545
|
-
map.get(key).push(event);
|
|
546
|
-
});
|
|
547
|
-
return map;
|
|
548
|
-
}, [events, getZonedDate]);
|
|
896
|
+
const weekStartsOn = resolveWeekStartsOn(locale, 1);
|
|
897
|
+
const days = useMemo(() => getMonthGrid(currentDate, weekStartsOn), [currentDate, weekStartsOn]);
|
|
898
|
+
const weekDays = useMemo(() => getWeekDays(currentDate, weekStartsOn), [currentDate, weekStartsOn]);
|
|
899
|
+
const eventsByDay = eventsByStartDay(events, timezone);
|
|
900
|
+
const today = displayToday(timezone);
|
|
549
901
|
return /* @__PURE__ */ React7.createElement("div", { className: "flex flex-col h-full bg-background border-[0.5px] scrollbar-hide border-border/50 rounded-2xl overflow-hidden min-w-[800px] md:min-w-0 shadow-sm" }, /* @__PURE__ */ React7.createElement("div", { className: "overflow-y-auto flex-1 relative" }, /* @__PURE__ */ React7.createElement("div", { className: "grid grid-cols-7 border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/30 via-muted/40 to-muted/30 sticky top-0 z-20 backdrop-blur-sm" }, weekDays.map((day) => /* @__PURE__ */ React7.createElement(
|
|
550
902
|
"div",
|
|
551
903
|
{
|
|
552
904
|
key: day.toISOString(),
|
|
553
905
|
className: "py-3 text-center text-xs font-semibold text-muted-foreground border-r-[0.5px] border-border/30 last:border-r-0 uppercase tracking-wider"
|
|
554
906
|
},
|
|
555
|
-
|
|
907
|
+
format7(day, "EEE", { locale })
|
|
556
908
|
))), /* @__PURE__ */ React7.createElement("div", { className: "grid grid-cols-7", style: { gridAutoRows: "130px" } }, days.map((day) => {
|
|
557
|
-
const
|
|
558
|
-
const dayEvents = eventsByDay.get(
|
|
909
|
+
const dayKey2 = format7(day, "yyyy-MM-dd");
|
|
910
|
+
const dayEvents = eventsByDay.get(dayKey2) || [];
|
|
559
911
|
const isCurrentMonth = isSameMonth2(day, currentDate);
|
|
560
912
|
const cellId = day.toISOString();
|
|
561
913
|
return /* @__PURE__ */ React7.createElement(
|
|
@@ -567,26 +919,87 @@ var MonthView = ({
|
|
|
567
919
|
className: cn(
|
|
568
920
|
"h-[130px] p-2 border-b-[0.5px] border-r-[0.5px] border-border/30 last:border-r-0 relative transition-all duration-200 hover:bg-accent/5 flex flex-col gap-1.5 overflow-hidden group",
|
|
569
921
|
!isCurrentMonth && "bg-muted/5 text-muted-foreground/60",
|
|
570
|
-
|
|
922
|
+
isSameDay3(day, today) && "bg-primary/5 ring-1 ring-inset ring-primary/20"
|
|
571
923
|
),
|
|
572
924
|
onClick: () => onDateClick?.(day)
|
|
573
925
|
},
|
|
574
926
|
/* @__PURE__ */ React7.createElement("div", { className: "flex justify-between items-start" }, /* @__PURE__ */ React7.createElement("div", { className: cn(
|
|
575
927
|
"text-sm font-semibold w-7 h-7 flex items-center justify-center rounded-full transition-all duration-200",
|
|
576
|
-
|
|
577
|
-
) },
|
|
578
|
-
/* @__PURE__ */ React7.createElement("div", { className: "flex-1 flex flex-col gap-1 scrollbar-hide overflow-y-auto overflow-x-hidden" }, dayEvents.slice(0, 4).map((event) => /* @__PURE__ */ React7.createElement(EventItem, { key: `${event.id}-${
|
|
928
|
+
isSameDay3(day, today) ? "bg-primary text-primary-foreground shadow-md shadow-primary/30" : "group-hover:bg-accent"
|
|
929
|
+
) }, format7(day, "d", { locale })), dayEvents.length > 0 && /* @__PURE__ */ React7.createElement("div", { className: "text-[10px] font-medium text-muted-foreground/60 bg-muted/50 px-1.5 py-0.5 rounded-full" }, dayEvents.length)),
|
|
930
|
+
/* @__PURE__ */ React7.createElement("div", { className: "flex-1 flex flex-col gap-1 scrollbar-hide overflow-y-auto overflow-x-hidden" }, dayEvents.slice(0, 4).map((event) => /* @__PURE__ */ React7.createElement(EventItem, { key: `${event.id}-${dayKey2}`, event, onEventClick })), dayEvents.length > 4 && /* @__PURE__ */ React7.createElement("div", { className: "text-[10px] text-primary font-semibold text-center py-1 px-2 rounded-md bg-primary/5 hover:bg-primary/10 cursor-pointer transition-colors" }, "+", dayEvents.length - 4, " more"))
|
|
579
931
|
);
|
|
580
932
|
}))));
|
|
581
933
|
};
|
|
582
934
|
|
|
583
935
|
// src/views/WeekView.tsx
|
|
584
936
|
import React9 from "react";
|
|
585
|
-
import {
|
|
586
|
-
|
|
937
|
+
import { addDays as addDays2, format as format8, differenceInMinutes, isSameDay as isSameDay4 } from "date-fns";
|
|
938
|
+
|
|
939
|
+
// src/core/layout.ts
|
|
940
|
+
var layoutCache = /* @__PURE__ */ new WeakMap();
|
|
941
|
+
function layoutDayEvents(segments) {
|
|
942
|
+
const cached = layoutCache.get(segments);
|
|
943
|
+
if (cached) return cached;
|
|
944
|
+
const sorted = [...segments].sort(
|
|
945
|
+
(a, b) => a.start.getTime() - b.start.getTime() || b.end.getTime() - a.end.getTime() || (a.event.id < b.event.id ? -1 : a.event.id > b.event.id ? 1 : 0)
|
|
946
|
+
);
|
|
947
|
+
const result = [];
|
|
948
|
+
let cluster = [];
|
|
949
|
+
let columnEnds = [];
|
|
950
|
+
let clusterEnd = -Infinity;
|
|
951
|
+
const closeCluster = () => {
|
|
952
|
+
for (const placed of cluster) placed.columns = columnEnds.length;
|
|
953
|
+
cluster = [];
|
|
954
|
+
columnEnds = [];
|
|
955
|
+
};
|
|
956
|
+
for (const segment of sorted) {
|
|
957
|
+
const start = segment.start.getTime();
|
|
958
|
+
const end = Math.max(segment.end.getTime(), start + 1);
|
|
959
|
+
if (start >= clusterEnd) closeCluster();
|
|
960
|
+
let column = columnEnds.findIndex((columnEnd) => columnEnd <= start);
|
|
961
|
+
if (column === -1) {
|
|
962
|
+
column = columnEnds.length;
|
|
963
|
+
columnEnds.push(end);
|
|
964
|
+
} else {
|
|
965
|
+
columnEnds[column] = end;
|
|
966
|
+
}
|
|
967
|
+
const placed = { ...segment, column, columns: 1 };
|
|
968
|
+
cluster.push(placed);
|
|
969
|
+
result.push(placed);
|
|
970
|
+
clusterEnd = Math.max(clusterEnd, end);
|
|
971
|
+
}
|
|
972
|
+
closeCluster();
|
|
973
|
+
layoutCache.set(segments, result);
|
|
974
|
+
return result;
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
// src/components/dnd/ResizableEvent.tsx
|
|
978
|
+
import React8, { useState as useState2, useRef, useCallback } from "react";
|
|
979
|
+
|
|
980
|
+
// src/core/dnd.ts
|
|
981
|
+
function computeDrop({ event, overDate, overResourceId, grabOffsetMs = 0, view, tz }) {
|
|
982
|
+
let wallStart;
|
|
983
|
+
if (view === "month") {
|
|
984
|
+
const original = toDisplay(event.start, tz);
|
|
985
|
+
wallStart = new Date(overDate);
|
|
986
|
+
wallStart.setHours(original.getHours(), original.getMinutes(), original.getSeconds(), original.getMilliseconds());
|
|
987
|
+
} else {
|
|
988
|
+
wallStart = new Date(overDate.getTime() - grabOffsetMs);
|
|
989
|
+
wallStart.setSeconds(0, 0);
|
|
990
|
+
}
|
|
991
|
+
const start = fromDisplay(wallStart, tz);
|
|
992
|
+
const end = new Date(start.getTime() + (event.end.getTime() - event.start.getTime()));
|
|
993
|
+
const resourceId = overResourceId ?? event.resourceId;
|
|
994
|
+
if (start.getTime() === event.start.getTime() && resourceId === event.resourceId) return null;
|
|
995
|
+
return { start, end, resourceId };
|
|
996
|
+
}
|
|
997
|
+
function computeResizeEnd(event, deltaMinutes, minDuration = 15) {
|
|
998
|
+
const minEnd = event.start.getTime() + minDuration * 6e4;
|
|
999
|
+
return new Date(Math.max(event.end.getTime() + deltaMinutes * 6e4, minEnd));
|
|
1000
|
+
}
|
|
587
1001
|
|
|
588
1002
|
// src/components/dnd/ResizableEvent.tsx
|
|
589
|
-
import React8, { useState as useState2, useRef, useCallback as useCallback2 } from "react";
|
|
590
1003
|
var ResizableEvent = ({
|
|
591
1004
|
event,
|
|
592
1005
|
hourHeight,
|
|
@@ -604,7 +1017,7 @@ var ResizableEvent = ({
|
|
|
604
1017
|
const startYRef = useRef(0);
|
|
605
1018
|
const startHeightRef = useRef(0);
|
|
606
1019
|
const resizeHeightRef = useRef(null);
|
|
607
|
-
const handleResizeStart =
|
|
1020
|
+
const handleResizeStart = useCallback((e) => {
|
|
608
1021
|
e.preventDefault();
|
|
609
1022
|
e.stopPropagation();
|
|
610
1023
|
const clientY = "touches" in e ? e.touches[0].clientY : e.clientY;
|
|
@@ -628,14 +1041,8 @@ var ResizableEvent = ({
|
|
|
628
1041
|
setIsResizing(false);
|
|
629
1042
|
const finalHeight = resizeHeightRef.current;
|
|
630
1043
|
if (finalHeight !== null && containerRef.current && onResize) {
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
const minutesDiff = heightDiff / hourHeight * 60;
|
|
634
|
-
const newEnd = new Date(event.end);
|
|
635
|
-
newEnd.setMinutes(newEnd.getMinutes() + minutesDiff);
|
|
636
|
-
if (newEnd > event.start) {
|
|
637
|
-
onResize(event, newEnd);
|
|
638
|
-
}
|
|
1044
|
+
const minutesDiff = (finalHeight - startHeightRef.current) / hourHeight * 60;
|
|
1045
|
+
onResize(event, computeResizeEnd(event, minutesDiff, minDuration));
|
|
639
1046
|
}
|
|
640
1047
|
resizeHeightRef.current = null;
|
|
641
1048
|
setResizeHeight(null);
|
|
@@ -649,7 +1056,7 @@ var ResizableEvent = ({
|
|
|
649
1056
|
document.addEventListener("touchmove", handleMove, { passive: false });
|
|
650
1057
|
document.addEventListener("touchend", handleEnd);
|
|
651
1058
|
}, [event, hourHeight, minDuration, onResize]);
|
|
652
|
-
const handlePointerDown =
|
|
1059
|
+
const handlePointerDown = useCallback((e) => {
|
|
653
1060
|
e.stopPropagation();
|
|
654
1061
|
}, []);
|
|
655
1062
|
return /* @__PURE__ */ React8.createElement(
|
|
@@ -686,7 +1093,16 @@ var ResizableEvent = ({
|
|
|
686
1093
|
};
|
|
687
1094
|
|
|
688
1095
|
// src/views/WeekView.tsx
|
|
689
|
-
var
|
|
1096
|
+
var HOUR_HEIGHT = 60;
|
|
1097
|
+
var HOURS = Array.from({ length: 24 }, (_, i) => i);
|
|
1098
|
+
var QUARTERS = [0, 15, 30, 45];
|
|
1099
|
+
var NO_SEGMENTS = [];
|
|
1100
|
+
var slotDate = (day, hour, minute) => {
|
|
1101
|
+
const date = new Date(day);
|
|
1102
|
+
date.setHours(hour, minute, 0, 0);
|
|
1103
|
+
return date;
|
|
1104
|
+
};
|
|
1105
|
+
var WeekViewImpl = ({
|
|
690
1106
|
currentDate,
|
|
691
1107
|
events,
|
|
692
1108
|
onEventClick,
|
|
@@ -696,14 +1112,16 @@ var WeekView = ({
|
|
|
696
1112
|
locale,
|
|
697
1113
|
readonly
|
|
698
1114
|
}) => {
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
const
|
|
704
|
-
|
|
1115
|
+
const weekDays = React9.useMemo(
|
|
1116
|
+
() => getWeekDays(currentDate, resolveWeekStartsOn(locale, 1)),
|
|
1117
|
+
[currentDate, locale]
|
|
1118
|
+
);
|
|
1119
|
+
const visibleRange = React9.useMemo(
|
|
1120
|
+
() => ({ start: weekDays[0], end: addDays2(weekDays[weekDays.length - 1], 1) }),
|
|
1121
|
+
[weekDays]
|
|
1122
|
+
);
|
|
705
1123
|
const scrollContainerRef = React9.useRef(null);
|
|
706
|
-
const [now, setNow] = React9.useState(/* @__PURE__ */ new Date());
|
|
1124
|
+
const [now, setNow] = React9.useState(() => /* @__PURE__ */ new Date());
|
|
707
1125
|
React9.useEffect(() => {
|
|
708
1126
|
const interval = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
|
|
709
1127
|
return () => clearInterval(interval);
|
|
@@ -711,132 +1129,93 @@ var WeekView = ({
|
|
|
711
1129
|
React9.useEffect(() => {
|
|
712
1130
|
if (scrollContainerRef.current) {
|
|
713
1131
|
const offsetHour = 4;
|
|
714
|
-
scrollContainerRef.current.scrollTop = ((/* @__PURE__ */ new Date()).getHours() - offsetHour) *
|
|
715
|
-
}
|
|
716
|
-
}, []);
|
|
717
|
-
const getZonedDate = (date) => {
|
|
718
|
-
return timezone ? toZonedTime3(date, timezone) : date;
|
|
719
|
-
};
|
|
720
|
-
const zonedNow = getZonedDate(/* @__PURE__ */ new Date());
|
|
721
|
-
const getTimezoneDisplay = (tz) => {
|
|
722
|
-
const date = now;
|
|
723
|
-
let displayTime = "";
|
|
724
|
-
let acronym = "";
|
|
725
|
-
if (!tz) {
|
|
726
|
-
displayTime = format6(date, "HH:mm");
|
|
727
|
-
try {
|
|
728
|
-
acronym = new Intl.DateTimeFormat("en-US", { timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value || "";
|
|
729
|
-
} catch {
|
|
730
|
-
acronym = "LOC";
|
|
731
|
-
}
|
|
732
|
-
} else {
|
|
733
|
-
try {
|
|
734
|
-
const zDate = toZonedTime3(date, tz);
|
|
735
|
-
displayTime = format6(zDate, "HH:mm");
|
|
736
|
-
acronym = new Intl.DateTimeFormat("en-US", { timeZone: tz, timeZoneName: "short" }).formatToParts(date).find((part) => part.type === "timeZoneName")?.value || "";
|
|
737
|
-
} catch {
|
|
738
|
-
displayTime = format6(date, "HH:mm");
|
|
739
|
-
acronym = tz;
|
|
740
|
-
}
|
|
1132
|
+
scrollContainerRef.current.scrollTop = (toDisplay(/* @__PURE__ */ new Date(), timezone).getHours() - offsetHour) * HOUR_HEIGHT;
|
|
741
1133
|
}
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
const
|
|
745
|
-
const
|
|
746
|
-
const
|
|
747
|
-
const
|
|
1134
|
+
}, [timezone]);
|
|
1135
|
+
const segmentsByDay = segmentsByDisplayDay(events, timezone, visibleRange);
|
|
1136
|
+
const today = displayToday(timezone, now);
|
|
1137
|
+
const zonedNow = toDisplay(now, timezone);
|
|
1138
|
+
const nowTop = minutesSinceDisplayMidnight(now, timezone) / 60 * HOUR_HEIGHT;
|
|
1139
|
+
const showsToday = weekDays.some((day) => isSameDay4(day, today));
|
|
1140
|
+
const zone = resolveTimeZone(timezone);
|
|
1141
|
+
const zoneLabel = timeZoneAbbreviation(now, zone) || zone || "LOC";
|
|
1142
|
+
const formats = timeFormats(locale);
|
|
748
1143
|
return /* @__PURE__ */ React9.createElement("div", { className: "flex flex-col h-full bg-background border-[0.5px] border-border/50 rounded-2xl overflow-hidden min-w-[800px] md:min-w-0 shadow-sm" }, /* @__PURE__ */ React9.createElement(
|
|
749
1144
|
"div",
|
|
750
1145
|
{
|
|
751
1146
|
ref: scrollContainerRef,
|
|
752
1147
|
className: "flex-1 overflow-y-auto scrollbar-hide relative bg-background scroll-smooth"
|
|
753
1148
|
},
|
|
754
|
-
/* @__PURE__ */ React9.createElement("div", { className: "flex border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/20 via-background to-muted/20 z-20 sticky top-0 backdrop-blur-sm" }, /* @__PURE__ */ React9.createElement("div", { className: "flex-none p-3 text-center text-xs font-semibold text-muted-foreground w-16 flex items-center justify-center border-r-[0.5px] border-border/30 bg-muted/10" },
|
|
1149
|
+
/* @__PURE__ */ React9.createElement("div", { className: "flex border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/20 via-background to-muted/20 z-20 sticky top-0 backdrop-blur-sm" }, /* @__PURE__ */ React9.createElement("div", { className: "flex-none p-3 text-center text-xs font-semibold text-muted-foreground w-16 flex items-center justify-center border-r-[0.5px] border-border/30 bg-muted/10" }, /* @__PURE__ */ React9.createElement("div", { className: "flex flex-col items-center justify-center leading-tight" }, /* @__PURE__ */ React9.createElement("span", null, format8(zonedNow, "HH:mm")), /* @__PURE__ */ React9.createElement("span", { className: "text-[10px] opacity-75" }, "(", zoneLabel, ")"))), /* @__PURE__ */ React9.createElement("div", { className: "flex-1 grid grid-cols-7" }, weekDays.map((day, index) => /* @__PURE__ */ React9.createElement("div", { key: day.toISOString(), className: cn("py-3 px-2 text-center", index > 0 && "border-l-[0.5px] border-border/30") }, /* @__PURE__ */ React9.createElement("div", { className: "text-[10px] font-semibold text-muted-foreground uppercase tracking-wider mb-1.5" }, format8(day, "EEE", { locale })), /* @__PURE__ */ React9.createElement("div", { className: cn(
|
|
755
1150
|
"w-9 h-9 flex items-center justify-center rounded-xl mx-auto text-sm font-semibold transition-all duration-200",
|
|
756
|
-
|
|
757
|
-
) },
|
|
1151
|
+
isSameDay4(day, today) ? "bg-primary text-primary-foreground shadow-lg shadow-primary/30 scale-110" : "text-foreground hover:bg-accent/80"
|
|
1152
|
+
) }, format8(day, "d", { locale })))))),
|
|
758
1153
|
/* @__PURE__ */ React9.createElement(
|
|
759
1154
|
"div",
|
|
760
1155
|
{
|
|
761
1156
|
className: "flex min-w-full relative",
|
|
762
|
-
style: { height:
|
|
1157
|
+
style: { height: HOURS.length * HOUR_HEIGHT }
|
|
763
1158
|
},
|
|
764
|
-
/* @__PURE__ */ React9.createElement("div", { className: "flex-none w-16 border-r-[0.5px] border-border/30 relative bg-muted/5" },
|
|
1159
|
+
/* @__PURE__ */ React9.createElement("div", { className: "flex-none w-16 border-r-[0.5px] border-border/30 relative bg-muted/5" }, HOURS.map((hour) => /* @__PURE__ */ React9.createElement(
|
|
765
1160
|
"div",
|
|
766
1161
|
{
|
|
767
1162
|
key: hour,
|
|
768
1163
|
className: "relative w-full text-[11px] text-muted-foreground/80 text-right pr-3 font-medium tabular-nums box-border",
|
|
769
|
-
style: { height:
|
|
1164
|
+
style: { height: HOUR_HEIGHT }
|
|
770
1165
|
},
|
|
771
|
-
/* @__PURE__ */ React9.createElement("span", { className: "block -translate-y-1/2" }, hour !== 0 &&
|
|
772
|
-
))
|
|
1166
|
+
/* @__PURE__ */ React9.createElement("span", { className: "block -translate-y-1/2" }, hour !== 0 && format8(slotDate(currentDate, hour, 0), formats.hour, { locale }))
|
|
1167
|
+
)), showsToday && /* @__PURE__ */ React9.createElement(
|
|
1168
|
+
"div",
|
|
1169
|
+
{
|
|
1170
|
+
className: "absolute left-0 w-full pointer-events-none z-30 flex justify-end pr-2",
|
|
1171
|
+
style: { top: `${nowTop}px` }
|
|
1172
|
+
},
|
|
1173
|
+
/* @__PURE__ */ React9.createElement("span", { className: "text-[10px] font-bold text-primary-foreground bg-primary px-1.5 py-0.5 rounded-md shadow-md -translate-y-1/2 backdrop-blur-none" }, format8(zonedNow, formats.compact, { locale }))
|
|
1174
|
+
)),
|
|
773
1175
|
/* @__PURE__ */ React9.createElement("div", { className: "flex-1 grid grid-cols-7 relative" }, weekDays.map((day, dayIndex) => {
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
"
|
|
781
|
-
{
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
{
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
date: cellDate,
|
|
796
|
-
className: "w-full absolute left-0 right-0 z-0 transition-colors",
|
|
797
|
-
style: {
|
|
798
|
-
height: "25%",
|
|
799
|
-
top: `${minute / 60 * 100}%`
|
|
800
|
-
}
|
|
1176
|
+
const dayKey2 = format8(day, "yyyy-MM-dd");
|
|
1177
|
+
const positioned = layoutDayEvents(segmentsByDay.get(dayKey2) ?? NO_SEGMENTS);
|
|
1178
|
+
return /* @__PURE__ */ React9.createElement("div", { key: dayKey2, className: cn("relative h-full", dayIndex > 0 && "border-l-[0.5px] border-border/30") }, HOURS.map((hour) => /* @__PURE__ */ React9.createElement(
|
|
1179
|
+
"div",
|
|
1180
|
+
{
|
|
1181
|
+
key: hour,
|
|
1182
|
+
className: "w-full border-b-[0.5px] border-dashed border-border/20 box-border relative hover:bg-accent/5 transition-colors",
|
|
1183
|
+
style: { height: HOUR_HEIGHT }
|
|
1184
|
+
},
|
|
1185
|
+
QUARTERS.map((minute) => {
|
|
1186
|
+
const cellDate = slotDate(day, hour, minute);
|
|
1187
|
+
return /* @__PURE__ */ React9.createElement(
|
|
1188
|
+
DroppableCell,
|
|
1189
|
+
{
|
|
1190
|
+
key: minute,
|
|
1191
|
+
id: cellDate.toISOString(),
|
|
1192
|
+
date: cellDate,
|
|
1193
|
+
className: "w-full absolute left-0 right-0 z-0 transition-colors",
|
|
1194
|
+
style: {
|
|
1195
|
+
height: "25%",
|
|
1196
|
+
top: `${minute / 60 * 100}%`
|
|
801
1197
|
},
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
);
|
|
812
|
-
|
|
813
|
-
const
|
|
814
|
-
|
|
815
|
-
const s1 = getZonedDate(event.start).getTime();
|
|
816
|
-
const e1 = getZonedDate(event.end).getTime();
|
|
817
|
-
const s2 = getZonedDate(e.start).getTime();
|
|
818
|
-
const e2 = getZonedDate(e.end).getTime();
|
|
819
|
-
return s1 < e2 && e1 > s2;
|
|
820
|
-
});
|
|
821
|
-
const group = [event, ...overlappingEvents].sort(
|
|
822
|
-
(a, b) => getZonedDate(a.start).getTime() - getZonedDate(b.start).getTime() || (a.id > b.id ? 1 : -1)
|
|
823
|
-
// stable sort
|
|
824
|
-
);
|
|
825
|
-
const index = group.findIndex((e) => e.id === event.id);
|
|
826
|
-
const count = group.length;
|
|
827
|
-
const widthPercent = 100 / count;
|
|
828
|
-
const leftPercent = index * widthPercent;
|
|
829
|
-
const zonedEventStart = getZonedDate(event.start);
|
|
830
|
-
const zonedEventEnd = getZonedDate(event.end);
|
|
831
|
-
const startMinutes = zonedEventStart.getHours() * 60 + zonedEventStart.getMinutes();
|
|
832
|
-
const durationMinutes = differenceInMinutes(zonedEventEnd, zonedEventStart);
|
|
833
|
-
const top = startMinutes / 60 * hourHeight;
|
|
834
|
-
const height = durationMinutes / 60 * hourHeight;
|
|
1198
|
+
onSlotClick: onTimeSlotClick
|
|
1199
|
+
}
|
|
1200
|
+
);
|
|
1201
|
+
})
|
|
1202
|
+
)), positioned.map(({ event, start, end, column, columns }) => {
|
|
1203
|
+
const widthPercent = 100 / columns;
|
|
1204
|
+
const leftPercent = column * widthPercent;
|
|
1205
|
+
const zonedEventStart = toDisplay(event.start, timezone);
|
|
1206
|
+
const zonedEventEnd = toDisplay(event.end, timezone);
|
|
1207
|
+
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
|
1208
|
+
const durationMinutes = differenceInMinutes(end, start);
|
|
1209
|
+
const top = startMinutes / 60 * HOUR_HEIGHT;
|
|
1210
|
+
const height = durationMinutes / 60 * HOUR_HEIGHT;
|
|
835
1211
|
const isShortEvent = durationMinutes < 60;
|
|
1212
|
+
const isLastSegment = end.getTime() === zonedEventEnd.getTime();
|
|
836
1213
|
return /* @__PURE__ */ React9.createElement(
|
|
837
1214
|
DraggableEvent,
|
|
838
1215
|
{
|
|
839
|
-
key: `${event.id}-${
|
|
1216
|
+
key: `${event.id}-${dayKey2}`,
|
|
1217
|
+
dragId: `${event.id}@${dayKey2}`,
|
|
1218
|
+
grabOffsetMs: start.getTime() - zonedEventStart.getTime(),
|
|
840
1219
|
event,
|
|
841
1220
|
className: `absolute z-10 transition-all ${readonly ? "cursor-default" : ""}`,
|
|
842
1221
|
style: {
|
|
@@ -845,15 +1224,15 @@ var WeekView = ({
|
|
|
845
1224
|
left: `${leftPercent}%`,
|
|
846
1225
|
width: `${widthPercent}%`,
|
|
847
1226
|
// Add minimal spacing between overlapping events
|
|
848
|
-
paddingRight:
|
|
1227
|
+
paddingRight: columns > 1 ? "2px" : "0"
|
|
849
1228
|
}
|
|
850
1229
|
},
|
|
851
1230
|
/* @__PURE__ */ React9.createElement(
|
|
852
1231
|
ResizableEvent,
|
|
853
1232
|
{
|
|
854
|
-
readonly,
|
|
1233
|
+
readonly: readonly || !isLastSegment,
|
|
855
1234
|
event,
|
|
856
|
-
hourHeight,
|
|
1235
|
+
hourHeight: HOUR_HEIGHT,
|
|
857
1236
|
onResize: onEventResize,
|
|
858
1237
|
className: "h-full",
|
|
859
1238
|
style: { height: "100%" }
|
|
@@ -868,7 +1247,7 @@ var WeekView = ({
|
|
|
868
1247
|
!event.color && "border-primary/20 bg-primary/10",
|
|
869
1248
|
isShortEvent ? "px-1 flex items-center justify-center" : "p-2",
|
|
870
1249
|
// Add active border for overlapped events to distinguish them
|
|
871
|
-
|
|
1250
|
+
columns > 1 && "border-l-4 border-l-primary/50"
|
|
872
1251
|
),
|
|
873
1252
|
style: {
|
|
874
1253
|
height: "100%",
|
|
@@ -881,46 +1260,43 @@ var WeekView = ({
|
|
|
881
1260
|
e.stopPropagation();
|
|
882
1261
|
onEventClick?.(event);
|
|
883
1262
|
},
|
|
884
|
-
title:
|
|
1263
|
+
title: columns > 1 ? `${event.title} (${column + 1}/${columns})` : void 0
|
|
885
1264
|
},
|
|
886
1265
|
/* @__PURE__ */ React9.createElement("div", { className: "flex flex-col h-full overflow-hidden w-full" }, /* @__PURE__ */ React9.createElement("div", { className: cn(
|
|
887
1266
|
"font-semibold truncate text-foreground/90 leading-tight",
|
|
888
1267
|
isShortEvent ? "text-xs text-center" : "text-xs"
|
|
889
|
-
) }, event.title), !isShortEvent && /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement("div", { className: "text-[10px] text-muted-foreground truncate mt-0.5 font-medium leading-tight" },
|
|
1268
|
+
) }, event.title), !isShortEvent && /* @__PURE__ */ React9.createElement(React9.Fragment, null, /* @__PURE__ */ React9.createElement("div", { className: "text-[10px] text-muted-foreground truncate mt-0.5 font-medium leading-tight" }, format8(zonedEventStart, formats.time, { locale }), " - ", format8(zonedEventEnd, formats.time, { locale })), event.description && height > 50 && /* @__PURE__ */ React9.createElement("div", { className: "text-[10px] text-muted-foreground/80 truncate mt-1 leading-tight opacity-80" }, event.description)), columns > 1 && !isShortEvent && /* @__PURE__ */ React9.createElement("div", { className: "absolute top-1 right-1 bg-background/80 backdrop-blur-sm rounded-full w-4 h-4 flex items-center justify-center text-[9px] font-bold text-muted-foreground border border-border shadow-sm" }, columns))
|
|
890
1269
|
)
|
|
891
1270
|
)
|
|
892
1271
|
);
|
|
893
|
-
}),
|
|
1272
|
+
}), isSameDay4(day, today) && /* @__PURE__ */ React9.createElement(
|
|
894
1273
|
"div",
|
|
895
1274
|
{
|
|
896
1275
|
className: "absolute left-0 right-0 z-20 pointer-events-none flex items-center",
|
|
897
|
-
style: {
|
|
898
|
-
top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight}px`
|
|
899
|
-
}
|
|
1276
|
+
style: { top: `${nowTop}px` }
|
|
900
1277
|
},
|
|
901
1278
|
/* @__PURE__ */ React9.createElement("div", { className: "h-[2px] w-full bg-gradient-to-r from-primary via-primary to-primary/50" }),
|
|
902
1279
|
/* @__PURE__ */ React9.createElement("div", { className: "absolute -left-1.5 w-3 h-3 bg-primary rounded-full shadow-lg shadow-primary/40 ring-2 ring-background animate-pulse" })
|
|
903
1280
|
));
|
|
904
1281
|
}))
|
|
905
|
-
),
|
|
906
|
-
/* @__PURE__ */ React9.createElement(
|
|
907
|
-
"div",
|
|
908
|
-
{
|
|
909
|
-
className: "absolute left-0 w-16 pointer-events-none z-30 flex justify-end pr-2",
|
|
910
|
-
style: {
|
|
911
|
-
top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight + 80}px`
|
|
912
|
-
}
|
|
913
|
-
},
|
|
914
|
-
/* @__PURE__ */ React9.createElement("span", { className: "text-[10px] font-bold text-primary-foreground bg-primary px-1.5 py-0.5 rounded-md shadow-md -translate-y-1/2 backdrop-blur-none" }, format6(zonedNow, nowFormat, { locale }))
|
|
915
1282
|
)
|
|
916
1283
|
));
|
|
917
1284
|
};
|
|
1285
|
+
var WeekView = React9.memo(WeekViewImpl);
|
|
918
1286
|
|
|
919
1287
|
// src/views/DayView.tsx
|
|
920
1288
|
import React10 from "react";
|
|
921
|
-
import { format as
|
|
922
|
-
|
|
923
|
-
var
|
|
1289
|
+
import { addDays as addDays3, format as format9, differenceInMinutes as differenceInMinutes2, isSameDay as isSameDay5, startOfDay as startOfDay3 } from "date-fns";
|
|
1290
|
+
var HOUR_HEIGHT2 = 80;
|
|
1291
|
+
var HOURS2 = Array.from({ length: 24 }, (_, i) => i);
|
|
1292
|
+
var QUARTERS2 = [0, 15, 30, 45];
|
|
1293
|
+
var NO_SEGMENTS2 = [];
|
|
1294
|
+
var slotDate2 = (day, hour, minute) => {
|
|
1295
|
+
const date = new Date(day);
|
|
1296
|
+
date.setHours(hour, minute, 0, 0);
|
|
1297
|
+
return date;
|
|
1298
|
+
};
|
|
1299
|
+
var DayViewImpl = ({
|
|
924
1300
|
currentDate,
|
|
925
1301
|
events,
|
|
926
1302
|
onEventClick,
|
|
@@ -930,10 +1306,12 @@ var DayView = ({
|
|
|
930
1306
|
locale,
|
|
931
1307
|
readonly
|
|
932
1308
|
}) => {
|
|
933
|
-
const
|
|
934
|
-
|
|
1309
|
+
const visibleRange = React10.useMemo(() => {
|
|
1310
|
+
const start = startOfDay3(currentDate);
|
|
1311
|
+
return { start, end: addDays3(start, 1) };
|
|
1312
|
+
}, [currentDate]);
|
|
935
1313
|
const scrollContainerRef = React10.useRef(null);
|
|
936
|
-
const [now, setNow] = React10.useState(/* @__PURE__ */ new Date());
|
|
1314
|
+
const [now, setNow] = React10.useState(() => /* @__PURE__ */ new Date());
|
|
937
1315
|
React10.useEffect(() => {
|
|
938
1316
|
const interval = setInterval(() => setNow(/* @__PURE__ */ new Date()), 6e4);
|
|
939
1317
|
return () => clearInterval(interval);
|
|
@@ -941,99 +1319,71 @@ var DayView = ({
|
|
|
941
1319
|
React10.useEffect(() => {
|
|
942
1320
|
if (scrollContainerRef.current) {
|
|
943
1321
|
const offsetHour = 3;
|
|
944
|
-
scrollContainerRef.current.scrollTop = ((/* @__PURE__ */ new Date()).getHours() - offsetHour) *
|
|
1322
|
+
scrollContainerRef.current.scrollTop = (toDisplay(/* @__PURE__ */ new Date(), timezone).getHours() - offsetHour) * HOUR_HEIGHT2;
|
|
945
1323
|
}
|
|
946
|
-
}, []);
|
|
947
|
-
const
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
const zonedNow =
|
|
951
|
-
const
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
});
|
|
955
|
-
const is24h = uses24HourClock(locale);
|
|
956
|
-
const timeFormat = is24h ? "H:mm" : "h a";
|
|
957
|
-
const eventTimeFormat = is24h ? "H:mm" : "h:mm a";
|
|
958
|
-
const nowFormat = is24h ? "H:mm" : "h:mm a";
|
|
959
|
-
return /* @__PURE__ */ React10.createElement("div", { className: "flex flex-col h-full bg-background border-[0.5px] border-border/50 rounded-2xl overflow-hidden shadow-sm" }, /* @__PURE__ */ React10.createElement("div", { className: "px-6 py-4 border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/20 via-background to-muted/20 text-center shrink-0" }, /* @__PURE__ */ React10.createElement("div", { className: "flex items-center justify-center gap-3" }, /* @__PURE__ */ React10.createElement("h2", { className: "text-xl font-semibold capitalize text-foreground" }, format7(currentDate, "EEEE, MMMM d, yyyy", { locale })), isToday4(currentDate) && /* @__PURE__ */ React10.createElement("span", { className: "text-xs font-semibold bg-primary text-primary-foreground px-3 py-1 rounded-full shadow-md shadow-primary/20" }, "Today"))), /* @__PURE__ */ React10.createElement("div", { ref: scrollContainerRef, className: "flex-1 overflow-y-auto relative" }, /* @__PURE__ */ React10.createElement("div", { className: "flex relative", style: { height: hours.length * hourHeight } }, /* @__PURE__ */ React10.createElement("div", { className: "w-20 bg-muted/5 border-r-[0.5px] border-border/30 relative" }, hours.map((hour) => /* @__PURE__ */ React10.createElement(
|
|
1324
|
+
}, [timezone]);
|
|
1325
|
+
const dayKey2 = format9(currentDate, "yyyy-MM-dd");
|
|
1326
|
+
const positioned = layoutDayEvents(segmentsByDisplayDay(events, timezone, visibleRange).get(dayKey2) ?? NO_SEGMENTS2);
|
|
1327
|
+
const isToday2 = isSameDay5(currentDate, displayToday(timezone, now));
|
|
1328
|
+
const zonedNow = toDisplay(now, timezone);
|
|
1329
|
+
const nowTop = minutesSinceDisplayMidnight(now, timezone) / 60 * HOUR_HEIGHT2;
|
|
1330
|
+
const formats = timeFormats(locale);
|
|
1331
|
+
return /* @__PURE__ */ React10.createElement("div", { className: "flex flex-col h-full bg-background border-[0.5px] border-border/50 rounded-2xl overflow-hidden shadow-sm" }, /* @__PURE__ */ React10.createElement("div", { className: "px-6 py-4 border-b-[0.5px] border-border/50 bg-gradient-to-r from-muted/20 via-background to-muted/20 text-center shrink-0" }, /* @__PURE__ */ React10.createElement("div", { className: "flex items-center justify-center gap-3" }, /* @__PURE__ */ React10.createElement("h2", { className: "text-xl font-semibold capitalize text-foreground" }, format9(currentDate, "EEEE, MMMM d, yyyy", { locale })), isToday2 && /* @__PURE__ */ React10.createElement("span", { className: "text-xs font-semibold bg-primary text-primary-foreground px-3 py-1 rounded-full shadow-md shadow-primary/20" }, "Today"))), /* @__PURE__ */ React10.createElement("div", { ref: scrollContainerRef, className: "flex-1 overflow-y-auto relative" }, /* @__PURE__ */ React10.createElement("div", { className: "flex relative", style: { height: HOURS2.length * HOUR_HEIGHT2 } }, /* @__PURE__ */ React10.createElement("div", { className: "w-20 bg-muted/5 border-r-[0.5px] border-border/30 relative" }, HOURS2.map((hour) => /* @__PURE__ */ React10.createElement(
|
|
960
1332
|
"div",
|
|
961
1333
|
{
|
|
962
1334
|
key: hour,
|
|
963
1335
|
className: "relative w-full",
|
|
964
|
-
style: { height:
|
|
1336
|
+
style: { height: HOUR_HEIGHT2 }
|
|
965
1337
|
},
|
|
966
|
-
hour !== 0 && /* @__PURE__ */ React10.createElement("span", { className: "absolute w-full text-center -top-3 left-1/2 -translate-x-1/2 text-[11px] text-muted-foreground/80 font-medium tabular-nums bg-background px-1.5 py-0.5 rounded-md" },
|
|
967
|
-
)),
|
|
1338
|
+
hour !== 0 && /* @__PURE__ */ React10.createElement("span", { className: "absolute w-full text-center -top-3 left-1/2 -translate-x-1/2 text-[11px] text-muted-foreground/80 font-medium tabular-nums bg-background px-1.5 py-0.5 rounded-md" }, format9(slotDate2(currentDate, hour, 0), formats.hour, { locale }))
|
|
1339
|
+
)), isToday2 && /* @__PURE__ */ React10.createElement(
|
|
968
1340
|
"div",
|
|
969
1341
|
{
|
|
970
1342
|
className: "absolute left-0 w-full pointer-events-none z-30 flex justify-end pr-2",
|
|
971
|
-
style: {
|
|
972
|
-
top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight}px`
|
|
973
|
-
}
|
|
1343
|
+
style: { top: `${nowTop}px` }
|
|
974
1344
|
},
|
|
975
|
-
/* @__PURE__ */ React10.createElement("span", { className: "text-[10px] font-bold text-white bg-primary px-2 py-1 rounded-lg shadow-md shadow-primary/30 -translate-y-1/2" },
|
|
976
|
-
)), /* @__PURE__ */ React10.createElement("div", { className: "flex-1 relative" },
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
style: {
|
|
996
|
-
height: "25%",
|
|
997
|
-
top: `${minute / 60 * 100}%`
|
|
998
|
-
}
|
|
1345
|
+
/* @__PURE__ */ React10.createElement("span", { className: "text-[10px] font-bold text-white bg-primary px-2 py-1 rounded-lg shadow-md shadow-primary/30 -translate-y-1/2" }, format9(zonedNow, formats.time, { locale }))
|
|
1346
|
+
)), /* @__PURE__ */ React10.createElement("div", { className: "flex-1 relative" }, HOURS2.map((hour) => /* @__PURE__ */ React10.createElement(
|
|
1347
|
+
"div",
|
|
1348
|
+
{
|
|
1349
|
+
key: hour,
|
|
1350
|
+
className: "border-b-[0.5px] border-dashed border-border/20 box-border relative hover:bg-accent/5 transition-colors",
|
|
1351
|
+
style: { height: HOUR_HEIGHT2 }
|
|
1352
|
+
},
|
|
1353
|
+
QUARTERS2.map((minute) => {
|
|
1354
|
+
const cellDate = slotDate2(currentDate, hour, minute);
|
|
1355
|
+
return /* @__PURE__ */ React10.createElement(
|
|
1356
|
+
DroppableCell,
|
|
1357
|
+
{
|
|
1358
|
+
key: minute,
|
|
1359
|
+
id: cellDate.toISOString(),
|
|
1360
|
+
date: cellDate,
|
|
1361
|
+
className: "w-full absolute left-0 right-0 z-0 transition-colors",
|
|
1362
|
+
style: {
|
|
1363
|
+
height: "25%",
|
|
1364
|
+
top: `${minute / 60 * 100}%`
|
|
999
1365
|
},
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
);
|
|
1010
|
-
|
|
1011
|
-
const
|
|
1012
|
-
|
|
1013
|
-
const s1 = getZonedDate(event.start).getTime();
|
|
1014
|
-
const e1 = getZonedDate(event.end).getTime();
|
|
1015
|
-
const s2 = getZonedDate(e.start).getTime();
|
|
1016
|
-
const e2 = getZonedDate(e.end).getTime();
|
|
1017
|
-
return s1 < e2 && e1 > s2;
|
|
1018
|
-
});
|
|
1019
|
-
const group = [event, ...overlappingEvents].sort(
|
|
1020
|
-
(a, b) => getZonedDate(a.start).getTime() - getZonedDate(b.start).getTime() || (a.id > b.id ? 1 : -1)
|
|
1021
|
-
);
|
|
1022
|
-
const index = group.findIndex((e) => e.id === event.id);
|
|
1023
|
-
const count = group.length;
|
|
1024
|
-
const widthPercent = 100 / count;
|
|
1025
|
-
const leftPercent = index * widthPercent;
|
|
1026
|
-
const zonedStart = getZonedDate(event.start);
|
|
1027
|
-
const zonedEnd = getZonedDate(event.end);
|
|
1028
|
-
const startMinutes = zonedStart.getHours() * 60 + zonedStart.getMinutes();
|
|
1029
|
-
const durationMinutes = differenceInMinutes2(zonedEnd, zonedStart);
|
|
1030
|
-
const top = startMinutes / 60 * hourHeight;
|
|
1031
|
-
const height = durationMinutes / 60 * hourHeight;
|
|
1366
|
+
onSlotClick: onTimeSlotClick
|
|
1367
|
+
}
|
|
1368
|
+
);
|
|
1369
|
+
})
|
|
1370
|
+
)), positioned.map(({ event, start, end, column, columns }) => {
|
|
1371
|
+
const widthPercent = 100 / columns;
|
|
1372
|
+
const leftPercent = column * widthPercent;
|
|
1373
|
+
const zonedStart = toDisplay(event.start, timezone);
|
|
1374
|
+
const zonedEnd = toDisplay(event.end, timezone);
|
|
1375
|
+
const startMinutes = start.getHours() * 60 + start.getMinutes();
|
|
1376
|
+
const durationMinutes = differenceInMinutes2(end, start);
|
|
1377
|
+
const top = startMinutes / 60 * HOUR_HEIGHT2;
|
|
1378
|
+
const height = durationMinutes / 60 * HOUR_HEIGHT2;
|
|
1032
1379
|
const isShortEvent = durationMinutes < 45;
|
|
1380
|
+
const isLastSegment = end.getTime() === zonedEnd.getTime();
|
|
1033
1381
|
return /* @__PURE__ */ React10.createElement(
|
|
1034
1382
|
DraggableEvent,
|
|
1035
1383
|
{
|
|
1036
|
-
key: `${event.id}-${
|
|
1384
|
+
key: `${event.id}-${dayKey2}`,
|
|
1385
|
+
dragId: `${event.id}@${dayKey2}`,
|
|
1386
|
+
grabOffsetMs: start.getTime() - zonedStart.getTime(),
|
|
1037
1387
|
event,
|
|
1038
1388
|
className: `absolute z-10 transition-all ${readonly ? "cursor-default" : ""}`,
|
|
1039
1389
|
style: {
|
|
@@ -1041,15 +1391,15 @@ var DayView = ({
|
|
|
1041
1391
|
height: `${Math.max(height, 28)}px`,
|
|
1042
1392
|
left: `calc(${leftPercent}% + 4px)`,
|
|
1043
1393
|
width: `calc(${widthPercent}% - 8px)`,
|
|
1044
|
-
paddingRight:
|
|
1394
|
+
paddingRight: columns > 1 ? "2px" : "0"
|
|
1045
1395
|
}
|
|
1046
1396
|
},
|
|
1047
1397
|
/* @__PURE__ */ React10.createElement(
|
|
1048
1398
|
ResizableEvent,
|
|
1049
1399
|
{
|
|
1050
|
-
readonly,
|
|
1400
|
+
readonly: readonly || !isLastSegment,
|
|
1051
1401
|
event,
|
|
1052
|
-
hourHeight,
|
|
1402
|
+
hourHeight: HOUR_HEIGHT2,
|
|
1053
1403
|
onResize: onEventResize,
|
|
1054
1404
|
className: "h-full",
|
|
1055
1405
|
style: { height: "100%" }
|
|
@@ -1063,44 +1413,43 @@ var DayView = ({
|
|
|
1063
1413
|
readonly || isEventDragDisabled(event) ? "cursor-default" : "cursor-grab active:cursor-grabbing",
|
|
1064
1414
|
!event.color && "bg-primary/10 border-primary/20",
|
|
1065
1415
|
isShortEvent ? "px-2 flex items-center" : "px-3 py-2",
|
|
1066
|
-
|
|
1416
|
+
columns > 1 && "border-l-4"
|
|
1067
1417
|
),
|
|
1068
1418
|
style: {
|
|
1069
1419
|
backgroundColor: event.color ? `${event.color}15` : void 0,
|
|
1070
1420
|
borderColor: event.color ? `${event.color}30` : void 0,
|
|
1071
1421
|
borderLeftColor: event.color || "var(--primary)",
|
|
1072
|
-
borderLeftWidth:
|
|
1422
|
+
borderLeftWidth: columns > 1 ? "4px" : "3px"
|
|
1073
1423
|
},
|
|
1074
1424
|
onClick: (e) => {
|
|
1075
1425
|
e.stopPropagation();
|
|
1076
1426
|
onEventClick?.(event);
|
|
1077
1427
|
},
|
|
1078
|
-
title:
|
|
1428
|
+
title: columns > 1 ? `${event.title} (${column + 1}/${columns})` : void 0
|
|
1079
1429
|
},
|
|
1080
1430
|
/* @__PURE__ */ React10.createElement("div", { className: "flex flex-col h-full overflow-hidden w-full" }, /* @__PURE__ */ React10.createElement("div", { className: cn(
|
|
1081
1431
|
"font-semibold truncate leading-tight",
|
|
1082
1432
|
isShortEvent ? "text-xs" : "text-sm",
|
|
1083
1433
|
event.color ? "text-foreground" : "text-foreground/90"
|
|
1084
|
-
) }, event.title), !isShortEvent && /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement("div", { className: "text-xs text-muted-foreground mt-0.5 font-medium" },
|
|
1434
|
+
) }, event.title), !isShortEvent && /* @__PURE__ */ React10.createElement(React10.Fragment, null, /* @__PURE__ */ React10.createElement("div", { className: "text-xs text-muted-foreground mt-0.5 font-medium" }, format9(zonedStart, formats.time, { locale }), " - ", format9(zonedEnd, formats.time, { locale })), event.description && height > 60 && /* @__PURE__ */ React10.createElement("div", { className: "text-xs text-muted-foreground/80 mt-1 line-clamp-2" }, event.description)), columns > 1 && !isShortEvent && /* @__PURE__ */ React10.createElement("div", { className: "absolute top-1.5 right-1.5 bg-background/90 backdrop-blur-sm rounded-full w-5 h-5 flex items-center justify-center text-[10px] font-bold text-muted-foreground border border-border shadow-sm" }, columns))
|
|
1085
1435
|
)
|
|
1086
1436
|
)
|
|
1087
1437
|
);
|
|
1088
|
-
}),
|
|
1438
|
+
}), isToday2 && /* @__PURE__ */ React10.createElement(
|
|
1089
1439
|
"div",
|
|
1090
1440
|
{
|
|
1091
1441
|
className: "absolute left-0 right-0 z-20 pointer-events-none flex items-center",
|
|
1092
|
-
style: {
|
|
1093
|
-
top: `${(zonedNow.getHours() * 60 + zonedNow.getMinutes()) / 60 * hourHeight}px`
|
|
1094
|
-
}
|
|
1442
|
+
style: { top: `${nowTop}px` }
|
|
1095
1443
|
},
|
|
1096
1444
|
/* @__PURE__ */ React10.createElement("div", { className: "h-[2px] w-full bg-gradient-to-r from-primary via-primary to-primary/50" }),
|
|
1097
1445
|
/* @__PURE__ */ React10.createElement("div", { className: "absolute -left-1.5 w-3 h-3 bg-primary rounded-full shadow-lg shadow-primary/40 ring-2 ring-background animate-pulse" })
|
|
1098
1446
|
)))));
|
|
1099
1447
|
};
|
|
1448
|
+
var DayView = React10.memo(DayViewImpl);
|
|
1100
1449
|
|
|
1101
1450
|
// src/views/AgendaView.tsx
|
|
1102
1451
|
import React12, { useMemo as useMemo2 } from "react";
|
|
1103
|
-
import { format as
|
|
1452
|
+
import { format as format10, isSameDay as isSameDay6, addDays as addDays4, startOfDay as startOfDay4, differenceInMinutes as differenceInMinutes3 } from "date-fns";
|
|
1104
1453
|
|
|
1105
1454
|
// src/components/EmptyState.tsx
|
|
1106
1455
|
import React11 from "react";
|
|
@@ -1126,10 +1475,10 @@ var formatDuration = (start, end) => {
|
|
|
1126
1475
|
if (remainingMinutes === 0) return `${hours}h`;
|
|
1127
1476
|
return `${hours}h ${remainingMinutes}m`;
|
|
1128
1477
|
};
|
|
1129
|
-
var getDateLabel = (date, locale, translations) => {
|
|
1130
|
-
if (
|
|
1131
|
-
if (
|
|
1132
|
-
return
|
|
1478
|
+
var getDateLabel = (date, today, locale, translations) => {
|
|
1479
|
+
if (isSameDay6(date, today)) return translations?.today || "Today";
|
|
1480
|
+
if (isSameDay6(date, addDays4(today, 1))) return translations?.tomorrow || "Tomorrow";
|
|
1481
|
+
return format10(date, "EEEE", { locale });
|
|
1133
1482
|
};
|
|
1134
1483
|
var AgendaView = ({
|
|
1135
1484
|
currentDate,
|
|
@@ -1137,24 +1486,24 @@ var AgendaView = ({
|
|
|
1137
1486
|
onEventClick,
|
|
1138
1487
|
onCreateEvent,
|
|
1139
1488
|
locale,
|
|
1140
|
-
translations
|
|
1489
|
+
translations,
|
|
1490
|
+
timezone
|
|
1141
1491
|
}) => {
|
|
1142
1492
|
const is24h = uses24HourClock(locale);
|
|
1143
|
-
const
|
|
1144
|
-
const
|
|
1493
|
+
const { time: timePattern, compact: compactTimePattern } = timeFormats(locale);
|
|
1494
|
+
const today = displayToday(timezone);
|
|
1145
1495
|
const groupedEvents = useMemo2(() => {
|
|
1146
|
-
const
|
|
1496
|
+
const byDay = eventsByStartDay(events, timezone);
|
|
1147
1497
|
const groups = [];
|
|
1148
|
-
const sortedEvents = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
1149
1498
|
for (let i = 0; i < 30; i++) {
|
|
1150
|
-
const day =
|
|
1151
|
-
const dayEvents =
|
|
1152
|
-
if (dayEvents
|
|
1153
|
-
groups.push({ date: day, events: dayEvents });
|
|
1499
|
+
const day = addDays4(startOfDay4(currentDate), i);
|
|
1500
|
+
const dayEvents = byDay.get(format10(day, "yyyy-MM-dd"));
|
|
1501
|
+
if (dayEvents?.length) {
|
|
1502
|
+
groups.push({ date: day, events: [...dayEvents].sort((a, b) => a.start.getTime() - b.start.getTime()) });
|
|
1154
1503
|
}
|
|
1155
1504
|
}
|
|
1156
1505
|
return groups;
|
|
1157
|
-
}, [currentDate, events]);
|
|
1506
|
+
}, [currentDate, events, timezone]);
|
|
1158
1507
|
const container = {
|
|
1159
1508
|
hidden: { opacity: 0 },
|
|
1160
1509
|
show: {
|
|
@@ -1185,11 +1534,11 @@ var AgendaView = ({
|
|
|
1185
1534
|
},
|
|
1186
1535
|
/* @__PURE__ */ React12.createElement("div", { className: "sticky top-0 bg-background/95 backdrop-blur-md py-4 z-10 border-b border-border/50" }, /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-4" }, /* @__PURE__ */ React12.createElement("div", { className: cn(
|
|
1187
1536
|
"flex flex-col items-center justify-center w-16 h-16 rounded-2xl transition-all",
|
|
1188
|
-
|
|
1189
|
-
) }, /* @__PURE__ */ React12.createElement("span", { className: "text-2xl font-bold leading-none" },
|
|
1537
|
+
isSameDay6(group.date, today) ? "bg-primary text-primary-foreground shadow-lg shadow-primary/30" : "bg-muted/50 text-foreground"
|
|
1538
|
+
) }, /* @__PURE__ */ React12.createElement("span", { className: "text-2xl font-bold leading-none" }, format10(group.date, "d")), /* @__PURE__ */ React12.createElement("span", { className: "text-xs font-medium uppercase tracking-wide opacity-80" }, format10(group.date, "MMM", { locale }))), /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React12.createElement("span", { className: cn(
|
|
1190
1539
|
"text-lg font-semibold",
|
|
1191
|
-
|
|
1192
|
-
) }, getDateLabel(group.date, locale, translations)), /* @__PURE__ */ React12.createElement("span", { className: "text-sm text-muted-foreground" }, group.events.length, " ", group.events.length !== 1 ? translations?.eventPlural || "events" : translations?.eventSingular || "event")))),
|
|
1540
|
+
isSameDay6(group.date, today) && "text-primary"
|
|
1541
|
+
) }, getDateLabel(group.date, today, locale, translations)), /* @__PURE__ */ React12.createElement("span", { className: "text-sm text-muted-foreground" }, group.events.length, " ", group.events.length !== 1 ? translations?.eventPlural || "events" : translations?.eventSingular || "event")))),
|
|
1193
1542
|
/* @__PURE__ */ React12.createElement("div", { className: "py-4 space-y-3" }, group.events.map((event) => /* @__PURE__ */ React12.createElement(
|
|
1194
1543
|
motion.div,
|
|
1195
1544
|
{
|
|
@@ -1211,14 +1560,14 @@ var AgendaView = ({
|
|
|
1211
1560
|
style: { backgroundColor: event.color || "var(--primary)" }
|
|
1212
1561
|
}
|
|
1213
1562
|
),
|
|
1214
|
-
/* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center min-w-[70px] pl-2" }, event.allDay ? /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ React12.createElement("span", { className: "text-xs font-semibold text-muted-foreground bg-muted/80 px-2.5 py-1 rounded-full" }, translations?.allDay || "All Day")) : /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ React12.createElement("span", { className: "text-base font-semibold text-foreground" },
|
|
1563
|
+
/* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center min-w-[70px] pl-2" }, event.allDay ? /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ React12.createElement("span", { className: "text-xs font-semibold text-muted-foreground bg-muted/80 px-2.5 py-1 rounded-full" }, translations?.allDay || "All Day")) : /* @__PURE__ */ React12.createElement("div", { className: "flex flex-col items-center" }, /* @__PURE__ */ React12.createElement("span", { className: "text-base font-semibold text-foreground" }, format10(toDisplay(event.start, timezone), compactTimePattern, { locale })), !is24h && /* @__PURE__ */ React12.createElement("span", { className: "text-xs text-muted-foreground uppercase" }, format10(toDisplay(event.start, timezone), "a")), /* @__PURE__ */ React12.createElement("div", { className: "w-px h-3 bg-border my-1" }), /* @__PURE__ */ React12.createElement("span", { className: "text-xs text-muted-foreground/70 font-medium" }, formatDuration(event.start, event.end)))),
|
|
1215
1564
|
/* @__PURE__ */ React12.createElement("div", { className: "flex-1 min-w-0 space-y-2" }, /* @__PURE__ */ React12.createElement("div", { className: "flex items-start justify-between gap-2" }, /* @__PURE__ */ React12.createElement("h4", { className: "text-base font-semibold text-foreground group-hover:text-primary transition-colors line-clamp-1" }, event.title), /* @__PURE__ */ React12.createElement(
|
|
1216
1565
|
"div",
|
|
1217
1566
|
{
|
|
1218
1567
|
className: "w-3 h-3 rounded-full shrink-0 mt-1.5",
|
|
1219
1568
|
style: { backgroundColor: event.color || "var(--primary)" }
|
|
1220
1569
|
}
|
|
1221
|
-
)), event.description && /* @__PURE__ */ React12.createElement("p", { className: "text-sm text-muted-foreground line-clamp-2 leading-relaxed" }, event.description), /* @__PURE__ */ React12.createElement("div", { className: "flex flex-wrap items-center gap-3 pt-1" }, !event.allDay && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Clock, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null,
|
|
1570
|
+
)), event.description && /* @__PURE__ */ React12.createElement("p", { className: "text-sm text-muted-foreground line-clamp-2 leading-relaxed" }, event.description), /* @__PURE__ */ React12.createElement("div", { className: "flex flex-wrap items-center gap-3 pt-1" }, !event.allDay && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Clock, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, format10(toDisplay(event.start, timezone), timePattern, { locale }), " - ", format10(toDisplay(event.end, timezone), timePattern, { locale }))), event.guests && event.guests.length > 0 && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Users, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, event.guests.length, " ", event.guests.length !== 1 ? translations?.guestsCount || "guests" : translations?.guestCount || "guest")), event.attachments && event.attachments.length > 0 && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Paperclip, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, event.attachments.length)), event.reminders && event.reminders.length > 0 && /* @__PURE__ */ React12.createElement("div", { className: "flex items-center gap-1.5 text-xs text-muted-foreground" }, /* @__PURE__ */ React12.createElement(Bell, { className: "w-3.5 h-3.5" }), /* @__PURE__ */ React12.createElement("span", null, event.reminders.length)))),
|
|
1222
1571
|
/* @__PURE__ */ React12.createElement("div", { className: "absolute right-4 top-1/2 -translate-y-1/2 opacity-0 group-hover:opacity-100 transition-opacity" }, /* @__PURE__ */ React12.createElement("svg", { className: "w-5 h-5 text-muted-foreground", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React12.createElement("path", { d: "M9 18l6-6-6-6" })))
|
|
1223
1572
|
)))
|
|
1224
1573
|
))
|
|
@@ -1227,23 +1576,24 @@ var AgendaView = ({
|
|
|
1227
1576
|
|
|
1228
1577
|
// src/views/ResourceView.tsx
|
|
1229
1578
|
import React13, { useRef as useRef2 } from "react";
|
|
1230
|
-
import { format as
|
|
1579
|
+
import { format as format11, startOfDay as startOfDay5, differenceInMinutes as differenceInMinutes4 } from "date-fns";
|
|
1231
1580
|
var ResourceView = ({
|
|
1232
1581
|
currentDate,
|
|
1233
1582
|
events,
|
|
1234
1583
|
resources,
|
|
1235
1584
|
onEventClick,
|
|
1236
1585
|
onTimeSlotClick,
|
|
1237
|
-
locale
|
|
1586
|
+
locale,
|
|
1587
|
+
timezone
|
|
1238
1588
|
}) => {
|
|
1239
1589
|
const containerRef = useRef2(null);
|
|
1240
1590
|
const hours = Array.from({ length: 24 }, (_, i) => i);
|
|
1241
1591
|
const hourWidth = 100;
|
|
1242
|
-
const timeFormat =
|
|
1592
|
+
const timeFormat = timeFormats(locale).hour;
|
|
1243
1593
|
const getEventStyle = (event) => {
|
|
1244
|
-
const start =
|
|
1245
|
-
const end =
|
|
1246
|
-
const dayStart =
|
|
1594
|
+
const start = toDisplay(event.start, timezone);
|
|
1595
|
+
const end = toDisplay(event.end, timezone);
|
|
1596
|
+
const dayStart = startOfDay5(currentDate);
|
|
1247
1597
|
const startMinutes = differenceInMinutes4(start, dayStart);
|
|
1248
1598
|
const durationMinutes = differenceInMinutes4(end, start);
|
|
1249
1599
|
const left = startMinutes / 60 * hourWidth;
|
|
@@ -1261,10 +1611,10 @@ var ResourceView = ({
|
|
|
1261
1611
|
className: "border-r border-border/50 text-xs text-muted-foreground p-2 font-medium shrink-0",
|
|
1262
1612
|
style: { width: hourWidth }
|
|
1263
1613
|
},
|
|
1264
|
-
|
|
1614
|
+
format11((/* @__PURE__ */ new Date()).setHours(hour, 0, 0, 0), timeFormat, { locale })
|
|
1265
1615
|
))))), /* @__PURE__ */ React13.createElement("div", { className: "flex-1 overflow-auto relative", ref: containerRef }, /* @__PURE__ */ React13.createElement("div", { className: "min-w-fit" }, resources.map((resource) => {
|
|
1266
1616
|
const resourceEvents = events.filter(
|
|
1267
|
-
(e) => e.resourceId === resource.id &&
|
|
1617
|
+
(e) => e.resourceId === resource.id && isSameDisplayDay(e.start, currentDate, timezone)
|
|
1268
1618
|
);
|
|
1269
1619
|
return /* @__PURE__ */ React13.createElement("div", { key: resource.id, className: "flex border-b border-border min-h-[100px]" }, /* @__PURE__ */ React13.createElement("div", { className: "w-48 shrink-0 border-r border-border p-4 bg-background sticky left-0 z-10 flex items-center gap-3" }, resource.avatar ? /* @__PURE__ */ React13.createElement("img", { src: resource.avatar, alt: resource.label, className: "w-8 h-8 rounded-full object-cover" }) : /* @__PURE__ */ React13.createElement("div", { className: "w-8 h-8 rounded-full bg-primary/10 flex items-center justify-center text-xs font-bold text-primary" }, resource.label.substring(0, 2).toUpperCase()), /* @__PURE__ */ React13.createElement("div", { className: "flex flex-col" }, /* @__PURE__ */ React13.createElement("span", { className: "text-sm font-medium" }, resource.label), /* @__PURE__ */ React13.createElement("span", { className: "text-xs text-muted-foreground" }, "ID: ", resource.id))), /* @__PURE__ */ React13.createElement("div", { className: "relative flex", style: { width: hours.length * hourWidth } }, hours.map((hour) => /* @__PURE__ */ React13.createElement(
|
|
1270
1620
|
"div",
|
|
@@ -1274,20 +1624,18 @@ var ResourceView = ({
|
|
|
1274
1624
|
style: { width: hourWidth }
|
|
1275
1625
|
},
|
|
1276
1626
|
[0, 15, 30, 45].map((minute) => {
|
|
1277
|
-
const
|
|
1278
|
-
|
|
1279
|
-
const slotId = `${resource.id}-${
|
|
1627
|
+
const slotDate3 = new Date(currentDate);
|
|
1628
|
+
slotDate3.setHours(hour, minute, 0, 0);
|
|
1629
|
+
const slotId = `${resource.id}-${slotDate3.toISOString()}`;
|
|
1280
1630
|
return /* @__PURE__ */ React13.createElement(
|
|
1281
1631
|
DroppableCell,
|
|
1282
1632
|
{
|
|
1283
1633
|
key: minute,
|
|
1284
1634
|
id: slotId,
|
|
1285
|
-
date:
|
|
1635
|
+
date: slotDate3,
|
|
1286
1636
|
resourceId: resource.id,
|
|
1287
1637
|
className: "h-full flex-1 border-r border-border/10 last:border-border/30 hover:bg-accent/10 transition-colors",
|
|
1288
|
-
|
|
1289
|
-
onTimeSlotClick?.(slotDate, resource.id);
|
|
1290
|
-
}
|
|
1638
|
+
onSlotClick: onTimeSlotClick
|
|
1291
1639
|
}
|
|
1292
1640
|
);
|
|
1293
1641
|
})
|
|
@@ -1373,7 +1721,7 @@ var Modal = ({
|
|
|
1373
1721
|
};
|
|
1374
1722
|
|
|
1375
1723
|
// src/components/EventModal.tsx
|
|
1376
|
-
import { format as
|
|
1724
|
+
import { format as format12, differenceInMinutes as differenceInMinutes5 } from "date-fns";
|
|
1377
1725
|
import { motion as motion2, AnimatePresence } from "framer-motion";
|
|
1378
1726
|
import {
|
|
1379
1727
|
Clock as Clock2,
|
|
@@ -1530,7 +1878,7 @@ var EventModal = ({
|
|
|
1530
1878
|
};
|
|
1531
1879
|
const formatDateForInput = (date) => {
|
|
1532
1880
|
if (!date) return "";
|
|
1533
|
-
return
|
|
1881
|
+
return format12(date, "yyyy-MM-dd'T'HH:mm");
|
|
1534
1882
|
};
|
|
1535
1883
|
const handleDateChange = (field, value) => {
|
|
1536
1884
|
const date = new Date(value);
|
|
@@ -1627,7 +1975,7 @@ var EventModal = ({
|
|
|
1627
1975
|
/* @__PURE__ */ React15.createElement(X2, { className: "w-4 h-4" })
|
|
1628
1976
|
))),
|
|
1629
1977
|
/* @__PURE__ */ React15.createElement("h1", { className: "text-2xl font-bold text-foreground mb-3 leading-tight" }, event?.title || "(No title)"),
|
|
1630
|
-
/* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-3 text-sm" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-background/60 backdrop-blur-sm rounded-lg border border-border/30" }, /* @__PURE__ */ React15.createElement(Calendar2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "font-medium" }, event?.start &&
|
|
1978
|
+
/* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-3 text-sm" }, /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-background/60 backdrop-blur-sm rounded-lg border border-border/30" }, /* @__PURE__ */ React15.createElement(Calendar2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "font-medium" }, event?.start && format12(new Date(event.start), "EEE, MMM d"))), /* @__PURE__ */ React15.createElement("div", { className: "flex items-center gap-2 px-3 py-1.5 bg-background/60 backdrop-blur-sm rounded-lg border border-border/30" }, /* @__PURE__ */ React15.createElement(Clock2, { className: "w-4 h-4 text-muted-foreground" }), /* @__PURE__ */ React15.createElement("span", { className: "font-medium" }, event?.start && format12(new Date(event.start), "h:mm a"), " \u2013 ", event?.end && format12(new Date(event.end), "h:mm a"))))
|
|
1631
1979
|
), /* @__PURE__ */ React15.createElement("div", { className: "px-6 pb-6 space-y-4 overflow-y-auto flex-1" }, event?.location && /* @__PURE__ */ React15.createElement(
|
|
1632
1980
|
motion2.div,
|
|
1633
1981
|
{
|
|
@@ -2021,7 +2369,7 @@ var DayViewSkeleton = () => /* @__PURE__ */ React16.createElement("div", { class
|
|
|
2021
2369
|
var AgendaViewSkeleton = () => /* @__PURE__ */ React16.createElement("div", { className: "h-full bg-background border border-border/50 rounded-2xl overflow-hidden p-6" }, /* @__PURE__ */ React16.createElement("div", { className: "space-y-6" }, Array.from({ length: 4 }).map((_, dayIdx) => /* @__PURE__ */ React16.createElement("div", { key: dayIdx }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-5 w-40 mb-4" }), /* @__PURE__ */ React16.createElement("div", { className: "space-y-3" }, Array.from({ length: 2 + Math.floor(Math.random() * 3) }).map((_2, eventIdx) => /* @__PURE__ */ React16.createElement("div", { key: eventIdx, className: "flex items-center gap-4 p-3 rounded-xl bg-muted/10" }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-10 w-10 rounded-lg" }), /* @__PURE__ */ React16.createElement("div", { className: "flex-1 space-y-2" }, /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-4 w-3/4" }), /* @__PURE__ */ React16.createElement(Skeleton, { className: "h-3 w-1/2" })))))))));
|
|
2022
2370
|
|
|
2023
2371
|
// src/components/ContextMenu.tsx
|
|
2024
|
-
import React17, { useState as useState4, useCallback as
|
|
2372
|
+
import React17, { useState as useState4, useCallback as useCallback2, useEffect as useEffect4, useRef as useRef4 } from "react";
|
|
2025
2373
|
import { Edit3, Trash2 as Trash22, Copy } from "lucide-react";
|
|
2026
2374
|
import { AnimatePresence as AnimatePresence2, motion as motion3 } from "framer-motion";
|
|
2027
2375
|
var EventContextMenu = ({
|
|
@@ -2134,7 +2482,7 @@ var EventContextMenu = ({
|
|
|
2134
2482
|
};
|
|
2135
2483
|
var useEventContextMenu = () => {
|
|
2136
2484
|
const [contextMenu, setContextMenu] = useState4({ event: null, position: null });
|
|
2137
|
-
const openContextMenu =
|
|
2485
|
+
const openContextMenu = useCallback2((event, e) => {
|
|
2138
2486
|
e.preventDefault();
|
|
2139
2487
|
e.stopPropagation();
|
|
2140
2488
|
setContextMenu({
|
|
@@ -2142,7 +2490,7 @@ var useEventContextMenu = () => {
|
|
|
2142
2490
|
position: { x: e.clientX, y: e.clientY }
|
|
2143
2491
|
});
|
|
2144
2492
|
}, []);
|
|
2145
|
-
const closeContextMenu =
|
|
2493
|
+
const closeContextMenu = useCallback2(() => {
|
|
2146
2494
|
setContextMenu({ event: null, position: null });
|
|
2147
2495
|
}, []);
|
|
2148
2496
|
return {
|
|
@@ -2153,146 +2501,6 @@ var useEventContextMenu = () => {
|
|
|
2153
2501
|
};
|
|
2154
2502
|
};
|
|
2155
2503
|
|
|
2156
|
-
// src/locales/index.ts
|
|
2157
|
-
var en = {
|
|
2158
|
-
today: "Today",
|
|
2159
|
-
month: "Month",
|
|
2160
|
-
week: "Week",
|
|
2161
|
-
day: "Day",
|
|
2162
|
-
agenda: "Agenda",
|
|
2163
|
-
resource: "Resources",
|
|
2164
|
-
createEvent: "Create Event",
|
|
2165
|
-
editEvent: "Edit Event",
|
|
2166
|
-
delete: "Delete",
|
|
2167
|
-
save: "Save",
|
|
2168
|
-
cancel: "Cancel",
|
|
2169
|
-
title: "Add title",
|
|
2170
|
-
start: "Start",
|
|
2171
|
-
end: "End",
|
|
2172
|
-
allDay: "All Day",
|
|
2173
|
-
description: "Description",
|
|
2174
|
-
repeat: "Repeat",
|
|
2175
|
-
noRepeat: "Does not repeat",
|
|
2176
|
-
selectCalendar: "Select Calendar",
|
|
2177
|
-
selectType: "Select Type",
|
|
2178
|
-
daily: "Daily",
|
|
2179
|
-
weekly: "Weekly",
|
|
2180
|
-
monthly: "Monthly",
|
|
2181
|
-
yearly: "Yearly",
|
|
2182
|
-
search: "Search events...",
|
|
2183
|
-
allTypes: "All Types",
|
|
2184
|
-
timezone: "Time zone",
|
|
2185
|
-
localTime: "Local Time",
|
|
2186
|
-
create: "Create",
|
|
2187
|
-
calendars: "Calendars",
|
|
2188
|
-
event: "Event",
|
|
2189
|
-
task: "Task",
|
|
2190
|
-
appointmentSchedule: "Appointment schedule",
|
|
2191
|
-
new: "New",
|
|
2192
|
-
dateAndTime: "Date & Time",
|
|
2193
|
-
whosJoining: "Who's joining?",
|
|
2194
|
-
suggestedTimes: "Suggested times for guests",
|
|
2195
|
-
viewSuggestions: "View suggestions",
|
|
2196
|
-
whereWillItBe: "Where will it be?",
|
|
2197
|
-
location: "Enter address",
|
|
2198
|
-
descriptionAndAttachments: "Description",
|
|
2199
|
-
dragAndDrop: "Drag & drop files or",
|
|
2200
|
-
guests: "Guests",
|
|
2201
|
-
addGuests: "Add guests",
|
|
2202
|
-
addAttachment: "Upload files",
|
|
2203
|
-
moreOptions: "More options",
|
|
2204
|
-
doesNotRepeat: "Does not repeat",
|
|
2205
|
-
locationHelpText: 'Enter a physical address (e.g. "123 Main St") to generate the map.',
|
|
2206
|
-
notes: "Notes...",
|
|
2207
|
-
guestCount: "guest",
|
|
2208
|
-
guestsCount: "guests",
|
|
2209
|
-
repeatFor: "Repeat for",
|
|
2210
|
-
times: "times",
|
|
2211
|
-
until: "Until",
|
|
2212
|
-
endRepeat: "End repeat",
|
|
2213
|
-
never: "Never",
|
|
2214
|
-
afterOccurrences: "After occurrences",
|
|
2215
|
-
onDate: "On date",
|
|
2216
|
-
tomorrow: "Tomorrow",
|
|
2217
|
-
edit: "Edit",
|
|
2218
|
-
duplicate: "Duplicate",
|
|
2219
|
-
eventSingular: "event",
|
|
2220
|
-
eventPlural: "events",
|
|
2221
|
-
allCaughtUp: "All caught up!",
|
|
2222
|
-
noEventsScheduled: "No events scheduled",
|
|
2223
|
-
noUpcoming: "Your schedule is clear",
|
|
2224
|
-
noUpcomingHint: "No upcoming events to show. Create a new event to start planning."
|
|
2225
|
-
};
|
|
2226
|
-
var fr = {
|
|
2227
|
-
today: "Aujourd'hui",
|
|
2228
|
-
month: "Mois",
|
|
2229
|
-
week: "Semaine",
|
|
2230
|
-
day: "Jour",
|
|
2231
|
-
agenda: "Agenda",
|
|
2232
|
-
resource: "Ressources",
|
|
2233
|
-
createEvent: "Cr\xE9er un \xE9v\xE9nement",
|
|
2234
|
-
editEvent: "Modifier l'\xE9v\xE9nement",
|
|
2235
|
-
delete: "Supprimer",
|
|
2236
|
-
save: "Enregistrer",
|
|
2237
|
-
cancel: "Annuler",
|
|
2238
|
-
title: "Ajouter un titre",
|
|
2239
|
-
start: "D\xE9but",
|
|
2240
|
-
end: "Fin",
|
|
2241
|
-
allDay: "Toute la journ\xE9e",
|
|
2242
|
-
description: "Description",
|
|
2243
|
-
repeat: "R\xE9p\xE9ter",
|
|
2244
|
-
noRepeat: "Ne se r\xE9p\xE8te pas",
|
|
2245
|
-
selectCalendar: "S\xE9lectionner un calendrier",
|
|
2246
|
-
selectType: "S\xE9lectionner un type",
|
|
2247
|
-
daily: "Quotidien",
|
|
2248
|
-
weekly: "Hebdomadaire",
|
|
2249
|
-
monthly: "Mensuel",
|
|
2250
|
-
yearly: "Annuel",
|
|
2251
|
-
search: "Rechercher...",
|
|
2252
|
-
allTypes: "Tous les types",
|
|
2253
|
-
timezone: "Fuseau horaire",
|
|
2254
|
-
localTime: "Heure locale",
|
|
2255
|
-
create: "Cr\xE9er",
|
|
2256
|
-
calendars: "Calendriers",
|
|
2257
|
-
event: "\xC9v\xE9nement",
|
|
2258
|
-
task: "T\xE2che",
|
|
2259
|
-
appointmentSchedule: "Planning de rendez-vous",
|
|
2260
|
-
new: "Nouveau",
|
|
2261
|
-
dateAndTime: "Date et heure",
|
|
2262
|
-
whosJoining: "Qui participe ?",
|
|
2263
|
-
suggestedTimes: "Horaires sugg\xE9r\xE9s",
|
|
2264
|
-
viewSuggestions: "Voir les suggestions",
|
|
2265
|
-
whereWillItBe: "O\xF9 aura lieu ?",
|
|
2266
|
-
location: "Entrer une adresse",
|
|
2267
|
-
descriptionAndAttachments: "Description",
|
|
2268
|
-
dragAndDrop: "Glisser-d\xE9poser des fichiers ou",
|
|
2269
|
-
guests: "Invit\xE9s",
|
|
2270
|
-
addGuests: "Ajouter des invit\xE9s",
|
|
2271
|
-
addAttachment: "T\xE9l\xE9charger des fichiers",
|
|
2272
|
-
moreOptions: "Plus d'options",
|
|
2273
|
-
doesNotRepeat: "Ne se r\xE9p\xE8te pas",
|
|
2274
|
-
locationHelpText: 'Entrez une adresse physique (ex: "123 Rue Principale") pour g\xE9n\xE9rer la carte.',
|
|
2275
|
-
notes: "Notes...",
|
|
2276
|
-
guestCount: "invit\xE9",
|
|
2277
|
-
guestsCount: "invit\xE9s",
|
|
2278
|
-
repeatFor: "R\xE9p\xE9ter pendant",
|
|
2279
|
-
times: "fois",
|
|
2280
|
-
until: "Jusqu'au",
|
|
2281
|
-
endRepeat: "Fin de r\xE9p\xE9tition",
|
|
2282
|
-
never: "Jamais",
|
|
2283
|
-
afterOccurrences: "Apr\xE8s occurrences",
|
|
2284
|
-
onDate: "\xC0 la date",
|
|
2285
|
-
tomorrow: "Demain",
|
|
2286
|
-
edit: "Modifier",
|
|
2287
|
-
duplicate: "Dupliquer",
|
|
2288
|
-
eventSingular: "\xE9v\xE9nement",
|
|
2289
|
-
eventPlural: "\xE9v\xE9nements",
|
|
2290
|
-
allCaughtUp: "Tout est \xE0 jour !",
|
|
2291
|
-
noEventsScheduled: "Aucun \xE9v\xE9nement pr\xE9vu",
|
|
2292
|
-
noUpcoming: "Votre planning est libre",
|
|
2293
|
-
noUpcomingHint: "Aucun \xE9v\xE9nement \xE0 venir. Cr\xE9ez un nouvel \xE9v\xE9nement pour commencer \xE0 planifier."
|
|
2294
|
-
};
|
|
2295
|
-
|
|
2296
2504
|
// src/lib/theme.ts
|
|
2297
2505
|
function colorToHsl(color) {
|
|
2298
2506
|
let r = 0, g = 0, b = 0;
|
|
@@ -2373,18 +2581,160 @@ function getThemeStyles(theme) {
|
|
|
2373
2581
|
}
|
|
2374
2582
|
|
|
2375
2583
|
// src/hooks/useCalendarLogic.ts
|
|
2376
|
-
import {
|
|
2584
|
+
import { useMemo as useMemo3, useState as useState5 } from "react";
|
|
2377
2585
|
import {
|
|
2378
|
-
|
|
2379
|
-
|
|
2586
|
+
addDays as addDays5,
|
|
2587
|
+
addMonths as addMonths3,
|
|
2380
2588
|
addWeeks,
|
|
2381
|
-
|
|
2382
|
-
|
|
2589
|
+
endOfMonth as endOfMonth2,
|
|
2590
|
+
format as format13,
|
|
2591
|
+
startOfMonth as startOfMonth3,
|
|
2592
|
+
startOfWeek as startOfWeek2,
|
|
2383
2593
|
subDays,
|
|
2384
|
-
|
|
2594
|
+
subMonths as subMonths2,
|
|
2595
|
+
subWeeks
|
|
2385
2596
|
} from "date-fns";
|
|
2386
|
-
|
|
2597
|
+
|
|
2598
|
+
// src/core/recurrence.ts
|
|
2599
|
+
import { addMonths as addMonths2, startOfMonth as startOfMonth2 } from "date-fns";
|
|
2387
2600
|
import { RRule } from "rrule";
|
|
2601
|
+
var WEEKDAYS = [RRule.MO, RRule.TU, RRule.WE, RRule.TH, RRule.FR, RRule.SA, RRule.SU];
|
|
2602
|
+
var toFloating = (instant, tz) => {
|
|
2603
|
+
const w = toDisplay(instant, tz);
|
|
2604
|
+
return new Date(Date.UTC(w.getFullYear(), w.getMonth(), w.getDate(), w.getHours(), w.getMinutes(), w.getSeconds(), w.getMilliseconds()));
|
|
2605
|
+
};
|
|
2606
|
+
var fromFloating = (floating, tz) => fromDisplay(
|
|
2607
|
+
new Date(
|
|
2608
|
+
floating.getUTCFullYear(),
|
|
2609
|
+
floating.getUTCMonth(),
|
|
2610
|
+
floating.getUTCDate(),
|
|
2611
|
+
floating.getUTCHours(),
|
|
2612
|
+
floating.getUTCMinutes(),
|
|
2613
|
+
floating.getUTCSeconds(),
|
|
2614
|
+
floating.getUTCMilliseconds()
|
|
2615
|
+
),
|
|
2616
|
+
tz
|
|
2617
|
+
);
|
|
2618
|
+
var minuteKey = (date) => Math.floor(date.getTime() / 6e4);
|
|
2619
|
+
var FREQUENCIES = { DAILY: RRule.DAILY, WEEKLY: RRule.WEEKLY, MONTHLY: RRule.MONTHLY, YEARLY: RRule.YEARLY };
|
|
2620
|
+
function buildRule(event, tz) {
|
|
2621
|
+
const r = event.recurrence;
|
|
2622
|
+
const freq = FREQUENCIES[r.freq];
|
|
2623
|
+
if (freq === void 0) throw new Error(`unsupported frequency "${r.freq}"`);
|
|
2624
|
+
return new RRule({
|
|
2625
|
+
freq,
|
|
2626
|
+
interval: r.interval || 1,
|
|
2627
|
+
dtstart: toFloating(event.start, tz),
|
|
2628
|
+
until: r.until ? toFloating(r.until, tz) : void 0,
|
|
2629
|
+
count: r.count,
|
|
2630
|
+
byweekday: r.byweekday?.length ? r.byweekday.map((d) => WEEKDAYS[d]) : void 0
|
|
2631
|
+
});
|
|
2632
|
+
}
|
|
2633
|
+
function occurrencesInMonth(event, monthStart, tz) {
|
|
2634
|
+
const rule = buildRule(event, tz);
|
|
2635
|
+
const excluded = new Set((event.recurrence.exdates ?? []).map(minuteKey));
|
|
2636
|
+
const duration = event.end.getTime() - event.start.getTime();
|
|
2637
|
+
const from = toFloating(monthStart, tz);
|
|
2638
|
+
const to = toFloating(addMonths2(monthStart, 1), tz);
|
|
2639
|
+
const result = [];
|
|
2640
|
+
for (const floating of rule.between(from, to, true)) {
|
|
2641
|
+
if (floating.getTime() === to.getTime()) continue;
|
|
2642
|
+
const start = fromFloating(floating, tz);
|
|
2643
|
+
if (excluded.has(minuteKey(start))) continue;
|
|
2644
|
+
result.push({
|
|
2645
|
+
...event,
|
|
2646
|
+
id: `${event.id}-${start.getTime()}`,
|
|
2647
|
+
originalEventId: event.id,
|
|
2648
|
+
occurrenceStart: start,
|
|
2649
|
+
start,
|
|
2650
|
+
end: new Date(start.getTime() + duration)
|
|
2651
|
+
});
|
|
2652
|
+
}
|
|
2653
|
+
return result;
|
|
2654
|
+
}
|
|
2655
|
+
var MAX_MONTHS_PER_EVENT = 12;
|
|
2656
|
+
var occurrenceCache = /* @__PURE__ */ new WeakMap();
|
|
2657
|
+
function cachedMonth(event, monthStart, tz) {
|
|
2658
|
+
let months = occurrenceCache.get(event);
|
|
2659
|
+
if (!months) {
|
|
2660
|
+
months = /* @__PURE__ */ new Map();
|
|
2661
|
+
occurrenceCache.set(event, months);
|
|
2662
|
+
}
|
|
2663
|
+
const key = `${monthStart.getFullYear()}-${monthStart.getMonth()}|${tz ?? ""}`;
|
|
2664
|
+
let occurrences = months.get(key);
|
|
2665
|
+
if (occurrences) {
|
|
2666
|
+
months.delete(key);
|
|
2667
|
+
} else {
|
|
2668
|
+
occurrences = occurrencesInMonth(event, monthStart, tz);
|
|
2669
|
+
if (months.size >= MAX_MONTHS_PER_EVENT) months.delete(months.keys().next().value);
|
|
2670
|
+
}
|
|
2671
|
+
months.set(key, occurrences);
|
|
2672
|
+
return occurrences;
|
|
2673
|
+
}
|
|
2674
|
+
function expandRecurrences(events, range, anchorTz) {
|
|
2675
|
+
const result = [];
|
|
2676
|
+
const firstMonth = startOfMonth2(toDisplay(range.start, anchorTz));
|
|
2677
|
+
const lastMonth = startOfMonth2(toDisplay(range.end, anchorTz));
|
|
2678
|
+
for (const event of events) {
|
|
2679
|
+
if (!event.recurrence) {
|
|
2680
|
+
result.push(event);
|
|
2681
|
+
continue;
|
|
2682
|
+
}
|
|
2683
|
+
try {
|
|
2684
|
+
for (let month = firstMonth; month <= lastMonth; month = addMonths2(month, 1)) {
|
|
2685
|
+
for (const occurrence of cachedMonth(event, fromDisplay(month, anchorTz), anchorTz)) {
|
|
2686
|
+
if (occurrence.end >= range.start && occurrence.start <= range.end) result.push(occurrence);
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
} catch (error) {
|
|
2690
|
+
warnOnce(`invalid-recurrence:${event.id}`, `Event "${event.id}" has an invalid recurrence rule: ${String(error)}`);
|
|
2691
|
+
result.push(event);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
return result;
|
|
2695
|
+
}
|
|
2696
|
+
function stripOccurrenceFields(event) {
|
|
2697
|
+
const { originalEventId: _originalEventId, occurrenceStart: _occurrenceStart, ...rest } = event;
|
|
2698
|
+
return rest;
|
|
2699
|
+
}
|
|
2700
|
+
var shiftFloating = (instant, deltaMs, tz) => fromFloating(new Date(toFloating(instant, tz).getTime() + deltaMs), tz);
|
|
2701
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
2702
|
+
var floatingDay = (floating) => Math.floor(floating.getTime() / DAY_MS);
|
|
2703
|
+
function shiftSeries(master, occurrence, newStart, newEnd, anchorTz) {
|
|
2704
|
+
const fromFloat = toFloating(occurrence.start, anchorTz);
|
|
2705
|
+
const toFloat = toFloating(newStart, anchorTz);
|
|
2706
|
+
const deltaMs = toFloat.getTime() - fromFloat.getTime();
|
|
2707
|
+
const dayDelta = floatingDay(toFloat) - floatingDay(fromFloat);
|
|
2708
|
+
const start = shiftFloating(master.start, deltaMs, anchorTz);
|
|
2709
|
+
const end = new Date(start.getTime() + (newEnd.getTime() - newStart.getTime()));
|
|
2710
|
+
const recurrence = master.recurrence && {
|
|
2711
|
+
...master.recurrence,
|
|
2712
|
+
byweekday: master.recurrence.byweekday && dayDelta % 7 !== 0 ? master.recurrence.byweekday.map((d) => ((d + dayDelta) % 7 + 7) % 7) : master.recurrence.byweekday,
|
|
2713
|
+
exdates: master.recurrence.exdates?.map((d) => shiftFloating(d, deltaMs, anchorTz))
|
|
2714
|
+
};
|
|
2715
|
+
return stripOccurrenceFields({ ...master, start, end, recurrence });
|
|
2716
|
+
}
|
|
2717
|
+
function excludeOccurrence(master, occurrenceStart) {
|
|
2718
|
+
const recurrence = master.recurrence ?? { freq: "DAILY" };
|
|
2719
|
+
return { ...master, recurrence: { ...recurrence, exdates: [...recurrence.exdates ?? [], occurrenceStart] } };
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2722
|
+
// src/hooks/useStableCallback.ts
|
|
2723
|
+
import { useCallback as useCallback3, useRef as useRef5 } from "react";
|
|
2724
|
+
function useStableCallback(fn) {
|
|
2725
|
+
const ref = useRef5(fn);
|
|
2726
|
+
ref.current = fn;
|
|
2727
|
+
return useCallback3((...args) => ref.current(...args), []);
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
// src/hooks/useCalendarLogic.ts
|
|
2731
|
+
function expansionRange(monthStart) {
|
|
2732
|
+
return {
|
|
2733
|
+
start: startOfWeek2(startOfMonth3(subMonths2(monthStart, 1))),
|
|
2734
|
+
end: addDays5(endOfMonth2(addMonths3(monthStart, 1)), 37)
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
var DAY_MS2 = 24 * 60 * 60 * 1e3;
|
|
2388
2738
|
var useCalendarLogic = ({
|
|
2389
2739
|
events,
|
|
2390
2740
|
view: controlledView,
|
|
@@ -2395,69 +2745,44 @@ var useCalendarLogic = ({
|
|
|
2395
2745
|
onEventUpdate,
|
|
2396
2746
|
onEventCreate,
|
|
2397
2747
|
onEventDelete,
|
|
2748
|
+
onEventDrop,
|
|
2749
|
+
onEventResize,
|
|
2750
|
+
onOccurrenceDelete,
|
|
2398
2751
|
readOnly,
|
|
2399
2752
|
timezone
|
|
2400
2753
|
}) => {
|
|
2401
2754
|
const [internalView, setInternalView] = useState5("week");
|
|
2402
|
-
const [internalDate, setInternalDate] = useState5(/* @__PURE__ */ new Date());
|
|
2755
|
+
const [internalDate, setInternalDate] = useState5(() => /* @__PURE__ */ new Date());
|
|
2403
2756
|
const [isSidebarOpen, setIsSidebarOpen] = useState5(true);
|
|
2404
2757
|
const [isModalOpen, setIsModalOpen] = useState5(false);
|
|
2405
2758
|
const [selectedEvent, setSelectedEvent] = useState5(null);
|
|
2406
2759
|
const [modalInitialDate, setModalInitialDate] = useState5(void 0);
|
|
2760
|
+
const [modalInitialResourceId, setModalInitialResourceId] = useState5(void 0);
|
|
2407
2761
|
const view = controlledView ?? internalView;
|
|
2408
2762
|
const currentDate = controlledDate ?? internalDate;
|
|
2409
|
-
const
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
interval: event.recurrence.interval || 1,
|
|
2419
|
-
dtstart: new Date(event.start),
|
|
2420
|
-
until: event.recurrence.until ? new Date(event.recurrence.until) : void 0,
|
|
2421
|
-
count: event.recurrence.count
|
|
2422
|
-
});
|
|
2423
|
-
const dates = rule.between(rangeStart, rangeEnd);
|
|
2424
|
-
dates.forEach((date) => {
|
|
2425
|
-
const duration = differenceInMilliseconds(new Date(event.end), new Date(event.start));
|
|
2426
|
-
allEvents.push({
|
|
2427
|
-
...event,
|
|
2428
|
-
id: `${event.id}-${date.getTime()}`,
|
|
2429
|
-
// Unique ID for each instance
|
|
2430
|
-
originalEventId: event.id,
|
|
2431
|
-
// Reference to original
|
|
2432
|
-
start: date,
|
|
2433
|
-
end: new Date(date.getTime() + duration)
|
|
2434
|
-
});
|
|
2435
|
-
});
|
|
2436
|
-
} catch (e) {
|
|
2437
|
-
console.error("Error parsing recurrence rule", e);
|
|
2438
|
-
allEvents.push(event);
|
|
2439
|
-
}
|
|
2440
|
-
} else {
|
|
2441
|
-
allEvents.push(event);
|
|
2442
|
-
}
|
|
2443
|
-
});
|
|
2444
|
-
return allEvents;
|
|
2445
|
-
}, [events, currentDate]);
|
|
2446
|
-
const handleViewChange = (newView) => {
|
|
2763
|
+
const normalizedEvents = useMemo3(() => normalizeEvents(events), [events]);
|
|
2764
|
+
const monthKey = format13(currentDate, "yyyy-MM");
|
|
2765
|
+
const range = useMemo3(() => expansionRange(/* @__PURE__ */ new Date(`${monthKey}-01T00:00:00`)), [monthKey]);
|
|
2766
|
+
const expandedEvents = useMemo3(
|
|
2767
|
+
() => expandRecurrences(normalizedEvents, range, timezone),
|
|
2768
|
+
[normalizedEvents, range, timezone]
|
|
2769
|
+
);
|
|
2770
|
+
const masterOf = (occurrence) => normalizedEvents.find((e) => e.id === occurrence.originalEventId);
|
|
2771
|
+
const handleViewChange = useStableCallback((newView) => {
|
|
2447
2772
|
if (controlledOnViewChange) {
|
|
2448
2773
|
controlledOnViewChange(newView);
|
|
2449
2774
|
} else {
|
|
2450
2775
|
setInternalView(newView);
|
|
2451
2776
|
}
|
|
2452
|
-
};
|
|
2453
|
-
const handleDateChange = (newDate) => {
|
|
2777
|
+
});
|
|
2778
|
+
const handleDateChange = useStableCallback((newDate) => {
|
|
2454
2779
|
if (controlledOnDateChange) {
|
|
2455
2780
|
controlledOnDateChange(newDate);
|
|
2456
2781
|
} else {
|
|
2457
2782
|
setInternalDate(newDate);
|
|
2458
2783
|
}
|
|
2459
|
-
};
|
|
2460
|
-
const handlePrev = () => {
|
|
2784
|
+
});
|
|
2785
|
+
const handlePrev = useStableCallback(() => {
|
|
2461
2786
|
switch (view) {
|
|
2462
2787
|
case "month":
|
|
2463
2788
|
handleDateChange(subMonths2(currentDate, 1));
|
|
@@ -2473,133 +2798,139 @@ var useCalendarLogic = ({
|
|
|
2473
2798
|
handleDateChange(subDays(currentDate, 7));
|
|
2474
2799
|
break;
|
|
2475
2800
|
}
|
|
2476
|
-
};
|
|
2477
|
-
const handleNext = () => {
|
|
2801
|
+
});
|
|
2802
|
+
const handleNext = useStableCallback(() => {
|
|
2478
2803
|
switch (view) {
|
|
2479
2804
|
case "month":
|
|
2480
|
-
handleDateChange(
|
|
2805
|
+
handleDateChange(addMonths3(currentDate, 1));
|
|
2481
2806
|
break;
|
|
2482
2807
|
case "week":
|
|
2483
2808
|
handleDateChange(addWeeks(currentDate, 1));
|
|
2484
2809
|
break;
|
|
2485
2810
|
case "day":
|
|
2486
2811
|
case "resource":
|
|
2487
|
-
handleDateChange(
|
|
2812
|
+
handleDateChange(addDays5(currentDate, 1));
|
|
2488
2813
|
break;
|
|
2489
2814
|
case "agenda":
|
|
2490
|
-
handleDateChange(
|
|
2815
|
+
handleDateChange(addDays5(currentDate, 7));
|
|
2491
2816
|
break;
|
|
2492
2817
|
}
|
|
2493
|
-
};
|
|
2494
|
-
const handleToday = () => {
|
|
2818
|
+
});
|
|
2819
|
+
const handleToday = useStableCallback(() => {
|
|
2495
2820
|
handleDateChange(/* @__PURE__ */ new Date());
|
|
2496
|
-
};
|
|
2497
|
-
const handleDateClick = (date) => {
|
|
2821
|
+
});
|
|
2822
|
+
const handleDateClick = useStableCallback((date) => {
|
|
2498
2823
|
handleDateChange(date);
|
|
2499
2824
|
handleViewChange("day");
|
|
2500
|
-
};
|
|
2501
|
-
const handleTimeSlotClick = (date) => {
|
|
2825
|
+
});
|
|
2826
|
+
const handleTimeSlotClick = useStableCallback((date, resourceId) => {
|
|
2502
2827
|
if (readOnly) return;
|
|
2503
2828
|
setSelectedEvent(null);
|
|
2504
2829
|
setModalInitialDate(date);
|
|
2830
|
+
setModalInitialResourceId(resourceId);
|
|
2505
2831
|
setIsModalOpen(true);
|
|
2506
|
-
};
|
|
2507
|
-
const handleEventClickInternal = (event) => {
|
|
2508
|
-
|
|
2509
|
-
onEventClick(event);
|
|
2510
|
-
}
|
|
2832
|
+
});
|
|
2833
|
+
const handleEventClickInternal = useStableCallback((event) => {
|
|
2834
|
+
onEventClick?.(event);
|
|
2511
2835
|
if (!readOnly) {
|
|
2512
2836
|
setSelectedEvent(event);
|
|
2513
2837
|
setModalInitialDate(void 0);
|
|
2838
|
+
setModalInitialResourceId(void 0);
|
|
2514
2839
|
setIsModalOpen(true);
|
|
2515
2840
|
}
|
|
2516
|
-
};
|
|
2517
|
-
const handleCreateEvent = () => {
|
|
2841
|
+
});
|
|
2842
|
+
const handleCreateEvent = useStableCallback(() => {
|
|
2518
2843
|
if (readOnly) return;
|
|
2519
2844
|
setSelectedEvent(null);
|
|
2520
2845
|
setModalInitialDate(/* @__PURE__ */ new Date());
|
|
2846
|
+
setModalInitialResourceId(void 0);
|
|
2521
2847
|
setIsModalOpen(true);
|
|
2522
|
-
};
|
|
2523
|
-
const handleModalSave = (eventData) => {
|
|
2524
|
-
let effectiveEventId = selectedEvent?.id;
|
|
2848
|
+
});
|
|
2849
|
+
const handleModalSave = useStableCallback((eventData) => {
|
|
2525
2850
|
if (selectedEvent?.originalEventId) {
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
}
|
|
2851
|
+
const master = masterOf(selectedEvent);
|
|
2852
|
+
if (!master) return;
|
|
2853
|
+
const edited = {
|
|
2854
|
+
...master,
|
|
2855
|
+
...stripOccurrenceFields(eventData),
|
|
2856
|
+
id: master.id,
|
|
2857
|
+
start: master.start,
|
|
2858
|
+
end: master.end
|
|
2859
|
+
};
|
|
2860
|
+
onEventUpdate?.(
|
|
2861
|
+
shiftSeries(edited, selectedEvent, eventData.start ?? selectedEvent.start, eventData.end ?? selectedEvent.end, timezone)
|
|
2862
|
+
);
|
|
2863
|
+
} else if (selectedEvent) {
|
|
2864
|
+
onEventUpdate?.({ ...eventData, id: selectedEvent.id });
|
|
2535
2865
|
} else {
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
}
|
|
2540
|
-
};
|
|
2541
|
-
const handleModalDelete = (eventId) => {
|
|
2542
|
-
let effectiveEventId = eventId;
|
|
2543
|
-
if (selectedEvent?.originalEventId && selectedEvent.id === eventId) {
|
|
2544
|
-
effectiveEventId = selectedEvent.originalEventId;
|
|
2866
|
+
onEventCreate?.(
|
|
2867
|
+
modalInitialResourceId && eventData.resourceId === void 0 ? { ...eventData, resourceId: modalInitialResourceId } : eventData
|
|
2868
|
+
);
|
|
2545
2869
|
}
|
|
2546
|
-
|
|
2547
|
-
|
|
2870
|
+
});
|
|
2871
|
+
const handleEventDelete = useStableCallback((event) => {
|
|
2872
|
+
if (!event.originalEventId) {
|
|
2873
|
+
onEventDelete?.(event.id);
|
|
2874
|
+
return;
|
|
2548
2875
|
}
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
if (!over) return;
|
|
2553
|
-
const activeEvent = active.data.current?.event;
|
|
2554
|
-
const overDate = over.data.current?.date;
|
|
2555
|
-
if (!activeEvent || !overDate) return;
|
|
2556
|
-
const originalStart = new Date(activeEvent.start);
|
|
2557
|
-
const originalEnd = new Date(activeEvent.end);
|
|
2558
|
-
const duration = differenceInMilliseconds(originalEnd, originalStart);
|
|
2559
|
-
let newStart;
|
|
2560
|
-
if (view === "month") {
|
|
2561
|
-
if (timezone) {
|
|
2562
|
-
const zonedOriginal = toZonedTime5(originalStart, timezone);
|
|
2563
|
-
const zonedNew = new Date(overDate);
|
|
2564
|
-
zonedNew.setHours(zonedOriginal.getHours());
|
|
2565
|
-
zonedNew.setMinutes(zonedOriginal.getMinutes());
|
|
2566
|
-
zonedNew.setSeconds(zonedOriginal.getSeconds());
|
|
2567
|
-
zonedNew.setMilliseconds(zonedOriginal.getMilliseconds());
|
|
2568
|
-
newStart = fromZonedTime(zonedNew, timezone);
|
|
2569
|
-
} else {
|
|
2570
|
-
newStart = new Date(overDate);
|
|
2571
|
-
newStart.setHours(originalStart.getHours());
|
|
2572
|
-
newStart.setMinutes(originalStart.getMinutes());
|
|
2573
|
-
newStart.setSeconds(originalStart.getSeconds());
|
|
2574
|
-
newStart.setMilliseconds(originalStart.getMilliseconds());
|
|
2575
|
-
}
|
|
2876
|
+
const master = masterOf(event);
|
|
2877
|
+
if (onOccurrenceDelete && master) {
|
|
2878
|
+
onOccurrenceDelete(event, excludeOccurrence(master, event.occurrenceStart ?? event.start));
|
|
2576
2879
|
} else {
|
|
2577
|
-
|
|
2578
|
-
newStart = fromZonedTime(overDate, timezone);
|
|
2579
|
-
} else {
|
|
2580
|
-
newStart = new Date(overDate);
|
|
2581
|
-
}
|
|
2582
|
-
newStart.setSeconds(0);
|
|
2583
|
-
newStart.setMilliseconds(0);
|
|
2584
|
-
}
|
|
2585
|
-
const newEnd = new Date(newStart.getTime() + duration);
|
|
2586
|
-
let newResourceId = activeEvent.resourceId;
|
|
2587
|
-
const overResourceId = over.data.current?.resourceId;
|
|
2588
|
-
if (overResourceId) {
|
|
2589
|
-
newResourceId = overResourceId;
|
|
2590
|
-
}
|
|
2591
|
-
if (newStart.getTime() === originalStart.getTime() && newEnd.getTime() === originalEnd.getTime() && newResourceId === activeEvent.resourceId) {
|
|
2592
|
-
return;
|
|
2880
|
+
onEventDelete?.(event.originalEventId);
|
|
2593
2881
|
}
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
});
|
|
2882
|
+
});
|
|
2883
|
+
const handleModalDelete = useStableCallback((eventId) => {
|
|
2884
|
+
if (selectedEvent && selectedEvent.id === eventId) {
|
|
2885
|
+
handleEventDelete(selectedEvent);
|
|
2886
|
+
} else {
|
|
2887
|
+
onEventDelete?.(eventId);
|
|
2601
2888
|
}
|
|
2889
|
+
});
|
|
2890
|
+
const handleEventDuplicate = useStableCallback((event) => {
|
|
2891
|
+
const { recurrence: _recurrence, ...copy } = stripOccurrenceFields(event);
|
|
2892
|
+
onEventCreate?.({
|
|
2893
|
+
...copy,
|
|
2894
|
+
id: `${event.originalEventId ?? event.id}-copy-${Date.now()}`,
|
|
2895
|
+
title: `${event.title} (Copy)`,
|
|
2896
|
+
start: new Date(event.start.getTime() + DAY_MS2),
|
|
2897
|
+
end: new Date(event.end.getTime() + DAY_MS2)
|
|
2898
|
+
});
|
|
2899
|
+
});
|
|
2900
|
+
const applyChange = (event, start, end) => {
|
|
2901
|
+
if (!event.originalEventId) return { original: event, updated: { ...event, start, end } };
|
|
2902
|
+
const master = masterOf(event);
|
|
2903
|
+
if (!master) return null;
|
|
2904
|
+
return { original: master, updated: shiftSeries(master, event, start, end, timezone) };
|
|
2602
2905
|
};
|
|
2906
|
+
const handleDragEnd = useStableCallback((dragEvent) => {
|
|
2907
|
+
const { active, over } = dragEvent;
|
|
2908
|
+
if (!over) return;
|
|
2909
|
+
const dragged = active.data.current?.event;
|
|
2910
|
+
const overDate = over.data.current?.date;
|
|
2911
|
+
if (!dragged || !overDate) return;
|
|
2912
|
+
const drop = computeDrop({
|
|
2913
|
+
event: dragged,
|
|
2914
|
+
overDate,
|
|
2915
|
+
overResourceId: over.data.current?.resourceId,
|
|
2916
|
+
grabOffsetMs: active.data.current?.grabOffsetMs,
|
|
2917
|
+
view,
|
|
2918
|
+
tz: timezone
|
|
2919
|
+
});
|
|
2920
|
+
if (!drop) return;
|
|
2921
|
+
const change = applyChange(dragged, drop.start, drop.end);
|
|
2922
|
+
if (!change) return;
|
|
2923
|
+
const updated = { ...change.updated, resourceId: drop.resourceId };
|
|
2924
|
+
onEventDrop?.(change.original, updated.start, updated.end);
|
|
2925
|
+
onEventUpdate?.(updated);
|
|
2926
|
+
});
|
|
2927
|
+
const handleEventResize = useStableCallback((event, newEnd) => {
|
|
2928
|
+
if (readOnly) return;
|
|
2929
|
+
const change = applyChange(event, event.start, newEnd);
|
|
2930
|
+
if (!change) return;
|
|
2931
|
+
onEventResize?.(change.original, change.updated.start, change.updated.end);
|
|
2932
|
+
onEventUpdate?.(change.updated);
|
|
2933
|
+
});
|
|
2603
2934
|
return {
|
|
2604
2935
|
view,
|
|
2605
2936
|
currentDate,
|
|
@@ -2621,15 +2952,18 @@ var useCalendarLogic = ({
|
|
|
2621
2952
|
handleCreateEvent,
|
|
2622
2953
|
handleModalSave,
|
|
2623
2954
|
handleModalDelete,
|
|
2955
|
+
handleEventDelete,
|
|
2956
|
+
handleEventDuplicate,
|
|
2957
|
+
handleEventResize,
|
|
2624
2958
|
handleDragEnd
|
|
2625
2959
|
};
|
|
2626
2960
|
};
|
|
2627
2961
|
|
|
2628
2962
|
// src/index.tsx
|
|
2629
|
-
import { differenceInMinutes as differenceInMinutes6, format as
|
|
2963
|
+
import { differenceInMinutes as differenceInMinutes6, format as format14 } from "date-fns";
|
|
2630
2964
|
|
|
2631
2965
|
// src/hooks/useSwipeGesture.ts
|
|
2632
|
-
import { useRef as
|
|
2966
|
+
import { useRef as useRef6, useCallback as useCallback4, useEffect as useEffect5 } from "react";
|
|
2633
2967
|
var useSwipeGesture = (options) => {
|
|
2634
2968
|
const {
|
|
2635
2969
|
onSwipeLeft,
|
|
@@ -2641,8 +2975,8 @@ var useSwipeGesture = (options) => {
|
|
|
2641
2975
|
allowedTime = 500,
|
|
2642
2976
|
enabled = true
|
|
2643
2977
|
} = options;
|
|
2644
|
-
const touchInfoRef =
|
|
2645
|
-
const elementRef =
|
|
2978
|
+
const touchInfoRef = useRef6(null);
|
|
2979
|
+
const elementRef = useRef6(null);
|
|
2646
2980
|
const handleTouchStart = useCallback4((e) => {
|
|
2647
2981
|
if (!enabled) return;
|
|
2648
2982
|
const touch = e.touches[0];
|
|
@@ -2710,7 +3044,9 @@ var Scheduler = ({
|
|
|
2710
3044
|
onEventCreate,
|
|
2711
3045
|
onEventUpdate,
|
|
2712
3046
|
onEventDelete,
|
|
3047
|
+
onEventDrop,
|
|
2713
3048
|
onEventResize,
|
|
3049
|
+
onOccurrenceDelete,
|
|
2714
3050
|
timezone,
|
|
2715
3051
|
onTimezoneChange,
|
|
2716
3052
|
className,
|
|
@@ -2758,9 +3094,11 @@ var Scheduler = ({
|
|
|
2758
3094
|
handleCreateEvent,
|
|
2759
3095
|
handleModalSave,
|
|
2760
3096
|
handleModalDelete,
|
|
3097
|
+
handleEventDelete,
|
|
3098
|
+
handleEventDuplicate,
|
|
3099
|
+
handleEventResize,
|
|
2761
3100
|
handleDragEnd,
|
|
2762
3101
|
expandedEvents
|
|
2763
|
-
// New export
|
|
2764
3102
|
} = useCalendarLogic({
|
|
2765
3103
|
events,
|
|
2766
3104
|
view: controlledView,
|
|
@@ -2771,6 +3109,9 @@ var Scheduler = ({
|
|
|
2771
3109
|
onEventUpdate,
|
|
2772
3110
|
onEventCreate,
|
|
2773
3111
|
onEventDelete,
|
|
3112
|
+
onEventDrop,
|
|
3113
|
+
onEventResize,
|
|
3114
|
+
onOccurrenceDelete,
|
|
2774
3115
|
readOnly,
|
|
2775
3116
|
timezone
|
|
2776
3117
|
});
|
|
@@ -2785,37 +3126,20 @@ var Scheduler = ({
|
|
|
2785
3126
|
const snapToGrid = createSnapModifier(gridSize);
|
|
2786
3127
|
const modifiers = [snapToGrid, restrictToWindowEdges];
|
|
2787
3128
|
const dndSensors = readOnly ? [] : sensors;
|
|
2788
|
-
const handleEventResize = (event, newEnd) => {
|
|
2789
|
-
if (readOnly) return;
|
|
2790
|
-
if (onEventResize) {
|
|
2791
|
-
onEventResize(event, event.start, newEnd);
|
|
2792
|
-
}
|
|
2793
|
-
if (onEventUpdate) {
|
|
2794
|
-
onEventUpdate({
|
|
2795
|
-
...event,
|
|
2796
|
-
end: newEnd
|
|
2797
|
-
});
|
|
2798
|
-
}
|
|
2799
|
-
};
|
|
2800
3129
|
const id = useId();
|
|
2801
3130
|
const swipeRef = useViewSwipe(handlePrev, handleNext, true);
|
|
2802
|
-
const
|
|
2803
|
-
const
|
|
2804
|
-
|
|
2805
|
-
|
|
2806
|
-
};
|
|
2807
|
-
const overlayTimeFormat = uses24HourClock(locale) ? "H:mm" : "h:mm a";
|
|
2808
|
-
const handleDragStart = (event) => {
|
|
2809
|
-
const { active } = event;
|
|
2810
|
-
const draggedEvent = expandedEvents.find((e) => e.id === active.id);
|
|
3131
|
+
const t = useMemo4(() => resolveTranslations(language, translations), [language, translations]);
|
|
3132
|
+
const overlayTimeFormat = timeFormats(locale).time;
|
|
3133
|
+
const handleDragStart = useCallback5((event) => {
|
|
3134
|
+
const draggedEvent = event.active.data.current?.event;
|
|
2811
3135
|
if (draggedEvent) {
|
|
2812
3136
|
setActiveDragEvent(draggedEvent);
|
|
2813
3137
|
}
|
|
2814
|
-
};
|
|
2815
|
-
const onDragEndWrapper = (event) => {
|
|
3138
|
+
}, []);
|
|
3139
|
+
const onDragEndWrapper = useCallback5((event) => {
|
|
2816
3140
|
setActiveDragEvent(null);
|
|
2817
3141
|
handleDragEnd(event);
|
|
2818
|
-
};
|
|
3142
|
+
}, [handleDragEnd]);
|
|
2819
3143
|
const getDragHeight = () => {
|
|
2820
3144
|
if (!activeDragEvent) return void 0;
|
|
2821
3145
|
if (view === "resource") {
|
|
@@ -2980,7 +3304,8 @@ var Scheduler = ({
|
|
|
2980
3304
|
onEventClick: handleEventClickInternal,
|
|
2981
3305
|
onCreateEvent: handleCreateEvent,
|
|
2982
3306
|
locale,
|
|
2983
|
-
translations: t
|
|
3307
|
+
translations: t,
|
|
3308
|
+
timezone
|
|
2984
3309
|
}
|
|
2985
3310
|
),
|
|
2986
3311
|
view === "resource" && resources && /* @__PURE__ */ React18.createElement(
|
|
@@ -2990,11 +3315,9 @@ var Scheduler = ({
|
|
|
2990
3315
|
events: filteredEvents,
|
|
2991
3316
|
resources,
|
|
2992
3317
|
onEventClick: handleEventClickInternal,
|
|
2993
|
-
onTimeSlotClick:
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
},
|
|
2997
|
-
locale
|
|
3318
|
+
onTimeSlotClick: handleTimeSlotClick,
|
|
3319
|
+
locale,
|
|
3320
|
+
timezone
|
|
2998
3321
|
}
|
|
2999
3322
|
)
|
|
3000
3323
|
)))), /* @__PURE__ */ React18.createElement("div", { className: "md:hidden absolute bottom-6 right-6 z-50" }, /* @__PURE__ */ React18.createElement(
|
|
@@ -3036,20 +3359,12 @@ var Scheduler = ({
|
|
|
3036
3359
|
handleEventClickInternal(event);
|
|
3037
3360
|
closeContextMenu();
|
|
3038
3361
|
},
|
|
3039
|
-
onDelete: (
|
|
3040
|
-
|
|
3362
|
+
onDelete: () => {
|
|
3363
|
+
if (contextMenuEvent) handleEventDelete(contextMenuEvent);
|
|
3041
3364
|
closeContextMenu();
|
|
3042
3365
|
},
|
|
3043
3366
|
onDuplicate: (event) => {
|
|
3044
|
-
|
|
3045
|
-
...event,
|
|
3046
|
-
id: `${event.id}-copy-${Date.now()}`,
|
|
3047
|
-
title: `${event.title} (Copy)`,
|
|
3048
|
-
start: new Date(event.start.getTime() + 24 * 60 * 60 * 1e3),
|
|
3049
|
-
// +1 day
|
|
3050
|
-
end: new Date(event.end.getTime() + 24 * 60 * 60 * 1e3)
|
|
3051
|
-
};
|
|
3052
|
-
onEventCreate?.(duplicatedEvent);
|
|
3367
|
+
handleEventDuplicate(event);
|
|
3053
3368
|
closeContextMenu();
|
|
3054
3369
|
},
|
|
3055
3370
|
translations: {
|
|
@@ -3083,7 +3398,7 @@ var Scheduler = ({
|
|
|
3083
3398
|
className: "absolute left-0 top-0 bottom-0 w-1 rounded-l-lg",
|
|
3084
3399
|
style: { backgroundColor: activeDragEvent.color || "var(--primary)" }
|
|
3085
3400
|
}
|
|
3086
|
-
), /* @__PURE__ */ React18.createElement("div", { className: "pl-2" }, /* @__PURE__ */ React18.createElement("div", { className: "font-semibold truncate text-sm" }, activeDragEvent.title), (view === "week" || view === "day") && getDragHeight() && getDragHeight() > 40 && /* @__PURE__ */ React18.createElement("div", { className: "text-xs opacity-80 mt-0.5 flex items-center gap-1" }, /* @__PURE__ */ React18.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React18.createElement("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ React18.createElement("path", { d: "M12 6v6l4 2" })),
|
|
3401
|
+
), /* @__PURE__ */ React18.createElement("div", { className: "pl-2" }, /* @__PURE__ */ React18.createElement("div", { className: "font-semibold truncate text-sm" }, activeDragEvent.title), (view === "week" || view === "day") && getDragHeight() && getDragHeight() > 40 && /* @__PURE__ */ React18.createElement("div", { className: "text-xs opacity-80 mt-0.5 flex items-center gap-1" }, /* @__PURE__ */ React18.createElement("svg", { className: "w-3 h-3", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2" }, /* @__PURE__ */ React18.createElement("circle", { cx: "12", cy: "12", r: "10" }), /* @__PURE__ */ React18.createElement("path", { d: "M12 6v6l4 2" })), format14(activeDragEvent.start, overlayTimeFormat, { locale }), " - ", format14(activeDragEvent.end, overlayTimeFormat, { locale }))), /* @__PURE__ */ React18.createElement("div", { className: "absolute bottom-1.5 right-1.5 opacity-60" }, /* @__PURE__ */ React18.createElement("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", fill: "currentColor" }, /* @__PURE__ */ React18.createElement("circle", { cx: "9", cy: "5", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "15", cy: "5", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "9", cy: "12", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "15", cy: "12", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "9", cy: "19", r: "1.5" }), /* @__PURE__ */ React18.createElement("circle", { cx: "15", cy: "19", r: "1.5" }))))
|
|
3087
3402
|
) : null)
|
|
3088
3403
|
)
|
|
3089
3404
|
);
|