/** * Team Calendar Page * Shows presence and parking for all team members * Filtered by office (office-centric model) */ let currentUser = null; let currentStartDate = null; let viewMode = 'week'; // 'week' or 'month' let offices = []; let teamData = []; let parkingDataLookup = {}; let parkingAssignmentLookup = {}; let selectedUserId = null; let selectedDate = null; let currentAssignmentId = null; let officeClosingRules = {}; // { officeId: { weekly: [], specific: [] } } document.addEventListener('DOMContentLoaded', async () => { currentUser = await api.requireAuth(); if (!currentUser) return; // Initialize start date based on week start preference const weekStartDay = currentUser.week_start_day || 1; currentStartDate = utils.getWeekStart(new Date(), weekStartDay); await loadOffices(); await loadTeamData(); await loadTeamData(); // Initialize Modal Logic ModalLogic.init({ onMarkPresence: handleMarkPresence, onClearPresence: handleClearPresence, onReleaseParking: handleReleaseParking, onReassignParking: handleReassignParking }); renderCalendar(); setupEventListeners(); }); function updateOfficeDisplay() { const display = document.getElementById('currentOfficeNameDisplay'); if (!display) return; const select = document.getElementById('officeFilter'); // If user is employee, show their office name directly if (currentUser.role === 'employee') { display.textContent = currentUser.office_name || "Mio Ufficio"; return; } // For admin/manager, show selected if (select && select.value) { // Find name in options const option = select.options[select.selectedIndex]; if (option) { // Remove the count (xx utenti) part if desired, or keep it. // User requested "nome del'ufficio", let's keep it simple. // Option text is "Name (Count users)" // let text = option.textContent.split('(')[0].trim(); display.textContent = option.textContent; } else { display.textContent = "Tutti gli Uffici"; } } else { display.textContent = "Tutti gli Uffici"; } } async function loadOffices() { const select = document.getElementById('officeFilter'); // Only Admins and Managers can list offices // Employees will just see their own office logic handled in loadTeamData // Only Admins can see the office selector if (currentUser.role !== 'admin') { select.style.display = 'none'; // Employees stop here, Managers continue to allow auto-selection logic below if (currentUser.role === 'employee') return; } const response = await api.get('/api/offices'); if (response && response.ok) { offices = await response.json(); let filteredOffices = offices; if (currentUser.role === 'manager') { // Manager only sees their own office in the filter? // Actually managers might want to filter if they (hypothetically) managed multiple, // but currently User has 1 office. if (currentUser.office_id) { filteredOffices = offices.filter(o => o.id === currentUser.office_id); } else { filteredOffices = []; } } filteredOffices.forEach(office => { const option = document.createElement('option'); option.value = office.id; option.textContent = `${office.name} (${office.user_count || 0} utenti)`; select.appendChild(option); }); // Auto-select for managers if (currentUser.role === 'manager' && filteredOffices.length === 1) { select.value = filteredOffices[0].id; } } // Initial update of office display updateOfficeDisplay(); // Show export card for Admin/Manager if (['admin', 'manager'].includes(currentUser.role)) { const exportCard = document.getElementById('exportCard'); if (exportCard) { exportCard.style.display = 'block'; // Set defaults (current month) const today = new Date(); const firstDay = new Date(today.getFullYear(), today.getMonth(), 1); const lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0); document.getElementById('exportStartDate').valueAsDate = firstDay; document.getElementById('exportEndDate').valueAsDate = lastDay; document.getElementById('exportBtn').addEventListener('click', handleExport); } } } async function handleExport() { const startStr = document.getElementById('exportStartDate').value; const endStr = document.getElementById('exportEndDate').value; const officeId = document.getElementById('officeFilter').value; if (!startStr || !endStr) { alert('Seleziona le date di inizio e fine'); return; } // Construct URL let url = `/api/reports/team-export?start_date=${startStr}&end_date=${endStr}`; if (officeId) { url += `&office_id=${officeId}`; } try { const btn = document.getElementById('exportBtn'); const originalText = btn.innerHTML; btn.disabled = true; btn.textContent = 'Generazione...'; // Use fetch directly to handle blob const token = api.getToken(); const headers = token ? { 'Authorization': `Bearer ${token}` } : {}; const response = await fetch(url, { headers }); if (response.ok) { const blob = await response.blob(); const downloadUrl = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = downloadUrl; a.download = `report_presenze_${startStr}_${endStr}.xlsx`; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(downloadUrl); a.remove(); } else { const err = await response.json(); alert('Errore export: ' + (err.detail || 'Sconosciuto')); } btn.disabled = false; btn.innerHTML = originalText; } catch (e) { console.error(e); alert('Errore durante l\'export'); document.getElementById('exportBtn').disabled = false; document.getElementById('exportBtn').textContent = 'Esporta Excel'; } } function getDateRange() { let startDate = new Date(currentStartDate); let endDate = new Date(currentStartDate); if (viewMode === 'week') { endDate.setDate(endDate.getDate() + 6); } else { // Month view - start from first day of month startDate = new Date(currentStartDate.getFullYear(), currentStartDate.getMonth(), 1); endDate = new Date(currentStartDate.getFullYear(), currentStartDate.getMonth() + 1, 0); } return { startDate, endDate }; } async function loadTeamData() { await loadClosingData(); const { startDate, endDate } = getDateRange(); const startStr = utils.formatDate(startDate); const endStr = utils.formatDate(endDate); let url = `/api/presence/team?start_date=${startStr}&end_date=${endStr}`; const officeFilter = document.getElementById('officeFilter').value; if (officeFilter) { url += `&office_id=${officeFilter}`; } const response = await api.get(url); if (response && response.ok) { teamData = await response.json(); // Build parking lookup with spot names and assignment IDs parkingDataLookup = {}; parkingAssignmentLookup = {}; teamData.forEach(member => { if (member.parking_info) { member.parking_info.forEach(p => { const key = `${member.id}_${p.date}`; parkingDataLookup[key] = p.spot_display_name || p.spot_id; parkingAssignmentLookup[key] = p.id; }); } }); } } async function loadClosingData() { officeClosingRules = {}; let officeIdsToLoad = []; const selectedOfficeId = document.getElementById('officeFilter').value; if (selectedOfficeId) { officeIdsToLoad = [selectedOfficeId]; } else if (currentUser.role === 'employee' || (currentUser.role === 'manager' && currentUser.office_id)) { officeIdsToLoad = [currentUser.office_id]; } else if (offices.length > 0) { // Admin viewing all or Manager with access to list officeIdsToLoad = offices.map(o => o.id); } if (officeIdsToLoad.length === 0) return; // Fetch in parallel const promises = officeIdsToLoad.map(async (oid) => { try { const [weeklyRes, specificRes] = await Promise.all([ api.get(`/api/offices/${oid}/weekly-closing-days`), api.get(`/api/offices/${oid}/closing-days`) ]); officeClosingRules[oid] = { weekly: [], specific: [] }; if (weeklyRes && weeklyRes.ok) { const days = await weeklyRes.json(); officeClosingRules[oid].weekly = days.map(d => d.weekday); } if (specificRes && specificRes.ok) { officeClosingRules[oid].specific = await specificRes.json(); // OPTIMIZATION: Pre-calculate all specific closed dates into a Set const closedSet = new Set(); officeClosingRules[oid].specific.forEach(range => { let start = new Date(range.date); let end = range.end_date ? new Date(range.end_date) : new Date(range.date); // Normalize to noon to avoid timezone issues when stepping start.setHours(12, 0, 0, 0); end.setHours(12, 0, 0, 0); let current = new Date(start); while (current <= end) { closedSet.add(utils.formatDate(current)); current.setDate(current.getDate() + 1); } }); officeClosingRules[oid].closedDatesSet = closedSet; } } catch (e) { console.error(`Error loading closing days for office ${oid}:`, e); } }); await Promise.all(promises); } function renderCalendar() { const header = document.getElementById('calendarHeader'); const body = document.getElementById('calendarBody'); const { startDate, endDate } = getDateRange(); // Update header text if (viewMode === 'week') { document.getElementById('currentWeek').textContent = `${utils.formatDateShort(startDate)} - ${utils.formatDateShort(endDate)}`; } else { document.getElementById('currentWeek').textContent = `${utils.getMonthName(startDate.getMonth())} ${startDate.getFullYear()}`; } // Calculate number of days const dayCount = Math.round((endDate - startDate) / (1000 * 60 * 60 * 24)) + 1; // Build header row const dayNames = ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab']; let headerHtml = '