// Rôle : rend les sous-composants d'édition et de prévisualisation de l'arbre d'objectifs.
import { useState } from "react";
import { CompactDropdown } from "../../../components/CompactDropdown.jsx";
import { Icon } from "../../../components/Icon.jsx";
import { MixTagsInput } from "../../../components/MixTagsInput.jsx";
import { Tooltip } from "../../../components/Tooltip.jsx";
import {
GOAL_TREE_ICON_NAMES,
formatGoalTreeNumber,
getGoalTreeChildren,
getGoalTreeDescendantIds,
getGoalTreeParentIds
} from "./goalTreeUtils.js";
import {
BASE_TOKEN,
getEffectiveGoalTreeProgressMode,
getGoalTreeNodeIcon,
getGoalTreeNodeIconColor,
getGoalTreeNodeTooltip,
isSingleValueGoalTreeNode
} from "./goalTreeDisplay.js";
export function GoalTreePrerequisitePicker({ nodes, node, textContent, onToggle }) {
const parentIds = new Set(getGoalTreeParentIds(node));
const blockedIds = new Set([node.id, ...getGoalTreeDescendantIds(nodes, node.id)]);
const primaryParent = nodes.find((candidate) => candidate.id === node.parentId);
const options = nodes.filter((candidate) => !blockedIds.has(candidate.id) && candidate.id !== node.parentId);
if (!primaryParent && !options.length) return null;
return (
);
}
export function GoalTreeBranch({ nodes, parentId, selectedNodeId, editing, reorder, textContent, onSelect, onAdd, onAddContentItem, onSetStepLeafQuantity, onSetStepChildrenCompletion, onUpdateNodeItem, onEditNode, onToggleBranch, onDeleteNode }) {
const children = getGoalTreeChildren(nodes, parentId);
if (!children.length) return null;
return (
{children.map((node) => {
const nestedChildren = getGoalTreeChildren(nodes, node.id);
const linkedChildren = nodes.filter((candidate) => candidate.parentId !== node.id && getGoalTreeParentIds(candidate).includes(node.id));
const branchCollapsed = node.collapsed;
const isSelected = selectedNodeId === node.id;
const isComplete = node.progress.complete;
const hasSingleValueChildren = nestedChildren.length > 0 && nestedChildren.every(isSingleValueGoalTreeNode);
const manualProgress = getEffectiveGoalTreeProgressMode(node, nestedChildren) === "manual";
const hasBranchToggle = nestedChildren.length > 0 || linkedChildren.length > 0;
const hasNodeActions = editing || hasBranchToggle;
const iconColor = getGoalTreeNodeIconColor(node);
const selectFromCard = (event) => {
if (!editing) return;
if (event.target.closest("button:not(.goal-tree-node-main)")) return;
onSelect(node.id);
};
const className = [
"goal-tree-node",
`is-${node.kind}`,
isSelected ? "is-selected" : "",
isComplete ? "is-complete" : "",
reorder.isItemDragging(node.id) ? "is-dragging" : "",
reorder.isItemDropTarget(node.id) ? "is-drop-target" : "",
reorder.getDropPlacement("item", node.id) === "after" ? "drop-after" : ""
].filter(Boolean).join(" ");
return (
-
{!editing && (
)}
{editing && (
)}
{node.kind === "step" ? (
<>
{(manualProgress || !nestedChildren.length) && (
onSetStepLeafQuantity(node.id, quantity)} />
)}
{!manualProgress && hasSingleValueChildren && (
onSetStepChildrenCompletion(node.id, quantity > 0)}
/>
)}
>
) : (
<>
{node.title}
onAddContentItem(node.id)}
onUpdateItem={(itemId, updater) => onUpdateNodeItem(node.id, itemId, updater)}
/>
>
)}
{hasNodeActions && (
{editing && (
)}
{hasBranchToggle && (
)}
{editing && (
)}
)}
{!node.collapsed && nestedChildren.length > 0 && (
)}
);
})}
);
}
export function GoalTreeTitleInput({ value, onChange }) {
const [draft, setDraft] = useState(value || "");
return (
{
const title = event.target.value;
setDraft(title);
onChange(title);
}}
/>
);
}
export function GoalTreeFormulaInput({ value, onChange, hasParent, textContent, placeholder, title, className = "", "aria-label": ariaLabel }) {
const normalizedValue = String(value ?? "");
if (!hasParent) {
return onChange(event.target.value.replaceAll(",", "."))} placeholder={placeholder} title={title} value={normalizedValue} />;
}
return (
onChange(nextValue.replaceAll(",", "."))}
placeholder={placeholder}
title={title}
tokens={[{
value: BASE_TOKEN,
label: textContent.baseTokenLabel || "Valeur du parent"
}]}
value={normalizedValue}
/>
);
}
export function GoalTreeContentEditor({ node, textContent, onAdd, onUpdate, onDelete }) {
return (
{textContent.contentSectionTitle || "Contenu"}
{(node.items || []).map((item) => (
onUpdate(item.id, (current) => ({ ...current, label: event.target.value }))} aria-label={textContent.itemLabelLabel || "Libellé"} />
{node.kind === "checklist" && (
onUpdate(item.id, (current) => ({ ...current, formula: value.replaceAll(",", ".") }))}
value={item.formula || "1"}
placeholder={textContent.itemTotalPlaceholder || "base"}
textContent={textContent}
title={textContent.itemTotalHelp || "Saisissez un nombre ou une formule avec base pour référencer la cible du parent."}
/>
)}
))}
);
}
function GoalTreeChecklistInlineControl({ item, textContent, onUpdate }) {
const target = Math.max(1, item.qtyTarget || 1);
const current = Math.min(Math.max(0, item.qtyCurrent || 0), target);
if (target <= 1) {
return = 1} onChange={(event) => onUpdate((entry) => ({ ...entry, qtyCurrent: event.target.checked ? 1 : 0 }))} aria-label={textContent.itemCheckedLabel || "État"} />;
}
return (
{formatGoalTreeNumber(current)} / {formatGoalTreeNumber(target)}
);
}
function GoalTreeStepLeafControl({ node, textContent, onChange }) {
const target = Math.max(1, node.computedQtyTarget || node.qtyTarget || 1);
const current = Math.min(Math.max(0, node.qtyCurrent || 0), target);
if (target <= 1) {
return = 1} onChange={(event) => onChange(event.target.checked ? 1 : 0)} aria-label={textContent.itemCheckedLabel || "État"} />;
}
return (
{formatGoalTreeNumber(current)} / {formatGoalTreeNumber(target)}
);
}
function GoalTreeNodeContentPreview({ node, editing, textContent, onAddContentItem, onUpdateItem }) {
const items = node.items || [];
return (
{items.map((item) => (
{node.kind === "checklist" && onUpdateItem(item.id, updater)} />}
{item.label}
))}
{editing && (
)}
);
}
export function GoalTreeIconPicker({ label, value, textContent, onChange }) {
return (
{label}
(
)}
>
{({ close }) => (
<>
{GOAL_TREE_ICON_NAMES.map((icon) => (
))}
>
)}
);
}