Highlight task planner items near reset
All checks were successful
Deploy Sokko G / deploy (push) Successful in 14s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 14s
This commit is contained in:
parent
49ba0c4cb2
commit
e4cbe3e087
5 changed files with 80 additions and 2 deletions
|
|
@ -3,7 +3,7 @@ import { readFile } from "node:fs/promises";
|
||||||
import { test } from "node:test";
|
import { test } from "node:test";
|
||||||
import assert from "node:assert/strict";
|
import assert from "node:assert/strict";
|
||||||
import { SOCKET_SHAPES, formatValue, getSocketItemName, setItemCategory } from "../website/src/features/toolboxes/modules/equipmentPlannerOptions.js";
|
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";
|
import { applyDueResets, getTaskParentMap, getTaskTreeIds, isTaskResetSoon, moveTask } from "../website/src/features/toolboxes/modules/taskPlannerUtils.js";
|
||||||
import { createInput, getInputLabel, normalizeCustomKey, parseHoldSeconds } from "../website/src/features/toolboxes/modules/combosUtils.js";
|
import { createInput, getInputLabel, normalizeCustomKey, parseHoldSeconds } from "../website/src/features/toolboxes/modules/combosUtils.js";
|
||||||
import { getBoundsFromStartAndSize, getSelectionBounds, moveSelectionCells, parseTsvBlock, pasteCellBlock } from "../website/src/features/toolboxes/modules/tableUtils.js";
|
import { getBoundsFromStartAndSize, getSelectionBounds, moveSelectionCells, parseTsvBlock, pasteCellBlock } from "../website/src/features/toolboxes/modules/tableUtils.js";
|
||||||
import { canToggleAutoRefresh, getCountdownValidationError, isAlertGuarded } from "../website/src/features/toolboxes/modules/timerOptions.js";
|
import { canToggleAutoRefresh, getCountdownValidationError, isAlertGuarded } from "../website/src/features/toolboxes/modules/timerOptions.js";
|
||||||
|
|
@ -471,6 +471,11 @@ test("task planner helpers keep hierarchy, reorder and reset behavior stable", (
|
||||||
lastResetAt: Date.UTC(2026, 0, 1, 5)
|
lastResetAt: Date.UTC(2026, 0, 1, 5)
|
||||||
}, Date.UTC(2026, 0, 2, 8));
|
}, Date.UTC(2026, 0, 2, 8));
|
||||||
assert.equal(resetData.tasks.find((task) => task.id === "a").checked, false);
|
assert.equal(resetData.tasks.find((task) => task.id === "a").checked, false);
|
||||||
|
assert.equal(isTaskResetSoon({ id: "d", title: "D", type: "daily", checked: false }, resetData, new Date(2026, 0, 2, 0, 1).getTime()), true);
|
||||||
|
assert.equal(isTaskResetSoon({ id: "e", title: "E", type: "daily", checked: false }, resetData, new Date(2026, 0, 1, 20, 0).getTime()), false);
|
||||||
|
assert.equal(isTaskResetSoon({ id: "f", title: "F", type: "weekly", checked: false, weeklyResetDay: 4 }, resetData, new Date(2026, 0, 6, 7, 0).getTime()), true);
|
||||||
|
assert.equal(isTaskResetSoon({ id: "g", title: "G", type: "weekly", checked: true, weeklyResetDay: 4 }, resetData, new Date(2026, 0, 6, 7, 0).getTime()), false);
|
||||||
|
assert.equal(isTaskResetSoon({ id: "h", title: "H", type: "unique", checked: false }, resetData, new Date(2026, 0, 6, 7, 0).getTime()), false);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("equipment planner options expose stable formatting and socket helpers", () => {
|
test("equipment planner options expose stable formatting and socket helpers", () => {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
getTaskTitle,
|
getTaskTitle,
|
||||||
getTaskTypeLabel,
|
getTaskTypeLabel,
|
||||||
getTasksForParent,
|
getTasksForParent,
|
||||||
|
isTaskResetSoon,
|
||||||
isTaskTreeComplete
|
isTaskTreeComplete
|
||||||
} from "./taskPlannerUtils.js";
|
} from "./taskPlannerUtils.js";
|
||||||
|
|
||||||
|
|
@ -23,6 +24,7 @@ export function TaskPlannerBranch({
|
||||||
parentMap,
|
parentMap,
|
||||||
tasks,
|
tasks,
|
||||||
data,
|
data,
|
||||||
|
nowMs,
|
||||||
reorder,
|
reorder,
|
||||||
textContent,
|
textContent,
|
||||||
openDescriptions,
|
openDescriptions,
|
||||||
|
|
@ -60,6 +62,7 @@ export function TaskPlannerBranch({
|
||||||
parentMap={parentMap}
|
parentMap={parentMap}
|
||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
data={data}
|
data={data}
|
||||||
|
nowMs={nowMs}
|
||||||
reorder={reorder}
|
reorder={reorder}
|
||||||
textContent={textContent}
|
textContent={textContent}
|
||||||
openDescriptions={openDescriptions}
|
openDescriptions={openDescriptions}
|
||||||
|
|
@ -106,6 +109,7 @@ export function TaskPlannerBranch({
|
||||||
task={task}
|
task={task}
|
||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
data={data}
|
data={data}
|
||||||
|
nowMs={nowMs}
|
||||||
parentMap={parentMap}
|
parentMap={parentMap}
|
||||||
parentId={parentId}
|
parentId={parentId}
|
||||||
reorder={reorder}
|
reorder={reorder}
|
||||||
|
|
@ -208,6 +212,7 @@ function TaskPlannerItem({
|
||||||
task,
|
task,
|
||||||
tasks,
|
tasks,
|
||||||
data,
|
data,
|
||||||
|
nowMs,
|
||||||
parentMap,
|
parentMap,
|
||||||
parentId,
|
parentId,
|
||||||
reorder,
|
reorder,
|
||||||
|
|
@ -237,6 +242,7 @@ function TaskPlannerItem({
|
||||||
const className = [
|
const className = [
|
||||||
"task-planner-item",
|
"task-planner-item",
|
||||||
task.checked ? "is-complete" : "",
|
task.checked ? "is-complete" : "",
|
||||||
|
isTaskResetSoon(task, data, nowMs) ? "is-reset-soon" : "",
|
||||||
missingPrerequisites.length ? "has-missing-prerequisite" : "",
|
missingPrerequisites.length ? "has-missing-prerequisite" : "",
|
||||||
reorder.isItemDragging(task.id) ? "is-dragging" : "",
|
reorder.isItemDragging(task.id) ? "is-dragging" : "",
|
||||||
reorder.isItemDropTarget(task.id) ? "is-drop-target" : "",
|
reorder.isItemDropTarget(task.id) ? "is-drop-target" : "",
|
||||||
|
|
@ -519,4 +525,3 @@ function LinksEditor({ label, relations, task, tasks, parentMap, relationTargets
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -354,6 +354,7 @@ export function TaskPlannerModule({ toolboxId, moduleId, context, editing }) {
|
||||||
parentMap={parentMap}
|
parentMap={parentMap}
|
||||||
tasks={data.tasks}
|
tasks={data.tasks}
|
||||||
data={data}
|
data={data}
|
||||||
|
nowMs={nowMs}
|
||||||
reorder={reorder}
|
reorder={reorder}
|
||||||
textContent={textContent}
|
textContent={textContent}
|
||||||
openDescriptions={openDescriptions}
|
openDescriptions={openDescriptions}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
// Rôle : regroupe les constantes et helpers purs de l'outil task planner.
|
// Rôle : regroupe les constantes et helpers purs de l'outil task planner.
|
||||||
export const TASK_TYPES = ["daily", "weekly", "unique"];
|
export const TASK_TYPES = ["daily", "weekly", "unique"];
|
||||||
|
const DAILY_RESET_SOON_MS = 6 * 60 * 60 * 1000;
|
||||||
|
const WEEKLY_RESET_SOON_MS = 48 * 60 * 60 * 1000;
|
||||||
|
|
||||||
export const WEEK_DAYS = [
|
export const WEEK_DAYS = [
|
||||||
{ value: 1, label: "Lundi" },
|
{ value: 1, label: "Lundi" },
|
||||||
{ value: 2, label: "Mardi" },
|
{ value: 2, label: "Mardi" },
|
||||||
|
|
@ -42,6 +45,39 @@ export function getLatestWeeklyReset(now, resetTime, weekDay) {
|
||||||
return reset.getTime();
|
return reset.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNextDailyReset(now, resetTime) {
|
||||||
|
const { hours, minutes } = parseResetTime(resetTime);
|
||||||
|
const reset = new Date(now);
|
||||||
|
reset.setHours(hours, minutes, 0, 0);
|
||||||
|
if (reset.getTime() <= now) reset.setDate(reset.getDate() + 1);
|
||||||
|
return reset.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getNextWeeklyReset(now, resetTime, weekDay) {
|
||||||
|
const { hours, minutes } = parseResetTime(resetTime);
|
||||||
|
const reset = new Date(now);
|
||||||
|
reset.setHours(hours, minutes, 0, 0);
|
||||||
|
const dayDiff = (weekDay - reset.getDay() + 7) % 7;
|
||||||
|
reset.setDate(reset.getDate() + dayDiff);
|
||||||
|
if (reset.getTime() <= now) reset.setDate(reset.getDate() + 7);
|
||||||
|
return reset.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTaskResetTimeRemainingMs(task, data, now = Date.now()) {
|
||||||
|
if (task.checked || task.type === "unique") return null;
|
||||||
|
const nextResetAt = task.type === "daily"
|
||||||
|
? getNextDailyReset(now, task.dailyResetTime || data.resetTime)
|
||||||
|
: getNextWeeklyReset(now, data.resetTime, task.weeklyResetDay ?? data.weeklyResetDay);
|
||||||
|
return Math.max(0, nextResetAt - now);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isTaskResetSoon(task, data, now = Date.now()) {
|
||||||
|
const remainingMs = getTaskResetTimeRemainingMs(task, data, now);
|
||||||
|
if (remainingMs == null) return false;
|
||||||
|
const threshold = task.type === "daily" ? DAILY_RESET_SOON_MS : WEEKLY_RESET_SOON_MS;
|
||||||
|
return remainingMs <= threshold;
|
||||||
|
}
|
||||||
|
|
||||||
export function applyDueResets(data, now = Date.now()) {
|
export function applyDueResets(data, now = Date.now()) {
|
||||||
let latestResetAt = data.lastResetAt;
|
let latestResetAt = data.lastResetAt;
|
||||||
let changed = false;
|
let changed = false;
|
||||||
|
|
|
||||||
|
|
@ -360,6 +360,37 @@
|
||||||
rgba(5, 7, 17, 0.36);
|
rgba(5, 7, 17, 0.36);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.task-planner-item.is-reset-soon:not(.is-complete) {
|
||||||
|
border-color: rgba(34, 211, 238, 0.34);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(34, 211, 238, 0.07),
|
||||||
|
0 0 0 rgba(34, 211, 238, 0);
|
||||||
|
animation: task-planner-reset-soon-pulse 2600ms var(--ease-standard) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes task-planner-reset-soon-pulse {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
border-color: rgba(34, 211, 238, 0.26);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(34, 211, 238, 0.05),
|
||||||
|
0 0 0 rgba(34, 211, 238, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
border-color: rgba(34, 211, 238, 0.44);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 0 0 1px rgba(34, 211, 238, 0.1),
|
||||||
|
0 0 12px rgba(34, 211, 238, 0.07);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.task-planner-item.is-reset-soon:not(.is-complete) {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.task-planner-line {
|
.task-planner-line {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 30px 24px minmax(0, 1fr) 134px auto auto;
|
grid-template-columns: 30px 24px minmax(0, 1fr) 134px auto auto;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue