sokko-g/website/src/features/toolboxes/modules/ChecklistModule.jsx
Shinuwa ed710b3399
All checks were successful
Deploy Sokko G / deploy (push) Successful in 15s
Refactor checklist module structure
2026-08-24 22:19:11 +02:00

186 lines
7.7 KiB
JavaScript

// Rôle : fournit l'outil checklist avec quantités, catégories et échange texte.
import { useState } from "react";
import { Icon } from "../../../components/Icon.jsx";
import { useGroupedReorder, moveItem } from "../../../hooks/useGroupedReorder.js";
import { ChecklistSection } from "./ChecklistParts.jsx";
import { TextExchangeActions } from "./TextExchangeActions.jsx";
import {
appendChecklistItems,
getChecklistReorderItems,
isSectionComplete,
keepChecklistSection,
moveChecklistItem,
moveChecklistItemToCategory,
renameChecklistSection,
updateChecklistItem
} from "./checklistUtils.js";
export function ChecklistModule({ toolboxId, moduleId, context, editing, textExchangeOpen }) {
const data = context.normalizeChecklistData(context.getModuleData(toolboxId, moduleId, { items: [] }));
const textContent = context.moduleText?.checklist || {};
const [label, setLabel] = useState("");
const [sectionTitle, setSectionTitle] = useState("");
const [qty, setQty] = useState(1);
const namedSections = data.sections.filter((section) => section.title);
const completedSectionsMode = data.hideCompletedSectionsFully ? "hidden" : data.hideCompletedSections ? "reduced" : "visible";
const reorderItems = getChecklistReorderItems(data.sections);
const {
itemReorder,
groupReorder,
getItemProps,
getGroupProps,
getGroupBoundaryProps,
isItemDragging,
isItemDropTarget,
isGroupDragging,
isGroupDropTarget,
isBoundaryDropTarget,
getDropPlacement,
shouldShowGroupBoundaries
} = useGroupedReorder({
namespace: `checklist-${moduleId}`,
items: reorderItems,
getItemId: (item) => item.id,
getItemGroup: (item) => item.sectionId,
getEffectiveGroup: (item) => item.sectionId,
reorderFeatures: {
item: { groupChange: true, boundaryDrop: true },
group: { reorder: true, boundaryDrop: true }
},
onItemMove: (operation) => save(moveChecklistItem(data.sections, operation, context)),
onGroupMove: (operation) => save(moveItem(data.sections, operation.sourceId, operation.targetId, operation.placement))
});
const reorder = {
itemReorder,
groupReorder,
getItemProps,
getGroupProps,
getGroupBoundaryProps,
isItemDragging,
isItemDropTarget,
isGroupDragging,
isGroupDropTarget,
isBoundaryDropTarget,
getDropPlacement,
shouldShowGroupBoundaries
};
const showBoundaryDropZones = reorder.shouldShowGroupBoundaries();
const visibleSections = data.sections.filter((section) => !(section.title && data.hideCompletedSectionsFully && isSectionComplete(section, context)));
function save(sections, settings = {}) {
context.setModuleData(toolboxId, moduleId, { ...data, ...settings, sections });
}
function addItem(event) {
event.preventDefault();
const cleanLabel = label.trim();
if (!cleanLabel) return;
const nextItem = { id: context.uid("item"), label: cleanLabel, qtyTarget: Math.max(1, Number(qty) || 1), qtyCurrent: 0 };
save(appendChecklistItems(data.sections, sectionTitle, [nextItem], context));
setLabel("");
setQty(1);
}
function setCompletedSectionsMode(mode) {
save(data.sections.map((section) => ({ ...section, hideWhenComplete: undefined })), {
hideCompletedSections: mode === "reduced",
hideCompletedSectionsFully: mode === "hidden"
});
}
function updateSection(sectionId, updater) {
save(data.sections.map((section) => section.id === sectionId ? updater(section) : section).filter(keepChecklistSection));
}
function renameSection(sectionId, title) {
save(renameChecklistSection(data.sections, sectionId, title));
}
function updateItem(itemId, updater) {
save(updateChecklistItem(data.sections, itemId, updater));
}
function moveItemToCategory(itemId, title) {
save(moveChecklistItemToCategory(data.sections, itemId, title, context));
}
function deleteItem(itemId) {
save(data.sections.map((section) => ({ ...section, items: section.items.filter((item) => item.id !== itemId) })).filter(keepChecklistSection));
}
return (
<>
{editing && (
<div className="module-add-panel">
<form className="inline-form checklist-add-form" onSubmit={addItem}>
<input name="section" placeholder={textContent.sectionPlaceholder || "Catégorie"} value={sectionTitle} onChange={(event) => setSectionTitle(event.target.value)} />
<input name="label" placeholder={textContent.itemPlaceholder || "Nouvel item"} value={label} onChange={(event) => setLabel(event.target.value)} />
<input className="checklist-qty-input" name="qty" type="number" min="1" value={qty} onChange={(event) => setQty(event.target.value)} aria-label={textContent.quantityLabel || "Quantité cible"} />
<button className="primary">{textContent.addButton || "Ajouter"}</button>
</form>
</div>
)}
{textExchangeOpen && (
<div className="module-text-exchange-panel">
<TextExchangeActions type="checklist" data={data} context={context} textContent={textContent} onImport={(nextData) => context.setModuleData(toolboxId, moduleId, nextData, "checklist")} />
</div>
)}
{namedSections.length > 0 && (
<div className="checklist-toolbar" role="radiogroup" aria-label={textContent.completedSectionsModeTitle || "Affichage des catégories terminées"}>
{[
{ mode: "visible", label: textContent.completedSectionsVisibleLabel || "Visible", icon: "eye-open" },
{ mode: "reduced", label: textContent.completedSectionsReducedLabel || "Réduit", icon: "hide" },
{ mode: "hidden", label: textContent.completedSectionsHiddenLabel || "Caché", icon: "eye-closed" }
].map((option) => (
<button
className={`checklist-complete-mode-button ${completedSectionsMode === option.mode ? "active" : ""}`}
type="button"
role="radio"
aria-checked={completedSectionsMode === option.mode}
aria-label={option.label}
key={option.mode}
onClick={() => setCompletedSectionsMode(option.mode)}
title={option.label}
>
<Icon name={option.icon} />
</button>
))}
</div>
)}
<ul className="checklist">
{visibleSections.map((section, index) => {
const isLastSection = index === visibleSections.length - 1;
return [
showBoundaryDropZones ? (
<li
className={`checklist-category-boundary-drop-zone ${reorder.isBoundaryDropTarget({ groupId: section.id, placement: "before" }) ? "is-drop-target" : ""}`}
key={`${section.id}:before-drop`}
{...reorder.getGroupBoundaryProps({ groupId: section.id, placement: "before" })}
/>
) : null,
<ChecklistSection
key={section.id}
section={section}
data={data}
context={context}
reorder={reorder}
onRenameSection={renameSection}
onUpdateSection={updateSection}
onSaveSections={save}
onUpdateItem={updateItem}
onMoveItemToCategory={moveItemToCategory}
onDeleteItem={deleteItem}
/>,
showBoundaryDropZones && isLastSection ? (
<li
className={`checklist-category-boundary-drop-zone ${reorder.isBoundaryDropTarget({ groupId: section.id, placement: "after" }) ? "is-drop-target" : ""}`}
key={`${section.id}:after-drop`}
{...reorder.getGroupBoundaryProps({ groupId: section.id, placement: "after" })}
/>
) : null
];
})}
</ul>
</>
);
}