Files
pta/assets/js/structure-overview.js
Loic Masi 5fbf76868f pour prod
2026-08-07 16:13:43 +02:00

178 lines
8.0 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(() => {
'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';
function entryBadge(entry) {
return `
<div class="overview-entry motif-${escapeHtml(String(entry.motif_code || 'autre').toLowerCase())}">
<strong>${escapeHtml(entry.heure_debut)}${escapeHtml(entry.heure_fin)}</strong>
<span>${escapeHtml(entry.motif_libelle)}</span>
${entry.statut === 'BROUILLON' ? '<em>Brouillon</em>' : ''}
</div>
`;
}
function coveragePanel(coverage) {
if (!coverage?.configured) {
return `
<div class="coverage-inline-notice coverage-inline-info">
<strong>Couverture non configurée</strong>
<span>Définissez les horaires de présence minimale dans Administration pour détecter automatiquement les créneaux vides.</span>
</div>`;
}
const gaps = Array.isArray(coverage.gaps) ? coverage.gaps : [];
if (!gaps.length) {
return `
<div class="coverage-inline-notice coverage-inline-success">
<strong>Couverture complète</strong>
<span>Aucun manque dagent détecté sur les plages de couverture configurées.</span>
</div>`;
}
const items = gaps.slice(0, 8).map((gap) => `
<li><strong>${escapeHtml(gap.jour_libelle)} ${escapeHtml(gap.heure_debut)}${escapeHtml(gap.heure_fin)}</strong> : ${Number(gap.agents_manquants)} agent${Number(gap.agents_manquants) > 1 ? 's' : ''} manquant${Number(gap.agents_manquants) > 1 ? 's' : ''}</li>
`).join('');
const more = gaps.length > 8 ? `<li>… et ${gaps.length - 8} autre${gaps.length - 8 > 1 ? 's' : ''} plage${gaps.length - 8 > 1 ? 's' : ''}.</li>` : '';
return `
<div class="coverage-inline-notice coverage-inline-danger">
<strong>⚠ ${gaps.length} plage${gaps.length > 1 ? 's' : ''} insuffisamment couverte${gaps.length > 1 ? 's' : ''}</strong>
<ul>${items}${more}</ul>
</div>`;
}
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 = `
<div class="overview-title-row">
<div>
<h3>${escapeHtml(data.structure.nom)}</h3>
<p>${typeLabel(data.structure.type_affectation)} · semaine du ${formatDate(days[0]?.date)} au ${formatDate(days[days.length - 1]?.date)}</p>
</div>
<span class="overview-count">0 agent</span>
</div>
${coveragePanel(data.coverage)}
<div class="empty-state">Aucun agent nest rattaché ou planifié sur ce lieu pour cette semaine.</div>`;
return;
}
const headerDays = days.map((day) => `<th><strong>${day.label}</strong><span>${day.display}</span></th>`).join('');
const rows = data.agents.map((agent) => {
const cells = days.map((day) => {
const entries = agent.entries.filter((entry) => entry.date_jour === day.date);
return `<td>${entries.length ? entries.map((entry) => entryBadge(entry)).join('') : '<span class="cell-empty">—</span>'}</td>`;
}).join('');
return `
<tr>
<th class="agent-cell">
<strong>${escapeHtml(agent.prenom)} ${escapeHtml(agent.nom)}</strong>
<span>${escapeHtml(agent.matricule)}</span>
<small>${agent.lieu_principal ? 'Lieu principal' : 'Affectation ponctuelle'}</small>
</th>
${cells}
<td class="quota-cell">
<strong>${escapeHtml(agent.quota.restantes.libelle)}</strong>
<span>sur ${escapeHtml(agent.quota.quota_cible.libelle)}</span>
<small>${Number(agent.quota.quotite_travail).toLocaleString('fr-FR')} %</small>
</td>
</tr>
`;
}).join('');
container.innerHTML = `
<div class="overview-title-row">
<div>
<h3>${escapeHtml(data.structure.nom)}</h3>
<p>${typeLabel(data.structure.type_affectation)} · semaine du ${formatDate(days[0]?.date)} au ${formatDate(days[days.length - 1]?.date)}</p>
</div>
<span class="overview-count">${data.agents.length} agent${data.agents.length > 1 ? 's' : ''}</span>
</div>
${coveragePanel(data.coverage)}
<div class="table-scroll">
<table class="planning-table">
<thead>
<tr>
<th>Agent</th>
${headerDays}
<th>Quota restant</th>
</tr>
</thead>
<tbody>${rows}</tbody>
</table>
</div>
`;
}
function updatePdfButton() {
if (!openStructurePdfButton) return;
const structureId = document.querySelector('#overview-structure')?.value;
const week = document.querySelector('#overview-week')?.value;
openStructurePdfButton.disabled = !(structureId && week);
}
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');
}
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 daffectation 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 };
})();