Improve task planner reset settings
All checks were successful
Deploy Sokko G / deploy (push) Successful in 6s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 6s
This commit is contained in:
parent
d54a93e1f4
commit
ee1c8afa9a
7 changed files with 180 additions and 63 deletions
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<div className="task-planner-task-settings">
|
||||
{task.type === "weekly" && (
|
||||
<label className="task-planner-weekly-override">
|
||||
<span>{textContent.taskWeeklyResetDayLabel || "Jour hebdo"}</span>
|
||||
<select
|
||||
value={task.weeklyResetDay ?? ""}
|
||||
onChange={(event) => onUpdateTask(task.id, (current) => {
|
||||
const nextTask = { ...current };
|
||||
if (event.target.value === "") {
|
||||
delete nextTask.weeklyResetDay;
|
||||
} else {
|
||||
nextTask.weeklyResetDay = Number(event.target.value);
|
||||
}
|
||||
return nextTask;
|
||||
})}
|
||||
>
|
||||
<option value="">{textContent.inheritWeeklyResetDay || "Global"}</option>
|
||||
{WEEK_DAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
<LinksEditor
|
||||
label={textContent.linksLabel || "Parent"}
|
||||
relations={relations}
|
||||
task={task}
|
||||
tasks={tasks}
|
||||
parentMap={parentMap}
|
||||
relationTargets={relationTargets}
|
||||
emptyLabel={textContent.emptyLinks || "Aucun parent"}
|
||||
addLabel={textContent.addLinkLabel || "Définir un parent"}
|
||||
removeLabel={textContent.removeRelationTitle || "Retirer"}
|
||||
prerequisiteLabel={textContent.prerequisiteCheckboxLabel || "Pré requis"}
|
||||
onAddLinkTarget={onAddLinkTarget}
|
||||
onRemoveLinkTarget={onRemoveLinkTarget}
|
||||
onSetPrerequisite={onSetPrerequisite}
|
||||
/>
|
||||
<TaskResetSettings task={task} data={data} textContent={textContent} onUpdateTask={onUpdateTask} />
|
||||
<LinksEditor
|
||||
label={textContent.linksLabel || "Parent"}
|
||||
relations={relations}
|
||||
task={task}
|
||||
tasks={tasks}
|
||||
parentMap={parentMap}
|
||||
relationTargets={relationTargets}
|
||||
emptyLabel={textContent.emptyLinks || "Aucun parent"}
|
||||
addLabel={textContent.addLinkLabel || "Définir un parent"}
|
||||
removeLabel={textContent.removeRelationTitle || "Retirer"}
|
||||
prerequisiteLabel={textContent.prerequisiteCheckboxLabel || "Pré requis"}
|
||||
onAddLinkTarget={onAddLinkTarget}
|
||||
onRemoveLinkTarget={onRemoveLinkTarget}
|
||||
onSetPrerequisite={onSetPrerequisite}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{children && <ul className="task-planner-list task-planner-child-list">{children}</ul>}
|
||||
|
|
@ -951,6 +933,73 @@ function TaskPlannerItem({
|
|||
);
|
||||
}
|
||||
|
||||
function TaskResetSettings({ task, data, textContent, onUpdateTask }) {
|
||||
if (task.type === "daily") {
|
||||
const hasOverride = Boolean(task.dailyResetTime);
|
||||
return (
|
||||
<div className="task-planner-reset-override">
|
||||
<span>{textContent.taskDailyResetTimeLabel || "Heure quotidienne"}</span>
|
||||
<div className="task-planner-reset-control">
|
||||
<input
|
||||
type="time"
|
||||
value={task.dailyResetTime || data.resetTime}
|
||||
onChange={(event) => 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}`}
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => onUpdateTask(task.id, (current) => {
|
||||
const nextTask = { ...current };
|
||||
delete nextTask.dailyResetTime;
|
||||
return nextTask;
|
||||
})}
|
||||
disabled={!hasOverride}
|
||||
title={textContent.inheritResetTime || "Utiliser l'heure globale"}
|
||||
aria-label={`${textContent.inheritResetTime || "Utiliser l'heure globale"} ${task.title}`}
|
||||
>
|
||||
<Icon name="rubber" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (task.type === "weekly") {
|
||||
return (
|
||||
<label className="task-planner-reset-override">
|
||||
<span>{textContent.taskWeeklyResetDayLabel || "Jour hebdo"}</span>
|
||||
<select
|
||||
value={task.weeklyResetDay ?? ""}
|
||||
onChange={(event) => onUpdateTask(task.id, (current) => {
|
||||
const nextTask = { ...current };
|
||||
if (event.target.value === "") {
|
||||
delete nextTask.weeklyResetDay;
|
||||
} else {
|
||||
nextTask.weeklyResetDay = Number(event.target.value);
|
||||
}
|
||||
return nextTask;
|
||||
})}
|
||||
>
|
||||
<option value="">{textContent.inheritWeeklyResetDay || "Global"}</option>
|
||||
{WEEK_DAYS.map((day) => <option key={day.value} value={day.value}>{day.label}</option>)}
|
||||
</select>
|
||||
</label>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="task-planner-reset-override is-empty">
|
||||
<span>{textContent.taskResetLabel || "Réinitialisation"}</span>
|
||||
<p>{textContent.noTaskResetLabel || "Aucun reset"}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="task-planner-relations">
|
||||
<span>{label}</span>
|
||||
<select
|
||||
value=""
|
||||
onChange={(event) => {
|
||||
onAddLinkTarget(task.id, event.target.value);
|
||||
event.target.value = "";
|
||||
}}
|
||||
aria-label={addLabel}
|
||||
>
|
||||
<option value="">{addLabel}</option>
|
||||
{availableTargets.map((target) => <option key={target.id} value={target.id}>{target.title}</option>)}
|
||||
</select>
|
||||
<div>
|
||||
{relations.length ? relations.map((relation) => (
|
||||
<div className="task-planner-relation-chip" key={relation.id}>
|
||||
|
|
@ -984,17 +1044,6 @@ function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets
|
|||
</div>
|
||||
)) : <em>{emptyLabel}</em>}
|
||||
</div>
|
||||
<select
|
||||
value=""
|
||||
onChange={(event) => {
|
||||
onAddLinkTarget(task.id, event.target.value);
|
||||
event.target.value = "";
|
||||
}}
|
||||
aria-label={addLabel}
|
||||
>
|
||||
<option value="">{addLabel}</option>
|
||||
{availableTargets.map((target) => <option key={target.id} value={target.id}>{target.title}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue