Files
org-parking/frontend/js/utils.js
Stefano Manfredi c74a0ed350 Initial commit: Parking Manager
Features:
- Manager-centric parking spot management
- Fair assignment algorithm (parking/presence ratio)
- Presence tracking calendar
- Closing days (specific & weekly recurring)
- Guarantees and exclusions
- Authelia/LLDAP integration for SSO

Stack:
- FastAPI backend
- SQLite database
- Vanilla JS frontend
- Docker deployment
2025-11-26 23:37:50 +00:00

223 lines
5.6 KiB
JavaScript

/**
* 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('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric'
});
}
/**
* Get month name
*/
function getMonthName(month) {
const months = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
];
return months[month];
}
/**
* Get day name
*/
function getDayName(dayIndex) {
const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
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 = 0) {
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('en-US', {
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;
top: 1rem;
right: 1rem;
z-index: 9999;
display: flex;
flex-direction: column;
gap: 0.5rem;
`;
document.body.appendChild(toastContainer);
}
const toast = document.createElement('div');
toast.className = `message ${type}`;
toast.style.cssText = `
padding: 0.75rem 1rem;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0,0,0,0.15);
animation: slideIn 0.2s ease;
`;
toast.textContent = message;
toastContainer.appendChild(toast);
if (duration > 0) {
setTimeout(() => {
toast.style.animation = 'slideOut 0.2s ease';
setTimeout(() => toast.remove(), 200);
}, duration);
}
}
/**
* Close modal when clicking outside
*/
function setupModalClose(modalId) {
const modal = document.getElementById(modalId);
if (modal) {
modal.addEventListener('click', (e) => {
if (e.target.id === modalId) {
modal.style.display = 'none';
}
});
}
}
// Export utilities
window.utils = {
loadHolidaysForYear,
isItalianHoliday,
formatDate,
formatDateDisplay,
formatDateShort,
getMonthName,
getDayName,
getDaysInMonth,
getWeekStart,
showMessage,
setupModalClose
};