diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md
index 8278c30..cdcaf58 100644
--- a/DESIGN_SYSTEM.md
+++ b/DESIGN_SYSTEM.md
@@ -173,6 +173,7 @@ Regles UI :
- les boutons de controle utilisent les icones `play-circle`, `pause-circle`, `record-circle`, `stop-circle`, `refresh`, `sort-time`, `scrollable`.
- pour `Heure precise` et `Intervalle`, le refresh inline est un toggle compact de repetition automatique ; il est bloque si la frequence est inferieure a 5 minutes.
- le formulaire `Intervalle` affiche deux champs `hh:mm:ss` compacts : duree de repetition et depart optionnel ; un depart vide signifie `Maintenant`.
+- les invalidations du formulaire countdown s'affichent en notice inline rouge compacte, portée par `useDraftForm`.
Alertes timer :
diff --git a/tests/helpers/data-validation.mjs b/tests/helpers/data-validation.mjs
index 4eea9fe..03053ad 100644
--- a/tests/helpers/data-validation.mjs
+++ b/tests/helpers/data-validation.mjs
@@ -265,6 +265,8 @@ export function validateSiteContent(site) {
"toolboxes.modules.timer.intervalStartTimeOption",
"toolboxes.modules.timer.intervalStartTimeLabel",
"toolboxes.modules.timer.addCountdownButton",
+ "toolboxes.modules.timer.requiredError",
+ "toolboxes.modules.timer.invalidError",
"toolboxes.modules.timer.defaultCountdownLabel",
"toolboxes.modules.timer.finishedLabel",
"toolboxes.modules.timer.targetLabel",
diff --git a/website/public/data/site.json b/website/public/data/site.json
index 363bca8..4752927 100644
--- a/website/public/data/site.json
+++ b/website/public/data/site.json
@@ -430,6 +430,8 @@
"intervalStartTimeOption": "Heure précise",
"intervalStartTimeLabel": "Départ",
"addCountdownButton": "Ajouter",
+ "requiredError": "Valeur requise.",
+ "invalidError": "Valeur invalide.",
"defaultCountdownLabel": "Timer",
"finishedLabel": "Terminé",
"targetLabel": "Suivant",
diff --git a/website/src/features/toolboxes/modules/TimerModule.jsx b/website/src/features/toolboxes/modules/TimerModule.jsx
index 6166b43..10bcb1c 100644
--- a/website/src/features/toolboxes/modules/TimerModule.jsx
+++ b/website/src/features/toolboxes/modules/TimerModule.jsx
@@ -94,6 +94,27 @@ function hasTimePartValue(parts) {
return Boolean(String(parts?.hours || "").trim() || String(parts?.minutes || "").trim() || String(parts?.seconds || "").trim());
}
+function getCountdownValidationError(type, draft, textContent) {
+ const requiredError = textContent.requiredError || "Valeur requise.";
+ const invalidError = textContent.invalidError || "Valeur invalide.";
+ if (type === "duration" && timePartsToDurationMs(draft.duration) <= 0) {
+ return requiredError;
+ }
+ if (type === "daily_time" && !timePartsToString(draft.time)) {
+ return invalidError;
+ }
+ if (type === "time_pattern") {
+ if (!hasTimePartValue(draft.pattern)) return requiredError;
+ if (!timePartsToString(draft.pattern, { allowWildcard: true })) return invalidError;
+ }
+ if (type === "interval") {
+ const startTimeHasValue = hasTimePartValue(draft.intervalStartTime);
+ if (timePartsToDurationMs(draft.interval) <= 0) return requiredError;
+ if (startTimeHasValue && !timePartsToString(draft.intervalStartTime)) return invalidError;
+ }
+ return "";
+}
+
export function TimerModule({ toolboxId, moduleId, context }) {
const data = context.normalizeTimerData(context.getModuleData(toolboxId, moduleId, { activeTab: "stopwatch" }));
const textContent = context.moduleText?.timer || {};
@@ -194,6 +215,8 @@ export function TimerModule({ toolboxId, moduleId, context }) {
function createCountdown(event) {
countdownDraft.handleSubmit(event, (draft, { updateValues }) => {
const type = COUNTDOWN_TYPES.includes(draft.type) ? draft.type : "duration";
+ const validationError = getCountdownValidationError(type, draft, textContent);
+ if (validationError) return validationError;
const label = draft.label.trim() || textContent.defaultCountdownLabel || "Timer";
const now = getNowMs();
let countdown = null;
@@ -335,6 +358,7 @@ export function TimerModule({ toolboxId, moduleId, context }) {