// Rôle : fournit l'outil task planner avec tâches récurrentes, parents, pré requis et échange texte. import { useEffect, useMemo, useRef, useState } from "react"; import { useDraftForm } from "../../../hooks/useDraftForm.js"; import { TaskPlannerBranch } from "./TaskPlannerBranch.jsx"; import { TaskPlannerSettings } from "./TaskPlannerSettings.jsx"; import { TaskPlannerTopbar } from "./TaskPlannerTopbar.jsx"; import { applyDueResets, getTaskParentMap } from "./taskPlannerUtils.js"; import { useTaskPlannerReorder } from "./useTaskPlannerReorder.js"; export function TaskPlannerModule({ toolboxId, moduleId, context, editing, textExchangeOpen }) { const data = context.normalizeTaskPlannerData(context.getModuleData(toolboxId, moduleId, { tasks: [] })); const textContent = context.moduleText?.taskPlanner || {}; const taskDraft = useDraftForm({ title: "", type: "daily", category: "" }); const categoryOptions = useMemo(() => [ ...data.categoryOrder, ...data.tasks.map((task) => task.category).filter(Boolean).filter((category) => !data.categoryOrder.includes(category)) ], [data.categoryOrder, data.tasks]); const [nowMs, setNowMs] = useState(() => Date.now()); const [settingsOpen, setSettingsOpen] = useState(false); const [openDescriptions, setOpenDescriptions] = useState(() => new Set()); const [openTaskSettings, setOpenTaskSettings] = useState(() => new Set()); const [prerequisiteNoticeTaskIds, setPrerequisiteNoticeTaskIds] = useState(() => new Set()); const prerequisiteNoticeTimeouts = useRef(new Map()); const parentMap = useMemo(() => getTaskParentMap(data.tasks, data.relations), [data.tasks, data.relations]); function save(nextData) { context.setModuleData(toolboxId, moduleId, nextData, "taskPlanner"); } const reorder = useTaskPlannerReorder({ data, parentMap, save }); useEffect(() => { const intervalId = window.setInterval(() => setNowMs(Date.now()), 60000); return () => window.clearInterval(intervalId); }, []); useEffect(() => () => { prerequisiteNoticeTimeouts.current.forEach((timeoutId) => window.clearTimeout(timeoutId)); }, []); useEffect(() => { const resetData = applyDueResets(data, nowMs); if (resetData !== data) save(resetData); }); function addTask(event) { taskDraft.handleSubmit(event, (draft, { reset }) => { const cleanTitle = draft.title.trim(); if (!cleanTitle) return; const category = draft.category.trim(); const task = { id: context.uid("task"), title: cleanTitle, description: "", type: draft.type, checked: false, checkedAt: 0 }; if (category) task.category = category; save({ ...data, tasks: [...data.tasks, task], categoryOrder: category && !data.categoryOrder.includes(category) ? [...data.categoryOrder, category] : data.categoryOrder }); reset(); }); } function updateTask(taskId, updater) { save({ ...data, tasks: data.tasks.map((task) => task.id === taskId ? updater(task) : task) }); } function deleteTask(taskId) { save({ ...data, tasks: data.tasks.filter((task) => task.id !== taskId), relations: data.relations.filter((relation) => relation.fromTaskId !== taskId && relation.toTaskId !== taskId) }); } function addLinkTarget(fromTaskId, toTaskId) { if (!toTaskId || fromTaskId === toTaskId || data.relations.some((relation) => relation.fromTaskId === fromTaskId && relation.toTaskId === toTaskId)) return; save({ ...data, tasks: data.tasks.map((task) => { if (task.id !== fromTaskId || !task.category) return task; const nextTask = { ...task }; delete nextTask.category; return nextTask; }), relations: [ ...data.relations.filter((relation) => relation.fromTaskId !== fromTaskId), { id: context.uid("relation"), fromTaskId, toTaskId, prerequisite: false } ] }); } function removeLinkTarget(fromTaskId, toTaskId) { save({ ...data, relations: data.relations.filter((relation) => relation.fromTaskId !== fromTaskId || relation.toTaskId !== toTaskId) }); } function setPrerequisite(fromTaskId, toTaskId, prerequisite) { const hasRelation = data.relations.some((relation) => relation.fromTaskId === fromTaskId && relation.toTaskId === toTaskId); save({ ...data, relations: hasRelation ? data.relations.map((relation) => relation.fromTaskId === fromTaskId && relation.toTaskId === toTaskId ? { ...relation, prerequisite } : relation) : [...data.relations, { id: context.uid("relation"), fromTaskId, toTaskId, prerequisite }] }); } function setTaskCategory(taskId, category) { const normalizedCategory = String(category || "").trim(); const tasks = data.tasks.map((task) => { if (task.id !== taskId) return task; const nextTask = { ...task }; if (normalizedCategory) nextTask.category = normalizedCategory; else delete nextTask.category; return nextTask; }); const categoryOrder = [ ...data.categoryOrder.filter((categoryName) => tasks.some((task) => task.category === categoryName)), ...tasks.map((task) => task.category).filter(Boolean).filter((categoryName) => !data.categoryOrder.includes(categoryName)) ]; save({ ...data, tasks, categoryOrder }); } function toggleCategoryCollapsed(category) { const collapsedCategories = data.collapsedCategories.includes(category) ? data.collapsedCategories.filter((item) => item !== category) : [...data.collapsedCategories, category]; save({ ...data, collapsedCategories }); } function clearCompletedUniqueTasks() { const removedTaskIds = new Set(data.tasks.filter((task) => task.type === "unique" && task.checked).map((task) => task.id)); if (!removedTaskIds.size) return; const tasks = data.tasks.filter((task) => !removedTaskIds.has(task.id)); const relations = data.relations.filter((relation) => !removedTaskIds.has(relation.fromTaskId) && !removedTaskIds.has(relation.toTaskId)); save({ ...data, tasks, relations }); } function toggleSet(setter, taskId) { setter((current) => { const next = new Set(current); if (next.has(taskId)) next.delete(taskId); else next.add(taskId); return next; }); } function showPrerequisiteNotice(taskId) { const currentTimeoutId = prerequisiteNoticeTimeouts.current.get(taskId); if (currentTimeoutId) window.clearTimeout(currentTimeoutId); setPrerequisiteNoticeTaskIds((current) => new Set(current).add(taskId)); const timeoutId = window.setTimeout(() => { prerequisiteNoticeTimeouts.current.delete(taskId); setPrerequisiteNoticeTaskIds((current) => { const next = new Set(current); next.delete(taskId); return next; }); }, 2600); prerequisiteNoticeTimeouts.current.set(taskId, timeoutId); } return (
save({ ...data, hideCompleted: !data.hideCompleted })} onSettingsToggle={() => setSettingsOpen((value) => !value)} onImport={(nextData) => save(nextData)} /> {settingsOpen && ( save({ ...data, weeklyResetDay })} onResetTimeChange={(resetTime) => save({ ...data, resetTime })} onClearCompletedUniqueTasks={clearCompletedUniqueTasks} /> )}
    {data.tasks.length ? ( toggleSet(setOpenDescriptions, taskId)} onToggleSettings={(taskId) => toggleSet(setOpenTaskSettings, taskId)} onPrerequisiteNotice={showPrerequisiteNotice} onUpdateTask={updateTask} onDeleteTask={deleteTask} onAddLinkTarget={addLinkTarget} onRemoveLinkTarget={removeLinkTarget} onSetPrerequisite={setPrerequisite} onSetTaskCategory={setTaskCategory} onToggleCategoryCollapsed={toggleCategoryCollapsed} /> ) : (
  • {textContent.emptyTasks || "Aucune tâche planifiée."}
  • )}
); }