(() => {
'use strict';
const PTA = window.PTA;
if (!PTA) return;
const { readJsonResponse, apiErrorMessage, escapeHtml, formatDate } = PTA.utils;
const el = (id) => document.getElementById(id);
const escapeAttr = (value) => String(value ?? '')
.replaceAll('&', '&')
.replaceAll('\"', '"')
.replaceAll("'", ''')
.replaceAll('<', '<')
.replaceAll('>', '>');
let current = null;
function show(text, type = 'info') {
const box = el('annual-message');
if (!box) return;
box.className = `message message-${type}`;
box.textContent = text;
}
function formatGenerated(value) {
const date = new Date(String(value).replace(' ', 'T'));
return Number.isNaN(date.getTime()) ? value : date.toLocaleString('fr-FR', { dateStyle: 'short', timeStyle: 'short' });
}
function formatContract(value) {
if (value === 'PERMANENT') return 'Permanent';
if (value === 'TEMPORAIRE') return 'Temporaire';
return value || 'Non renseigné';
}
function buildPlanningUrl(day) {
const agentId = Number(current?.agent?.id_agent || 0);
const structureId = Number(day.structure_id || current?.agent?.id_structure || 0);
const week = `${Number(day.week_year)}-W${String(day.week).padStart(2, '0')}`;
const params = new URLSearchParams({ agent_id: String(agentId), week, focus: 'annual' });
if (structureId) params.set('structure_id', String(structureId));
params.set('notice', `Ouverture depuis la vue annuelle — ${formatDate(day.date)}.`);
return `planning.php?${params.toString()}`;
}
function cellHtml(cell, day) {
const content = cell.display ? escapeHtml(cell.display) : ' ';
const title = cell.title ? ` title="${escapeAttr(cell.title)}"` : '';
const draftClass = day.has_draft && cell.minutes > 0 ? ' annual-cell-draft' : '';
return `
${content} | `;
}
function monthHtml(month) {
const rows = month.days.map((day) => {
const classes = [
day.is_weekend ? 'annual-weekend' : '',
day.is_vacation ? 'annual-vacation-row' : '',
day.has_entries ? 'annual-has-entry' : '',
].filter(Boolean).join(' ');
const vacationTitle = day.vacation_label ? ` title="${escapeAttr(day.vacation_label)}"` : '';
const weekText = day.show_week ? String(day.week).padStart(2, '0') : '';
return `
| ${escapeHtml(day.weekday_short)}${day.day} |
${weekText} |
${cellHtml(day.cells.AP, day)}
${cellHtml(day.cells.PP, day)}
${cellHtml(day.cells.AE, day)}
${cellHtml(day.cells.PE, day)}
`;
}).join('');
return `
| ${escapeHtml(month.name)} |
| Jour | S | AP | PP | AE | PE |
${rows}
| Total | ${escapeHtml(month.totals.AP.compact || '0h')} | ${escapeHtml(month.totals.PP.compact || '0h')} | ${escapeHtml(month.totals.AE.compact || '0h')} | ${escapeHtml(month.totals.PE.compact || '0h')} |
`;
}
function quotaPeriodsHtml(periods) {
return periods.map((period) => `
${escapeHtml(formatDate(period.date_debut_periode))} → ${escapeHtml(formatDate(period.date_fin_periode))}
${escapeHtml(period.affectees?.libelle || '0 h 00')} affectées / ${escapeHtml(period.quota_cible?.libelle || '0 h 00')}
Reste : ${escapeHtml(period.restantes?.libelle || '0 h 00')}
`).join('');
}
function render(data) {
current = data;
const agent = data.agent || {};
const periods = data.quota_periods || [];
const referencePeriod = periods[0] || {};
el('annual-content').hidden = false;
el('annual-title').textContent = `ANNUALISATION ${data.year}`;
el('annual-updated').textContent = `Mise à jour : ${formatGenerated(data.generated_at)}`;
el('annual-agent-name').textContent = `${agent.prenom || ''} ${agent.nom || ''}`.trim() || '—';
el('annual-agent-number').textContent = agent.matricule || '—';
el('annual-rate').textContent = referencePeriod.quotite_travail !== undefined ? `${Number(referencePeriod.quotite_travail).toLocaleString('fr-FR')} %` : '—';
el('annual-target').textContent = referencePeriod.quota_cible?.libelle || '—';
el('annual-contract').textContent = formatContract(agent.type_contrat);
el('annual-location').textContent = agent.structure_principale_nom || 'Sans lieu principal';
el('annual-post').textContent = agent.poste_libelle || 'Poste non renseigné';
el('annual-remaining').textContent = referencePeriod.restantes?.libelle || '—';
el('annual-quota-periods').innerHTML = quotaPeriodsHtml(periods);
el('annual-calendar-total').textContent = data.calendar_totals?.duration || '0 h 00';
el('annual-validated-total').textContent = data.calendar_totals?.validated || '0 h 00';
el('annual-draft-total').textContent = data.calendar_totals?.draft || '0 h 00';
el('annual-year-label').textContent = `01/01/${data.year} → 31/12/${data.year}`;
el('annual-months').innerHTML = (data.months || []).map(monthHtml).join('');
el('annual-legend').innerHTML = (data.legend || []).map((item) => `
${escapeHtml(item.code)} ${escapeHtml(item.label)}
`).join('');
el('annual-open-pta').href = `pta.php?agent_id=${Number(agent.id_agent)}&date=${data.year}-07-01`;
el('annual-open-month').href = `agents.php?agent_id=${Number(agent.id_agent)}&month=${data.year}-01`;
el('annual-print').disabled = false;
el('annual-fullscreen').disabled = false;
if (el('annual-validate-year')) el('annual-validate-year').disabled = false;
}
async function load(options = {}) {
const agentId = Number(el('annual-agent')?.value || 0);
const year = Number(el('annual-year')?.value || 0);
if (!agentId || year < 2000 || year > 2100) {
show('Choisissez un agent et une année valide.', 'error');
return;
}
if (options.clearValidation !== false && el('annual-validation-result')) {
el('annual-validation-result').innerHTML = '';
}
show('Construction de la vue annuelle…');
el('annual-content').hidden = true;
try {
const response = await fetch(`api/annual_overview.php?agent_id=${agentId}&year=${year}`);
const data = await readJsonResponse(response);
if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de charger la vue annuelle.'));
render(data);
show('Vue annuelle actualisée.', 'success');
} catch (error) {
show(error.message, 'error');
}
}
function validationPlanningUrl(planning) {
const agentId = Number(current?.agent?.id_agent || el('annual-agent')?.value || 0);
const week = `${Number(planning.annee)}-W${String(planning.numero_semaine).padStart(2, '0')}`;
const params = new URLSearchParams({
agent_id: String(agentId),
structure_id: String(Number(planning.id_structure)),
week,
focus: 'annual-validation',
notice: `Correction demandée depuis la vue annuelle — semaine ${String(planning.numero_semaine).padStart(2, '0')}.`,
});
return `planning.php?${params.toString()}`;
}
function renderValidationIssues(data) {
const container = el('annual-validation-result');
if (!container) return;
const invalid = (data.plannings || []).filter((planning) => planning.has_errors);
container.innerHTML = `
Aucune semaine n’a été validée.
${escapeHtml(data.error || 'Des erreurs doivent être corrigées avant la validation annuelle.')}
${invalid.length} planning(s) à corriger.
${invalid.map((planning) => {
const errors = (planning.controls || []).filter((control) => control.niveau === 'ERREUR');
return `
Semaine ${String(planning.numero_semaine).padStart(2, '0')} — ${escapeHtml(planning.structure_nom || 'Lieu non renseigné')}
Corriger cette semaine
${escapeHtml(formatDate(planning.date_debut))} → ${escapeHtml(formatDate(planning.date_fin))}
${errors.map((control) => `- ${escapeHtml(control.message)}
`).join('')}
`;
}).join('')}
`;
}
function renderValidationWarning(data) {
const container = el('annual-validation-result');
if (!container) return;
container.innerHTML = `
Avertissement avant validation.
${escapeHtml(data.message || 'Une confirmation est nécessaire.')}
`;
}
function renderValidationSuccess(data) {
const container = el('annual-validation-result');
if (!container) return;
container.innerHTML = `
Validation terminée.
${escapeHtml(data.message || 'Les plannings ont été validés.')}
`;
}
async function validateWholeYear(forceQuota = false) {
const agentId = Number(el('annual-agent')?.value || 0);
const year = Number(el('annual-year')?.value || 0);
if (!agentId || !year || !current || Number(current.agent?.id_agent) !== agentId || Number(current.year) !== year) {
show('Affichez d’abord la vue annuelle de l’agent à contrôler.', 'error');
return;
}
const button = el('annual-validate-year');
if (button) button.disabled = true;
const container = el('annual-validation-result');
if (container) {
container.innerHTML = 'Contrôle de toutes les semaines en brouillon…
';
}
try {
const response = await fetch('api/annual_validate.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
csrf_token: PTA.config.csrfToken,
agent_id: agentId,
year,
force_quota: Boolean(forceQuota),
}),
});
const data = await readJsonResponse(response);
if (response.status === 422 && data.status === 'errors') {
renderValidationIssues(data);
show('Des semaines doivent être corrigées avant la validation annuelle.', 'error');
return;
}
if (response.status === 409 && data.status === 'warning') {
renderValidationWarning(data);
const confirmed = window.confirm(`${data.message || 'Le quota annuel présente un avertissement.'}\n\nValider malgré cet avertissement ?`);
if (confirmed) await validateWholeYear(true);
return;
}
if (!response.ok) {
throw new Error(apiErrorMessage(data, 'Impossible de valider l’année.'));
}
renderValidationSuccess(data);
show(data.message || 'Validation annuelle terminée.', 'success');
await load({ clearValidation: false });
} catch (error) {
if (container) {
container.innerHTML = `${escapeHtml(error.message)}
`;
}
show(error.message, 'error');
} finally {
if (button) button.disabled = false;
}
}
function handleCalendarClick(event) {
const row = event.target.closest('tr[data-url]');
if (!row) return;
window.location.href = row.dataset.url;
}
async function toggleFullscreen() {
const target = el('annual-content');
if (!target) return;
try {
if (!document.fullscreenElement) {
await target.requestFullscreen?.();
} else {
await document.exitFullscreen?.();
}
} catch (error) {
show('Le mode plein écran n’est pas disponible dans ce navigateur.', 'error');
}
}
function init() {
el('load-annual')?.addEventListener('click', load);
el('annual-print')?.addEventListener('click', () => window.print());
el('annual-fullscreen')?.addEventListener('click', toggleFullscreen);
el('annual-validate-year')?.addEventListener('click', () => validateWholeYear(false));
el('annual-months')?.addEventListener('click', handleCalendarClick);
const params = window.PTA_PAGE_CONFIG?.params || {};
if (params.agentId) el('annual-agent').value = String(params.agentId);
if (params.year) el('annual-year').value = String(params.year);
if (params.agentId) load();
}
PTA.modules.annualOverview = { init, load };
})();