diff --git a/public/js/holidays.js b/public/js/holidays.js
index be0b3fa..7e215fe 100644
--- a/public/js/holidays.js
+++ b/public/js/holidays.js
@@ -45,6 +45,15 @@ function getPublicHolidays(year, bundesland) {
holidays.push({ date: new Date(year, 11, 25), name: '1. Weihnachtstag' });
holidays.push({ date: new Date(year, 11, 26), name: '2. Weihnachtstag' });
+ // Company-provided holiday: Christmas Eve (24.12) or New Year's Eve (31.12)
+ // Default to Christmas if companyHolidayPreference is not defined
+ const companyHolidayPref = typeof companyHolidayPreference !== 'undefined' ? companyHolidayPreference : 'christmas';
+ if (companyHolidayPref === 'christmas') {
+ holidays.push({ date: new Date(year, 11, 24), name: 'Heiligabend (Betriebsfrei)' });
+ } else if (companyHolidayPref === 'newyearseve') {
+ holidays.push({ date: new Date(year, 11, 31), name: 'Silvester (Betriebsfrei)' });
+ }
+
// Heilige Drei Könige (BW, BY, ST)
if (['BW', 'BY', 'ST'].includes(bundesland)) {
holidays.push({ date: new Date(year, 0, 6), name: 'Heilige Drei Könige' });
diff --git a/public/js/main.js b/public/js/main.js
index 1f77f6e..bc22ebd 100644
--- a/public/js/main.js
+++ b/public/js/main.js
@@ -21,109 +21,6 @@ let totalVacationDays = 30; // Default vacation days per year
// UTILITY FUNCTIONS
// ============================================
-/**
- * Format date from YYYY-MM-DD to DD.MM.YYYY
- */
-function formatDateDisplay(dateStr) {
- const [year, month, day] = dateStr.split('-');
- return `${day}.${month}.${year}`;
-}
-
-/**
- * Format date from DD.MM.YYYY to YYYY-MM-DD
- */
-function formatDateISO(dateStr) {
- const [day, month, year] = dateStr.split('.');
- return `${year}-${month}-${day}`;
-}
-
-/**
- * Get today's date in YYYY-MM-DD format
- */
-function getTodayISO() {
- const today = new Date();
- const year = today.getFullYear();
- const month = String(today.getMonth() + 1).padStart(2, '0');
- const day = String(today.getDate()).padStart(2, '0');
- return `${year}-${month}-${day}`;
-}
-
-/**
- * Round time down to nearest 15 minutes
- */
-function roundDownTo15Min(date) {
- const minutes = date.getMinutes();
- const roundedMinutes = Math.floor(minutes / 15) * 15;
- date.setMinutes(roundedMinutes);
- date.setSeconds(0);
- date.setMilliseconds(0);
- return date;
-}
-
-/**
- * Round time up to nearest 15 minutes
- */
-function roundUpTo15Min(date) {
- const minutes = date.getMinutes();
- const roundedMinutes = Math.ceil(minutes / 15) * 15;
- date.setMinutes(roundedMinutes);
- date.setSeconds(0);
- date.setMilliseconds(0);
- return date;
-}
-
-/**
- * Format time as HH:MM
- */
-function formatTime(date) {
- const hours = String(date.getHours()).padStart(2, '0');
- const minutes = String(date.getMinutes()).padStart(2, '0');
- return `${hours}:${minutes}`;
-}
-
-/**
- * Format seconds to HH:MM:SS
- */
-function formatDuration(seconds) {
- const hrs = Math.floor(seconds / 3600);
- const mins = Math.floor((seconds % 3600) / 60);
- const secs = seconds % 60;
- return `${String(hrs).padStart(2, '0')}:${String(mins).padStart(2, '0')}:${String(secs).padStart(2, '0')}`;
-}
-
-/**
- * Show toast notification
- */
-function showNotification(message, type = 'info') {
- const container = document.getElementById('toastContainer');
-
- // Create toast element
- const toast = document.createElement('div');
- toast.className = `toast toast-${type}`;
-
- // Icon based on type
- const icons = {
- success: '✓',
- error: '✕',
- info: 'ℹ'
- };
-
- toast.innerHTML = `
- ${icons[type] || 'ℹ'}
- ${message}
- `;
-
- container.appendChild(toast);
-
- // Auto-remove after 3 seconds
- setTimeout(() => {
- toast.classList.add('hiding');
- setTimeout(() => {
- container.removeChild(toast);
- }, 300);
- }, 3000);
-}
-
/**
* Get day of week abbreviation in German
*/
@@ -132,156 +29,6 @@ function getDayOfWeek(date) {
return days[date.getDay()];
}
-/**
- * Check if date is weekend
- */
-function isWeekend(date) {
- const day = date.getDay();
- return day === 0 || day === 6; // Sunday or Saturday
-}
-
-/**
- * Calculate Easter Sunday for a given year (Gauss algorithm)
- */
-function getEasterSunday(year) {
- const a = year % 19;
- const b = Math.floor(year / 100);
- const c = year % 100;
- const d = Math.floor(b / 4);
- const e = b % 4;
- const f = Math.floor((b + 8) / 25);
- const g = Math.floor((b - f + 1) / 3);
- const h = (19 * a + b - d - g + 15) % 30;
- const i = Math.floor(c / 4);
- const k = c % 4;
- const l = (32 + 2 * e + 2 * i - h - k) % 7;
- const m = Math.floor((a + 11 * h + 22 * l) / 451);
- const month = Math.floor((h + l - 7 * m + 114) / 31);
- const day = ((h + l - 7 * m + 114) % 31) + 1;
- return new Date(year, month - 1, day);
-}
-
-/**
- * Get all public holidays for a given year and Bundesland
- */
-function getPublicHolidays(year, bundesland = currentBundesland) {
- const holidays = [];
-
- // Fixed holidays (all states)
- holidays.push({ date: new Date(year, 0, 1), name: 'Neujahr' });
- holidays.push({ date: new Date(year, 4, 1), name: 'Tag der Arbeit' });
- holidays.push({ date: new Date(year, 9, 3), name: 'Tag der Deutschen Einheit' });
- holidays.push({ date: new Date(year, 11, 25), name: '1. Weihnachtstag' });
- holidays.push({ date: new Date(year, 11, 26), name: '2. Weihnachtstag' });
-
- // Heilige Drei Könige (BW, BY, ST)
- if (['BW', 'BY', 'ST'].includes(bundesland)) {
- holidays.push({ date: new Date(year, 0, 6), name: 'Heilige Drei Könige' });
- }
-
- // Internationaler Frauentag (BE, MV since 2023)
- if (['BE'].includes(bundesland) || (bundesland === 'MV' && year >= 2023)) {
- holidays.push({ date: new Date(year, 2, 8), name: 'Internationaler Frauentag' });
- }
-
- // Weltkindertag (TH since 2019)
- if (bundesland === 'TH' && year >= 2019) {
- holidays.push({ date: new Date(year, 8, 20), name: 'Weltkindertag' });
- }
-
- // Reformationstag (BB, MV, SN, ST, TH, + HB, HH, NI, SH since 2018)
- const reformationstagStates = ['BB', 'MV', 'SN', 'ST', 'TH'];
- if (year >= 2018) {
- reformationstagStates.push('HB', 'HH', 'NI', 'SH');
- }
- if (reformationstagStates.includes(bundesland)) {
- holidays.push({ date: new Date(year, 9, 31), name: 'Reformationstag' });
- }
-
- // Allerheiligen (BW, BY, NW, RP, SL)
- if (['BW', 'BY', 'NW', 'RP', 'SL'].includes(bundesland)) {
- holidays.push({ date: new Date(year, 10, 1), name: 'Allerheiligen' });
- }
-
- // Buß- und Bettag (only SN)
- if (bundesland === 'SN') {
- // Buß- und Bettag is the Wednesday before November 23
- let bussbettag = new Date(year, 10, 23);
- while (bussbettag.getDay() !== 3) { // 3 = Wednesday
- bussbettag.setDate(bussbettag.getDate() - 1);
- }
- bussbettag.setDate(bussbettag.getDate() - 7); // One week before
- holidays.push({ date: bussbettag, name: 'Buß- und Bettag' });
- }
-
- // Easter-dependent holidays
- const easter = getEasterSunday(year);
-
- // Karfreitag (Good Friday) - 2 days before Easter (all states)
- const goodFriday = new Date(easter);
- goodFriday.setDate(easter.getDate() - 2);
- holidays.push({ date: goodFriday, name: 'Karfreitag' });
-
- // Ostermontag (Easter Monday) - 1 day after Easter (all states)
- const easterMonday = new Date(easter);
- easterMonday.setDate(easter.getDate() + 1);
- holidays.push({ date: easterMonday, name: 'Ostermontag' });
-
- // Christi Himmelfahrt (Ascension Day) - 39 days after Easter (all states)
- const ascension = new Date(easter);
- ascension.setDate(easter.getDate() + 39);
- holidays.push({ date: ascension, name: 'Christi Himmelfahrt' });
-
- // Pfingstmontag (Whit Monday) - 50 days after Easter (all states)
- const whitMonday = new Date(easter);
- whitMonday.setDate(easter.getDate() + 50);
- holidays.push({ date: whitMonday, name: 'Pfingstmontag' });
-
- // Fronleichnam (Corpus Christi) - 60 days after Easter (BW, BY, HE, NW, RP, SL, + some communities in SN, TH)
- if (['BW', 'BY', 'HE', 'NW', 'RP', 'SL'].includes(bundesland)) {
- const corpusChristi = new Date(easter);
- corpusChristi.setDate(easter.getDate() + 60);
- holidays.push({ date: corpusChristi, name: 'Fronleichnam' });
- }
-
- // Mariä Himmelfahrt (Assumption of Mary) - August 15 (BY in some communities, SL)
- if (['SL'].includes(bundesland)) {
- holidays.push({ date: new Date(year, 7, 15), name: 'Mariä Himmelfahrt' });
- }
-
- return holidays;
-}
-
-/**
- * Check if date is a public holiday
- * Returns the holiday name or null
- */
-function getHolidayName(date) {
- const year = date.getFullYear();
- const holidays = getPublicHolidays(year, currentBundesland);
-
- const dateStr = date.toISOString().split('T')[0];
- const holiday = holidays.find(h => {
- return h.date.toISOString().split('T')[0] === dateStr;
- });
-
- return holiday ? holiday.name : null;
-}
-
-/**
- * Check if date is a public holiday in Baden-Württemberg
- */
-function isPublicHoliday(date) {
- return getHolidayName(date) !== null;
-}
-
-/**
- * Check if date is weekend or public holiday
- */
-function isWeekendOrHoliday(date) {
- return isWeekend(date) || isPublicHoliday(date);
-}
-
/**
* Get month name in German
*/
@@ -641,134 +388,9 @@ function pauseTimer(durationSeconds) {
}, durationSeconds * 1000);
}
-/**
- * Fetch entries from the backend
- */
-async function fetchEntries(fromDate = null, toDate = null) {
- try {
- let url = '/api/entries';
- const params = new URLSearchParams();
-
- if (fromDate) params.append('from', fromDate);
- if (toDate) params.append('to', toDate);
-
- if (params.toString()) {
- url += '?' + params.toString();
- }
-
- const response = await fetch(url);
-
- if (!response.ok) {
- throw new Error('Failed to fetch entries');
- }
-
- const entries = await response.json();
- return entries;
- } catch (error) {
- console.error('Error fetching entries:', error);
- showNotification('Fehler beim Laden der Einträge', 'error');
- return [];
- }
-}
-
-/**
- * Create a new entry
- */
-async function createEntry(date, startTime, endTime, pauseMinutes, location) {
- try {
- const body = {
- date: formatDateISO(date),
- startTime,
- endTime,
- location: location || 'office'
- };
-
- // Only include pauseMinutes if explicitly provided (not empty)
- if (pauseMinutes !== null && pauseMinutes !== undefined && pauseMinutes !== '') {
- body.pauseMinutes = parseInt(pauseMinutes);
- }
-
- const response = await fetch('/api/entries', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(body)
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error || 'Failed to create entry');
- }
-
- const entry = await response.json();
- return entry;
- } catch (error) {
- console.error('Error creating entry:', error);
- showNotification(error.message || 'Fehler beim Erstellen des Eintrags', 'error');
- return null;
- }
-}
-
-/**
- * Update an existing entry
- */
-async function updateEntry(id, date, startTime, endTime, pauseMinutes, location) {
- try {
- const body = {
- date: formatDateISO(date),
- startTime,
- endTime,
- location: location || 'office'
- };
-
- // Only include pauseMinutes if explicitly provided (not empty)
- if (pauseMinutes !== null && pauseMinutes !== undefined && pauseMinutes !== '') {
- body.pauseMinutes = parseInt(pauseMinutes);
- }
-
- const response = await fetch(`/api/entries/${id}`, {
- method: 'PUT',
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify(body)
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error || 'Failed to update entry');
- }
-
- const entry = await response.json();
- return entry;
- } catch (error) {
- console.error('Error updating entry:', error);
- showNotification(error.message || 'Fehler beim Aktualisieren des Eintrags', 'error');
- return null;
- }
-}
-
-/**
- * Delete an entry
- */
-async function deleteEntry(id) {
- try {
- const response = await fetch(`/api/entries/${id}`, {
- method: 'DELETE'
- });
-
- if (!response.ok) {
- throw new Error('Failed to delete entry');
- }
-
- return true;
- } catch (error) {
- console.error('Error deleting entry:', error);
- showNotification('Fehler beim Löschen des Eintrags', 'error');
- return false;
- }
-}
+// ============================================
+// SETTINGS API
+// ============================================
/**
* Get a setting by key
@@ -1142,9 +764,11 @@ function renderMonthlyView(entries) {
|
+ ${!holidayName ? `
+ ` : ''}
${!weekend ? `
|