256 lines
7.9 KiB
JavaScript
256 lines
7.9 KiB
JavaScript
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();
|
|
}
|