diff --git a/tests/static-toolboxes.test.mjs b/tests/static-toolboxes.test.mjs
index c776e46..8a04f1e 100644
--- a/tests/static-toolboxes.test.mjs
+++ b/tests/static-toolboxes.test.mjs
@@ -161,6 +161,8 @@ test("toolbox module registry and modules expose expected behavior", async () =>
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");
+ const taskPlannerItemPanels = await readFile("website/src/features/toolboxes/modules/TaskPlannerItemPanels.jsx", "utf8");
+ const taskPlannerBundle = `${taskPlannerItems}\n${taskPlannerItemPanels}`;
const taskPlannerUtils = await readFile("website/src/features/toolboxes/modules/taskPlannerUtils.js", "utf8");
const goalTreeModule = await readFile("website/src/features/toolboxes/modules/GoalTreeModule.jsx", "utf8");
const goalTreeParts = await readFile("website/src/features/toolboxes/modules/GoalTreeParts.jsx", "utf8");
@@ -365,11 +367,11 @@ test("toolbox module registry and modules expose expected behavior", async () =>
assert.match(taskPlannerModule, /reorderFeatures/);
assert.match(taskPlannerItems, /export function TaskPlannerBranch/);
assert.match(taskPlannerItems, /function TaskPlannerItem/);
- assert.match(taskPlannerItems, /task-planner-warning/);
- assert.match(taskPlannerItems, /useInlineEdit/);
- assert.match(taskPlannerItems, /commitOnEnter: false/);
- assert.match(taskPlannerItems, /categoryLabel/);
- assert.match(taskPlannerItems, /prerequisite/);
+ assert.match(taskPlannerBundle, /task-planner-warning/);
+ assert.match(taskPlannerBundle, /useInlineEdit/);
+ assert.match(taskPlannerBundle, /commitOnEnter: false/);
+ assert.match(taskPlannerBundle, /categoryLabel/);
+ assert.match(taskPlannerBundle, /prerequisite/);
assert.match(taskPlannerUtils, /export function getTaskParentMap/);
assert.match(taskPlannerUtils, /export function applyDueResets/);
assert.doesNotMatch(taskPlannerModule, /canMoveItem:/);
diff --git a/website/src/features/toolboxes/modules/TaskPlannerItemPanels.jsx b/website/src/features/toolboxes/modules/TaskPlannerItemPanels.jsx
new file mode 100644
index 0000000..cbe63cb
--- /dev/null
+++ b/website/src/features/toolboxes/modules/TaskPlannerItemPanels.jsx
@@ -0,0 +1,246 @@
+// Rôle : regroupe les panneaux secondaires d'une tâche du task planner.
+import { Icon } from "../../../components/Icon.jsx";
+import { InlineNotice } from "../../../components/AppOverlays.jsx";
+import { Tooltip } from "../../../components/Tooltip.jsx";
+import { useInlineEdit } from "../../../hooks/useInlineEdit.js";
+import {
+ WEEK_DAYS,
+ getEffectiveTaskCategory,
+ getTaskById,
+ getTaskTitle
+} from "./taskPlannerUtils.js";
+
+export function MissingPrerequisitesTooltip({ missingPrerequisites, tasks, textContent }) {
+ return (
+
+ {textContent.missingPrerequisiteTitle || "Pré requis non effectué"}
+
+ {missingPrerequisites.map((relation) => (
+ {getTaskTitle(tasks, relation.toTaskId)}
+ ))}
+
+
+ )}
+ position="top-start"
+ >
+ {textContent.missingPrerequisiteBadge || "Pré requis"}
+
+ );
+}
+
+export function TaskPlannerItemDetails({
+ task,
+ tasks,
+ data,
+ parentMap,
+ parentId,
+ textContent,
+ descriptionOpen,
+ settingsOpen,
+ noticeMessage,
+ relations,
+ relationTargets,
+ onUpdateTask,
+ onAddLinkTarget,
+ onRemoveLinkTarget,
+ onSetPrerequisite,
+ onSetTaskCategory
+}) {
+ return (
+ <>
+
+ {descriptionOpen && (
+
+ )}
+ {settingsOpen && (
+
+
+
+
+ )}
+ >
+ );
+}
+
+function TaskDescriptionPanel({ task, tasks, parentMap, parentId, textContent, onUpdateTask, onSetTaskCategory }) {
+ const parentTask = parentId ? getTaskById(tasks, parentId) : null;
+ const inheritedCategory = parentTask ? getEffectiveTaskCategory(parentTask, tasks, parentMap) : "";
+ const categoryValue = parentTask ? inheritedCategory : task.category || "";
+ const categoryEditable = !parentTask;
+ const categoryEdit = useInlineEdit({
+ value: categoryValue,
+ onCommit: (category) => {
+ if (categoryEditable) onSetTaskCategory(task.id, category);
+ }
+ });
+ const descriptionEdit = useInlineEdit({
+ value: task.description,
+ onCommit: (description) => onUpdateTask(task.id, (current) => ({ ...current, description })),
+ commitOnEnter: false
+ });
+
+ return (
+
+
+
+
+ );
+}
+
+function TaskResetSettings({ task, data, textContent, onUpdateTask }) {
+ if (task.type === "daily") {
+ const hasOverride = Boolean(task.dailyResetTime);
+ return (
+
+
{textContent.taskDailyResetTimeLabel || "Heure quotidienne"}
+
+ onUpdateTask(task.id, (current) => {
+ const nextTask = { ...current };
+ if (event.target.value && event.target.value !== data.resetTime) nextTask.dailyResetTime = event.target.value;
+ else delete nextTask.dailyResetTime;
+ return nextTask;
+ })}
+ aria-label={`${textContent.taskDailyResetTimeLabel || "Heure quotidienne"} ${task.title}`}
+ />
+
+
+
+ );
+ }
+
+ if (task.type === "weekly") {
+ return (
+
+ );
+ }
+
+ return (
+
+
{textContent.taskResetLabel || "Réinitialisation"}
+
{textContent.noTaskResetLabel || "Aucun reset"}
+
+ );
+}
+
+function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets, emptyLabel, addLabel, removeLabel, prerequisiteLabel, onAddLinkTarget, onRemoveLinkTarget, onSetPrerequisite }) {
+ const descendantIds = new Set();
+ function collectDescendants(taskId) {
+ tasks.filter((item) => parentMap.get(item.id) === taskId).forEach((child) => {
+ descendantIds.add(child.id);
+ collectDescendants(child.id);
+ });
+ }
+ collectDescendants(task.id);
+ const availableTargets = relationTargets.filter((target) => !descendantIds.has(target.id));
+
+ return (
+
+
{label}
+
+
+ {relations.length ? relations.map((relation) => (
+
+ {getTaskTitle(tasks, relation.toTaskId)}
+
+
+
+ )) :
{emptyLabel}}
+
+
+ );
+}
diff --git a/website/src/features/toolboxes/modules/TaskPlannerItems.jsx b/website/src/features/toolboxes/modules/TaskPlannerItems.jsx
index 912c5c9..791ef65 100644
--- a/website/src/features/toolboxes/modules/TaskPlannerItems.jsx
+++ b/website/src/features/toolboxes/modules/TaskPlannerItems.jsx
@@ -1,18 +1,13 @@
-// Rôle : rend les branches, items et panneaux secondaires du task planner.
+// Rôle : rend les branches et lignes de tâches du task planner.
import { Icon } from "../../../components/Icon.jsx";
-import { InlineNotice } from "../../../components/AppOverlays.jsx";
-import { Tooltip } from "../../../components/Tooltip.jsx";
import { useInlineEdit } from "../../../hooks/useInlineEdit.js";
+import { MissingPrerequisitesTooltip, TaskPlannerItemDetails } from "./TaskPlannerItemPanels.jsx";
import {
TASK_TYPES,
- WEEK_DAYS,
countCompletedTaskTree,
countTaskTree,
- getEffectiveTaskCategory,
- getTaskById,
getTaskCategoryDataset,
getTaskCategoryGroups,
- getTaskTitle,
getTaskTypeLabel,
getTasksForParent,
isTaskResetSoon,
@@ -231,10 +226,6 @@ function TaskPlannerItem({
onSetTaskCategory,
children
}) {
- const parentTask = parentId ? getTaskById(tasks, parentId) : null;
- const inheritedCategory = parentTask ? getEffectiveTaskCategory(parentTask, tasks, parentMap) : "";
- const categoryValue = parentTask ? inheritedCategory : task.category || "";
- const categoryEditable = !parentTask;
const relations = data.relations.filter((relation) => relation.fromTaskId === task.id);
const missingPrerequisites = relations.filter((relation) => relation.prerequisite && !tasks.find((item) => item.id === relation.toTaskId)?.checked);
const relationTargets = tasks.filter((item) => item.id !== task.id && !relations.some((relation) => relation.toTaskId === item.id));
@@ -253,17 +244,6 @@ function TaskPlannerItem({
transform: (draft) => String(draft || "").trim() || task.title,
onCommit: (title) => onUpdateTask(task.id, (current) => ({ ...current, title }))
});
- const categoryEdit = useInlineEdit({
- value: categoryValue,
- onCommit: (category) => {
- if (categoryEditable) onSetTaskCategory(task.id, category);
- }
- });
- const descriptionEdit = useInlineEdit({
- value: task.description,
- onCommit: (description) => onUpdateTask(task.id, (current) => ({ ...current, description })),
- commitOnEnter: false
- });
function updateChecked(event) {
const checked = event.target.checked;
@@ -314,13 +294,7 @@ function TaskPlannerItem({
{TASK_TYPES.map((taskType) => )}
{missingPrerequisites.length > 0 && (
- }
- position="top-start"
- >
- {textContent.missingPrerequisiteBadge || "Pré requis"}
-
+
)}
-
- {descriptionOpen && (
-
-
-
-
- )}
- {settingsOpen && (
-
-
-
-
- )}
+
{children && }
);
}
-
-function MissingPrerequisitesTooltip({ missingPrerequisites, tasks, textContent }) {
- return (
-
- {textContent.missingPrerequisiteTitle || "Pré requis non effectué"}
-
- {missingPrerequisites.map((relation) => (
- {getTaskTitle(tasks, relation.toTaskId)}
- ))}
-
-
- );
-}
-
-function TaskResetSettings({ task, data, textContent, onUpdateTask }) {
- if (task.type === "daily") {
- const hasOverride = Boolean(task.dailyResetTime);
- return (
-
-
{textContent.taskDailyResetTimeLabel || "Heure quotidienne"}
-
- onUpdateTask(task.id, (current) => {
- const nextTask = { ...current };
- if (event.target.value && event.target.value !== data.resetTime) nextTask.dailyResetTime = event.target.value;
- else delete nextTask.dailyResetTime;
- return nextTask;
- })}
- aria-label={`${textContent.taskDailyResetTimeLabel || "Heure quotidienne"} ${task.title}`}
- />
-
-
-
- );
- }
-
- if (task.type === "weekly") {
- return (
-
- );
- }
-
- return (
-
-
{textContent.taskResetLabel || "Réinitialisation"}
-
{textContent.noTaskResetLabel || "Aucun reset"}
-
- );
-}
-
-function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets, emptyLabel, addLabel, removeLabel, prerequisiteLabel, onAddLinkTarget, onRemoveLinkTarget, onSetPrerequisite }) {
- const descendantIds = new Set();
- function collectDescendants(taskId) {
- tasks.filter((item) => parentMap.get(item.id) === taskId).forEach((child) => {
- descendantIds.add(child.id);
- collectDescendants(child.id);
- });
- }
- collectDescendants(task.id);
- const availableTargets = relationTargets.filter((target) => !descendantIds.has(target.id));
-
- return (
-
-
{label}
-
-
- {relations.length ? relations.map((relation) => (
-
- {getTaskTitle(tasks, relation.toTaskId)}
-
-
-
- )) :
{emptyLabel}}
-
-
- );
-}