keepsafe rework & alert ajustments
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
68005ef918
commit
2e6d8a2c0c
11 changed files with 129 additions and 45 deletions
|
|
@ -11,6 +11,7 @@ import {
|
|||
getDailyTargetMs,
|
||||
getNowMs,
|
||||
getTimePatternTargetMs,
|
||||
getTimePatternRecurrenceMs,
|
||||
timePartsToDurationMs,
|
||||
timePartsToString
|
||||
} from "./timerUtils.js";
|
||||
|
|
@ -27,6 +28,17 @@ const ALERT_MODES = [
|
|||
{ mode: "site", icon: "sound-max" }
|
||||
];
|
||||
const ALERT_MIN_DELAY_MS = 5 * 60 * 1000;
|
||||
const AUTO_REFRESH_COUNTDOWN_TYPES = new Set(["time_pattern", "interval"]);
|
||||
|
||||
function isAlertGuarded(countdown) {
|
||||
if (countdown?.type === "interval") return Number(countdown.intervalMs) < ALERT_MIN_DELAY_MS;
|
||||
if (countdown?.type === "time_pattern") return getTimePatternRecurrenceMs(countdown.pattern) < ALERT_MIN_DELAY_MS;
|
||||
return false;
|
||||
}
|
||||
|
||||
function getAlertGuardMessage(textContent) {
|
||||
return textContent.alertTooSoonTitle || "Alerte indisponible pour une répétition inférieure à 5 minutes";
|
||||
}
|
||||
|
||||
function TimerValue({ ms, compact = false, showCentiseconds = true }) {
|
||||
if (!showCentiseconds) return <span className="timer-value">{formatDuration(ms)}</span>;
|
||||
|
|
@ -204,9 +216,8 @@ export function TimerModule({ toolboxId, moduleId, context }) {
|
|||
|
||||
function setCountdownAlertMode(countdownId, alertMode) {
|
||||
const countdown = data.countdowns.find((item) => item.id === countdownId);
|
||||
const targetMs = countdown ? getCountdownTargetMs(countdown, nowMs) : 0;
|
||||
if (alertMode !== "off" && targetMs && targetMs - nowMs < ALERT_MIN_DELAY_MS) {
|
||||
context.notify?.(textContent.alertTooSoonTitle || "Alerte indisponible à moins de 5 minutes");
|
||||
if (alertMode !== "off" && AUTO_REFRESH_COUNTDOWN_TYPES.has(countdown?.type) && isAlertGuarded(countdown)) {
|
||||
context.notify?.(getAlertGuardMessage(textContent));
|
||||
return;
|
||||
}
|
||||
save({
|
||||
|
|
@ -303,7 +314,7 @@ function TimerAlertModeSwitch({ mode, blocked, textContent, onChange }) {
|
|||
visible: textContent.alertVisibleTitle || "Alerte quand la toolbox est affichée",
|
||||
site: textContent.alertSiteTitle || "Alerte sur tout le site"
|
||||
};
|
||||
const blockedTitle = textContent.alertTooSoonTitle || "Alerte indisponible à moins de 5 minutes";
|
||||
const blockedTitle = getAlertGuardMessage(textContent);
|
||||
|
||||
return (
|
||||
<div
|
||||
|
|
@ -316,11 +327,11 @@ function TimerAlertModeSwitch({ mode, blocked, textContent, onChange }) {
|
|||
>
|
||||
{ALERT_MODES.map((option) => {
|
||||
const label = labels[option.mode];
|
||||
const disabled = blocked && option.mode !== "off";
|
||||
const disabled = Boolean(blocked);
|
||||
return (
|
||||
<button
|
||||
key={option.mode}
|
||||
className={`timer-alert-mode-button ${mode === option.mode ? "active" : ""}`}
|
||||
className={`timer-alert-mode-button ${!blocked && mode === option.mode ? "active" : ""}`}
|
||||
type="button"
|
||||
onClick={() => onChange(option.mode)}
|
||||
disabled={disabled}
|
||||
|
|
@ -368,31 +379,31 @@ function CountdownControls({ textContent, form, onChange, onSubmit }) {
|
|||
<select value={form.type} onChange={(event) => onChange({ type: event.target.value })}>
|
||||
<option value="duration">{textContent.durationType || "Durée"}</option>
|
||||
<option value="daily_time">{textContent.dailyTimeType || "Heure précise"}</option>
|
||||
<option value="time_pattern">{textContent.timePatternType || "Horaire spécifique"}</option>
|
||||
<option value="time_pattern">{textContent.timePatternType || "Pattern horaire"}</option>
|
||||
<option value="interval">{textContent.intervalType || "Intervalle"}</option>
|
||||
</select>
|
||||
</label>
|
||||
{form.type === "duration" && (
|
||||
<label>
|
||||
<span>{textContent.durationLabel || "Durée"}</span>
|
||||
<span className="sr-only">{textContent.durationLabel || "Durée"}</span>
|
||||
<TimePartsInput value={form.duration} onChange={(duration) => onChange({ duration })} />
|
||||
</label>
|
||||
)}
|
||||
{form.type === "daily_time" && (
|
||||
<label>
|
||||
<span>{textContent.dailyTimeLabel || "Heure"}</span>
|
||||
<span className="sr-only">{textContent.dailyTimeLabel || "Heure"}</span>
|
||||
<TimePartsInput value={form.time} onChange={(time) => onChange({ time })} />
|
||||
</label>
|
||||
)}
|
||||
{form.type === "time_pattern" && (
|
||||
<label>
|
||||
<span>{textContent.timePatternLabel || "Heure / minute / seconde"}</span>
|
||||
<span className="sr-only">{textContent.timePatternLabel || "Heure / minute / seconde"}</span>
|
||||
<TimePartsInput value={form.pattern} onChange={(pattern) => onChange({ pattern })} allowWildcard />
|
||||
</label>
|
||||
)}
|
||||
{form.type === "interval" && (
|
||||
<label>
|
||||
<span>{textContent.intervalLabel || "Toutes les"}</span>
|
||||
<span className="sr-only">{textContent.intervalLabel || "Toutes les"}</span>
|
||||
<TimePartsInput value={form.interval} onChange={(interval) => onChange({ interval })} />
|
||||
</label>
|
||||
)}
|
||||
|
|
@ -512,7 +523,7 @@ function CountdownList({ countdowns, nowMs, textContent, onRename, onAlertModeCh
|
|||
)}
|
||||
<TimerAlertModeSwitch
|
||||
mode={countdown.alertMode}
|
||||
blocked={countdown.targetMs && countdown.targetMs - nowMs < ALERT_MIN_DELAY_MS}
|
||||
blocked={AUTO_REFRESH_COUNTDOWN_TYPES.has(countdown.type) && isAlertGuarded(countdown)}
|
||||
textContent={textContent}
|
||||
onChange={(alertMode) => onAlertModeChange(countdown.id, alertMode)}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -81,17 +81,31 @@ export function getTimePatternTargetMs(pattern, nowMs) {
|
|||
const now = new Date(nowMs);
|
||||
const candidate = new Date(nowMs);
|
||||
candidate.setMilliseconds(0);
|
||||
const hasHour = parsed.hours !== "X";
|
||||
const hasMinute = parsed.minutes !== "X";
|
||||
|
||||
for (let offset = 1; offset <= 86400; offset += 1) {
|
||||
candidate.setTime(now.getTime() + offset * 1000);
|
||||
const hours = parsed.hours === "X" || Number(parsed.hours) === candidate.getHours();
|
||||
const minutes = parsed.minutes === "X" || Number(parsed.minutes) === candidate.getMinutes();
|
||||
const seconds = parsed.seconds === "X" || Number(parsed.seconds) === candidate.getSeconds();
|
||||
const minutes = parsed.minutes === "X"
|
||||
? (!hasHour || candidate.getMinutes() === 0)
|
||||
: Number(parsed.minutes) === candidate.getMinutes();
|
||||
const seconds = parsed.seconds === "X"
|
||||
? (!hasHour && !hasMinute) || candidate.getSeconds() === 0
|
||||
: Number(parsed.seconds) === candidate.getSeconds();
|
||||
if (hours && minutes && seconds) return candidate.getTime();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
export function getTimePatternRecurrenceMs(pattern) {
|
||||
const baseMs = new Date(2026, 0, 1, 0, 0, 0, 0).getTime();
|
||||
const firstTargetMs = getTimePatternTargetMs(pattern, baseMs);
|
||||
if (!firstTargetMs) return 0;
|
||||
const secondTargetMs = getTimePatternTargetMs(pattern, firstTargetMs);
|
||||
return secondTargetMs ? secondTargetMs - firstTargetMs : 0;
|
||||
}
|
||||
|
||||
export function getIntervalTargetMs(intervalMs, anchorAt, nowMs) {
|
||||
const safeIntervalMs = Math.max(1000, Number(intervalMs) || 0);
|
||||
const safeAnchor = Number.isFinite(anchorAt) && anchorAt > 0 ? anchorAt : nowMs;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue