This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
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();
|
||||
}
|
||||
Reference in New Issue
Block a user