1108 lines
42 KiB
JavaScript
1108 lines
42 KiB
JavaScript
// 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);
|
|
}
|
|
|
|
// Statistiken laden
|
|
loadUserStats();
|
|
|
|
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}<br>${formatDateDE(currentWeekStart)} - ${formatDateDE(formatDate(endDate))}`;
|
|
|
|
let html = `
|
|
<div class="timesheet-grid">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Tag</th>
|
|
<th>Datum</th>
|
|
<th>Start</th>
|
|
<th>Ende</th>
|
|
<th>Pause (Min)</th>
|
|
<th>Stunden</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
`;
|
|
|
|
let totalHours = 0;
|
|
let allWeekdaysFilled = true;
|
|
|
|
// 7 Tage (Montag bis Sonntag)
|
|
for (let i = 0; i < 7; 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 || '';
|
|
|
|
// 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 gilt der Tag als ausgefüllt
|
|
if (i < 5 && vacationType !== 'full' && (!startTime || !endTime || startTime.trim() === '' || endTime.trim() === '')) {
|
|
allWeekdaysFilled = false;
|
|
}
|
|
|
|
// Stunden zur Summe hinzufügen
|
|
// Bei ganztägigem Urlaub 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 werden Zeitfelder deaktiviert
|
|
const isFullDayVacation = vacationType === 'full';
|
|
const timeFieldsDisabled = isFullDayVacation ? 'disabled' : '';
|
|
const disabled = '';
|
|
|
|
html += `
|
|
<tr>
|
|
<td><strong>${getWeekday(dateStr)}</strong></td>
|
|
<td>${formatDateDE(dateStr)}${isFullDayVacation ? ' <span style="color: #28a745;">(Urlaub - ganzer Tag)</span>' : ''}</td>
|
|
<td>
|
|
<input type="time" value="${startTime}"
|
|
data-date="${dateStr}" data-field="start_time"
|
|
step="60"
|
|
${timeFieldsDisabled} ${disabled} oninput="saveEntry(this)" onchange="saveEntry(this)" onblur="saveEntry(this); checkWeekComplete();">
|
|
</td>
|
|
<td>
|
|
<input type="time" value="${endTime}"
|
|
data-date="${dateStr}" data-field="end_time"
|
|
step="60"
|
|
${timeFieldsDisabled} ${disabled} oninput="saveEntry(this)" onchange="saveEntry(this)" onblur="saveEntry(this); checkWeekComplete();">
|
|
</td>
|
|
<td>
|
|
<input type="number" value="${breakMinutes}" min="0" step="15"
|
|
data-date="${dateStr}" data-field="break_minutes"
|
|
${timeFieldsDisabled} ${disabled} oninput="saveEntry(this)" onchange="saveEntry(this)">
|
|
</td>
|
|
<td><strong id="hours_${dateStr}">${isFullDayVacation ? '8.00 h (Urlaub)' : hours.toFixed(2) + ' h'}</strong></td>
|
|
</tr>
|
|
<tr class="activities-row">
|
|
<td colspan="6" class="activities-cell">
|
|
<div class="activities-form">
|
|
<div class="activities-header"><strong>Tätigkeiten:</strong></div>
|
|
${activities.map((activity, idx) => `
|
|
<div class="activity-row">
|
|
<div class="activity-desc">
|
|
<input type="text"
|
|
data-date="${dateStr}"
|
|
data-field="activity${idx + 1}_desc"
|
|
value="${activity.desc || ''}"
|
|
placeholder="Tätigkeit ${idx + 1}"
|
|
${timeFieldsDisabled} ${disabled}
|
|
onblur="saveEntry(this)"
|
|
class="activity-input">
|
|
</div>
|
|
<div class="activity-project">
|
|
<input type="text"
|
|
data-date="${dateStr}"
|
|
data-field="activity${idx + 1}_project_number"
|
|
value="${activity.projectNumber || ''}"
|
|
placeholder="Projektnummer"
|
|
${timeFieldsDisabled} ${disabled}
|
|
onblur="saveEntry(this)"
|
|
class="activity-project-input">
|
|
</div>
|
|
<div class="activity-hours">
|
|
<input type="number"
|
|
data-date="${dateStr}"
|
|
data-field="activity${idx + 1}_hours"
|
|
value="${activity.hours > 0 ? activity.hours.toFixed(2) : ''}"
|
|
min="0"
|
|
step="0.25"
|
|
placeholder="0.00"
|
|
${timeFieldsDisabled} ${disabled}
|
|
onblur="saveEntry(this)"
|
|
oninput="updateOvertimeDisplay();"
|
|
onchange="updateOvertimeDisplay();"
|
|
class="activity-hours-input">
|
|
<span class="activity-hours-label">h</span>
|
|
</div>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
<div class="overtime-vacation-controls" style="margin-top: 15px; display: flex; gap: 15px; align-items: center;">
|
|
<div class="overtime-control">
|
|
<button type="button" class="btn btn-secondary btn-sm" onclick="toggleOvertimeInput('${dateStr}')" style="margin-right: 5px;">
|
|
Überstunden
|
|
</button>
|
|
<div id="overtime-input-${dateStr}" style="display: ${overtimeTaken ? 'inline-block' : 'none'};">
|
|
<input type="number"
|
|
data-date="${dateStr}"
|
|
data-field="overtime_taken_hours"
|
|
value="${overtimeTaken}"
|
|
min="0"
|
|
step="0.25"
|
|
placeholder="0.00"
|
|
${disabled}
|
|
onblur="saveEntry(this)"
|
|
oninput="updateOvertimeDisplay();"
|
|
onchange="updateOvertimeDisplay();"
|
|
style="width: 80px; margin-left: 5px;"
|
|
class="overtime-input">
|
|
<span>h</span>
|
|
</div>
|
|
</div>
|
|
<div class="vacation-control">
|
|
<button type="button" class="btn btn-secondary btn-sm" onclick="toggleVacationSelect('${dateStr}')" style="margin-right: 5px;">
|
|
Urlaub
|
|
</button>
|
|
<div id="vacation-select-${dateStr}" style="display: ${vacationType ? 'inline-block' : 'none'};">
|
|
<select data-date="${dateStr}"
|
|
data-field="vacation_type"
|
|
${disabled}
|
|
onchange="saveEntry(this); updateOvertimeDisplay();"
|
|
oninput="updateOvertimeDisplay();"
|
|
style="margin-left: 5px;"
|
|
class="vacation-select">
|
|
<option value="">--</option>
|
|
<option value="half" ${vacationType === 'half' ? 'selected' : ''}>Halber Tag</option>
|
|
<option value="full" ${vacationType === 'full' ? 'selected' : ''}>Ganzer Tag</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
`;
|
|
}
|
|
|
|
html += `
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
`;
|
|
|
|
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-Anzeige erstellen (falls noch nicht vorhanden)
|
|
let overtimeDisplay = document.getElementById('overtimeDisplay');
|
|
if (!overtimeDisplay) {
|
|
const summaryDiv = document.querySelector('.summary');
|
|
overtimeDisplay = document.createElement('div');
|
|
overtimeDisplay.id = 'overtimeDisplay';
|
|
overtimeDisplay.className = 'summary-item';
|
|
summaryDiv.appendChild(overtimeDisplay);
|
|
}
|
|
|
|
// Ü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
|
|
const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`);
|
|
const vacationType = vacationSelect ? vacationSelect.value : (currentEntries[dateStr]?.vacation_type || '');
|
|
|
|
if (vacationType === 'full') {
|
|
totalHours += 8; // Ganzer Tag Urlaub = 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;
|
|
const remainingOvertime = overtimeHours - overtimeTaken;
|
|
|
|
// Überstunden-Anzeige aktualisieren
|
|
const overtimeDisplay = document.getElementById('overtimeDisplay');
|
|
if (overtimeDisplay) {
|
|
overtimeDisplay.innerHTML = `
|
|
<strong>Überstunden diese Woche:</strong>
|
|
<span id="overtimeHours">${overtimeHours.toFixed(2)} h</span>
|
|
${overtimeTaken > 0 ? `<br><strong>Davon genommen:</strong> <span>${overtimeTaken.toFixed(2)} h</span>` : ''}
|
|
${remainingOvertime !== overtimeHours ? `<br><strong>Verbleibend:</strong> <span>${remainingOvertime.toFixed(2)} h</span>` : ''}
|
|
`;
|
|
}
|
|
}
|
|
|
|
// 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"]`);
|
|
|
|
// 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);
|
|
|
|
// 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;
|
|
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
|
|
})
|
|
});
|
|
|
|
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
|
|
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 || '');
|
|
|
|
// Wenn ganzer Tag Urlaub, dann ist der Tag als ausgefüllt zu betrachten
|
|
if (vacationValue === 'full') {
|
|
continue; // Tag ist ausgefüllt (ganzer Tag Urlaub)
|
|
}
|
|
|
|
// 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
|
|
const entry = currentEntries[dateStr] || {};
|
|
const vacationSelect = document.querySelector(`select[data-date="${dateStr}"][data-field="vacation_type"]`);
|
|
const vacationValue = vacationSelect ? vacationSelect.value : (entry.vacation_type || '');
|
|
|
|
// Wenn ganzer Tag Urlaub, dann ist der Tag als ausgefüllt zu betrachten
|
|
if (vacationValue === 'full') {
|
|
continue; // Tag ist ausgefüllt (ganzer Tag Urlaub)
|
|
}
|
|
|
|
// 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 = `
|
|
<h3 style="margin-top: 0; margin-bottom: 15px;">Neue Version einreichen</h3>
|
|
<p style="margin-bottom: 15px; color: #666;">
|
|
Es existiert bereits eine Version für diese Woche. Bitte geben Sie einen Grund an, warum Sie eine neue Version einreichen:
|
|
</p>
|
|
<textarea
|
|
id="versionReasonInput"
|
|
placeholder="Grund für die neue Version..."
|
|
style="width: 100%; min-height: 100px; padding: 10px; border: 1px solid #ddd; border-radius: 4px; font-family: inherit; resize: vertical;"
|
|
required></textarea>
|
|
<div style="margin-top: 20px; display: flex; gap: 10px; justify-content: flex-end;">
|
|
<button id="cancelReasonBtn" class="btn btn-secondary">Abbrechen</button>
|
|
<button id="submitReasonBtn" class="btn btn-success">Einreichen</button>
|
|
</div>
|
|
`;
|
|
|
|
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
|
|
loadUserStats(); // Statistiken aktualisieren
|
|
} 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|