list optimization
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s

This commit is contained in:
Shinuwa 2026-07-24 17:29:08 +02:00
parent 8b9171d4f9
commit ef5f977f8a
15 changed files with 3571 additions and 272 deletions

View file

@ -9,6 +9,14 @@ function getChecklistSections(list) {
}));
}
function SymbolText({ text }) {
return String(text || "").split(/([♂♀])/g).map((part, index) => (
part === "♂" || part === "♀"
? <span className="game-list-symbol" key={index}>{part}</span>
: part
));
}
export function GameListsPage({ game, lists, selectedListId, onSelectList, linkedToolboxId, onCreateChecklist }) {
const selectedList = lists.find((list) => list.id === selectedListId) || lists[0] || null;
const selectedSections = getChecklistSections(selectedList);
@ -30,7 +38,7 @@ export function GameListsPage({ game, lists, selectedListId, onSelectList, linke
<section className="game-list-selector" aria-label="Listes disponibles">
{lists.map((list) => (
<article className={`game-list-selector-item ${selectedList?.id === list.id ? "active" : ""}`} key={list.id}>
<button type="button" onClick={() => onSelectList(list.id)}>
<button className="game-list-select-button" type="button" onClick={() => onSelectList(list.id)}>
<strong>{list.title}</strong>
<small>{list.itemCount} éléments</small>
</button>
@ -62,7 +70,7 @@ export function GameListsPage({ game, lists, selectedListId, onSelectList, linke
{selectedList.categories.map((category) => (
<article className="game-list-category" key={category.id}>
<header>
<h3>{category.title}</h3>
<h3><SymbolText text={category.title} /></h3>
<div className="game-list-actions">
<span className="results-count">{category.items.length}</span>
<CopyChecklistItemsButton sections={[getChecklistSections({ categories: [category] })[0]]} title={`Copier ${category.title.toLowerCase()} pour checklist`} />
@ -72,7 +80,7 @@ export function GameListsPage({ game, lists, selectedListId, onSelectList, linke
{category.items.map((item) => (
<li key={`${selectedList.id}-${category.id}-${item.name}-${item.description}`}>
<div className="game-list-item-title">
<strong>{item.name}</strong>
<strong><SymbolText text={item.name} /></strong>
{item.hasQuantity && item.quantity !== 1 ? <em>x{item.quantity}</em> : null}
</div>
{item.description ? <span>{item.description}</span> : null}

View file

@ -12,8 +12,8 @@ export function ChecklistModule({ toolboxId, moduleId, context, editing }) {
const [textImport, setTextImport] = useState("");
const [importOpen, setImportOpen] = useState(false);
const closeImportModal = useCallback(() => setImportOpen(false), []);
const completedNamedSections = data.sections.filter((section) => section.title && isSectionComplete(section, context));
const allCompletedSectionsHidden = completedNamedSections.length > 0 && completedNamedSections.every((section) => getSectionHideWhenComplete(section, data.hideCompletedSections));
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 });
@ -49,8 +49,11 @@ export function ChecklistModule({ toolboxId, moduleId, context, editing }) {
context.notify?.("Import checklist terminé.");
}
function setHideCompletedSections(hideCompletedSections) {
save(data.sections.map((section) => ({ ...section, hideWhenComplete: undefined })), { hideCompletedSections });
function setCompletedSectionsMode(mode) {
save(data.sections.map((section) => ({ ...section, hideWhenComplete: undefined })), {
hideCompletedSections: mode === "reduced",
hideCompletedSectionsFully: mode === "hidden"
});
}
return (
@ -82,19 +85,26 @@ export function ChecklistModule({ toolboxId, moduleId, context, editing }) {
onClose={closeImportModal}
/>
)}
{completedNamedSections.length > 0 && (
<div className="checklist-toolbar">
<button
className={`checklist-hide-toggle ${allCompletedSectionsHidden ? "active" : ""}`}
type="button"
onClick={() => setHideCompletedSections(!allCompletedSectionsHidden)}
aria-pressed={allCompletedSectionsHidden}
title={textContent.hideCompletedSectionsTitle || "Masquer les catégories terminées"}
aria-label={textContent.hideCompletedSectionsTitle || "Masquer les catégories terminées"}
>
<Icon name="hide" />
<i aria-hidden="true" />
</button>
{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">
@ -140,6 +150,14 @@ 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 === "♀"
? <span className="checklist-gender-symbol" key={index}>{part}</span>
: part
));
}
function ChecklistSection({ section, context, sections, data, save }) {
const showTitle = Boolean(section.title);
const sectionComplete = showTitle && isSectionComplete(section, context);
@ -147,6 +165,8 @@ function ChecklistSection({ section, context, sections, data, save }) {
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));
}
@ -178,10 +198,10 @@ function ChecklistSection({ section, context, sections, data, save }) {
}
return (
<li className={`checklist-section ${showTitle ? "is-grouped" : ""} ${isCollapsed ? "is-collapsed" : ""}`}>
<li className={`checklist-section ${showTitle ? "is-grouped" : ""} ${sectionComplete ? "is-complete" : ""} ${isCollapsed ? "is-collapsed" : ""}`}>
{showTitle && (
<div className="checklist-section-header">
<h3>{section.title || "Sans catégorie"}</h3>
<h3><CategoryTitle title={section.title || "Sans catégorie"} /></h3>
<div>
<span>{section.items.filter((item) => context.clampQty(item.qtyCurrent, item.qtyTarget) >= item.qtyTarget).length} / {section.items.length}</span>
<button
@ -189,8 +209,8 @@ function ChecklistSection({ section, context, sections, data, save }) {
type="button"
onClick={toggleSectionCollapsed}
aria-expanded={!isCollapsed}
title={isCollapsed ? context.moduleText?.checklist?.showCompletedSectionTitle || "Afficher cette catégorie" : context.moduleText?.checklist?.hideCompletedSectionTitle || "Masquer cette catégorie"}
aria-label={isCollapsed ? context.moduleText?.checklist?.showCompletedSectionTitle || "Afficher cette catégorie" : context.moduleText?.checklist?.hideCompletedSectionTitle || "Masquer cette catégorie"}
title={isCollapsed ? context.moduleText?.checklist?.showCompletedSectionTitle || "Afficher cette catégorie" : context.moduleText?.checklist?.hideCompletedSectionTitle || "Réduire cette catégorie"}
aria-label={isCollapsed ? context.moduleText?.checklist?.showCompletedSectionTitle || "Afficher cette catégorie" : context.moduleText?.checklist?.hideCompletedSectionTitle || "Réduire cette catégorie"}
>
<Icon name={isCollapsed ? "chevron-down" : "chevron-up"} />
</button>