// Rôle : fournit l'outil checklist avec quantités, catégories et imports texte.
import { useCallback, useState } from "react";
import { Icon } from "../../../components/Icon.jsx";
import { TextImportModal } from "./TextImportModal.jsx";
import { parseColonImportLines } from "./textImport.js";
export function ChecklistModule({ toolboxId, moduleId, context, editing }) {
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 [textImport, setTextImport] = useState("");
const [importOpen, setImportOpen] = useState(false);
const closeImportModal = useCallback(() => setImportOpen(false), []);
const namedSections = data.sections.filter((section) => section.title);
const completedSectionsMode = data.hideCompletedSectionsFully ? "hidden" : data.hideCompletedSections ? "reduced" : "visible";
function save(sections, settings = {}) {
context.setModuleData(toolboxId, moduleId, { ...data, ...settings, sections });
}
function appendItems(sections, title, items) {
const cleanTitle = title.trim();
const existingIndex = sections.findIndex((section) => section.title.toLowerCase() === cleanTitle.toLowerCase());
if (existingIndex >= 0) {
return sections.map((section, index) => index === existingIndex ? { ...section, items: [...section.items, ...items] } : section);
}
return [...sections, { id: context.uid("section"), title: cleanTitle, items }];
}
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(appendItems(data.sections, sectionTitle, [nextItem]));
setLabel("");
setQty(1);
}
function importItems(event) {
event.preventDefault();
const importedSections = parseChecklistImport(textImport, context);
if (!importedSections.length) return;
const nextSections = importedSections.reduce((sections, section) => appendItems(sections, section.title, section.items), data.sections);
save(nextSections);
setTextImport("");
setImportOpen(false);
context.notify?.("Import checklist terminé.");
}
function setCompletedSectionsMode(mode) {
save(data.sections.map((section) => ({ ...section, hideWhenComplete: undefined })), {
hideCompletedSections: mode === "reduced",
hideCompletedSectionsFully: mode === "hidden"
});
}
return (
<>
{editing && (
)}
{importOpen && (
)}
{namedSections.length > 0 && (
{[
{ 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) => (
))}
)}
{data.sections.map((section) => (
))}
>
);
}
function parseChecklistImport(text, context) {
const sections = [];
let currentSection = { id: context.uid("section"), title: "", items: [] };
String(text || "").split(/\r?\n/).forEach((line) => {
const cleanLine = line.trim();
if (!cleanLine) return;
if (cleanLine.startsWith("#")) {
if (currentSection.items.length || currentSection.title) sections.push(currentSection);
currentSection = { id: context.uid("section"), title: cleanLine.replace(/^#+/, "").trim(), items: [] };
return;
}
parseColonImportLines(cleanLine).forEach((entry) => {
currentSection.items.push({
id: context.uid("item"),
label: entry.label,
qtyTarget: Math.max(1, Number.parseInt(entry.value, 10) || 1),
qtyCurrent: 0
});
});
});
if (currentSection.items.length || currentSection.title) sections.push(currentSection);
return sections.filter((section) => section.items.length);
}
function isSectionComplete(section, context) {
return section.items.length > 0 && section.items.every((item) => context.clampQty(item.qtyCurrent, item.qtyTarget) >= item.qtyTarget);
}
function getSectionHideWhenComplete(section, globalHide) {
return typeof section.hideWhenComplete === "boolean" ? section.hideWhenComplete : globalHide;
}
function CategoryTitle({ title }) {
return String(title || "").split(/([♂♀])/g).map((part, index) => (
part === "♂" || part === "♀"
? {part}
: part
));
}
function ChecklistSection({ section, context, sections, data, save }) {
const showTitle = Boolean(section.title);
const sectionComplete = showTitle && isSectionComplete(section, context);
const hideWhenComplete = getSectionHideWhenComplete(section, data.hideCompletedSections);
const isAutoCollapsed = sectionComplete && hideWhenComplete;
const isCollapsed = Boolean(section.collapsed) || isAutoCollapsed;
if (sectionComplete && data.hideCompletedSectionsFully) return null;
function saveItems(items) {
save(sections.map((entry) => entry.id === section.id ? { ...entry, items } : entry).filter((entry) => entry.items.length || entry.title));
}
function toggleSectionCollapsed() {
const nextCollapsed = !isCollapsed;
const nextSections = sections.map((entry) => {
if (entry.id === section.id) {
const nextSection = { ...entry };
if (nextCollapsed) {
if (sectionComplete) {
nextSection.hideWhenComplete = true;
delete nextSection.collapsed;
} else {
nextSection.collapsed = true;
}
} else {
delete nextSection.collapsed;
if (sectionComplete) nextSection.hideWhenComplete = false;
}
return nextSection;
}
if (data.hideCompletedSections && !nextCollapsed && entry.title && isSectionComplete(entry, context)) return { ...entry, hideWhenComplete: true };
return entry;
});
const nextCompletedSections = nextSections.filter((entry) => entry.title && isSectionComplete(entry, context));
const allHidden = nextCompletedSections.length > 0 && nextCompletedSections.every((entry) => getSectionHideWhenComplete(entry, false));
save(nextSections, { hideCompletedSections: allHidden });
}
return (
{showTitle && (
{section.items.filter((item) => context.clampQty(item.qtyCurrent, item.qtyTarget) >= item.qtyTarget).length} / {section.items.length}
)}
{!isCollapsed && (
{section.items.map((item) => (
))}
)}
);
}
function ChecklistItem({ item, context, items, save }) {
const done = context.clampQty(item.qtyCurrent, item.qtyTarget) >= item.qtyTarget;
function updateItem(updater) {
save(items.map((entry) => entry.id === item.id ? updater(entry) : entry));
}
return (
);
}