diff --git a/docs/STORAGE_SCHEMA.md b/docs/STORAGE_SCHEMA.md
index b03f5f0..6d21d9e 100644
--- a/docs/STORAGE_SCHEMA.md
+++ b/docs/STORAGE_SCHEMA.md
@@ -326,6 +326,7 @@ Type : `taskPlanner`
"description": "Préparer la route.",
"category": "Raid",
"type": "daily",
+ "dailyResetTime": "06:30",
"checked": true,
"checkedAt": 1760000000000
},
@@ -358,6 +359,7 @@ Notes :
- `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.
+- `dailyResetTime` sur une tâche quotidienne surcharge l'heure globale.
- `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.
@@ -380,6 +382,7 @@ Stockage compact :
- `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.
+- `dailyResetTime` unitaire est omis si non défini ou identique à l'heure globale.
- `weeklyResetDay` unitaire est omis si non défini.
- `relations` est omis si vide.
- `relations[].prerequisite` est omis si `false`.
diff --git a/tests/helpers/data-validation.mjs b/tests/helpers/data-validation.mjs
index 03053ad..76d7e30 100644
--- a/tests/helpers/data-validation.mjs
+++ b/tests/helpers/data-validation.mjs
@@ -318,6 +318,10 @@ export function validateSiteContent(site) {
"toolboxes.modules.taskPlanner.uncategorizedDropTitle",
"toolboxes.modules.taskPlanner.showCategoryTitle",
"toolboxes.modules.taskPlanner.hideCategoryTitle",
+ "toolboxes.modules.taskPlanner.taskDailyResetTimeLabel",
+ "toolboxes.modules.taskPlanner.inheritResetTime",
+ "toolboxes.modules.taskPlanner.taskResetLabel",
+ "toolboxes.modules.taskPlanner.noTaskResetLabel",
"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 ece4441..ecad531 100644
--- a/tests/toolbox-modules.test.mjs
+++ b/tests/toolbox-modules.test.mjs
@@ -406,9 +406,9 @@ test("task planner data normalizes invalid settings and relations", () => {
categoryOrder: ["Raid", "Missing", "Farm", "Raid"],
collapsedCategories: ["Raid", "", "Raid"],
tasks: [
- { id: "task1", title: "Daily", type: "daily", checked: true, checkedAt: 1000, category: "Farm" },
+ { id: "task1", title: "Daily", type: "daily", checked: true, checkedAt: 1000, category: "Farm", dailyResetTime: "06:30" },
{ id: "task2", title: "Weekly", type: "weekly", weeklyResetDay: 4, category: "Raid" },
- { id: "task3", title: "Invalid type", type: "later", category: "Farm" }
+ { id: "task3", title: "Invalid type", type: "later", category: "Farm", dailyResetTime: "25:00" }
],
relations: [
{ id: "link1", fromTaskId: "task1", toTaskId: "task2" },
@@ -424,9 +424,11 @@ test("task planner data normalizes invalid settings and relations", () => {
assert.deepEqual(normalized.categoryOrder, ["Raid"]);
assert.deepEqual(normalized.collapsedCategories, ["Raid"]);
assert.equal(normalized.tasks[0].category, undefined);
+ assert.equal(normalized.tasks[0].dailyResetTime, "06:30");
assert.equal(normalized.tasks[1].weeklyResetDay, 4);
assert.equal(normalized.tasks[1].category, "Raid");
assert.equal(normalized.tasks[2].type, "unique");
+ assert.equal(normalized.tasks[2].dailyResetTime, undefined);
assert.deepEqual(normalized.relations.map((relation) => relation.id), ["link1", "dep1"]);
assert.equal(normalized.relations[0].prerequisite, false);
assert.equal(normalized.relations[1].prerequisite, true);
@@ -439,7 +441,7 @@ test("task planner storage compacts defaults and export remaps task relations",
hideCompleted: true,
collapsedCategories: ["Raid"],
tasks: [
- { id: "task1", title: "Daily", description: "", type: "daily", checked: false },
+ { id: "task1", title: "Daily", description: "", type: "daily", checked: false, dailyResetTime: "06:30" },
{ id: "task2", title: "Weekly", description: "Run", type: "weekly", checked: true, checkedAt: 2000, weeklyResetDay: 5, category: "Raid" }
],
categoryOrder: ["Raid"],
@@ -453,7 +455,7 @@ test("task planner storage compacts defaults and export remaps task relations",
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.deepEqual(compact.tasks[0], { id: "task1", title: "Daily", type: "daily", dailyResetTime: "06:30" });
assert.equal(compact.tasks[1].category, "Raid");
assert.equal(compact.tasks[1].weeklyResetDay, 5);
diff --git a/website/public/data/site.json b/website/public/data/site.json
index 4752927..5cefe53 100644
--- a/website/public/data/site.json
+++ b/website/public/data/site.json
@@ -485,6 +485,10 @@
"uncategorizedDropTitle": "Sans catégorie",
"showCategoryTitle": "Afficher cette catégorie",
"hideCategoryTitle": "Réduire cette catégorie",
+ "taskDailyResetTimeLabel": "Heure quotidienne",
+ "inheritResetTime": "Utiliser l'heure globale",
+ "taskResetLabel": "Réinitialisation",
+ "noTaskResetLabel": "Aucun reset",
"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 67381a2..d6a7707 100644
--- a/website/src/features/toolboxes/modules/TaskPlannerModule.jsx
+++ b/website/src/features/toolboxes/modules/TaskPlannerModule.jsx
@@ -55,7 +55,7 @@ function applyDueResets(data, now = Date.now()) {
const tasks = data.tasks.map((task) => {
if (task.type === "unique") return task;
const resetAt = task.type === "daily"
- ? getLatestDailyReset(now, data.resetTime)
+ ? getLatestDailyReset(now, task.dailyResetTime || data.resetTime)
: getLatestWeeklyReset(now, data.resetTime, task.weeklyResetDay ?? data.weeklyResetDay);
latestResetAt = Math.max(latestResetAt, resetAt);
if (!task.checked || resetAt <= (task.checkedAt || data.lastResetAt)) return task;
@@ -845,6 +845,7 @@ function TaskPlannerItem({
const nextType = event.target.value;
const nextTask = { ...current, type: nextType };
if (nextType !== "weekly") delete nextTask.weeklyResetDay;
+ if (nextType !== "daily") delete nextTask.dailyResetTime;
return nextTask;
})}
aria-label={textContent.typeLabel || "Type"}
@@ -909,41 +910,22 @@ function TaskPlannerItem({
)}
{settingsOpen && (
- {task.type === "weekly" && (
-
- )}
-
+
+
)}
{children && }
@@ -951,6 +933,73 @@ function TaskPlannerItem({
);
}
+function TaskResetSettings({ task, data, textContent, onUpdateTask }) {
+ if (task.type === "daily") {
+ const hasOverride = Boolean(task.dailyResetTime);
+ return (
+
+
{textContent.taskDailyResetTimeLabel || "Heure quotidienne"}
+
+ onUpdateTask(task.id, (current) => {
+ const nextTask = { ...current };
+ if (event.target.value && event.target.value !== data.resetTime) nextTask.dailyResetTime = event.target.value;
+ else delete nextTask.dailyResetTime;
+ return nextTask;
+ })}
+ aria-label={`${textContent.taskDailyResetTimeLabel || "Heure quotidienne"} ${task.title}`}
+ />
+
+
+
+ );
+ }
+
+ if (task.type === "weekly") {
+ return (
+
+ );
+ }
+
+ return (
+
+
{textContent.taskResetLabel || "Réinitialisation"}
+
{textContent.noTaskResetLabel || "Aucun reset"}
+
+ );
+}
+
function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets, emptyLabel, addLabel, removeLabel, prerequisiteLabel, onAddLinkTarget, onRemoveLinkTarget, onSetPrerequisite }) {
const descendantIds = new Set();
function collectDescendants(taskId) {
@@ -965,6 +1014,17 @@ function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets
return (
{label}
+
{relations.length ? relations.map((relation) => (
@@ -984,17 +1044,6 @@ function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets
)) :
{emptyLabel}}
-
);
}
diff --git a/website/src/features/toolboxes/storage/toolboxStorage.js b/website/src/features/toolboxes/storage/toolboxStorage.js
index a96a400..ae73787 100644
--- a/website/src/features/toolboxes/storage/toolboxStorage.js
+++ b/website/src/features/toolboxes/storage/toolboxStorage.js
@@ -610,6 +610,7 @@ function normalizeTaskPlannerTask(task) {
checkedAt: normalizeTimestamp(task?.checkedAt)
};
if (category) normalized.category = category;
+ if (type === "daily" && isClockTimeString(task?.dailyResetTime)) normalized.dailyResetTime = task.dailyResetTime;
if (type === "weekly" && task?.weeklyResetDay !== undefined) normalized.weeklyResetDay = normalizeWeekDay(task.weeklyResetDay);
if (!normalized.checked) normalized.checkedAt = 0;
return normalized;
@@ -869,6 +870,7 @@ export function compactModuleDataForStorage(type, value) {
if (task.category) compactTask.category = task.category;
if (task.checked) compactTask.checked = true;
if (task.checked && task.checkedAt) compactTask.checkedAt = task.checkedAt;
+ if (task.type === "daily" && task.dailyResetTime && task.dailyResetTime !== normalized.resetTime) compactTask.dailyResetTime = task.dailyResetTime;
if (task.type === "weekly" && task.weeklyResetDay !== undefined) compactTask.weeklyResetDay = task.weeklyResetDay;
return compactTask;
});
diff --git a/website/src/styles/_toolboxes.scss b/website/src/styles/_toolboxes.scss
index 97ef861..d576b60 100644
--- a/website/src/styles/_toolboxes.scss
+++ b/website/src/styles/_toolboxes.scss
@@ -1948,14 +1948,14 @@ textarea:focus {
}
.task-planner-settings label,
-.task-planner-weekly-override {
+.task-planner-reset-override {
display: grid;
gap: 6px;
min-width: 0;
}
.task-planner-settings span,
-.task-planner-weekly-override span,
+.task-planner-reset-override > span,
.task-planner-relations > span {
color: var(--color-text-secondary);
font-size: var(--font-size-xs);
@@ -1967,9 +1967,11 @@ textarea:focus {
.task-planner-settings input,
.task-planner-add-form select,
.task-planner-type-select,
-.task-planner-weekly-override select,
+.task-planner-reset-override select,
+.task-planner-reset-override input,
.task-planner-relations select {
width: 100%;
+ min-height: 36px;
border: 1px solid var(--color-border);
border-radius: var(--radius-md);
background-color: rgba(15, 23, 42, 0.64);
@@ -1979,7 +1981,7 @@ textarea:focus {
.task-planner-settings select option,
.task-planner-add-form select option,
.task-planner-type-select option,
-.task-planner-weekly-override select option,
+.task-planner-reset-override select option,
.task-planner-relations select option {
background: #11182f;
color: var(--color-text-primary);
@@ -2349,8 +2351,8 @@ textarea:focus {
.task-planner-task-settings {
display: grid;
- grid-template-columns: minmax(130px, 0.32fr) minmax(0, 1fr);
- gap: 10px;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
+ gap: 12px;
align-items: start;
padding: 10px;
border: 1px solid rgba(165, 180, 252, 0.1);
@@ -2358,6 +2360,50 @@ textarea:focus {
background: rgba(7, 10, 24, 0.24);
}
+.task-planner-reset-control {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 34px;
+ gap: 8px;
+ align-items: center;
+}
+
+.task-planner-reset-control button {
+ display: inline-grid;
+ width: 34px;
+ min-width: 34px;
+ min-height: 36px;
+ place-items: center;
+ padding: 0;
+ border: 1px solid rgba(165, 180, 252, 0.12);
+ border-radius: var(--radius-md);
+ background: rgba(21, 26, 48, 0.58);
+ color: var(--color-text-secondary);
+}
+
+.task-planner-reset-control button:disabled {
+ opacity: 0.42;
+ cursor: not-allowed;
+}
+
+.task-planner-reset-control button .ui-icon {
+ width: 14px;
+ height: 14px;
+}
+
+.task-planner-reset-override.is-empty p {
+ display: grid;
+ min-height: 36px;
+ align-items: center;
+ margin: 0;
+ padding: 0 12px;
+ border: 1px solid rgba(165, 180, 252, 0.08);
+ border-radius: var(--radius-md);
+ background: rgba(15, 23, 42, 0.34);
+ color: var(--color-text-muted);
+ font-size: var(--font-size-sm);
+ font-weight: 800;
+}
+
.task-planner-relations {
display: grid;
grid-column: span 1;
@@ -2367,8 +2413,8 @@ textarea:focus {
.task-planner-relations > div {
display: grid;
- gap: 5px;
- min-height: 28px;
+ gap: 6px;
+ min-height: 36px;
}
.task-planner-relations em {
@@ -2434,10 +2480,16 @@ textarea:focus {
}
.task-planner-relations select {
- min-height: 34px;
+ min-height: 36px;
padding-inline: 10px;
}
+@media (max-width: 760px) {
+ .task-planner-task-settings {
+ grid-template-columns: 1fr;
+ }
+}
+
.combos-module {
display: grid;
gap: 12px;
@@ -3497,7 +3549,7 @@ button.combo-input-token.combo-value-grab .ui-icon {
.task-planner-settings select,
.task-planner-add-form select,
.task-planner-type-select,
-.task-planner-weekly-override select,
+.task-planner-reset-override select,
.task-planner-relations select {
appearance: none;
-webkit-appearance: none;
@@ -3527,8 +3579,8 @@ button.combo-input-token.combo-value-grab .ui-icon {
.task-planner-add-form select:focus,
.task-planner-type-select:hover,
.task-planner-type-select:focus,
-.task-planner-weekly-override select:hover,
-.task-planner-weekly-override select:focus,
+.task-planner-reset-override select:hover,
+.task-planner-reset-override select:focus,
.task-planner-relations select:hover,
.task-planner-relations select:focus {
border-color: rgba(196, 181, 253, 0.22);
@@ -4296,6 +4348,7 @@ button.combo-input-token.combo-value-grab .ui-icon {
.task-planner-settings-toggle:hover,
.task-planner-dropdown-button:hover,
.task-planner-settings-button:hover,
+.task-planner-reset-control button:hover:not(:disabled),
.task-planner-relation-chip button:hover,
.module-scroll-button:hover,
.tool-split-scroll-toggle:hover,