feat: add bridge days recommendations feature with display and calculation logic
All checks were successful
Build and Push Docker Image / build (push) Successful in 35s
All checks were successful
Build and Push Docker Image / build (push) Successful in 35s
This commit is contained in:
@@ -263,6 +263,10 @@
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.details-chevron {
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
|
||||
details summary {
|
||||
list-style: none;
|
||||
}
|
||||
@@ -677,6 +681,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bridge Days Recommendations -->
|
||||
<details id="bridgeDaysContainer" class="mb-6 glass-card rounded-xl border border-cyan-600/30 hidden">
|
||||
<summary class="cursor-pointer p-4 hover:bg-gray-700/30 rounded-t-xl transition-colors select-none">
|
||||
<div class="flex items-center gap-2">
|
||||
<i data-lucide="lightbulb" class="w-5 h-5 text-cyan-400"></i>
|
||||
<h3 class="text-sm font-semibold text-cyan-400 uppercase tracking-wide">Brückentags-Empfehlungen</h3>
|
||||
<i data-lucide="chevron-down" class="w-4 h-4 text-gray-400 ml-auto details-chevron"></i>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="p-4 pt-2">
|
||||
<div id="bridgeDaysList" class="space-y-2">
|
||||
<!-- Will be populated by JavaScript -->
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<!-- Bulk Edit Actions Bar -->
|
||||
<div id="bulkEditBar" class="hidden mb-6 bg-gray-800 rounded-lg shadow p-4 border border-gray-700">
|
||||
<div class="flex items-center justify-between flex-wrap gap-3">
|
||||
@@ -878,6 +898,7 @@
|
||||
<script src="js/state.js"></script>
|
||||
<script src="js/utils.js"></script>
|
||||
<script src="js/holidays.js"></script>
|
||||
<script src="js/bridge-days.js"></script>
|
||||
<script src="js/api.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
|
||||
|
||||
219
public/js/bridge-days.js
Normal file
219
public/js/bridge-days.js
Normal file
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Bridge Days Calculator
|
||||
* Calculates optimal vacation days based on public holidays
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculate bridge days and optimal vacation periods for a month
|
||||
* @param {number} year - The year to calculate for
|
||||
* @param {number} month - The month (0-11)
|
||||
* @param {string} bundesland - The German state code
|
||||
* @returns {Array} Array of bridge day recommendations
|
||||
*/
|
||||
function calculateBridgeDays(year, month, bundesland) {
|
||||
const recommendations = [];
|
||||
|
||||
// Get all holidays for the year
|
||||
const holidays = getPublicHolidays(year, bundesland);
|
||||
|
||||
// Create a calendar map for the entire year
|
||||
const calendar = createYearCalendar(year, holidays);
|
||||
|
||||
// Find all work day blocks (consecutive work days between weekends/holidays)
|
||||
const workBlocks = findWorkDayBlocks(calendar);
|
||||
|
||||
// Evaluate each block and calculate benefit
|
||||
workBlocks.forEach(block => {
|
||||
const benefit = evaluateBlock(block, calendar);
|
||||
if (benefit.ratio >= 2.0) { // Only show if at least 2x benefit
|
||||
recommendations.push(benefit);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort by benefit ratio (best deals first)
|
||||
recommendations.sort((a, b) => b.ratio - a.ratio);
|
||||
|
||||
// Filter for the specific month
|
||||
const monthRecommendations = recommendations.filter(rec => {
|
||||
const startDate = new Date(rec.startDate);
|
||||
return startDate.getMonth() === month && startDate.getFullYear() === year;
|
||||
});
|
||||
|
||||
return monthRecommendations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a calendar map for the entire year
|
||||
* @param {number} year - The year
|
||||
* @param {Array} holidays - Array of holiday objects
|
||||
* @returns {Map} Map of date strings to day types
|
||||
*/
|
||||
function createYearCalendar(year, holidays) {
|
||||
const calendar = new Map();
|
||||
|
||||
// Create holiday map for fast lookup
|
||||
const holidayMap = new Map();
|
||||
holidays.forEach(h => {
|
||||
const dateStr = formatDateKey(h.date);
|
||||
holidayMap.set(dateStr, h.name);
|
||||
});
|
||||
|
||||
// Process each day of the year
|
||||
for (let month = 0; month < 12; month++) {
|
||||
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||||
|
||||
for (let day = 1; day <= daysInMonth; day++) {
|
||||
const date = new Date(year, month, day);
|
||||
const dateStr = formatDateKey(date);
|
||||
const dayOfWeek = date.getDay();
|
||||
|
||||
let type;
|
||||
if (holidayMap.has(dateStr)) {
|
||||
type = { status: 'HOLIDAY', name: holidayMap.get(dateStr) };
|
||||
} else if (dayOfWeek === 0 || dayOfWeek === 6) {
|
||||
type = { status: 'WEEKEND' };
|
||||
} else {
|
||||
type = { status: 'WORKDAY' };
|
||||
}
|
||||
|
||||
calendar.set(dateStr, type);
|
||||
}
|
||||
}
|
||||
|
||||
return calendar;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format date as YYYY-MM-DD
|
||||
*/
|
||||
function formatDateKey(date) {
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all work day blocks in the calendar
|
||||
* @param {Map} calendar - The calendar map
|
||||
* @returns {Array} Array of work day blocks
|
||||
*/
|
||||
function findWorkDayBlocks(calendar) {
|
||||
const blocks = [];
|
||||
let currentBlock = null;
|
||||
|
||||
// Sort dates for sequential processing
|
||||
const sortedDates = Array.from(calendar.keys()).sort();
|
||||
|
||||
sortedDates.forEach(dateStr => {
|
||||
const dayType = calendar.get(dateStr);
|
||||
|
||||
if (dayType.status === 'WORKDAY') {
|
||||
if (!currentBlock) {
|
||||
currentBlock = {
|
||||
startDate: dateStr,
|
||||
endDate: dateStr,
|
||||
days: [dateStr]
|
||||
};
|
||||
} else {
|
||||
currentBlock.endDate = dateStr;
|
||||
currentBlock.days.push(dateStr);
|
||||
}
|
||||
} else {
|
||||
if (currentBlock) {
|
||||
blocks.push(currentBlock);
|
||||
currentBlock = null;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Don't forget the last block
|
||||
if (currentBlock) {
|
||||
blocks.push(currentBlock);
|
||||
}
|
||||
|
||||
return blocks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a work day block and calculate benefit
|
||||
* @param {Object} block - The work day block
|
||||
* @param {Map} calendar - The calendar map
|
||||
* @returns {Object} Benefit information
|
||||
*/
|
||||
function evaluateBlock(block, calendar) {
|
||||
const vacationDaysNeeded = block.days.length;
|
||||
|
||||
// Find the extended free period (including surrounding weekends/holidays)
|
||||
let startDate = new Date(block.startDate);
|
||||
let endDate = new Date(block.endDate);
|
||||
|
||||
// Extend backwards to include preceding weekends/holidays
|
||||
let currentDate = new Date(startDate);
|
||||
currentDate.setDate(currentDate.getDate() - 1);
|
||||
while (true) {
|
||||
const dateStr = formatDateKey(currentDate);
|
||||
const dayType = calendar.get(dateStr);
|
||||
if (!dayType || dayType.status === 'WORKDAY') break;
|
||||
startDate = new Date(currentDate);
|
||||
currentDate.setDate(currentDate.getDate() - 1);
|
||||
}
|
||||
|
||||
// Extend forwards to include following weekends/holidays
|
||||
currentDate = new Date(endDate);
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
while (true) {
|
||||
const dateStr = formatDateKey(currentDate);
|
||||
const dayType = calendar.get(dateStr);
|
||||
if (!dayType || dayType.status === 'WORKDAY') break;
|
||||
endDate = new Date(currentDate);
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
}
|
||||
|
||||
// Calculate total free days
|
||||
const totalFreeDays = Math.floor((endDate - startDate) / (1000 * 60 * 60 * 24)) + 1;
|
||||
|
||||
// Calculate benefit ratio
|
||||
const ratio = totalFreeDays / vacationDaysNeeded;
|
||||
|
||||
// Find holidays in the period for description
|
||||
const holidaysInPeriod = [];
|
||||
currentDate = new Date(startDate);
|
||||
while (currentDate <= endDate) {
|
||||
const dateStr = formatDateKey(currentDate);
|
||||
const dayType = calendar.get(dateStr);
|
||||
if (dayType && dayType.status === 'HOLIDAY') {
|
||||
holidaysInPeriod.push(dayType.name);
|
||||
}
|
||||
currentDate.setDate(currentDate.getDate() + 1);
|
||||
}
|
||||
|
||||
return {
|
||||
startDate: formatDateKey(startDate),
|
||||
endDate: formatDateKey(endDate),
|
||||
vacationDays: block.days,
|
||||
vacationDaysNeeded: vacationDaysNeeded,
|
||||
totalFreeDays: totalFreeDays,
|
||||
ratio: ratio,
|
||||
holidays: holidaysInPeriod
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a human-readable description for a bridge day recommendation
|
||||
* @param {Object} recommendation - The recommendation object
|
||||
* @returns {string} Description text
|
||||
*/
|
||||
function getBridgeDayDescription(recommendation) {
|
||||
const { vacationDaysNeeded, totalFreeDays, ratio, holidays } = recommendation;
|
||||
|
||||
let description = `${vacationDaysNeeded} Urlaubstag${vacationDaysNeeded > 1 ? 'e' : ''} für ${totalFreeDays} freie Tage`;
|
||||
|
||||
if (holidays.length > 0) {
|
||||
description += ` (inkl. ${holidays.join(', ')})`;
|
||||
}
|
||||
|
||||
description += ` - ${ratio.toFixed(1)}x Ertrag`;
|
||||
|
||||
return description;
|
||||
}
|
||||
@@ -1282,6 +1282,7 @@ async function loadMonthlyView() {
|
||||
renderMonthlyView(entries);
|
||||
updateMonthDisplay();
|
||||
updateStatistics(entries);
|
||||
updateBridgeDaysDisplay();
|
||||
|
||||
// Show/hide PDF export button based on whether month is complete
|
||||
const today = new Date();
|
||||
@@ -1531,6 +1532,116 @@ async function updateVacationStatistics() {
|
||||
document.getElementById('statVacationRemaining').textContent = `${vacationRemaining} / ${totalVacationDays}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update bridge days display for current month
|
||||
*/
|
||||
function updateBridgeDaysDisplay() {
|
||||
const container = document.getElementById('bridgeDaysContainer');
|
||||
const list = document.getElementById('bridgeDaysList');
|
||||
|
||||
// Calculate bridge days for current month
|
||||
const recommendations = calculateBridgeDays(displayYear, displayMonth, currentBundesland);
|
||||
|
||||
if (recommendations.length === 0) {
|
||||
container.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Show container and populate list
|
||||
container.classList.remove('hidden');
|
||||
list.innerHTML = '';
|
||||
|
||||
recommendations.forEach((rec, index) => {
|
||||
const startDate = new Date(rec.startDate);
|
||||
const endDate = new Date(rec.endDate);
|
||||
|
||||
const item = document.createElement('div');
|
||||
item.className = 'flex items-start gap-3 p-3 bg-gray-800/50 rounded-lg border border-cyan-600/20 hover:border-cyan-600/40 transition-colors';
|
||||
|
||||
// Format date range
|
||||
const startStr = formatDateDisplay(rec.startDate);
|
||||
const endStr = formatDateDisplay(rec.endDate);
|
||||
|
||||
// Create vacation days list
|
||||
const vacDaysList = rec.vacationDays.map(d => {
|
||||
const date = new Date(d);
|
||||
return `${date.getDate()}.${String(date.getMonth() + 1).padStart(2, '0')}.`;
|
||||
}).join(', ');
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="flex-shrink-0 w-8 h-8 rounded-full bg-cyan-600/20 flex items-center justify-center text-cyan-400 font-bold text-sm">
|
||||
${Math.round(rec.ratio)}x
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-gray-200">
|
||||
${startStr} - ${endStr}
|
||||
</div>
|
||||
<div class="text-xs text-gray-400 mt-1">
|
||||
${rec.vacationDaysNeeded} Urlaubstag${rec.vacationDaysNeeded > 1 ? 'e' : ''} (${vacDaysList}) für ${rec.totalFreeDays} freie Tage
|
||||
</div>
|
||||
${rec.holidays.length > 0 ? `
|
||||
<div class="text-xs text-cyan-400 mt-1">
|
||||
${rec.holidays.join(', ')}
|
||||
</div>
|
||||
` : ''}
|
||||
</div>
|
||||
<button class="btn-add-bridge-days flex-shrink-0 px-3 py-1 bg-cyan-600 hover:bg-cyan-700 text-white text-xs rounded transition-colors"
|
||||
data-days='${JSON.stringify(rec.vacationDays)}'
|
||||
title="Als Urlaub eintragen">
|
||||
<i data-lucide="calendar-plus" class="w-3 h-3"></i>
|
||||
</button>
|
||||
`;
|
||||
|
||||
list.appendChild(item);
|
||||
});
|
||||
|
||||
// Re-initialize icons
|
||||
if (typeof lucide !== 'undefined' && lucide.createIcons) {
|
||||
lucide.createIcons();
|
||||
}
|
||||
|
||||
// Add event listeners for quick add buttons
|
||||
document.querySelectorAll('.btn-add-bridge-days').forEach(btn => {
|
||||
btn.addEventListener('click', async () => {
|
||||
const days = JSON.parse(btn.dataset.days);
|
||||
await addBridgeDaysAsVacation(days);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add bridge days as vacation entries
|
||||
*/
|
||||
async function addBridgeDaysAsVacation(days) {
|
||||
let created = 0;
|
||||
let skipped = 0;
|
||||
|
||||
for (const dateStr of days) {
|
||||
try {
|
||||
const response = await fetch('/api/entries', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
date: dateStr,
|
||||
entryType: 'vacation'
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
created++;
|
||||
} else {
|
||||
skipped++;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating vacation entry:', error);
|
||||
skipped++;
|
||||
}
|
||||
}
|
||||
|
||||
await reloadView();
|
||||
showNotification(`✓ ${created} Urlaubstag${created > 1 ? 'e' : ''} eingetragen${skipped > 0 ? `, ${skipped} übersprungen` : ''}`, 'success');
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate balance from all previous months (starting from first month with entries)
|
||||
*/
|
||||
@@ -2696,7 +2807,65 @@ async function handleBundeslandChange(event) {
|
||||
const newBundesland = event.target.value;
|
||||
const oldBundesland = currentBundesland;
|
||||
|
||||
// Show warning with backup recommendation
|
||||
// Check for conflicts with existing entries first
|
||||
const entries = await fetchEntries();
|
||||
const conflicts = [];
|
||||
|
||||
// Get old and new holidays for comparison
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years = [currentYear - 1, currentYear, currentYear + 1];
|
||||
|
||||
const oldHolidays = new Set();
|
||||
const newHolidays = new Set();
|
||||
|
||||
years.forEach(year => {
|
||||
getPublicHolidays(year, oldBundesland).forEach(h => {
|
||||
// Convert to YYYY-MM-DD format for comparison
|
||||
const year = h.date.getFullYear();
|
||||
const month = String(h.date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(h.date.getDate()).padStart(2, '0');
|
||||
oldHolidays.add(`${year}-${month}-${day}`);
|
||||
});
|
||||
getPublicHolidays(year, newBundesland).forEach(h => {
|
||||
// Convert to YYYY-MM-DD format for comparison
|
||||
const year = h.date.getFullYear();
|
||||
const month = String(h.date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(h.date.getDate()).padStart(2, '0');
|
||||
const dateKey = `${year}-${month}-${day}`;
|
||||
newHolidays.add(dateKey);
|
||||
});
|
||||
});
|
||||
|
||||
// Create a map of new holidays with their names
|
||||
const newHolidayMap = new Map();
|
||||
years.forEach(year => {
|
||||
getPublicHolidays(year, newBundesland).forEach(h => {
|
||||
const year = h.date.getFullYear();
|
||||
const month = String(h.date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(h.date.getDate()).padStart(2, '0');
|
||||
const dateKey = `${year}-${month}-${day}`;
|
||||
newHolidayMap.set(dateKey, h.name);
|
||||
});
|
||||
});
|
||||
|
||||
// Find dates that are holidays in new state but not in old state, and have entries
|
||||
entries.forEach(entry => {
|
||||
if (newHolidays.has(entry.date) && !oldHolidays.has(entry.date)) {
|
||||
conflicts.push({
|
||||
date: entry.date,
|
||||
displayDate: formatDateDisplay(entry.date),
|
||||
holidayName: newHolidayMap.get(entry.date)
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// If no conflicts, change directly without warning
|
||||
if (conflicts.length === 0) {
|
||||
await performBundeslandChange(newBundesland, oldBundesland, event);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show warning with backup recommendation only if conflicts exist
|
||||
const bundeslandNames = {
|
||||
'BW': 'Baden-Württemberg',
|
||||
'BY': 'Bayern',
|
||||
@@ -2716,17 +2885,23 @@ async function handleBundeslandChange(event) {
|
||||
'TH': 'Thüringen'
|
||||
};
|
||||
|
||||
const conflictList = conflicts.map(c => `<li class="text-sm">• ${c.displayDate} (<span class="text-yellow-300">${c.holidayName}</span>)</li>`).join('');
|
||||
|
||||
// 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>
|
||||
<h3 class="text-xl font-bold text-yellow-500">Achtung: Konflikte gefunden</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>
|
||||
<p class="text-yellow-400"><strong>Warnung:</strong> Die folgenden Tage werden zu Feiertagen und haben bereits Einträge:</p>
|
||||
<ul class="bg-gray-900 rounded-lg p-3 max-h-40 overflow-y-auto">
|
||||
${conflictList}
|
||||
</ul>
|
||||
<p class="text-sm text-gray-400">Die Einträge bleiben erhalten, aber die Tage werden als Feiertage markiert.</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">
|
||||
@@ -2780,59 +2955,6 @@ async function handleBundeslandChange(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 = [];
|
||||
|
||||
// Get old and new holidays for comparison
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years = [currentYear - 1, currentYear, currentYear + 1];
|
||||
|
||||
const oldHolidays = new Set();
|
||||
const newHolidays = new Set();
|
||||
|
||||
years.forEach(year => {
|
||||
getPublicHolidays(year, oldBundesland).forEach(h => {
|
||||
oldHolidays.add(h.date.toISOString().split('T')[0]);
|
||||
});
|
||||
getPublicHolidays(year, newBundesland).forEach(h => {
|
||||
newHolidays.add(h.date.toISOString().split('T')[0]);
|
||||
});
|
||||
});
|
||||
|
||||
// Find dates that are holidays in new state but not in old state, and have entries
|
||||
entries.forEach(entry => {
|
||||
if (newHolidays.has(entry.date) && !oldHolidays.has(entry.date)) {
|
||||
const dateObj = new Date(entry.date);
|
||||
// Temporarily set to new bundesland to get holiday name
|
||||
const tempBundesland = currentBundesland;
|
||||
currentBundesland = newBundesland;
|
||||
const holidayName = getHolidayName(dateObj);
|
||||
currentBundesland = tempBundesland;
|
||||
|
||||
conflicts.push({
|
||||
date: entry.date,
|
||||
displayDate: formatDateDisplay(entry.date),
|
||||
holidayName: holidayName
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Show conflict info if exists
|
||||
if (conflicts.length > 0) {
|
||||
const conflictList = conflicts.map(c => ` • ${c.displayDate} (${c.holidayName})`).join('\n');
|
||||
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
|
||||
currentBundesland = newBundesland;
|
||||
await setSetting('bundesland', newBundesland);
|
||||
|
||||
// Reload view to show updated holidays
|
||||
await reloadView();
|
||||
|
||||
const bundeslandNames = {
|
||||
'BW': 'Baden-Württemberg',
|
||||
'BY': 'Bayern',
|
||||
@@ -2852,6 +2974,13 @@ async function performBundeslandChange(newBundesland, oldBundesland, event) {
|
||||
'TH': 'Thüringen'
|
||||
};
|
||||
|
||||
// Update state and save
|
||||
currentBundesland = newBundesland;
|
||||
await setSetting('bundesland', newBundesland);
|
||||
|
||||
// Reload view to show updated holidays
|
||||
await reloadView();
|
||||
|
||||
showNotification(`✓ Bundesland auf ${bundeslandNames[newBundesland]} gesetzt`, 'success');
|
||||
}
|
||||
|
||||
@@ -2915,6 +3044,34 @@ async function handleVacationDaysChange(event) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for conflicts: More vacation days already taken/planned than new value?
|
||||
const currentYear = new Date().getFullYear();
|
||||
const fromDate = `${currentYear}-01-01`;
|
||||
const toDate = `${currentYear}-12-31`;
|
||||
|
||||
try {
|
||||
const allEntries = await fetchEntries(fromDate, toDate);
|
||||
const vacationCount = allEntries.filter(e => e.entryType === 'vacation').length;
|
||||
|
||||
if (vacationCount > newValue) {
|
||||
// Show warning
|
||||
const difference = vacationCount - newValue;
|
||||
const confirmed = confirm(
|
||||
`⚠️ Warnung: Konflikt erkannt!\n\n` +
|
||||
`Sie haben bereits ${vacationCount} Urlaubstage für ${currentYear} eingetragen.\n` +
|
||||
`Der neue Wert von ${newValue} Tag${newValue !== 1 ? 'en' : ''} ist ${difference} Tag${difference > 1 ? 'e' : ''} zu niedrig.\n\n` +
|
||||
`Möchten Sie den Wert trotzdem auf ${newValue} setzen?`
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
event.target.value = totalVacationDays;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking vacation conflicts:', error);
|
||||
}
|
||||
|
||||
totalVacationDays = newValue;
|
||||
await setSetting('vacationDays', newValue.toString());
|
||||
await updateVacationStatistics();
|
||||
|
||||
Reference in New Issue
Block a user