// Dashboard JavaScript let currentWeekStart = getMonday(new Date()); let currentEntries = {}; let currentHolidayDates = new Set(); // Feiertage der aktuellen Woche (YYYY-MM-DD) let userWochenstunden = 0; // Wochenstunden des Users let userArbeitstage = 5; // Arbeitstage pro Woche des Users (Standard: 5) let weekendPercentages = { saturday: 100, sunday: 100 }; // Wochenend-Prozentsätze (100% = normal) let latestSubmittedTimesheetId = null; // ID der neuesten eingereichten Version // Wochenend-Prozentsätze laden async function loadWeekendPercentages() { try { const response = await fetch('/api/user/weekend-percentages'); if (!response.ok) { throw new Error('Fehler beim Laden der Wochenend-Prozentsätze'); } const data = await response.json(); weekendPercentages.saturday = data.saturday_percentage || 100; weekendPercentages.sunday = data.sunday_percentage || 100; } catch (error) { console.error('Fehler beim Laden der Wochenend-Prozentsätze:', error); // Standardwerte verwenden weekendPercentages.saturday = 100; weekendPercentages.sunday = 100; } } // Hilfsfunktion: Prüft ob ein Datum ein Wochenendtag ist und gibt den Prozentsatz zurück function getWeekendPercentage(date) { const day = date.getDay(); if (day === 6) { // Samstag return weekendPercentages.saturday; } else if (day === 0) { // Sonntag return weekendPercentages.sunday; } return 100; // Kein Wochenende = 100% (normal) } // Statistiken laden async function loadUserStats() { try { const response = await fetch('/api/user/stats'); if (!response.ok) { throw new Error('Fehler beim Laden der Statistiken'); } const stats = await response.json(); // Überstunden anzeigen const currentOvertimeEl = document.getElementById('currentOvertime'); if (currentOvertimeEl) { const overtime = stats.currentOvertime || 0; currentOvertimeEl.textContent = overtime >= 0 ? `+${overtime.toFixed(2)}` : overtime.toFixed(2); currentOvertimeEl.style.color = overtime >= 0 ? '#27ae60' : '#e74c3c'; // Auch die Border-Farbe des Cards anpassen const overtimeCard = currentOvertimeEl.closest('.stat-card'); if (overtimeCard) { overtimeCard.style.borderLeftColor = overtime >= 0 ? '#27ae60' : '#e74c3c'; } } // Urlaubstage anzeigen const remainingVacationEl = document.getElementById('remainingVacation'); if (remainingVacationEl) { remainingVacationEl.textContent = (stats.remainingVacation || 0).toFixed(1); } const totalVacationEl = document.getElementById('totalVacation'); if (totalVacationEl) { totalVacationEl.textContent = (stats.urlaubstage || 0).toFixed(1); } // Verplante Urlaubstage anzeigen const plannedVacationEl = document.getElementById('plannedVacation'); if (plannedVacationEl) { plannedVacationEl.textContent = (stats.plannedVacationDays || 0).toFixed(1); } // Kalenderwochen anzeigen const plannedWeeksEl = document.getElementById('plannedWeeks'); if (plannedWeeksEl) { if (stats.plannedWeeks && stats.plannedWeeks.length > 0) { const weeksHTML = stats.plannedWeeks.map(w => { const daysText = w.days === 1 ? '1 Tag' : `${w.days.toFixed(1)} Tage`; return `${w.year} KW${String(w.week).padStart(2, '0')} (${daysText})`; }).join('
'); plannedWeeksEl.innerHTML = weeksHTML; plannedWeeksEl.style.display = 'block'; } else { plannedWeeksEl.textContent = ''; plannedWeeksEl.style.display = 'none'; } } } catch (error) { console.error('Fehler beim Laden der Statistiken:', error); // Fehlerbehandlung: Zeige "-" oder "Fehler" const currentOvertimeEl = document.getElementById('currentOvertime'); if (currentOvertimeEl) currentOvertimeEl.textContent = '-'; const remainingVacationEl = document.getElementById('remainingVacation'); if (remainingVacationEl) remainingVacationEl.textContent = '-'; const totalVacationEl = document.getElementById('totalVacation'); if (totalVacationEl) totalVacationEl.textContent = '-'; const plannedVacationEl = document.getElementById('plannedVacation'); if (plannedVacationEl) plannedVacationEl.textContent = '-'; const plannedWeeksEl = document.getElementById('plannedWeeks'); if (plannedWeeksEl) { plannedWeeksEl.textContent = ''; plannedWeeksEl.style.display = 'none'; } } } // Beim Laden der Seite document.addEventListener('DOMContentLoaded', async function() { // Letzte Woche vom Server laden try { const response = await fetch('/api/user/last-week'); const data = await response.json(); if (data.last_week_start) { // Prüfe ob last_week_start wirklich ein Montag ist if (isMonday(data.last_week_start)) { currentWeekStart = data.last_week_start; } else { // Korrigiere zu Montag falls nicht console.warn('last_week_start war kein Montag, korrigiere:', data.last_week_start); currentWeekStart = getMonday(data.last_week_start); // Speichere die korrigierte Woche saveLastWeek(); } } } catch (error) { console.warn('Konnte letzte Woche nicht vom Server laden:', error); } // Stelle sicher, dass currentWeekStart immer ein Montag ist if (currentWeekStart && !isMonday(currentWeekStart)) { console.warn('currentWeekStart war kein Montag, korrigiere:', currentWeekStart); currentWeekStart = getMonday(currentWeekStart); saveLastWeek(); } // Ping-IP laden loadPingIP(); // Wochenend-Prozentsätze laden loadWeekendPercentages(); // Statistiken laden loadUserStats(); loadWeek(); document.getElementById('prevWeek').addEventListener('click', function() { // Parse als lokales Datum const parts = currentWeekStart.split('-'); const date = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2])); date.setDate(date.getDate() - 7); // Stelle sicher, dass es ein Montag ist currentWeekStart = getMonday(date); saveLastWeek(); loadWeek(); }); document.getElementById('nextWeek').addEventListener('click', function() { // Parse als lokales Datum const parts = currentWeekStart.split('-'); const date = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2])); date.setDate(date.getDate() + 7); // Stelle sicher, dass es ein Montag ist currentWeekStart = getMonday(date); saveLastWeek(); loadWeek(); }); // Event-Listener für Submit-Button - mit Event-Delegation für bessere Zuverlässigkeit document.addEventListener('click', function(e) { if (e.target && e.target.id === 'submitWeek') { e.preventDefault(); e.stopPropagation(); const submitButton = e.target; console.log('Submit-Button wurde geklickt!'); console.log('Button disabled?', submitButton.disabled); console.log('Button element:', submitButton); if (!submitButton.disabled) { submitWeek(); } else { console.warn('Submit-Button ist deaktiviert!'); const tooltip = submitButton.title || 'Bitte füllen Sie alle Werktage aus.'; alert(tooltip); } } }); // Zusätzlicher direkter Event-Listener als Fallback const submitButton = document.getElementById('submitWeek'); if (submitButton) { submitButton.onclick = function(e) { e.preventDefault(); e.stopPropagation(); console.log('Submit-Button onclick wurde ausgelöst!'); if (!this.disabled) { submitWeek(); } return false; }; console.log('Submit-Button Event-Listener gesetzt'); } else { console.error('Submit-Button nicht gefunden beim Initialisieren!'); } // Event-Listener für PDF-Button const viewPdfButton = document.getElementById('viewPdfBtn'); if (viewPdfButton) { viewPdfButton.addEventListener('click', function(e) { e.preventDefault(); if (!this.disabled && latestSubmittedTimesheetId) { // Öffne PDF in neuem Tab window.open(`/api/timesheet/pdf/${latestSubmittedTimesheetId}?inline=true`, '_blank'); } }); console.log('PDF-Button Event-Listener gesetzt'); } else { console.error('PDF-Button nicht gefunden beim Initialisieren!'); } }); // Letzte Woche auf dem Server speichern async function saveLastWeek() { try { await fetch('/api/user/last-week', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ week_start: currentWeekStart }) }); } catch (error) { console.warn('Konnte letzte Woche nicht auf dem Server speichern:', error); } } // Prüft ob ein Datum ein Montag ist (1 = Montag) function isMonday(dateStr) { const d = new Date(dateStr + 'T00:00:00'); // Lokale Zeit verwenden, nicht UTC return d.getDay() === 1; } // Montag der aktuellen Woche ermitteln (robust gegen Zeitzonenprobleme) function getMonday(date) { // Wenn date bereits ein String ist (YYYY-MM-DD), parsen wir es als lokales Datum let d; if (typeof date === 'string') { // Parse als lokales Datum, nicht UTC const parts = date.split('-'); d = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2])); } else { d = new Date(date); } // Stelle sicher, dass wir mit lokaler Zeit arbeiten const day = d.getDay(); // 0 = Sonntag, 1 = Montag, ..., 6 = Samstag // Berechne Differenz zum Montag: Montag = 1, also diff = 1 - day // Aber: Sonntag = 0, also für Sonntag: diff = 1 - 0 = 1, aber wir wollen -6 Tage zurück const diff = day === 0 ? -6 : 1 - day; d.setDate(d.getDate() + diff); // Format als YYYY-MM-DD in lokaler Zeit const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const dayOfMonth = String(d.getDate()).padStart(2, '0'); return `${year}-${month}-${dayOfMonth}`; } // Kalenderwoche berechnen (ISO 8601 - Woche beginnt Montag) function getCalendarWeek(dateStr) { const date = new Date(dateStr); const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); const dayNum = d.getUTCDay() || 7; d.setUTCDate(d.getUTCDate() + 4 - dayNum); const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); const weekNo = Math.ceil((((d - yearStart) / 86400000) + 1) / 7); return weekNo; } // Datum formatieren (YYYY-MM-DD) function formatDate(date) { const d = new Date(date); return d.toISOString().split('T')[0]; } // Datum formatieren (DD.MM.YYYY) function formatDateDE(dateStr) { const d = new Date(dateStr); return d.toLocaleDateString('de-DE'); } // Wochentag ermitteln function getWeekday(dateStr) { const days = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']; const d = new Date(dateStr); return days[d.getDay()]; } // Hilfsfunktion: Berechnet die Stunden pro Tag (Wochenarbeitszeit / Arbeitstage) function getFullDayHours() { return userWochenstunden && userArbeitstage ? (userWochenstunden / userArbeitstage) : 8; } // Woche laden async function loadWeek() { try { // User-Daten laden (Wochenstunden, Arbeitstage) try { const userResponse = await fetch('/api/user/data'); const userData = await userResponse.json(); userWochenstunden = userData.wochenstunden || 0; userArbeitstage = userData.arbeitstage || 5; } catch (error) { console.warn('Konnte User-Daten nicht laden:', error); userWochenstunden = 0; userArbeitstage = 5; } const parts = currentWeekStart.split('-'); const weekEndDate = new Date(parseInt(parts[0], 10), parseInt(parts[1], 10) - 1, parseInt(parts[2], 10) + 6); const weekEnd = weekEndDate.getFullYear() + '-' + String(weekEndDate.getMonth() + 1).padStart(2, '0') + '-' + String(weekEndDate.getDate()).padStart(2, '0'); const [entriesResponse, holidaysResponse, latestSubmittedResponse] = await Promise.all([ fetch(`/api/timesheet/week/${currentWeekStart}`), fetch(`/api/timesheet/holidays?week_start=${currentWeekStart}&week_end=${weekEnd}`), fetch(`/api/timesheet/latest-submitted/${currentWeekStart}`) ]); const entries = await entriesResponse.json(); const holidaysData = await holidaysResponse.json(); const latestSubmittedData = await latestSubmittedResponse.json(); currentHolidayDates = new Set(holidaysData.dates || []); // Speichere die neueste eingereichte Timesheet-ID latestSubmittedTimesheetId = latestSubmittedData.timesheetId || null; // Entries in Object umwandeln für schnellen Zugriff currentEntries = {}; let weekIsSubmitted = false; entries.forEach(entry => { currentEntries[entry.date] = entry; // Prüfe ob die Woche bereits eingereicht wurde if (entry.week_submitted) { weekIsSubmitted = true; } }); // Speichere den Status ob die Woche bereits eingereicht wurde currentEntries._weekSubmitted = weekIsSubmitted; // Speichere ob bereits eine Version existiert (für Grund-Validierung) if (entries.length > 0) { currentEntries._hasExistingVersion = entries[0].has_existing_version || false; currentEntries._latestVersion = entries[0].latest_version || 0; } renderWeek(); } catch (error) { console.error('Fehler beim Laden der Woche:', error); alert('Fehler beim Laden der Daten'); } } // Woche rendern function renderWeek() { const startDate = new Date(currentWeekStart); const endDate = new Date(startDate); endDate.setDate(endDate.getDate() + 6); const calendarWeek = getCalendarWeek(currentWeekStart); document.getElementById('weekTitle').innerHTML = `Kalenderwoche ${calendarWeek}
${formatDateDE(currentWeekStart)} - ${formatDateDE(formatDate(endDate))}`; let html = `
`; let totalHours = 0; let allWeekdaysFilled = true; // Hilfsfunktion zum Rendern eines einzelnen Tages function renderDay(i) { const date = new Date(startDate); date.setDate(date.getDate() + i); const dateStr = formatDate(date); const entry = currentEntries[dateStr] || {}; const startTime = entry.start_time || ''; const endTime = entry.end_time || ''; const breakMinutes = entry.break_minutes || 0; const hours = entry.total_hours || 0; const overtimeTaken = entry.overtime_taken_hours || ''; const vacationType = entry.vacation_type || ''; const sickStatus = entry.sick_status || false; const isHoliday = currentHolidayDates.has(dateStr); const weekendTravel = entry.weekend_travel || false; const appliedWeekendPercentage = entry.applied_weekend_percentage; const isWeekend = (i >= 5); // Samstag (5) oder Sonntag (6) // Tätigkeiten laden const activities = [ { desc: entry.activity1_desc || '', hours: entry.activity1_hours || 0, projectNumber: entry.activity1_project_number || '' }, { desc: entry.activity2_desc || '', hours: entry.activity2_hours || 0, projectNumber: entry.activity2_project_number || '' }, { desc: entry.activity3_desc || '', hours: entry.activity3_hours || 0, projectNumber: entry.activity3_project_number || '' }, { desc: entry.activity4_desc || '', hours: entry.activity4_hours || 0, projectNumber: entry.activity4_project_number || '' }, { desc: entry.activity5_desc || '', hours: entry.activity5_hours || 0, projectNumber: entry.activity5_project_number || '' } ]; // Prüfen ob Werktag (Montag-Freitag, i < 5) ausgefüllt ist // Bei Feiertag, ganztägigem Urlaub oder Krank gilt der Tag als ausgefüllt // Bei 8 Überstunden (ganzer Tag) gilt der Tag auch als ausgefüllt const overtimeValue = overtimeTaken ? parseFloat(overtimeTaken) : 0; const fullDayHours = getFullDayHours(); const isFullDayOvertime = overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01; if (i < 5 && !isHoliday && vacationType !== 'full' && !sickStatus && !isFullDayOvertime && (!startTime || !endTime || startTime.trim() === '' || endTime.trim() === '')) { allWeekdaysFilled = false; } // Stunden zur Summe hinzufügen // Bei ganztägigem Urlaub oder Krank sollten es bereits 8 Stunden sein (vom Backend gesetzt) // Feiertag: 8h Basis + gearbeitete Stunden (jede gearbeitete Stunde = Überstunde) // Bei halbem Tag Urlaub werden die Urlaubsstunden später in der Überstunden-Berechnung hinzugezählt // Wochenend-Prozentsätze: Nur auf tatsächlich gearbeitete Stunden anwenden (nicht auf Urlaub, Krankheit, Feiertage) let hoursToAdd = 0; if (isHoliday) { hoursToAdd = fullDayHours + (hours || 0); // (Wochenarbeitszeit / Arbeitstage) Feiertag + gearbeitete Stunden (= Überstunden) } else { hoursToAdd = hours || 0; // Wochenend-Prozentsatz anwenden (nur wenn weekend_travel aktiviert UND es ist ein Wochenendtag) if (isWeekend && weekendTravel && hours > 0 && vacationType !== 'full' && !sickStatus && !isFullDayOvertime) { // Verwende gespeicherten Prozentsatz falls vorhanden, sonst aktuellen let weekendPercentage = 100; if (appliedWeekendPercentage !== null && appliedWeekendPercentage !== undefined) { weekendPercentage = appliedWeekendPercentage; } else { weekendPercentage = getWeekendPercentage(date); } if (weekendPercentage >= 100) { hoursToAdd = hours * (weekendPercentage / 100); } } } totalHours += hoursToAdd; // 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 ? ' (Feiertag)' : ''; // 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 ` `; } // Werktage (Montag bis Freitag) rendern for (let i = 0; i < 5; i++) { html += renderDay(i); } // Wochenende (Samstag und Sonntag) in zusammenklappbarer Sektion html += ` `; html += `
Tag Datum Start Ende Pause (Min) Stunden
${getWeekday(dateStr)} ${formatDateDE(dateStr)}${isFullDayVacation ? ' (Urlaub - ganzer Tag)' : ''}${isSick ? ' (Krank)' : ''}${holidayLabel} ${hoursDisplay}
Tätigkeiten:
${activities.map((activity, idx) => `
h
`).join('')}
h
${isWeekend ? `
` : ''}

