Refactor large toolbox modules
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
c2e37ad3f2
commit
6dd64f87c3
19 changed files with 2042 additions and 1659 deletions
|
|
@ -1,22 +1,14 @@
|
|||
// 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 { Icon } from "../../../components/Icon.jsx";
|
||||
import { useDraftForm } from "../../../hooks/useDraftForm.js";
|
||||
import { applyGroupedReorderOperation, useGroupedReorder } from "../../../hooks/useGroupedReorder.js";
|
||||
import { TextExchangeActions } from "./TextExchangeActions.jsx";
|
||||
import { TaskPlannerBranch } from "./TaskPlannerItems.jsx";
|
||||
import { TaskPlannerSettings } from "./TaskPlannerSettings.jsx";
|
||||
import { TaskPlannerTopbar } from "./TaskPlannerTopbar.jsx";
|
||||
import {
|
||||
TASK_TYPES,
|
||||
WEEK_DAYS,
|
||||
applyDueResets,
|
||||
getBoundaryTaskId,
|
||||
getRootTaskIdsForCategory,
|
||||
getTaskCategory,
|
||||
getTaskCategoryDataset,
|
||||
getTaskParentMap,
|
||||
getTaskTypeLabel,
|
||||
moveTask
|
||||
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: [] }));
|
||||
|
|
@ -29,117 +21,11 @@ export function TaskPlannerModule({ toolboxId, moduleId, context, editing, textE
|
|||
const [prerequisiteNoticeTaskIds, setPrerequisiteNoticeTaskIds] = useState(() => new Set());
|
||||
const prerequisiteNoticeTimeouts = useRef(new Map());
|
||||
const parentMap = useMemo(() => getTaskParentMap(data.tasks, data.relations), [data.tasks, data.relations]);
|
||||
const {
|
||||
itemReorder,
|
||||
groupReorder,
|
||||
getItemProps,
|
||||
getGroupProps,
|
||||
getGroupBoundaryProps,
|
||||
isItemDragging,
|
||||
isItemDropTarget,
|
||||
isGroupDragging,
|
||||
isGroupDropTarget,
|
||||
isBoundaryDropTarget,
|
||||
getDropPlacement,
|
||||
shouldShowGroupBoundaries
|
||||
} = useGroupedReorder({
|
||||
namespace: "task-planner",
|
||||
items: data.tasks,
|
||||
getItemId: (task) => task.id,
|
||||
getParentId: (task) => parentMap.get(task.id) || "",
|
||||
getItemGroup: (task) => getTaskCategoryDataset(task, data.tasks, parentMap),
|
||||
getEffectiveGroup: (task) => getTaskCategoryDataset(task, data.tasks, parentMap),
|
||||
reorderFeatures: {
|
||||
item: {
|
||||
groupChange: true,
|
||||
boundaryDrop: true,
|
||||
rootOnly: true
|
||||
},
|
||||
group: {
|
||||
reorder: true,
|
||||
boundaryDrop: true,
|
||||
itemDrop: true,
|
||||
rootOnly: true
|
||||
}
|
||||
},
|
||||
onItemMove: (operation) => {
|
||||
const fromTaskId = operation.sourceId;
|
||||
const fromTask = data.tasks.find((task) => task.id === fromTaskId);
|
||||
if (!fromTask) return;
|
||||
function setRootTaskCategory(tasks, category) {
|
||||
return tasks.map((task) => {
|
||||
if (task.id !== fromTaskId) return task;
|
||||
const nextTask = { ...task };
|
||||
if (category) nextTask.category = category;
|
||||
else delete nextTask.category;
|
||||
return nextTask;
|
||||
});
|
||||
}
|
||||
function moveToCategory(category, placement = operation.placement) {
|
||||
const nextTasks = setRootTaskCategory(data.tasks, category);
|
||||
const targetIds = category
|
||||
? getRootTaskIdsForCategory(data.tasks, parentMap, category).filter((taskId) => taskId !== fromTaskId)
|
||||
: data.tasks.filter((task) => !parentMap.get(task.id) && !getTaskCategory(task) && task.id !== fromTaskId).map((task) => task.id);
|
||||
const boundaryTaskId = getBoundaryTaskId(data.tasks, targetIds, placement);
|
||||
save({ ...data, tasks: boundaryTaskId ? moveTask(nextTasks, fromTaskId, boundaryTaskId, placement) : nextTasks });
|
||||
}
|
||||
if (operation.targetType === "boundary") {
|
||||
const targetIds = getRootTaskIdsForCategory(data.tasks, parentMap, operation.targetGroup);
|
||||
const boundaryTaskId = getBoundaryTaskId(data.tasks, targetIds, operation.placement);
|
||||
if (!boundaryTaskId) return;
|
||||
const sourceCategory = getTaskCategoryDataset(fromTask, data.tasks, parentMap);
|
||||
const tasks = sourceCategory ? setRootTaskCategory(data.tasks, "") : data.tasks;
|
||||
save({ ...data, tasks: moveTask(tasks, fromTaskId, boundaryTaskId, operation.placement) });
|
||||
return;
|
||||
}
|
||||
if (operation.targetType === "group") {
|
||||
const targetCategory = operation.targetGroup;
|
||||
const sourceCategory = getTaskCategoryDataset(fromTask, data.tasks, parentMap);
|
||||
if (!sourceCategory) {
|
||||
const targetIds = getRootTaskIdsForCategory(data.tasks, parentMap, targetCategory);
|
||||
const boundaryTaskId = getBoundaryTaskId(data.tasks, targetIds, operation.placement);
|
||||
if (boundaryTaskId) save({ ...data, tasks: moveTask(data.tasks, fromTaskId, boundaryTaskId, operation.placement) });
|
||||
return;
|
||||
}
|
||||
moveToCategory(targetCategory);
|
||||
return;
|
||||
}
|
||||
const targetTask = data.tasks.find((task) => task.id === operation.targetId);
|
||||
const targetCategory = targetTask ? getTaskCategoryDataset(targetTask, data.tasks, parentMap) : "";
|
||||
const sourceCategory = getTaskCategoryDataset(fromTask, data.tasks, parentMap);
|
||||
const shouldChangeRootCategory = !parentMap.get(fromTaskId) && targetTask && !parentMap.get(operation.targetId) && targetCategory !== sourceCategory;
|
||||
const tasks = shouldChangeRootCategory ? setRootTaskCategory(data.tasks, targetCategory) : data.tasks;
|
||||
save({ ...data, tasks: moveTask(tasks, fromTaskId, operation.targetId, operation.placement) });
|
||||
},
|
||||
onGroupMove: (operation) => {
|
||||
save(applyGroupedReorderOperation(data, {
|
||||
operation,
|
||||
itemsKey: "tasks",
|
||||
groupOrderKey: "categoryOrder",
|
||||
getItemGroup: (task) => parentMap.get(task.id) ? "" : getTaskCategory(task),
|
||||
setItemGroup: (task) => task
|
||||
}));
|
||||
},
|
||||
hierarchy: { enabled: true, stickyParents: true }
|
||||
});
|
||||
const reorder = {
|
||||
itemReorder,
|
||||
groupReorder,
|
||||
getItemProps,
|
||||
getGroupProps,
|
||||
getGroupBoundaryProps,
|
||||
isItemDragging,
|
||||
isItemDropTarget,
|
||||
isGroupDragging,
|
||||
isGroupDropTarget,
|
||||
isBoundaryDropTarget,
|
||||
getDropPlacement,
|
||||
shouldShowGroupBoundaries
|
||||
};
|
||||
|
||||
function save(nextData) {
|
||||
context.setModuleData(toolboxId, moduleId, nextData, "taskPlanner");
|
||||
}
|
||||
const reorder = useTaskPlannerReorder({ data, parentMap, save });
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = window.setInterval(() => setNowMs(Date.now()), 60000);
|
||||
|
|
@ -280,75 +166,28 @@ export function TaskPlannerModule({ toolboxId, moduleId, context, editing, textE
|
|||
|
||||
return (
|
||||
<div className="task-planner-module">
|
||||
<div className="task-planner-topbar">
|
||||
<div className="task-planner-global-controls">
|
||||
<button
|
||||
className={`tool-split-scroll-toggle task-planner-hide-completed-toggle ${data.hideCompleted ? "active" : ""}`}
|
||||
type="button"
|
||||
onClick={() => save({ ...data, hideCompleted: !data.hideCompleted })}
|
||||
aria-pressed={data.hideCompleted}
|
||||
aria-label={textContent.hideCompletedTitle || "Masquer les tâches effectuées"}
|
||||
title={textContent.hideCompletedTitle || "Masquer les tâches effectuées"}
|
||||
>
|
||||
<Icon name={data.hideCompleted ? "eye-closed" : "eye-open"} />
|
||||
<i aria-hidden="true" />
|
||||
</button>
|
||||
<button
|
||||
className={`task-planner-settings-toggle ${settingsOpen ? "active" : ""}`}
|
||||
type="button"
|
||||
onClick={() => setSettingsOpen((value) => !value)}
|
||||
aria-expanded={settingsOpen}
|
||||
aria-label={textContent.settingsTitle || "Réglages du planificateur"}
|
||||
title={textContent.settingsTitle || "Réglages du planificateur"}
|
||||
>
|
||||
<Icon name="settings" />
|
||||
<span>{textContent.settingsButtonLabel || "Paramètres des réinitialisations"}</span>
|
||||
</button>
|
||||
</div>
|
||||
{editing && (
|
||||
<div className="task-planner-add-stack">
|
||||
<form className="inline-form task-planner-add-form" onSubmit={addTask}>
|
||||
<input {...taskDraft.getFieldProps("title", { placeholder: textContent.titlePlaceholder || "Nouvelle tâche" })} />
|
||||
<select {...taskDraft.getFieldProps("type", { "aria-label": textContent.typeLabel || "Type" })}>
|
||||
{TASK_TYPES.map((taskType) => <option key={taskType} value={taskType}>{getTaskTypeLabel(taskType, textContent)}</option>)}
|
||||
</select>
|
||||
<button className="primary">{textContent.addButton || "Ajouter"}</button>
|
||||
</form>
|
||||
</div>
|
||||
)}
|
||||
{textExchangeOpen && (
|
||||
<div className="module-text-exchange-panel">
|
||||
<TextExchangeActions type="taskPlanner" data={data} context={context} textContent={textContent} onImport={(nextData) => save(nextData)} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<TaskPlannerTopbar
|
||||
data={data}
|
||||
context={context}
|
||||
editing={editing}
|
||||
settingsOpen={settingsOpen}
|
||||
taskDraft={taskDraft}
|
||||
textContent={textContent}
|
||||
textExchangeOpen={textExchangeOpen}
|
||||
onAddTask={addTask}
|
||||
onHideCompletedToggle={() => save({ ...data, hideCompleted: !data.hideCompleted })}
|
||||
onSettingsToggle={() => setSettingsOpen((value) => !value)}
|
||||
onImport={(nextData) => save(nextData)}
|
||||
/>
|
||||
|
||||
{settingsOpen && (
|
||||
<section className="task-planner-settings" aria-label={textContent.settingsTitle || "Réglages du planificateur"}>
|
||||
<label>
|
||||
<span>{textContent.globalWeeklyResetDayLabel || "Reset hebdo"}</span>
|
||||
<select value={data.weeklyResetDay} onChange={(event) => save({ ...data, weeklyResetDay: Number(event.target.value) })}>
|
||||
{WEEK_DAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>{textContent.resetTimeLabel || "Heure de reset"}</span>
|
||||
<input type="time" value={data.resetTime} onChange={(event) => save({ ...data, resetTime: event.target.value || "00:00" })} />
|
||||
</label>
|
||||
<div className="task-planner-settings-actions">
|
||||
<button
|
||||
className="task-planner-clear-completed-button danger"
|
||||
type="button"
|
||||
onClick={clearCompletedUniqueTasks}
|
||||
disabled={!data.tasks.some((task) => task.type === "unique" && task.checked)}
|
||||
title={textContent.clearCompletedUniqueTitle || "Nettoyer les tâches ponctuelles terminées"}
|
||||
aria-label={textContent.clearCompletedUniqueTitle || "Nettoyer les tâches ponctuelles terminées"}
|
||||
>
|
||||
<Icon name="trash" />
|
||||
<span>{textContent.clearCompletedUniqueButton || "Nettoyer les tâches ponctuelles terminées"}</span>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
<TaskPlannerSettings
|
||||
data={data}
|
||||
textContent={textContent}
|
||||
onWeeklyResetDayChange={(weeklyResetDay) => save({ ...data, weeklyResetDay })}
|
||||
onResetTimeChange={(resetTime) => save({ ...data, resetTime })}
|
||||
onClearCompletedUniqueTasks={clearCompletedUniqueTasks}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ul className="task-planner-list">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue