aggiunti trasferte, export excel, miglioramenti generali
This commit is contained in:
@@ -33,6 +33,9 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Initialize Parking Status
|
||||
initParkingStatus();
|
||||
setupStatusListeners();
|
||||
|
||||
// Initialize Exclusion Logic
|
||||
initExclusionLogic();
|
||||
});
|
||||
|
||||
async function loadPresences() {
|
||||
@@ -337,19 +340,57 @@ function setupEventListeners() {
|
||||
const promises = [];
|
||||
let current = new Date(startDate);
|
||||
|
||||
// Validate filtering
|
||||
let skippedCount = 0;
|
||||
|
||||
while (current <= endDate) {
|
||||
const dStr = current.toISOString().split('T')[0];
|
||||
if (status === 'clear') {
|
||||
promises.push(api.delete(`/api/presence/${dStr}`));
|
||||
|
||||
// Create local date for rules check (matches renderCalendar logic)
|
||||
const localCurrent = new Date(dStr + 'T00:00:00');
|
||||
const dayOfWeek = localCurrent.getDay(); // 0-6
|
||||
|
||||
// Check closing days
|
||||
// Only enforce rules if we are not clearing (or should we enforce for clearing too?
|
||||
// Usually clearing is allowed always, but "Inserimento" implies adding.
|
||||
// Ensuring we don't ADD presence on closed days is the main goal.)
|
||||
let isClosed = false;
|
||||
|
||||
if (status !== 'clear') {
|
||||
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);
|
||||
|
||||
// localCurrent is already set to 00:00:00 local
|
||||
return localCurrent >= start && localCurrent <= end;
|
||||
});
|
||||
|
||||
if (isWeeklyClosed || isSpecificClosed) isClosed = true;
|
||||
}
|
||||
|
||||
if (isClosed) {
|
||||
skippedCount++;
|
||||
} else {
|
||||
promises.push(api.post('/api/presence/mark', { date: dStr, status: status }));
|
||||
if (status === 'clear') {
|
||||
promises.push(api.delete(`/api/presence/${dStr}`));
|
||||
} else {
|
||||
promises.push(api.post('/api/presence/mark', { date: dStr, status: status }));
|
||||
}
|
||||
}
|
||||
current.setDate(current.getDate() + 1);
|
||||
}
|
||||
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
utils.showMessage('Inserimento completato!', 'success');
|
||||
if (skippedCount > 0) {
|
||||
utils.showMessage(`Inserimento completato! (${skippedCount} giorni chiusi ignorati)`, 'warning');
|
||||
} else {
|
||||
utils.showMessage('Inserimento completato!', 'success');
|
||||
}
|
||||
await Promise.all([loadPresences(), loadParkingAssignments()]);
|
||||
renderCalendar();
|
||||
} catch (err) {
|
||||
@@ -531,3 +572,211 @@ function setupStatusListeners() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Exclusion Logic
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
async function initExclusionLogic() {
|
||||
await loadExclusionStatus();
|
||||
setupExclusionListeners();
|
||||
}
|
||||
|
||||
async function loadExclusionStatus() {
|
||||
try {
|
||||
const response = await api.get('/api/users/me/exclusion');
|
||||
if (response && response.ok) {
|
||||
const data = await response.json();
|
||||
updateExclusionUI(data); // data is now a list
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Error loading exclusion status", e);
|
||||
}
|
||||
}
|
||||
|
||||
function updateExclusionUI(exclusions) {
|
||||
const statusDiv = document.getElementById('exclusionStatusDisplay');
|
||||
const manageBtn = document.getElementById('manageExclusionBtn');
|
||||
|
||||
// Always show manage button as "Aggiungi Esclusione"
|
||||
manageBtn.textContent = 'Aggiungi Esclusione';
|
||||
// Clear previous binding to avoid duplicates or simply use a new function
|
||||
// But specific listeners are set in setupExclusionListeners.
|
||||
// Actually, manageBtn logic was resetting UI.
|
||||
|
||||
if (exclusions && exclusions.length > 0) {
|
||||
statusDiv.style.display = 'block';
|
||||
|
||||
let html = '<div style="display:flex; flex-direction:column; gap:0.5rem;">';
|
||||
|
||||
exclusions.forEach(ex => {
|
||||
let period = 'Tempo Indeterminato';
|
||||
if (ex.start_date && ex.end_date) {
|
||||
period = `${utils.formatDate(new Date(ex.start_date))} - ${utils.formatDate(new Date(ex.end_date))}`;
|
||||
} else if (ex.start_date) {
|
||||
period = `Dal ${utils.formatDate(new Date(ex.start_date))}`;
|
||||
}
|
||||
|
||||
html += `
|
||||
<div style="background: white; border: 1px solid #e5e7eb; padding: 0.75rem; border-radius: 4px; display: flex; justify-content: space-between; align-items: center;">
|
||||
<div>
|
||||
<div style="font-weight: 500; font-size: 0.9rem;">${period}</div>
|
||||
${ex.notes ? `<div style="font-size: 0.8rem; color: #6b7280;">${ex.notes}</div>` : ''}
|
||||
</div>
|
||||
<div style="display: flex; gap: 0.5rem;">
|
||||
<button class="btn-icon" onclick='openEditMyExclusion("${ex.id}", ${JSON.stringify(ex).replace(/'/g, "'")})' title="Modifica">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="btn-icon btn-danger" onclick="deleteMyExclusion('${ex.id}')" title="Rimuovi">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
</div>`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
statusDiv.innerHTML = html;
|
||||
|
||||
// Update container style for list
|
||||
statusDiv.style.backgroundColor = '#f9fafb';
|
||||
statusDiv.style.color = 'inherit';
|
||||
statusDiv.style.border = 'none'; // remove border from container, items have border
|
||||
statusDiv.style.padding = '0'; // reset padding
|
||||
|
||||
} else {
|
||||
statusDiv.style.display = 'none';
|
||||
statusDiv.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Global for edit
|
||||
let myEditingExclusionId = null;
|
||||
|
||||
function openEditMyExclusion(id, data) {
|
||||
myEditingExclusionId = id;
|
||||
const modal = document.getElementById('userExclusionModal');
|
||||
const radioForever = document.querySelector('input[name="exclusionType"][value="forever"]');
|
||||
const radioRange = document.querySelector('input[name="exclusionType"][value="range"]');
|
||||
const rangeDiv = document.getElementById('exclusionDateRange');
|
||||
const deleteBtn = document.getElementById('deleteExclusionBtn'); // Hide in edit mode (we have icon) or keep?
|
||||
// User requested "matita a destra per la modifica ed eliminazione".
|
||||
// I added trash icon to the list. So modal "Rimuovi" is redundant but harmless.
|
||||
// I'll hide it for clarity.
|
||||
if (deleteBtn) deleteBtn.style.display = 'none';
|
||||
|
||||
if (data.start_date || data.end_date) {
|
||||
radioRange.checked = true;
|
||||
rangeDiv.style.display = 'block';
|
||||
if (data.start_date) document.getElementById('ueStartDate').value = data.start_date;
|
||||
if (data.end_date) document.getElementById('ueEndDate').value = data.end_date;
|
||||
} else {
|
||||
radioForever.checked = true;
|
||||
rangeDiv.style.display = 'none';
|
||||
document.getElementById('ueStartDate').value = '';
|
||||
document.getElementById('ueEndDate').value = '';
|
||||
}
|
||||
document.getElementById('ueNotes').value = data.notes || '';
|
||||
|
||||
document.querySelector('#userExclusionModal h3').textContent = 'Modifica Esclusione';
|
||||
modal.style.display = 'flex';
|
||||
}
|
||||
|
||||
async function deleteMyExclusion(id) {
|
||||
if (!confirm('Rimuovere questa esclusione?')) return;
|
||||
const response = await api.delete(`/api/users/me/exclusion/${id}`);
|
||||
if (response && response.ok) {
|
||||
utils.showMessage('Esclusione rimossa con successo', 'success');
|
||||
loadExclusionStatus();
|
||||
} else {
|
||||
const err = await response.json();
|
||||
utils.showMessage(err.detail || 'Errore rimozione', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function resetMyExclusionForm() {
|
||||
document.getElementById('userExclusionForm').reset();
|
||||
myEditingExclusionId = null;
|
||||
document.querySelector('#userExclusionModal h3').textContent = 'Nuova Esclusione';
|
||||
|
||||
const rangeDiv = document.getElementById('exclusionDateRange');
|
||||
rangeDiv.style.display = 'none';
|
||||
document.querySelector('input[name="exclusionType"][value="forever"]').checked = true;
|
||||
|
||||
// Hide delete btn in modal (using list icon instead)
|
||||
const deleteBtn = document.getElementById('deleteExclusionBtn');
|
||||
if (deleteBtn) deleteBtn.style.display = 'none';
|
||||
}
|
||||
|
||||
function setupExclusionListeners() {
|
||||
const modal = document.getElementById('userExclusionModal');
|
||||
const manageBtn = document.getElementById('manageExclusionBtn');
|
||||
const closeBtn = document.getElementById('closeUserExclusionModal');
|
||||
const cancelBtn = document.getElementById('cancelUserExclusion');
|
||||
const form = document.getElementById('userExclusionForm');
|
||||
|
||||
const radioForever = document.querySelector('input[name="exclusionType"][value="forever"]');
|
||||
const radioRange = document.querySelector('input[name="exclusionType"][value="range"]');
|
||||
const rangeDiv = document.getElementById('exclusionDateRange');
|
||||
|
||||
if (manageBtn) {
|
||||
manageBtn.addEventListener('click', () => {
|
||||
resetMyExclusionForm();
|
||||
modal.style.display = 'flex';
|
||||
});
|
||||
}
|
||||
|
||||
if (closeBtn) closeBtn.addEventListener('click', () => modal.style.display = 'none');
|
||||
if (cancelBtn) cancelBtn.addEventListener('click', () => modal.style.display = 'none');
|
||||
|
||||
// Radio logic
|
||||
radioForever.addEventListener('change', () => rangeDiv.style.display = 'none');
|
||||
radioRange.addEventListener('change', () => rangeDiv.style.display = 'block');
|
||||
|
||||
// Save
|
||||
if (form) {
|
||||
form.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
const type = document.querySelector('input[name="exclusionType"]:checked').value;
|
||||
const payload = {
|
||||
notes: document.getElementById('ueNotes').value
|
||||
};
|
||||
|
||||
if (type === 'range') {
|
||||
const start = document.getElementById('ueStartDate').value;
|
||||
const end = document.getElementById('ueEndDate').value;
|
||||
|
||||
if (start) payload.start_date = start;
|
||||
if (end) payload.end_date = end;
|
||||
|
||||
if (start && end && new Date(end) < new Date(start)) {
|
||||
return utils.showMessage('La data di fine deve essere dopo la data di inizio', 'error');
|
||||
}
|
||||
} else {
|
||||
payload.start_date = null;
|
||||
payload.end_date = null;
|
||||
}
|
||||
|
||||
let response;
|
||||
if (myEditingExclusionId) {
|
||||
response = await api.put(`/api/users/me/exclusion/${myEditingExclusionId}`, payload);
|
||||
} else {
|
||||
response = await api.post('/api/users/me/exclusion', payload);
|
||||
}
|
||||
|
||||
if (response && response.ok) {
|
||||
utils.showMessage('Esclusione salvata', 'success');
|
||||
modal.style.display = 'none';
|
||||
loadExclusionStatus();
|
||||
} else {
|
||||
const err = await response.json();
|
||||
utils.showMessage(err.detail || 'Errore salvataggio', 'error');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Globals
|
||||
window.openEditMyExclusion = openEditMyExclusion;
|
||||
window.deleteMyExclusion = deleteMyExclusion;
|
||||
|
||||
Reference in New Issue
Block a user