fix(interventions): polish filter modal UX and add global select-all checkbox
- Remove active filter bar above toolbar - Fix 'All' / 'None' buttons for thematic tiles in filter modal - Stabilize modal height and top alignment across all 3 tabs - Replace toolbar select buttons with synchronized global checkboxes
This commit is contained in:
parent
ad1a339dbe
commit
8da1542ebb
3 changed files with 103 additions and 52 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@
|
|||
|
||||
<!-- ─── Modale Filtre des interventions (3 Onglets) ─────────────── -->
|
||||
<div class="modal fade" id="filterModal" tabindex="-1" aria-labelledby="filterModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable">
|
||||
<div class="modal-dialog modal-dialog-scrollable">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<div class="w-100">
|
||||
|
|
@ -106,7 +106,7 @@
|
|||
<button type="button" class="btn deselect-all-btn" data-field="thematic">{% translate "Aucun" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="thematic-grid" data-field="thematic">
|
||||
<div class="thematic-grid filter-options" data-field="thematic">
|
||||
{% for key, value, icon in thematic_choices %}
|
||||
<div class="thematic-tile">
|
||||
<input type="checkbox" name="thematic" id="thematic_{{ key }}" value="{{ key }}" {% if key in selected_thematics %}checked{% endif %}>
|
||||
|
|
@ -768,11 +768,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Puces des filtres actifs -->
|
||||
<div class="active-filters-bar d-none" id="active-filters-bar">
|
||||
<!-- Injecté dynamiquement par JS -->
|
||||
</div>
|
||||
|
||||
<!-- Barre d'outils du tableau -->
|
||||
<div class="interventions-toolbar d-flex justify-content-between align-items-center flex-wrap gap-2 mb-2">
|
||||
<div class="d-flex align-items-center flex-wrap gap-2">
|
||||
|
|
@ -866,10 +861,12 @@
|
|||
|
||||
<!-- Outils de sélection et sélecteur de page -->
|
||||
<div class="d-flex align-items-center flex-wrap gap-2">
|
||||
<div class="d-flex gap-1 align-items-center">
|
||||
<button type="button" class="btn btn-xs btn-outline-secondary py-1 px-2" style="font-size: 0.76rem;" id="select-all">{% translate "Tout sélectionner" %}</button>
|
||||
<button type="button" class="btn btn-xs btn-outline-secondary py-1 px-2" style="font-size: 0.76rem;" id="deselect-all">{% translate "Tout désélectionner" %}</button>
|
||||
<span id="selection-status" class="text-muted small ms-1" style="font-size: 0.78rem;">0 intervention sélectionnée</span>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<div class="form-check d-flex align-items-center mb-0 ps-0">
|
||||
<input type="checkbox" class="form-check-input me-1 mt-0" id="select-all-checkbox" style="cursor: pointer;">
|
||||
<label for="select-all-checkbox" class="form-check-label small text-secondary cursor-pointer user-select-none" style="font-size: 0.8rem;">{% translate "Tout sélectionner" %}</label>
|
||||
</div>
|
||||
<span id="selection-status" class="text-muted small" style="font-size: 0.78rem;">(0)</span>
|
||||
</div>
|
||||
|
||||
<span id="filter-limit-alert" class="alert alert-info py-1 px-2 mb-0 d-none small">
|
||||
|
|
@ -902,7 +899,9 @@
|
|||
<table id="interventions-table" class="interventions-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-actions" style="width: 52px; text-align: center;"></th>
|
||||
<th class="col-actions" style="width: 52px; text-align: center;">
|
||||
<input type="checkbox" class="form-check-input mt-0" id="th-select-all-checkbox" style="cursor: pointer;" title="{% translate 'Tout sélectionner' %}">
|
||||
</th>
|
||||
<th class="sortable col-code" data-field="code">
|
||||
<div class="sort-header">
|
||||
<span>{% translate "Code" %}</span>
|
||||
|
|
|
|||
Loading…
Reference in a new issue