(() => { 'use strict'; const PTA = window.PTA; if (!PTA) throw new Error('PTA core doit être chargé avant agent-overview.js'); const permissions = window.PTA_CONFIG?.permissions || {}; const accessProfile = window.PTA_CONFIG?.access || {}; const canEditDraft = Boolean(permissions.can_edit_draft); const canEditCrossStructure = Boolean(permissions.can_edit_cross_structure); const canCopyFullWeek = Boolean(permissions.can_copy_full_week); const { shared } = PTA; const { escapeHtml, readJsonResponse, apiErrorMessage, formatDate, timeToMinutes, formatDuration, } = PTA.utils; let draggedEntry = null; let suppressClickUntil = 0; let activeSourceWeek = ''; const typeLabel = (type) => type === 'EXTRASCOLAIRE' ? 'Extrascolaire' : 'Périscolaire'; function setMessage(element, text = '', type = 'info') { if (!element) return; element.className = text ? `message message-${type}` : 'message'; element.textContent = text; } function canEditEntry(entry) { if (!canEditDraft) return false; if (canEditCrossStructure) return true; return Number(entry.id_structure) === Number(accessProfile.structure_id || 0); } function entryBadge(entry) { const editable = canEditEntry(entry); const interactionAttributes = editable ? `draggable="true" tabindex="0" role="button" data-editable="1" title="Glissez vers un autre jour pour déplacer. Ctrl/⌘ + glisser pour dupliquer."` : 'data-editable="0"'; return `
${editable ? '' : ''}
${escapeHtml(entry.heure_debut)}–${escapeHtml(entry.heure_fin)} ${escapeHtml(entry.motif_libelle)} 📍 ${escapeHtml(entry.structure_nom)} · ${typeLabel(entry.structure_type_affectation)} ${entry.statut === 'BROUILLON' ? 'Brouillon' : ''}
${editable ? `` : 'Lecture seule'}
`; } function countedMinutes(entries) { return entries.reduce((sum, entry) => { if (!Number(entry.compte_dans_quota)) return sum; return sum + (timeToMinutes(entry.heure_fin) - timeToMinutes(entry.heure_debut)); }, 0); } function render(data) { const container = document.querySelector('#agent-overview-content'); const empty = document.querySelector('#agent-overview-empty'); if (!container || !empty) return; shared.lastAgentOverviewData = data; empty.hidden = true; container.hidden = false; const monthMinutes = countedMinutes(data.entries || []); const weekBlocks = (data.weeks || []).map((week) => { const days = week.days.map((day) => { const entries = (data.entries || []).filter((entry) => entry.date_jour === day.date); return `
${escapeHtml(day.label)} ${escapeHtml(day.display)}
${entries.length ? entries.map(entryBadge).join('') : '

Aucune affectation

'}
`; }).join(''); return `
Semaine ${Number(week.number)} du ${formatDate(week.date_debut)} au ${formatDate(week.date_fin)}
${canCopyFullWeek ? `` : ''}
${days}
`; }).join(''); container.innerHTML = `

${escapeHtml(data.agent.prenom)} ${escapeHtml(data.agent.nom)}

${escapeHtml(data.agent.matricule)}${data.agent.structure_principale_nom ? ` · lieu principal : ${escapeHtml(data.agent.structure_principale_nom)} (${typeLabel(data.agent.structure_principale_type_affectation)})` : ''} · ${escapeHtml(data.month.label)}

${Number(data.agent.est_diplome) ? `🎓 Diplômé${data.agent.diplome_libelle ? ` — ${escapeHtml(data.agent.diplome_libelle)}` : ''}` : 'Non diplômé'}${data.agent.telephone ? ` · 📞 ${escapeHtml(data.agent.telephone)}` : ''}${data.agent.adresse ? ` · 📍 ${escapeHtml(data.agent.adresse)}` : ''}${data.agent.date_debut_contrat ? ` · Contrat depuis le ${escapeHtml(formatDate(data.agent.date_debut_contrat))}` : ' · ⚠ Date de début de contrat non renseignée'}

Quotité${Number(data.quota.quotite_travail).toLocaleString('fr-FR')} %
Quota de la période${escapeHtml(data.quota.quota_cible.libelle)}${escapeHtml(data.quota.periode_libelle || '')}
Décompté sur le mois affiché${escapeHtml(formatDuration(monthMinutes))}Travail + motifs comptant dans le quota
Reste à affecter${escapeHtml(data.quota.restantes.libelle)}Calcul sur l’année civile du 1er janvier au 31 décembre.

${canEditDraft ? (canCopyFullWeek ? 'Glissez un créneau modifiable vers un autre jour pour le déplacer. Maintenez Ctrl (ou sur Mac) pour le dupliquer. La duplication complète d’une semaine est réservée au Service Enfance.' : 'Vous pouvez modifier les créneaux de votre lieu. Les créneaux saisis sur un autre lieu restent en lecture seule et toute modification est enregistrée en brouillon.') : 'Cette vue est en lecture seule. Vous pouvez consulter votre planning et télécharger les documents disponibles.'}

${weekBlocks}
`; bindMonthInteractions(); } function findEntry(entryId) { return shared.lastAgentOverviewData?.entries?.find((item) => Number(item.id_creneau) === Number(entryId)) || null; } async function persistEntryMove(entryId, targetDate, duplicate) { const response = await fetch('api/move_or_duplicate_entry.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf_token: window.PTA_CONFIG.csrfToken, entry_id: Number(entryId), target_date: targetDate, action: duplicate ? 'duplicate' : 'move', }), }); const data = await readJsonResponse(response); if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de déplacer ce créneau.')); return data; } function clearDropTargets() { document.querySelectorAll('#agent-overview-content .agent-month-day-card').forEach((card) => { card.classList.remove('is-drop-target', 'is-copy-target'); }); } function bindMonthInteractions() { const container = document.querySelector('#agent-overview-content'); if (!container) return; container.querySelectorAll('.agent-month-entry[data-editable="1"]').forEach((item) => { item.addEventListener('dragstart', (event) => { draggedEntry = findEntry(Number(item.dataset.entryId)); item.classList.add('is-dragging'); event.dataTransfer.effectAllowed = 'copyMove'; event.dataTransfer.setData('text/plain', item.dataset.entryId || ''); }); item.addEventListener('dragend', () => { item.classList.remove('is-dragging'); draggedEntry = null; suppressClickUntil = Date.now() + 250; clearDropTargets(); }); item.addEventListener('keydown', (event) => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); openModal(Number(item.dataset.entryId)); } }); item.addEventListener('click', (event) => { if (Date.now() < suppressClickUntil || event.target.closest('button')) return; openModal(Number(item.dataset.entryId)); }); }); if (!canEditDraft) return; container.querySelectorAll('.agent-month-day-card').forEach((card) => { card.addEventListener('dragover', (event) => { if (!draggedEntry) return; event.preventDefault(); const duplicate = event.ctrlKey || event.metaKey; event.dataTransfer.dropEffect = duplicate ? 'copy' : 'move'; clearDropTargets(); card.classList.add('is-drop-target'); if (duplicate) card.classList.add('is-copy-target'); }); card.addEventListener('dragleave', (event) => { if (!card.contains(event.relatedTarget)) card.classList.remove('is-drop-target', 'is-copy-target'); }); card.addEventListener('drop', async (event) => { if (!draggedEntry) return; event.preventDefault(); const entry = draggedEntry; const targetDate = card.dataset.date || ''; const duplicate = event.ctrlKey || event.metaKey; clearDropTargets(); try { const result = await persistEntryMove(entry.id_creneau, targetDate, duplicate); await load(); setMessage(document.querySelector('#agent-overview-message'), result.message || (duplicate ? 'Créneau dupliqué.' : 'Créneau déplacé.'), 'success'); } catch (error) { setMessage(document.querySelector('#agent-overview-message'), error.message, 'error'); } }); }); } function openHoursContractPdf() { const data = shared.lastAgentOverviewData; const agentId = data?.agent?.id_agent || document.querySelector('#overview-agent')?.value; const date = data?.month?.date_debut; if (!agentId || !date) return; const params = new URLSearchParams({ agent_id: String(agentId), date: String(date) }); window.open(`api/agent_hours_contract_pdf.php?${params.toString()}`, '_blank', 'noopener'); } function openTimeSummaryPdf() { const data = shared.lastAgentOverviewData; const agentId = data?.agent?.id_agent || document.querySelector('#overview-agent')?.value; const date = data?.month?.date_debut; if (!agentId || !date) return; const params = new URLSearchParams({ agent_id: String(agentId), date: String(date) }); window.open(`api/agent_time_summary_pdf.php?${params.toString()}`, '_blank', 'noopener'); } function closeModal() { const modal = document.querySelector('#agent-entry-modal'); const form = document.querySelector('#agent-entry-edit-form'); const message = document.querySelector('#agent-entry-edit-message'); if (!modal) return; modal.hidden = true; document.body.classList.remove('modal-open'); form?.reset(); setMessage(message); } function openModal(entryId) { const entry = findEntry(entryId); if (!entry || !canEditEntry(entry)) return; const modal = document.querySelector('#agent-entry-modal'); if (!entry || !modal) return; document.querySelector('#edit-agent-entry-id').value = String(Number(entry.id_creneau)); document.querySelector('#edit-agent-entry-date').value = entry.date_jour; document.querySelector('#edit-agent-entry-structure').value = String(Number(entry.id_structure)); document.querySelector('#edit-agent-entry-motif').value = String(Number(entry.id_motif)); document.querySelector('#edit-agent-entry-start').value = entry.heure_debut; document.querySelector('#edit-agent-entry-end').value = entry.heure_fin; setMessage(document.querySelector('#agent-entry-edit-message')); modal.hidden = false; document.body.classList.add('modal-open'); document.querySelector('#edit-agent-entry-motif')?.focus(); } async function deleteEntry(entryId) { const entry = findEntry(entryId); if (!entry || !canEditEntry(entry)) return; if (!entry) return; const details = `${entry.motif_libelle} · ${entry.heure_debut}–${entry.heure_fin} · ${entry.structure_nom}`; if (!window.confirm(`Supprimer définitivement ce créneau ?\n\n${details}\n${formatDate(entry.date_jour)}`)) return; const button = document.querySelector('#delete-agent-entry-edit'); const message = document.querySelector('#agent-entry-edit-message'); if (button) button.disabled = true; setMessage(message, 'Suppression du créneau en cours…', 'info'); try { const response = await fetch('api/delete_agent_entry.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf_token: window.PTA_CONFIG.csrfToken, entry_id: Number(entryId), }), }); const data = await readJsonResponse(response); if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de supprimer ce créneau.')); await load(); closeModal(); setMessage(document.querySelector('#agent-overview-message'), data.message || 'Créneau supprimé.', 'success'); // La suppression peut modifier la file de validation et créer un trou de couverture. PTA.modules.pendingValidation?.refresh?.({ silent: true }); PTA.modules.coverage?.refresh?.({ silent: true }); } catch (error) { setMessage(message, error.message, 'error'); } finally { if (button) button.disabled = false; } } async function saveEntry(event) { event.preventDefault(); if (!canEditDraft) return; const submit = document.querySelector('#save-agent-entry-edit'); const message = document.querySelector('#agent-entry-edit-message'); if (!submit || !message) return; submit.disabled = true; setMessage(message); try { const response = await fetch('api/update_agent_entry.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf_token: window.PTA_CONFIG.csrfToken, entry_id: Number(document.querySelector('#edit-agent-entry-id').value), structure_id: Number(document.querySelector('#edit-agent-entry-structure').value), motif_id: Number(document.querySelector('#edit-agent-entry-motif').value), date: document.querySelector('#edit-agent-entry-date').value, start: document.querySelector('#edit-agent-entry-start').value, end: document.querySelector('#edit-agent-entry-end').value, }), }); const data = await readJsonResponse(response); if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de modifier ce créneau.')); setMessage(message, data.message || 'Créneau modifié.', 'success'); await load(); setTimeout(closeModal, 450); } catch (error) { setMessage(message, error.message, 'error'); } finally { submit.disabled = false; } } function isoWeekValueFromDate(date) { const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); const day = d.getUTCDay() || 7; d.setUTCDate(d.getUTCDate() + 4 - day); const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1)); const weekNo = Math.ceil((((d - yearStart) / 86400000) + 1) / 7); return `${d.getUTCFullYear()}-W${String(weekNo).padStart(2, '0')}`; } function weekMonday(weekValue) { const match = /^(\d{4})-W(\d{2})$/.exec(weekValue); if (!match) return null; const year = Number(match[1]); const week = Number(match[2]); const jan4 = new Date(year, 0, 4); const day = jan4.getDay() || 7; const monday = new Date(jan4); monday.setDate(jan4.getDate() - day + 1 + ((week - 1) * 7)); return monday; } function openWeekCopyModal(sourceWeek) { if (!canCopyFullWeek) return; const modal = document.querySelector('#agent-week-copy-modal'); const sourceInput = document.querySelector('#agent-week-copy-source'); const targetInput = document.querySelector('#agent-week-copy-target'); const summary = document.querySelector('#agent-week-copy-summary'); if (!modal || !sourceInput || !targetInput) return; activeSourceWeek = sourceWeek; sourceInput.value = sourceWeek; const monday = weekMonday(sourceWeek); if (monday) { monday.setDate(monday.getDate() + 7); targetInput.value = isoWeekValueFromDate(monday); } if (summary) summary.textContent = `La semaine ${sourceWeek.replace('-W', ' / S')} sera recopiée avec ses lieux, motifs et horaires.`; setMessage(document.querySelector('#agent-week-copy-message')); modal.hidden = false; document.body.classList.add('modal-open'); targetInput.focus(); } function closeWeekCopyModal() { const modal = document.querySelector('#agent-week-copy-modal'); if (!modal) return; modal.hidden = true; document.body.classList.remove('modal-open'); activeSourceWeek = ''; document.querySelector('#agent-week-copy-form')?.reset(); setMessage(document.querySelector('#agent-week-copy-message')); } async function copyWeek(event) { event.preventDefault(); if (!canCopyFullWeek) return; const agentId = Number(document.querySelector('#overview-agent')?.value || 0); const sourceWeek = document.querySelector('#agent-week-copy-source')?.value || activeSourceWeek; const targetWeek = document.querySelector('#agent-week-copy-target')?.value || ''; const mode = document.querySelector('input[name="agent-week-copy-mode"]:checked')?.value || 'merge'; const message = document.querySelector('#agent-week-copy-message'); const submit = document.querySelector('#confirm-agent-week-copy'); if (!agentId || !sourceWeek || !targetWeek) { setMessage(message, 'Sélectionnez une semaine cible.', 'warning'); return; } if (sourceWeek === targetWeek) { setMessage(message, 'La semaine cible doit être différente de la semaine source.', 'warning'); return; } if (mode === 'replace' && !window.confirm('Les créneaux existants de la semaine cible seront supprimés avant la copie. Continuer ?')) return; if (submit) submit.disabled = true; setMessage(message, 'Duplication de la semaine en cours...', 'info'); try { const response = await fetch('api/copy_agent_week.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf_token: window.PTA_CONFIG.csrfToken, agent_id: agentId, source_week: sourceWeek, target_week: targetWeek, mode, }), }); const data = await readJsonResponse(response); if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de dupliquer cette semaine.')); setMessage(message, data.message || 'Semaine dupliquée.', 'success'); await load(); setMessage(document.querySelector('#agent-overview-message'), data.message || 'Semaine dupliquée.', 'success'); setTimeout(closeWeekCopyModal, 500); } catch (error) { setMessage(message, error.message, 'error'); } finally { if (submit) submit.disabled = false; } } async function load() { const agentId = document.querySelector('#overview-agent')?.value; const month = document.querySelector('#agent-overview-month')?.value; const container = document.querySelector('#agent-overview-content'); const empty = document.querySelector('#agent-overview-empty'); if (!container || !empty) return; if (!agentId || !month) { empty.hidden = false; empty.textContent = 'Choisissez un agent et un mois.'; container.hidden = true; return; } setMessage(document.querySelector('#agent-overview-message')); empty.hidden = false; empty.textContent = 'Chargement du planning mensuel de l’agent…'; container.hidden = true; try { const params = new URLSearchParams({ agent_id: agentId, month }); const response = await fetch(`api/agent_month_overview.php?${params.toString()}`); const data = await readJsonResponse(response); if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de charger la vue mensuelle de l’agent.')); render(data); } catch (error) { empty.hidden = false; empty.textContent = error.message; } } function init() { document.querySelector('#load-agent-overview')?.addEventListener('click', load); document.querySelector('#overview-agent')?.addEventListener('change', load); document.querySelector('#agent-overview-month')?.addEventListener('change', load); document.querySelector('#agent-overview-content')?.addEventListener('click', (event) => { if (event.target.closest('.agent-hours-contract-pdf')) { openHoursContractPdf(); return; } if (event.target.closest('.agent-time-summary-pdf')) { openTimeSummaryPdf(); return; } const copyButton = event.target.closest('.agent-copy-week'); if (copyButton && canCopyFullWeek) { openWeekCopyModal(copyButton.dataset.sourceWeek || ''); return; } const editButton = event.target.closest('.overview-entry-edit'); if (editButton && canEditDraft) openModal(Number(editButton.dataset.entryId)); }); if (canEditDraft) { document.querySelector('#agent-entry-edit-form')?.addEventListener('submit', saveEntry); document.querySelector('#delete-agent-entry-edit')?.addEventListener('click', () => { const entryId = Number(document.querySelector('#edit-agent-entry-id')?.value || 0); if (entryId) deleteEntry(entryId); }); document.querySelector('#close-agent-entry-modal')?.addEventListener('click', closeModal); document.querySelector('#cancel-agent-entry-edit')?.addEventListener('click', closeModal); document.querySelector('#agent-entry-modal')?.addEventListener('click', (event) => { if (event.target.id === 'agent-entry-modal') closeModal(); }); } if (canCopyFullWeek) { document.querySelector('#agent-week-copy-form')?.addEventListener('submit', copyWeek); document.querySelector('#close-agent-week-copy-modal')?.addEventListener('click', closeWeekCopyModal); document.querySelector('#cancel-agent-week-copy')?.addEventListener('click', closeWeekCopyModal); document.querySelector('#agent-week-copy-modal')?.addEventListener('click', (event) => { if (event.target.id === 'agent-week-copy-modal') closeWeekCopyModal(); }); } document.addEventListener('keydown', (event) => { if (event.key !== 'Escape') return; if (!document.querySelector('#agent-entry-modal')?.hidden) closeModal(); if (!document.querySelector('#agent-week-copy-modal')?.hidden) closeWeekCopyModal(); }); } PTA.modules.agentOverview = { init, load }; })();