(() => { 'use strict'; const PTA = window.PTA; if (!PTA) throw new Error('PTA core doit être chargé avant week-templates.js'); const { state } = PTA; const { agentSelect, weekInput } = PTA.elements; const { showMessage, readJsonResponse, apiErrorMessage, escapeHtml } = PTA.utils; const saveButton = document.querySelector('#save-week-template'); const applyButton = document.querySelector('#apply-week-template'); const saveModal = document.querySelector('#save-week-template-modal'); const saveForm = document.querySelector('#save-week-template-form'); const saveNameInput = document.querySelector('#week-template-name'); const saveContext = document.querySelector('#save-week-template-context'); const saveMessage = document.querySelector('#save-week-template-message'); const applyModal = document.querySelector('#apply-week-template-modal'); const applyForm = document.querySelector('#apply-week-template-form'); const templateSelect = document.querySelector('#week-template-select'); const templateDetails = document.querySelector('#week-template-details'); const replaceWarning = document.querySelector('#week-template-replace-warning'); const deleteButton = document.querySelector('#delete-week-template'); const applyMessage = document.querySelector('#apply-week-template-message'); const periodButton = document.querySelector('#apply-week-template-period'); const periodModal = document.querySelector('#apply-week-template-period-modal'); const periodForm = document.querySelector('#apply-week-template-period-form'); const periodTemplateSelect = document.querySelector('#week-template-period-select'); const periodStartInput = document.querySelector('#week-template-period-start'); const periodEndInput = document.querySelector('#week-template-period-end'); const periodReplaceWarning = document.querySelector('#week-template-period-replace-warning'); const periodPreviewButton = document.querySelector('#preview-week-template-period'); const periodPreviewPanel = document.querySelector('#week-template-period-preview'); const periodConfirmButton = document.querySelector('#confirm-apply-week-template-period'); const periodMessage = document.querySelector('#apply-week-template-period-message'); let templates = []; let lastPeriodPreview = null; let lastPeriodPreviewSignature = ''; function contextReady() { return Boolean(agentSelect?.value && weekInput?.value); } function globalEntryCount() { return (state.entries?.length || 0) + (state.otherStructureEntries?.length || 0); } function refreshButtons() { const ready = contextReady(); if (saveButton) saveButton.disabled = !ready; if (applyButton) applyButton.disabled = !ready; if (periodButton) periodButton.disabled = !ready; } function setInlineMessage(element, text = '', type = 'info') { if (!element) return; element.className = text ? `message message-${type}` : 'message'; element.textContent = text; } function updateBodyModalState() { const anyOpen = Array.from(document.querySelectorAll('.modal-backdrop')) .some((modal) => !modal.hidden); document.body.classList.toggle('modal-open', anyOpen); } function closeModal(modal) { if (!modal) return; modal.hidden = true; updateBodyModalState(); } function openModal(modal) { if (!modal) return; modal.hidden = false; document.body.classList.add('modal-open'); } function agentLabel() { return agentSelect?.options[agentSelect.selectedIndex]?.textContent?.trim() || 'Agent sélectionné'; } async function persistCurrentEditorIfNeeded() { const planning = PTA.modules.planning; if (!planning || state.planningStatus === 'VALIDE') return true; if (!planning.isPlanningContextReady?.()) return true; const planningId = await planning.saveDraft?.({ silent: true }); return planningId !== null && planningId !== undefined; } async function openSaveModal() { if (!contextReady()) { showMessage('Sélectionnez un agent et une semaine avant d’enregistrer une semaine type.', 'warning'); return; } if (state.planningStatus !== 'VALIDE') { const synced = PTA.modules.planning?.syncEntriesFromWeeklyForm?.({ silent: true }); if (synced === false) return; } if (globalEntryCount() === 0) { showMessage('La semaine ne contient aucune affectation à enregistrer comme modèle.', 'warning'); return; } if (saveNameInput) saveNameInput.value = ''; if (saveContext) { saveContext.innerHTML = ` ${escapeHtml(agentLabel())} Semaine ${escapeHtml(weekInput.value)} · ${globalEntryCount()} créneau${globalEntryCount() > 1 ? 'x' : ''} actuellement affiché${globalEntryCount() > 1 ? 's' : ''} `; } setInlineMessage(saveMessage); openModal(saveModal); setTimeout(() => saveNameInput?.focus(), 0); } async function saveTemplate(replace = false) { const name = saveNameInput?.value?.trim() || ''; if (!name) { setInlineMessage(saveMessage, 'Saisissez un nom pour la semaine type.', 'warning'); saveNameInput?.focus(); return; } const submitButton = saveForm?.querySelector('button[type="submit"]'); if (submitButton) submitButton.disabled = true; setInlineMessage(saveMessage, 'Enregistrement du modèle...', 'info'); try { const persisted = await persistCurrentEditorIfNeeded(); if (!persisted) { throw new Error('Le planning courant n’a pas pu être enregistré avant la création du modèle.'); } const response = await fetch('api/save_week_template.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf_token: window.PTA_CONFIG.csrfToken, agent_id: Number(agentSelect.value), week: weekInput.value, name, replace, }), }); const data = await readJsonResponse(response); if (response.status === 409 && data.code === 'TEMPLATE_EXISTS' && !replace) { const confirmed = window.confirm(`Une semaine type nommée « ${name} » existe déjà pour cet agent. Voulez-vous la remplacer par la semaine actuelle ?`); if (confirmed) { await saveTemplate(true); } else { setInlineMessage(saveMessage, 'Enregistrement annulé.', 'info'); } return; } if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible d’enregistrer la semaine type.')); setInlineMessage(saveMessage, data.message, 'success'); showMessage(data.message, 'success'); setTimeout(() => closeModal(saveModal), 450); } catch (error) { setInlineMessage(saveMessage, error.message, 'error'); } finally { if (submitButton) submitButton.disabled = false; } } async function loadTemplates() { if (!agentSelect?.value) return []; const response = await fetch(`api/week_templates.php?agent_id=${encodeURIComponent(agentSelect.value)}`); const data = await readJsonResponse(response); if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de charger les semaines types.')); templates = Array.isArray(data.templates) ? data.templates : []; return templates; } function renderTemplateSelect() { if (!templateSelect) return; if (!templates.length) { templateSelect.innerHTML = ''; templateSelect.disabled = true; if (deleteButton) deleteButton.disabled = true; if (templateDetails) templateDetails.innerHTML = '
Créez d’abord une semaine type depuis une semaine déjà planifiée.
'; return; } templateSelect.disabled = false; templateSelect.innerHTML = '' + templates.map((template) => ( `` )).join(''); renderSelectedTemplateDetails(); } function renderSelectedTemplateDetails() { const selectedId = Number(templateSelect?.value || 0); const template = templates.find((item) => Number(item.id) === selectedId); if (deleteButton) deleteButton.disabled = !template; if (!templateDetails) return; if (!template) { templateDetails.innerHTML = 'Sélectionnez un modèle pour l’appliquer à la semaine affichée.
'; return; } templateDetails.innerHTML = `| Date | Contexte | Créés | Ignorés | Conflits |
|---|---|---|---|---|
| Aucune journée du modèle ne correspond à cette période. | ||||
Aperçu limité aux 80 premières journées concernées sur ${relevantDays.length}.
` : ''} `; periodPreviewPanel.hidden = false; const canApply = Number(summary.entry_count || 0) > 0 && (mode !== 'merge' || Number(summary.conflict_count || 0) === 0); if (periodConfirmButton) periodConfirmButton.disabled = !canApply; } async function previewPeriodApplication() { const templateId = Number(periodTemplateSelect?.value || 0); const startDate = periodStartInput?.value || ''; const endDate = periodEndInput?.value || ''; if (!templateId || !startDate || !endDate) { setInlineMessage(periodMessage, 'Sélectionnez une semaine type et renseignez les deux dates.', 'warning'); return; } if (endDate < startDate) { setInlineMessage(periodMessage, 'La date de fin doit être postérieure ou égale à la date de début.', 'warning'); return; } if (periodPreviewButton) periodPreviewButton.disabled = true; if (periodConfirmButton) periodConfirmButton.disabled = true; setInlineMessage(periodMessage, 'Analyse de la période et des vacances...', 'info'); try { const response = await fetch('api/preview_week_template_period.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf_token: window.PTA_CONFIG.csrfToken, template_id: templateId, agent_id: Number(agentSelect.value), start_date: startDate, end_date: endDate, mode: periodMode(), period_filter: periodFilter(), }), }); const data = await readJsonResponse(response); if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de prévisualiser l’application sur la période.')); lastPeriodPreview = data.preview; lastPeriodPreviewSignature = currentPeriodSignature(); renderPeriodPreview(lastPeriodPreview); setInlineMessage(periodMessage, 'Aperçu calculé. Vérifiez les dates avant de confirmer.', 'success'); } catch (error) { lastPeriodPreview = null; lastPeriodPreviewSignature = ''; if (periodPreviewPanel) periodPreviewPanel.hidden = true; setInlineMessage(periodMessage, error.message, 'error'); } finally { if (periodPreviewButton) periodPreviewButton.disabled = false; } } async function applyTemplatePeriod() { if (!lastPeriodPreview || lastPeriodPreviewSignature !== currentPeriodSignature()) { setInlineMessage(periodMessage, 'La période a changé. Relancez la prévisualisation avant d’appliquer le modèle.', 'warning'); if (periodConfirmButton) periodConfirmButton.disabled = true; return; } const templateId = Number(periodTemplateSelect?.value || 0); const template = templates.find((item) => Number(item.id) === templateId); const mode = periodMode(); const summary = lastPeriodPreview.summary || {}; if (mode === 'replace' && Number(summary.existing_entry_count_on_target_dates || 0) > 0) { const confirmed = window.confirm( `${Number(summary.existing_entry_count_on_target_dates)} créneau(x) existant(s) seront supprimé(s) sur les jours réellement concernés avant d’appliquer « ${template?.name || 'la semaine type'} ».\n\nLes dates exclues par le filtre de calendrier ne seront pas modifiées. Continuer ?` ); if (!confirmed) return; } if (periodConfirmButton) periodConfirmButton.disabled = true; if (periodPreviewButton) periodPreviewButton.disabled = true; setInlineMessage(periodMessage, 'Application de la semaine type sur la période...', 'info'); try { const response = await fetch('api/apply_week_template_period.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf_token: window.PTA_CONFIG.csrfToken, template_id: templateId, agent_id: Number(agentSelect.value), start_date: periodStartInput.value, end_date: periodEndInput.value, mode, period_filter: periodFilter(), }), }); const data = await readJsonResponse(response); if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible d’appliquer la semaine type sur la période.')); await PTA.modules.planning?.loadPlanning?.(); PTA.modules.pendingValidation?.refresh?.({ silent: true }); PTA.modules.coverage?.refresh?.({ silent: true }); showMessage(data.message, 'success'); setInlineMessage(periodMessage, data.message, 'success'); lastPeriodPreview = null; lastPeriodPreviewSignature = ''; setTimeout(() => closeModal(periodModal), 700); } catch (error) { setInlineMessage(periodMessage, error.message, 'error'); // Le planning peut avoir changé depuis l'aperçu : imposer une nouvelle prévisualisation. lastPeriodPreview = null; lastPeriodPreviewSignature = ''; if (periodConfirmButton) periodConfirmButton.disabled = true; } finally { if (periodPreviewButton) periodPreviewButton.disabled = false; } } async function deleteSelectedTemplate() { const templateId = Number(templateSelect?.value || 0); const template = templates.find((item) => Number(item.id) === templateId); if (!template) return; const confirmed = window.confirm(`Supprimer définitivement la semaine type « ${template.name} » ?`); if (!confirmed) return; if (deleteButton) deleteButton.disabled = true; try { const response = await fetch('api/delete_week_template.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf_token: window.PTA_CONFIG.csrfToken, template_id: templateId, agent_id: Number(agentSelect.value), }), }); const data = await readJsonResponse(response); if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible de supprimer la semaine type.')); setInlineMessage(applyMessage, data.message, 'success'); await loadTemplates(); renderTemplateSelect(); } catch (error) { setInlineMessage(applyMessage, error.message, 'error'); renderSelectedTemplateDetails(); } } function updateReplaceWarning() { const mode = applyForm?.querySelector('input[name="week-template-mode"]:checked')?.value || 'merge'; if (replaceWarning) replaceWarning.hidden = mode !== 'replace'; } function init() { saveButton?.addEventListener('click', openSaveModal); applyButton?.addEventListener('click', openApplyModal); periodButton?.addEventListener('click', openPeriodModal); saveForm?.addEventListener('submit', (event) => { event.preventDefault(); saveTemplate(false); }); applyForm?.addEventListener('submit', (event) => { event.preventDefault(); applyTemplate(); }); periodForm?.addEventListener('submit', (event) => { event.preventDefault(); applyTemplatePeriod(); }); periodPreviewButton?.addEventListener('click', previewPeriodApplication); document.querySelector('#close-save-week-template-modal')?.addEventListener('click', () => closeModal(saveModal)); document.querySelector('#cancel-save-week-template')?.addEventListener('click', () => closeModal(saveModal)); document.querySelector('#close-apply-week-template-modal')?.addEventListener('click', () => closeModal(applyModal)); document.querySelector('#cancel-apply-week-template')?.addEventListener('click', () => closeModal(applyModal)); document.querySelector('#close-apply-week-template-period-modal')?.addEventListener('click', () => closeModal(periodModal)); document.querySelector('#cancel-apply-week-template-period')?.addEventListener('click', () => closeModal(periodModal)); saveModal?.addEventListener('click', (event) => { if (event.target === saveModal) closeModal(saveModal); }); applyModal?.addEventListener('click', (event) => { if (event.target === applyModal) closeModal(applyModal); }); periodModal?.addEventListener('click', (event) => { if (event.target === periodModal) closeModal(periodModal); }); templateSelect?.addEventListener('change', renderSelectedTemplateDetails); applyForm?.querySelectorAll('input[name="week-template-mode"]').forEach((input) => { input.addEventListener('change', updateReplaceWarning); }); periodForm?.querySelectorAll('input[name="week-template-period-mode"]').forEach((input) => { input.addEventListener('change', invalidatePeriodPreview); }); periodForm?.querySelectorAll('input[name="week-template-period-filter"]').forEach((input) => { input.addEventListener('change', invalidatePeriodPreview); }); [periodTemplateSelect, periodStartInput, periodEndInput].forEach((input) => { input?.addEventListener('change', invalidatePeriodPreview); input?.addEventListener('input', invalidatePeriodPreview); }); deleteButton?.addEventListener('click', deleteSelectedTemplate); agentSelect?.addEventListener('change', refreshButtons); weekInput?.addEventListener('input', refreshButtons); weekInput?.addEventListener('change', refreshButtons); document.addEventListener('keydown', (event) => { if (event.key !== 'Escape') return; if (saveModal && !saveModal.hidden) closeModal(saveModal); if (applyModal && !applyModal.hidden) closeModal(applyModal); if (periodModal && !periodModal.hidden) closeModal(periodModal); }); updateReplaceWarning(); updatePeriodReplaceWarning(); refreshButtons(); } PTA.modules.weekTemplates = { init, refreshButtons, loadTemplates, }; })();