From 1d3bdd60f104762a55934e21d4abebfd252386a6 Mon Sep 17 00:00:00 2001 From: Shinuwa Date: Tue, 28 Jul 2026 17:48:13 +0200 Subject: [PATCH] task planner categories optimisation --- docs/STORAGE_SCHEMA.md | 6 + tests/helpers/data-validation.mjs | 5 + tests/toolbox-modules.test.mjs | 8 + website/public/data/site.json | 5 + .../toolboxes/modules/TaskPlannerModule.jsx | 167 ++++++++++++++---- .../toolboxes/storage/toolboxStorage.js | 9 +- website/src/styles/_toolboxes.scss | 54 +++++- 7 files changed, 217 insertions(+), 37 deletions(-) diff --git a/docs/STORAGE_SCHEMA.md b/docs/STORAGE_SCHEMA.md index d963423..1178649 100644 --- a/docs/STORAGE_SCHEMA.md +++ b/docs/STORAGE_SCHEMA.md @@ -269,7 +269,9 @@ Type : `taskPlanner` "weeklyResetDay": 1, "resetTime": "00:00", "lastResetAt": 1760000000000, + "hideCompleted": true, "categoryOrder": ["Raid", "Farm"], + "collapsedCategories": ["Farm"], "tasks": [ { "id": "task1", @@ -309,10 +311,12 @@ Notes : - `sortMode` vaut `manual` ou `type`. Le tri par type affiche quotidiennes, hebdo, puis uniques. - `weeklyResetDay` suit les jours JavaScript : `0` dimanche, `1` lundi, ..., `6` samedi. - `resetTime` est global à l'outil et vaut `HH:MM`. +- `hideCompleted` masque les tâches effectuées à l'affichage, ainsi que les catégories dont toutes les tâches sont effectuées. - `weeklyResetDay` sur une tâche hebdo surcharge le jour global. - `category` sur une tâche racine est facultatif. Les tâches enfants héritent de la catégorie effective de leur parent et ne stockent pas de catégorie propre. - Les tâches catégorisées sont regroupées par catégorie au niveau racine. - `categoryOrder` stocke l'ordre d'affichage des catégories utilisées. Les catégories absentes des tâches sont retirées à la normalisation. +- `collapsedCategories` stocke les noms des catégories repliées. - `relations` stocke les parents directionnels. `fromTaskId` désigne la tâche enfant, `toTaskId` son parent. - une tâche ne conserve qu'un seul parent. - `prerequisite: true` marque aussi ce parent comme pré requis. @@ -325,9 +329,11 @@ Stockage compact : - `weeklyResetDay` global est omis si la valeur vaut `1`. - `resetTime` est omis si la valeur vaut `00:00`. - `lastResetAt` est omis si la valeur vaut `0`. +- `hideCompleted` est omis si `false`. - `description` est omis si vide. - `category` est omis si vide ou si la tâche a un parent. - `categoryOrder` est omis si aucune catégorie n'est utilisée. +- `collapsedCategories` est omis si aucune catégorie n'est repliée. - `checked` et `checkedAt` sont omis si la tâche n'est pas effectuée. - `weeklyResetDay` unitaire est omis si non défini. - `relations` est omis si vide. diff --git a/tests/helpers/data-validation.mjs b/tests/helpers/data-validation.mjs index d396393..702f7dd 100644 --- a/tests/helpers/data-validation.mjs +++ b/tests/helpers/data-validation.mjs @@ -192,8 +192,11 @@ export function validateSiteContent(site) { "toolboxes.modules.taskPlanner.settingsTitle", "toolboxes.modules.taskPlanner.settingsButtonLabel", "toolboxes.modules.taskPlanner.sortModeLabel", + "toolboxes.modules.taskPlanner.hideCompletedTitle", "toolboxes.modules.taskPlanner.globalWeeklyResetDayLabel", "toolboxes.modules.taskPlanner.resetTimeLabel", + "toolboxes.modules.taskPlanner.clearCompletedUniqueTitle", + "toolboxes.modules.taskPlanner.clearCompletedUniqueButton", "toolboxes.modules.taskPlanner.titlePlaceholder", "toolboxes.modules.taskPlanner.titleLabel", "toolboxes.modules.taskPlanner.descriptionPlaceholder", @@ -217,6 +220,8 @@ export function validateSiteContent(site) { "toolboxes.modules.taskPlanner.categoryLabel", "toolboxes.modules.taskPlanner.categoryPlaceholder", "toolboxes.modules.taskPlanner.categoryReorderTitle", + "toolboxes.modules.taskPlanner.showCategoryTitle", + "toolboxes.modules.taskPlanner.hideCategoryTitle", "toolboxes.modules.taskPlanner.taskWeeklyResetDayLabel", "toolboxes.modules.taskPlanner.inheritWeeklyResetDay", "toolboxes.modules.taskPlanner.linksLabel", diff --git a/tests/toolbox-modules.test.mjs b/tests/toolbox-modules.test.mjs index 538fb47..ee542d1 100644 --- a/tests/toolbox-modules.test.mjs +++ b/tests/toolbox-modules.test.mjs @@ -33,7 +33,9 @@ test("task planner data normalizes invalid settings and relations", () => { sortMode: "unknown", weeklyResetDay: 9, resetTime: "27:80", + hideCompleted: true, categoryOrder: ["Raid", "Missing", "Farm", "Raid"], + collapsedCategories: ["Raid", "", "Raid"], tasks: [ { id: "task1", title: "Daily", type: "daily", checked: true, checkedAt: 1000, category: "Farm" }, { id: "task2", title: "Weekly", type: "weekly", weeklyResetDay: 4, category: "Raid" }, @@ -50,7 +52,9 @@ test("task planner data normalizes invalid settings and relations", () => { assert.equal(normalized.sortMode, "manual"); assert.equal(normalized.weeklyResetDay, 1); assert.equal(normalized.resetTime, "00:00"); + assert.equal(normalized.hideCompleted, true); assert.deepEqual(normalized.categoryOrder, ["Raid"]); + assert.deepEqual(normalized.collapsedCategories, ["Raid"]); assert.equal(normalized.tasks[0].category, undefined); assert.equal(normalized.tasks[1].weeklyResetDay, 4); assert.equal(normalized.tasks[1].category, "Raid"); @@ -65,6 +69,8 @@ test("task planner storage compacts defaults and export remaps task relations", sortMode: "type", weeklyResetDay: 1, resetTime: "00:00", + hideCompleted: true, + collapsedCategories: ["Raid"], tasks: [ { id: "task1", title: "Daily", description: "", type: "daily", checked: false }, { id: "task2", title: "Weekly", description: "Run", type: "weekly", checked: true, checkedAt: 2000, weeklyResetDay: 5, category: "Raid" } @@ -78,6 +84,8 @@ test("task planner storage compacts defaults and export remaps task relations", assert.equal(compact.sortMode, "type"); assert.equal(compact.weeklyResetDay, undefined); assert.equal(compact.resetTime, undefined); + assert.equal(compact.hideCompleted, true); + assert.deepEqual(compact.collapsedCategories, ["Raid"]); assert.deepEqual(compact.categoryOrder, ["Raid"]); assert.deepEqual(compact.tasks[0], { id: "task1", title: "Daily", type: "daily" }); assert.equal(compact.tasks[1].category, "Raid"); diff --git a/website/public/data/site.json b/website/public/data/site.json index 42e9487..b876b60 100644 --- a/website/public/data/site.json +++ b/website/public/data/site.json @@ -326,8 +326,11 @@ "settingsTitle": "Réglages du planificateur", "settingsButtonLabel": "Paramètres des réinitialisations", "sortModeLabel": "Tri automatique par type", + "hideCompletedTitle": "Masquer les tâches effectuées", "globalWeeklyResetDayLabel": "Reset hebdo", "resetTimeLabel": "Heure de reset", + "clearCompletedUniqueTitle": "Nettoyer les tâches ponctuelles effectuées", + "clearCompletedUniqueButton": "Nettoyer les tâches ponctuelles terminées", "titlePlaceholder": "Nouvelle tâche", "titleLabel": "Titre", "descriptionPlaceholder": "Description", @@ -351,6 +354,8 @@ "categoryLabel": "Catégorie", "categoryPlaceholder": "Nom de catégorie", "categoryReorderTitle": "Déplacer la catégorie", + "showCategoryTitle": "Afficher cette catégorie", + "hideCategoryTitle": "Réduire cette catégorie", "taskWeeklyResetDayLabel": "Jour hebdo", "inheritWeeklyResetDay": "Global", "linksLabel": "Parent", diff --git a/website/src/features/toolboxes/modules/TaskPlannerModule.jsx b/website/src/features/toolboxes/modules/TaskPlannerModule.jsx index 3bcde23..c25a0e4 100644 --- a/website/src/features/toolboxes/modules/TaskPlannerModule.jsx +++ b/website/src/features/toolboxes/modules/TaskPlannerModule.jsx @@ -178,6 +178,31 @@ function countTaskTree(taskIds, tasks, parentMap) { return count; } +function getTaskTreeIds(taskIds, tasks, parentMap) { + const ids = []; + const visited = new Set(); + function visit(taskId) { + if (visited.has(taskId)) return; + visited.add(taskId); + ids.push(taskId); + tasks + .filter((task) => (parentMap.get(task.id) || "") === taskId) + .forEach((child) => visit(child.id)); + } + taskIds.forEach(visit); + return ids; +} + +function countCompletedTaskTree(taskIds, tasks, parentMap) { + const taskIdsSet = new Set(getTaskTreeIds(taskIds, tasks, parentMap)); + return tasks.filter((task) => taskIdsSet.has(task.id) && task.checked).length; +} + +function isTaskTreeComplete(taskIds, tasks, parentMap) { + const treeIds = getTaskTreeIds(taskIds, tasks, parentMap); + return treeIds.length > 0 && treeIds.every((taskId) => getTaskById(tasks, taskId)?.checked); +} + function getTaskCategoryGroups(tasks, parentMap, parentId, sortMode, categoryOrder) { const siblings = getTasksForParent(tasks, parentMap, parentId, sortMode); if (parentId) { @@ -420,6 +445,21 @@ export function TaskPlannerModule({ toolboxId, moduleId, context, editing }) { save({ ...data, tasks, categoryOrder }); } + function toggleCategoryCollapsed(category) { + const collapsedCategories = data.collapsedCategories.includes(category) + ? data.collapsedCategories.filter((item) => item !== category) + : [...data.collapsedCategories, category]; + save({ ...data, collapsedCategories }); + } + + function clearCompletedUniqueTasks() { + const removedTaskIds = new Set(data.tasks.filter((task) => task.type === "unique" && task.checked).map((task) => task.id)); + if (!removedTaskIds.size) return; + const tasks = data.tasks.filter((task) => !removedTaskIds.has(task.id)); + const relations = data.relations.filter((relation) => !removedTaskIds.has(relation.fromTaskId) && !removedTaskIds.has(relation.toTaskId)); + save({ ...data, tasks, relations }); + } + function toggleSet(setter, taskId) { setter((current) => { const next = new Set(current); @@ -459,6 +499,17 @@ export function TaskPlannerModule({ toolboxId, moduleId, context, editing }) {