add task planner & alert improvments
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
All checks were successful
Deploy Sokko G / deploy (push) Successful in 7s
This commit is contained in:
parent
2e6d8a2c0c
commit
e306bfc5d6
15 changed files with 1547 additions and 4 deletions
|
|
@ -1,9 +1,13 @@
|
|||
// Rôle : normalise, compacte et sérialise les données toolbox persistées.
|
||||
const ID_PREFIXES = { tbx: "t", mod: "m", item: "i", section: "g", image: "s", link: "l", counter: "c", calc: "r", marker: "k", timer: "z" };
|
||||
const ID_PREFIXES = { tbx: "t", mod: "m", item: "i", section: "g", image: "s", link: "l", counter: "c", calc: "r", marker: "k", timer: "z", task: "a", relation: "e" };
|
||||
const ID_ALPHABET = "abcdefghijklmnopqrstuvwxyz0123456789";
|
||||
const TIMER_TABS = new Set(["stopwatch", "countdown"]);
|
||||
const COUNTDOWN_TYPES = new Set(["duration", "daily_time", "time_pattern", "interval"]);
|
||||
const TIMER_ALERT_MODES = new Set(["off", "visible", "site"]);
|
||||
const TASK_TYPES = new Set(["unique", "daily", "weekly"]);
|
||||
const TASK_SORT_MODES = new Set(["manual", "type"]);
|
||||
const DEFAULT_TASK_PLANNER_WEEKLY_RESET_DAY = 1;
|
||||
const DEFAULT_TASK_PLANNER_RESET_TIME = "00:00";
|
||||
const TOOLBOX_ICON_BASE = "/static/img/toolbox-icons/";
|
||||
const TOOLBOX_ICON_FILES = [
|
||||
"toolbox.png",
|
||||
|
|
@ -34,6 +38,7 @@ const DEFAULT_MODULE_TITLES = {
|
|||
counters: "Compteurs",
|
||||
calculator: "Calculateur",
|
||||
timer: "Timer",
|
||||
taskPlanner: "Planificateur de tâches",
|
||||
imageAnnotation: "Annotation d'images"
|
||||
};
|
||||
|
||||
|
|
@ -224,6 +229,22 @@ function isTimeString(value, allowWildcard = false) {
|
|||
});
|
||||
}
|
||||
|
||||
function isClockTimeString(value) {
|
||||
if (!/^\d{2}:\d{2}$/.test(String(value || ""))) return false;
|
||||
const [hours, minutes] = String(value).split(":").map(Number);
|
||||
return Number.isInteger(hours) && Number.isInteger(minutes) && hours >= 0 && hours <= 23 && minutes >= 0 && minutes <= 59;
|
||||
}
|
||||
|
||||
function normalizeWeekDay(value, fallback = DEFAULT_TASK_PLANNER_WEEKLY_RESET_DAY) {
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
return Number.isInteger(parsed) && parsed >= 0 && parsed <= 6 ? parsed : fallback;
|
||||
}
|
||||
|
||||
function normalizeTimestamp(value) {
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
||||
}
|
||||
|
||||
export function labelFromFileName(name) {
|
||||
if (!name || name === "image.png") return "";
|
||||
return name.replace(/\.[^.]+$/, "").trim();
|
||||
|
|
@ -300,6 +321,65 @@ export function normalizeTimerData(data) {
|
|||
};
|
||||
}
|
||||
|
||||
function normalizeTaskPlannerTask(task) {
|
||||
const title = String(task?.title || "").trim() || "Tâche";
|
||||
const type = TASK_TYPES.has(task?.type) ? task.type : "unique";
|
||||
const normalized = {
|
||||
id: task?.id || uid("task"),
|
||||
title,
|
||||
description: String(task?.description || "").trim(),
|
||||
type,
|
||||
checked: task?.checked === true,
|
||||
checkedAt: normalizeTimestamp(task?.checkedAt)
|
||||
};
|
||||
if (type === "weekly" && task?.weeklyResetDay !== undefined) normalized.weeklyResetDay = normalizeWeekDay(task.weeklyResetDay);
|
||||
if (!normalized.checked) normalized.checkedAt = 0;
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function normalizeTaskPlannerRelations(data, taskIds) {
|
||||
const seen = new Set();
|
||||
const seenChildren = new Set();
|
||||
const legacyDependencies = new Set((Array.isArray(data?.dependencies) ? data.dependencies : [])
|
||||
.map((relation) => `${relation?.fromTaskId || ""}:${relation?.toTaskId || ""}`));
|
||||
const sourceRelations = Array.isArray(data?.relations)
|
||||
? data.relations
|
||||
: [
|
||||
...(Array.isArray(data?.links) ? data.links : []),
|
||||
...(Array.isArray(data?.dependencies) ? data.dependencies : []).map((relation) => ({ ...relation, dependency: true }))
|
||||
];
|
||||
return sourceRelations
|
||||
.map((relation) => {
|
||||
const fromTaskId = String(relation?.fromTaskId || "");
|
||||
const toTaskId = String(relation?.toTaskId || "");
|
||||
if (!taskIds.has(fromTaskId) || !taskIds.has(toTaskId) || fromTaskId === toTaskId) return null;
|
||||
const key = `${fromTaskId}:${toTaskId}`;
|
||||
if (seen.has(key) || seenChildren.has(fromTaskId)) return null;
|
||||
seen.add(key);
|
||||
seenChildren.add(fromTaskId);
|
||||
return {
|
||||
id: relation?.id || uid("relation"),
|
||||
fromTaskId,
|
||||
toTaskId,
|
||||
dependency: relation?.dependency === true || legacyDependencies.has(key)
|
||||
};
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function normalizeTaskPlannerData(data) {
|
||||
const tasks = (Array.isArray(data?.tasks) ? data.tasks : []).map(normalizeTaskPlannerTask).filter(Boolean);
|
||||
const taskIds = new Set(tasks.map((task) => task.id));
|
||||
return {
|
||||
sortMode: TASK_SORT_MODES.has(data?.sortMode) ? data.sortMode : "manual",
|
||||
weeklyResetDay: normalizeWeekDay(data?.weeklyResetDay),
|
||||
resetTime: isClockTimeString(data?.resetTime) ? data.resetTime : DEFAULT_TASK_PLANNER_RESET_TIME,
|
||||
lastResetAt: normalizeTimestamp(data?.lastResetAt),
|
||||
tasks,
|
||||
relations: normalizeTaskPlannerRelations(data, taskIds)
|
||||
};
|
||||
}
|
||||
|
||||
function clampPercent(value) {
|
||||
const parsed = Number(value);
|
||||
if (!Number.isFinite(parsed)) return 0;
|
||||
|
|
@ -433,6 +513,40 @@ export function compactModuleDataForStorage(type, value) {
|
|||
}
|
||||
return Object.keys(compact).length ? compact : null;
|
||||
}
|
||||
if (type === "taskPlanner") {
|
||||
const normalized = normalizeTaskPlannerData(value);
|
||||
const compact = {};
|
||||
if (normalized.sortMode !== "manual") compact.sortMode = normalized.sortMode;
|
||||
if (normalized.weeklyResetDay !== DEFAULT_TASK_PLANNER_WEEKLY_RESET_DAY) compact.weeklyResetDay = normalized.weeklyResetDay;
|
||||
if (normalized.resetTime !== DEFAULT_TASK_PLANNER_RESET_TIME) compact.resetTime = normalized.resetTime;
|
||||
if (normalized.lastResetAt) compact.lastResetAt = normalized.lastResetAt;
|
||||
if (normalized.tasks.length) {
|
||||
compact.tasks = normalized.tasks.map((task) => {
|
||||
const compactTask = {
|
||||
id: task.id,
|
||||
title: task.title,
|
||||
type: task.type
|
||||
};
|
||||
if (task.description) compactTask.description = task.description;
|
||||
if (task.checked) compactTask.checked = true;
|
||||
if (task.checked && task.checkedAt) compactTask.checkedAt = task.checkedAt;
|
||||
if (task.type === "weekly" && task.weeklyResetDay !== undefined) compactTask.weeklyResetDay = task.weeklyResetDay;
|
||||
return compactTask;
|
||||
});
|
||||
}
|
||||
if (normalized.relations.length) {
|
||||
compact.relations = normalized.relations.map((relation) => {
|
||||
const compactRelation = {
|
||||
id: relation.id,
|
||||
fromTaskId: relation.fromTaskId,
|
||||
toTaskId: relation.toTaskId
|
||||
};
|
||||
if (relation.dependency) compactRelation.dependency = true;
|
||||
return compactRelation;
|
||||
});
|
||||
}
|
||||
return Object.keys(compact).length ? compact : null;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
|
|
@ -511,6 +625,24 @@ function remapModuleDataForExport(type, data, nextId) {
|
|||
return remapped;
|
||||
}
|
||||
|
||||
if (type === "taskPlanner") {
|
||||
const taskIdMap = new Map();
|
||||
compact.tasks?.forEach((task) => taskIdMap.set(task.id, nextId("task")));
|
||||
const remapped = { ...compact };
|
||||
if (compact.tasks) remapped.tasks = compact.tasks.map((task) => ({ ...task, id: taskIdMap.get(task.id) }));
|
||||
if (compact.relations) {
|
||||
remapped.relations = compact.relations
|
||||
.map((relation) => ({
|
||||
...relation,
|
||||
id: nextId("relation"),
|
||||
fromTaskId: taskIdMap.get(relation.fromTaskId),
|
||||
toTaskId: taskIdMap.get(relation.toTaskId)
|
||||
}))
|
||||
.filter((relation) => relation.fromTaskId && relation.toTaskId);
|
||||
}
|
||||
return remapped;
|
||||
}
|
||||
|
||||
return compact;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue