/**
* Planning par lieu (`PTA.modules.structureOverview`), écran structures.php.
*
* Vue EN LECTURE SEULE : aucun créneau n'est modifiable ici, la saisie se fait
* dans planning.php. C'est la vue complémentaire de la vue par agent — ici on
* regarde qui est présent sur un lieu, là où va un agent.
*
* La liste d'agents ne se limite pas aux agents rattachés au lieu : elle inclut
* ceux qui y sont ponctuellement planifiés, un agent pouvant travailler sur
* plusieurs lieux la même semaine.
*
* Un rapport de couverture est intégré à la page : il évite d'avoir à ouvrir
* l'écran dédié pour vérifier qu'un lieu est correctement pourvu.
*/
(() => {
'use strict';
const PTA = window.PTA;
if (!PTA) throw new Error('PTA core doit être chargé avant structure-overview.js');
const { openStructurePdfButton } = PTA.elements;
const { escapeHtml, readJsonResponse, apiErrorMessage, parseIsoWeek, formatDate } = PTA.utils;
const typeLabel = (type) => type === 'EXTRASCOLAIRE' ? 'Extrascolaire' : 'Périscolaire';
/**
* Balisage d'un créneau : horaires, motif, et la mention « Brouillon » si le
* planning n'est pas validé — la présence n'est alors pas garantie.
* `motif_code` alimente un nom de classe CSS et passe donc par escapeHtml().
*/
function entryBadge(entry) {
return `
${escapeHtml(entry.heure_debut)}–${escapeHtml(entry.heure_fin)}
${escapeHtml(entry.motif_libelle)}
${entry.statut === 'BROUILLON' ? 'Brouillon' : ''}
`;
}
/**
* Bandeau de couverture, en trois états : lieu NON CONFIGURÉ (aucune règle,
* donc aucun contrôle possible), couverture complète, ou liste des manques.
* La liste est plafonnée à huit plages, le reste étant dénombré.
*/
function coveragePanel(coverage) {
if (!coverage?.configured) {
return `
Couverture non configurée
Définissez les horaires de présence minimale dans Administration pour détecter automatiquement les créneaux vides.
`;
}
const gaps = Array.isArray(coverage.gaps) ? coverage.gaps : [];
if (!gaps.length) {
return `
Couverture complète
Aucun manque d’agent détecté sur les plages de couverture configurées.
`;
}
const items = gaps.slice(0, 8).map((gap) => `
${escapeHtml(gap.jour_libelle)} ${escapeHtml(gap.heure_debut)}–${escapeHtml(gap.heure_fin)} : ${Number(gap.agents_manquants)} agent${Number(gap.agents_manquants) > 1 ? 's' : ''} manquant${Number(gap.agents_manquants) > 1 ? 's' : ''}
`).join('');
const more = gaps.length > 8 ? `… et ${gaps.length - 8} autre${gaps.length - 8 > 1 ? 's' : ''} plage${gaps.length - 8 > 1 ? 's' : ''}.` : '';
return `
⚠ ${gaps.length} plage${gaps.length > 1 ? 's' : ''} insuffisamment couverte${gaps.length > 1 ? 's' : ''}
`;
}
/** Rend le tableau agents × jours de la semaine, plus le bandeau de couverture. */
function render(data) {
const container = document.querySelector('#structure-overview-content');
const empty = document.querySelector('#structure-overview-empty');
const days = parseIsoWeek(data.week.value);
if (!container || !empty) return;
empty.hidden = true;
container.hidden = false;
if (!data.agents.length) {
container.innerHTML = `
${escapeHtml(data.structure.nom)}
${typeLabel(data.structure.type_affectation)} · semaine du ${formatDate(days[0]?.date)} au ${formatDate(days[days.length - 1]?.date)}
0 agent
${coveragePanel(data.coverage)}
Aucun agent n’est rattaché ou planifié sur ce lieu pour cette semaine.
`;
return;
}
const headerDays = days.map((day) => `${day.label}${day.display} | `).join('');
const rows = data.agents.map((agent) => {
const cells = days.map((day) => {
const entries = agent.entries.filter((entry) => entry.date_jour === day.date);
return `${entries.length ? entries.map((entry) => entryBadge(entry)).join('') : '—'} | `;
}).join('');
return `
|
${escapeHtml(agent.prenom)} ${escapeHtml(agent.nom)}
${escapeHtml(agent.matricule)}
${agent.lieu_principal ? 'Lieu principal' : 'Affectation ponctuelle'}
|
${cells}
${escapeHtml(agent.quota.restantes.libelle)}
sur ${escapeHtml(agent.quota.quota_cible.libelle)}
${Number(agent.quota.quotite_travail).toLocaleString('fr-FR')} %
|
`;
}).join('');
container.innerHTML = `
${escapeHtml(data.structure.nom)}
${typeLabel(data.structure.type_affectation)} · semaine du ${formatDate(days[0]?.date)} au ${formatDate(days[days.length - 1]?.date)}
${data.agents.length} agent${data.agents.length > 1 ? 's' : ''}
${coveragePanel(data.coverage)}
`;
}
/** Le PDF du lieu exige un lieu ET une semaine. */
function updatePdfButton() {
if (!openStructurePdfButton) return;
const structureId = document.querySelector('#overview-structure')?.value;
const week = document.querySelector('#overview-week')?.value;
openStructurePdfButton.disabled = !(structureId && week);
}
/** Ouvre le PDF du lieu dans un nouvel onglet (`noopener` par principe). */
function openPdf() {
const structureId = document.querySelector('#overview-structure')?.value;
const week = document.querySelector('#overview-week')?.value;
if (!structureId || !week) return;
const params = new URLSearchParams({ structure_id: structureId, week });
window.open(`api/structure_planning_pdf.php?${params.toString()}`, '_blank', 'noopener');
}
/** Charge et rend la semaine du lieu sélectionné. */
async function load() {
const structureId = document.querySelector('#overview-structure')?.value;
const week = document.querySelector('#overview-week')?.value;
const container = document.querySelector('#structure-overview-content');
const empty = document.querySelector('#structure-overview-empty');
updatePdfButton();
if (!container || !empty) return;
if (!structureId || !week) {
empty.hidden = false;
empty.textContent = 'Choisissez un lieu d’affectation et une semaine.';
container.hidden = true;
return;
}
empty.hidden = false;
empty.textContent = 'Chargement du planning du lieu…';
container.hidden = true;
try {
const params = new URLSearchParams({ structure_id: structureId, week });
const response = await fetch(`api/structure_overview.php?${params.toString()}`);
const data = await readJsonResponse(response);
if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de charger la vue du lieu.'));
render(data);
} catch (error) {
empty.hidden = false;
empty.textContent = error.message;
}
}
function init() {
document.querySelector('#load-structure-overview')?.addEventListener('click', load);
document.querySelector('#overview-structure')?.addEventListener('change', load);
document.querySelector('#overview-week')?.addEventListener('change', load);
openStructurePdfButton?.addEventListener('click', openPdf);
updatePdfButton();
}
PTA.modules.structureOverview = { init, load, render, updatePdfButton };
})();