Wochenende

`; document.getElementById('timesheetTable').innerHTML = html; document.getElementById('totalHours').textContent = totalHours.toFixed(2) + ' h'; // Überstunden-Berechnung (startDate und endDate sind bereits oben deklariert) // Anzahl Werktage berechnen (Montag-Freitag) let workdays = 0; for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) { const day = d.getDay(); if (day >= 1 && day <= 5) { // Montag bis Freitag workdays++; } } // Sollstunden berechnen const sollStunden = (userWochenstunden / userArbeitstage) * workdays; // Urlaubsstunden berechnen (Urlaub zählt als normale Arbeitszeit) let vacationHours = 0; const fullDayHours = getFullDayHours(); Object.values(currentEntries).forEach(e => { if (e.vacation_type === 'full') { vacationHours += fullDayHours; // Ganzer Tag = (Wochenarbeitszeit / Arbeitstage) Stunden } else if (e.vacation_type === 'half') { vacationHours += fullDayHours / 2; // Halber Tag = (Wochenarbeitszeit / Arbeitstage) / 2 Stunden } }); // Überstunden berechnen let overtimeTaken = 0; Object.values(currentEntries).forEach(e => { if (e.overtime_taken_hours) { overtimeTaken += parseFloat(e.overtime_taken_hours) || 0; } }); // Überstunden-Berechnung aufrufen updateOvertimeDisplay(); // Nach dem Rendern die vollständige Validierung durchführen // Dies prüft auch direkt die Input-Felder im DOM checkWeekComplete(); } // Überstunden-Anzeige aktualisieren (wird bei jeder Änderung aufgerufen) function updateOvertimeDisplay() { const startDate = new Date(currentWeekStart); const endDate = new Date(startDate); endDate.setDate(endDate.getDate() + 6); // Anzahl Werktage berechnen (Montag-Freitag) let workdays = 0; for (let d = new Date(startDate); d <= endDate; d.setDate(d.getDate() + 1)) { const day = d.getDay(); if (day >= 1 && day <= 5) { // Montag bis Freitag workdays++; } } // Sollstunden berechnen const sollStunden = (userWochenstunden / userArbeitstage) * workdays; // Gesamtstunden berechnen - direkt aus DOM-Elementen lesen für Echtzeit-Aktualisierung let totalHours = 0; let vacationHours = 0; const fullDayHours = userWochenstunden ? (userWochenstunden / 5) : 8; let fullDayOvertimeDays = 0; // Anzahl Tage mit 8 Überstunden (wie im Backend) const startDateObj = new Date(startDate); for (let i = 0; i < 7; i++) { const date = new Date(startDateObj); date.setDate(date.getDate() + i); const dateStr = formatDate(date); // Prüfe Urlaub-Status und Krank-Status const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`); const vacationType = vacationSelect ? vacationSelect.value : (currentEntries[dateStr]?.vacation_type || ''); // Für sick_status: Wert aus currentEntries lesen (da keine Checkbox mehr vorhanden) const sickStatus = currentEntries[dateStr]?.sick_status || false; // Prüfe ob 8 Überstunden (ganzer Tag) eingetragen sind const overtimeInput = document.querySelector(`input[data-date="${dateStr}"][data-field="overtime_taken_hours"]`); const overtimeValue = overtimeInput ? parseFloat(overtimeInput.value) || 0 : (currentEntries[dateStr]?.overtime_taken_hours ? parseFloat(currentEntries[dateStr].overtime_taken_hours) : 0); const isFullDayOvertime = overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01; // Zähle volle Überstundentage (wie im Backend) if (isFullDayOvertime) { fullDayOvertimeDays++; } // Wenn Urlaub oder Krank, zähle nur diese Stunden (nicht zusätzlich Arbeitsstunden) if (vacationType === 'full') { vacationHours += fullDayHours; // Ganzer Tag Urlaub = (Wochenarbeitszeit / Arbeitstage) Stunden } else if (vacationType === 'half') { vacationHours += fullDayHours / 2; // Halber Tag Urlaub = (Wochenarbeitszeit / Arbeitstage) / 2 Stunden // Bei halbem Tag Urlaub können noch Arbeitsstunden vorhanden sein const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`); const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`); const breakInput = document.querySelector(`input[data-date="${dateStr}"][data-field="break_minutes"]`); const startTime = startInput ? startInput.value : ''; const endTime = endInput ? endInput.value : ''; const breakMinutes = parseInt(breakInput ? breakInput.value : 0) || 0; if (startTime && endTime && !isFullDayOvertime) { const start = new Date(`2000-01-01T${startTime}`); const end = new Date(`2000-01-01T${endTime}`); const diffMs = end - start; const hours = (diffMs / (1000 * 60 * 60)) - (breakMinutes / 60); // Wochenend-Prozentsatz anwenden (nur wenn weekend_travel aktiviert UND es ist ein Wochenendtag) const dayOfWeek = date.getDay(); const isWeekend = (dayOfWeek === 6 || dayOfWeek === 0); // Für weekend_travel: Wert aus currentEntries lesen (da keine Checkbox mehr vorhanden) const weekendTravel = currentEntries[dateStr]?.weekend_travel || false; let adjustedHours = hours; if (isWeekend && weekendTravel && hours > 0 && vacationType !== 'full' && !sickStatus && !isFullDayOvertime) { // Verwende gespeicherten Prozentsatz falls vorhanden, sonst aktuellen let weekendPercentage = 100; const entry = currentEntries[dateStr] || {}; if (entry.applied_weekend_percentage !== null && entry.applied_weekend_percentage !== undefined) { weekendPercentage = entry.applied_weekend_percentage; } else { weekendPercentage = getWeekendPercentage(date); } if (weekendPercentage >= 100) { adjustedHours = hours * (weekendPercentage / 100); } } totalHours += adjustedHours; } else if (currentEntries[dateStr]?.total_hours && !isFullDayOvertime) { // Fallback auf gespeicherte Werte let hours = parseFloat(currentEntries[dateStr].total_hours) || 0; // Wochenend-Prozentsatz anwenden (nur wenn weekend_travel aktiviert UND es ist ein Wochenendtag) const dayOfWeek = date.getDay(); const isWeekend = (dayOfWeek === 6 || dayOfWeek === 0); // Für weekend_travel: Wert aus currentEntries lesen (da keine Checkbox mehr vorhanden) const weekendTravel = currentEntries[dateStr]?.weekend_travel || false; if (isWeekend && weekendTravel && hours > 0 && vacationType !== 'full' && !sickStatus) { // Verwende gespeicherten Prozentsatz falls vorhanden, sonst aktuellen const entry = currentEntries[dateStr] || {}; let weekendPercentage = 100; if (entry.applied_weekend_percentage !== null && entry.applied_weekend_percentage !== undefined) { weekendPercentage = entry.applied_weekend_percentage; } else { weekendPercentage = getWeekendPercentage(date); } if (weekendPercentage >= 100) { hours = hours * (weekendPercentage / 100); } } totalHours += hours; } } else if (sickStatus) { totalHours += fullDayHours; // Krank = (Wochenarbeitszeit / Arbeitstage) Stunden } else if (currentHolidayDates.has(dateStr)) { // Feiertag: (Wochenarbeitszeit / Arbeitstage) Basis + gearbeitete Stunden (jede Stunde = Überstunde) const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`); const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`); const startTime = startInput ? startInput.value : ''; const endTime = endInput ? endInput.value : ''; let worked = 0; if (startTime && endTime) { const breakInput = document.querySelector(`input[data-date="${dateStr}"][data-field="break_minutes"]`); const breakMinutes = parseInt(breakInput ? breakInput.value : 0) || 0; const start = new Date(`2000-01-01T${startTime}`); const end = new Date(`2000-01-01T${endTime}`); worked = (end - start) / (1000 * 60 * 60) - (breakMinutes / 60); } else if (currentEntries[dateStr]?.total_hours) { worked = parseFloat(currentEntries[dateStr].total_hours) || 0; } totalHours += fullDayHours + worked; // (Wochenarbeitszeit / Arbeitstage) Feiertag + gearbeitete Stunden (= Überstunden) } else { // Wenn 8 Überstunden (ganzer Tag) eingetragen sind, zählt der Tag als 0 Stunden if (isFullDayOvertime) { // Tag zählt als 0 Stunden (Überstunden werden separat abgezogen) // totalHours bleibt unverändert (0 Stunden für diesen Tag) } else { // Berechne Stunden direkt aus Start-/Endzeit und Pause const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`); const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`); const breakInput = document.querySelector(`input[data-date="${dateStr}"][data-field="break_minutes"]`); const startTime = startInput ? startInput.value : ''; const endTime = endInput ? endInput.value : ''; const breakMinutes = parseInt(breakInput ? breakInput.value : 0) || 0; if (startTime && endTime) { const start = new Date(`2000-01-01T${startTime}`); const end = new Date(`2000-01-01T${endTime}`); const diffMs = end - start; const hours = (diffMs / (1000 * 60 * 60)) - (breakMinutes / 60); // Wochenend-Prozentsatz anwenden (nur wenn weekend_travel aktiviert UND es ist ein Wochenendtag) const dayOfWeek = date.getDay(); const isWeekend = (dayOfWeek === 6 || dayOfWeek === 0); const travelCheckbox = document.querySelector(`input[data-date="${dateStr}"][data-field="weekend_travel"]`); const weekendTravel = travelCheckbox ? travelCheckbox.checked : (currentEntries[dateStr]?.weekend_travel || false); let adjustedHours = hours; if (isWeekend && weekendTravel && hours > 0 && !isFullDayOvertime) { // Verwende gespeicherten Prozentsatz falls vorhanden, sonst aktuellen const entry = currentEntries[dateStr] || {}; let weekendPercentage = 100; if (entry.applied_weekend_percentage !== null && entry.applied_weekend_percentage !== undefined) { weekendPercentage = entry.applied_weekend_percentage; } else { weekendPercentage = getWeekendPercentage(date); } if (weekendPercentage >= 100) { adjustedHours = hours * (weekendPercentage / 100); } } totalHours += adjustedHours; } else if (currentEntries[dateStr]?.total_hours) { // Fallback auf gespeicherte Werte let hours = parseFloat(currentEntries[dateStr].total_hours) || 0; // Wochenend-Prozentsatz anwenden (nur wenn weekend_travel aktiviert UND es ist ein Wochenendtag) const dayOfWeek = date.getDay(); const isWeekend = (dayOfWeek === 6 || dayOfWeek === 0); const travelCheckbox = document.querySelector(`input[data-date="${dateStr}"][data-field="weekend_travel"]`); const weekendTravel = travelCheckbox ? travelCheckbox.checked : (currentEntries[dateStr]?.weekend_travel || false); if (isWeekend && weekendTravel && hours > 0 && !isFullDayOvertime) { // Verwende gespeicherten Prozentsatz falls vorhanden, sonst aktuellen const entry = currentEntries[dateStr] || {}; let weekendPercentage = 100; if (entry.applied_weekend_percentage !== null && entry.applied_weekend_percentage !== undefined) { weekendPercentage = entry.applied_weekend_percentage; } else { weekendPercentage = getWeekendPercentage(date); } if (weekendPercentage >= 100) { hours = hours * (weekendPercentage / 100); } } totalHours += hours; } } } } // Genommene Überstunden berechnen - direkt aus DOM lesen let overtimeTaken = 0; const startDateObj3 = new Date(startDate); for (let i = 0; i < 7; i++) { const date = new Date(startDateObj3); date.setDate(date.getDate() + i); const dateStr = formatDate(date); const overtimeInput = document.querySelector(`input[data-date="${dateStr}"][data-field="overtime_taken_hours"]`); if (overtimeInput && overtimeInput.value) { overtimeTaken += parseFloat(overtimeInput.value) || 0; } else if (currentEntries[dateStr]?.overtime_taken_hours) { overtimeTaken += parseFloat(currentEntries[dateStr].overtime_taken_hours) || 0; } } // Überstunden berechnen (wie im Backend: mit adjustedSollStunden) // totalHours enthält bereits Feiertagsstunden (8h oder gearbeitete Stunden) aus dem Feiertag-Zweig oben const totalHoursWithVacation = totalHours + vacationHours; const adjustedSollStunden = sollStunden - (fullDayOvertimeDays * fullDayHours); // overtimeHours = Überstunden diese Woche (wie im Backend berechnet) const overtimeHours = totalHoursWithVacation - adjustedSollStunden; // Überstunden-Anzeige aktualisieren const overtimeSummaryItem = document.getElementById('overtimeSummaryItem'); const overtimeHoursSpan = document.getElementById('overtimeHours'); if (overtimeSummaryItem && overtimeHoursSpan) { overtimeSummaryItem.style.display = 'block'; const sign = overtimeHours >= 0 ? '+' : ''; overtimeHoursSpan.textContent = `${sign}${overtimeHours.toFixed(2)} h`; overtimeHoursSpan.style.color = overtimeHours >= 0 ? '#27ae60' : '#e74c3c'; } } // Überstunden-Änderung verarbeiten function handleOvertimeChange(dateStr, overtimeHours) { if (!userWochenstunden || userWochenstunden <= 0) { console.warn('Wochenstunden nicht verfügbar, kann Überstunden-Logik nicht anwenden'); return; } const fullDayHours = getFullDayHours(); const overtimeValue = parseFloat(overtimeHours) || 0; // Entferne "Überstunden" aus Activity-Feldern falls vorhanden // (Überstunden werden nur im PDF angezeigt, nicht als Tätigkeit) 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"]`); if (descInput && descInput.value && descInput.value.trim().toLowerCase() === 'überstunden') { descInput.value = ''; saveEntry(descInput); if (hoursInput) { hoursInput.value = ''; saveEntry(hoursInput); } } } // Prüfe ob ganzer Tag Überstunden if (overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01) { // Ganzer Tag Überstunden - leere Start- und End-Zeit const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`); const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`); if (startInput) { startInput.value = ''; saveEntry(startInput); } if (endInput) { endInput.value = ''; saveEntry(endInput); } } // Bei weniger als ganzer Tag oder keine Überstunden: keine weiteren Aktionen // Überstunden werden nur im PDF als Information angezeigt } // Berechnet die gesetzlich erforderliche Mindestpause basierend auf der Arbeitszeit function calculateRequiredBreakMinutes(startTime, endTime) { if (!startTime || !endTime) return null; // Berechne Arbeitszeit in Stunden const [startHours, startMinutes] = startTime.split(':').map(Number); const [endHours, endMinutes] = endTime.split(':').map(Number); const startTotalMinutes = startHours * 60 + startMinutes; const endTotalMinutes = endHours * 60 + endMinutes; const workMinutes = endTotalMinutes - startTotalMinutes; const workHours = workMinutes / 60; // Gesetzliche Mindestpause bestimmen if (workHours > 9) { return 45; // Mehr als 9 Stunden: 45 Minuten } else if (workHours >= 6) { return 30; // 6 bis 9 Stunden: 30 Minuten } return 0; // Weniger als 6 Stunden: keine gesetzliche Pause erforderlich } // Eintrag speichern async function saveEntry(input) { const date = input.dataset.date; const field = input.dataset.field; const value = input.value; // Entferne Fehlermarkierung wenn Feld ausgefüllt wird if (input.classList.contains('missing-field')) { input.classList.remove('missing-field'); input.style.borderColor = ''; input.style.backgroundColor = ''; } // Aktualisiere currentEntries if (!currentEntries[date]) { currentEntries[date] = { date }; } currentEntries[date][field] = value; // Lese alle aktuellen Werte direkt aus dem DOM, nicht nur aus currentEntries // Das stellt sicher, dass auch Werte gespeichert werden, die noch nicht in currentEntries sind // WICHTIG: Wenn das aktuelle Input-Element das Feld ist, das wir suchen, verwende direkt dessen Wert 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 notesInput = document.querySelector(`textarea[data-date="${date}"][data-field="notes"]`); const vacationSelect = document.querySelector(`select[data-date="${date}"][data-field="vacation_type"]`); const overtimeInput = document.querySelector(`input[data-date="${date}"][data-field="overtime_taken_hours"]`); const sickCheckbox = document.querySelector(`input[data-date="${date}"][data-field="sick_status"]`); // Wenn das aktuelle Input-Element das gesuchte Feld ist, verwende dessen Wert direkt // Das stellt sicher, dass der Wert auch bei oninput/onchange sofort verfügbar ist const actualStartTime = (input.dataset.field === 'start_time' && input.value) ? input.value : (startInput && startInput.value && startInput.value.trim() !== '') ? startInput.value : (currentEntries[date].start_time || null); const actualEndTime = (input.dataset.field === 'end_time' && input.value) ? input.value : (endInput && endInput.value && endInput.value.trim() !== '') ? endInput.value : (currentEntries[date].end_time || null); // Aktuelle Werte aus DOM lesen (falls vorhanden), sonst aus currentEntries // Wichtig: Leere Strings werden zu null konvertiert, aber ein Wert sollte vorhanden sein const start_time = actualStartTime; const end_time = actualEndTime; let break_minutes = breakInput && breakInput.value ? (parseInt(breakInput.value) || 0) : (parseInt(currentEntries[date].break_minutes) || 0); // Automatische Vorbelegung der Pausenzeiten basierend auf gesetzlichen Vorgaben // Wird ausgelöst, wenn start_time oder end_time geändert werden if ((input.dataset.field === 'start_time' || input.dataset.field === 'end_time') && start_time && end_time) { const requiredBreakMinutes = calculateRequiredBreakMinutes(start_time, end_time); if (requiredBreakMinutes !== null && requiredBreakMinutes > break_minutes) { // Setze den höheren Wert (gesetzliche Mindestpause) break_minutes = requiredBreakMinutes; // Aktualisiere das Input-Feld im DOM if (breakInput) { breakInput.value = break_minutes; } } } const notes = notesInput ? (notesInput.value || '') : (currentEntries[date].notes || ''); const vacation_type = vacationSelect && vacationSelect.value ? vacationSelect.value : (currentEntries[date].vacation_type || null); const overtime_taken_hours = overtimeInput && overtimeInput.value ? overtimeInput.value : (currentEntries[date].overtime_taken_hours || null); // Für sick_status und weekend_travel: Wert aus currentEntries lesen (da keine Checkboxen mehr vorhanden) const sick_status = (input.dataset.field === 'sick_status') ? (value === 'true' || value === true || value === '1' || value === 1) : (currentEntries[date].sick_status || false); const weekend_travel = (input.dataset.field === 'weekend_travel') ? (value === 'true' || value === true || value === '1' || value === 1) : (currentEntries[date].weekend_travel || false); // Activity-Felder aus DOM lesen const activities = []; 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"]`); activities.push({ desc: descInput ? (descInput.value || null) : (currentEntries[date][`activity${i}_desc`] || null), hours: hoursInput ? (parseFloat(hoursInput.value) || 0) : (parseFloat(currentEntries[date][`activity${i}_hours`]) || 0), projectNumber: projectInput ? (projectInput.value || null) : (currentEntries[date][`activity${i}_project_number`] || null) }); } // Aktualisiere currentEntries mit den DOM-Werten currentEntries[date].start_time = start_time; currentEntries[date].end_time = end_time; currentEntries[date].break_minutes = break_minutes; currentEntries[date].notes = notes; currentEntries[date].vacation_type = vacation_type; currentEntries[date].overtime_taken_hours = overtime_taken_hours; currentEntries[date].sick_status = sick_status; currentEntries[date].weekend_travel = weekend_travel; for (let i = 1; i <= 5; i++) { currentEntries[date][`activity${i}_desc`] = activities[i-1].desc; currentEntries[date][`activity${i}_hours`] = activities[i-1].hours; 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 = '(Urlaub - ganzer Tag)'; if (isFullDayVacation) { // Entferne zuerst alle Urlaub-Spans falls vorhanden (mit verschiedenen möglichen Formaten) dateText = dateText.replace(/\s*]*>\(Urlaub - ganzer Tag\)<\/span>/gi, ''); // Entferne auch "(Krank)" falls vorhanden dateText = dateText.replace(/\s*]*>\(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*]*>\(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]; try { const response = await fetch('/api/timesheet/save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ date: date, start_time: start_time, end_time: end_time, break_minutes: break_minutes, notes: notes, activity1_desc: activities[0].desc, activity1_hours: activities[0].hours, activity1_project_number: activities[0].projectNumber, activity2_desc: activities[1].desc, activity2_hours: activities[1].hours, activity2_project_number: activities[1].projectNumber, activity3_desc: activities[2].desc, activity3_hours: activities[2].hours, activity3_project_number: activities[2].projectNumber, activity4_desc: activities[3].desc, activity4_hours: activities[3].hours, activity4_project_number: activities[3].projectNumber, activity5_desc: activities[4].desc, activity5_hours: activities[4].hours, activity5_project_number: activities[4].projectNumber, overtime_taken_hours: overtime_taken_hours, vacation_type: vacation_type, sick_status: sick_status, weekend_travel: weekend_travel }) }); const result = await response.json(); if (result.success) { // Aktualisiere Stunden-Anzeige const hoursElement = document.getElementById(`hours_${date}`); if (hoursElement && result.total_hours !== undefined) { // 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 let totalHours = 0; Object.values(currentEntries).forEach(e => { totalHours += e.total_hours || 0; }); document.getElementById('totalHours').textContent = totalHours.toFixed(2) + ' h'; // Überstunden-Anzeige aktualisieren (bei jeder Änderung) updateOvertimeDisplay(); // Wenn vacation_type geändert wurde, Statistiken aktualisieren (für verplante Tage) if (input.dataset.field === 'vacation_type') { loadUserStats(); } // Submit-Button Status prüfen (nach jedem Speichern) checkWeekComplete(); // Visuelles Feedback input.style.backgroundColor = '#d4edda'; setTimeout(() => { input.style.backgroundColor = ''; }, 500); // Statistiken aktualisieren (nur wenn es sich um eingereichte Wochen handelt) // Wir aktualisieren die Statistiken nicht bei jedem Speichern, da sie nur für eingereichte Wochen relevant sind // Die Statistiken werden beim Laden der Seite und nach dem Abschicken aktualisiert } } catch (error) { console.error('Fehler beim Speichern:', error); alert('Fehler beim Speichern'); } } // Prüfen ob alle Werktage der Woche ausgefüllt sind function checkWeekComplete() { const startDate = new Date(currentWeekStart); let allWeekdaysFilled = true; const missingFields = []; // Prüfe nur Werktage (Montag-Freitag, i < 5) // Samstag und Sonntag (i >= 5) sind optional for (let i = 0; i < 5; i++) { const date = new Date(startDate); date.setDate(date.getDate() + i); const dateStr = formatDate(date); // Prüfe ob Feiertag const isHoliday = currentHolidayDates.has(dateStr); // Prüfe Urlaub-Status und Krank-Status const entry = currentEntries[dateStr] || {}; const vacationType = entry.vacation_type; const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`); const vacationValue = vacationSelect ? vacationSelect.value : (vacationType || ''); const sickCheckbox = document.querySelector(`input[data-date="${dateStr}"][data-field="sick_status"]`); const sickStatus = sickCheckbox ? sickCheckbox.checked : (entry.sick_status || false); // Wenn Feiertag, ganzer Tag Urlaub oder Krank, dann ist der Tag als ausgefüllt zu betrachten if (isHoliday || vacationValue === 'full' || sickStatus) { continue; // Tag ist ausgefüllt (Feiertag, ganzer Tag Urlaub oder Krank) } // Prüfe ob 8 Überstunden eingetragen sind (dann ist der Tag auch ausgefüllt) const overtimeInput = document.querySelector(`input[data-date="${dateStr}"][data-field="overtime_taken_hours"]`); const overtimeValue = overtimeInput ? parseFloat(overtimeInput.value) || 0 : (entry.overtime_taken_hours ? parseFloat(entry.overtime_taken_hours) : 0); // Berechne fullDayHours (normalerweise 8 Stunden) const fullDayHours = getFullDayHours(); // Wenn Überstunden (ganzer Tag) eingetragen sind, ist der Tag ausgefüllt if (overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01) { continue; // Tag ist ausgefüllt (Überstunden = ganzer Tag) } // Prüfe IMMER direkt die Input-Felder im DOM (das ist die zuverlässigste Quelle) // Auch bei manueller Eingabe werden die Werte hier erkannt const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`); const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`); // Hole die Werte direkt aus den Input-Feldern const startTime = startInput ? (startInput.value || '').trim() : ''; const endTime = endInput ? (endInput.value || '').trim() : ''; // Debug-Ausgabe - zeigt auch den tatsächlichen Wert im Input-Feld console.log(`Tag ${i + 1} (${dateStr}): Start="${startTime || 'LEER'}", Ende="${endTime || 'LEER'}", Urlaub="${vacationValue || 'KEIN'}", Überstunden="${overtimeValue}"`, { startInputExists: !!startInput, endInputExists: !!endInput, startInputValue: startInput ? startInput.value : 'N/A', endInputValue: endInput ? endInput.value : 'N/A', vacationValue: vacationValue, overtimeValue: overtimeValue, fullDayHours: fullDayHours }); // Wenn Überstunden > fullDayHours, dann müssen Start/Ende vorhanden sein if (overtimeValue > fullDayHours) { if (!startTime || !endTime || startTime === '' || endTime === '') { allWeekdaysFilled = false; missingFields.push(formatDateDE(dateStr) + ' (bei Überstunden > ' + fullDayHours.toFixed(2) + 'h müssen Start/Ende vorhanden sein)'); continue; // Weiter zum nächsten Tag } } // Bei halbem Tag Urlaub oder keinem Urlaub müssen Start- und Endzeit vorhanden sein // (außer wenn Überstunden = fullDayHours eingetragen sind, dann sind Start/Ende nicht nötig) if (!startTime || !endTime || startTime === '' || endTime === '') { allWeekdaysFilled = false; missingFields.push(formatDateDE(dateStr)); } } // Prüfe ob die Woche bereits eingereicht wurde (nicht der Status einzelner Einträge!) const weekIsSubmitted = currentEntries._weekSubmitted === true; const submitButton = document.getElementById('submitWeek'); if (submitButton) { submitButton.disabled = weekIsSubmitted || !allWeekdaysFilled; if (weekIsSubmitted) { submitButton.title = 'Diese Woche wurde bereits eingereicht und kann nicht mehr geändert werden.'; } else if (!allWeekdaysFilled) { submitButton.title = `Bitte füllen Sie alle Werktage (Montag bis Freitag) aus (Start- und Endzeit). Wochenende ist optional. Fehlend: ${missingFields.join(', ')}`; } else { submitButton.title = ''; } console.log(`Submit-Button Status: disabled=${submitButton.disabled}, allWeekdaysFilled=${allWeekdaysFilled}, weekIsSubmitted=${weekIsSubmitted}`); } // PDF-Button Status aktualisieren const viewPdfButton = document.getElementById('viewPdfBtn'); if (viewPdfButton) { // Button aktivieren wenn eine eingereichte Version existiert viewPdfButton.disabled = !latestSubmittedTimesheetId; if (latestSubmittedTimesheetId) { viewPdfButton.title = 'Letzte eingereichte Version anzeigen'; } else { viewPdfButton.title = 'Keine eingereichte Version verfügbar'; } } } // Globaler Handler für onclick-Attribut (im HTML) window.submitWeekHandler = function(e) { e.preventDefault(); e.stopPropagation(); console.log('submitWeekHandler wurde aufgerufen'); const submitButton = document.getElementById('submitWeek'); // Auch wenn der Button disabled ist, versuchen wir zu prüfen was fehlt if (submitButton) { if (!submitButton.disabled) { submitWeek(); } else { // Button ist disabled - zeige was fehlt console.warn('Button ist disabled - zeige fehlende Felder'); // Rufe submitWeek auf, um die Validierung durchzuführen (wird wegen fehlender Felder abgebrochen) submitWeek(); } } else { console.error('Button nicht gefunden'); alert('Fehler: Button nicht gefunden'); } return false; }; // Woche abschicken async function submitWeek() { console.log('submitWeek() wurde aufgerufen'); const startDate = new Date(currentWeekStart); const endDate = new Date(startDate); endDate.setDate(endDate.getDate() + 6); console.log('Prüfe Validierung für Woche:', currentWeekStart); // Frontend-Validierung: Prüfen ob alle Werktage (Montag-Freitag) ausgefüllt sind let missingFields = []; let firstMissingInput = null; // Entferne vorherige Fehlermarkierungen document.querySelectorAll('.missing-field').forEach(el => { el.classList.remove('missing-field'); el.style.borderColor = ''; el.style.backgroundColor = ''; }); for (let i = 0; i < 5; i++) { const date = new Date(startDate); date.setDate(date.getDate() + i); const dateStr = formatDate(date); const weekday = getWeekday(dateStr); const dateDisplay = formatDateDE(dateStr); // Prüfe ob Feiertag const isHoliday = currentHolidayDates.has(dateStr); // Prüfe Urlaub-Status und Krank-Status const entry = currentEntries[dateStr] || {}; const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`); const vacationValue = vacationSelect ? vacationSelect.value : (entry.vacation_type || ''); const sickCheckbox = document.querySelector(`input[data-date="${dateStr}"][data-field="sick_status"]`); const sickStatus = sickCheckbox ? sickCheckbox.checked : (entry.sick_status || false); // Wenn Feiertag, ganzer Tag Urlaub oder Krank, dann ist der Tag als ausgefüllt zu betrachten if (isHoliday || vacationValue === 'full' || sickStatus) { continue; // Tag ist ausgefüllt (Feiertag, ganzer Tag Urlaub oder Krank) } // Prüfe ob 8 Überstunden eingetragen sind (dann ist der Tag auch ausgefüllt, Start/Ende nicht nötig) const overtimeInput = document.querySelector(`input[data-date="${dateStr}"][data-field="overtime_taken_hours"]`); const overtimeValue = overtimeInput ? parseFloat(overtimeInput.value) || 0 : (entry.overtime_taken_hours ? parseFloat(entry.overtime_taken_hours) : 0); const fullDayHours = getFullDayHours(); // Wenn 8 Überstunden (ganzer Tag) eingetragen sind, ist der Tag ausgefüllt if (overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01) { continue; // Tag ist ausgefüllt (8 Überstunden = ganzer Tag) } // Prüfe IMMER direkt die Input-Felder im DOM - auch bei manueller Eingabe const startInput = document.querySelector(`input[data-date="${dateStr}"][data-field="start_time"]`); const endInput = document.querySelector(`input[data-date="${dateStr}"][data-field="end_time"]`); // Hole die Werte direkt aus den Input-Feldern (nicht aus currentEntries!) const startTime = startInput ? (startInput.value || '').trim() : ''; const endTime = endInput ? (endInput.value || '').trim() : ''; // Debug-Ausgabe mit detaillierten Informationen console.log(`Tag ${i + 1} (${dateStr}): Start="${startTime || 'LEER'}", Ende="${endTime || 'LEER'}", Urlaub="${vacationValue || 'KEIN'}", Überstunden="${overtimeValue}"`, { startInputExists: !!startInput, endInputExists: !!endInput, startInputValue: startInput ? `"${startInput.value}"` : 'N/A', endInputValue: endInput ? `"${endInput.value}"` : 'N/A', startInputType: startInput ? typeof startInput.value : 'N/A', vacationValue: vacationValue, overtimeValue: overtimeValue, fullDayHours: fullDayHours }); const missing = []; if (!startTime || startTime === '') { missing.push('Startzeit'); if (startInput) { startInput.classList.add('missing-field'); startInput.style.borderColor = '#dc3545'; startInput.style.backgroundColor = '#fff5f5'; if (!firstMissingInput) firstMissingInput = startInput; } } if (!endTime || endTime === '') { missing.push('Endzeit'); if (endInput) { endInput.classList.add('missing-field'); endInput.style.borderColor = '#dc3545'; endInput.style.backgroundColor = '#fff5f5'; if (!firstMissingInput) firstMissingInput = endInput; } } if (missing.length > 0) { missingFields.push(`${weekday} (${dateDisplay}): ${missing.join(' und ')}`); } } if (missingFields.length > 0) { console.warn('Fehlende Felder:', missingFields); // Scroll zum ersten fehlenden Feld if (firstMissingInput) { firstMissingInput.scrollIntoView({ behavior: 'smooth', block: 'center' }); setTimeout(() => firstMissingInput.focus(), 300); } // Detaillierte Fehlermeldung const message = `❌ Bitte füllen Sie alle Werktage (Montag bis Freitag) vollständig aus!\n\n` + `Fehlende Eingaben:\n${missingFields.map((field, index) => `\n${index + 1}. ${field}`).join('')}\n\n` + `Die fehlenden Felder wurden rot markiert.\n` + `Hinweis: Samstag und Sonntag sind optional.`; alert(message); return; } console.log('Alle Werktage sind ausgefüllt'); // Prüfe ob bereits eine Version existiert const hasExistingVersion = currentEntries._hasExistingVersion || false; if (hasExistingVersion) { // Zeige Modal für Grund-Eingabe showVersionReasonModal((reason) => { if (reason) { submitWeekWithReason(reason); } }); } else { // Erste Version - normale Bestätigung const confirmed = confirm( `Möchten Sie die Stundenerfassung für die Woche vom ${formatDateDE(currentWeekStart)} bis ${formatDateDE(formatDate(endDate))} wirklich abschicken?` ); if (!confirmed) { console.log('Benutzer hat abgebrochen'); return; } submitWeekWithReason(null); } } // Modal für Grund-Eingabe anzeigen function showVersionReasonModal(callback) { // Erstelle Modal const modal = document.createElement('div'); modal.id = 'versionReasonModal'; modal.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 10000; `; const modalContent = document.createElement('div'); modalContent.style.cssText = ` background-color: white; padding: 30px; border-radius: 8px; max-width: 500px; width: 90%; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); `; modalContent.innerHTML = `

