async function refreshCurrentViewPreservingScroll() { const scrollX = window.scrollX; const scrollY = window.scrollY; const response = await fetch(window.location.href, { credentials: 'same-origin', headers: { 'X-Requested-With': 'fetch' }, }); if (!response.ok) { throw new Error(`refresh_failed_${response.status}`); } const html = await response.text(); const nextDocument = new DOMParser().parseFromString(html, 'text/html'); ['.site-header', '.app-page-actions-wrap', 'main.page'].forEach((selector) => { const currentNode = document.querySelector(selector); const nextNode = nextDocument.querySelector(selector); if (currentNode && nextNode) { currentNode.replaceWith(nextNode); } }); window.scrollTo({ left: scrollX, top: scrollY }); if (typeof window.__stundenfuchsInitApp === 'function') { window.__stundenfuchsInitApp(); } } function parseTimeToMinutes(value) { const match = /^(\d{2}):(\d{2})$/.exec(value || ''); if (!match) { return null; } return Number(match[1]) * 60 + Number(match[2]); } function formatMinutesToTime(value) { const minutes = Math.max(0, Math.min(24 * 60 - 1, Number(value) || 0)); const hoursPart = String(Math.floor(minutes / 60)).padStart(2, '0'); const minutesPart = String(minutes % 60).padStart(2, '0'); return `${hoursPart}:${minutesPart}`; } function requiredBreakMinutesForSpan(spanMinutes) { if (spanMinutes > 9 * 60) { return 45; } if (spanMinutes > 6 * 60) { return 30; } return 0; } function requiredBreakMinutesForNetMinutes(netMinutes) { if (netMinutes > (9 * 60 - 45)) { return 45; } if (netMinutes > (6 * 60 - 30)) { return 30; } return 0; } function initFullDayButtons() { document.querySelectorAll('[data-action="entry-apply-full-day"]').forEach((button) => { if (!(button instanceof HTMLButtonElement) || button.dataset.fullDayBound === 'true') { return; } button.dataset.fullDayBound = 'true'; const form = button.closest('form[data-component="break-rules-form"]'); if (!(form instanceof HTMLFormElement)) { return; } const startInput = form.querySelector('[data-break-input="start"]'); const endInput = form.querySelector('[data-break-input="end"]'); if (!(startInput instanceof HTMLInputElement) || !(endInput instanceof HTMLInputElement)) { return; } button.addEventListener('click', () => { const netMinutes = Number(form.dataset.fullDayNetMinutes || ''); if (!Number.isFinite(netMinutes) || netMinutes <= 0) { return; } const defaultStartValue = form.dataset.defaultStartTime || '08:30'; const startMinutes = parseTimeToMinutes(startInput.value) ?? parseTimeToMinutes(defaultStartValue); if (startMinutes === null) { return; } const autoBreakEnabled = form.dataset.autoBreakEnabled === 'true'; const configuredBreakMinutes = Number(form.dataset.defaultBreakMinutes || '0'); const breakMinutes = autoBreakEnabled ? requiredBreakMinutesForNetMinutes(netMinutes) : Math.max(0, configuredBreakMinutes); const endMinutes = startMinutes + netMinutes + breakMinutes; startInput.value = formatMinutesToTime(startMinutes); endInput.value = formatMinutesToTime(endMinutes); }); }); } function initBreakRuleForms() { document.querySelectorAll('form[data-component="break-rules-form"]').forEach((form) => { if (form.dataset.breakBound === 'true') { return; } form.dataset.breakBound = 'true'; const autoBreakEnabled = form.dataset.autoBreakEnabled === 'true'; const modeInput = form.querySelector('[data-break-mode]'); const startInput = form.querySelector('[data-break-input="start"]'); const endInput = form.querySelector('[data-break-input="end"]'); const breakInput = form.querySelector('[data-break-input="minutes"]'); const statusNode = form.querySelector('[data-break-status]'); const resetButton = form.querySelector('[data-action="break-reset-auto"]'); if (!(modeInput instanceof HTMLInputElement) || !(startInput instanceof HTMLInputElement) || !(endInput instanceof HTMLInputElement)) { return; } const updateStatus = () => { if (!statusNode) { return; } statusNode.textContent = modeInput.value === 'manual' ? 'Pause manuell gesetzt. Gesetzliche Mindestpause wird nicht automatisch überschrieben.' : 'Gesetzliche Mindestpause nach deutschem Arbeitsrecht wird automatisch vorgeschlagen.'; }; const applyAutoBreak = () => { if (!(breakInput instanceof HTMLInputElement)) { return; } const startMinutes = parseTimeToMinutes(startInput.value); const endMinutes = parseTimeToMinutes(endInput.value); if (startMinutes === null || endMinutes === null || endMinutes <= startMinutes) { return; } modeInput.value = 'auto'; breakInput.value = String(requiredBreakMinutesForSpan(endMinutes - startMinutes)); updateStatus(); }; const setManualMode = () => { modeInput.value = 'manual'; updateStatus(); }; startInput.addEventListener('input', () => { if (modeInput.value === 'auto') { applyAutoBreak(); } }); endInput.addEventListener('input', () => { if (modeInput.value === 'auto') { applyAutoBreak(); } }); if (breakInput instanceof HTMLInputElement) { breakInput.addEventListener('input', setManualMode); } if (resetButton) { resetButton.addEventListener('click', applyAutoBreak); } if (!autoBreakEnabled) { return; } if (!modeInput.value) { modeInput.value = 'auto'; } if (modeInput.value === 'auto') { applyAutoBreak(); } else { updateStatus(); } }); } function initAsyncRefreshForms() { document.querySelectorAll('form[data-async-refresh="view"]').forEach((form) => { if (form.dataset.asyncBound === 'true') { return; } form.dataset.asyncBound = 'true'; form.addEventListener('submit', async (event) => { event.preventDefault(); const submitter = event.submitter instanceof HTMLElement ? event.submitter : null; if (submitter) { submitter.setAttribute('disabled', 'disabled'); } try { const response = await fetch(form.action, { method: 'POST', body: new FormData(form), credentials: 'same-origin', headers: { 'X-Requested-With': 'fetch' }, }); if (!response.ok) { throw new Error(`submit_failed_${response.status}`); } await refreshCurrentViewPreservingScroll(); } catch (error) { window.location.assign(window.location.href); } finally { if (submitter) { submitter.removeAttribute('disabled'); } } }); }); } function initBreakSettingsForms() { document.querySelectorAll('form[data-component="break-settings-form"]').forEach((form) => { if (form.dataset.breakSettingsBound === 'true') { return; } form.dataset.breakSettingsBound = 'true'; const toggle = form.querySelector('[data-break-settings-toggle]'); const minutesInput = form.querySelector('[data-break-settings-minutes]'); if (!(toggle instanceof HTMLInputElement) || !(minutesInput instanceof HTMLInputElement)) { return; } const syncDisabledState = () => { minutesInput.disabled = toggle.checked; }; toggle.addEventListener('change', syncDisabledState); syncDisabledState(); }); } export function initForms() { document.querySelectorAll('form[data-confirm]').forEach((form) => { form.addEventListener('submit', (event) => { const message = form.getAttribute('data-confirm') || 'Aktion wirklich ausführen?'; if (!window.confirm(message)) { event.preventDefault(); } }); }); initAsyncRefreshForms(); initFullDayButtons(); initBreakRuleForms(); initBreakSettingsForms(); }