// Rôle : rend l'éditeur des caractéristiques d'un équipement avec catégories réordonnables. import { useState } from "react"; import { Icon } from "../../../components/Icon.jsx"; import { applyGroupedReorderOperation, useGroupedReorder } from "../../../hooks/useGroupedReorder.js"; import { useInlineEdit } from "../../../hooks/useInlineEdit.js"; import { TRAIT_ICONS, createLocalId, formatValue, getCharacteristicCategories, setItemCategory } from "./equipmentPlannerOptions.js"; import { IconChoiceDropdown } from "./EquipmentPlannerPickers.jsx"; export function CharacteristicsEditor({ equipment, textContent, editing, onUpdate }) { const [draft, setDraft] = useState({ icon: "sword", category: "", name: "", value: "" }); const categories = getCharacteristicCategories(equipment); const { itemReorder, groupReorder, getItemProps, getGroupProps, isItemDragging, isItemDropTarget, isGroupDragging, isGroupDropTarget, getDropPlacement } = useGroupedReorder({ namespace: `equipment-characteristics-${equipment.id}`, items: equipment.characteristics, getItemId: (trait) => trait.id, getItemGroup: (trait) => trait.category, getEffectiveGroup: (trait) => trait.category, reorderFeatures: { item: { groupChange: true }, group: { reorder: true, itemDrop: true } }, onItemMove: (operation) => onUpdate((current) => applyGroupedReorderOperation(current, { operation, itemsKey: "characteristics", groupOrderKey: "categoryOrder", collapsedGroupsKey: "collapsedCategories", getItemGroup: (trait) => trait.category, setItemGroup: setItemCategory })), onGroupMove: (operation) => onUpdate((current) => applyGroupedReorderOperation(current, { operation, itemsKey: "characteristics", groupOrderKey: "categoryOrder", collapsedGroupsKey: "collapsedCategories", getItemGroup: (trait) => trait.category, setItemGroup: setItemCategory })) }); const reorder = { itemReorder, groupReorder, getItemProps, getGroupProps, isItemDragging, isItemDropTarget, isGroupDragging, isGroupDropTarget, getDropPlacement }; function addTrait(event) { event.preventDefault(); const cleanName = draft.name.trim(); if (!cleanName) return; onUpdate((current) => ({ ...current, characteristics: [...current.characteristics, { id: createLocalId("trait"), icon: draft.icon, category: draft.category.trim(), name: cleanName, value: draft.value.trim() }], categoryOrder: draft.category.trim() && !current.categoryOrder.includes(draft.category.trim()) ? [...current.categoryOrder, draft.category.trim()] : current.categoryOrder })); setDraft({ icon: draft.icon, category: draft.category, name: "", value: "" }); } function updateTrait(traitId, updater) { onUpdate((current) => ({ ...current, characteristics: current.characteristics.map((trait) => trait.id === traitId ? updater(trait) : trait).filter((trait) => trait.name) })); } return ( {textContent.characteristicsTitle || "Caractéristiques"} {editing && ( ({ icon, label: icon }))} onChange={(icon) => setDraft((current) => ({ ...current, icon }))} /> setDraft((current) => ({ ...current, category: event.target.value }))} placeholder={textContent.categoryPlaceholder || "Catégorie"} /> setDraft((current) => ({ ...current, name: event.target.value }))} placeholder={textContent.traitNamePlaceholder || "Nom"} /> setDraft((current) => ({ ...current, value: event.target.value }))} placeholder={textContent.traitValuePlaceholder || "Valeur"} /> {textContent.addTraitButton || "Ajouter"} )} onUpdate((current) => ({ ...current, collapsedCategories: current.collapsedCategories.includes(category) ? current.collapsedCategories.filter((item) => item !== category) : [...current.collapsedCategories, category] }))} onUpdateTrait={updateTrait} editing={editing} /> ); } function TraitCategoryList({ traits, categories, collapsedCategories, reorder, textContent, onToggleCategory, onUpdateTrait, editing }) { const ungrouped = traits.filter((trait) => !trait.category); function renderTrait(trait) { const traitClassName = [ "equipment-planner-trait", reorder.isItemDragging(trait.id) ? "is-dragging" : "", reorder.isItemDropTarget(trait.id) ? "is-drop-target" : "", reorder.getDropPlacement("item", trait.id) === "after" ? "drop-after" : "" ].filter(Boolean).join(" "); return ( reorder.itemReorder.startDrag(event, trait.id)} onUpdateTrait={onUpdateTrait} /> ); } return ( {ungrouped.length ? {ungrouped.map(renderTrait)} : null} {categories.map((category) => { const categoryTraits = traits.filter((trait) => trait.category === category); const collapsed = collapsedCategories.includes(category); const className = [ "equipment-planner-trait-category", collapsed ? "is-collapsed" : "", reorder.isGroupDragging(category) ? "is-dragging" : "", reorder.isGroupDropTarget(category) ? "is-drop-target" : "" ].filter(Boolean).join(" "); return ( reorder.groupReorder.startDrag(event, { groupId: category })} title={textContent.reorderCategoryTitle || "Déplacer la catégorie"}> {category} {categoryTraits.length} onToggleCategory(category)} aria-expanded={!collapsed}> {!collapsed && {categoryTraits.map(renderTrait)}} ); })} {!traits.length && {textContent.emptyCharacteristics || "Aucune caractéristique."}} ); } function EquipmentTraitRow({ trait, className, itemProps, editing, textContent, onStartDrag, onUpdateTrait }) { const categoryEdit = useInlineEdit({ value: trait.category || "", onCommit: (category) => onUpdateTrait(trait.id, (current) => ({ ...current, category })) }); const nameEdit = useInlineEdit({ value: trait.name, transform: (draft) => String(draft || "").trim() || trait.name, onCommit: (name) => onUpdateTrait(trait.id, (current) => ({ ...current, name })) }); const valueEdit = useInlineEdit({ value: formatValue(trait.value), onCommit: (value) => onUpdateTrait(trait.id, (current) => ({ ...current, value })) }); return ( {editing ? ( <> ({ icon, label: icon }))} onChange={(icon) => onUpdateTrait(trait.id, (current) => ({ ...current, icon }))} /> onUpdateTrait(trait.id, () => ({ ...trait, name: "" }))} title={textContent.deleteTraitTitle || "Supprimer"}> > ) : ( {trait.name} {trait.value !== "" && {formatValue(trait.value)}} )} ); }
{textContent.emptyCharacteristics || "Aucune caractéristique."}