/* ============================================================ BAN YARO — UI Helpers Alle UI-Interaktionen an einem Ort. Toast, Modal, Loading, Confirm — einmal gebaut, überall nutzbar. ============================================================ */ const UI = (() => { // ---------------------------------------------------------- // TOAST // ---------------------------------------------------------- const toast = (() => { const container = () => document.getElementById('toast-container'); function show(message, type = 'default', duration = 3500) { const el = document.createElement('div'); el.className = `toast${type !== 'default' ? ` toast-${type}` : ''}`; const icon = { success: '✓', danger: '✕', warning: '⚠', info: 'ℹ' }[type] || ''; el.innerHTML = icon ? `${icon}${message}` : `${message}`; container().appendChild(el); const timer = setTimeout(() => remove(el), duration); el.addEventListener('click', () => { clearTimeout(timer); remove(el); }); } function remove(el) { el.classList.add('removing'); const fallback = setTimeout(() => el.remove(), 300); el.addEventListener('animationend', () => { clearTimeout(fallback); el.remove(); }, { once: true }); } return { show, success: (msg, dur) => show(msg, 'success', dur), error: (msg, dur) => show(msg, 'danger', dur || 5000), warning: (msg, dur) => show(msg, 'warning', dur), info: (msg, dur) => show(msg, 'info', dur), }; })(); // ---------------------------------------------------------- // MODAL // ---------------------------------------------------------- const modal = (() => { let _current = null; function open({ title, body, footer, onClose } = {}) { close(); // vorheriges schließen const overlay = document.createElement('div'); overlay.className = 'modal-overlay'; overlay.innerHTML = `
${message}
`, footer: ` `, onClose: () => resolve(false), }); m.parentElement.querySelector('#modal-cancel').addEventListener('click', () => { resolve(false); close(); }); m.parentElement.querySelector('#modal-confirm').addEventListener('click', () => { resolve(true); close(); }); }); } return { open, close, confirm }; })(); // ---------------------------------------------------------- // LOADING STATE für Buttons // ---------------------------------------------------------- function setLoading(btn, loading) { if (loading) { btn._originalContent = btn.innerHTML; btn.innerHTML = ''; btn.disabled = true; } else { btn.innerHTML = btn._originalContent || btn.innerHTML; btn.disabled = false; } } // ---------------------------------------------------------- // ASYNC BUTTON: Button-Click → Loader → Ergebnis → Toast // Verwendung: UI.asyncButton(btn, async () => { await API.something() }) // ---------------------------------------------------------- async function asyncButton(btn, fn, { successMsg, errorMsg } = {}) { setLoading(btn, true); try { const result = await fn(); if (successMsg) toast.success(successMsg); return result; } catch (err) { const msg = errorMsg || err.message || 'Ein Fehler ist aufgetreten.'; toast.error(msg); throw err; } finally { setLoading(btn, false); } } // ---------------------------------------------------------- // FORMULAR-HELPER // ---------------------------------------------------------- function formData(form) { const data = {}; new FormData(form).forEach((v, k) => { data[k] = v; }); return data; } function setFormError(form, fieldName, message) { const field = form.querySelector(`[name="${fieldName}"]`); if (!field) return; field.classList.add('is-invalid'); let hint = field.parentElement.querySelector('.form-error'); if (!hint) { hint = document.createElement('span'); hint.className = 'form-error'; field.parentElement.appendChild(hint); } hint.textContent = message; } function clearFormErrors(form) { form.querySelectorAll('.is-invalid').forEach(el => el.classList.remove('is-invalid')); form.querySelectorAll('.form-error').forEach(el => el.remove()); } // ---------------------------------------------------------- // LEERER ZUSTAND (Empty State) // ---------------------------------------------------------- function emptyState({ icon, title, text, action } = {}) { return `