pour prod

This commit is contained in:
Loic Masi
2026-08-07 16:13:43 +02:00
commit 5fbf76868f
157 changed files with 24085 additions and 0 deletions

691
assets/js/week-templates.js Normal file
View File

@@ -0,0 +1,691 @@
(() => {
'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,
};
})();