feat: enhance flextime tracking with future days count and tooltip hints
All checks were successful
Build and Push Docker Image / build (push) Successful in 35s

This commit is contained in:
Felix Schlusche
2025-10-24 18:26:50 +02:00
parent e91a2fbe3e
commit 8d24744c91
2 changed files with 149 additions and 14 deletions

View File

@@ -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 = `
<div id="bundeslandWarningModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" style="display: flex;">
<div class="bg-gray-800 rounded-xl shadow-2xl p-6 max-w-md w-full mx-4 border border-yellow-600">
<div class="flex items-center gap-3 mb-4">
<i data-lucide="alert-triangle" class="w-8 h-8 text-yellow-500"></i>
<h3 class="text-xl font-bold text-yellow-500">Achtung: Bundesland ändern</h3>
</div>
<div class="text-gray-300 mb-6 space-y-3">
<p>Sie möchten das Bundesland von <strong>${bundeslandNames[oldBundesland]}</strong> auf <strong>${bundeslandNames[newBundesland]}</strong> ändern.</p>
<p class="text-yellow-400"><strong>Warnung:</strong> Durch die Änderung der Feiertage können bestehende Einträge betroffen sein. An Tagen, die zu Feiertagen werden, bleiben Arbeitseinträge erhalten.</p>
<div class="bg-gray-900 border border-blue-600 rounded-lg p-3 mt-4">
<p class="text-blue-400 text-sm mb-2"><i data-lucide="info" class="w-4 h-4 inline mr-1"></i> Wir empfehlen ein Backup vor der Änderung:</p>
<button id="quickBackupBtn" class="w-full flex items-center justify-center gap-2 px-4 py-2 bg-blue-600 hover:bg-blue-700 text-white rounded-lg transition-colors text-sm">
<i data-lucide="download" class="w-4 h-4"></i>
Jetzt Backup erstellen
</button>
</div>
</div>
<div class="flex gap-3">
<button id="cancelBundeslandChange" class="flex-1 px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors">
Abbrechen
</button>
<button id="confirmBundeslandChange" class="flex-1 px-4 py-2 bg-yellow-600 hover:bg-yellow-700 text-white rounded-lg transition-colors font-semibold">
Trotzdem ändern
</button>
</div>
</div>
</div>
`;
// 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