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

166 lines
7.3 KiB
JavaScript

(() => {
'use strict';
const PTA = window.PTA;
if (!PTA) throw new Error('PTA core doit être chargé avant pending-validation.js');
const { utils } = PTA;
const { readJsonResponse, apiErrorMessage, escapeHtml, formatDuration, formatDate } = utils;
const badge = document.querySelector('#pending-validation-tab-badge');
const refreshButton = document.querySelector('#refresh-pending-validation');
const countElement = document.querySelector('#pending-validation-count');
const hoursElement = document.querySelector('#pending-validation-hours');
const countedHoursElement = document.querySelector('#pending-validation-counted-hours');
const loadingElement = document.querySelector('#pending-validation-loading');
const emptyElement = document.querySelector('#pending-validation-empty');
const contentElement = document.querySelector('#pending-validation-content');
const bodyElement = document.querySelector('#pending-validation-body');
const messageElement = document.querySelector('#pending-validation-message');
let items = [];
let loading = false;
function weekValue(item) {
return `${item.annee}-W${String(item.numero_semaine).padStart(2, '0')}`;
}
function formatDateTime(value) {
if (!value) return '—';
const normalized = String(value).replace(' ', 'T');
const date = new Date(normalized);
if (Number.isNaN(date.getTime())) return escapeHtml(value);
return date.toLocaleString('fr-FR', {
day: '2-digit', month: '2-digit', year: 'numeric',
hour: '2-digit', minute: '2-digit',
});
}
function structureTypeLabel(value) {
return value === 'EXTRASCOLAIRE' ? 'Extrascolaire' : 'Périscolaire';
}
function setBadge(count) {
if (!badge) return;
badge.textContent = String(count);
badge.hidden = count === 0;
badge.setAttribute('aria-label', `${count} planning${count > 1 ? 's' : ''} en attente de validation`);
}
function setMessage(text = '', type = 'info') {
if (!messageElement) return;
messageElement.className = text ? `message message-${type}` : 'message';
messageElement.textContent = text;
}
function render(data) {
items = Array.isArray(data.plannings) ? data.plannings : [];
const summary = data.summary || {};
const count = Number(summary.count ?? items.length);
setBadge(count);
if (countElement) countElement.textContent = String(count);
if (hoursElement) hoursElement.textContent = formatDuration(Number(summary.total_minutes || 0));
if (countedHoursElement) countedHoursElement.textContent = formatDuration(Number(summary.counted_minutes || 0));
if (loadingElement) loadingElement.hidden = true;
if (emptyElement) emptyElement.hidden = items.length !== 0;
if (contentElement) contentElement.hidden = items.length === 0;
if (!bodyElement) return;
bodyElement.innerHTML = items.map((item) => {
const week = weekValue(item);
const rowId = Number(item.id_planning);
return `
<tr class="pending-validation-row" data-planning-id="${rowId}" tabindex="0" role="button" aria-label="Ouvrir le planning de ${escapeHtml(item.agent_prenom)} ${escapeHtml(item.agent_nom)} pour la semaine ${escapeHtml(week)}">
<td>
<strong>${escapeHtml(item.agent_prenom)} ${escapeHtml(item.agent_nom)}</strong>
<small>${escapeHtml(item.matricule || '')}</small>
</td>
<td>
<strong>${escapeHtml(item.structure_nom)}</strong>
<small>${escapeHtml(structureTypeLabel(item.structure_type_affectation))}</small>
</td>
<td>
<strong>Semaine ${Number(item.numero_semaine)}</strong>
<small>${formatDate(item.date_debut)}${formatDate(item.date_fin_ouvrable || item.date_fin)}</small>
</td>
<td><span class="pending-count-chip">${Number(item.nombre_creneaux)}</span></td>
<td>
<strong>${formatDuration(Number(item.total_minutes || 0))}</strong>
<small>${formatDuration(Number(item.minutes_decomptees || 0))} décomptées</small>
</td>
<td>${formatDateTime(item.date_modification)}</td>
<td class="pending-validation-action-cell">
<button class="button button-success open-pending-planning" type="button" data-planning-id="${rowId}">Consulter et valider</button>
</td>
</tr>`;
}).join('');
}
async function refresh(options = {}) {
if (loading) return;
loading = true;
if (!options.silent) setMessage('');
if (loadingElement) {
loadingElement.hidden = false;
if (emptyElement) emptyElement.hidden = true;
if (contentElement) contentElement.hidden = true;
}
if (refreshButton) refreshButton.disabled = true;
try {
const response = await fetch('api/pending_plannings.php', { headers: { Accept: 'application/json' } });
const data = await readJsonResponse(response);
if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de charger les plannings en attente de validation.'));
render(data);
} catch (error) {
if (loadingElement) loadingElement.hidden = true;
setMessage(error.message, 'error');
} finally {
loading = false;
if (refreshButton) refreshButton.disabled = false;
}
}
function openPlanning(item) {
if (!item) return;
const params = new URLSearchParams({
structure_id: String(item.id_structure),
agent_id: String(item.id_agent),
week: weekValue(item),
focus: 'validation',
notice: `Planning de ${item.agent_prenom} ${item.agent_nom} chargé depuis la file de validation.`,
});
window.location.href = `planning.php?${params.toString()}`;
}
function findItemFromEventTarget(target) {
const trigger = target.closest('[data-planning-id]');
if (!trigger) return null;
const id = Number(trigger.dataset.planningId);
return items.find((item) => Number(item.id_planning) === id) || null;
}
function init() {
refreshButton?.addEventListener('click', () => refresh());
bodyElement?.addEventListener('click', (event) => {
const item = findItemFromEventTarget(event.target);
if (item) openPlanning(item);
});
bodyElement?.addEventListener('keydown', (event) => {
if (!['Enter', ' '].includes(event.key)) return;
if (event.target.closest('button')) return;
const row = event.target.closest('.pending-validation-row');
if (!row) return;
event.preventDefault();
const item = findItemFromEventTarget(row);
if (item) openPlanning(item);
});
refresh({ silent: true });
}
PTA.modules.pendingValidation = { init, refresh, openPlanning };
})();