diff --git a/tests/static-toolboxes.test.mjs b/tests/static-toolboxes.test.mjs
index 91fea27..7813019 100644
--- a/tests/static-toolboxes.test.mjs
+++ b/tests/static-toolboxes.test.mjs
@@ -151,6 +151,9 @@ test("toolbox module registry and modules expose expected behavior", async () =>
const timerParts = await readFile("website/src/features/toolboxes/modules/TimerParts.jsx", "utf8");
const timerOptions = await readFile("website/src/features/toolboxes/modules/timerOptions.js", "utf8");
const calendarModule = await readFile("website/src/features/toolboxes/modules/CalendarModule.jsx", "utf8");
+ const calendarEventForm = await readFile("website/src/features/toolboxes/modules/CalendarEventForm.jsx", "utf8");
+ const calendarViews = await readFile("website/src/features/toolboxes/modules/CalendarViews.jsx", "utf8");
+ const calendarBundle = `${calendarModule}\n${calendarEventForm}\n${calendarViews}`;
const calendarUtils = await readFile("website/src/features/toolboxes/modules/calendarUtils.js", "utf8");
const taskPlannerModule = await readFile("website/src/features/toolboxes/modules/TaskPlannerModule.jsx", "utf8");
const taskPlannerItems = await readFile("website/src/features/toolboxes/modules/TaskPlannerItems.jsx", "utf8");
@@ -321,28 +324,28 @@ test("toolbox module registry and modules expose expected behavior", async () =>
assert.match(timerModule, /TextExchangeActions/);
assert.match(calendarModule, /export function CalendarModule/);
assert.match(calendarModule, /normalizeCalendarData/);
- assert.match(calendarModule, /calendar-mode-choice/);
- assert.match(calendarModule, /calendar-week-grid/);
- assert.match(calendarModule, /calendar-month-grid/);
- assert.match(calendarModule, /getMonthOccurrenceSegments/);
- assert.match(calendarModule, /calendar-month-bar/);
- assert.match(calendarModule, /MonthDateDropdown/);
- assert.match(calendarModule, /MonthEventRepeatSwitch/);
- assert.match(calendarModule, /monthlyRepeatBy/);
+ assert.match(calendarBundle, /calendar-mode-choice/);
+ assert.match(calendarBundle, /calendar-week-grid/);
+ assert.match(calendarBundle, /calendar-month-grid/);
+ assert.match(calendarBundle, /getMonthOccurrenceSegments/);
+ assert.match(calendarBundle, /calendar-month-bar/);
+ assert.match(calendarBundle, /MonthDateDropdown/);
+ assert.match(calendarBundle, /MonthEventRepeatSwitch/);
+ assert.match(calendarBundle, /monthlyRepeatBy/);
assert.match(calendarModule, /displayedMonth/);
assert.match(calendarModule, /deleteOccurrence/);
- assert.match(calendarModule, /onPointerDown/);
+ assert.match(calendarBundle, /onPointerDown/);
assert.match(calendarModule, /copyEvent/);
- assert.match(calendarModule, /calendar-repeat-switch/);
- assert.match(calendarModule, /layout-switch calendar-month-repeat-switch/);
- assert.match(calendarModule, /WEEK_TIME_INPUT_STEP_MINUTES \* 60/);
- assert.match(calendarModule, /durationMinutes < 120/);
- assert.match(calendarModule, /is-compact/);
+ assert.match(calendarBundle, /calendar-repeat-switch/);
+ assert.match(calendarBundle, /layout-switch calendar-month-repeat-switch/);
+ assert.match(calendarBundle, /WEEK_TIME_INPUT_STEP_MINUTES \* 60/);
+ assert.match(calendarBundle, /durationMinutes < 120/);
+ assert.match(calendarBundle, /is-compact/);
assert.match(calendarModule, /handleKeyDown/);
assert.match(calendarModule, /TextExchangeActions/);
assert.match(calendarModule, /type="calendar"/);
- assert.match(calendarModule, /repeatLabel/);
- assert.match(calendarModule, /CalendarAlertModeSwitch/);
+ assert.match(calendarBundle, /repeatLabel/);
+ assert.match(calendarBundle, /CalendarAlertModeSwitch/);
assert.doesNotMatch(moduleRegistry, /calendarInitOpen/);
assert.doesNotMatch(moduleRegistry, /calendar-init-modal/);
assert.match(calendarUtils, /export function getCalendarOccurrences/);
diff --git a/website/src/features/toolboxes/modules/CalendarEventForm.jsx b/website/src/features/toolboxes/modules/CalendarEventForm.jsx
new file mode 100644
index 0000000..7b6d62c
--- /dev/null
+++ b/website/src/features/toolboxes/modules/CalendarEventForm.jsx
@@ -0,0 +1,392 @@
+// Rôle : rend le formulaire calendrier et ses contrôles de date, répétition, couleur et alertes.
+import { Icon } from "../../../components/Icon.jsx";
+import {
+ CALENDAR_ALERT_OFFSETS,
+ CALENDAR_COLORS,
+ WEEK_TIME_INPUT_STEP_MINUTES,
+ addMonths,
+ formatLocalDateKey,
+ getLocalDayIndex,
+ getMonthGridDays,
+ getMonthStart,
+ pad2,
+ parseLocalDateKey
+} from "./calendarUtils.js";
+import { WEEKDAY_LABELS } from "./calendarLayout.js";
+import { ALERT_MODES } from "./timerOptions.js";
+
+const CALENDAR_COLOR_VALUES = {
+ violet: "#8b5cf6",
+ gold: "#f6c453",
+ cyan: "#22d3ee",
+ pink: "#d946ef",
+ green: "#22c55e",
+ gray: "#7d879f"
+};
+
+function normalizeDraftDateKey(value) {
+ const date = parseLocalDateKey(value);
+ return date ? formatLocalDateKey(date) : "";
+}
+
+function formatMonthDateLabel(dateKey, mode) {
+ const date = parseLocalDateKey(dateKey);
+ if (!date) return "";
+ const weekday = WEEKDAY_LABELS[getLocalDayIndex(date)];
+ const dayLabel = date.toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" });
+ if (mode !== "weekday") return dayLabel;
+ const ordinal = Math.floor((date.getDate() - 1) / 7) + 1;
+ return `S${pad2(ordinal)} - ${weekday}`;
+}
+
+function formatMonthTitle(dateLike) {
+ return new Date(dateLike).toLocaleDateString(undefined, { month: "long", year: "numeric" });
+}
+
+function getWeekdayPickerRows(monthDate) {
+ const monthGridDays = getMonthGridDays(monthDate).filter((day) => day.inMonth);
+ return Array.from({ length: 5 }, (_, rowIndex) => {
+ const ordinal = rowIndex + 1;
+ return {
+ ordinal,
+ days: WEEKDAY_LABELS.map((_, dayIndex) => {
+ const matches = monthGridDays.filter((day) => day.dayIndex === dayIndex);
+ return matches[rowIndex] || null;
+ })
+ };
+ });
+}
+
+export function CalendarEventForm({
+ data,
+ draft,
+ error,
+ activeOccurrenceDate,
+ monthStart,
+ selectedAlertOffsets,
+ textContent,
+ onSubmit,
+ onDraftChange,
+ onAlertModeChange,
+ onAlertOffsetToggle,
+ onDeleteEvent,
+ onDeleteOccurrence,
+ onClose,
+ onDisplayedMonthChange
+}) {
+ return (
+
- {formOpen &&
}
+ {formOpen && (
+
+ )}
{textExchangeOpen && (
save(nextData)} />
@@ -662,413 +491,42 @@ export function CalendarModule({ toolboxId, moduleId, context, editing = false,
)}
{data.viewMode === "month" ? (
- <>
-
-
- {formatMonthTitle(monthStart)}
-
-
-
- setMonthSelection(null)}
- onEditEvent={editEvent}
- />
- >
- ) :
-
- {days.map((day) => {formatDayLabel(day)}
)}
-
- {weekGridLayout.rows.map((row) => (
- row.type === "hidden"
- ? ...
- : {formatHourLabel(row.hourIndex)}
- ))}
-
- {days.map((day, dayIndex) => {
- const dayOccurrences = layoutConcurrentOccurrences(occurrences.filter((occurrence) => occurrence.event.dayIndex === dayIndex));
- const isToday = dayIndex === nowDayIndex;
- return (
-
- {weekGridLayout.rows.map((row) => (
- row.type === "hidden" ? (
-
- {dayIndex === 3 && {`${formatHourLabel(row.startHour)} - ${formatHourLabel(row.endHour + 1)}`}}
-
- ) : (
-
- {Array.from({ length: HOUR_HALF_SLOT_COUNT }, (_, halfIndex) => {
- const slotIndex = row.hourIndex * HOUR_HALF_SLOT_COUNT + halfIndex;
- const slotLabel = minutesToTimeValue(slotIndex * WEEK_SLOT_MINUTES);
- return (
-
- )
- ))}
-
- {dayOccurrences.map((occurrence) => (
- editEvent(occurrence.event)}
- />
- ))}
-
- {isToday && (
-
- {minutesToTimeValue(nowMinutes)}
-
- )}
-
- );
- })}
- }
-
- );
-}
-
-function MonthDateDropdown({ value, displayedMonth, minDate = "", mode, textContent, label, align = "start", onChange, onMonthChange }) {
- const safeValue = normalizeDraftDateKey(value) || formatLocalDateKey(displayedMonth);
- return (
-
-
-
-
- );
-}
-
-function MonthDatePicker({ value, displayedMonth, minDate, mode, textContent, onChange, onMonthChange }) {
- const days = getMonthGridDays(displayedMonth);
- const weekdayRows = getWeekdayPickerRows(displayedMonth);
- const minDateKey = normalizeDraftDateKey(minDate);
- return (
-
- {mode === "weekday" ? (
- <>
- {weekdayRows.flatMap((row) => [
-
{`S${pad2(row.ordinal)}`},
- ...row.days.map((day, dayIndex) => {
- const dateKey = day ? formatLocalDateKey(day.date) : "";
- const disabled = !day || (minDateKey && dateKey < minDateKey);
- return (
-
- );
- })
- ])}
- >
+
setDisplayedMonth(getMonthStart(addMonths(monthStart, -1)))}
+ onCurrentMonth={() => setDisplayedMonth(getMonthStart(new Date()))}
+ onNextMonth={() => setDisplayedMonth(getMonthStart(addMonths(monthStart, 1)))}
+ onPointerDown={handleMonthPointerDown}
+ onPointerEnter={handleMonthPointerEnter}
+ onPointerUp={() => setMonthSelection(null)}
+ onEditEvent={editEvent}
+ />
) : (
- <>
-
-
- {formatMonthTitle(displayedMonth)}
-
-
- {WEEKDAY_LABELS.map((day) => {day.slice(0, 3)})}
- {days.map((day) => {
- const dateKey = formatLocalDateKey(day.date);
- const disabled = !day.inMonth || (minDateKey && dateKey < minDateKey);
- return (
-
- );
- })}
- >
+ setSelection(null)}
+ onEditEvent={editEvent}
+ />
)}
);
}
-
-function MonthlyRepeatSwitch({ value, textContent, onChange }) {
- const options = [
- { value: "monthDay", icon: "day-number", label: textContent.monthlyRepeatMonthDay || "Numéro du jour" },
- { value: "weekday", icon: "day-name", label: textContent.monthlyRepeatWeekday || "Jour de semaine" }
- ];
-
- return (
-
- {options.map((option) => (
-
- ))}
-
- );
-}
-
-function MonthEventRepeatSwitch({ value, monthlyRepeatBy, textContent, onChange }) {
- const options = [
- { value: "none", icon: "close", label: textContent.repeatNone || "Aucune" },
- { value: "weekly", icon: "calendar-week", label: textContent.repeatWeekly || "Chaque semaine", disabled: monthlyRepeatBy === "monthDay" },
- { value: "monthly", icon: "calendar-month", label: textContent.repeatMonthly || "Chaque mois" }
- ];
- const safeValue = monthlyRepeatBy === "monthDay" && value === "weekly" ? "none" : value;
-
- return (
-
- {options.map((option) => (
-
- ))}
-
- );
-}
-
-function CalendarAlertOffsets({ selectedOffsets, textContent, onToggle }) {
- const labels = {
- 10: textContent.alertOffset10 || "10 min avant",
- 5: textContent.alertOffset5 || "5 min avant",
- 0: textContent.alertOffset0 || "Au début"
- };
-
- return (
-
- );
-}
-
-function CalendarColorPicker({ value, textContent, onChange }) {
- const label = textContent.colorLabel || "Couleur";
- return (
-
-
{label}
-
-
- {CALENDAR_COLORS.map((color) => (
-
-
- );
-}
-
-function CalendarOccurrence({ occurrence, layout, active, onEdit }) {
- const durationMinutes = Math.max(WEEK_SLOT_MINUTES, (occurrence.endMs - occurrence.startMs) / 60000 || WEEK_SLOT_MINUTES);
- const startMinutes = occurrence.event.startMinutes;
- const timeLabel = `${minutesToTimeValue(occurrence.event.startMinutes)}${occurrence.event.endMinutes ? ` - ${minutesToTimeValue(occurrence.event.endMinutes)}` : ""}`;
- const compact = durationMinutes < 120;
- const top = getWeekMinutePosition(layout, startMinutes);
- const bottom = getWeekMinutePosition(layout, Math.min(24 * 60, startMinutes + durationMinutes));
- const height = compact ? WEEK_COMPACT_EVENT_HEIGHT : Math.max(WEEK_COMPACT_EVENT_HEIGHT, bottom - top);
-
- return (
-
- {occurrence.event.title}{timeLabel}>}
- onClick={onEdit}
- >
- {occurrence.event.title}
- {!compact && {timeLabel}}
-
-
- );
-}
-
-function CalendarMonthGrid({ days, occurrences, textContent, activeEventId, selectedRange, dragSelection, todayDate, nowLabel, onPointerDown, onPointerEnter, onPointerUp, onEditEvent }) {
- const monthBars = getMonthOccurrenceSegments(days, occurrences);
- const weekRowCount = Math.ceil(days.length / WEEKDAY_LABELS.length);
- return (
-
- {WEEKDAY_LABELS.map((day) => {day}
)}
- {days.map((day) => {
- const dateKey = formatLocalDateKey(day.date);
- const isToday = todayDate === dateKey;
- return (
- day.inMonth && onPointerDown(dateKey)}
- onPointerEnter={(event) => day.inMonth && onPointerEnter(event, dateKey)}
- onPointerUp={() => day.inMonth && onPointerUp()}
- onKeyDown={(event) => {
- if (!day.inMonth || (event.key !== "Enter" && event.key !== " ")) return;
- event.preventDefault();
- onPointerDown(dateKey);
- onPointerUp();
- }}
- aria-label={day.inMonth ? `${textContent.dayLabel || "Jour"} ${day.dayNumber}` : undefined}
- >
- {day.inMonth && (
- <>
- {day.dayNumber}
- {isToday && {nowLabel}}
- >
- )}
-
- );
- })}
-
- {monthBars.map((segment, index) => (
- {
- event.stopPropagation();
- onEditEvent(segment.occurrence.event, segment.occurrence);
- }}
- />
- ))}
-
-
- );
-}
-
-function CalendarMonthBar({ segment, active, onEdit }) {
- const gridRow = Math.floor(segment.startIndex / WEEKDAY_LABELS.length) + 2;
- const startColumn = segment.startIndex % WEEKDAY_LABELS.length + 1;
- const endColumn = segment.endIndex % WEEKDAY_LABELS.length + 2;
- const laneCount = Math.max(1, Math.min(3, segment.laneCount || 1));
- const barHeight = laneCount === 1 ? 28 : laneCount === 2 ? 23 : 18;
- const barGap = laneCount === 3 ? 3 : 4;
- const startLabel = new Date(segment.occurrence.startMs).toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" });
- const endDate = addDays(new Date(segment.occurrence.endMs), -1);
- const endLabel = endDate.toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" });
- const dateLabel = startLabel === endLabel ? startLabel : `${startLabel} - ${endLabel}`;
-
- return (
-
- {segment.occurrence.event.title}{dateLabel}>}
- onPointerDown={(event) => event.stopPropagation()}
- onClick={onEdit}
- >
- {segment.occurrence.event.title}
-
-
- );
-}
-
-function CalendarAlertModeSwitch({ mode, textContent, onChange }) {
- const labels = {
- off: textContent.alertOffTitle || "Pas d'alerte",
- visible: textContent.alertVisibleTitle || "Alerte quand la toolbox est affichée",
- site: textContent.alertSiteTitle || "Alerte sur tout le site"
- };
-
- return (
-
- {ALERT_MODES.map((option) => (
-
- ))}
-
- );
-}
diff --git a/website/src/features/toolboxes/modules/CalendarViews.jsx b/website/src/features/toolboxes/modules/CalendarViews.jsx
new file mode 100644
index 0000000..5588e23
--- /dev/null
+++ b/website/src/features/toolboxes/modules/CalendarViews.jsx
@@ -0,0 +1,302 @@
+// Rôle : rend les vues calendrier semaine et mois avec occurrences, sélection et ligne horaire courante.
+import { Tooltip } from "../../../components/Tooltip.jsx";
+import {
+ WEEK_SLOT_MINUTES,
+ addDays,
+ formatLocalDateKey,
+ minutesToTimeValue,
+ pad2
+} from "./calendarUtils.js";
+import {
+ HOUR_HALF_SLOT_COUNT,
+ WEEK_COMPACT_EVENT_HEIGHT,
+ WEEKDAY_LABELS,
+ getMonthOccurrenceSegments,
+ getWeekMinutePosition,
+ layoutConcurrentOccurrences
+} from "./calendarLayout.js";
+
+function formatDayLabel(day) {
+ return day.toLocaleDateString(undefined, { weekday: "long" });
+}
+
+function formatHourLabel(hourIndex) {
+ const minutes = hourIndex * 60;
+ return `${pad2(Math.floor(minutes / 60))}:${pad2(minutes % 60)}`;
+}
+
+function getSelectionRange(selection) {
+ if (!selection) return null;
+ const startSlot = Math.min(selection.startSlot, selection.endSlot);
+ const endSlot = Math.max(selection.startSlot, selection.endSlot);
+ return { dayIndex: selection.dayIndex, startSlot, endSlot };
+}
+
+function isSlotSelected(selection, dayIndex, slotIndex) {
+ const range = getSelectionRange(selection);
+ return Boolean(range && range.dayIndex === dayIndex && slotIndex >= range.startSlot && slotIndex <= range.endSlot);
+}
+
+function getMonthSelectionRange(selection) {
+ if (!selection) return null;
+ const startDate = selection.startDate <= selection.endDate ? selection.startDate : selection.endDate;
+ const endDate = selection.startDate <= selection.endDate ? selection.endDate : selection.startDate;
+ return { startDate, endDate };
+}
+
+function isMonthDateSelected(selection, dateKey) {
+ const range = getMonthSelectionRange(selection);
+ return Boolean(range && dateKey >= range.startDate && dateKey <= range.endDate);
+}
+
+function formatMonthTitle(dateLike) {
+ return new Date(dateLike).toLocaleDateString(undefined, { month: "long", year: "numeric" });
+}
+
+export function CalendarWeekGrid({
+ days,
+ occurrences,
+ textContent,
+ activeEventId,
+ formOpen,
+ draftSelection,
+ selection,
+ weekGridLayout,
+ nowDayIndex,
+ nowMinutes,
+ onSlotPointerDown,
+ onSlotPointerEnter,
+ onSlotPointerUp,
+ onEditEvent
+}) {
+ return (
+
+
+ {days.map((day) => {formatDayLabel(day)}
)}
+
+ {weekGridLayout.rows.map((row) => (
+ row.type === "hidden"
+ ? ...
+ : {formatHourLabel(row.hourIndex)}
+ ))}
+
+ {days.map((day, dayIndex) => {
+ const dayOccurrences = layoutConcurrentOccurrences(occurrences.filter((occurrence) => occurrence.event.dayIndex === dayIndex));
+ const isToday = dayIndex === nowDayIndex;
+ return (
+
+ {weekGridLayout.rows.map((row) => (
+ row.type === "hidden" ? (
+
+ {dayIndex === 3 && {`${formatHourLabel(row.startHour)} - ${formatHourLabel(row.endHour + 1)}`}}
+
+ ) : (
+
+ {Array.from({ length: HOUR_HALF_SLOT_COUNT }, (_, halfIndex) => {
+ const slotIndex = row.hourIndex * HOUR_HALF_SLOT_COUNT + halfIndex;
+ const slotLabel = minutesToTimeValue(slotIndex * WEEK_SLOT_MINUTES);
+ return (
+
+ )
+ ))}
+
+ {dayOccurrences.map((occurrence) => (
+ onEditEvent(occurrence.event)}
+ />
+ ))}
+
+ {isToday && (
+
+ {minutesToTimeValue(nowMinutes)}
+
+ )}
+
+ );
+ })}
+
+ );
+}
+
+function CalendarOccurrence({ occurrence, layout, active, onEdit }) {
+ const durationMinutes = Math.max(WEEK_SLOT_MINUTES, (occurrence.endMs - occurrence.startMs) / 60000 || WEEK_SLOT_MINUTES);
+ const startMinutes = occurrence.event.startMinutes;
+ const timeLabel = `${minutesToTimeValue(occurrence.event.startMinutes)}${occurrence.event.endMinutes ? ` - ${minutesToTimeValue(occurrence.event.endMinutes)}` : ""}`;
+ const compact = durationMinutes < 120;
+ const top = getWeekMinutePosition(layout, startMinutes);
+ const bottom = getWeekMinutePosition(layout, Math.min(24 * 60, startMinutes + durationMinutes));
+ const height = compact ? WEEK_COMPACT_EVENT_HEIGHT : Math.max(WEEK_COMPACT_EVENT_HEIGHT, bottom - top);
+
+ return (
+
+ {occurrence.event.title}{timeLabel}>}
+ onClick={onEdit}
+ >
+ {occurrence.event.title}
+ {!compact && {timeLabel}}
+
+
+ );
+}
+
+export function CalendarMonthView({
+ monthStart,
+ monthGridDays,
+ occurrences,
+ textContent,
+ activeEventId,
+ selectedRange,
+ dragSelection,
+ todayDate,
+ nowLabel,
+ onPreviousMonth,
+ onCurrentMonth,
+ onNextMonth,
+ onPointerDown,
+ onPointerEnter,
+ onPointerUp,
+ onEditEvent
+}) {
+ return (
+ <>
+
+
+ {formatMonthTitle(monthStart)}
+
+
+
+
+ >
+ );
+}
+
+function CalendarMonthGrid({ days, occurrences, textContent, activeEventId, selectedRange, dragSelection, todayDate, nowLabel, onPointerDown, onPointerEnter, onPointerUp, onEditEvent }) {
+ const monthBars = getMonthOccurrenceSegments(days, occurrences);
+ const weekRowCount = Math.ceil(days.length / WEEKDAY_LABELS.length);
+ return (
+
+ {WEEKDAY_LABELS.map((day) => {day}
)}
+ {days.map((day) => {
+ const dateKey = formatLocalDateKey(day.date);
+ const isToday = todayDate === dateKey;
+ return (
+ day.inMonth && onPointerDown(dateKey)}
+ onPointerEnter={(event) => day.inMonth && onPointerEnter(event, dateKey)}
+ onPointerUp={() => day.inMonth && onPointerUp()}
+ onKeyDown={(event) => {
+ if (!day.inMonth || (event.key !== "Enter" && event.key !== " ")) return;
+ event.preventDefault();
+ onPointerDown(dateKey);
+ onPointerUp();
+ }}
+ aria-label={day.inMonth ? `${textContent.dayLabel || "Jour"} ${day.dayNumber}` : undefined}
+ >
+ {day.inMonth && (
+ <>
+ {day.dayNumber}
+ {isToday && {nowLabel}}
+ >
+ )}
+
+ );
+ })}
+
+ {monthBars.map((segment, index) => (
+ {
+ event.stopPropagation();
+ onEditEvent(segment.occurrence.event, segment.occurrence);
+ }}
+ />
+ ))}
+
+
+ );
+}
+
+function CalendarMonthBar({ segment, active, onEdit }) {
+ const gridRow = Math.floor(segment.startIndex / WEEKDAY_LABELS.length) + 2;
+ const startColumn = segment.startIndex % WEEKDAY_LABELS.length + 1;
+ const endColumn = segment.endIndex % WEEKDAY_LABELS.length + 2;
+ const laneCount = Math.max(1, Math.min(3, segment.laneCount || 1));
+ const barHeight = laneCount === 1 ? 28 : laneCount === 2 ? 23 : 18;
+ const barGap = laneCount === 3 ? 3 : 4;
+ const startLabel = new Date(segment.occurrence.startMs).toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" });
+ const endDate = addDays(new Date(segment.occurrence.endMs), -1);
+ const endLabel = endDate.toLocaleDateString(undefined, { day: "2-digit", month: "short", year: "numeric" });
+ const dateLabel = startLabel === endLabel ? startLabel : `${startLabel} - ${endLabel}`;
+
+ return (
+
+ {segment.occurrence.event.title}{dateLabel}>}
+ onPointerDown={(event) => event.stopPropagation()}
+ onClick={onEdit}
+ >
+ {segment.occurrence.event.title}
+
+
+ );
+}