// Rôle : rend les branches récursives, catégories et zones de drop du task planner. import { Icon } from "../../../components/Icon.jsx"; import { TaskPlannerItem } from "./TaskPlannerItem.jsx"; import { countCompletedTaskTree, countTaskTree, getTaskTypeLabel, getTasksForParent, getTaskCategoryGroups, isTaskTreeComplete } from "./taskPlannerUtils.js"; export function TaskPlannerBranch({ parentId, parentMap, tasks, data, nowMs, reorder, textContent, openDescriptions, openTaskSettings, prerequisiteNoticeTaskIds, onToggleDescription, onToggleSettings, onPrerequisiteNotice, onUpdateTask, onDeleteTask, onAddLinkTarget, onRemoveLinkTarget, onSetPrerequisite, onSetTaskCategory, onToggleCategoryCollapsed, visited = new Set() }) { const groupedTasks = getTaskCategoryGroups(tasks, parentMap, parentId, data.categoryOrder); const children = [...groupedTasks.uncategorized, ...groupedTasks.categories.flatMap((group) => group.tasks)].filter((task) => !visited.has(task.id)); if (!children.length) return null; const nextVisited = new Set([...visited, ...children.map((task) => task.id)]); const showCategoryBoundaryDropZones = !parentId && reorder.shouldShowGroupBoundaries({ parentId }); function hasVisibleDescendants(taskId, seen = new Set()) { if (seen.has(taskId)) return false; seen.add(taskId); return getTasksForParent(tasks, parentMap, taskId).some((child) => !child.checked || hasVisibleDescendants(child.id, seen)); } function renderTask(task) { const nestedChildren = getTasksForParent(tasks, parentMap, task.id).filter((child) => !nextVisited.has(child.id)); const nestedBranch = nestedChildren.length ? ( ) : null; if (data.hideCompleted && task.checked) { if (!hasVisibleDescendants(task.id)) return null; return (
  • onUpdateTask(task.id, (current) => ({ ...current, checked: event.target.checked, checkedAt: event.target.checked ? Date.now() : 0 }))} aria-label={`${textContent.uncheckTitle || "Marquer non effectué"} ${task.title}`} /> {task.title} {getTaskTypeLabel(task.type, textContent)}
  • ); } return ( onToggleDescription(task.id)} onToggleSettings={() => onToggleSettings(task.id)} onPrerequisiteNotice={onPrerequisiteNotice} onUpdateTask={onUpdateTask} onDeleteTask={onDeleteTask} onAddLinkTarget={onAddLinkTarget} onRemoveLinkTarget={onRemoveLinkTarget} onSetPrerequisite={onSetPrerequisite} onSetTaskCategory={onSetTaskCategory} > {nestedBranch} ); } function renderCategory(group) { const groupTasks = group.tasks.filter((task) => !visited.has(task.id)); if (!groupTasks.length) return null; if (data.hideCompleted && isTaskTreeComplete(groupTasks.map((task) => task.id), tasks, parentMap)) return null; const groupCount = countTaskTree(groupTasks.map((task) => task.id), tasks, parentMap); const groupCompletedCount = countCompletedTaskTree(groupTasks.map((task) => task.id), tasks, parentMap); const isCollapsed = data.collapsedCategories.includes(group.category); const sectionClassName = [ "task-planner-category-section", groupCompletedCount === groupCount ? "is-complete" : "", isCollapsed ? "is-collapsed" : "", reorder.isGroupDragging(group.category, parentId) ? "is-dragging" : "", reorder.isGroupDropTarget(group.category, parentId) ? "is-drop-target" : "", reorder.getDropPlacement("group", group.category, parentId) === "after" ? "drop-after" : "" ].filter(Boolean).join(" "); const beforeDropZone = showCategoryBoundaryDropZones ? (
  • ) : null; const afterDropZone = showCategoryBoundaryDropZones ? (
  • ) : null; return [ beforeDropZone,
  • {group.category}

    {groupCompletedCount} / {groupCount}
    {!isCollapsed && ( )}
  • , afterDropZone ]; } return groupedTasks.entries.map((entry) => { if (entry.type === "task") return visited.has(entry.task.id) ? null : renderTask(entry.task); return renderCategory(entry.group); }).filter(Boolean); }