108 lines
4.9 KiB
JavaScript
108 lines
4.9 KiB
JavaScript
import { useState } from "react";
|
|
import { Icon } from "../../../components/Icon.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 [qty, setQty] = useState(1);
|
|
const [textImport, setTextImport] = useState("");
|
|
|
|
function save(items) {
|
|
context.setModuleData(toolboxId, moduleId, { items });
|
|
}
|
|
|
|
function addItem(event) {
|
|
event.preventDefault();
|
|
const cleanLabel = label.trim();
|
|
if (!cleanLabel) return;
|
|
save([...data.items, { id: context.uid("item"), label: cleanLabel, qtyTarget: Math.max(1, Number(qty) || 1), qtyCurrent: 0 }]);
|
|
setLabel("");
|
|
setQty(1);
|
|
}
|
|
|
|
function importItems(event) {
|
|
event.preventDefault();
|
|
const imported = parseColonImportLines(textImport).map((entry) => ({
|
|
id: context.uid("item"),
|
|
label: entry.label,
|
|
qtyTarget: Math.max(1, Number.parseInt(entry.value, 10) || 1),
|
|
qtyCurrent: 0
|
|
}));
|
|
if (!imported.length) return;
|
|
save([...data.items, ...imported]);
|
|
setTextImport("");
|
|
}
|
|
|
|
return (
|
|
<>
|
|
{editing && (
|
|
<div className="module-add-panel">
|
|
<form className="inline-form checklist-add-form" onSubmit={addItem}>
|
|
<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>
|
|
<form className="text-import-form" onSubmit={importItems}>
|
|
<textarea
|
|
value={textImport}
|
|
onChange={(event) => setTextImport(event.target.value)}
|
|
placeholder={textContent.importPlaceholder || "Importer plusieurs items...\nPotion:10\nMéga potion:5"}
|
|
rows={3}
|
|
/>
|
|
<button type="submit">{textContent.importButton || "Importer le texte"}</button>
|
|
</form>
|
|
</div>
|
|
)}
|
|
<ul className="checklist">
|
|
{data.items.map((item) => (
|
|
<ChecklistItem key={item.id} item={item} toolboxId={toolboxId} moduleId={moduleId} context={context} items={data.items} save={save} />
|
|
))}
|
|
</ul>
|
|
</>
|
|
);
|
|
}
|
|
|
|
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 (
|
|
<li className={`checklist-item ${done ? "is-complete" : ""}`}>
|
|
<div className="checklist-item-main">
|
|
{item.qtyTarget === 1 ? (
|
|
<input
|
|
type="checkbox"
|
|
checked={done}
|
|
onChange={(event) => updateItem((entry) => ({ ...entry, qtyTarget: 1, qtyCurrent: event.target.checked ? 1 : 0 }))}
|
|
aria-label={`Terminer ${item.label}`}
|
|
/>
|
|
) : (
|
|
<div className="checklist-qty-controls" aria-label={`Quantité ${item.label}`}>
|
|
<button type="button" onClick={() => updateItem((entry) => ({ ...entry, qtyCurrent: context.clampQty(entry.qtyCurrent - 1, entry.qtyTarget) }))} aria-label={context.moduleText?.checklist?.decrementLabel || "Retirer une quantité"}>-</button>
|
|
<label className="checklist-qty-current">
|
|
<input
|
|
type="number"
|
|
max={item.qtyTarget}
|
|
value={context.clampQty(item.qtyCurrent, item.qtyTarget)}
|
|
style={{ "--qty-current-digits": String(context.clampQty(item.qtyCurrent, item.qtyTarget)).length }}
|
|
onChange={(event) => updateItem((entry) => ({ ...entry, qtyCurrent: context.clampQty(event.target.value, entry.qtyTarget) }))}
|
|
aria-label={`${context.moduleText?.checklist?.currentQuantityLabel || "Quantité actuelle"} ${item.label}`}
|
|
/>
|
|
<span>/ {item.qtyTarget}</span>
|
|
</label>
|
|
<button type="button" onClick={() => updateItem((entry) => ({ ...entry, qtyCurrent: context.clampQty(entry.qtyCurrent + 1, entry.qtyTarget) }))} aria-label={context.moduleText?.checklist?.incrementLabel || "Ajouter une quantité"}>+</button>
|
|
</div>
|
|
)}
|
|
<span>{item.label}</span>
|
|
</div>
|
|
<button className="checklist-delete-button danger" onClick={() => save(items.filter((entry) => entry.id !== item.id))} aria-label={`${context.moduleText?.checklist?.deleteTitle || "Supprimer"} ${item.label}`} title={context.moduleText?.checklist?.deleteTitle || "Supprimer"}>
|
|
<Icon name="trash" />
|
|
</button>
|
|
</li>
|
|
);
|
|
}
|