sokko-g/website/src/hooks/useToolboxAlerts.js
Shinuwa 992665f4ea
All checks were successful
Deploy Sokko G / deploy (push) Successful in 14s
Add calendar toolbox with weekly and monthly scheduling
2026-08-20 15:48:24 +02:00

99 lines
4.6 KiB
JavaScript

// Rôle : surveille globalement les alertes internes des outils timer et calendrier.
import { useEffect, useRef } from "react";
import { getCalendarAlertOffsets, getCurrentCalendarOccurrence, getNextCalendarAlertTime } from "../features/toolboxes/modules/calendarUtils.js";
import { getCountdownTargetMs } from "../features/toolboxes/modules/timerUtils.js";
import { moduleStorageKey } from "../features/toolboxes/storage/toolboxStorage.js";
function getVisibleToolboxIds(route, drawerGameId, links) {
const visibleIds = new Set();
const toolboxMatch = route.match(/^\/toolbox\/([^/]+)/);
if (toolboxMatch?.[1]) visibleIds.add(toolboxMatch[1]);
const drawerToolboxId = drawerGameId ? links[drawerGameId] : "";
if (drawerToolboxId) visibleIds.add(drawerToolboxId);
return visibleIds;
}
function isAlertAllowed(alertMode, toolboxId, visibleToolboxIds) {
if (alertMode === "off") return false;
if (alertMode === "visible") return visibleToolboxIds.has(toolboxId);
return alertMode === "site";
}
function formatAlertMessage(template, toolbox, label) {
return template
.replaceAll("{label}", label)
.replaceAll("{toolbox}", toolbox.name);
}
export function useToolboxAlerts({ toolboxes, moduleData, links, route, drawerGameId, normalizeTimerData, normalizeCalendarData, textContent, notify }) {
const previousTargetsRef = useRef(new Map());
const resumedCalendarEventsRef = useRef(new Set());
const lastTickRef = useRef(Date.now());
useEffect(() => {
const visibleToolboxIds = getVisibleToolboxIds(route, drawerGameId, links);
const timerAlertTemplate = textContent?.timer?.alertMessage || "Compte à rebours terminé : {label}";
const calendarAlertTemplate = textContent?.calendar?.alertMessage || "Événement à venir : {label}";
const calendarResumeTemplate = textContent?.calendar?.resumeAlertMessage || "Événement en cours : {label}";
function tick() {
const nowMs = Date.now();
const lastTickMs = lastTickRef.current;
const nextTargets = new Map();
toolboxes.forEach((toolbox) => {
toolbox.modules.forEach((module) => {
if (module.type === "timer") {
const data = normalizeTimerData(moduleData[moduleStorageKey(toolbox.id, module.id)]);
data.countdowns.forEach((countdown) => {
if (!isAlertAllowed(countdown.alertMode, toolbox.id, visibleToolboxIds)) return;
const key = `${toolbox.id}:${module.id}:${countdown.id}`;
const targetMs = getCountdownTargetMs(countdown, nowMs);
const previousTargetMs = previousTargetsRef.current.get(key) || targetMs;
nextTargets.set(key, targetMs);
if (!targetMs || previousTargetMs <= lastTickMs || previousTargetMs > nowMs) return;
notify(formatAlertMessage(timerAlertTemplate, toolbox, countdown.label));
});
}
if (module.type === "calendar") {
const data = normalizeCalendarData(moduleData[moduleStorageKey(toolbox.id, module.id)]);
data.events.forEach((event) => {
if (!isAlertAllowed(event.alertMode, toolbox.id, visibleToolboxIds)) return;
const key = `${toolbox.id}:${module.id}:${event.id}`;
getCalendarAlertOffsets(event).forEach((offset) => {
const alertKey = `${key}:alert:${offset}`;
const alertMs = getNextCalendarAlertTime({ ...event, alertOffsetMinutes: offset }, nowMs);
const previousAlertMs = previousTargetsRef.current.get(alertKey) || alertMs;
if (alertMs) nextTargets.set(alertKey, alertMs);
if (alertMs && previousAlertMs > lastTickMs && previousAlertMs <= nowMs) {
notify(formatAlertMessage(calendarAlertTemplate, toolbox, event.title));
}
});
const currentOccurrence = getCurrentCalendarOccurrence(event, nowMs);
if (!currentOccurrence) return;
const resumeKey = `${key}:${currentOccurrence.startMs}`;
if (resumedCalendarEventsRef.current.has(resumeKey)) return;
resumedCalendarEventsRef.current.add(resumeKey);
notify(formatAlertMessage(calendarResumeTemplate, toolbox, event.title));
});
}
});
});
previousTargetsRef.current = nextTargets;
lastTickRef.current = nowMs;
}
tick();
const intervalId = window.setInterval(tick, 500);
return () => window.clearInterval(intervalId);
}, [drawerGameId, links, moduleData, normalizeCalendarData, normalizeTimerData, notify, route, textContent, toolboxes]);
}