From 8d24744c9174439021843d5ca085ba23d2a5e5a6 Mon Sep 17 00:00:00 2001 From: Felix Schlusche Date: Fri, 24 Oct 2025 18:26:50 +0200 Subject: [PATCH] feat: enhance flextime tracking with future days count and tooltip hints --- public/index.html | 14 ++++- public/js/main.js | 149 ++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 149 insertions(+), 14 deletions(-) diff --git a/public/index.html b/public/index.html index 0c43cbb..d80aa0d 100644 --- a/public/index.html +++ b/public/index.html @@ -558,7 +558,12 @@
0h
-
Saldo (Monat)
+
+ Saldo (Monat) + +
0h
@@ -594,7 +599,12 @@
-
Gesamt-Saldo (inkl. Vormonat)
+
+ Gesamt-Saldo (inkl. Vormonat) + +
0h
diff --git a/public/js/main.js b/public/js/main.js index 1ad10cd..5ea91a9 100644 --- a/public/js/main.js +++ b/public/js/main.js @@ -1352,6 +1352,8 @@ async function updateStatistics(entries) { .map(e => e.date) ); + let futureFlextimeDays = 0; // Count future flextime days in current month + for (let day = 1; day <= lastDay; day++) { const dateObj = new Date(currentYear, currentMonth, day); const year = dateObj.getFullYear(); @@ -1363,13 +1365,26 @@ async function updateStatistics(entries) { const isFlextime = flextimeDays.has(dateISO); const isWeekendHoliday = isWeekendOrHoliday(dateObj); - if (!isWeekendHoliday && !isVacation) { - // Normal workday (excluding vacation days) + if (!isWeekendHoliday && !isVacation && !isFlextime) { + // Normal workday (excluding vacation and flextime days) totalWorkdaysInMonth++; workdaysCount++; if (dateObj <= today) { workdaysPassed++; } + } else if (!isWeekendHoliday && !isVacation && isFlextime) { + // Flextime on a workday - still counts as workday in calendar + totalWorkdaysInMonth++; + workdaysCount++; + if (dateObj <= today) { + workdaysPassed++; + } else { + // Future flextime in current month + const isCurrentMonth = currentYear === today.getFullYear() && currentMonth === today.getMonth(); + if (isCurrentMonth) { + futureFlextimeDays++; + } + } } else if (isFlextime && isWeekendHoliday) { // Flextime on weekend/holiday counts as additional workday totalWorkdaysInMonth++; @@ -1380,7 +1395,7 @@ async function updateStatistics(entries) { // Vacation days are excluded from all counts } - // Calculate target hours (8h per workday passed) + // Calculate target hours (8h per workday passed, no reduction) const targetHours = workdaysPassed * 8; // Calculate actual hours worked (only up to today) @@ -1406,7 +1421,10 @@ async function updateStatistics(entries) { } // Calculate balance for current month - const balance = actualHours - targetHours; + let balance = actualHours - targetHours; + + // Subtract future flextime days from balance (they consume flextime) + balance -= (futureFlextimeDays * 8); // Calculate previous month balance const previousBalance = await calculatePreviousBalance(); @@ -1425,6 +1443,33 @@ async function updateStatistics(entries) { document.getElementById('statActualHours').textContent = actualHours.toFixed(1) + 'h'; document.getElementById('statWorkdays').textContent = `${workEntriesCount}/${totalWorkdaysInMonth}`; + // Show/hide flextime hint icons + const balanceFlextimeHint = document.getElementById('balanceFlextimeHint'); + const totalBalanceFlextimeHint = document.getElementById('totalBalanceFlextimeHint'); + + if (futureFlextimeDays > 0) { + const tooltipText = `Inkl. ${futureFlextimeDays} geplanter Gleitzeittag${futureFlextimeDays > 1 ? 'e' : ''} (-${futureFlextimeDays * 8}h)`; + + balanceFlextimeHint.classList.remove('hidden'); + totalBalanceFlextimeHint.classList.remove('hidden'); + + // Set title attribute before re-initializing icons + balanceFlextimeHint.setAttribute('title', tooltipText); + totalBalanceFlextimeHint.setAttribute('title', tooltipText); + + // Re-initialize icons + if (typeof lucide !== 'undefined' && lucide.createIcons) { + lucide.createIcons(); + } + + // Re-apply title after icon initialization (in case it was cleared) + balanceFlextimeHint.setAttribute('title', tooltipText); + totalBalanceFlextimeHint.setAttribute('title', tooltipText); + } else { + balanceFlextimeHint.classList.add('hidden'); + totalBalanceFlextimeHint.classList.add('hidden'); + } + // Current month balance const balanceElement = document.getElementById('statBalance'); balanceElement.textContent = (balance >= 0 ? '+' : '') + balance.toFixed(1) + 'h'; @@ -2651,6 +2696,90 @@ async function handleBundeslandChange(event) { const newBundesland = event.target.value; const oldBundesland = currentBundesland; + // Show warning with backup recommendation + const bundeslandNames = { + 'BW': 'Baden-Württemberg', + 'BY': 'Bayern', + 'BE': 'Berlin', + 'BB': 'Brandenburg', + 'HB': 'Bremen', + 'HH': 'Hamburg', + 'HE': 'Hessen', + 'MV': 'Mecklenburg-Vorpommern', + 'NI': 'Niedersachsen', + 'NW': 'Nordrhein-Westfalen', + 'RP': 'Rheinland-Pfalz', + 'SL': 'Saarland', + 'SN': 'Sachsen', + 'ST': 'Sachsen-Anhalt', + 'SH': 'Schleswig-Holstein', + 'TH': 'Thüringen' + }; + + // Create custom modal for bundesland change confirmation + const modalHTML = ` +
+
+
+ +

