feat: aggiunti: loggica random, tema scuro, correzioni mail, miglioramenti generali, cache;
This commit is contained in:
@@ -175,11 +175,12 @@ function renderCalendar() {
|
||||
return date >= start && date <= end;
|
||||
});
|
||||
|
||||
const isClosed = isWeeklyClosed || isSpecificClosed;
|
||||
const isClosed = isHoliday || isWeeklyClosed || isSpecificClosed;
|
||||
|
||||
if (isClosed) {
|
||||
cell.classList.add('closed');
|
||||
cell.title = "Ufficio Chiuso";
|
||||
if (isHoliday) cell.title = "Festività";
|
||||
else cell.title = "Gruppo Chiuso";
|
||||
} else if (presence) {
|
||||
cell.classList.add(`status-${presence.status}`);
|
||||
}
|
||||
@@ -418,10 +419,10 @@ function initParkingStatus() {
|
||||
if (headerDisplay) headerDisplay.textContent = currentUser.office_name;
|
||||
} else {
|
||||
const nameDisplay = document.getElementById('statusOfficeName');
|
||||
if (nameDisplay) nameDisplay.textContent = 'Tuo Ufficio';
|
||||
if (nameDisplay) nameDisplay.textContent = 'Tuo Gruppo';
|
||||
|
||||
const headerDisplay = document.getElementById('currentOfficeDisplay');
|
||||
if (headerDisplay) headerDisplay.textContent = 'Tuo Ufficio';
|
||||
if (headerDisplay) headerDisplay.textContent = 'Tuo Gruppo';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -455,6 +456,34 @@ async function loadDailyStatus() {
|
||||
const officeId = currentUser.office_id;
|
||||
|
||||
const grid = document.getElementById('spotsGrid');
|
||||
const badge = document.getElementById('spotsCountBadge');
|
||||
|
||||
// Check if it's a closing day
|
||||
const dayOfWeek = statusDate.getDay();
|
||||
const isHoliday = utils.isItalianHoliday(statusDate);
|
||||
const isWeeklyClosed = weeklyClosingDays.includes(dayOfWeek);
|
||||
const isSpecificClosed = specificClosingDays.some(d => {
|
||||
const start = new Date(d.date);
|
||||
const end = d.end_date ? new Date(d.end_date) : start;
|
||||
start.setHours(0, 0, 0, 0); end.setHours(0, 0, 0, 0);
|
||||
const check = new Date(statusDate); check.setHours(0, 0, 0, 0);
|
||||
return check >= start && check <= end;
|
||||
});
|
||||
|
||||
if (isHoliday || isWeeklyClosed || isSpecificClosed) {
|
||||
if (grid) {
|
||||
grid.innerHTML = `
|
||||
<div style="width:100%; text-align:center; padding:1rem 1rem; color: var(--text-secondary); background: var(--bg-hover); border-radius: 8px;">
|
||||
<div style="font-size: 1.2rem; font-weight: 500;">Ufficio Chiuso</div>
|
||||
<div style="font-size: 0.9rem; margin-top: 0.5rem; opacity: 0.8;">Nessun parcheggio disponibile in questa data.</div>
|
||||
</div>`;
|
||||
}
|
||||
if (badge) badge.style.display = 'none';
|
||||
return;
|
||||
} else {
|
||||
if (badge) badge.style.display = 'inline-block';
|
||||
}
|
||||
|
||||
// Keep grid height to avoid jump if possible, or just loading styling
|
||||
if (grid) grid.innerHTML = '<div style="width:100%; text-align:center; padding:2rem; color:var(--text-secondary);">Caricamento...</div>';
|
||||
|
||||
@@ -505,10 +534,9 @@ function renderParkingStatus(assignments) {
|
||||
// Colors: Free = Green (default), Occupied = Yellow (requested)
|
||||
// Yellow palette: Border #eab308, bg #fefce8, text #a16207, icon #eab308
|
||||
|
||||
const borderColor = isFree ? '#22c55e' : '#eab308';
|
||||
const bgColor = isFree ? '#f0fdf4' : '#fefce8';
|
||||
const textColor = isFree ? '#15803d' : '#a16207';
|
||||
const iconColor = isFree ? '#22c55e' : '#eab308';
|
||||
const borderColor = isFree ? 'var(--spot-free-border)' : 'var(--spot-occ-border)';
|
||||
const bgColor = isFree ? 'var(--spot-free-bg)' : 'var(--spot-occ-bg)';
|
||||
const textColor = isFree ? 'var(--spot-free-text)' : 'var(--spot-occ-text)';
|
||||
|
||||
const el = document.createElement('div');
|
||||
el.className = 'spot-card';
|
||||
@@ -527,15 +555,49 @@ function renderParkingStatus(assignments) {
|
||||
transition: all 0.2s;
|
||||
`;
|
||||
|
||||
// New Car Icon (Front Facing Sedan style or similar simple shape)
|
||||
// Using a cleaner SVG path
|
||||
el.innerHTML = `
|
||||
<div style="font-weight: 600; font-size: 1.1rem; color: #1f2937;">${spotName}</div>
|
||||
<div style="font-weight: 600; font-size: 1.1rem; color: var(--text);">${spotName}</div>
|
||||
<div style="color: ${textColor}; font-size: 0.85rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 100%;" title="${statusText}">
|
||||
${statusText}
|
||||
</div>
|
||||
`;
|
||||
|
||||
if (isFree && (currentUser.role === 'admin' || currentUser.role === 'manager')) {
|
||||
el.style.cursor = 'pointer';
|
||||
el.addEventListener('mouseenter', () => el.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)');
|
||||
el.addEventListener('mouseleave', () => el.style.boxShadow = 'none');
|
||||
el.title = "Clicca per assegnare manualmente";
|
||||
el.addEventListener('click', () => {
|
||||
openAdminAssignModal(a.spot_id, spotName);
|
||||
});
|
||||
}
|
||||
|
||||
if (!isFree && (currentUser.role === 'admin' || currentUser.role === 'manager')) {
|
||||
el.style.cursor = 'pointer';
|
||||
el.addEventListener('mouseenter', () => el.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)');
|
||||
el.addEventListener('mouseleave', () => el.style.boxShadow = 'none');
|
||||
el.title = "Clicca per liberare questo posto";
|
||||
el.addEventListener('click', async () => {
|
||||
if (!confirm(`Vuoi liberare il posto ${spotName} occupato da ${statusText}?`)) return;
|
||||
|
||||
utils.showMessage('Rilascio in corso...', 'warning');
|
||||
const response = await api.post('/api/parking/reassign-spot', {
|
||||
assignment_id: a.id,
|
||||
new_user_id: null
|
||||
});
|
||||
|
||||
if (response && response.ok) {
|
||||
utils.showMessage('Posto liberato con successo', 'success');
|
||||
loadDailyStatus();
|
||||
loadParkingAssignments();
|
||||
renderCalendar();
|
||||
} else {
|
||||
const err = await response.json();
|
||||
utils.showMessage(err.detail || 'Impossibile liberare il posto', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
grid.appendChild(el);
|
||||
});
|
||||
|
||||
@@ -569,6 +631,97 @@ function setupStatusListeners() {
|
||||
loadDailyStatus();
|
||||
}
|
||||
});
|
||||
|
||||
setupAdminAssignModal();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Admin Manual Assign Logic
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
let currentManualSpotId = null;
|
||||
|
||||
function setupAdminAssignModal() {
|
||||
const closeBtn = document.getElementById('closeAdminAssignModal');
|
||||
const cancelBtn = document.getElementById('cancelAdminAssign');
|
||||
const form = document.getElementById('adminAssignForm');
|
||||
const modal = document.getElementById('adminAssignSpotModal');
|
||||
|
||||
if (closeBtn) closeBtn.addEventListener('click', () => modal.style.display = 'none');
|
||||
if (cancelBtn) cancelBtn.addEventListener('click', () => modal.style.display = 'none');
|
||||
|
||||
if (form) {
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const targetUserId = document.getElementById('adminAssignUser').value;
|
||||
if (!targetUserId || !currentManualSpotId) return;
|
||||
|
||||
const dateStr = utils.formatDate(statusDate);
|
||||
|
||||
utils.showMessage('Assegnazione in corso...', 'warning');
|
||||
const response = await api.post('/api/parking/manual-assign', {
|
||||
date: dateStr,
|
||||
user_id: targetUserId,
|
||||
spot_id: currentManualSpotId,
|
||||
office_id: currentUser.office_id
|
||||
});
|
||||
|
||||
if (response && response.ok) {
|
||||
utils.showMessage('Posto assegnato con successo', 'success');
|
||||
modal.style.display = 'none';
|
||||
loadDailyStatus(); // refresh parking status grid
|
||||
// optionally refresh my presences if it affected the logged in admin
|
||||
loadParkingAssignments();
|
||||
} else {
|
||||
const err = await response.json();
|
||||
utils.showMessage(err.detail || 'Impossibile assegnare il parcheggio', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function openAdminAssignModal(spotId, spotName) {
|
||||
currentManualSpotId = spotId;
|
||||
const modal = document.getElementById('adminAssignSpotModal');
|
||||
const infoDisplay = document.getElementById('adminAssignSpotInfo');
|
||||
const selectUser = document.getElementById('adminAssignUser');
|
||||
const dateStr = utils.formatDate(statusDate);
|
||||
|
||||
infoDisplay.innerHTML = `Seleziona l'utente a cui assegnare il posto <strong>${spotName}</strong> per la giornata del <strong>${dateStr}</strong>.`;
|
||||
selectUser.innerHTML = '<option value="">Caricamento...</option>';
|
||||
modal.style.display = 'flex';
|
||||
|
||||
// Fetch team presences for the office effectively identifying people physically present but without parking
|
||||
try {
|
||||
const response = await api.get(`/api/presence/team?start_date=${dateStr}&end_date=${dateStr}&office_id=${currentUser.office_id}`);
|
||||
if (response && response.ok) {
|
||||
const result = await response.json();
|
||||
const members = Array.isArray(result) ? result : [];
|
||||
|
||||
// Filter out people who already have parking
|
||||
const availableMembers = members.filter(m => {
|
||||
const presence = m.presences && m.presences.find(p => p.date === dateStr);
|
||||
const hasParking = m.parking_dates && m.parking_dates.includes(dateStr);
|
||||
return presence && presence.status === 'present' && !hasParking;
|
||||
});
|
||||
|
||||
if (availableMembers.length === 0) {
|
||||
selectUser.innerHTML = '<option value="">Nessun utente presente e senza parcheggio oggi...</option>';
|
||||
} else {
|
||||
selectUser.innerHTML = '<option value="">Seleziona utente...</option>';
|
||||
availableMembers.forEach(user => {
|
||||
const option = document.createElement('option');
|
||||
option.value = user.id;
|
||||
option.textContent = `${user.name}`;
|
||||
selectUser.appendChild(option);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Errore fetch team presences:", e);
|
||||
console.error(e);
|
||||
selectUser.innerHTML = '<option value="">Errore caricamento utenti</option>';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user