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

692 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(() => {
'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 denregistrer 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 = `
<strong>${escapeHtml(agentLabel())}</strong>
<span>Semaine ${escapeHtml(weekInput.value)} · ${globalEntryCount()} créneau${globalEntryCount() > 1 ? 'x' : ''} actuellement affiché${globalEntryCount() > 1 ? 's' : ''}</span>
`;
}
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 na 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 denregistrer 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 = '<option value="">Aucune semaine type enregistrée</option>';
templateSelect.disabled = true;
if (deleteButton) deleteButton.disabled = true;
if (templateDetails) templateDetails.innerHTML = '<p class="hint">Créez dabord une semaine type depuis une semaine déjà planifiée.</p>';
return;
}
templateSelect.disabled = false;
templateSelect.innerHTML = '<option value="">Choisir une semaine type</option>' + templates.map((template) => (
`<option value="${template.id}">${escapeHtml(template.name)}</option>`
)).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 = '<p class="hint">Sélectionnez un modèle pour lappliquer à la semaine affichée.</p>';
return;
}
templateDetails.innerHTML = `
<div class="week-template-detail-card">
<strong>${escapeHtml(template.name)}</strong>
<span>${Number(template.entry_count)} créneau${Number(template.entry_count) > 1 ? 'x' : ''} · ${Number(template.location_count)} lieu${Number(template.location_count) > 1 ? 'x' : ''}</span>
</div>
`;
}
async function openApplyModal() {
if (!contextReady()) {
showMessage('Sélectionnez un agent et la semaine cible avant dimporter une semaine type.', 'warning');
return;
}
setInlineMessage(applyMessage, 'Chargement des modèles...', 'info');
if (templateSelect) {
templateSelect.disabled = true;
templateSelect.innerHTML = '<option value="">Chargement...</option>';
}
openModal(applyModal);
try {
await loadTemplates();
renderTemplateSelect();
setInlineMessage(applyMessage);
} catch (error) {
setInlineMessage(applyMessage, error.message, 'error');
}
}
async function applyTemplate() {
const templateId = Number(templateSelect?.value || 0);
const mode = applyForm?.querySelector('input[name="week-template-mode"]:checked')?.value || 'merge';
const template = templates.find((item) => Number(item.id) === templateId);
if (!templateId || !template) {
setInlineMessage(applyMessage, 'Sélectionnez une semaine type.', 'warning');
return;
}
if (mode === 'replace') {
const confirmed = window.confirm(
`Le mode « Remplacer » va supprimer toutes les affectations actuelles de ${agentLabel()} pour ${weekInput.value}, tous lieux confondus, puis appliquer « ${template.name} ».\n\nContinuer ?`
);
if (!confirmed) return;
}
const submitButton = applyForm?.querySelector('button[type="submit"]');
if (submitButton) submitButton.disabled = true;
if (deleteButton) deleteButton.disabled = true;
setInlineMessage(applyMessage, 'Import de la semaine type...', 'info');
try {
// En mode compléter, persister d'abord les éventuelles modifications locales non encore enregistrées.
if (mode === 'merge') {
const persisted = await persistCurrentEditorIfNeeded();
if (!persisted) {
throw new Error('Le planning courant na pas pu être enregistré avant limport.');
}
}
const response = await fetch('api/apply_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),
week: weekInput.value,
mode,
}),
});
const data = await readJsonResponse(response);
if (!response.ok) throw new Error(apiErrorMessage(data, 'Impossible dimporter la semaine type.'));
if (data.quota) state.quota = data.quota;
await PTA.modules.planning?.loadPlanning?.();
setInlineMessage(applyMessage, data.message, 'success');
showMessage(data.message, 'success');
setTimeout(() => closeModal(applyModal), 550);
} catch (error) {
setInlineMessage(applyMessage, error.message, 'error');
} finally {
if (submitButton) submitButton.disabled = false;
renderSelectedTemplateDetails();
}
}
function periodMode() {
return periodForm?.querySelector('input[name="week-template-period-mode"]:checked')?.value || 'merge';
}
function periodFilter() {
return periodForm?.querySelector('input[name="week-template-period-filter"]:checked')?.value || 'all';
}
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(Date.UTC(year, 0, 4));
const jan4Day = jan4.getUTCDay() || 7;
const monday = new Date(jan4);
monday.setUTCDate(jan4.getUTCDate() - jan4Day + 1 + (week - 1) * 7);
return monday;
}
function dateInputValue(date) {
if (!(date instanceof Date) || Number.isNaN(date.getTime())) return '';
const year = date.getUTCFullYear();
const month = String(date.getUTCMonth() + 1).padStart(2, '0');
const day = String(date.getUTCDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
function formatDateFr(value) {
if (!value) return '';
const date = new Date(`${value}T12:00:00`);
return new Intl.DateTimeFormat('fr-FR', { day: '2-digit', month: '2-digit', year: 'numeric' }).format(date);
}
function invalidatePeriodPreview() {
lastPeriodPreview = null;
lastPeriodPreviewSignature = '';
if (periodConfirmButton) periodConfirmButton.disabled = true;
if (periodPreviewPanel) {
periodPreviewPanel.hidden = true;
periodPreviewPanel.innerHTML = '';
}
setInlineMessage(periodMessage);
updatePeriodReplaceWarning();
}
function currentPeriodSignature() {
return JSON.stringify({
template_id: Number(periodTemplateSelect?.value || 0),
start_date: periodStartInput?.value || '',
end_date: periodEndInput?.value || '',
mode: periodMode(),
period_filter: periodFilter(),
});
}
function renderPeriodTemplateSelect() {
if (!periodTemplateSelect) return;
if (!templates.length) {
periodTemplateSelect.innerHTML = '<option value="">Aucune semaine type enregistrée</option>';
periodTemplateSelect.disabled = true;
return;
}
periodTemplateSelect.disabled = false;
periodTemplateSelect.innerHTML = '<option value="">Choisir une semaine type</option>' + templates.map((template) => (
`<option value="${template.id}">${escapeHtml(template.name)}</option>`
)).join('');
}
function setDefaultPeriodDates() {
const monday = weekMonday(weekInput?.value);
if (!monday) return;
if (periodStartInput && !periodStartInput.value) periodStartInput.value = dateInputValue(monday);
if (periodEndInput && !periodEndInput.value) {
const friday = new Date(monday);
friday.setUTCDate(monday.getUTCDate() + 4);
periodEndInput.value = dateInputValue(friday);
}
}
async function openPeriodModal() {
if (!contextReady()) {
showMessage('Sélectionnez un agent et une semaine avant dappliquer une semaine type sur une période.', 'warning');
return;
}
invalidatePeriodPreview();
setDefaultPeriodDates();
if (periodTemplateSelect) {
periodTemplateSelect.disabled = true;
periodTemplateSelect.innerHTML = '<option value="">Chargement...</option>';
}
openModal(periodModal);
setInlineMessage(periodMessage, 'Chargement des modèles...', 'info');
try {
await loadTemplates();
renderPeriodTemplateSelect();
setInlineMessage(periodMessage);
} catch (error) {
setInlineMessage(periodMessage, error.message, 'error');
}
}
function updatePeriodReplaceWarning() {
if (periodReplaceWarning) periodReplaceWarning.hidden = periodMode() !== 'replace';
}
function renderPeriodPreview(preview) {
if (!periodPreviewPanel) return;
const summary = preview.summary || {};
const conflicts = Array.isArray(preview.conflicts) ? preview.conflicts : [];
const days = Array.isArray(preview.days) ? preview.days : [];
const vacations = Array.isArray(preview.vacation_periods) ? preview.vacation_periods : [];
const mode = preview.mode || periodMode();
const filter = preview.period_filter || periodFilter();
const filterLabel = filter === 'school'
? 'Périodes scolaires uniquement'
: filter === 'extra'
? 'Périodes extrascolaires uniquement'
: 'Toutes les semaines';
const vacationHtml = vacations.length
? `<div class="week-template-period-vacations"><strong>Vacances prises en compte</strong><ul>${vacations.map((item) => `<li>${escapeHtml(item.label)} : ${formatDateFr(item.start_date)}${formatDateFr(item.end_date)}</li>`).join('')}</ul></div>`
: filter === 'extra'
? '<div class="week-template-period-alert is-warning"><strong>Aucune période de vacances enregistrée sur cette plage.</strong> Aucun jour ne pourra être considéré comme extrascolaire tant que le calendrier nest pas renseigné.</div>'
: '<div class="week-template-period-alert is-warning"><strong>Aucune période de vacances enregistrée sur cette plage.</strong> Les dates seront considérées comme scolaires.</div>';
const relevantDays = days.filter((day) => Number(day.eligible_count) > 0 || Number(day.skipped_count) > 0 || Number(day.conflict_count) > 0);
const dayRows = relevantDays.slice(0, 80).map((day) => {
const periodLabel = day.period_type === 'EXTRASCOLAIRE'
? `Extrascolaire${day.vacation_label ? ` · ${escapeHtml(day.vacation_label)}` : ''}`
: 'Périscolaire';
return `<tr>
<td>${formatDateFr(day.date)}</td>
<td><span class="weekly-period-badge ${day.period_type === 'EXTRASCOLAIRE' ? 'is-extra' : 'is-peri'}">${periodLabel}</span></td>
<td>${Number(day.eligible_count)}</td>
<td>${Number(day.skipped_count)}</td>
<td>${Number(day.conflict_count) || '—'}</td>
</tr>`;
}).join('');
const conflictHtml = conflicts.length
? `<div class="week-template-period-alert is-error"><strong>${conflicts.length} conflit${conflicts.length > 1 ? 's' : ''} détecté${conflicts.length > 1 ? 's' : ''}.</strong> ${mode === 'merge' ? 'Le mode « Compléter » ne peut pas être appliqué tant que ces conflits existent. Vous pouvez choisir « Remplacer les jours concernés » après vérification.' : 'Ces créneaux existants seront remplacés sur les jours concernés.'}</div>`
: '<div class="week-template-period-alert is-success"><strong>Aucun chevauchement détecté.</strong></div>';
periodPreviewPanel.innerHTML = `
<div class="week-template-period-summary">
<article><span>Périmètre</span><strong>${escapeHtml(filterLabel)}</strong></article>
<article><span>Créneaux à créer</span><strong>${Number(summary.entry_count || 0)}</strong></article>
<article><span>Jours concernés</span><strong>${Number(summary.target_day_count || 0)}</strong></article>
<article><span>Semaines touchées</span><strong>${Number(summary.week_count || 0)}</strong></article>
<article><span>Ignorés (vacances)</span><strong>${Number(summary.skipped_vacation_count || 0)}</strong></article>
<article><span>Ignorés (hors vacances)</span><strong>${Number(summary.skipped_school_count || 0)}</strong></article>
<article><span>${mode === 'replace' ? 'Créneaux existants remplacés' : 'Conflits'}</span><strong>${mode === 'replace' ? Number(summary.existing_entry_count_on_target_dates || 0) : Number(summary.conflict_count || 0)}</strong></article>
</div>
${vacationHtml}
${conflictHtml}
<div class="table-scroll week-template-period-table-wrap">
<table class="data-table week-template-period-table">
<thead><tr><th>Date</th><th>Contexte</th><th>Créés</th><th>Ignorés</th><th>Conflits</th></tr></thead>
<tbody>${dayRows || '<tr><td colspan="5" class="empty-cell">Aucune journée du modèle ne correspond à cette période.</td></tr>'}</tbody>
</table>
</div>
${relevantDays.length > 80 ? `<p class="hint">Aperçu limité aux 80 premières journées concernées sur ${relevantDays.length}.</p>` : ''}
`;
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 lapplication 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 dappliquer 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 dappliquer « ${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 dappliquer 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,
};
})();