diff --git a/loko/interventions/static/interventions/interventions_index.css b/loko/interventions/static/interventions/interventions_index.css index 0eff3bc..f5df64f 100644 --- a/loko/interventions/static/interventions/interventions_index.css +++ b/loko/interventions/static/interventions/interventions_index.css @@ -39,7 +39,7 @@ /* ─── Airbnb-style Filter Modal ────────────────────────────────────── */ #filterModal .modal-dialog { max-width: 880px; - margin: 1.5rem auto; + margin: 1.75rem auto; } #filterModal .modal-content { @@ -48,7 +48,8 @@ box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25); display: flex; flex-direction: column; - max-height: 88vh; + height: 84vh; + max-height: 84vh; overflow: hidden; background-color: #ffffff; } @@ -119,6 +120,8 @@ #filterModal .modal-body { padding: 1.25rem 1.5rem; overflow-y: auto; + flex: 1 1 auto; + min-height: 0; background-color: #f8fafc; } @@ -772,6 +775,29 @@ user-select: none; } +.interventions-table th.col-actions { + text-align: center !important; + width: 52px; + min-width: 52px; + padding: 0.55rem 0.25rem !important; +} + +#th-select-all-checkbox, +#select-all-checkbox { + width: 16px; + height: 16px; + cursor: pointer; + border-color: #cbd5e1; +} + +#th-select-all-checkbox:checked, +#select-all-checkbox:checked, +#th-select-all-checkbox:indeterminate, +#select-all-checkbox:indeterminate { + background-color: #2563eb; + border-color: #2563eb; +} + .interventions-table thead th.sortable { cursor: pointer; transition: background-color 0.12s ease; diff --git a/loko/interventions/static/interventions/interventions_index.js b/loko/interventions/static/interventions/interventions_index.js index e60d231..70affe4 100644 --- a/loko/interventions/static/interventions/interventions_index.js +++ b/loko/interventions/static/interventions/interventions_index.js @@ -869,10 +869,11 @@ function renderTable() { applyColumnVisibility(); detectTruncatedTitles(); persistInterventionsListContext(); + updateInputAndStatus(); } function renderRowHtml(row) { - const isSelected = selected_interventions_set.has(row.id); + const isSelected = selected_interventions_set.has(parseInt(row.id, 10)); const codeLabel = escapeHtml(row.code || `INT#${row.id}`); const detailUrl = `${interventionsurl}${row.id}/`; @@ -1449,31 +1450,64 @@ function toggleSelectedIntervention(interventionId, $button) { function updateInputAndStatus() { const statusBar = document.getElementById("selection-status"); - const ids = Array.from(selected_interventions_set); - const count = ids.length; - const msg = ngettext( - "%(count)s intervention sélectionnée", - "%(count)s interventions sélectionnées", - count - ); - statusBar.innerText = interpolate(msg, { count }, true); + const count = selected_interventions_set.size; + if (statusBar) { + if (count > 0) { + const msg = ngettext( + "%(count)s sélectionnée", + "%(count)s sélectionnées", + count + ); + statusBar.innerText = `(${interpolate(msg, { count }, true)})`; + } else { + statusBar.innerText = "(0)"; + } + } + + const rows = filteredInterventions && filteredInterventions.length ? filteredInterventions : allInterventions; + const totalRows = rows ? rows.length : 0; + + const cbToolbar = document.getElementById("select-all-checkbox"); + const cbTh = document.getElementById("th-select-all-checkbox"); + + [cbToolbar, cbTh].forEach(cb => { + if (!cb) return; + if (totalRows === 0 || count === 0) { + cb.checked = false; + cb.indeterminate = false; + } else if (count >= totalRows) { + cb.checked = true; + cb.indeterminate = false; + } else { + cb.checked = false; + cb.indeterminate = true; + } + }); +} + +function handleGlobalSelectAll(checked) { + const rows = filteredInterventions && filteredInterventions.length ? filteredInterventions : allInterventions; + if (checked) { + rows.forEach(row => { + selected_interventions_set.add(parseInt(row.id, 10)); + }); + } else { + selected_interventions_set.clear(); + } + renderTable(); + updateInputAndStatus(); } document.addEventListener("DOMContentLoaded", function () { + const cbToolbar = document.getElementById("select-all-checkbox"); + const cbTh = document.getElementById("th-select-all-checkbox"); - document.getElementById("select-all")?.addEventListener("click", () => { - const rows = filteredInterventions && filteredInterventions.length ? filteredInterventions : allInterventions; - rows.forEach(row => { - selected_interventions_set.add(parseInt(row.id)); - }); - renderTable(); - updateInputAndStatus(); + cbToolbar?.addEventListener("change", function () { + handleGlobalSelectAll(this.checked); }); - document.getElementById("deselect-all")?.addEventListener("click", () => { - selected_interventions_set.clear(); - renderTable(); - updateInputAndStatus(); + cbTh?.addEventListener("change", function () { + handleGlobalSelectAll(this.checked); }); // Initial state @@ -2485,12 +2519,13 @@ function togglePauseReasonFilter() { btn.addEventListener('click', function(e) { e.preventDefault(); const field = this.getAttribute('data-field'); - const container = document.querySelector(`.filter-options[data-field="${field}"]`); + if (!field) return; + const container = document.querySelector(`.filter-options[data-field="${field}"], .thematic-grid[data-field="${field}"]`); if (container) { const checkboxes = container.querySelectorAll('input[type="checkbox"]:not(:disabled)'); checkboxes.forEach(checkbox => { - // Only check visible checkboxes (for categories filtered by thematics) - const parent = checkbox.closest('.filter-option'); + // Only check visible checkboxes (for categories filtered by thematics, or thematic tiles) + const parent = checkbox.closest('.filter-option, .thematic-tile'); if (!parent || parent.style.display !== 'none') { checkbox.checked = true; } @@ -2504,15 +2539,10 @@ function togglePauseReasonFilter() { filterCategoriesByThematics(); filterProvidersAndContracts(); } - if (field === 'provider') { - filterProvidersAndContracts(); - } - if (field === 'contract') { - filterProvidersAndContracts(); - } - if (field === 'assigned_team') { + if (field === 'provider' || field === 'contract' || field === 'assigned_team') { filterProvidersAndContracts(); } + updateFilterTabBadges(); } }); }); @@ -2521,7 +2551,8 @@ function togglePauseReasonFilter() { btn.addEventListener('click', function(e) { e.preventDefault(); const field = this.getAttribute('data-field'); - const container = document.querySelector(`.filter-options[data-field="${field}"]`); + if (!field) return; + const container = document.querySelector(`.filter-options[data-field="${field}"], .thematic-grid[data-field="${field}"]`); if (container) { const checkboxes = container.querySelectorAll('input[type="checkbox"]:not(:disabled)'); checkboxes.forEach(checkbox => { @@ -2536,15 +2567,10 @@ function togglePauseReasonFilter() { filterCategoriesByThematics(); filterProvidersAndContracts(); } - if (field === 'provider') { - filterProvidersAndContracts(); - } - if (field === 'contract') { - filterProvidersAndContracts(); - } - if (field === 'assigned_team') { + if (field === 'provider' || field === 'contract' || field === 'assigned_team') { filterProvidersAndContracts(); } + updateFilterTabBadges(); } }); }); diff --git a/loko/interventions/templates/interventions/interventions_index.html b/loko/interventions/templates/interventions/interventions_index.html index 1693230..b6b065f 100644 --- a/loko/interventions/templates/interventions/interventions_index.html +++ b/loko/interventions/templates/interventions/interventions_index.html @@ -50,7 +50,7 @@