From a5ae0bac7e18e198b4442c6113dc31e013f71dfc Mon Sep 17 00:00:00 2001 From: Ulas Date: Sun, 5 Apr 2026 12:33:57 +0200 Subject: [PATCH] fix(ux): locale reload feedback, submit validation, dedup blur logic - Locale-Picker: disable select + fade before location.reload() (system mode) gives visual feedback before the page jumps - Extract _validateField() helper from wireBlurValidation to avoid duplication - Add validateAll(formContainer): validates all required fields on demand, marks inline errors, focuses first invalid field - tasks.js: call validateAll() at submit to catch untouched required fields --- public/components/modal.js | 36 ++++++++++++++++++++---- public/components/oikos-locale-picker.js | 5 +++- public/pages/tasks.js | 5 +++- 3 files changed, 38 insertions(+), 8 deletions(-) diff --git a/public/components/modal.js b/public/components/modal.js index b37b5ef..b010ace 100644 --- a/public/components/modal.js +++ b/public/components/modal.js @@ -484,21 +484,45 @@ export function confirmModal(message, { confirmLabel, danger = false } = {}) { // Inline Blur-Validierung // -------------------------------------------------------- +function _validateField(input) { + const group = input.closest('.form-field') ?? input.parentElement; + const hasValue = input.value.trim().length > 0; + group?.classList.toggle('form-field--error', !hasValue); + group?.classList.toggle('form-field--valid', hasValue); + return hasValue; +} + /** * Aktiviert Blur-Validierung für alle required-Inputs in einem Container. * @param {HTMLElement} formContainer */ export function wireBlurValidation(formContainer) { formContainer.querySelectorAll('input[required], select[required], textarea[required]').forEach((input) => { - input.addEventListener('blur', () => { - const group = input.closest('.form-field') ?? input.parentElement; - const hasValue = input.value.trim().length > 0; - group?.classList.toggle('form-field--error', !hasValue); - group?.classList.toggle('form-field--valid', hasValue); - }); + input.addEventListener('blur', () => _validateField(input)); }); } +/** + * Validiert alle required-Inputs sofort (z.B. beim Submit ohne vorangehendes Blur). + * Markiert Fehler inline und fokussiert das erste ungültige Feld. + * + * @param {HTMLElement} formContainer + * @returns {boolean} true wenn alle Felder valide sind + */ +export function validateAll(formContainer) { + let firstInvalid = null; + let allValid = true; + + formContainer.querySelectorAll('input[required], select[required], textarea[required]').forEach((input) => { + const valid = _validateField(input); + if (!valid && !firstInvalid) firstInvalid = input; + if (!valid) allValid = false; + }); + + if (firstInvalid) firstInvalid.focus(); + return allValid; +} + // -------------------------------------------------------- // Submit-Feedback (Checkmark + Shake) // -------------------------------------------------------- diff --git a/public/components/oikos-locale-picker.js b/public/components/oikos-locale-picker.js index 1a4eb29..394c07b 100644 --- a/public/components/oikos-locale-picker.js +++ b/public/components/oikos-locale-picker.js @@ -55,8 +55,11 @@ class OikosLocalePicker extends HTMLElement { select.addEventListener('change', () => { if (select.value === 'system') { + select.disabled = true; + select.style.opacity = '0.5'; localStorage.removeItem('oikos-locale'); - location.reload(); + // Kurze Verzögerung damit der Browser den disabled-Zustand rendert + setTimeout(() => location.reload(), 60); } else { setLocale(select.value); } diff --git a/public/pages/tasks.js b/public/pages/tasks.js index 53a1356..c799f3f 100644 --- a/public/pages/tasks.js +++ b/public/pages/tasks.js @@ -6,7 +6,7 @@ import { api } from '/api.js'; import { renderRRuleFields, bindRRuleEvents, getRRuleValues } from '/rrule-ui.js'; -import { openModal as openSharedModal, closeModal, wireBlurValidation, btnSuccess, btnError, promptModal, confirmModal } from '/components/modal.js'; +import { openModal as openSharedModal, closeModal, wireBlurValidation, validateAll, btnSuccess, btnError, promptModal, confirmModal } from '/components/modal.js'; import { stagger, vibrate } from '/utils/ux.js'; import { t, formatDate } from '/i18n.js'; import { esc } from '/utils/html.js'; @@ -430,6 +430,9 @@ async function handleFormSubmit(e, container) { const submitBtn = document.getElementById('task-submit-btn'); const taskId = document.getElementById('task-id').value; + // Alle required-Felder sofort validieren (auch unberührte) + if (!validateAll(form)) return; + errorEl.hidden = true; submitBtn.disabled = true; submitBtn.textContent = t('common.saving');