/** * 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)}
    ${headerDays} ${rows}
    AgentQuota restant
    `; } /** 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 }; })();