diff --git a/eslint.config.js b/eslint.config.js index c9c9357..e628cdc 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -41,7 +41,7 @@ export default [ varsIgnorePattern: "^_", caughtErrorsIgnorePattern: "^_" }], - "react-refresh/only-export-components": ["warn", { allowConstantExport: true }] + "react-refresh/only-export-components": "off" } }, { diff --git a/tests/static-styles.test.mjs b/tests/static-styles.test.mjs index b4f6cc3..0fe0d53 100644 --- a/tests/static-styles.test.mjs +++ b/tests/static-styles.test.mjs @@ -13,18 +13,7 @@ test("style entrypoint and theme layers are wired", async () => { const styleCards = await readFile("website/src/styles/_cards.scss", "utf8"); const styleHome = await readFile("website/src/styles/_home.scss", "utf8"); const styleToolboxesEntry = await readFile("website/src/styles/_toolboxes.scss", "utf8"); - const styleToolboxesCore = await readFile("website/src/styles/toolboxes/_core.scss", "utf8"); - const styleToolboxLayout = await readFile("website/src/styles/toolboxes/_layout.scss", "utf8"); - const styleToolboxShell = await readFile("website/src/styles/toolboxes/_module-shell.scss", "utf8"); - const styleToolboxTextExchange = await readFile("website/src/styles/toolboxes/_text-exchange.scss", "utf8"); - const styleToolboxNotepad = await readFile("website/src/styles/toolboxes/_notepad.scss", "utf8"); - const styleToolboxChecklist = await readFile("website/src/styles/toolboxes/_checklist.scss", "utf8"); - const styleToolboxCalculator = await readFile("website/src/styles/toolboxes/_calculator.scss", "utf8"); - const styleToolboxCalculatorResults = await readFile("website/src/styles/toolboxes/_calculator-results.scss", "utf8"); - const styleToolboxTable = await readFile("website/src/styles/toolboxes/_table.scss", "utf8"); - const styleToolboxCombos = await readFile("website/src/styles/toolboxes/_combos.scss", "utf8"); - const styleToolboxImages = await readFile("website/src/styles/toolboxes/_images.scss", "utf8"); - const styleToolboxSharedControls = await readFile("website/src/styles/toolboxes/_shared-controls.scss", "utf8"); + const styleToolboxes = await readFile("website/src/styles/toolboxes/_core.scss", "utf8"); const styleOverlays = await readFile("website/src/styles/_overlays.scss", "utf8"); const styleIcons = await readFile("website/src/styles/_icons.scss", "utf8"); const styleGames = await readFile("website/src/styles/_games.scss", "utf8"); @@ -35,11 +24,6 @@ test("style entrypoint and theme layers are wired", async () => { assert.match(styles, /@use "nebula"/); assert.match(styles, /@use "toolboxes"/); assert.match(styleToolboxesEntry, /@use "toolboxes\/core"/); - assert.match(styleToolboxesCore, /@use "layout"/); - assert.match(styleToolboxesCore, /@use "module-shell"/); - assert.match(styleToolboxesCore, /@use "task-planner"/); - assert.match(styleToolboxesCore, /@use "equipment-planner"/); - assert.match(styleToolboxesCore, /@use "shared-controls"/); assert.match(styles, /@use "mhwilds"/); assert.match(styles, /@use "diablo4"/); assert.match(styleTokens, /--gradient-brand/); @@ -64,22 +48,22 @@ test("style entrypoint and theme layers are wired", async () => { assert.match(styleMixins, /@mixin gold-panel-frame/); assert.match(styleShell, /\.sidebar-about-link/); assert.match(styleShell, /\.sidebar-mascot/); - assert.match(styleToolboxShell, /\.module-icon-abacus/); - assert.match(styleToolboxShell, /\.module-icon-calculator/); - assert.match(styleToolboxShell, /\.module-icon-columns/); - assert.match(styleToolboxShell, /\.module-icon-table/); - assert.match(styleToolboxCalculator, /\.calculator-module/); - assert.match(styleToolboxTable, /\.table-module/); - assert.match(styleToolboxTable, /\.table-cell-input\.is-selected/); - assert.match(styleToolboxTable, /\.table-cell-input\.is-move-target/); - assert.match(styleToolboxSharedControls, /\.notepad-toolbar-button\.danger/); - assert.match(styleToolboxLayout, /\.toolbox-icon-picker/); - assert.match(styleToolboxLayout, /\.toolbox-game-icon/); - assert.match(styleToolboxLayout, /\.toolbox-hero-link/); - assert.match(styleToolboxLayout, /\.toolbox-actions-row/); - assert.match(styleToolboxTextExchange, /\.text-import-actions/); - assert.match(styleToolboxTextExchange, /\.text-import-modal-dialog/); - assert.doesNotMatch(styleToolboxNotepad, /lecture seule/); + assert.match(styleToolboxes, /\.module-icon-abacus/); + assert.match(styleToolboxes, /\.module-icon-calculator/); + assert.match(styleToolboxes, /\.module-icon-columns/); + assert.match(styleToolboxes, /\.module-icon-table/); + assert.match(styleToolboxes, /\.calculator-module/); + assert.match(styleToolboxes, /\.table-module/); + assert.match(styleToolboxes, /\.table-cell-input\.is-selected/); + assert.match(styleToolboxes, /\.table-cell-input\.is-move-target/); + assert.match(styleToolboxes, /\.notepad-toolbar-button\.danger/); + assert.match(styleToolboxes, /\.toolbox-icon-picker/); + assert.match(styleToolboxes, /\.toolbox-game-icon/); + assert.match(styleToolboxes, /\.toolbox-hero-link/); + assert.match(styleToolboxes, /\.toolbox-actions-row/); + assert.match(styleToolboxes, /\.text-import-actions/); + assert.match(styleToolboxes, /\.text-import-modal-dialog/); + assert.doesNotMatch(styleToolboxes, /lecture seule/); assert.match(styleIcons, /ui-icon-drag/); assert.match(styleIcons, /ui-icon-dropdown/); assert.match(styleIcons, /ui-icon-enter/); @@ -101,17 +85,12 @@ test("style entrypoint and theme layers are wired", async () => { assert.match(styleIcons, /ui-icon-grab/); assert.match(styleIcons, /ui-icon-shield/); assert.match(styleIcons, /ui-icon-gun/); - assert.match(styleToolboxShell, /module-icon-chest-armor/); - assert.match(styleToolboxCombos, /\.combo-hold-controls/); - assert.match(styleToolboxCombos, /\.combo-input-direction/); - assert.match(styleToolboxCombos, /\.combo-device-n64\.combo-value-stick-up/); - assert.match(styleToolboxCombos, /\.combo-device-n64\.combo-value-up/); - assert.match(styleToolboxCombos, /\.combo-value-grab \.ui-icon/); - assert.match(styleToolboxChecklist, /\.timer-alert-mode-button/); - assert.match(styleToolboxSharedControls, /\.timer-alert-mode-switch\.is-blocked/); - assert.match(styleToolboxCalculatorResults, /\.calculator-result/); - assert.match(styleToolboxSharedControls, /\.tool-split-scroll-toggle/); - assert.match(styleToolboxImages, /\.annotation-marker/); + assert.match(styleToolboxes, /module-icon-chest-armor/); + assert.match(styleToolboxes, /\.combo-hold-controls/); + assert.match(styleToolboxes, /\.combo-input-direction/); + assert.match(styleToolboxes, /\.combo-device-n64\.combo-value-stick-up/); + assert.match(styleToolboxes, /\.combo-device-n64\.combo-value-up/); + assert.match(styleToolboxes, /\.combo-value-grab \.ui-icon/); assert.match(styleGames, /\.game-home-card/); assert.match(styleGames, /\.game-layout/); assert.match(styleGames, /\.game-grid/); diff --git a/tests/static-toolboxes.test.mjs b/tests/static-toolboxes.test.mjs index 500c63a..313b650 100644 --- a/tests/static-toolboxes.test.mjs +++ b/tests/static-toolboxes.test.mjs @@ -2,8 +2,6 @@ import { readFile } from "node:fs/promises"; import { test } from "node:test"; import assert from "node:assert/strict"; -import { SOCKET_SHAPES, formatValue, getSocketItemName, setItemCategory } from "../website/src/features/toolboxes/modules/equipmentPlannerOptions.js"; -import { applyDueResets, getTaskParentMap, getTaskTreeIds, moveTask } from "../website/src/features/toolboxes/modules/taskPlannerUtils.js"; test("toolbox storage, cards and pages are wired", async () => { const source = await readFile("website/src/main.jsx", "utf8"); @@ -113,10 +111,7 @@ test("toolbox module registry and modules expose expected behavior", async () => const tableFormulaEngine = await readFile("website/src/features/toolboxes/modules/tableFormulaEngine.js", "utf8"); const timerModule = await readFile("website/src/features/toolboxes/modules/TimerModule.jsx", "utf8"); const taskPlannerModule = await readFile("website/src/features/toolboxes/modules/TaskPlannerModule.jsx", "utf8"); - const taskPlannerItems = await readFile("website/src/features/toolboxes/modules/TaskPlannerItems.jsx", "utf8"); - const taskPlannerUtils = await readFile("website/src/features/toolboxes/modules/taskPlannerUtils.js", "utf8"); const equipmentPlannerModule = await readFile("website/src/features/toolboxes/modules/EquipmentPlannerModule.jsx", "utf8"); - const equipmentPlannerOptions = await readFile("website/src/features/toolboxes/modules/equipmentPlannerOptions.js", "utf8"); const checklistModule = await readFile("website/src/features/toolboxes/modules/ChecklistModule.jsx", "utf8"); const imagesModule = await readFile("website/src/features/toolboxes/modules/ImagesModule.jsx", "utf8"); const imageAnnotationModule = await readFile("website/src/features/toolboxes/modules/ImageAnnotationModule.jsx", "utf8"); @@ -258,20 +253,17 @@ test("toolbox module registry and modules expose expected behavior", async () => assert.match(taskPlannerModule, /export function TaskPlannerModule/); assert.match(taskPlannerModule, /normalizeTaskPlannerData/); assert.match(taskPlannerModule, /applyDueResets/); - assert.match(taskPlannerModule, /TaskPlannerBranch/); + assert.match(taskPlannerModule, /relations/); + assert.match(taskPlannerModule, /prerequisite/); + assert.match(taskPlannerModule, /categoryOrder/); + assert.match(taskPlannerModule, /categoryLabel/); + assert.match(taskPlannerModule, /task-planner-warning/); assert.match(taskPlannerModule, /useGroupedReorder/); + assert.match(taskPlannerModule, /useInlineEdit/); assert.match(taskPlannerModule, /useDraftForm/); + assert.match(taskPlannerModule, /commitOnEnter: false/); assert.match(taskPlannerModule, /TextExchangeActions/); assert.match(taskPlannerModule, /reorderFeatures/); - assert.match(taskPlannerItems, /export function TaskPlannerBranch/); - assert.match(taskPlannerItems, /function TaskPlannerItem/); - assert.match(taskPlannerItems, /task-planner-warning/); - assert.match(taskPlannerItems, /useInlineEdit/); - assert.match(taskPlannerItems, /commitOnEnter: false/); - assert.match(taskPlannerItems, /categoryLabel/); - assert.match(taskPlannerItems, /prerequisite/); - assert.match(taskPlannerUtils, /export function getTaskParentMap/); - assert.match(taskPlannerUtils, /export function applyDueResets/); assert.doesNotMatch(taskPlannerModule, /canMoveItem:/); assert.doesNotMatch(taskPlannerModule, /canMoveGroup:/); assert.match(equipmentPlannerModule, /export function EquipmentPlannerModule/); @@ -282,8 +274,6 @@ test("toolbox module registry and modules expose expected behavior", async () => assert.match(equipmentPlannerModule, /useGroupedReorder/); assert.match(equipmentPlannerModule, /reorderFeatures/); assert.match(equipmentPlannerModule, /TextExchangeActions/); - assert.match(equipmentPlannerOptions, /export const EQUIPMENT_ICONS/); - assert.match(equipmentPlannerOptions, /export function formatValue/); assert.match(checklistModule, /export function ChecklistModule/); assert.match(checklistModule, /TextExchangeActions/); assert.match(checklistModule, /editing &&/); @@ -364,35 +354,3 @@ test("toolbox module registry and modules expose expected behavior", async () => assert.match(imageViewer, / { - const tasks = [ - { id: "a", title: "A", type: "daily", checked: true, checkedAt: Date.UTC(2026, 0, 1, 7) }, - { id: "b", title: "B", type: "unique", checked: false }, - { id: "c", title: "C", type: "weekly", checked: true, checkedAt: Date.UTC(2026, 0, 1, 7) } - ]; - const relations = [{ id: "r1", fromTaskId: "b", toTaskId: "a", prerequisite: false }]; - const parentMap = getTaskParentMap(tasks, relations); - - assert.equal(parentMap.get("b"), "a"); - assert.deepEqual(getTaskTreeIds(["a"], tasks, parentMap), ["a", "b"]); - assert.deepEqual(moveTask(tasks, "c", "a").map((task) => task.id), ["c", "a", "b"]); - - const resetData = applyDueResets({ - tasks, - relations, - resetTime: "06:00", - weeklyResetDay: 4, - categoryOrder: [], - lastResetAt: Date.UTC(2026, 0, 1, 5) - }, Date.UTC(2026, 0, 2, 8)); - assert.equal(resetData.tasks.find((task) => task.id === "a").checked, false); -}); - -test("equipment planner options expose stable formatting and socket helpers", () => { - assert.deepEqual(SOCKET_SHAPES, ["ball", "jewel"]); - assert.equal(formatValue(1.23456), "1.235"); - assert.deepEqual(setItemCategory({ id: "item" }, "DPS"), { id: "item", category: "DPS" }); - assert.deepEqual(setItemCategory({ id: "item", category: "DPS" }, ""), { id: "item" }); - assert.equal(getSocketItemName({ socketItems: [{ id: "gem", name: "Ruby" }] }, "gem"), "Ruby"); -}); diff --git a/website/src/features/games/CopyChecklistItemsButton.jsx b/website/src/features/games/CopyChecklistItemsButton.jsx index 609b6e0..70855e8 100644 --- a/website/src/features/games/CopyChecklistItemsButton.jsx +++ b/website/src/features/games/CopyChecklistItemsButton.jsx @@ -1,5 +1,4 @@ // Rôle : copie des données filtrées au format compatible avec l'import checklist. -/* eslint-disable react-refresh/only-export-components -- expose aussi les formatters publics testés pour l'import checklist. */ import { useState } from "react"; import { Icon } from "../../components/Icon.jsx"; diff --git a/website/src/features/toolboxes/ToolboxCard.jsx b/website/src/features/toolboxes/ToolboxCard.jsx index 35e841b..09a7d55 100644 --- a/website/src/features/toolboxes/ToolboxCard.jsx +++ b/website/src/features/toolboxes/ToolboxCard.jsx @@ -1,5 +1,4 @@ // Rôle : affiche une toolbox dans la liste, avec actions rapides et icône personnalisable. -/* eslint-disable react-refresh/only-export-components -- expose aussi les helpers publics de rendu des cartes toolbox. */ import { CompactDropdown } from "../../components/CompactDropdown.jsx"; import { Icon } from "../../components/Icon.jsx"; import { diff --git a/website/src/features/toolboxes/modules/EquipmentPlannerModule.jsx b/website/src/features/toolboxes/modules/EquipmentPlannerModule.jsx index 580f728..512204e 100644 --- a/website/src/features/toolboxes/modules/EquipmentPlannerModule.jsx +++ b/website/src/features/toolboxes/modules/EquipmentPlannerModule.jsx @@ -6,19 +6,70 @@ import { Tooltip } from "../../../components/Tooltip.jsx"; import { applyGroupedReorderOperation, moveItem, useGroupedReorder } from "../../../hooks/useGroupedReorder.js"; import { useInlineEdit } from "../../../hooks/useInlineEdit.js"; import { TextExchangeActions } from "./TextExchangeActions.jsx"; -import { - DETAIL_TABS, - EQUIPMENT_ICONS, - SOCKET_COLORS, - SOCKET_COLOR_VALUES, - SOCKET_SHAPES, - TRAIT_ICONS, - createLocalId, - formatValue, - getCharacteristicCategories, - getSocketItemName, - setItemCategory -} from "./equipmentPlannerOptions.js"; + +const SOCKET_SHAPES = ["ball", "jewel"]; +const SOCKET_COLORS = ["red", "orange", "amber", "yellow", "lime", "green", "teal", "cyan", "blue", "indigo", "violet", "purple", "pink", "rose", "white", "gray", "black"]; +const SOCKET_COLOR_VALUES = { + red: "#ef4444", + orange: "#f97316", + amber: "#f59e0b", + yellow: "var(--color-accent-gold)", + lime: "#84cc16", + green: "#22c55e", + teal: "#14b8a6", + cyan: "var(--color-accent-cyan)", + blue: "#3b82f6", + indigo: "var(--color-accent-indigo)", + violet: "#7c3aed", + purple: "var(--color-primary)", + pink: "var(--color-accent-pink)", + rose: "#f43f5e", + white: "#f8fafc", + gray: "#64748b", + black: "#020617" +}; +const EQUIPMENT_ICONS = [ + { icon: "helmet", label: "Casque" }, + { icon: "chest-armor", label: "Chest" }, + { icon: "glove", label: "Gants" }, + { icon: "boot", label: "Bottes" }, + { icon: "necklace", label: "Collier" }, + { icon: "earring", label: "Earring" }, + { icon: "ring", label: "Anneau" }, + { icon: "sword", label: "Épée" }, + { icon: "gun", label: "Gun" }, + { icon: "shield", label: "Bouclier" } +]; +const TRAIT_ICONS = ["sword", "shield"]; +const DETAIL_TABS = ["stats", "sockets", "craft"]; + +function getCharacteristicCategories(equipment) { + const categories = equipment.characteristics.map((trait) => trait.category).filter(Boolean); + return [ + ...equipment.categoryOrder.filter((category) => categories.includes(category)), + ...categories.filter((category, index) => categories.indexOf(category) === index && !equipment.categoryOrder.includes(category)) + ]; +} + +function setItemCategory(item, category) { + const nextItem = { ...item }; + if (category) nextItem.category = category; + else delete nextItem.category; + return nextItem; +} + +function formatValue(value) { + if (typeof value === "number") return Number.isInteger(value) ? String(value) : String(Math.round(value * 1000) / 1000); + return String(value || ""); +} + +function createLocalId(prefix) { + return globalThis.crypto?.randomUUID?.() || `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; +} + +function getSocketItemName(equipment, socketItemId) { + return equipment.socketItems.find((socketItem) => socketItem.id === socketItemId)?.name || ""; +} function SocketShapeIcon({ shape, color = "gray", className = "" }) { return ( diff --git a/website/src/features/toolboxes/modules/TaskPlannerItems.jsx b/website/src/features/toolboxes/modules/TaskPlannerItems.jsx deleted file mode 100644 index fff3b41..0000000 --- a/website/src/features/toolboxes/modules/TaskPlannerItems.jsx +++ /dev/null @@ -1,522 +0,0 @@ -// Rôle : rend les branches, items et panneaux secondaires du task planner. -import { Icon } from "../../../components/Icon.jsx"; -import { InlineNotice } from "../../../components/AppOverlays.jsx"; -import { Tooltip } from "../../../components/Tooltip.jsx"; -import { useInlineEdit } from "../../../hooks/useInlineEdit.js"; -import { - TASK_TYPES, - WEEK_DAYS, - countCompletedTaskTree, - countTaskTree, - getEffectiveTaskCategory, - getTaskById, - getTaskCategoryDataset, - getTaskCategoryGroups, - getTaskTitle, - getTaskTypeLabel, - getTasksForParent, - isTaskTreeComplete -} from "./taskPlannerUtils.js"; - -export function TaskPlannerBranch({ - parentId, - parentMap, - tasks, - data, - reorder, - textContent, - openDescriptions, - openTaskSettings, - prerequisiteNoticeTaskIds, - onToggleDescription, - onToggleSettings, - onPrerequisiteNotice, - onUpdateTask, - onDeleteTask, - onAddLinkTarget, - onRemoveLinkTarget, - onSetPrerequisite, - onSetTaskCategory, - onToggleCategoryCollapsed, - visited = new Set() -}) { - const groupedTasks = getTaskCategoryGroups(tasks, parentMap, parentId, data.categoryOrder); - const children = [...groupedTasks.uncategorized, ...groupedTasks.categories.flatMap((group) => group.tasks)].filter((task) => !visited.has(task.id)); - if (!children.length) return null; - const nextVisited = new Set([...visited, ...children.map((task) => task.id)]); - const showCategoryBoundaryDropZones = !parentId && reorder.shouldShowGroupBoundaries({ parentId }); - - function hasVisibleDescendants(taskId, seen = new Set()) { - if (seen.has(taskId)) return false; - seen.add(taskId); - return getTasksForParent(tasks, parentMap, taskId).some((child) => !child.checked || hasVisibleDescendants(child.id, seen)); - } - - function renderTask(task) { - const nestedChildren = getTasksForParent(tasks, parentMap, task.id).filter((child) => !nextVisited.has(child.id)); - const nestedBranch = nestedChildren.length ? ( - - ) : null; - if (data.hideCompleted && task.checked) { - if (!hasVisibleDescendants(task.id)) return null; - return ( -
  • -
    - onUpdateTask(task.id, (current) => ({ - ...current, - checked: event.target.checked, - checkedAt: event.target.checked ? Date.now() : 0 - }))} - aria-label={`${textContent.uncheckTitle || "Marquer non effectué"} ${task.title}`} - /> - {task.title} - {getTaskTypeLabel(task.type, textContent)} -
    -
      {nestedBranch}
    -
  • - ); - } - return ( - onToggleDescription(task.id)} - onToggleSettings={() => onToggleSettings(task.id)} - onPrerequisiteNotice={onPrerequisiteNotice} - onUpdateTask={onUpdateTask} - onDeleteTask={onDeleteTask} - onAddLinkTarget={onAddLinkTarget} - onRemoveLinkTarget={onRemoveLinkTarget} - onSetPrerequisite={onSetPrerequisite} - onSetTaskCategory={onSetTaskCategory} - > - {nestedBranch} - - ); - } - - function renderCategory(group) { - const groupTasks = group.tasks.filter((task) => !visited.has(task.id)); - if (!groupTasks.length) return null; - if (data.hideCompleted && isTaskTreeComplete(groupTasks.map((task) => task.id), tasks, parentMap)) return null; - const groupCount = countTaskTree(groupTasks.map((task) => task.id), tasks, parentMap); - const groupCompletedCount = countCompletedTaskTree(groupTasks.map((task) => task.id), tasks, parentMap); - const isCollapsed = data.collapsedCategories.includes(group.category); - const sectionClassName = [ - "task-planner-category-section", - groupCompletedCount === groupCount ? "is-complete" : "", - isCollapsed ? "is-collapsed" : "", - reorder.isGroupDragging(group.category, parentId) ? "is-dragging" : "", - reorder.isGroupDropTarget(group.category, parentId) ? "is-drop-target" : "", - reorder.getDropPlacement("group", group.category, parentId) === "after" ? "drop-after" : "" - ].filter(Boolean).join(" "); - const beforeDropZone = showCategoryBoundaryDropZones ? ( -
  • - ) : null; - const afterDropZone = showCategoryBoundaryDropZones ? ( -
  • - ) : null; - return [ - beforeDropZone, -
  • -
    -
    - -

    {group.category}

    -
    -
    - {groupCompletedCount} / {groupCount} - -
    -
    - {!isCollapsed && ( -
      - {groupTasks.map(renderTask).filter(Boolean)} -
    - )} -
  • , - afterDropZone - ]; - } - - const entries = groupedTasks.entries.map((entry) => { - if (entry.type === "task") return visited.has(entry.task.id) ? null : renderTask(entry.task); - return renderCategory(entry.group); - }).filter(Boolean); - return entries; -} - -function TaskPlannerItem({ - task, - tasks, - data, - parentMap, - parentId, - reorder, - textContent, - descriptionOpen, - settingsOpen, - prerequisiteNoticeVisible, - onToggleDescription, - onToggleSettings, - onPrerequisiteNotice, - onUpdateTask, - onDeleteTask, - onAddLinkTarget, - onRemoveLinkTarget, - onSetPrerequisite, - onSetTaskCategory, - children -}) { - const parentTask = parentId ? getTaskById(tasks, parentId) : null; - const inheritedCategory = parentTask ? getEffectiveTaskCategory(parentTask, tasks, parentMap) : ""; - const categoryValue = parentTask ? inheritedCategory : task.category || ""; - const categoryEditable = !parentTask; - const relations = data.relations.filter((relation) => relation.fromTaskId === task.id); - const missingPrerequisites = relations.filter((relation) => relation.prerequisite && !tasks.find((item) => item.id === relation.toTaskId)?.checked); - const relationTargets = tasks.filter((item) => item.id !== task.id && !relations.some((relation) => relation.toTaskId === item.id)); - const noticeMessage = prerequisiteNoticeVisible && missingPrerequisites.length > 0 ? textContent.missingPrerequisiteNotice || "Pré requis parent non effectué" : ""; - const className = [ - "task-planner-item", - task.checked ? "is-complete" : "", - missingPrerequisites.length ? "has-missing-prerequisite" : "", - reorder.isItemDragging(task.id) ? "is-dragging" : "", - reorder.isItemDropTarget(task.id) ? "is-drop-target" : "", - reorder.getDropPlacement("item", task.id) === "after" ? "drop-after" : "" - ].filter(Boolean).join(" "); - const titleEdit = useInlineEdit({ - value: task.title, - transform: (draft) => String(draft || "").trim() || task.title, - onCommit: (title) => onUpdateTask(task.id, (current) => ({ ...current, title })) - }); - const categoryEdit = useInlineEdit({ - value: categoryValue, - onCommit: (category) => { - if (categoryEditable) onSetTaskCategory(task.id, category); - } - }); - const descriptionEdit = useInlineEdit({ - value: task.description, - onCommit: (description) => onUpdateTask(task.id, (current) => ({ ...current, description })), - commitOnEnter: false - }); - - function updateChecked(event) { - const checked = event.target.checked; - if (checked && missingPrerequisites.length > 0) onPrerequisiteNotice(task.id); - onUpdateTask(task.id, (current) => ({ - ...current, - checked, - checkedAt: checked ? Date.now() : 0 - })); - } - - return ( -
  • -
    - - - - - {missingPrerequisites.length > 0 && ( - } - position="top-start" - > - {textContent.missingPrerequisiteBadge || "Pré requis"} - - )} -
    - - - -
    -
    - - {descriptionOpen && ( -
    - -