feat: add company holiday preference feature with UI and logic for holiday selection
All checks were successful
Build and Push Docker Image / build (push) Successful in 29s
All checks were successful
Build and Push Docker Image / build (push) Successful in 29s
This commit is contained in:
@@ -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 = `
|
||||
<span class="toast-icon">${icons[type] || 'ℹ'}</span>
|
||||
<span>${message}</span>
|
||||
`;
|
||||
|
||||
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) {
|
||||
</td>
|
||||
<td class="px-4 py-3 whitespace-nowrap text-sm text-center">
|
||||
<div class="flex gap-1 justify-center">
|
||||
${!holidayName ? `
|
||||
<button class="btn-add-work inline-flex items-center justify-center w-7 h-7 text-indigo-400/80 hover:text-indigo-400 hover:bg-indigo-500/10 rounded transition-all" data-date="${dateISO}" title="Arbeit eintragen">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
</button>
|
||||
` : ''}
|
||||
${!weekend ? `
|
||||
<button class="btn-add-vacation inline-flex items-center justify-center w-7 h-7 text-amber-400/80 hover:text-amber-400 hover:bg-amber-500/10 rounded transition-all" data-date="${dateISO}" title="Urlaub eintragen">
|
||||
<i data-lucide="plane" class="w-4 h-4"></i>
|
||||
@@ -1229,6 +853,15 @@ function renderMonthlyView(entries) {
|
||||
async function addSpecialEntry(dateISO, entryType) {
|
||||
const typeName = entryType === 'vacation' ? 'Urlaub' : 'Gleittag';
|
||||
|
||||
// Check if date is a public holiday
|
||||
const dateObj = new Date(dateISO + 'T00:00:00');
|
||||
const holidayName = getHolidayName(dateObj);
|
||||
|
||||
if (holidayName) {
|
||||
showNotification(`❌ An Feiertagen (${holidayName}) kann kein ${typeName} eingetragen werden`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!confirm(`${typeName} für ${formatDateDisplay(dateISO)} eintragen?`)) {
|
||||
return;
|
||||
}
|
||||
@@ -1854,6 +1487,16 @@ async function handleFormSubmit(e) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if date is a public holiday
|
||||
const dateISO = formatDateISO(date);
|
||||
const dateObj = new Date(dateISO + 'T00:00:00');
|
||||
const holidayName = getHolidayName(dateObj);
|
||||
|
||||
if (holidayName && !currentEditingId) {
|
||||
showNotification(`❌ An Feiertagen (${holidayName}) können keine Arbeitszeiten eingetragen werden`, 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate max 10h net time
|
||||
const netHours = calculateNetHours(startTime, endTime, pauseMinutes);
|
||||
if (netHours > 10) {
|
||||
@@ -3013,6 +2656,16 @@ async function loadSettings() {
|
||||
if (savedEmployeeId) {
|
||||
document.getElementById('employeeId').value = savedEmployeeId;
|
||||
}
|
||||
|
||||
const savedCompanyHoliday = await getSetting('companyHoliday');
|
||||
if (savedCompanyHoliday) {
|
||||
companyHolidayPreference = savedCompanyHoliday;
|
||||
if (savedCompanyHoliday === 'christmas') {
|
||||
document.getElementById('companyHolidayChristmas').checked = true;
|
||||
} else if (savedCompanyHoliday === 'newyearseve') {
|
||||
document.getElementById('companyHolidayNewYear').checked = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -3082,6 +2735,76 @@ async function handleVacationDaysChange(event) {
|
||||
showNotification(`✓ Urlaubstage auf ${newValue} pro Jahr gesetzt`, 'success');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle company holiday preference change
|
||||
*/
|
||||
async function handleCompanyHolidayChange(event) {
|
||||
const newPreference = event.target.value;
|
||||
|
||||
// Check if there are any entries on the affected dates
|
||||
const currentYear = new Date().getFullYear();
|
||||
const years = [currentYear - 1, currentYear, currentYear + 1];
|
||||
|
||||
let affectedDate, affectedDateDisplay, holidayName;
|
||||
|
||||
if (newPreference === 'christmas') {
|
||||
affectedDate = '12-24';
|
||||
affectedDateDisplay = '24.12.';
|
||||
holidayName = 'Heiligabend';
|
||||
} else {
|
||||
affectedDate = '12-31';
|
||||
affectedDateDisplay = '31.12.';
|
||||
holidayName = 'Silvester';
|
||||
}
|
||||
|
||||
// Check for conflicts across multiple years
|
||||
const conflicts = [];
|
||||
try {
|
||||
const allEntries = await fetchEntries();
|
||||
|
||||
years.forEach(year => {
|
||||
const dateToCheck = `${year}-${affectedDate}`;
|
||||
const hasEntry = allEntries.some(e => e.date === dateToCheck);
|
||||
|
||||
if (hasEntry) {
|
||||
conflicts.push(`${affectedDateDisplay}${year}`);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error checking conflicts:', error);
|
||||
}
|
||||
|
||||
// Show warning if there are existing entries on the new holiday date
|
||||
if (conflicts.length > 0) {
|
||||
const confirmed = confirm(
|
||||
`⚠️ Hinweis: Auf ${holidayName} existieren bereits Einträge:\n\n` +
|
||||
conflicts.join(', ') + '\n\n' +
|
||||
`Diese Tage werden nun als betriebsfrei markiert.\n\n` +
|
||||
`Möchten Sie fortfahren?`
|
||||
);
|
||||
|
||||
if (!confirmed) {
|
||||
// Revert the radio button selection
|
||||
if (newPreference === 'christmas') {
|
||||
document.getElementById('companyHolidayNewYear').checked = true;
|
||||
} else {
|
||||
document.getElementById('companyHolidayChristmas').checked = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Update setting
|
||||
companyHolidayPreference = newPreference;
|
||||
await setSetting('companyHoliday', newPreference);
|
||||
|
||||
// Reload view to show updated holidays
|
||||
await reloadView();
|
||||
|
||||
const holidayLabel = newPreference === 'christmas' ? 'Heiligabend (24.12.)' : 'Silvester (31.12.)';
|
||||
showNotification(`✓ Betriebsfrei auf ${holidayLabel} gesetzt`, 'success');
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// INLINE EDITING
|
||||
// ============================================
|
||||
@@ -3295,6 +3018,12 @@ function handleFilter() {
|
||||
currentFilterTo = toDate;
|
||||
hideMonthNavigation();
|
||||
|
||||
// Hide bridge days recommendations in filter view
|
||||
const bridgeDaysContainer = document.getElementById('bridgeDaysContainer');
|
||||
if (bridgeDaysContainer) {
|
||||
bridgeDaysContainer.classList.add('hidden');
|
||||
}
|
||||
|
||||
loadEntries(fromDate, toDate);
|
||||
}
|
||||
|
||||
@@ -3734,27 +3463,6 @@ async function importDatabase(file) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a temporary notification
|
||||
*/
|
||||
function showNotification(message, type = 'info') {
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `fixed top-4 right-4 px-6 py-3 rounded-lg shadow-lg text-white z-50 ${
|
||||
type === 'success' ? 'bg-green-600' :
|
||||
type === 'error' ? 'bg-red-600' :
|
||||
'bg-blue-600'
|
||||
}`;
|
||||
notification.textContent = message;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.style.transition = 'opacity 0.5s';
|
||||
notification.style.opacity = '0';
|
||||
setTimeout(() => document.body.removeChild(notification), 500);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// FLATPICKR INITIALIZATION
|
||||
// ============================================
|
||||
@@ -4042,6 +3750,10 @@ function initializeEventListeners() {
|
||||
// Vacation days input
|
||||
document.getElementById('vacationDaysInput').addEventListener('change', handleVacationDaysChange);
|
||||
|
||||
// Company holiday preference
|
||||
document.getElementById('companyHolidayChristmas').addEventListener('change', handleCompanyHolidayChange);
|
||||
document.getElementById('companyHolidayNewYear').addEventListener('change', handleCompanyHolidayChange);
|
||||
|
||||
// Employee name input
|
||||
document.getElementById('employeeName').addEventListener('change', async (e) => {
|
||||
await setSetting('employeeName', e.target.value);
|
||||
|
||||
Reference in New Issue
Block a user