diff --git a/tests/static-toolboxes.test.mjs b/tests/static-toolboxes.test.mjs
index 8a04f1e..c6a6241 100644
--- a/tests/static-toolboxes.test.mjs
+++ b/tests/static-toolboxes.test.mjs
@@ -165,7 +165,9 @@ test("toolbox module registry and modules expose expected behavior", async () =>
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 goalTreeEditor = await readFile("website/src/features/toolboxes/modules/GoalTreeEditor.jsx", "utf8");
const goalTreeParts = await readFile("website/src/features/toolboxes/modules/GoalTreeParts.jsx", "utf8");
+ const goalTreeBundle = `${goalTreeModule}\n${goalTreeEditor}\n${goalTreeParts}`;
const goalTreeDisplay = await readFile("website/src/features/toolboxes/modules/goalTreeDisplay.js", "utf8");
const goalTreeUtils = await readFile("website/src/features/toolboxes/modules/goalTreeUtils.js", "utf8");
const equipmentPlannerModule = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerModule.jsx", "utf8");
@@ -381,11 +383,15 @@ test("toolbox module registry and modules expose expected behavior", async () =>
assert.match(goalTreeModule, /applyGoalTreeDueResets/);
assert.match(goalTreeModule, /TextExchangeActions/);
assert.match(goalTreeModule, /DiagramConnectors/);
+ assert.match(goalTreeEditor, /export function GoalTreeEditor/);
+ assert.match(goalTreeEditor, /Tabs/);
+ assert.match(goalTreeEditor, /GoalTreeFormulaInput/);
+ assert.match(goalTreeEditor, /GoalTreePrerequisitePicker/);
assert.match(goalTreeParts, /Tooltip/);
assert.match(goalTreeParts, /export function GoalTreeIconPicker/);
assert.match(goalTreeParts, /export function GoalTreeContentEditor/);
assert.match(goalTreeParts, /function GoalTreeNodeContentPreview/);
- assert.doesNotMatch(`${goalTreeModule}\n${goalTreeParts}`, /\.slice\(0, 3\)/);
+ assert.doesNotMatch(goalTreeBundle, /\.slice\(0, 3\)/);
assert.match(goalTreeParts, /GoalTreeChecklistInlineControl/);
assert.match(goalTreeParts, /GoalTreeStepLeafControl/);
assert.match(goalTreeParts, /export function GoalTreePrerequisitePicker/);
diff --git a/website/src/features/toolboxes/modules/GoalTreeEditor.jsx b/website/src/features/toolboxes/modules/GoalTreeEditor.jsx
new file mode 100644
index 0000000..e7c71be
--- /dev/null
+++ b/website/src/features/toolboxes/modules/GoalTreeEditor.jsx
@@ -0,0 +1,227 @@
+// Rôle : rend le formulaire d'édition des noeuds de l'arbre d'objectifs.
+import { Icon } from "../../../components/Icon.jsx";
+import { Tabs } from "../../../components/Tabs.jsx";
+import {
+ GoalTreeContentEditor,
+ GoalTreeFormulaInput,
+ GoalTreeIconPicker,
+ GoalTreePrerequisitePicker,
+ GoalTreeTitleInput
+} from "./GoalTreeParts.jsx";
+import {
+ KIND_LABELS,
+ RESET_LABELS
+} from "./goalTreeDisplay.js";
+import {
+ GOAL_TREE_COLORS,
+ GOAL_TREE_COLOR_VALUES,
+ GOAL_TREE_KINDS,
+ GOAL_TREE_RESET_TYPES,
+ GOAL_TREE_WEEK_DAYS
+} from "./goalTreeUtils.js";
+
+export function GoalTreeEditor({
+ selectedNode,
+ selectedNodeIsStep,
+ selectedProgressMode,
+ formTabs,
+ activeFormTab,
+ evaluatedNodes,
+ textContent,
+ onTabChange,
+ onClose,
+ onDeleteNode,
+ onSetKind,
+ onUpdateNode,
+ onSetStepTarget,
+ onAddNodeItem,
+ onUpdateNodeItem,
+ onDeleteNodeItem,
+ onToggleLinkedParent
+}) {
+ if (!selectedNode) {
+ return (
+
+ {textContent.emptySelection || "Sélectionnez ou créez un objectif."}
+
+ );
+ }
+
+ return (
+
+
+
+
+ {activeFormTab === "base" && (
+
+ )}
+
+ {activeFormTab === "content" && !selectedNodeIsStep && (
+
+ onAddNodeItem(selectedNode.id)}
+ onUpdate={(itemId, updater) => onUpdateNodeItem(selectedNode.id, itemId, updater)}
+ onDelete={(itemId) => onDeleteNodeItem(selectedNode.id, itemId)}
+ />
+
+ )}
+
+ {activeFormTab === "content" && selectedNodeIsStep && (
+
+
{textContent.stepContentUnavailable || "Les étapes n'ont pas de contenu interne."}
+
+ )}
+
+ {activeFormTab === "advanced" && (
+
+ )}
+
+
+
+
+ {textContent.closeButton || "Fermer"}
+
+ onDeleteNode(selectedNode.id)}>
+
+ {textContent.deleteButton || "Supprimer la branche"}
+
+
+
+
+ );
+}
+
+function GoalTreeBasePanel({ selectedNode, selectedNodeIsStep, textContent, onSetKind, onUpdateNode }) {
+ return (
+
+
+ {textContent.kindLabel || "Type de l'objectif"}
+ onSetKind(selectedNode.id, event.target.value)}>
+ {GOAL_TREE_KINDS.map((kind) => {textContent[`${kind}Kind`] || KIND_LABELS[kind]} )}
+
+
+
+ {selectedNodeIsStep ? textContent.titleLabel || "Titre de l'objectif" : textContent.listTitleLabel || "Titre de la liste"}
+ onUpdateNode(selectedNode.id, (node) => ({ ...node, title }))}
+ />
+
+ {selectedNodeIsStep && (
+ <>
+
onUpdateNode(selectedNode.id, (node) => ({ ...node, icon }))}
+ />
+
+
{textContent.iconColorLabel || "Couleur de l'icône"}
+
+ {GOAL_TREE_COLORS.map((color) => (
+ onUpdateNode(selectedNode.id, (node) => ({ ...node, color }))}
+ aria-pressed={selectedNode.color === color}
+ aria-label={textContent[`color${color}`] || color}
+ title={textContent[`color${color}`] || color}
+ style={{ "--goal-tree-swatch-color": GOAL_TREE_COLOR_VALUES[color] }}
+ />
+ ))}
+
+
+ >
+ )}
+ {!selectedNodeIsStep && (
+ {textContent.contentTabHint || "Le contenu se modifie dans l'onglet Contenu."}
+ )}
+
+ );
+}
+
+function GoalTreeAdvancedPanel({ selectedNode, selectedNodeIsStep, selectedProgressMode, evaluatedNodes, textContent, onUpdateNode, onSetStepTarget, onToggleLinkedParent }) {
+ return (
+
+
+ {textContent.resetTypeLabel || "Type de reset"}
+ onUpdateNode(selectedNode.id, (node) => ({ ...node, resetType: event.target.value }))}>
+ {GOAL_TREE_RESET_TYPES.map((resetType) => {textContent[`${resetType}Reset`] || RESET_LABELS[resetType]} )}
+
+
+ {selectedNode.resetType !== "none" && (
+
+ {textContent.resetTimeLabel || "Heure de reset"}
+ onUpdateNode(selectedNode.id, (node) => ({ ...node, resetTime: event.target.value || "00:00" }))} />
+
+ )}
+ {selectedNode.resetType === "weekly" && (
+
+ {textContent.weeklyResetDayLabel || "Jour de reset"}
+ onUpdateNode(selectedNode.id, (node) => ({ ...node, weeklyResetDay: Number(event.target.value) }))}>
+ {GOAL_TREE_WEEK_DAYS.map((day) => {day.label} )}
+
+
+ )}
+ {selectedNodeIsStep && (
+ <>
+
{textContent.quantitySectionTitle || "Quantité"}
+
+ {textContent.targetQuantityLabel || "Cible"}
+ onSetStepTarget(selectedNode.id, value)}
+ value={selectedNode.formula || selectedNode.qtyTarget}
+ placeholder={textContent.formulaPlaceholder || "base * 3"}
+ textContent={textContent}
+ title={textContent.formulaHelp || "Utilisez base pour référencer la cible du parent."}
+ />
+
+
{textContent.progressSectionTitle || "Progression"}
+
onUpdateNode(selectedNode.id, (node) => ({ ...node, progressMode: selectedProgressMode === "auto" ? "manual" : "auto" }))}
+ aria-pressed={selectedProgressMode === "auto"}
+ title={selectedProgressMode === "auto" ? textContent.autoProgressTitle || "Progression automatique" : textContent.manualProgressTitle || "Mise à jour manuelle"}
+ >
+
+ {selectedProgressMode === "auto" ? textContent.autoProgressLabel || "Automatique" : textContent.manualProgressLabel || "Manuelle"}
+
+
+
+ {selectedProgressMode === "auto"
+ ? textContent.autoProgressHint || "Calcule la progression depuis les enfants."
+ : textContent.manualProgressHint || "Affiche une checkbox ou une quantité à mettre à jour directement."}
+
+ >
+ )}
+
onToggleLinkedParent(selectedNode.id, parentId)}
+ />
+
+ );
+}
diff --git a/website/src/features/toolboxes/modules/GoalTreeModule.jsx b/website/src/features/toolboxes/modules/GoalTreeModule.jsx
index 4469b54..4dbd417 100644
--- a/website/src/features/toolboxes/modules/GoalTreeModule.jsx
+++ b/website/src/features/toolboxes/modules/GoalTreeModule.jsx
@@ -2,22 +2,13 @@
import { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
import { DiagramConnectors } from "../../../components/DiagramConnectors.jsx";
import { Icon } from "../../../components/Icon.jsx";
-import { Tabs } from "../../../components/Tabs.jsx";
import { moveItem, useGroupedReorder } from "../../../hooks/useGroupedReorder.js";
+import { GoalTreeEditor } from "./GoalTreeEditor.jsx";
import { TextExchangeActions } from "./TextExchangeActions.jsx";
-import {
- GoalTreeBranch,
- GoalTreeContentEditor,
- GoalTreeFormulaInput,
- GoalTreeIconPicker,
- GoalTreePrerequisitePicker,
- GoalTreeTitleInput
-} from "./GoalTreeParts.jsx";
+import { GoalTreeBranch } from "./GoalTreeParts.jsx";
import {
GOAL_TREE_VIEW_SCALE,
KIND_ICONS,
- KIND_LABELS,
- RESET_LABELS,
getDefaultGoalTreeItem,
getEffectiveGoalTreeProgressMode,
getGoalTreeConvergenceKeepOpenIds,
@@ -26,11 +17,6 @@ import {
isSingleValueGoalTreeNode
} from "./goalTreeDisplay.js";
import {
- GOAL_TREE_COLORS,
- GOAL_TREE_COLOR_VALUES,
- GOAL_TREE_KINDS,
- GOAL_TREE_RESET_TYPES,
- GOAL_TREE_WEEK_DAYS,
applyGoalTreeDueResets,
evaluateGoalTreeNodes,
getGoalTreeChildren,
@@ -329,158 +315,25 @@ export function GoalTreeModule({ toolboxId, moduleId, context, editing, setEditi
return (
{editing && (
-
- {selectedNode ? (
-
-
-
- {activeFormTab === "base" && (
-
-
- {textContent.kindLabel || "Type de l'objectif"}
- setKind(selectedNode.id, event.target.value)}>
- {GOAL_TREE_KINDS.map((kind) => {textContent[`${kind}Kind`] || KIND_LABELS[kind]} )}
-
-
-
- {selectedNodeIsStep ? textContent.titleLabel || "Titre de l'objectif" : textContent.listTitleLabel || "Titre de la liste"}
- updateNode(selectedNode.id, (node) => ({ ...node, title }))}
- />
-
- {selectedNodeIsStep && (
- <>
-
updateNode(selectedNode.id, (node) => ({ ...node, icon }))}
- />
-
-
{textContent.iconColorLabel || "Couleur de l'icône"}
-
- {GOAL_TREE_COLORS.map((color) => (
- updateNode(selectedNode.id, (node) => ({ ...node, color }))}
- aria-pressed={selectedNode.color === color}
- aria-label={textContent[`color${color}`] || color}
- title={textContent[`color${color}`] || color}
- style={{ "--goal-tree-swatch-color": GOAL_TREE_COLOR_VALUES[color] }}
- />
- ))}
-
-
- >
- )}
- {!selectedNodeIsStep && (
- {textContent.contentTabHint || "Le contenu se modifie dans l'onglet Contenu."}
- )}
-
- )}
-
- {activeFormTab === "content" && !selectedNodeIsStep && (
-
- addNodeItem(selectedNode.id)}
- onUpdate={(itemId, updater) => updateNodeItem(selectedNode.id, itemId, updater)}
- onDelete={(itemId) => deleteNodeItem(selectedNode.id, itemId)}
- />
-
- )}
-
- {activeFormTab === "content" && selectedNodeIsStep && (
-
-
{textContent.stepContentUnavailable || "Les étapes n'ont pas de contenu interne."}
-
- )}
-
- {activeFormTab === "advanced" && (
-
-
- {textContent.resetTypeLabel || "Type de reset"}
- updateNode(selectedNode.id, (node) => ({ ...node, resetType: event.target.value }))}>
- {GOAL_TREE_RESET_TYPES.map((resetType) => {textContent[`${resetType}Reset`] || RESET_LABELS[resetType]} )}
-
-
- {selectedNode.resetType !== "none" && (
-
- {textContent.resetTimeLabel || "Heure de reset"}
- updateNode(selectedNode.id, (node) => ({ ...node, resetTime: event.target.value || "00:00" }))} />
-
- )}
- {selectedNode.resetType === "weekly" && (
-
- {textContent.weeklyResetDayLabel || "Jour de reset"}
- updateNode(selectedNode.id, (node) => ({ ...node, weeklyResetDay: Number(event.target.value) }))}>
- {GOAL_TREE_WEEK_DAYS.map((day) => {day.label} )}
-
-
- )}
- {selectedNodeIsStep && (
- <>
-
{textContent.quantitySectionTitle || "Quantité"}
-
- {textContent.targetQuantityLabel || "Cible"}
- setStepTarget(selectedNode.id, value)}
- value={selectedNode.formula || selectedNode.qtyTarget}
- placeholder={textContent.formulaPlaceholder || "base * 3"}
- textContent={textContent}
- title={textContent.formulaHelp || "Utilisez base pour référencer la cible du parent."}
- />
-
-
{textContent.progressSectionTitle || "Progression"}
-
updateNode(selectedNode.id, (node) => ({ ...node, progressMode: selectedProgressMode === "auto" ? "manual" : "auto" }))}
- aria-pressed={selectedProgressMode === "auto"}
- title={selectedProgressMode === "auto" ? textContent.autoProgressTitle || "Progression automatique" : textContent.manualProgressTitle || "Mise à jour manuelle"}
- >
-
- {selectedProgressMode === "auto" ? textContent.autoProgressLabel || "Automatique" : textContent.manualProgressLabel || "Manuelle"}
-
-
-
- {selectedProgressMode === "auto"
- ? textContent.autoProgressHint || "Calcule la progression depuis les enfants."
- : textContent.manualProgressHint || "Affiche une checkbox ou une quantité à mettre à jour directement."}
-
- >
- )}
-
toggleLinkedParent(selectedNode.id, parentId)}
- />
-
- )}
-
-
- setEditing(false)}>
-
- {textContent.closeButton || "Fermer"}
-
- deleteNode(selectedNode.id)}>
-
- {textContent.deleteButton || "Supprimer la branche"}
-
-
-
- ) : (
- {textContent.emptySelection || "Sélectionnez ou créez un objectif."}
- )}
-
+
setEditing(false)}
+ onDeleteNode={deleteNode}
+ onSetKind={setKind}
+ onUpdateNode={updateNode}
+ onSetStepTarget={setStepTarget}
+ onAddNodeItem={addNodeItem}
+ onUpdateNodeItem={updateNodeItem}
+ onDeleteNodeItem={deleteNodeItem}
+ onToggleLinkedParent={toggleLinkedParent}
+ />
)}
{textExchangeOpen && (