Neue Version einreichen

Es existiert bereits eine Version für diese Woche. Bitte geben Sie einen Grund an, warum Sie eine neue Version einreichen:

`; modal.appendChild(modalContent); document.body.appendChild(modal); const reasonInput = document.getElementById('versionReasonInput'); reasonInput.focus(); // Event-Handler document.getElementById('cancelReasonBtn').addEventListener('click', () => { document.body.removeChild(modal); callback(null); }); document.getElementById('submitReasonBtn').addEventListener('click', () => { const reason = reasonInput.value.trim(); if (!reason) { alert('Bitte geben Sie einen Grund für die neue Version an.'); reasonInput.focus(); return; } document.body.removeChild(modal); callback(reason); }); // Enter-Taste im Textarea reasonInput.addEventListener('keydown', (e) => { if (e.key === 'Enter' && e.ctrlKey) { document.getElementById('submitReasonBtn').click(); } }); // ESC-Taste zum Schließen modal.addEventListener('click', (e) => { if (e.target === modal) { document.body.removeChild(modal); callback(null); } }); } // Woche mit Grund abschicken async function submitWeekWithReason(versionReason) { const startDate = new Date(currentWeekStart); const endDate = new Date(startDate); endDate.setDate(endDate.getDate() + 6); console.log('Sende Anfrage an Server...'); try { const response = await fetch('/api/timesheet/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ week_start: currentWeekStart, week_end: formatDate(endDate), version_reason: versionReason || null }) }); console.log('Server-Antwort erhalten, Status:', response.status); const result = await response.json(); console.log('Server-Antwort:', result); if (result.success) { const versionText = result.version ? ` (Version ${result.version})` : ''; alert(`Stundenzettel wurde erfolgreich eingereicht${versionText}!`); loadWeek(); // Neu laden um Status zu aktualisieren // Statistiken aktualisieren loadUserStats(); } else { console.error('Fehler-Details:', result); alert(result.error || 'Fehler beim Einreichen des Stundenzettels'); } } catch (error) { console.error('Fehler beim Abschicken:', error); alert('Fehler beim Abschicken: ' + error.message); } } // Überstunden-Eingabefeld ein-/ausblenden function toggleOvertimeInput(dateStr) { const inputDiv = document.getElementById(`overtime-input-${dateStr}`); if (inputDiv) { if (inputDiv.style.display === 'none' || !inputDiv.style.display) { inputDiv.style.display = 'inline-block'; const input = inputDiv.querySelector('input'); if (input) { input.focus(); } } else { inputDiv.style.display = 'none'; // Wert löschen wenn ausgeblendet const input = inputDiv.querySelector('input'); if (input) { input.value = ''; // Speichern if (currentEntries[dateStr]) { currentEntries[dateStr].overtime_taken_hours = null; saveEntry(input); } } } } } // Urlaub-Auswahl ein-/ausblenden function toggleVacationSelect(dateStr) { const selectDiv = document.getElementById(`vacation-select-${dateStr}`); if (selectDiv) { if (selectDiv.style.display === 'none' || !selectDiv.style.display) { selectDiv.style.display = 'inline-block'; const select = selectDiv.querySelector('select'); if (select) { select.focus(); } } else { selectDiv.style.display = 'none'; // Wert löschen wenn ausgeblendet const select = selectDiv.querySelector('select'); if (select) { select.value = ''; // Speichern if (currentEntries[dateStr]) { currentEntries[dateStr].vacation_type = null; saveEntry(select); } } } } } // Wochenend-Reise-Status umschalten function toggleWeekendTravel(dateStr) { const button = document.querySelector(`button[onclick="toggleWeekendTravel('${dateStr}')"]`); if (!button) return; // Aktuellen Status aus currentEntries lesen const currentTravelStatus = currentEntries[dateStr]?.weekend_travel || false; const newStatus = !currentTravelStatus; // Status in currentEntries aktualisieren if (!currentEntries[dateStr]) { currentEntries[dateStr] = { date: dateStr }; } currentEntries[dateStr].weekend_travel = newStatus; // Button-Stil aktualisieren if (newStatus) { button.style.backgroundColor = '#28a745'; button.style.color = 'white'; } else { button.style.backgroundColor = ''; button.style.color = ''; } // Speichere den Wert (erstellen ein temporäres Input-Element für saveEntry) const tempInput = document.createElement('input'); tempInput.dataset.date = dateStr; tempInput.dataset.field = 'weekend_travel'; tempInput.value = newStatus; saveEntry(tempInput); updateOvertimeDisplay(); } function toggleSickStatus(dateStr) { const button = document.querySelector(`button[onclick="toggleSickStatus('${dateStr}')"]`); if (!button) return; // Aktuellen Status aus currentEntries lesen const currentSickStatus = currentEntries[dateStr]?.sick_status || false; const newStatus = !currentSickStatus; // Status in currentEntries aktualisieren if (!currentEntries[dateStr]) { currentEntries[dateStr] = { date: dateStr }; } currentEntries[dateStr].sick_status = newStatus; // Button-Stil aktualisieren if (newStatus) { button.style.backgroundColor = '#e74c3c'; button.style.color = 'white'; } else { button.style.backgroundColor = ''; 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 = '(Krank)'; if (newStatus) { // Prüfe ob bereits vorhanden if (!dateText.includes('(Krank)')) { dateText += ' ' + sickSpan; dateCell.innerHTML = dateText; } } else { // Entferne "(Krank)" Span dateText = dateText.replace(/ \(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; tempInput.dataset.field = 'sick_status'; tempInput.value = newStatus; saveEntry(tempInput); updateOvertimeDisplay(); } // Ping-IP laden async function loadPingIP() { try { const response = await fetch('/api/user/ping-ip'); if (!response.ok) { throw new Error('Fehler beim Laden der IP-Adresse'); } const data = await response.json(); const pingIpInput = document.getElementById('pingIpInput'); if (pingIpInput) { pingIpInput.value = data.ping_ip || ''; } } catch (error) { console.error('Fehler beim Laden der Ping-IP:', error); } } // Client-IP ermitteln und eintragen (global für onclick) window.detectClientIP = async function() { const pingIpInput = document.getElementById('pingIpInput'); const detectButton = document.querySelector('button[onclick*="detectClientIP"]'); if (!pingIpInput) { return; } // Button-Status während des Ladens if (detectButton) { const originalText = detectButton.textContent; detectButton.textContent = 'Ermittle...'; detectButton.disabled = true; try { const response = await fetch('/api/user/client-ip'); if (!response.ok) { throw new Error('Fehler beim Abrufen der IP-Adresse'); } const data = await response.json(); if (data.client_ip && data.client_ip !== 'unknown') { // IP in das Eingabefeld eintragen pingIpInput.value = data.client_ip; // Erfolgs-Feedback detectButton.textContent = 'IP ermittelt!'; detectButton.style.backgroundColor = '#27ae60'; setTimeout(() => { detectButton.textContent = originalText; detectButton.style.backgroundColor = '#3498db'; detectButton.disabled = false; }, 2000); } else { alert('IP-Adresse konnte nicht ermittelt werden.'); detectButton.textContent = originalText; detectButton.disabled = false; } } catch (error) { console.error('Fehler beim Ermitteln der Client-IP:', error); alert('Fehler beim Ermitteln der IP-Adresse'); if (detectButton) { detectButton.textContent = originalText; detectButton.disabled = false; } } } }; // Ping-IP speichern (global für onclick) window.savePingIP = async function() { const pingIpInput = document.getElementById('pingIpInput'); if (!pingIpInput) { return; } const pingIp = pingIpInput.value.trim(); // Finde den Button (nächstes Geschwisterelement oder über Parent) const button = pingIpInput.parentElement?.querySelector('button') || document.querySelector('button[onclick*="savePingIP"]'); try { const response = await fetch('/api/user/ping-ip', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ping_ip: pingIp }) }); const result = await response.json(); if (!response.ok) { alert(result.error || 'Fehler beim Speichern der IP-Adresse'); return; } // Erfolgs-Feedback if (button) { const originalText = button.textContent; button.textContent = 'Gespeichert!'; button.style.backgroundColor = '#27ae60'; setTimeout(() => { button.textContent = originalText; button.style.backgroundColor = ''; }, 2000); } console.log('Ping-IP gespeichert:', result.ping_ip); } catch (error) { console.error('Fehler beim Speichern der Ping-IP:', error); alert('Fehler beim Speichern der IP-Adresse'); } };