/** * Utility Functions * Date handling, holidays, and common helpers */ // Holiday cache: { year: Set of date strings } const holidayCache = {}; /** * Load holidays for a year from API (called automatically) */ async function loadHolidaysForYear(year) { if (holidayCache[year]) return; try { const response = await fetch(`/api/auth/holidays/${year}`); if (response.ok) { const holidays = await response.json(); holidayCache[year] = new Set(holidays.map(h => h.date)); } } catch (e) { // Fall back to local calculation if API fails console.warn('Holiday API failed, using local fallback'); } } /** * Calculate Easter Sunday using Computus algorithm (fallback) */ function calculateEaster(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); } /** * Check if a date is an Italian holiday * Uses cached API data if available, otherwise falls back to local calculation */ function isItalianHoliday(date) { const year = date.getFullYear(); const dateStr = formatDate(date); // Check cache first if (holidayCache[year] && holidayCache[year].has(dateStr)) { return true; } // Fallback: local calculation const month = date.getMonth() + 1; const day = date.getDate(); const fixedHolidays = [ [1, 1], [1, 6], [4, 25], [5, 1], [6, 2], [8, 15], [11, 1], [12, 8], [12, 25], [12, 26] ]; for (const [hm, hd] of fixedHolidays) { if (month === hm && day === hd) return true; } // Easter Monday const easter = calculateEaster(year); const easterMonday = new Date(easter); easterMonday.setDate(easter.getDate() + 1); if (date.getMonth() === easterMonday.getMonth() && date.getDate() === easterMonday.getDate()) { return true; } return false; } /** * Format date as YYYY-MM-DD */ function formatDate(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}`; } /** * Format date for display */ function formatDateDisplay(dateStr) { const date = new Date(dateStr + 'T12:00:00'); return date.toLocaleDateString('it-IT', { weekday: 'short', month: 'short', day: 'numeric' }); } /** * Get month name */ function getMonthName(month) { const months = [ 'Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre' ]; return months[month]; } /** * Get day name */ function getDayName(dayIndex) { const days = ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab']; return days[dayIndex]; } /** * Get days in month */ function getDaysInMonth(year, month) { return new Date(year, month + 1, 0).getDate(); } /** * Get start of week for a date */ function getWeekStart(date, weekStartDay = 1) { const d = new Date(date); const day = d.getDay(); const diff = (day - weekStartDay + 7) % 7; d.setDate(d.getDate() - diff); d.setHours(0, 0, 0, 0); return d; } /** * Format date as short display (e.g., "Nov 26") */ function formatDateShort(date) { return date.toLocaleDateString('it-IT', { month: 'short', day: 'numeric' }); } /** * Show a temporary message (creates toast if no container) */ function showMessage(message, type = 'success', duration = 3000) { // Create toast container if it doesn't exist let toastContainer = document.getElementById('toastContainer'); if (!toastContainer) { toastContainer = document.createElement('div'); toastContainer.id = 'toastContainer'; toastContainer.style.cssText = ` position: fixed; bottom: 2rem; left: 50%; transform: translateX(-50%); z-index: 9999; display: flex; flex-direction: column; gap: 1rem; align-items: center; `; document.body.appendChild(toastContainer); } const toast = document.createElement('div'); toast.className = `message ${type}`; toast.style.cssText = ` padding: 1rem 1.5rem; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); animation: slideInBottom 0.3s ease; font-size: 1.1rem; font-weight: 500; min-width: 300px; text-align: center; `; toast.textContent = message; toastContainer.appendChild(toast); if (duration > 0) { setTimeout(() => { toast.style.animation = 'fadeOut 0.3s ease'; setTimeout(() => toast.remove(), 200); }, duration); } } /** * Close modal when clicking outside */ function setupModalClose(modalId) { // Behavior disabled: clicking outside does not close modal } // Export utilities window.utils = { loadHolidaysForYear, isItalianHoliday, formatDate, formatDateDisplay, formatDateShort, getMonthName, getDayName, getDaysInMonth, getWeekStart, showMessage, setupModalClose };