746 lines
28 KiB
JavaScript
746 lines
28 KiB
JavaScript
/**
|
|
* Team Rules Page
|
|
* Manage closing days, guarantees, and exclusions
|
|
* Office-centric model
|
|
*/
|
|
|
|
let currentUser = null;
|
|
let offices = [];
|
|
let currentOfficeId = null;
|
|
let currentOffice = null;
|
|
let officeUsers = [];
|
|
let currentWeeklyClosingDays = [];
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
currentUser = await api.requireAuth();
|
|
if (!currentUser) return;
|
|
|
|
// Only admins and managers can access this page
|
|
if (currentUser.role !== 'admin' && currentUser.role !== 'manager') {
|
|
window.location.href = '/presence';
|
|
return;
|
|
}
|
|
|
|
populateHourSelect();
|
|
await loadOffices();
|
|
setupEventListeners();
|
|
});
|
|
|
|
async function loadOffices() {
|
|
const select = document.getElementById('officeSelect');
|
|
const card = document.getElementById('officeSelectionCard');
|
|
|
|
// Only Admins can see the office selector
|
|
if (currentUser.role !== 'admin') {
|
|
if (card) card.style.display = 'none';
|
|
}
|
|
|
|
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
|
|
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;
|
|
select.appendChild(option);
|
|
});
|
|
|
|
// Auto-select for managers
|
|
if (currentUser.role === 'manager' && filteredOffices.length === 1) {
|
|
select.value = filteredOffices[0].id;
|
|
loadOfficeRules(filteredOffices[0].id);
|
|
}
|
|
}
|
|
}
|
|
|
|
async function loadOfficeRules(officeId) {
|
|
if (!officeId) {
|
|
document.getElementById('rulesContent').style.display = 'none';
|
|
document.getElementById('noOfficeMessage').style.display = 'block';
|
|
return;
|
|
}
|
|
|
|
currentOfficeId = officeId;
|
|
document.getElementById('rulesContent').style.display = 'block';
|
|
document.getElementById('noOfficeMessage').style.display = 'none';
|
|
|
|
// Load full office object for algorithm settings
|
|
try {
|
|
const response = await api.get(`/api/offices/${officeId}`);
|
|
if (response && response.ok) {
|
|
currentOffice = await response.json();
|
|
// Populate algorithm form
|
|
const modeSelect = document.getElementById('assignmentModeSelect');
|
|
if (currentOffice.booking_window_enabled === false) {
|
|
modeSelect.value = 'realtime';
|
|
} else {
|
|
modeSelect.value = currentOffice.assignment_mode || 'random';
|
|
}
|
|
|
|
document.getElementById('bookingWindowHour').value = currentOffice.booking_window_end_hour ?? 18;
|
|
document.getElementById('bookingWindowMinute').value = currentOffice.booking_window_end_minute ?? 0;
|
|
updateAlgorithmVisibility();
|
|
}
|
|
} catch (e) {
|
|
console.error("Error loading office details:", e);
|
|
}
|
|
|
|
// Load users for this office (for dropdowns)
|
|
await loadOfficeUsers(officeId);
|
|
|
|
await Promise.all([
|
|
loadWeeklyClosingDays(officeId),
|
|
loadClosingDays(officeId),
|
|
loadGuarantees(officeId),
|
|
loadExclusions(officeId)
|
|
]);
|
|
}
|
|
|
|
async function loadOfficeUsers(officeId) {
|
|
const response = await api.get(`/api/offices/${officeId}/users`);
|
|
if (response && response.ok) {
|
|
officeUsers = await response.json();
|
|
}
|
|
}
|
|
|
|
function populateHourSelect() {
|
|
const hourSelect = document.getElementById('bookingWindowHour');
|
|
const minuteSelect = document.getElementById('bookingWindowMinute');
|
|
if (!hourSelect || !minuteSelect) return;
|
|
|
|
hourSelect.innerHTML = '';
|
|
for (let h = 0; h < 24; h++) {
|
|
const option = document.createElement('option');
|
|
option.value = h;
|
|
option.textContent = h.toString().padStart(2, '0');
|
|
hourSelect.appendChild(option);
|
|
}
|
|
|
|
minuteSelect.innerHTML = '';
|
|
for (let m = 0; m < 60; m++) {
|
|
const option = document.createElement('option');
|
|
option.value = m;
|
|
option.textContent = m.toString().padStart(2, '0');
|
|
minuteSelect.appendChild(option);
|
|
}
|
|
}
|
|
|
|
function updateAlgorithmVisibility() {
|
|
const mode = document.getElementById('assignmentModeSelect').value;
|
|
const group = document.getElementById('cutoffTimeGroup');
|
|
if (group) group.style.display = (mode === 'realtime') ? 'none' : 'block';
|
|
}
|
|
|
|
async function saveAlgorithmSettings(e) {
|
|
e.preventDefault();
|
|
if (!currentOfficeId) return;
|
|
|
|
const btn = e.target.querySelector('button[type="submit"]');
|
|
const originalText = btn.textContent;
|
|
btn.disabled = true;
|
|
btn.textContent = 'Salvataggio...';
|
|
|
|
const mode = document.getElementById('assignmentModeSelect').value;
|
|
|
|
const data = {
|
|
assignment_mode: mode === 'realtime' ? 'random' : mode,
|
|
booking_window_enabled: mode !== 'realtime',
|
|
booking_window_end_hour: parseInt(document.getElementById('bookingWindowHour').value),
|
|
booking_window_end_minute: parseInt(document.getElementById('bookingWindowMinute').value)
|
|
};
|
|
|
|
try {
|
|
const res = await api.put(`/api/offices/${currentOfficeId}`, data);
|
|
if (res) {
|
|
utils.showMessage('Impostazioni algoritmo salvate', 'success');
|
|
currentOffice = res;
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
utils.showMessage('Errore nel salvataggio impostazioni', 'error');
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = originalText;
|
|
}
|
|
}
|
|
|
|
// Weekly Closing Days
|
|
async function loadWeeklyClosingDays(officeId) {
|
|
const response = await api.get(`/api/offices/${officeId}/weekly-closing-days`);
|
|
if (response && response.ok) {
|
|
const days = await response.json();
|
|
currentWeeklyClosingDays = days;
|
|
const activeWeekdays = days.map(d => d.weekday);
|
|
|
|
document.querySelectorAll('#weeklyClosingDays input[type="checkbox"]').forEach(cb => {
|
|
const weekday = parseInt(cb.dataset.weekday);
|
|
cb.checked = activeWeekdays.includes(weekday);
|
|
});
|
|
}
|
|
}
|
|
|
|
async function saveWeeklyClosingDays() {
|
|
const btn = document.getElementById('saveWeeklyClosingDaysBtn');
|
|
if (!btn) return;
|
|
|
|
const originalText = btn.textContent;
|
|
btn.disabled = true;
|
|
btn.textContent = 'Salvataggio...';
|
|
|
|
try {
|
|
const promises = [];
|
|
const checkboxes = document.querySelectorAll('#weeklyClosingDays input[type="checkbox"]');
|
|
|
|
for (const cb of checkboxes) {
|
|
const weekday = parseInt(cb.dataset.weekday);
|
|
const isChecked = cb.checked;
|
|
const existingEntry = currentWeeklyClosingDays.find(d => d.weekday === weekday);
|
|
|
|
if (isChecked && !existingEntry) {
|
|
// Add
|
|
promises.push(api.post(`/api/offices/${currentOfficeId}/weekly-closing-days`, { weekday }));
|
|
} else if (!isChecked && existingEntry) {
|
|
// Remove
|
|
promises.push(api.delete(`/api/offices/${currentOfficeId}/weekly-closing-days/${existingEntry.id}`));
|
|
}
|
|
}
|
|
|
|
await Promise.all(promises);
|
|
utils.showMessage('Giorni di chiusura aggiornati', 'success');
|
|
api.invalidateCache(`/api/offices/${currentOfficeId}/weekly-closing-days`);
|
|
await loadWeeklyClosingDays(currentOfficeId);
|
|
} catch (error) {
|
|
console.error(error);
|
|
utils.showMessage('Errore durante il salvataggio', 'error');
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = originalText;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Closing Days
|
|
async function loadClosingDays(officeId) {
|
|
const response = await api.get(`/api/offices/${officeId}/closing-days`);
|
|
const container = document.getElementById('closingDaysList');
|
|
|
|
if (response && response.ok) {
|
|
const days = await response.json();
|
|
|
|
if (days.length === 0) {
|
|
container.innerHTML = '<p class="text-muted">Nessun giorno di chiusura specifico.</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = days.map(day => `
|
|
<div class="rule-item">
|
|
<div class="rule-info">
|
|
<strong>${utils.formatDateDisplay(day.date)}${day.end_date ? ' - ' + utils.formatDateDisplay(day.end_date) : ''}</strong>
|
|
${day.reason ? `<span class="rule-note">${day.reason}</span>` : ''}
|
|
</div>
|
|
<button class="btn-icon btn-danger" onclick="deleteClosingDay('${day.id}')">
|
|
×
|
|
</button>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
}
|
|
|
|
async function addClosingDay(data) {
|
|
const response = await api.post(`/api/offices/${currentOfficeId}/closing-days`, data);
|
|
if (response && response.ok) {
|
|
api.invalidateCache(`/api/offices/${currentOfficeId}/closing-days`);
|
|
await loadClosingDays(currentOfficeId);
|
|
document.getElementById('closingDayModal').style.display = 'none';
|
|
document.getElementById('closingDayForm').reset();
|
|
} else {
|
|
const error = await response.json();
|
|
alert(error.detail || 'Impossibile aggiungere il giorno di chiusura');
|
|
}
|
|
}
|
|
|
|
async function deleteClosingDay(id) {
|
|
if (!confirm('Eliminare questo giorno di chiusura?')) return;
|
|
const response = await api.delete(`/api/offices/${currentOfficeId}/closing-days/${id}`);
|
|
if (response && response.ok) {
|
|
api.invalidateCache(`/api/offices/${currentOfficeId}/closing-days`);
|
|
await loadClosingDays(currentOfficeId);
|
|
}
|
|
}
|
|
|
|
// Guarantees
|
|
async function loadGuarantees(officeId) {
|
|
const response = await api.get(`/api/offices/${officeId}/guarantees`);
|
|
const container = document.getElementById('guaranteesList');
|
|
|
|
if (response && response.ok) {
|
|
const guarantees = await response.json();
|
|
|
|
if (guarantees.length === 0) {
|
|
container.innerHTML = '<p class="text-muted">Nessuna garanzia di parcheggio attiva.</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = guarantees.map(g => `
|
|
<div class="rule-item">
|
|
<div class="rule-info">
|
|
<strong>${g.user_name || 'Utente sconosciuto'}</strong>
|
|
<span class="rule-dates">
|
|
${g.start_date ? 'Dal ' + utils.formatDateDisplay(g.start_date) : 'Da sempre'}
|
|
${g.end_date ? ' al ' + utils.formatDateDisplay(g.end_date) : ''}
|
|
</span>
|
|
${g.notes ? `<span class="rule-note">${g.notes}</span>` : ''}
|
|
</div>
|
|
<button class="btn-icon btn-danger" onclick="deleteGuarantee('${g.id}')">
|
|
×
|
|
</button>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
}
|
|
|
|
async function addGuarantee(data) {
|
|
const response = await api.post(`/api/offices/${currentOfficeId}/guarantees`, data);
|
|
if (response && response.ok) {
|
|
await loadGuarantees(currentOfficeId);
|
|
document.getElementById('guaranteeModal').style.display = 'none';
|
|
document.getElementById('guaranteeForm').reset();
|
|
} else {
|
|
const error = await response.json();
|
|
alert(error.detail || 'Impossibile aggiungere la garanzia');
|
|
}
|
|
}
|
|
|
|
async function deleteGuarantee(id) {
|
|
if (!confirm('Eliminare questa garanzia?')) return;
|
|
const response = await api.delete(`/api/offices/${currentOfficeId}/guarantees/${id}`);
|
|
if (response && response.ok) {
|
|
await loadGuarantees(currentOfficeId);
|
|
}
|
|
}
|
|
|
|
// Exclusions
|
|
async function loadExclusions(officeId) {
|
|
const response = await api.get(`/api/offices/${officeId}/exclusions`);
|
|
const container = document.getElementById('exclusionsList');
|
|
|
|
if (response && response.ok) {
|
|
const exclusions = await response.json();
|
|
|
|
if (exclusions.length === 0) {
|
|
container.innerHTML = '<p class="text-muted">Nessuna esclusione attiva.</p>';
|
|
return;
|
|
}
|
|
|
|
container.innerHTML = exclusions.map(e => `
|
|
<div class="rule-item">
|
|
<div class="rule-info">
|
|
<strong>${e.user_name || 'Utente sconosciuto'}</strong>
|
|
<span class="rule-dates">
|
|
${e.start_date ? 'Dal ' + utils.formatDateDisplay(e.start_date) : 'Da sempre'}
|
|
${e.end_date ? ' al ' + utils.formatDateDisplay(e.end_date) : ''}
|
|
</span>
|
|
${e.notes ? `<span class="rule-note">${e.notes}</span>` : ''}
|
|
</div>
|
|
<div class="rule-actions" style="display: flex; gap: 0.5rem; align-items: center;">
|
|
<button class="btn-icon" onclick='openEditExclusion("${e.id}", ${JSON.stringify(e).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="deleteExclusion('${e.id}')" title="Elimina">
|
|
×
|
|
</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
}
|
|
|
|
// Global variable to track edit mode
|
|
let editingExclusionId = null;
|
|
|
|
async function openEditExclusion(id, data) {
|
|
editingExclusionId = id;
|
|
|
|
// Populate form
|
|
populateUserSelects();
|
|
document.getElementById('exclusionUser').value = data.user_id;
|
|
// Disable user select in edit mode usually? Or allow change? API allows it.
|
|
|
|
document.getElementById('exclusionStartDate').value = data.start_date || '';
|
|
document.getElementById('exclusionEndDate').value = data.end_date || '';
|
|
document.getElementById('exclusionNotes').value = data.notes || '';
|
|
|
|
// Change modal title/button
|
|
document.querySelector('#exclusionModal h3').textContent = 'Modifica Esclusione';
|
|
document.querySelector('#exclusionForm button[type="submit"]').textContent = 'Salva Modifiche';
|
|
|
|
document.getElementById('exclusionModal').style.display = 'flex';
|
|
}
|
|
|
|
async function saveExclusion(data) {
|
|
let response;
|
|
if (editingExclusionId) {
|
|
response = await api.put(`/api/offices/${currentOfficeId}/exclusions/${editingExclusionId}`, data);
|
|
} else {
|
|
response = await api.post(`/api/offices/${currentOfficeId}/exclusions`, data);
|
|
}
|
|
|
|
if (response && response.ok) {
|
|
await loadExclusions(currentOfficeId);
|
|
document.getElementById('exclusionModal').style.display = 'none';
|
|
resetExclusionForm();
|
|
} else {
|
|
const error = await response.json();
|
|
alert(error.detail || 'Impossibile salvare l\'esclusione');
|
|
}
|
|
}
|
|
|
|
function resetExclusionForm() {
|
|
document.getElementById('exclusionForm').reset();
|
|
editingExclusionId = null;
|
|
document.querySelector('#exclusionModal h3').textContent = 'Aggiungi Esclusione Parcheggio';
|
|
document.querySelector('#exclusionForm button[type="submit"]').textContent = 'Aggiungi';
|
|
}
|
|
|
|
|
|
|
|
async function deleteExclusion(id) {
|
|
if (!confirm('Eliminare questa esclusione?')) return;
|
|
const response = await api.delete(`/api/offices/${currentOfficeId}/exclusions/${id}`);
|
|
if (response && response.ok) {
|
|
await loadExclusions(currentOfficeId);
|
|
}
|
|
}
|
|
|
|
function populateUserSelects() {
|
|
const selects = ['guaranteeUser', 'exclusionUser'];
|
|
selects.forEach(id => {
|
|
const select = document.getElementById(id);
|
|
const currentVal = select.value;
|
|
select.innerHTML = '<option value="">Seleziona utente...</option>';
|
|
|
|
officeUsers.forEach(user => {
|
|
const option = document.createElement('option');
|
|
option.value = user.id;
|
|
option.textContent = user.name;
|
|
select.appendChild(option);
|
|
});
|
|
|
|
if (currentVal) select.value = currentVal;
|
|
});
|
|
}
|
|
|
|
function setupEventListeners() {
|
|
// Office select
|
|
document.getElementById('officeSelect').addEventListener('change', (e) => {
|
|
loadOfficeRules(e.target.value);
|
|
});
|
|
|
|
// Save Weekly closing days
|
|
const saveBtn = document.getElementById('saveWeeklyClosingDaysBtn');
|
|
if (saveBtn) {
|
|
saveBtn.addEventListener('click', saveWeeklyClosingDays);
|
|
}
|
|
|
|
// Modals
|
|
const modals = [
|
|
{ id: 'closingDayModal', btn: 'addClosingDayBtn', close: 'closeClosingDayModal', cancel: 'cancelClosingDay' },
|
|
{ id: 'guaranteeModal', btn: 'addGuaranteeBtn', close: 'closeGuaranteeModal', cancel: 'cancelGuarantee' },
|
|
{ id: 'exclusionModal', btn: 'addExclusionBtn', close: 'closeExclusionModal', cancel: 'cancelExclusion' }
|
|
];
|
|
|
|
modals.forEach(m => {
|
|
document.getElementById(m.btn).addEventListener('click', () => {
|
|
if (m.id !== 'closingDayModal') populateUserSelects();
|
|
|
|
// Special handling for exclusion to reset edit mode
|
|
if (m.id === 'exclusionModal') resetExclusionForm();
|
|
|
|
document.getElementById(m.id).style.display = 'flex';
|
|
});
|
|
document.getElementById(m.close).addEventListener('click', () => {
|
|
document.getElementById(m.id).style.display = 'none';
|
|
});
|
|
document.getElementById(m.cancel).addEventListener('click', () => {
|
|
document.getElementById(m.id).style.display = 'none';
|
|
});
|
|
utils.setupModalClose(m.id);
|
|
});
|
|
|
|
// Forms
|
|
document.getElementById('closingDayForm').addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
addClosingDay({
|
|
date: document.getElementById('closingDate').value,
|
|
end_date: document.getElementById('closingEndDate').value || null,
|
|
reason: document.getElementById('closingReason').value || null
|
|
});
|
|
});
|
|
|
|
document.getElementById('guaranteeForm').addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
addGuarantee({
|
|
user_id: document.getElementById('guaranteeUser').value,
|
|
start_date: document.getElementById('guaranteeStartDate').value || null,
|
|
end_date: document.getElementById('guaranteeEndDate').value || null,
|
|
notes: document.getElementById('guaranteeNotes').value || null
|
|
});
|
|
});
|
|
|
|
document.getElementById('exclusionForm').addEventListener('submit', (e) => {
|
|
e.preventDefault();
|
|
saveExclusion({
|
|
user_id: document.getElementById('exclusionUser').value,
|
|
start_date: document.getElementById('exclusionStartDate').value || null,
|
|
end_date: document.getElementById('exclusionEndDate').value || null,
|
|
notes: document.getElementById('exclusionNotes').value || null
|
|
});
|
|
});
|
|
|
|
// Algorithm settings events
|
|
document.getElementById('assignmentModeSelect').addEventListener('change', updateAlgorithmVisibility);
|
|
document.getElementById('algorithmForm').addEventListener('submit', saveAlgorithmSettings);
|
|
|
|
// Test Tools Logic
|
|
// Set default date to tomorrow
|
|
const tomorrow = new Date();
|
|
tomorrow.setDate(tomorrow.getDate() + 1);
|
|
const testDateStart = document.getElementById('testDateStart');
|
|
if (testDateStart) testDateStart.valueAsDate = tomorrow;
|
|
|
|
document.getElementById('runAllocationBtn').addEventListener('click', async () => {
|
|
if (!confirm('Sei sicuro di voler avviare l\'assegnazione ORA? Questo potrebbe sovrascrivere le assegnazioni esistenti per la data selezionata.')) return;
|
|
|
|
const dateStart = document.getElementById('testDateStart').value;
|
|
const dateEnd = document.getElementById('testDateEnd').value;
|
|
|
|
if (!dateStart) return utils.showMessage('Seleziona una data di inizio', 'error');
|
|
|
|
let start = new Date(dateStart);
|
|
let end = dateEnd ? new Date(dateEnd) : new Date(dateStart);
|
|
|
|
if (end < start) {
|
|
return utils.showMessage('La data di fine deve essere successiva alla data di inizio', 'error');
|
|
}
|
|
|
|
let current = new Date(start);
|
|
let successCount = 0;
|
|
let errorCount = 0;
|
|
|
|
utils.showMessage('Avvio assegnazione...', 'success');
|
|
|
|
while (current <= end) {
|
|
const dateStr = utils.formatDate(current);
|
|
try {
|
|
await api.post('/api/parking/run-allocation', {
|
|
date: dateStr,
|
|
office_id: currentOfficeId
|
|
});
|
|
successCount++;
|
|
} catch (e) {
|
|
console.error(`Error for ${dateStr}`, e);
|
|
errorCount++;
|
|
}
|
|
current.setDate(current.getDate() + 1);
|
|
}
|
|
|
|
if (errorCount === 0) {
|
|
utils.showMessage(`Assegnazione completata per ${successCount} giorni.`, 'success');
|
|
} else {
|
|
utils.showMessage(`Completato con errori: ${successCount} successi, ${errorCount} errori.`, 'warning');
|
|
}
|
|
});
|
|
|
|
document.getElementById('clearAssignmentsBtn').addEventListener('click', async () => {
|
|
if (!confirm('ATTENZIONE: Stai per eliminare TUTTE le assegnazioni per il periodo selezionato. Procedere?')) return;
|
|
|
|
const dateStart = document.getElementById('testDateStart').value;
|
|
const dateEnd = document.getElementById('testDateEnd').value;
|
|
|
|
if (!dateStart) return utils.showMessage('Seleziona una data di inizio', 'error');
|
|
|
|
let start = new Date(dateStart);
|
|
let end = dateEnd ? new Date(dateEnd) : new Date(dateStart);
|
|
|
|
if (end < start) {
|
|
return utils.showMessage('La data di fine deve essere successiva alla data di inizio', 'error');
|
|
}
|
|
|
|
let current = new Date(start);
|
|
let totalRemoved = 0;
|
|
|
|
utils.showMessage('Rimozione in corso...', 'warning');
|
|
|
|
while (current <= end) {
|
|
const dateStr = utils.formatDate(current);
|
|
try {
|
|
const res = await api.post('/api/parking/clear-assignments', {
|
|
date: dateStr,
|
|
office_id: currentOfficeId
|
|
});
|
|
if (res && res.ok) {
|
|
const data = await res.json();
|
|
totalRemoved += (data.count || 0);
|
|
}
|
|
} catch (e) {
|
|
console.error(`Error clearing ${dateStr}`, e);
|
|
}
|
|
current.setDate(current.getDate() + 1);
|
|
}
|
|
|
|
utils.showMessage(`Operazione eseguita.`, 'warning');
|
|
});
|
|
|
|
const clearPresenceBtn = document.getElementById('clearPresenceBtn');
|
|
if (clearPresenceBtn) {
|
|
clearPresenceBtn.addEventListener('click', async () => {
|
|
if (!confirm('ATTENZIONE: Stai per eliminare TUTTI GLI STATI (Presente/Assente/ecc) e relative assegnazioni per tutti gli utenti del gruppo nel periodo selezionato. \n\nQuesta azione è irreversibile. Procedere?')) return;
|
|
|
|
const dateStart = document.getElementById('testDateStart').value;
|
|
const dateEnd = document.getElementById('testDateEnd').value;
|
|
|
|
if (!dateStart) return utils.showMessage('Seleziona una data di inizio', 'error');
|
|
|
|
// Validate office
|
|
if (!currentOfficeId) {
|
|
return utils.showMessage('Errore: Nessun gruppo selezionato', 'error');
|
|
}
|
|
|
|
const endDateVal = dateEnd || dateStart;
|
|
|
|
utils.showMessage('Rimozione stati in corso...', 'warning');
|
|
|
|
try {
|
|
const res = await api.post('/api/presence/admin/clear-office-presence', {
|
|
start_date: dateStart,
|
|
end_date: endDateVal,
|
|
office_id: currentOfficeId
|
|
});
|
|
|
|
if (res && res.ok) {
|
|
const data = await res.json();
|
|
utils.showMessage(`Operazione completata. Rimossi ${data.count_presence} stati e ${data.count_parking} assegnazioni.`, 'success');
|
|
} else {
|
|
const err = await res.json();
|
|
utils.showMessage('Errore: ' + (err.detail || 'Operazione fallita'), 'error');
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
utils.showMessage('Errore di comunicazione col server', 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
const testEmailBtn = document.getElementById('testEmailBtn');
|
|
if (testEmailBtn) {
|
|
testEmailBtn.addEventListener('click', async () => {
|
|
const dateVal = document.getElementById('testEmailDate').value;
|
|
|
|
// Validate office
|
|
if (!currentOfficeId) {
|
|
return utils.showMessage('Errore: Nessun gruppo selezionato', 'error');
|
|
}
|
|
|
|
utils.showMessage('Invio mail di test in corso...', 'warning');
|
|
|
|
try {
|
|
const res = await api.post('/api/parking/test-email', {
|
|
date: dateVal || null,
|
|
office_id: currentOfficeId
|
|
});
|
|
|
|
if (res && res.status >= 200 && res.status < 300) {
|
|
const data = await res.json();
|
|
|
|
if (data.success) {
|
|
let msg = `Email inviata con successo per la data: ${data.date}.`;
|
|
if (data.mode === 'FILE') {
|
|
msg += ' (SMTP disabilitato: Loggato su file)';
|
|
}
|
|
utils.showMessage(msg, 'success');
|
|
} else {
|
|
utils.showMessage('Invio fallito. Controlla i log del server.', 'error');
|
|
}
|
|
} else {
|
|
const err = res ? await res.json() : {};
|
|
console.error("Test Email Error:", err);
|
|
const errMsg = err.detail ? (typeof err.detail === 'object' ? JSON.stringify(err.detail) : err.detail) : 'Invio fallito';
|
|
utils.showMessage('Errore: ' + errMsg, 'error');
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
utils.showMessage('Errore di comunicazione col server', 'error');
|
|
}
|
|
});
|
|
}
|
|
|
|
const bulkEmailBtn = document.getElementById('bulkEmailBtn');
|
|
if (bulkEmailBtn) {
|
|
bulkEmailBtn.addEventListener('click', async () => {
|
|
const dateVal = document.getElementById('testEmailDate').value;
|
|
|
|
// Validate office
|
|
if (!currentOfficeId) {
|
|
return utils.showMessage('Errore: Nessun gruppo selezionato', 'error');
|
|
}
|
|
|
|
if (!dateVal) {
|
|
return utils.showMessage('Per il test a TUTTI è obbligatorio selezionare una data specifica.', 'error');
|
|
}
|
|
|
|
if (!confirm(`Sei sicuro di voler inviare una mail di promemoria a TUTTI gli utenti con parcheggio assegnato per il giorno ${dateVal}?\n\nQuesta azione invierà vere email.`)) return;
|
|
|
|
utils.showMessage('Invio mail massive in corso...', 'warning');
|
|
|
|
try {
|
|
const res = await api.post('/api/parking/test-email', {
|
|
date: dateVal,
|
|
office_id: currentOfficeId,
|
|
bulk_send: true
|
|
});
|
|
|
|
if (res && res.status >= 200 && res.status < 300) {
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
let msg = `Processo completato per il ${data.date}. Inviate: ${data.count || 0}, Fallite: ${data.failed || 0}.`;
|
|
if (data.mode === 'BULK' && (data.count || 0) === 0) msg += " (Nessun assegnatario trovato)";
|
|
utils.showMessage(msg, 'success');
|
|
} else {
|
|
utils.showMessage('Errore durante l\'invio.', 'error');
|
|
}
|
|
} else {
|
|
const err = res ? await res.json() : {};
|
|
console.error("Bulk Test Email Error:", err);
|
|
const errMsg = err.detail ? (typeof err.detail === 'object' ? JSON.stringify(err.detail) : err.detail) : 'Invio fallito';
|
|
utils.showMessage('Errore: ' + errMsg, 'error');
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
utils.showMessage('Errore di comunicazione col server: ' + e.message, 'error');
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
// Global functions
|
|
window.deleteClosingDay = deleteClosingDay;
|
|
window.deleteGuarantee = deleteGuarantee;
|
|
window.deleteClosingDay = deleteClosingDay;
|
|
window.deleteGuarantee = deleteGuarantee;
|
|
window.deleteExclusion = deleteExclusion;
|
|
window.openEditExclusion = openEditExclusion;
|