// Dashboard JavaScript let currentWeekStart = getMonday(new Date()); let currentEntries = {}; let userWochenstunden = 0; // Wochenstunden des Users // 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); } } 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 = '-'; } } // 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) { currentWeekStart = data.last_week_start; } } catch (error) { console.warn('Konnte letzte Woche nicht vom Server laden:', error); } // Ping-IP laden loadPingIP(); loadWeek(); document.getElementById('prevWeek').addEventListener('click', function() { const date = new Date(currentWeekStart); date.setDate(date.getDate() - 7); currentWeekStart = formatDate(date); saveLastWeek(); loadWeek(); }); document.getElementById('nextWeek').addEventListener('click', function() { const date = new Date(currentWeekStart); date.setDate(date.getDate() + 7); currentWeekStart = formatDate(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!'); } }); // 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); } } // Montag der aktuellen Woche ermitteln function getMonday(date) { const d = new Date(date); const day = d.getDay(); const diff = d.getDate() - day + (day === 0 ? -6 : 1); d.setDate(diff); return formatDate(d); } // 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()]; } // Woche laden async function loadWeek() { try { // User-Daten laden (Wochenstunden) try { const userResponse = await fetch('/api/user/data'); const userData = await userResponse.json(); userWochenstunden = userData.wochenstunden || 0; } catch (error) { console.warn('Konnte User-Daten nicht laden:', error); userWochenstunden = 0; } const response = await fetch(`/api/timesheet/week/${currentWeekStart}`); const entries = await response.json(); // 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; // 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 ganztägigem Urlaub oder Krank gilt der Tag als ausgefüllt if (i < 5 && vacationType !== 'full' && !sickStatus && (!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) // Bei halbem Tag Urlaub werden die Urlaubsstunden später in der Überstunden-Berechnung hinzugezählt totalHours += hours; // Bearbeitung ist immer möglich, auch nach Abschicken // Bei ganztägigem Urlaub oder Krank werden Zeitfelder deaktiviert const isFullDayVacation = vacationType === 'full'; const isSick = sickStatus === true || sickStatus === 1; const timeFieldsDisabled = (isFullDayVacation || isSick) ? 'disabled' : ''; const disabled = ''; 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)' : ''} ${isFullDayVacation ? '8.00 h (Urlaub)' : isSick ? '8.00 h (Krank)' : hours.toFixed(2) + ' h'}
Tätigkeiten:
${activities.map((activity, idx) => `
h
`).join('')}
h

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 / 5) * workdays; // Urlaubsstunden berechnen (Urlaub zählt als normale Arbeitszeit) let vacationHours = 0; Object.values(currentEntries).forEach(e => { if (e.vacation_type === 'full') { vacationHours += 8; // Ganzer Tag = 8 Stunden } else if (e.vacation_type === 'half') { vacationHours += 4; // Halber Tag = 4 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 / 5) * workdays; // Gesamtstunden berechnen - direkt aus DOM-Elementen lesen für Echtzeit-Aktualisierung let totalHours = 0; 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 || ''); const sickCheckbox = document.querySelector(`input[data-date="${dateStr}"][data-field="sick_status"]`); const sickStatus = sickCheckbox ? sickCheckbox.checked : (currentEntries[dateStr]?.sick_status || false); if (vacationType === 'full') { totalHours += 8; // Ganzer Tag Urlaub = 8 Stunden } else if (sickStatus) { totalHours += 8; // Krank = 8 Stunden } 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); totalHours += hours; } else if (currentEntries[dateStr]?.total_hours) { // Fallback auf gespeicherte Werte totalHours += parseFloat(currentEntries[dateStr].total_hours) || 0; } } } // Urlaubsstunden berechnen (Urlaub zählt als normale Arbeitszeit) let vacationHours = 0; const startDateObj2 = new Date(startDate); for (let i = 0; i < 7; i++) { const date = new Date(startDateObj2); date.setDate(date.getDate() + i); const dateStr = formatDate(date); const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`); const vacationType = vacationSelect ? vacationSelect.value : (currentEntries[dateStr]?.vacation_type || ''); if (vacationType === 'full') { vacationHours += 8; // Ganzer Tag = 8 Stunden } else if (vacationType === 'half') { vacationHours += 4; // Halber Tag = 4 Stunden } } // 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 = (Tatsächliche Stunden + Urlaubsstunden) - Sollstunden const totalHoursWithVacation = totalHours + vacationHours; const overtimeHours = totalHoursWithVacation - sollStunden; // Ü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 = userWochenstunden / 5; const overtimeValue = parseFloat(overtimeHours) || 0; // Prüfe ob ganzer Tag Überstunden if (overtimeValue > 0 && Math.abs(overtimeValue - fullDayHours) < 0.01) { // Ganzer Tag Überstunden // Setze Activity1 auf "Überstunden" mit 0 Stunden const activity1DescInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity1_desc"]`); const activity1HoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity1_hours"]`); if (activity1DescInput) { activity1DescInput.value = 'Überstunden'; // Trigger saveEntry für dieses Feld saveEntry(activity1DescInput); } if (activity1HoursInput) { activity1HoursInput.value = '0'; // Trigger saveEntry für dieses Feld saveEntry(activity1HoursInput); } // 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); } } else if (overtimeValue > 0 && overtimeValue < fullDayHours) { // Weniger als ganzer Tag - füge "Überstunden" als Tätigkeit hinzu // Finde erste freie Activity-Spalte oder prüfe ob bereits vorhanden let foundOvertime = false; let firstEmptySlot = null; 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') { foundOvertime = true; break; // Bereits vorhanden } if (!firstEmptySlot && descInput && (!descInput.value || descInput.value.trim() === '')) { firstEmptySlot = i; } } // Wenn nicht gefunden und freier Slot vorhanden, füge hinzu if (!foundOvertime && firstEmptySlot) { const descInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${firstEmptySlot}_desc"]`); const hoursInput = document.querySelector(`input[data-date="${dateStr}"][data-field="activity${firstEmptySlot}_hours"]`); if (descInput) { descInput.value = 'Überstunden'; saveEntry(descInput); } // Stunden bleiben unverändert (werden vom User eingegeben oder bleiben leer) // total_hours bleibt auch unverändert } } } // 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; const break_minutes = breakInput && breakInput.value ? (parseInt(breakInput.value) || 0) : (parseInt(currentEntries[date].break_minutes) || 0); 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); const sick_status = sickCheckbox ? (sickCheckbox.checked ? true : false) : (currentEntries[date].sick_status || 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; 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; } 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 }) }); const result = await response.json(); if (result.success) { // 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; } // 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(); // 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 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 ganzer Tag Urlaub oder Krank, dann ist der Tag als ausgefüllt zu betrachten if (vacationValue === 'full' || sickStatus) { continue; // Tag ist ausgefüllt (ganzer Tag Urlaub oder Krank) } // 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'}"`, { startInputExists: !!startInput, endInputExists: !!endInput, startInputValue: startInput ? startInput.value : 'N/A', endInputValue: endInput ? endInput.value : 'N/A', vacationValue: vacationValue }); // Bei halbem Tag Urlaub oder keinem Urlaub müssen Start- und Endzeit vorhanden sein 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}`); } } // 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 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 ganzer Tag Urlaub oder Krank, dann ist der Tag als ausgefüllt zu betrachten if (vacationValue === 'full' || sickStatus) { continue; // Tag ist ausgefüllt (ganzer Tag Urlaub oder Krank) } // 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'}"`, { 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 }); 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 werden durch updateOvertimeDisplay() aktualisiert } 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); } } } } } // Krank-Status ein-/ausblenden function toggleSickStatus(dateStr) { const checkboxDiv = document.getElementById(`sick-checkbox-${dateStr}`); if (checkboxDiv) { if (checkboxDiv.style.display === 'none' || !checkboxDiv.style.display) { checkboxDiv.style.display = 'inline-block'; const checkbox = checkboxDiv.querySelector('input[type="checkbox"]'); if (checkbox) { // Prüfe aktuellen Status aus currentEntries const currentSickStatus = currentEntries[dateStr]?.sick_status || false; checkbox.checked = currentSickStatus || true; // Wenn nicht gesetzt, auf true setzen checkbox.focus(); // Sofort speichern wenn aktiviert if (!currentSickStatus) { saveEntry(checkbox); } } } else { // Wert löschen wenn ausgeblendet const checkbox = checkboxDiv.querySelector('input[type="checkbox"]'); if (checkbox) { checkbox.checked = false; // Speichern if (currentEntries[dateStr]) { currentEntries[dateStr].sick_status = false; saveEntry(checkbox); } } checkboxDiv.style.display = 'none'; } } } // 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); } } // 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'); } };