Extract calendar draft helpers
All checks were successful
Deploy Sokko G / deploy (push) Successful in 15s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 15s
This commit is contained in:
parent
d9bbf40fad
commit
f2c88ee891
4 changed files with 110 additions and 65 deletions
|
|
@ -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/);
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
74
website/src/features/toolboxes/modules/calendarDraft.js
Normal file
74
website/src/features/toolboxes/modules/calendarDraft.js
Normal 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";
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue