pour prod

This commit is contained in:
Loic Masi
2026-08-07 16:13:43 +02:00
commit 5fbf76868f
157 changed files with 24085 additions and 0 deletions

View File

@@ -0,0 +1,177 @@
(() => {
'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 };
})();