Achtung: Bundesland ändern

+
+
+

Sie möchten das Bundesland von ${bundeslandNames[oldBundesland]} auf ${bundeslandNames[newBundesland]} ändern.

+

Warnung: Durch die Änderung der Feiertage können bestehende Einträge betroffen sein. An Tagen, die zu Feiertagen werden, bleiben Arbeitseinträge erhalten.

+
+

Wir empfehlen ein Backup vor der Änderung:

+ +
+
+
+ + +
+
+
+ `; + + // Insert modal into DOM + const modalContainer = document.createElement('div'); + modalContainer.innerHTML = modalHTML; + document.body.appendChild(modalContainer); + + // Initialize icons in modal + if (typeof lucide !== 'undefined' && lucide.createIcons) { + lucide.createIcons(); + } + + // Handle backup button + document.getElementById('quickBackupBtn').addEventListener('click', async () => { + await exportDatabase(); + showNotification('✓ Backup erstellt', 'success'); + }); + + // Handle cancel + document.getElementById('cancelBundeslandChange').addEventListener('click', () => { + event.target.value = oldBundesland; + document.getElementById('bundeslandWarningModal').remove(); + }); + + // Handle confirm + document.getElementById('confirmBundeslandChange').addEventListener('click', async () => { + document.getElementById('bundeslandWarningModal').remove(); + await performBundeslandChange(newBundesland, oldBundesland, event); + }); +} + +/** + * Perform the actual bundesland change after confirmation + */ +async function performBundeslandChange(newBundesland, oldBundesland, event) { // Check for conflicts with existing entries const entries = await fetchEntries(); const conflicts = []; @@ -2689,16 +2818,12 @@ async function handleBundeslandChange(event) { } }); - // Warn user if conflicts exist + // Show conflict info if exists if (conflicts.length > 0) { const conflictList = conflicts.map(c => ` • ${c.displayDate} (${c.holidayName})`).join('\n'); - const message = `Achtung!\n\nDie folgenden Tage werden zu Feiertagen und haben bereits Einträge:\n\n${conflictList}\n\nMöchten Sie fortfahren? Die Einträge bleiben erhalten, aber die Tage werden als Feiertage markiert.`; - - if (!confirm(message)) { - // Revert selection - event.target.value = oldBundesland; - return; - } + const message = `Folgende Tage werden zu Feiertagen und haben bereits Einträge:\n\n${conflictList}\n\nDie Einträge bleiben erhalten.`; + showNotification(`⚠️ ${conflicts.length} Konflikt(e) gefunden`, 'warning'); + console.info(message); } // Update state and save