Split remaining toolbox modules by responsibility
All checks were successful
Deploy Sokko G / deploy (push) Successful in 16s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 16s
This commit is contained in:
parent
43a9e791c5
commit
a0d7e80a1a
25 changed files with 2199 additions and 1992 deletions
199
website/src/features/toolboxes/modules/TaskPlannerBranch.jsx
Normal file
199
website/src/features/toolboxes/modules/TaskPlannerBranch.jsx
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
// 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 ? (
|
||||
<TaskPlannerBranch
|
||||
parentId={task.id}
|
||||
parentMap={parentMap}
|
||||
tasks={tasks}
|
||||
data={data}
|
||||
nowMs={nowMs}
|
||||
reorder={reorder}
|
||||
textContent={textContent}
|
||||
openDescriptions={openDescriptions}
|
||||
openTaskSettings={openTaskSettings}
|
||||
prerequisiteNoticeTaskIds={prerequisiteNoticeTaskIds}
|
||||
onToggleDescription={onToggleDescription}
|
||||
onToggleSettings={onToggleSettings}
|
||||
onPrerequisiteNotice={onPrerequisiteNotice}
|
||||
onUpdateTask={onUpdateTask}
|
||||
onDeleteTask={onDeleteTask}
|
||||
onAddLinkTarget={onAddLinkTarget}
|
||||
onRemoveLinkTarget={onRemoveLinkTarget}
|
||||
onSetPrerequisite={onSetPrerequisite}
|
||||
onSetTaskCategory={onSetTaskCategory}
|
||||
onToggleCategoryCollapsed={onToggleCategoryCollapsed}
|
||||
visited={nextVisited}
|
||||
/>
|
||||
) : null;
|
||||
if (data.hideCompleted && task.checked) {
|
||||
if (!hasVisibleDescendants(task.id)) return null;
|
||||
return (
|
||||
<li className="task-planner-hidden-parent" key={`${task.id}:visible-children`}>
|
||||
<div className="task-planner-hidden-parent-line">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={task.checked}
|
||||
onChange={(event) => 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}`}
|
||||
/>
|
||||
<span title={task.title}>{task.title}</span>
|
||||
<small>{getTaskTypeLabel(task.type, textContent)}</small>
|
||||
</div>
|
||||
<ul className="task-planner-list task-planner-child-list">{nestedBranch}</ul>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TaskPlannerItem
|
||||
key={task.id}
|
||||
task={task}
|
||||
tasks={tasks}
|
||||
data={data}
|
||||
nowMs={nowMs}
|
||||
parentMap={parentMap}
|
||||
parentId={parentId}
|
||||
reorder={reorder}
|
||||
textContent={textContent}
|
||||
descriptionOpen={openDescriptions.has(task.id)}
|
||||
settingsOpen={openTaskSettings.has(task.id)}
|
||||
prerequisiteNoticeVisible={prerequisiteNoticeTaskIds.has(task.id)}
|
||||
onToggleDescription={() => onToggleDescription(task.id)}
|
||||
onToggleSettings={() => onToggleSettings(task.id)}
|
||||
onPrerequisiteNotice={onPrerequisiteNotice}
|
||||
onUpdateTask={onUpdateTask}
|
||||
onDeleteTask={onDeleteTask}
|
||||
onAddLinkTarget={onAddLinkTarget}
|
||||
onRemoveLinkTarget={onRemoveLinkTarget}
|
||||
onSetPrerequisite={onSetPrerequisite}
|
||||
onSetTaskCategory={onSetTaskCategory}
|
||||
>
|
||||
{nestedBranch}
|
||||
</TaskPlannerItem>
|
||||
);
|
||||
}
|
||||
|
||||
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 ? (
|
||||
<li
|
||||
className={`task-planner-category-boundary-drop-zone ${reorder.isBoundaryDropTarget({ groupId: group.category, parentId, placement: "before" }) ? "is-drop-target" : ""}`}
|
||||
key={`${parentId || "root"}:${group.category}:before-drop`}
|
||||
{...reorder.getGroupBoundaryProps({ groupId: group.category, parentId, placement: "before" })}
|
||||
/>
|
||||
) : null;
|
||||
const afterDropZone = showCategoryBoundaryDropZones ? (
|
||||
<li
|
||||
className={`task-planner-category-boundary-drop-zone ${reorder.isBoundaryDropTarget({ groupId: group.category, parentId, placement: "after" }) ? "is-drop-target" : ""}`}
|
||||
key={`${parentId || "root"}:${group.category}:after-drop`}
|
||||
{...reorder.getGroupBoundaryProps({ groupId: group.category, parentId, placement: "after" })}
|
||||
/>
|
||||
) : null;
|
||||
return [
|
||||
beforeDropZone,
|
||||
<li className={sectionClassName} key={`${parentId || "root"}:${group.category}`} {...reorder.getGroupProps({ groupId: group.category, parentId })}>
|
||||
<div className="checklist-section-header task-planner-category-header">
|
||||
<div className="task-planner-category-title">
|
||||
<button
|
||||
className="task-planner-category-drag-handle"
|
||||
type="button"
|
||||
onPointerDown={(event) => reorder.groupReorder.startDrag(event, { groupId: group.category, parentId })}
|
||||
aria-label={`${textContent.categoryReorderTitle || "Déplacer la catégorie"} ${group.category}`}
|
||||
title={textContent.categoryReorderTitle || "Déplacer la catégorie"}
|
||||
>
|
||||
<Icon name="drag" />
|
||||
</button>
|
||||
<h3>{group.category}</h3>
|
||||
</div>
|
||||
<div className="task-planner-category-actions">
|
||||
<span>{groupCompletedCount} / {groupCount}</span>
|
||||
<button
|
||||
className="checklist-section-collapse-button"
|
||||
type="button"
|
||||
onClick={() => onToggleCategoryCollapsed(group.category)}
|
||||
aria-expanded={!isCollapsed}
|
||||
title={isCollapsed ? textContent.showCategoryTitle || "Afficher cette catégorie" : textContent.hideCategoryTitle || "Réduire cette catégorie"}
|
||||
aria-label={isCollapsed ? textContent.showCategoryTitle || "Afficher cette catégorie" : textContent.hideCategoryTitle || "Réduire cette catégorie"}
|
||||
>
|
||||
<Icon name={isCollapsed ? "chevron-down" : "chevron-up"} />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{!isCollapsed && (
|
||||
<ul className="task-planner-list">
|
||||
{groupTasks.map(renderTask).filter(Boolean)}
|
||||
</ul>
|
||||
)}
|
||||
</li>,
|
||||
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);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue