Extract calendar draft helpers
All checks were successful
Deploy Sokko G / deploy (push) Successful in 15s

This commit is contained in:
Shinuwa 2026-08-24 22:11:16 +02:00
parent d9bbf40fad
commit f2c88ee891
4 changed files with 110 additions and 65 deletions

View file

@ -155,9 +155,10 @@ 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 calendarDraft = await readFile("website/src/features/toolboxes/modules/calendarDraft.js", "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 calendarBundle = `${calendarModule}\n${calendarDraft}\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");
@ -354,6 +355,9 @@ test("toolbox module registry and modules expose expected behavior", async () =>
assert.match(calendarModule, /type="calendar"/);
assert.match(calendarBundle, /repeatLabel/);
assert.match(calendarBundle, /CalendarAlertModeSwitch/);
assert.match(calendarDraft, /export function createDefaultDraft/);
assert.match(calendarDraft, /export function getDraftSelection/);
assert.match(calendarDraft, /export function normalizeDraftAlertOffsets/);
assert.doesNotMatch(moduleRegistry, /calendarInitOpen/);
assert.doesNotMatch(moduleRegistry, /calendar-init-modal/);
assert.match(calendarUtils, /export function getCalendarOccurrences/);

View file

@ -6,6 +6,7 @@ import { exportModuleText, importModuleText, parseColonImportLines } from "../we
import { buildInsertedMixTagValue, normalizeMixTagSpacing, normalizeMixTagText, removeMixTagAtOffset, tokenizeMixTagValue } from "../website/src/utils/mixTagsInputUtils.js";
import { getEffectiveGoalTreeProgressMode, getVisibleGoalTreeEdges } from "../website/src/features/toolboxes/modules/goalTreeDisplay.js";
import { calculateGoalTreeFormula, evaluateGoalTreeNodes } from "../website/src/features/toolboxes/modules/goalTreeUtils.js";
import { createDefaultDraft, getDateSpanDays, getDraftSelection, normalizeDraftAlertOffsets, normalizeDraftMonthlyRepeatBy } from "../website/src/features/toolboxes/modules/calendarDraft.js";
import { getMonthOccurrenceSegments, getWeekGridLayout, getWeekMinutePosition, layoutConcurrentOccurrences } from "../website/src/features/toolboxes/modules/calendarLayout.js";
import { getTimePatternRecurrenceMs, getTimePatternTargetMs } from "../website/src/features/toolboxes/modules/timerUtils.js";
import { compactModuleDataForStorage, createToolboxExportPayload, normalizeCalculatorData, normalizeChecklistData, normalizeCombosData, normalizeCountersData, normalizeEquipmentPlannerData, normalizeGoalTreeData, normalizeImageAnnotationData, normalizeLinksData, normalizeModuleOrder, normalizeNotepadData, normalizeTableData, normalizeTaskPlannerData, normalizeTimerData, normalizeToolbox, normalizeUrl, summarizeEquipmentPlannerData } from "../website/src/features/toolboxes/storage/toolboxStorage.js";
@ -168,6 +169,26 @@ test("calendar layout keeps week compaction and overlapping lanes deterministic"
]);
});
test("calendar draft helpers normalize selections and options", () => {
const draft = createDefaultDraft(new Date(2026, 7, 14, 8, 17));
assert.equal(draft.dayIndex, 4);
assert.equal(draft.startDate, "2026-08-14");
assert.equal(draft.startTime, "08:30");
assert.equal(draft.endTime, "09:00");
assert.deepEqual(getDraftSelection({ ...draft, startTime: "08:30", endTime: "10:00" }), {
dayIndex: 4,
startSlot: 17,
endSlot: 19
});
assert.equal(getDraftSelection({ ...draft, dayIndex: 8 }), null);
assert.deepEqual(normalizeDraftAlertOffsets([10, 5, 10, "60", 999]), [10, 5]);
assert.equal(normalizeDraftMonthlyRepeatBy("weekday"), "weekday");
assert.equal(normalizeDraftMonthlyRepeatBy("invalid"), "monthDay");
assert.equal(getDateSpanDays("2026-08-14", "2026-08-17"), 3);
assert.equal(getDateSpanDays("2026-08-17", "2026-08-14"), 0);
});
test("calendar month layout splits bars on week boundaries and shares lanes", () => {
const days = Array.from({ length: 14 }, (_, index) => ({
date: new Date(2026, 0, 5 + index),

View file

@ -6,9 +6,7 @@ import { CalendarMonthView, CalendarWeekGrid } from "./CalendarViews.jsx";
import { TextExchangeActions } from "./TextExchangeActions.jsx";
import {
CALENDAR_ALERT_MODES,
CALENDAR_ALERT_OFFSETS,
CALENDAR_COLORS,
CALENDAR_MONTHLY_REPEAT_BY,
WEEK_SLOT_MINUTES,
addDays,
addMonths,
@ -24,68 +22,16 @@ import {
parseLocalDateKey,
timeValueToMinutes
} from "./calendarUtils.js";
import { WEEK_SLOT_COUNT, getWeekGridLayout } from "./calendarLayout.js";
import {
WEEK_SLOT_COUNT,
getWeekGridLayout
} from "./calendarLayout.js";
function createDefaultDraft(date = new Date()) {
const dayIndex = getLocalDayIndex(date);
const dateKey = formatLocalDateKey(date);
const startMinutes = Math.min(23 * 60 + 30, Math.ceil((date.getHours() * 60 + date.getMinutes()) / WEEK_SLOT_MINUTES) * WEEK_SLOT_MINUTES);
return {
id: "",
title: "",
dayIndex,
endDayIndex: dayIndex,
startDate: dateKey,
endDate: dateKey,
monthlyRepeatBy: "monthDay",
repeat: "monthly",
startTime: minutesToTimeValue(startMinutes),
endTime: minutesToTimeValue(Math.min(23 * 60 + 59, startMinutes + WEEK_SLOT_MINUTES)),
repeatable: true,
alertMode: "off",
alertOffsetMinutes: [],
color: "violet"
};
}
function getDraftSelection(draft) {
const dayIndex = Number(draft.dayIndex);
const startMinutes = timeValueToMinutes(draft.startTime);
const endMinutes = draft.endTime ? timeValueToMinutes(draft.endTime) : startMinutes + WEEK_SLOT_MINUTES;
if (dayIndex < 0 || dayIndex > 6 || startMinutes < 0 || endMinutes <= startMinutes) return null;
const startSlot = Math.floor(startMinutes / WEEK_SLOT_MINUTES);
const endSlot = Math.max(startSlot, Math.ceil(endMinutes / WEEK_SLOT_MINUTES) - 1);
return {
dayIndex,
startSlot: Math.max(0, Math.min(WEEK_SLOT_COUNT - 1, startSlot)),
endSlot: Math.max(0, Math.min(WEEK_SLOT_COUNT - 1, endSlot))
};
}
function getCalendarRepeatMode() {
return "weekly";
}
function getDateSpanDays(startDateKey, endDateKey) {
const start = parseLocalDateKey(startDateKey);
const end = parseLocalDateKey(endDateKey);
if (!start || !end || end < start) return 0;
return Math.round((end.getTime() - start.getTime()) / (24 * 60 * 60 * 1000));
}
function normalizeDraftDateKey(value) {
const date = parseLocalDateKey(value);
return date ? formatLocalDateKey(date) : "";
}
function normalizeDraftAlertOffsets(value) {
const rawOffsets = Array.isArray(value) ? value : [value];
const offsets = [...new Set(rawOffsets.map(Number).filter((offset) => CALENDAR_ALERT_OFFSETS.includes(offset)))];
return offsets.sort((a, b) => b - a);
}
createDefaultDraft,
getCalendarRepeatMode,
getDateSpanDays,
getDraftSelection,
normalizeDraftAlertOffsets,
normalizeDraftDateKey,
normalizeDraftMonthlyRepeatBy
} from "./calendarDraft.js";
export function CalendarModule({ toolboxId, moduleId, context, editing = false, setEditing, textExchangeOpen }) {
const data = context.normalizeCalendarData(context.getModuleData(toolboxId, moduleId, { initialized: false, viewMode: "week", events: [] }));
@ -330,7 +276,7 @@ export function CalendarModule({ toolboxId, moduleId, context, editing = false,
const endDate = normalizeDraftDateKey(draft.endDate || draft.startDate);
const startMinutes = data.viewMode === "month" ? 0 : timeValueToMinutes(draft.startTime);
const endMinutes = data.viewMode === "month" ? -1 : draft.endTime ? timeValueToMinutes(draft.endTime) : -1;
const monthlyRepeatBy = CALENDAR_MONTHLY_REPEAT_BY.includes(draft.monthlyRepeatBy) ? draft.monthlyRepeatBy : "monthDay";
const monthlyRepeatBy = normalizeDraftMonthlyRepeatBy(draft.monthlyRepeatBy);
const monthRepeat = monthlyRepeatBy === "monthDay" && draft.repeat === "weekly" ? "none" : ["none", "weekly", "monthly"].includes(draft.repeat) ? draft.repeat : "monthly";
const validWeekTarget = data.viewMode === "week" && dayIndex >= 0 && dayIndex <= 6;
const validMonthTarget = data.viewMode === "month" && startDate && endDate && endDate >= startDate;

View file

@ -0,0 +1,74 @@
// Rôle : regroupe les helpers purs de draft et de sélection du calendrier.
import {
CALENDAR_ALERT_OFFSETS,
CALENDAR_MONTHLY_REPEAT_BY,
WEEK_SLOT_MINUTES,
formatLocalDateKey,
getLocalDayIndex,
minutesToTimeValue,
parseLocalDateKey,
timeValueToMinutes
} from "./calendarUtils.js";
import { WEEK_SLOT_COUNT } from "./calendarLayout.js";
export function createDefaultDraft(date = new Date()) {
const dayIndex = getLocalDayIndex(date);
const dateKey = formatLocalDateKey(date);
const startMinutes = Math.min(23 * 60 + 30, Math.ceil((date.getHours() * 60 + date.getMinutes()) / WEEK_SLOT_MINUTES) * WEEK_SLOT_MINUTES);
return {
id: "",
title: "",
dayIndex,
endDayIndex: dayIndex,
startDate: dateKey,
endDate: dateKey,
monthlyRepeatBy: "monthDay",
repeat: "monthly",
startTime: minutesToTimeValue(startMinutes),
endTime: minutesToTimeValue(Math.min(23 * 60 + 59, startMinutes + WEEK_SLOT_MINUTES)),
repeatable: true,
alertMode: "off",
alertOffsetMinutes: [],
color: "violet"
};
}
export function getDraftSelection(draft) {
const dayIndex = Number(draft.dayIndex);
const startMinutes = timeValueToMinutes(draft.startTime);
const endMinutes = draft.endTime ? timeValueToMinutes(draft.endTime) : startMinutes + WEEK_SLOT_MINUTES;
if (dayIndex < 0 || dayIndex > 6 || startMinutes < 0 || endMinutes <= startMinutes) return null;
const startSlot = Math.floor(startMinutes / WEEK_SLOT_MINUTES);
const endSlot = Math.max(startSlot, Math.ceil(endMinutes / WEEK_SLOT_MINUTES) - 1);
return {
dayIndex,
startSlot: Math.max(0, Math.min(WEEK_SLOT_COUNT - 1, startSlot)),
endSlot: Math.max(0, Math.min(WEEK_SLOT_COUNT - 1, endSlot))
};
}
export function getCalendarRepeatMode() {
return "weekly";
}
export function getDateSpanDays(startDateKey, endDateKey) {
const start = parseLocalDateKey(startDateKey);
const end = parseLocalDateKey(endDateKey);
if (!start || !end || end < start) return 0;
return Math.round((end.getTime() - start.getTime()) / (24 * 60 * 60 * 1000));
}
export function normalizeDraftDateKey(value) {
const date = parseLocalDateKey(value);
return date ? formatLocalDateKey(date) : "";
}
export function normalizeDraftAlertOffsets(value) {
const rawOffsets = Array.isArray(value) ? value : [value];
const offsets = [...new Set(rawOffsets.map(Number).filter((offset) => CALENDAR_ALERT_OFFSETS.includes(offset)))];
return offsets.sort((a, b) => b - a);
}
export function normalizeDraftMonthlyRepeatBy(value) {
return CALENDAR_MONTHLY_REPEAT_BY.includes(value) ? value : "monthDay";
}