diff --git a/public/css/style.css b/public/css/style.css index d44ae62..06bb236 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -383,9 +383,11 @@ body { .week-selector { display: flex; + flex-wrap: wrap; justify-content: space-between; align-items: center; margin-bottom: 30px; + gap: 10px; } .week-selector h2 { @@ -394,6 +396,30 @@ body { color: #2c3e50; } +.week-selector-goto { + width: 100%; + display: flex; + align-items: center; + gap: 10px; + flex-wrap: wrap; + margin-top: 8px; +} + +.week-selector-goto .form-control { + width: 200px; + max-width: 100%; + padding: 8px 12px; + border: 1px solid #ccc; + border-radius: 4px; + font-size: 14px; +} + +.go-to-week-error { + color: #dc3545; + font-size: 13px; + display: none; +} + /* Timesheet Table */ .timesheet-grid { overflow-x: auto; diff --git a/public/js/dashboard.js b/public/js/dashboard.js index 3ad3cdb..967fca7 100644 --- a/public/js/dashboard.js +++ b/public/js/dashboard.js @@ -171,6 +171,56 @@ document.addEventListener('DOMContentLoaded', async function() { saveLastWeek(); loadWeek(); }); + + function showGoToWeekError(msg) { + const el = document.getElementById('goToWeekError'); + if (el) { el.textContent = msg; el.style.display = 'inline'; } + } + function clearGoToWeekError() { + const el = document.getElementById('goToWeekError'); + if (el) { el.textContent = ''; el.style.display = 'none'; } + } + function goToWeek() { + clearGoToWeekError(); + const input = document.getElementById('goToWeekInput'); + if (!input) return; + const raw = (input.value || '').trim(); + if (!raw) { + showGoToWeekError('Bitte KW eingeben (z. B. 12 oder 2025 12).'); + return; + } + let year = null; + let week = null; + const parts = raw.split(/[\s\/]+/).filter(Boolean).map(p => parseInt(p, 10)); + if (parts.length === 1 && !isNaN(parts[0])) { + week = parts[0]; + year = currentWeekStart ? parseInt(currentWeekStart.split('-')[0], 10) : new Date().getFullYear(); + } else if (parts.length === 2 && !isNaN(parts[0]) && !isNaN(parts[1])) { + if (parts[0] >= 1000) { + year = parts[0]; + week = parts[1]; + } else { + week = parts[0]; + year = parts[1]; + } + } + if (year == null || week == null || week < 1 || week > 53 || year < 2000 || year > 2100) { + showGoToWeekError('Ungültige Eingabe. KW 1–53, Jahr 2000–2100 (z. B. 12 oder 2025 12).'); + return; + } + currentWeekStart = getWeekStartFromYearAndWeek(year, week); + saveLastWeek(); + loadWeek(); + } + + const goToWeekBtn = document.getElementById('goToWeekBtn'); + if (goToWeekBtn) goToWeekBtn.addEventListener('click', goToWeek); + const goToWeekInput = document.getElementById('goToWeekInput'); + if (goToWeekInput) { + goToWeekInput.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { e.preventDefault(); goToWeek(); } + }); + } // Event-Listener für Submit-Button - mit Event-Delegation für bessere Zuverlässigkeit document.addEventListener('click', function(e) { @@ -285,6 +335,20 @@ function getCalendarWeek(dateStr) { return weekNo; } +// Montag (week_start) aus Jahr und Kalenderwoche (ISO 8601) +function getWeekStartFromYearAndWeek(year, weekNumber) { + const jan4 = new Date(Date.UTC(year, 0, 4)); + const jan4Day = jan4.getUTCDay() || 7; + const daysToMonday = jan4Day === 1 ? 0 : 1 - jan4Day; + const firstMonday = new Date(Date.UTC(year, 0, 4 + daysToMonday)); + const weekMonday = new Date(firstMonday); + weekMonday.setUTCDate(firstMonday.getUTCDate() + (weekNumber - 1) * 7); + const y = weekMonday.getUTCFullYear(); + const m = String(weekMonday.getUTCMonth() + 1).padStart(2, '0'); + const d = String(weekMonday.getUTCDate()).padStart(2, '0'); + return `${y}-${m}-${d}`; +} + // Datum formatieren (YYYY-MM-DD) function formatDate(date) { const d = new Date(date); diff --git a/routes/verwaltung-routes.js b/routes/verwaltung-routes.js index 670fd93..310cfae 100644 --- a/routes/verwaltung-routes.js +++ b/routes/verwaltung-routes.js @@ -241,19 +241,16 @@ function registerVerwaltungRoutes(app) { return res.status(500).json({ error: 'Fehler beim Abrufen der Wochen' }); } - // Für jede Woche die neuesten Einträge abrufen + // Nur Wochen bis Ende der angezeigten Kalenderwoche (Stand Urlaub = Ende dieser KW) + const weeksUpToDisplayed = (weeks || []).filter((w) => w.week_end <= week_end); let processedWeeks = 0; let totalVacationDays = 0; const vacationByDate = {}; - if (!weeks || weeks.length === 0) { - // Keine eingereichten Wochen - setze totalVacationDays auf 0 - totalVacationDays = 0; - // Weiter mit der normalen Verarbeitung der aktuellen Woche + if (weeksUpToDisplayed.length === 0) { processCurrentWeek(0); } else { - weeks.forEach((week) => { - // Einträge für diese Woche abrufen (nur neueste pro Tag) + weeksUpToDisplayed.forEach((week) => { db.all(`SELECT date, vacation_type, updated_at, id FROM timesheet_entries WHERE user_id = ? AND date >= ? AND date <= ? @@ -266,13 +263,11 @@ function registerVerwaltungRoutes(app) { return res.status(500).json({ error: 'Fehler beim Abrufen der Einträge' }); } - // Filtere auf neuesten Eintrag pro Tag (weekEntries || []).forEach(entry => { const existing = vacationByDate[entry.date]; if (!existing) { vacationByDate[entry.date] = entry; } else { - // Vergleiche updated_at (falls vorhanden) oder id (höhere ID = neuer) const existingTime = existing.updated_at ? new Date(existing.updated_at).getTime() : 0; const currentTime = entry.updated_at ? new Date(entry.updated_at).getTime() : 0; if (currentTime > existingTime || (currentTime === existingTime && entry.id > existing.id)) { @@ -282,8 +277,7 @@ function registerVerwaltungRoutes(app) { }); processedWeeks++; - if (processedWeeks === weeks.length) { - // Alle Wochen verarbeitet - summiere Urlaubstage + if (processedWeeks === weeksUpToDisplayed.length) { Object.values(vacationByDate).forEach(entry => { if (entry.vacation_type === 'full') { totalVacationDays += 1; @@ -291,8 +285,6 @@ function registerVerwaltungRoutes(app) { totalVacationDays += 0.5; } }); - - // Weiter mit der normalen Verarbeitung der aktuellen Woche processCurrentWeek(totalVacationDays); } }); diff --git a/views/dashboard.ejs b/views/dashboard.ejs index d2cb997..e7acd08 100644 --- a/views/dashboard.ejs +++ b/views/dashboard.ejs @@ -38,6 +38,11 @@

Kalenderwoche

+
+ + + +
diff --git a/views/verwaltung.ejs b/views/verwaltung.ejs index f13811b..5ac7ff3 100644 --- a/views/verwaltung.ejs +++ b/views/verwaltung.ejs @@ -334,10 +334,13 @@ ${data.remainingOvertimeWithOffset !== undefined ? `(verbleibend inkl. Offset: ${Number(data.remainingOvertimeWithOffset).toFixed(2)} h)` : ''}
`; } - if (data.vacationDays !== undefined) { + if (data.totalVacationDays !== undefined || data.vacationDays !== undefined) { + const totalTaken = data.totalVacationDays !== undefined ? data.totalVacationDays : 0; + const inWeek = data.vacationDays !== undefined ? data.vacationDays : 0; statsHTML += `
- Urlaub genommen: ${data.vacationDays.toFixed(1)} Tag${data.vacationDays !== 1 ? 'e' : ''} - ${data.remainingVacation !== undefined ? `(verbleibend: ${data.remainingVacation.toFixed(1)} Tage)` : ''} + Urlaub genommen (kumuliert bis Ende KW): ${Number(totalTaken).toFixed(1)} Tag${totalTaken !== 1 ? 'e' : ''} + ${inWeek > 0 ? ` (davon in dieser Woche: ${inWeek.toFixed(1)})` : ''} + ${data.remainingVacation !== undefined ? ` (verbleibend Stand Ende KW: ${Number(data.remainingVacation).toFixed(1)} Tage)` : ''}
`; } if (data.vacationOffsetDays !== undefined && data.vacationOffsetDays !== 0) {