Anpassungen Dashboard, Halbe Urlaubstage Berechnung

This commit is contained in:
2026-02-04 00:19:43 +01:00
parent d6e985998a
commit 132747ab06
2 changed files with 394 additions and 4 deletions

View File

@@ -464,11 +464,35 @@ function renderWeek() {
// Bearbeitung ist immer möglich, auch nach Abschicken
// Bei ganztägigem Urlaub oder Krank werden Zeitfelder deaktiviert; Feiertag: Anzeige, Zeitfelder optional (Überstunden)
const isFullDayVacation = vacationType === 'full';
const isHalfDayVacation = vacationType === 'half';
const isSick = sickStatus === true || sickStatus === 1;
const timeFieldsDisabled = (isFullDayVacation || isSick) ? 'disabled' : '';
const disabled = '';
const holidayLabel = isHoliday ? ' <span style="color: #6f42c1;">(Feiertag)</span>' : '';
// Stunden-Anzeige für halben Tag Urlaub berechnen
let hoursDisplay = '';
if (isFullDayVacation) {
hoursDisplay = fullDayHours.toFixed(2) + ' h (Urlaub)';
} else if (isHalfDayVacation) {
const halfHours = fullDayHours / 2;
const workHours = hours || 0; // Das sind die gearbeiteten Stunden (ohne Urlaub)
const totalHours = halfHours + workHours;
if (workHours > 0.01) {
hoursDisplay = totalHours.toFixed(2) + ' h (' + halfHours.toFixed(2) + ' h Urlaub + ' + workHours.toFixed(2) + ' h)';
} else {
hoursDisplay = halfHours.toFixed(2) + ' h (Urlaub)';
}
} else if (isSick) {
hoursDisplay = fullDayHours.toFixed(2) + ' h (Krank)';
} else if (isHoliday && !hours) {
hoursDisplay = fullDayHours.toFixed(2) + ' h (Feiertag)';
} else if (isHoliday && hours) {
hoursDisplay = fullDayHours.toFixed(2) + ' + ' + hours.toFixed(2) + ' h (Überst.)';
} else {
hoursDisplay = hours.toFixed(2) + ' h';
}
return `
<tr>
<td><strong>${getWeekday(dateStr)}</strong></td>
@@ -490,7 +514,7 @@ function renderWeek() {
data-date="${dateStr}" data-field="break_minutes"
${timeFieldsDisabled} ${disabled} oninput="saveEntry(this)" onchange="saveEntry(this)">
</td>
<td><strong id="hours_${dateStr}">${isFullDayVacation ? fullDayHours.toFixed(2) + ' h (Urlaub)' : isSick ? fullDayHours.toFixed(2) + ' h (Krank)' : isHoliday && !hours ? fullDayHours.toFixed(2) + ' h (Feiertag)' : isHoliday && hours ? fullDayHours.toFixed(2) + ' + ' + hours.toFixed(2) + ' h (Überst.)' : hours.toFixed(2) + ' h'}</strong></td>
<td><strong id="hours_${dateStr}">${hoursDisplay}</strong></td>
</tr>
<tr class="activities-row">
<td colspan="6" class="activities-cell">
@@ -988,6 +1012,7 @@ async function saveEntry(input) {
if (!currentEntries[date]) {
currentEntries[date] = { date };
}
currentEntries[date][field] = value;
// Lese alle aktuellen Werte direkt aus dem DOM, nicht nur aus currentEntries
@@ -1065,6 +1090,199 @@ async function saveEntry(input) {
currentEntries[date][`activity${i}_project_number`] = activities[i-1].projectNumber;
}
// SOFORTIGE DOM-UPDATES wenn vacation_type geändert wurde
if (input.dataset.field === 'vacation_type') {
const isFullDayVacation = vacation_type === 'full';
const isHalfDayVacation = vacation_type === 'half';
const fullDayHours = getFullDayHours();
const entry = currentEntries[date];
const isHoliday = currentHolidayDates.has(date);
const isSick = entry.sick_status || false;
const hours = entry.total_hours || 0;
// 1. Datum-Zelle: "(Urlaub - ganzer Tag)" in grün hinzufügen/entfernen
// Suche die Datum-Zelle über verschiedene Wege
let dateCell = null;
// Versuche zuerst über das Select-Element selbst
if (input.tagName === 'SELECT' && input.dataset.date === date) {
const selectRow = input.closest('tr');
if (selectRow) {
// Das Select ist in der activities-row, suche die vorherige Zeile
const prevRow = selectRow.previousElementSibling;
if (prevRow) {
dateCell = prevRow.querySelector('td:nth-child(2)');
}
}
}
// Fallback: Suche über start_time Input
if (!dateCell) {
const startInput = document.querySelector(`input[data-date="${date}"][data-field="start_time"]`);
if (startInput) {
const row = startInput.closest('tr');
if (row) {
dateCell = row.querySelector('td:nth-child(2)'); // Zweite Spalte ist das Datum
}
}
}
// Fallback: Suche direkt über alle Tabellenzeilen
if (!dateCell) {
const allRows = document.querySelectorAll('#timesheetTable tr, table tr');
for (let row of allRows) {
const testInput = row.querySelector(`input[data-date="${date}"][data-field="start_time"]`);
if (testInput) {
dateCell = row.querySelector('td:nth-child(2)');
break;
}
}
}
if (dateCell) {
let dateText = dateCell.innerHTML;
const vacationSpan = '<span style="color: #28a745;">(Urlaub - ganzer Tag)</span>';
if (isFullDayVacation) {
// Entferne zuerst alle Urlaub-Spans falls vorhanden (mit verschiedenen möglichen Formaten)
dateText = dateText.replace(/\s*<span[^>]*>\(Urlaub - ganzer Tag\)<\/span>/gi, '');
// Entferne auch "(Krank)" falls vorhanden
dateText = dateText.replace(/\s*<span[^>]*>\(Krank\)<\/span>/gi, '');
// Füge "(Urlaub - ganzer Tag)" hinzu, wenn noch nicht vorhanden
if (!dateText.includes('(Urlaub - ganzer Tag)')) {
dateText += ' ' + vacationSpan;
}
dateCell.innerHTML = dateText;
} else {
// Entferne "(Urlaub - ganzer Tag)" Span (mit verschiedenen möglichen Formaten)
dateText = dateText.replace(/\s*<span[^>]*>\(Urlaub - ganzer Tag\)<\/span>/gi, '');
dateCell.innerHTML = dateText;
}
}
// 2. Stunden-Anzeige sofort aktualisieren
const hoursElement = document.getElementById(`hours_${date}`);
if (hoursElement) {
if (isFullDayVacation) {
// Ganzer Tag Urlaub: Zeige fullDayHours mit "(Urlaub)" Label
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Urlaub)';
currentEntries[date].total_hours = fullDayHours;
} else if (isHalfDayVacation) {
// Halber Tag Urlaub: Berechne Stunden aus Start/Ende falls vorhanden
const startInput = document.querySelector(`input[data-date="${date}"][data-field="start_time"]`);
const endInput = document.querySelector(`input[data-date="${date}"][data-field="end_time"]`);
const breakInput = document.querySelector(`input[data-date="${date}"][data-field="break_minutes"]`);
const halfHours = fullDayHours / 2;
let workHours = 0;
if (startInput && endInput && startInput.value && endInput.value) {
const start = new Date(`2000-01-01T${startInput.value}`);
const end = new Date(`2000-01-01T${endInput.value}`);
const diffMs = end - start;
const breakMinutes = parseInt(breakInput ? breakInput.value : 0) || 0;
workHours = (diffMs / (1000 * 60 * 60)) - (breakMinutes / 60);
}
const totalHours = halfHours + workHours;
if (workHours > 0) {
hoursElement.textContent = totalHours.toFixed(2) + ' h (' + halfHours.toFixed(2) + ' h Urlaub + ' + workHours.toFixed(2) + ' h)';
} else {
hoursElement.textContent = halfHours.toFixed(2) + ' h (Urlaub)';
}
currentEntries[date].total_hours = totalHours;
} else {
// Zurück zu normaler Anzeige basierend auf anderen Status
if (isSick) {
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Krank)';
} else if (isHoliday && !hours) {
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Feiertag)';
} else if (isHoliday && hours) {
hoursElement.textContent = fullDayHours.toFixed(2) + ' + ' + hours.toFixed(2) + ' h (Überst.)';
} else {
hoursElement.textContent = hours.toFixed(2) + ' h';
}
}
}
// 3. Tätigkeiten-Felder sofort deaktivieren/aktivieren (nur bei ganztägigem Urlaub)
const activityInputs = document.querySelectorAll(`input[data-date="${date}"][data-field^="activity"]`);
activityInputs.forEach(input => {
if (isFullDayVacation) {
input.disabled = true;
} else {
// Nur aktivieren wenn nicht krank
input.disabled = isSick;
}
});
// 4. Zeitfelder sofort deaktivieren/aktivieren (nur bei ganztägigem Urlaub)
const timeInputs = document.querySelectorAll(`input[data-date="${date}"][data-field="start_time"], input[data-date="${date}"][data-field="end_time"], input[data-date="${date}"][data-field="break_minutes"]`);
timeInputs.forEach(input => {
if (isFullDayVacation) {
input.disabled = true;
} else {
// Nur aktivieren wenn nicht krank
input.disabled = isSick;
}
});
// 5. Bei ganztägigem Urlaub: Setze "Urlaub" als erste Tätigkeit und leere andere
if (isFullDayVacation) {
const descInput = document.querySelector(`input[data-date="${date}"][data-field="activity1_desc"]`);
const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity1_hours"]`);
if (descInput) {
descInput.value = 'Urlaub';
currentEntries[date].activity1_desc = 'Urlaub';
}
if (hoursInput) {
hoursInput.value = fullDayHours.toFixed(2);
currentEntries[date].activity1_hours = fullDayHours;
}
// Leere andere Tätigkeiten
for (let i = 2; i <= 5; i++) {
const descInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_desc"]`);
const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours"]`);
const projectInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_project_number"]`);
if (descInput) {
descInput.value = '';
currentEntries[date][`activity${i}_desc`] = null;
}
if (hoursInput) {
hoursInput.value = '';
currentEntries[date][`activity${i}_hours`] = 0;
}
if (projectInput) {
projectInput.value = '';
currentEntries[date][`activity${i}_project_number`] = null;
}
}
} else {
// Bei Abwahl von Urlaub (nicht full): Alle Tätigkeitsfelder leeren
for (let i = 1; i <= 5; i++) {
const descInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_desc"]`);
const hoursInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_hours"]`);
const projectInput = document.querySelector(`input[data-date="${date}"][data-field="activity${i}_project_number"]`);
if (descInput) {
descInput.value = '';
currentEntries[date][`activity${i}_desc`] = null;
}
if (hoursInput) {
hoursInput.value = '';
currentEntries[date][`activity${i}_hours`] = 0;
}
if (projectInput) {
projectInput.value = '';
currentEntries[date][`activity${i}_project_number`] = null;
}
}
}
}
const entry = currentEntries[date];
@@ -1108,8 +1326,48 @@ async function saveEntry(input) {
// Aktualisiere Stunden-Anzeige
const hoursElement = document.getElementById(`hours_${date}`);
if (hoursElement && result.total_hours !== undefined) {
hoursElement.textContent = result.total_hours.toFixed(2) + ' h';
currentEntries[date].total_hours = result.total_hours;
// Prüfe ob Urlaub oder Krank aktiv ist, um das richtige Label anzuzeigen
const entry = currentEntries[date] || {};
const vacationType = entry.vacation_type || '';
const isSick = entry.sick_status || false;
const isHoliday = currentHolidayDates.has(date);
const isFullDayVacation = vacationType === 'full';
const isHalfDayVacation = vacationType === 'half';
const fullDayHours = getFullDayHours();
let hoursText = result.total_hours.toFixed(2) + ' h';
if (isFullDayVacation) {
hoursText = fullDayHours.toFixed(2) + ' h (Urlaub)';
} else if (isHalfDayVacation) {
// Bei halbem Tag Urlaub: result.total_hours enthält nur die gearbeiteten Stunden
// Die Urlaubsstunden müssen addiert werden
const halfHours = fullDayHours / 2;
const workHours = result.total_hours || 0; // Das sind die gearbeiteten Stunden
const totalHours = halfHours + workHours; // Gesamt = Urlaub + gearbeitet
if (workHours > 0.01) {
hoursText = totalHours.toFixed(2) + ' h (' + halfHours.toFixed(2) + ' h Urlaub + ' + workHours.toFixed(2) + ' h)';
} else {
hoursText = halfHours.toFixed(2) + ' h (Urlaub)';
}
// Aktualisiere currentEntries mit den Gesamtstunden
currentEntries[date].total_hours = totalHours;
} else if (isSick) {
hoursText = fullDayHours.toFixed(2) + ' h (Krank)';
} else if (isHoliday && result.total_hours <= fullDayHours) {
hoursText = fullDayHours.toFixed(2) + ' h (Feiertag)';
} else if (isHoliday && result.total_hours > fullDayHours) {
const overtime = result.total_hours - fullDayHours;
hoursText = fullDayHours.toFixed(2) + ' + ' + overtime.toFixed(2) + ' h (Überst.)';
}
hoursElement.textContent = hoursText;
// total_hours wurde bereits für halben Tag Urlaub gesetzt, sonst verwende result.total_hours
if (!isHalfDayVacation) {
currentEntries[date].total_hours = result.total_hours;
}
}
// Gesamtstunden neu berechnen
@@ -1653,6 +1911,138 @@ function toggleSickStatus(dateStr) {
button.style.color = '';
}
// SOFORTIGE DOM-UPDATES
// 1. Datum-Zelle: "(Krank)" in rot hinzufügen/entfernen
const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`);
if (startInput) {
const row = startInput.closest('tr');
if (row) {
const dateCell = row.querySelector('td:nth-child(2)'); // Zweite Spalte ist das Datum
if (dateCell) {
let dateText = dateCell.innerHTML;
const sickSpan = '<span style="color: #e74c3c;">(Krank)</span>';
if (newStatus) {
// Prüfe ob bereits vorhanden
if (!dateText.includes('(Krank)')) {
dateText += ' ' + sickSpan;
dateCell.innerHTML = dateText;
}
} else {
// Entferne "(Krank)" Span
dateText = dateText.replace(/ <span style="color: #e74c3c;">\(Krank\)<\/span>/g, '');
dateCell.innerHTML = dateText;
}
}
}
}
// 2. Stunden-Anzeige sofort aktualisieren
const hoursElement = document.getElementById(`hours_${dateStr}`);
if (hoursElement) {
const fullDayHours = getFullDayHours();
const entry = currentEntries[dateStr] || {};
const vacationType = entry.vacation_type || '';
const isHoliday = currentHolidayDates.has(dateStr);
const hours = entry.total_hours || 0;
const isFullDayVacation = vacationType === 'full';
if (newStatus) {
// Krank: Zeige fullDayHours mit "(Krank)" Label
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Krank)';
currentEntries[dateStr].total_hours = fullDayHours;
} else {
// Zurück zu normaler Anzeige basierend auf anderen Status
if (isFullDayVacation) {
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Urlaub)';
} else if (isHoliday && !hours) {
hoursElement.textContent = fullDayHours.toFixed(2) + ' h (Feiertag)';
} else if (isHoliday && hours) {
hoursElement.textContent = fullDayHours.toFixed(2) + ' + ' + hours.toFixed(2) + ' h (Überst.)';
} else {
hoursElement.textContent = hours.toFixed(2) + ' h';
}
}
}
// 3. Tätigkeiten-Felder sofort deaktivieren/aktivieren
const activityInputs = document.querySelectorAll(`input[data-date="${dateStr}"][data-field^="activity"]`);
activityInputs.forEach(input => {
if (newStatus) {
input.disabled = true;
} else {
input.disabled = false;
}
});
// 4. Zeitfelder sofort deaktivieren/aktivieren
const timeInputs = document.querySelectorAll(`input[data-date="${dateStr}"][data-field="start_time"], input[data-date="${dateStr}"][data-field="end_time"], input[data-date="${dateStr}"][data-field="break_minutes"]`);
timeInputs.forEach(input => {
if (newStatus) {
input.disabled = true;
} else {
input.disabled = false;
}
});
// 5. Bei Abwahl: Alle Tätigkeitsfelder leeren
if (!newStatus) {
// Leere alle Tätigkeitsfelder
for (let i = 1; i <= 5; i++) {
const descInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_desc"]`);
const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours"]`);
const projectInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_project_number"]`);
if (descInput) {
descInput.value = '';
currentEntries[dateStr][`activity${i}_desc`] = null;
}
if (hoursInput) {
hoursInput.value = '';
currentEntries[dateStr][`activity${i}_hours`] = 0;
}
if (projectInput) {
projectInput.value = '';
currentEntries[dateStr][`activity${i}_project_number`] = null;
}
}
} else {
// Bei Aktivierung: Setze "Krank" als erste Tätigkeit und leere andere
const descInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity1_desc"]`);
const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity1_hours"]`);
const fullDayHours = getFullDayHours();
if (descInput) {
descInput.value = 'Krank';
currentEntries[dateStr].activity1_desc = 'Krank';
}
if (hoursInput) {
hoursInput.value = fullDayHours.toFixed(2);
currentEntries[dateStr].activity1_hours = fullDayHours;
}
// Leere andere Tätigkeiten
for (let i = 2; i <= 5; i++) {
const descInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_desc"]`);
const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_hours"]`);
const projectInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${i}_project_number"]`);
if (descInput) {
descInput.value = '';
currentEntries[dateStr][`activity${i}_desc`] = null;
}
if (hoursInput) {
hoursInput.value = '';
currentEntries[dateStr][`activity${i}_hours`] = 0;
}
if (projectInput) {
projectInput.value = '';
currentEntries[dateStr][`activity${i}_project_number`] = null;
}
}
}
// Speichere den Wert (erstellen ein temporäres Input-Element für saveEntry)
const tempInput = document.createElement('input');
tempInput.dataset.date = dateStr;