264 lines
12 KiB
JavaScript
264 lines
12 KiB
JavaScript
(() => {
|
||
'use strict';
|
||
|
||
const PTA = window.PTA;
|
||
if (!PTA) throw new Error('PTA core doit être chargé avant vacation-calendar.js');
|
||
|
||
const { escapeHtml, readJsonResponse, apiErrorMessage, formatDate } = PTA.utils;
|
||
let previewPeriods = [];
|
||
|
||
const elements = () => ({
|
||
academy: document.querySelector('#vacation-academy'),
|
||
calendarYear: document.querySelector('#vacation-calendar-year'),
|
||
previewButton: document.querySelector('#preview-vacations'),
|
||
previewMessage: document.querySelector('#vacation-preview-message'),
|
||
previewPanel: document.querySelector('#vacation-preview-panel'),
|
||
previewBody: document.querySelector('#vacation-preview-body'),
|
||
selectAll: document.querySelector('#vacation-select-all'),
|
||
saveSelected: document.querySelector('#save-selected-vacations'),
|
||
savedBody: document.querySelector('#vacation-saved-body'),
|
||
savedMessage: document.querySelector('#vacation-saved-message'),
|
||
refresh: document.querySelector('#refresh-vacations'),
|
||
manualForm: document.querySelector('#manual-vacation-form'),
|
||
manualMessage: document.querySelector('#manual-vacation-message'),
|
||
});
|
||
|
||
function setMessage(element, text = '', type = 'info') {
|
||
if (!element) return;
|
||
element.className = text ? `message message-${type}` : 'message';
|
||
element.textContent = text;
|
||
}
|
||
|
||
function sourceLabel(source) {
|
||
return source === 'DATA_GOUV' ? 'API officielle' : 'Saisie manuelle';
|
||
}
|
||
|
||
function renderPreview() {
|
||
const el = elements();
|
||
if (!el.previewBody || !el.previewPanel) return;
|
||
|
||
if (!previewPeriods.length) {
|
||
el.previewBody.innerHTML = '<tr><td colspan="5"><div class="empty-state">Aucune période proposée.</div></td></tr>';
|
||
el.previewPanel.hidden = false;
|
||
return;
|
||
}
|
||
|
||
el.previewBody.innerHTML = previewPeriods.map((period, index) => `
|
||
<tr class="vacation-preview-row" data-index="${index}">
|
||
<td><input class="vacation-import-check" type="checkbox" aria-label="Importer ${escapeHtml(period.libelle)}"></td>
|
||
<td>
|
||
<input class="vacation-preview-label" type="text" maxlength="150" value="${escapeHtml(period.libelle)}">
|
||
<small>${escapeHtml(period.annee_scolaire || '')}</small>
|
||
</td>
|
||
<td><input class="vacation-preview-start" type="date" value="${escapeHtml(period.date_debut)}"></td>
|
||
<td>
|
||
<input class="vacation-preview-end" type="date" value="${escapeHtml(period.date_fin)}">
|
||
${period.date_reprise_api ? `<small>Reprise API : ${escapeHtml(formatDate(period.date_reprise_api))}</small>` : ''}
|
||
</td>
|
||
<td>${escapeHtml([period.zone, period.academie].filter(Boolean).join(' · ') || 'Non précisé')}</td>
|
||
</tr>
|
||
`).join('');
|
||
el.previewPanel.hidden = false;
|
||
if (el.selectAll) el.selectAll.checked = false;
|
||
}
|
||
|
||
async function preview() {
|
||
const el = elements();
|
||
const academy = el.academy?.value.trim() || '';
|
||
const calendarYear = String(el.calendarYear?.value || '').trim();
|
||
if (!academy || !/^\d{4}$/.test(calendarYear)) {
|
||
setMessage(el.previewMessage, 'Renseignez une académie et une année civile au format 2026.', 'warning');
|
||
return;
|
||
}
|
||
|
||
el.previewButton.disabled = true;
|
||
setMessage(el.previewMessage, 'Consultation de l’API officielle en cours…', 'info');
|
||
if (el.previewPanel) el.previewPanel.hidden = true;
|
||
|
||
try {
|
||
const params = new URLSearchParams({ academy, year: calendarYear });
|
||
const response = await fetch(`api/preview_school_holidays.php?${params.toString()}`);
|
||
const data = await readJsonResponse(response);
|
||
if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de consulter le calendrier scolaire.'));
|
||
|
||
previewPeriods = Array.isArray(data.periods) ? data.periods : [];
|
||
renderPreview();
|
||
const warnings = Array.isArray(data.warnings) ? data.warnings.filter(Boolean) : [];
|
||
const baseMessage = data.message || `${previewPeriods.length} période(s) proposée(s). Aucune donnée n’a encore été enregistrée.`;
|
||
const fullMessage = warnings.length ? `${baseMessage} Attention : ${warnings.join(' ')}` : baseMessage;
|
||
setMessage(el.previewMessage, fullMessage, warnings.length ? 'warning' : (previewPeriods.length ? 'success' : 'warning'));
|
||
} catch (error) {
|
||
previewPeriods = [];
|
||
setMessage(el.previewMessage, error.message, 'error');
|
||
} finally {
|
||
el.previewButton.disabled = false;
|
||
}
|
||
}
|
||
|
||
function selectedPreviewPeriods() {
|
||
const el = elements();
|
||
return Array.from(el.previewBody?.querySelectorAll('.vacation-preview-row') || [])
|
||
.filter((row) => row.querySelector('.vacation-import-check')?.checked)
|
||
.map((row) => {
|
||
const source = previewPeriods[Number(row.dataset.index)] || {};
|
||
return {
|
||
libelle: row.querySelector('.vacation-preview-label')?.value.trim() || '',
|
||
date_debut: row.querySelector('.vacation-preview-start')?.value || '',
|
||
date_fin: row.querySelector('.vacation-preview-end')?.value || '',
|
||
annee_scolaire: source.annee_scolaire || '',
|
||
academie: source.academie || elements().academy?.value.trim() || '',
|
||
zone: source.zone || '',
|
||
source: 'DATA_GOUV',
|
||
};
|
||
});
|
||
}
|
||
|
||
async function savePeriods(periods, messageElement) {
|
||
const response = await fetch('api/save_vacation_periods.php', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
csrf_token: window.PTA_CONFIG.csrfToken,
|
||
periods,
|
||
}),
|
||
});
|
||
const data = await readJsonResponse(response);
|
||
if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible d’enregistrer les périodes.'));
|
||
setMessage(messageElement, data.message, 'success');
|
||
await loadSaved();
|
||
return data;
|
||
}
|
||
|
||
async function saveSelected() {
|
||
const el = elements();
|
||
const periods = selectedPreviewPeriods();
|
||
if (!periods.length) {
|
||
setMessage(el.previewMessage, 'Cochez au moins une période avant de l’enregistrer.', 'warning');
|
||
return;
|
||
}
|
||
|
||
el.saveSelected.disabled = true;
|
||
try {
|
||
await savePeriods(periods, el.previewMessage);
|
||
el.previewBody?.querySelectorAll('.vacation-import-check').forEach((checkbox) => { checkbox.checked = false; });
|
||
if (el.selectAll) el.selectAll.checked = false;
|
||
} catch (error) {
|
||
setMessage(el.previewMessage, error.message, 'error');
|
||
} finally {
|
||
el.saveSelected.disabled = false;
|
||
}
|
||
}
|
||
|
||
async function saveManual(event) {
|
||
event.preventDefault();
|
||
const el = elements();
|
||
const submit = el.manualForm?.querySelector('button[type="submit"]');
|
||
const period = {
|
||
libelle: document.querySelector('#manual-vacation-label')?.value.trim() || '',
|
||
date_debut: document.querySelector('#manual-vacation-start')?.value || '',
|
||
date_fin: document.querySelector('#manual-vacation-end')?.value || '',
|
||
annee_scolaire: document.querySelector('#manual-vacation-school-year')?.value.trim() || '',
|
||
academie: document.querySelector('#manual-vacation-academy')?.value.trim() || '',
|
||
zone: document.querySelector('#manual-vacation-zone')?.value.trim() || '',
|
||
source: 'MANUEL',
|
||
};
|
||
|
||
if (period.date_debut && period.date_fin && period.date_fin < period.date_debut) {
|
||
setMessage(el.manualMessage, 'La date de fin doit être postérieure ou égale à la date de début.', 'warning');
|
||
return;
|
||
}
|
||
|
||
if (submit) submit.disabled = true;
|
||
try {
|
||
await savePeriods([period], el.manualMessage);
|
||
document.querySelector('#manual-vacation-label').value = '';
|
||
document.querySelector('#manual-vacation-start').value = '';
|
||
document.querySelector('#manual-vacation-end').value = '';
|
||
document.querySelector('#manual-vacation-zone').value = '';
|
||
} catch (error) {
|
||
setMessage(el.manualMessage, error.message, 'error');
|
||
} finally {
|
||
if (submit) submit.disabled = false;
|
||
}
|
||
}
|
||
|
||
function renderSaved(periods) {
|
||
const el = elements();
|
||
if (!el.savedBody) return;
|
||
if (!periods.length) {
|
||
el.savedBody.innerHTML = '<tr><td colspan="6"><div class="empty-state">Aucune période de vacances n’est encore enregistrée.</div></td></tr>';
|
||
return;
|
||
}
|
||
|
||
el.savedBody.innerHTML = periods.map((period) => `
|
||
<tr data-period-id="${Number(period.id_periode)}">
|
||
<td><strong>${escapeHtml(period.libelle)}</strong></td>
|
||
<td>${escapeHtml(formatDate(period.date_debut))} → ${escapeHtml(formatDate(period.date_fin))}</td>
|
||
<td>${escapeHtml(period.annee_scolaire || '')}</td>
|
||
<td>${escapeHtml([period.academie, period.zone].filter(Boolean).join(' · ') || 'Non précisé')}</td>
|
||
<td><span class="vacation-source-badge">${escapeHtml(sourceLabel(period.source))}</span></td>
|
||
<td><button type="button" class="button button-danger delete-vacation-period">Supprimer</button></td>
|
||
</tr>
|
||
`).join('');
|
||
}
|
||
|
||
async function loadSaved() {
|
||
const el = elements();
|
||
try {
|
||
const response = await fetch('api/vacation_periods.php');
|
||
const data = await readJsonResponse(response);
|
||
if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de charger les périodes enregistrées.'));
|
||
renderSaved(Array.isArray(data.periods) ? data.periods : []);
|
||
setMessage(el.savedMessage, '', 'info');
|
||
} catch (error) {
|
||
setMessage(el.savedMessage, error.message, 'error');
|
||
}
|
||
}
|
||
|
||
async function deletePeriod(button) {
|
||
const row = button.closest('tr[data-period-id]');
|
||
const id = Number(row?.dataset.periodId || 0);
|
||
if (!id || !confirm('Supprimer cette période de vacances ? Les journées concernées seront à nouveau considérées comme périscolaires.')) return;
|
||
|
||
button.disabled = true;
|
||
try {
|
||
const response = await fetch('api/delete_vacation_period.php', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({
|
||
csrf_token: window.PTA_CONFIG.csrfToken,
|
||
period_id: id,
|
||
}),
|
||
});
|
||
const data = await readJsonResponse(response);
|
||
if (!response.ok) throw new Error(apiErrorMessage(data, 'Suppression impossible.'));
|
||
setMessage(elements().savedMessage, data.message, 'success');
|
||
await loadSaved();
|
||
} catch (error) {
|
||
setMessage(elements().savedMessage, error.message, 'error');
|
||
button.disabled = false;
|
||
}
|
||
}
|
||
|
||
function init() {
|
||
const el = elements();
|
||
el.previewButton?.addEventListener('click', preview);
|
||
el.saveSelected?.addEventListener('click', saveSelected);
|
||
el.refresh?.addEventListener('click', loadSaved);
|
||
el.manualForm?.addEventListener('submit', saveManual);
|
||
el.selectAll?.addEventListener('change', () => {
|
||
el.previewBody?.querySelectorAll('.vacation-import-check').forEach((checkbox) => {
|
||
checkbox.checked = el.selectAll.checked;
|
||
});
|
||
});
|
||
el.savedBody?.addEventListener('click', (event) => {
|
||
const button = event.target.closest('.delete-vacation-period');
|
||
if (button) deletePeriod(button);
|
||
});
|
||
|
||
loadSaved();
|
||
}
|
||
|
||
PTA.modules.vacationCalendar = { init, loadSaved, preview };
|
||
})();
|