chore: initialize public repository
CI / checks (push) Has been cancelled

This commit is contained in:
maddin
2026-03-22 12:55:55 +00:00
commit 9794362f39
143 changed files with 19832 additions and 0 deletions
+255
View File
@@ -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();
}