feat: implement dynamic location chips count and interactive date filter timeline preview
This commit is contained in:
parent
5c042531e5
commit
5e2fb023c9
2 changed files with 638 additions and 52 deletions
|
|
@ -336,6 +336,7 @@ document.getElementById('reset-filters').addEventListener('click', async functio
|
||||||
document.getElementById('begin_date_max').value = '';
|
document.getElementById('begin_date_max').value = '';
|
||||||
document.getElementById('end_date_min').value = '';
|
document.getElementById('end_date_min').value = '';
|
||||||
document.getElementById('end_date_max').value = '';
|
document.getElementById('end_date_max').value = '';
|
||||||
|
updateDateFilterTimelinePreview();
|
||||||
|
|
||||||
// Save empty preferences
|
// Save empty preferences
|
||||||
if (typeof savePreferencesUrl !== 'undefined') {
|
if (typeof savePreferencesUrl !== 'undefined') {
|
||||||
|
|
@ -1543,13 +1544,34 @@ function hasLocation(ct, obj) {
|
||||||
}
|
}
|
||||||
function renderLocationChips() {
|
function renderLocationChips() {
|
||||||
const wrap = document.getElementById('selected-locations-chips');
|
const wrap = document.getElementById('selected-locations-chips');
|
||||||
|
const countBadge = document.getElementById('selected-locations-badge');
|
||||||
|
const clearBtn = document.getElementById('clear-locations');
|
||||||
|
|
||||||
|
if (countBadge) {
|
||||||
|
if (selectedLocations.length > 0) {
|
||||||
|
countBadge.textContent = `${selectedLocations.length} ${selectedLocations.length > 1 ? gettext('sélectionnées') : gettext('sélectionnée')}`;
|
||||||
|
countBadge.classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
countBadge.classList.add('d-none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearBtn) {
|
||||||
|
if (selectedLocations.length > 0) {
|
||||||
|
clearBtn.classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
clearBtn.classList.add('d-none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!wrap) return;
|
||||||
wrap.innerHTML = '';
|
wrap.innerHTML = '';
|
||||||
|
|
||||||
// État vide → message léger
|
// État vide → message léger
|
||||||
if (!selectedLocations.length) {
|
if (!selectedLocations.length) {
|
||||||
const empty = document.createElement('div');
|
const empty = document.createElement('div');
|
||||||
empty.className = 'text-muted fst-italic';
|
empty.className = 'text-muted small fst-italic py-1';
|
||||||
empty.textContent = gettext('Aucune localisation sélectionnée');
|
empty.innerHTML = `<i class="bi bi-info-circle me-1"></i>${gettext('Aucune localisation sélectionnée (toutes les localisations sont incluses)')}`;
|
||||||
wrap.appendChild(empty);
|
wrap.appendChild(empty);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -1561,43 +1583,35 @@ function renderLocationChips() {
|
||||||
|
|
||||||
selectedLocations.forEach(item => {
|
selectedLocations.forEach(item => {
|
||||||
const li = document.createElement('li');
|
const li = document.createElement('li');
|
||||||
li.className = 'list-group-item d-flex justify-content-between align-items-center location-item';
|
li.className = 'list-group-item d-flex justify-content-between align-items-center location-item py-1 px-2 mb-1 border rounded shadow-sm';
|
||||||
|
|
||||||
// Bloc texte (label + sous-ligne)
|
// Bloc texte (icône + label)
|
||||||
const info = document.createElement('div');
|
const info = document.createElement('div');
|
||||||
info.className = 'd-flex flex-column flex-grow-1 me-3';
|
info.className = 'd-flex align-items-center flex-grow-1 me-2 text-truncate';
|
||||||
const title = document.createElement('span');
|
info.innerHTML = `<i class="bi bi-geo-alt text-primary me-2 flex-shrink-0"></i><span class="fw-medium text-truncate small" title="${item.label || ''}">${item.label || ''}</span>`;
|
||||||
title.className = 'fw-medium text-truncate';
|
|
||||||
title.title = item.label || '';
|
|
||||||
title.textContent = item.label || '';
|
|
||||||
info.append(title);
|
|
||||||
|
|
||||||
// Badge thématique (à droite du label)
|
// Badge thématique (à droite du label)
|
||||||
const badge = document.createElement('span');
|
const badge = document.createElement('span');
|
||||||
badge.className = 'badge text-bg-secondary me-2 align-self-center';
|
badge.className = 'badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-25 me-2 align-self-center';
|
||||||
|
badge.style.fontSize = '0.7rem';
|
||||||
badge.textContent = item.type_label || '';
|
badge.textContent = item.type_label || '';
|
||||||
|
|
||||||
// Bouton supprimer
|
// Bouton supprimer
|
||||||
const btn = document.createElement('button');
|
const btn = document.createElement('button');
|
||||||
btn.type = 'button';
|
btn.type = 'button';
|
||||||
btn.className = 'btn btn-sm btn-outline-secondary';
|
btn.className = 'btn btn-xs btn-outline-danger p-0 px-1 border-0';
|
||||||
|
btn.style.lineHeight = '1';
|
||||||
btn.setAttribute('aria-label', gettext('Retirer cette localisation'));
|
btn.setAttribute('aria-label', gettext('Retirer cette localisation'));
|
||||||
btn.setAttribute('data-action', 'remove-location');
|
btn.setAttribute('data-action', 'remove-location');
|
||||||
btn.setAttribute('data-ct', item.ct);
|
btn.setAttribute('data-ct', item.ct);
|
||||||
btn.setAttribute('data-obj', item.obj);
|
btn.setAttribute('data-obj', item.obj);
|
||||||
btn.innerHTML = '<i class="bi bi-x"></i>';
|
btn.innerHTML = '<i class="bi bi-x-circle-fill fs-6"></i>';
|
||||||
|
|
||||||
li.append(info, badge, btn);
|
li.append(info, badge, btn);
|
||||||
ul.appendChild(li);
|
ul.appendChild(li);
|
||||||
});
|
});
|
||||||
|
|
||||||
wrap.appendChild(ul);
|
wrap.appendChild(ul);
|
||||||
|
|
||||||
// Compteur (optionnel)
|
|
||||||
// const count = document.createElement('div');
|
|
||||||
// count.className = 'form-text mt-1';
|
|
||||||
// count.textContent = `${selectedLocations.length} localisation(s) sélectionnée(s)`;
|
|
||||||
// wrap.appendChild(count);
|
|
||||||
}
|
}
|
||||||
document.getElementById('selected-locations-chips')?.addEventListener('click', (e) => {
|
document.getElementById('selected-locations-chips')?.addEventListener('click', (e) => {
|
||||||
const btn = e.target.closest('[data-action="remove-location"]');
|
const btn = e.target.closest('[data-action="remove-location"]');
|
||||||
|
|
@ -2519,9 +2533,10 @@ async function savePreferencesFromForm(urlParams) {
|
||||||
|
|
||||||
// ==================== Initialization ====================
|
// ==================== Initialization ====================
|
||||||
|
|
||||||
// Initialize filter badge and sort on page load
|
// Initialize filter badge, sort, and date timeline preview on page load
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
updateFilterBadge();
|
updateFilterBadge();
|
||||||
|
initDateFilterPresets();
|
||||||
|
|
||||||
// Save preferences when table sort changes (user action only)
|
// Save preferences when table sort changes (user action only)
|
||||||
const $table = $('#interventions-table');
|
const $table = $('#interventions-table');
|
||||||
|
|
@ -2659,3 +2674,318 @@ function updateInterventionManagers() {
|
||||||
row.classList.add('d-none');
|
row.classList.add('d-none');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ==================== Date Filter Timeline & Interval Preview ====================
|
||||||
|
|
||||||
|
function _formatDateFr(dateStr) {
|
||||||
|
if (!dateStr) return '';
|
||||||
|
const parts = dateStr.split('-');
|
||||||
|
if (parts.length === 3) {
|
||||||
|
return `${parts[2]}/${parts[1]}/${parts[0]}`;
|
||||||
|
}
|
||||||
|
return dateStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
function _formatIsoDate(d) {
|
||||||
|
const yyyy = d.getFullYear();
|
||||||
|
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
||||||
|
const dd = String(d.getDate()).padStart(2, '0');
|
||||||
|
return `${yyyy}-${mm}-${dd}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateDateFilterTimelinePreview() {
|
||||||
|
const beginMinInput = document.getElementById('begin_date_min');
|
||||||
|
const beginMaxInput = document.getElementById('begin_date_max');
|
||||||
|
const endMinInput = document.getElementById('end_date_min');
|
||||||
|
const endMaxInput = document.getElementById('end_date_max');
|
||||||
|
|
||||||
|
if (!beginMinInput || !beginMaxInput || !endMinInput || !endMaxInput) return;
|
||||||
|
|
||||||
|
const bMin = beginMinInput.value;
|
||||||
|
const bMax = beginMaxInput.value;
|
||||||
|
const eMin = endMinInput.value;
|
||||||
|
const eMax = endMaxInput.value;
|
||||||
|
|
||||||
|
const beginBar = document.getElementById('timeline-bar-begin');
|
||||||
|
const endBar = document.getElementById('timeline-bar-end');
|
||||||
|
const beginText = document.getElementById('timeline-begin-text');
|
||||||
|
const endText = document.getElementById('timeline-end-text');
|
||||||
|
const summaryBox = document.getElementById('timeline-summary-box');
|
||||||
|
const summaryText = document.getElementById('timeline-summary-text');
|
||||||
|
const summaryIcon = document.getElementById('timeline-summary-icon');
|
||||||
|
const axisMinEl = document.getElementById('timeline-axis-min');
|
||||||
|
const axisMaxEl = document.getElementById('timeline-axis-max');
|
||||||
|
const rangeBadge = document.getElementById('timeline-range-badge');
|
||||||
|
|
||||||
|
if (!beginBar || !endBar || !beginText || !endText || !summaryText) return;
|
||||||
|
|
||||||
|
const hasBegin = !!(bMin || bMax);
|
||||||
|
const hasEnd = !!(eMin || eMax);
|
||||||
|
|
||||||
|
// Check consistency
|
||||||
|
const bInvalid = bMin && bMax && new Date(bMin) > new Date(bMax);
|
||||||
|
const eInvalid = eMin && eMax && new Date(eMin) > new Date(eMax);
|
||||||
|
|
||||||
|
if (bInvalid || eInvalid) {
|
||||||
|
if (summaryBox) {
|
||||||
|
summaryBox.className = 'timeline-summary-box p-2 rounded border small mb-2 has-warning';
|
||||||
|
}
|
||||||
|
if (summaryIcon) {
|
||||||
|
summaryIcon.className = 'bi bi-exclamation-triangle-fill text-danger mt-1 flex-shrink-0';
|
||||||
|
}
|
||||||
|
summaryText.innerHTML = `<span class="text-danger fw-semibold">${gettext("Attention : la date minimale (Du) ne peut pas être postérieure à la date maximale (Au).")}</span>`;
|
||||||
|
if (rangeBadge) {
|
||||||
|
rangeBadge.className = 'badge bg-danger text-white border small';
|
||||||
|
rangeBadge.textContent = gettext("Incohérence");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (summaryIcon) {
|
||||||
|
summaryIcon.className = 'bi bi-info-circle text-primary mt-1 flex-shrink-0';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine timeline scale
|
||||||
|
const dates = [];
|
||||||
|
if (bMin) dates.push(new Date(bMin));
|
||||||
|
if (bMax) dates.push(new Date(bMax));
|
||||||
|
if (eMin) dates.push(new Date(eMin));
|
||||||
|
if (eMax) dates.push(new Date(eMax));
|
||||||
|
|
||||||
|
const today = new Date();
|
||||||
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
let axisMinDate, axisMaxDate;
|
||||||
|
|
||||||
|
if (dates.length === 0) {
|
||||||
|
// Default scale: current quarter (from 1 month ago to 2 months ahead)
|
||||||
|
axisMinDate = new Date(today.getFullYear(), today.getMonth() - 1, 1);
|
||||||
|
axisMaxDate = new Date(today.getFullYear(), today.getMonth() + 2, 0);
|
||||||
|
} else {
|
||||||
|
const minTimestamp = Math.min(...dates.map(d => d.getTime()));
|
||||||
|
const maxTimestamp = Math.max(...dates.map(d => d.getTime()));
|
||||||
|
|
||||||
|
const dayMs = 24 * 60 * 60 * 1000;
|
||||||
|
const spanDays = Math.max(14, (maxTimestamp - minTimestamp) / dayMs);
|
||||||
|
const padDays = Math.max(5, Math.round(spanDays * 0.15));
|
||||||
|
|
||||||
|
axisMinDate = new Date(minTimestamp - padDays * dayMs);
|
||||||
|
axisMaxDate = new Date(maxTimestamp + padDays * dayMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const axisMinTime = axisMinDate.getTime();
|
||||||
|
const axisMaxTime = axisMaxDate.getTime();
|
||||||
|
const totalDuration = Math.max(1, axisMaxTime - axisMinTime);
|
||||||
|
|
||||||
|
if (axisMinEl) {
|
||||||
|
const dStr = dates.length === 0 ? gettext("Passé") : axisMinDate.toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: '2-digit' });
|
||||||
|
axisMinEl.textContent = dStr;
|
||||||
|
}
|
||||||
|
if (axisMaxEl) {
|
||||||
|
const dStr = dates.length === 0 ? gettext("Futur") : axisMaxDate.toLocaleDateString(undefined, { day: '2-digit', month: '2-digit', year: '2-digit' });
|
||||||
|
axisMaxEl.textContent = dStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to calculate percentage position [0..100]
|
||||||
|
function toPercent(dateStr) {
|
||||||
|
if (!dateStr) return null;
|
||||||
|
const t = new Date(dateStr).getTime();
|
||||||
|
const pct = ((t - axisMinTime) / totalDuration) * 100;
|
||||||
|
return Math.max(0, Math.min(100, pct));
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Begin track ---
|
||||||
|
if (!hasBegin) {
|
||||||
|
beginBar.className = 'timeline-bar timeline-bar-begin is-all';
|
||||||
|
beginBar.style.left = '0%';
|
||||||
|
beginBar.style.width = '100%';
|
||||||
|
beginText.textContent = gettext("Toutes dates");
|
||||||
|
beginText.className = 'badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-25';
|
||||||
|
} else {
|
||||||
|
beginBar.className = 'timeline-bar timeline-bar-begin';
|
||||||
|
beginText.className = 'badge bg-success bg-opacity-10 text-success border border-success border-opacity-25';
|
||||||
|
|
||||||
|
if (bMin && bMax) {
|
||||||
|
const pMin = toPercent(bMin);
|
||||||
|
const pMax = toPercent(bMax);
|
||||||
|
const w = Math.max(2, pMax - pMin);
|
||||||
|
beginBar.style.left = `${pMin}%`;
|
||||||
|
beginBar.style.width = `${w}%`;
|
||||||
|
beginText.textContent = `${gettext("Du")} ${_formatDateFr(bMin)} ${gettext("au")} ${_formatDateFr(bMax)}`;
|
||||||
|
} else if (bMin) {
|
||||||
|
const pMin = toPercent(bMin);
|
||||||
|
beginBar.classList.add('is-open-right');
|
||||||
|
beginBar.style.left = `${pMin}%`;
|
||||||
|
beginBar.style.width = `${100 - pMin}%`;
|
||||||
|
beginText.textContent = `${gettext("À partir du")} ${_formatDateFr(bMin)}`;
|
||||||
|
} else if (bMax) {
|
||||||
|
const pMax = toPercent(bMax);
|
||||||
|
beginBar.classList.add('is-open-left');
|
||||||
|
beginBar.style.left = '0%';
|
||||||
|
beginBar.style.width = `${pMax}%`;
|
||||||
|
beginText.textContent = `${gettext("Jusqu'au")} ${_formatDateFr(bMax)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- End track ---
|
||||||
|
if (!hasEnd) {
|
||||||
|
endBar.className = 'timeline-bar timeline-bar-end is-all';
|
||||||
|
endBar.style.left = '0%';
|
||||||
|
endBar.style.width = '100%';
|
||||||
|
endText.textContent = gettext("Toutes dates");
|
||||||
|
endText.className = 'badge bg-secondary bg-opacity-10 text-secondary border border-secondary border-opacity-25';
|
||||||
|
} else {
|
||||||
|
endBar.className = 'timeline-bar timeline-bar-end';
|
||||||
|
endText.className = 'badge bg-primary bg-opacity-10 text-primary border border-primary border-opacity-25';
|
||||||
|
|
||||||
|
if (eMin && eMax) {
|
||||||
|
const pMin = toPercent(eMin);
|
||||||
|
const pMax = toPercent(eMax);
|
||||||
|
const w = Math.max(2, pMax - pMin);
|
||||||
|
endBar.style.left = `${pMin}%`;
|
||||||
|
endBar.style.width = `${w}%`;
|
||||||
|
endText.textContent = `${gettext("Du")} ${_formatDateFr(eMin)} ${gettext("au")} ${_formatDateFr(eMax)}`;
|
||||||
|
} else if (eMin) {
|
||||||
|
const pMin = toPercent(eMin);
|
||||||
|
endBar.classList.add('is-open-right');
|
||||||
|
endBar.style.left = `${pMin}%`;
|
||||||
|
endBar.style.width = `${100 - pMin}%`;
|
||||||
|
endText.textContent = `${gettext("À partir du")} ${_formatDateFr(eMin)}`;
|
||||||
|
} else if (eMax) {
|
||||||
|
const pMax = toPercent(eMax);
|
||||||
|
endBar.classList.add('is-open-left');
|
||||||
|
endBar.style.left = '0%';
|
||||||
|
endBar.style.width = `${pMax}%`;
|
||||||
|
endText.textContent = `${gettext("Jusqu'au")} ${_formatDateFr(eMax)}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Summary & Badges ---
|
||||||
|
if (summaryBox) {
|
||||||
|
if (hasBegin || hasEnd) {
|
||||||
|
summaryBox.className = 'timeline-summary-box p-2 rounded border small mb-2 has-filter';
|
||||||
|
} else {
|
||||||
|
summaryBox.className = 'timeline-summary-box p-2 rounded border small mb-2';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rangeBadge) {
|
||||||
|
if (hasBegin && hasEnd) {
|
||||||
|
rangeBadge.className = 'badge bg-primary text-white border small';
|
||||||
|
rangeBadge.textContent = gettext("Début & Fin filtrés");
|
||||||
|
} else if (hasBegin) {
|
||||||
|
rangeBadge.className = 'badge bg-success text-white border small';
|
||||||
|
rangeBadge.textContent = gettext("Début filtré");
|
||||||
|
} else if (hasEnd) {
|
||||||
|
rangeBadge.className = 'badge bg-primary text-white border small';
|
||||||
|
rangeBadge.textContent = gettext("Fin filtrée");
|
||||||
|
} else {
|
||||||
|
rangeBadge.className = 'badge bg-white text-secondary border small';
|
||||||
|
rangeBadge.textContent = gettext("Aperçu interactif");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Natural Language Summary formulation
|
||||||
|
if (!hasBegin && !hasEnd) {
|
||||||
|
summaryText.innerHTML = gettext("Aucune restriction de date : toutes les interventions sont incluses.");
|
||||||
|
} else if (hasBegin && hasEnd) {
|
||||||
|
const beginDesc = bMin && bMax
|
||||||
|
? `${gettext("entre le")} <strong>${_formatDateFr(bMin)}</strong> ${gettext("et le")} <strong>${_formatDateFr(bMax)}</strong>`
|
||||||
|
: (bMin ? `${gettext("à partir du")} <strong>${_formatDateFr(bMin)}</strong>` : `${gettext("jusqu'au")} <strong>${_formatDateFr(bMax)}</strong>`);
|
||||||
|
|
||||||
|
const endDesc = eMin && eMax
|
||||||
|
? `${gettext("entre le")} <strong>${_formatDateFr(eMin)}</strong> ${gettext("et le")} <strong>${_formatDateFr(eMax)}</strong>`
|
||||||
|
: (eMin ? `${gettext("à partir du")} <strong>${_formatDateFr(eMin)}</strong>` : `${gettext("jusqu'au")} <strong>${_formatDateFr(eMax)}</strong>`);
|
||||||
|
|
||||||
|
summaryText.innerHTML = `${gettext("Sélectionne les interventions dont le")} <strong class="text-success">${gettext("début")}</strong> ${gettext("est")} ${beginDesc}, ${gettext("et dont la")} <strong class="text-primary">${gettext("fin (statut Terminé)")}</strong> ${gettext("est")} ${endDesc}.`;
|
||||||
|
} else if (hasBegin) {
|
||||||
|
const beginDesc = bMin && bMax
|
||||||
|
? `${gettext("entre le")} <strong>${_formatDateFr(bMin)}</strong> ${gettext("et le")} <strong>${_formatDateFr(bMax)}</strong>`
|
||||||
|
: (bMin ? `${gettext("à partir du")} <strong>${_formatDateFr(bMin)}</strong>` : `${gettext("jusqu'au")} <strong>${_formatDateFr(bMax)}</strong>`);
|
||||||
|
|
||||||
|
summaryText.innerHTML = `${gettext("Sélectionne les interventions dont le")} <strong class="text-success">${gettext("début")}</strong> ${gettext("est")} ${beginDesc} <em>(${gettext("aucune restriction sur la date de fin")})</em>.`;
|
||||||
|
} else if (hasEnd) {
|
||||||
|
const endDesc = eMin && eMax
|
||||||
|
? `${gettext("entre le")} <strong>${_formatDateFr(eMin)}</strong> ${gettext("et le")} <strong>${_formatDateFr(eMax)}</strong>`
|
||||||
|
: (eMin ? `${gettext("à partir du")} <strong>${_formatDateFr(eMin)}</strong>` : `${gettext("jusqu'au")} <strong>${_formatDateFr(eMax)}</strong>`);
|
||||||
|
|
||||||
|
summaryText.innerHTML = `${gettext("Sélectionne les interventions dont la")} <strong class="text-primary">${gettext("fin (passage à Terminé)")}</strong> ${gettext("est")} ${endDesc} <em>(${gettext("aucune restriction sur la date de début")})</em>.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initDateFilterPresets() {
|
||||||
|
document.querySelectorAll('.date-preset-btn').forEach(btn => {
|
||||||
|
btn.addEventListener('click', function(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const preset = this.getAttribute('data-preset');
|
||||||
|
const target = this.getAttribute('data-target');
|
||||||
|
const today = new Date();
|
||||||
|
|
||||||
|
let minVal = '', maxVal = '';
|
||||||
|
|
||||||
|
if (preset === 'clear') {
|
||||||
|
document.getElementById('begin_date_min').value = '';
|
||||||
|
document.getElementById('begin_date_max').value = '';
|
||||||
|
document.getElementById('end_date_min').value = '';
|
||||||
|
document.getElementById('end_date_max').value = '';
|
||||||
|
updateDateFilterTimelinePreview();
|
||||||
|
return;
|
||||||
|
} else if (preset === 'today') {
|
||||||
|
const s = _formatIsoDate(today);
|
||||||
|
minVal = s;
|
||||||
|
maxVal = s;
|
||||||
|
} else if (preset === 'this_month') {
|
||||||
|
const firstDay = new Date(today.getFullYear(), today.getMonth(), 1);
|
||||||
|
const lastDay = new Date(today.getFullYear(), today.getMonth() + 1, 0);
|
||||||
|
minVal = _formatIsoDate(firstDay);
|
||||||
|
maxVal = _formatIsoDate(lastDay);
|
||||||
|
} else if (preset === 'last_month') {
|
||||||
|
const firstDay = new Date(today.getFullYear(), today.getMonth() - 1, 1);
|
||||||
|
const lastDay = new Date(today.getFullYear(), today.getMonth(), 0);
|
||||||
|
minVal = _formatIsoDate(firstDay);
|
||||||
|
maxVal = _formatIsoDate(lastDay);
|
||||||
|
} else if (preset === 'this_year') {
|
||||||
|
const firstDay = new Date(today.getFullYear(), 0, 1);
|
||||||
|
const lastDay = new Date(today.getFullYear(), 11, 31);
|
||||||
|
minVal = _formatIsoDate(firstDay);
|
||||||
|
maxVal = _formatIsoDate(lastDay);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target === 'begin') {
|
||||||
|
document.getElementById('begin_date_min').value = minVal;
|
||||||
|
document.getElementById('begin_date_max').value = maxVal;
|
||||||
|
} else if (target === 'both') {
|
||||||
|
document.getElementById('begin_date_min').value = minVal;
|
||||||
|
document.getElementById('begin_date_max').value = '';
|
||||||
|
document.getElementById('end_date_min').value = '';
|
||||||
|
document.getElementById('end_date_max').value = maxVal;
|
||||||
|
} else {
|
||||||
|
// Apply presets on end dates (completion period)
|
||||||
|
document.getElementById('begin_date_min').value = '';
|
||||||
|
document.getElementById('begin_date_max').value = '';
|
||||||
|
document.getElementById('end_date_min').value = minVal;
|
||||||
|
document.getElementById('end_date_max').value = maxVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDateFilterTimelinePreview();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
['begin_date_min', 'begin_date_max', 'end_date_min', 'end_date_max'].forEach(id => {
|
||||||
|
const el = document.getElementById(id);
|
||||||
|
if (el) {
|
||||||
|
el.addEventListener('input', updateDateFilterTimelinePreview);
|
||||||
|
el.addEventListener('change', updateDateFilterTimelinePreview);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const filterModal = document.getElementById('filterModal');
|
||||||
|
if (filterModal) {
|
||||||
|
filterModal.addEventListener('shown.bs.modal', function() {
|
||||||
|
updateDateFilterTimelinePreview();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateDateFilterTimelinePreview();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,20 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.location-item {
|
.location-item {
|
||||||
background-color: #e1f6ff;
|
background-color: #f8fafc;
|
||||||
|
border-color: #e2e8f0;
|
||||||
|
border-left: 3px solid #0d6efd !important;
|
||||||
|
transition: all 0.15s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.location-item:hover {
|
||||||
|
background-color: #f1f5f9;
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#selected-locations-chips {
|
||||||
|
max-height: 150px;
|
||||||
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Styles pour la modale de filtre */
|
/* Styles pour la modale de filtre */
|
||||||
|
|
@ -405,6 +418,87 @@
|
||||||
border-color: #6c757d !important;
|
border-color: #6c757d !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Timeline preview in filter modal */
|
||||||
|
.date-timeline-card {
|
||||||
|
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-axis-line {
|
||||||
|
height: 3px;
|
||||||
|
background: linear-gradient(90deg, #cbd5e1 0%, #94a3b8 50%, #cbd5e1 100%);
|
||||||
|
border-radius: 2px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-bar-wrapper {
|
||||||
|
position: relative;
|
||||||
|
height: 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #e2e8f0;
|
||||||
|
border: 1px solid #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-track-bg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-bar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: left 0.3s cubic-bezier(0.4, 0, 0.2, 1), width 0.3s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-bar-begin {
|
||||||
|
background: linear-gradient(90deg, #10b981, #059669);
|
||||||
|
box-shadow: 0 0 8px rgba(16, 185, 129, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-bar-end {
|
||||||
|
background: linear-gradient(90deg, #3b82f6, #1d4ed8);
|
||||||
|
box-shadow: 0 0 8px rgba(59, 130, 246, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-bar.is-all {
|
||||||
|
left: 0% !important;
|
||||||
|
width: 100% !important;
|
||||||
|
opacity: 0.3;
|
||||||
|
background: #94a3b8 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-bar.is-open-left {
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-bar.is-open-right {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-summary-box {
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-summary-box.has-filter {
|
||||||
|
background-color: #f0fdf4 !important;
|
||||||
|
border-color: #86efac !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-summary-box.has-warning {
|
||||||
|
background-color: #fef2f2 !important;
|
||||||
|
border-color: #fca5a5 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock head %}
|
{% endblock head %}
|
||||||
|
|
||||||
|
|
@ -732,39 +826,201 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Localisations (full width) -->
|
<!-- Localisations (full width) -->
|
||||||
<div class="mt-3 mb-3">
|
<div class="filter-section mt-3 pt-3 border-top">
|
||||||
<label class="form-label">{% translate "Localisations" %}</label>
|
<div class="filter-section-header">
|
||||||
<div id="location-autocomplete-container" class="position-relative">
|
<label class="d-flex align-items-center">
|
||||||
<input id="location-autocomplete" type="text" class="form-control"
|
<i class="bi bi-geo-alt-fill me-2 text-danger"></i>{% translate "Localisations" %}
|
||||||
placeholder="{% translate 'Chercher une localisation (min. 2 caractères)…' %}">
|
<span class="badge bg-danger bg-opacity-10 text-danger border border-danger border-opacity-25 small ms-2 d-none" id="selected-locations-badge"></span>
|
||||||
|
</label>
|
||||||
|
<div class="filter-section-buttons">
|
||||||
|
<button id="clear-locations" type="button" class="btn btn-sm btn-outline-secondary d-none">
|
||||||
|
<i class="bi bi-x-circle me-1"></i>{% translate "Effacer localisations" %}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="selected-locations-chips" class="mt-2 d-flex flex-wrap gap-2"></div>
|
<div id="location-autocomplete-container" class="position-relative mb-2">
|
||||||
<button id="clear-locations" type="button" class="btn btn-sm btn-outline-secondary mt-2">
|
<div class="input-group input-group-sm">
|
||||||
{% translate "Effacer les localisations" %}
|
<span class="input-group-text bg-light text-muted"><i class="bi bi-search"></i></span>
|
||||||
</button>
|
<input id="location-autocomplete" type="text" class="form-control"
|
||||||
|
placeholder="{% translate 'Chercher une rue, un carrefour, un équipement (min. 2 caractères)…' %}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="selected-locations-chips" class="mt-2"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Dates -->
|
<!-- Dates -->
|
||||||
<div class="row mt-3">
|
<div class="row mt-3 pt-3 border-top">
|
||||||
<div class="col-6">
|
<div class="col-lg-6">
|
||||||
<div>
|
<div class="filter-section-header">
|
||||||
<label class="form-label">{% translate "Date de début" %}</label>
|
<label class="d-flex align-items-center">
|
||||||
<div class="d-flex align-items-center gap-2">
|
<i class="bi bi-calendar3 me-2 text-primary"></i>{% translate "Filtres par date" %}
|
||||||
<input type="date" name="begin_date_min" id="begin_date_min" class="form-control" placeholder="Min" value="{{ begin_date_min }}">
|
</label>
|
||||||
<span class="mx-1">→</span>
|
<div class="filter-section-buttons">
|
||||||
<input type="date" name="begin_date_max" id="begin_date_max" class="form-control" placeholder="Max" value="{{ begin_date_max }}">
|
<button type="button" class="btn btn-sm btn-outline-secondary date-preset-btn" data-preset="clear">
|
||||||
</div>
|
<i class="bi bi-x-circle me-1"></i>{% translate "Effacer dates" %}
|
||||||
</div>
|
</button>
|
||||||
<div class="mt-3">
|
|
||||||
<label class="form-label">{% translate "Date de fin" %}</label>
|
|
||||||
<div class="d-flex align-items-center gap-2">
|
|
||||||
<input type="date" name="end_date_min" id="end_date_min" class="form-control" placeholder="Min" value="{{ end_date_min }}">
|
|
||||||
<span class="mx-1">→</span>
|
|
||||||
<input type="date" name="end_date_max" id="end_date_max" class="form-control" placeholder="Max" value="{{ end_date_max }}">
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-check mt-3">
|
<!-- Date de début -->
|
||||||
|
<div class="date-filter-group mb-3 p-2 rounded bg-light bg-opacity-50 border">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
|
<label class="form-label mb-0 fw-semibold text-success small">
|
||||||
|
<i class="bi bi-play-circle-fill me-1"></i>{% translate "Date de début" %}
|
||||||
|
</label>
|
||||||
|
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25" style="font-size: 0.7rem;">
|
||||||
|
{% translate "Démarrage / Planification" %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="row g-2 align-items-center">
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text py-0 px-2 text-muted small">{% translate "Du" %}</span>
|
||||||
|
<input type="date" name="begin_date_min" id="begin_date_min" class="form-control form-control-sm" placeholder="Min" value="{{ begin_date_min }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-2 text-center text-muted">
|
||||||
|
<i class="bi bi-arrow-right"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text py-0 px-2 text-muted small">{% translate "Au" %}</span>
|
||||||
|
<input type="date" name="begin_date_max" id="begin_date_max" class="form-control form-control-sm" placeholder="Max" value="{{ begin_date_max }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Date de fin -->
|
||||||
|
<div class="date-filter-group mb-3 p-2 rounded bg-light bg-opacity-50 border">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
||||||
|
<label class="form-label mb-0 fw-semibold text-primary small">
|
||||||
|
<i class="bi bi-check-circle-fill me-1"></i>{% translate "Date de fin" %}
|
||||||
|
</label>
|
||||||
|
<span class="badge bg-primary bg-opacity-10 text-primary border border-primary border-opacity-25" style="font-size: 0.7rem;">
|
||||||
|
{% translate "Fin travaux (statut Terminé)" %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="row g-2 align-items-center">
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text py-0 px-2 text-muted small">{% translate "Du" %}</span>
|
||||||
|
<input type="date" name="end_date_min" id="end_date_min" class="form-control form-control-sm" placeholder="Min" value="{{ end_date_min }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-2 text-center text-muted">
|
||||||
|
<i class="bi bi-arrow-right"></i>
|
||||||
|
</div>
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="input-group input-group-sm">
|
||||||
|
<span class="input-group-text py-0 px-2 text-muted small">{% translate "Au" %}</span>
|
||||||
|
<input type="date" name="end_date_max" id="end_date_max" class="form-control form-control-sm" placeholder="Max" value="{{ end_date_max }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Raccourcis rapides / Presets -->
|
||||||
|
<div class="d-flex flex-wrap align-items-center gap-1 mb-2">
|
||||||
|
<span class="text-muted small me-1" style="font-size: 0.75rem;"><i class="bi bi-lightning-charge me-1"></i>{% translate "Périodes rapides (fin)" %} :</span>
|
||||||
|
<button type="button" class="btn btn-xs btn-outline-secondary date-preset-btn py-0 px-2" style="font-size: 0.72rem;" data-preset="today">{% translate "Aujourd'hui" %}</button>
|
||||||
|
<button type="button" class="btn btn-xs btn-outline-secondary date-preset-btn py-0 px-2" style="font-size: 0.72rem;" data-preset="this_month">{% translate "Ce mois" %}</button>
|
||||||
|
<button type="button" class="btn btn-xs btn-outline-secondary date-preset-btn py-0 px-2" style="font-size: 0.72rem;" data-preset="last_month">{% translate "Mois dernier" %}</button>
|
||||||
|
<button type="button" class="btn btn-xs btn-outline-secondary date-preset-btn py-0 px-2" style="font-size: 0.72rem;" data-preset="this_year">{% translate "Cette année" %}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Panneau interactif de ligne du temps & explication d'intervalle -->
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<div class="date-timeline-card h-100 p-3 rounded-3 border bg-light shadow-sm d-flex flex-column justify-content-between">
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||||
|
<div class="fw-semibold text-dark small d-flex align-items-center">
|
||||||
|
<i class="bi bi-clock-history text-primary me-2"></i>{% translate "Ligne du temps & Intervalles" %}
|
||||||
|
</div>
|
||||||
|
<span class="badge bg-white text-secondary border small" id="timeline-range-badge">{% translate "Aperçu interactif" %}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Timeline Visual Track -->
|
||||||
|
<div class="timeline-visual-container position-relative my-3 p-3 bg-white rounded border shadow-sm">
|
||||||
|
<!-- Time Axis Line -->
|
||||||
|
<div class="timeline-axis position-relative mb-3">
|
||||||
|
<div class="timeline-axis-line"></div>
|
||||||
|
<div class="d-flex justify-content-between text-muted text-uppercase" style="font-size: 0.68rem; margin-top: 3px;">
|
||||||
|
<span id="timeline-axis-min">{% translate "Passé" %}</span>
|
||||||
|
<span class="badge bg-light text-secondary border px-1" id="timeline-today-marker"><i class="bi bi-calendar-event me-1"></i>{% translate "Aujourd'hui" %}</span>
|
||||||
|
<span id="timeline-axis-max">{% translate "Futur" %}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Start Interval Track (Green) -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-1" style="font-size: 0.75rem;">
|
||||||
|
<span class="text-success fw-semibold">
|
||||||
|
<i class="bi bi-play-circle-fill me-1"></i>{% translate "Intervalle Date de début" %}
|
||||||
|
</span>
|
||||||
|
<span class="badge bg-success bg-opacity-10 text-success border border-success border-opacity-25" id="timeline-begin-text">
|
||||||
|
{% translate "Toutes dates" %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-bar-wrapper">
|
||||||
|
<div class="timeline-track-bg"></div>
|
||||||
|
<div class="timeline-bar timeline-bar-begin is-all" id="timeline-bar-begin"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- End Interval Track (Blue) -->
|
||||||
|
<div>
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-1" style="font-size: 0.75rem;">
|
||||||
|
<span class="text-primary fw-semibold">
|
||||||
|
<i class="bi bi-check-circle-fill me-1"></i>{% translate "Intervalle Date de fin (Terminé)" %}
|
||||||
|
</span>
|
||||||
|
<span class="badge bg-primary bg-opacity-10 text-primary border border-primary border-opacity-25" id="timeline-end-text">
|
||||||
|
{% translate "Toutes dates" %}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-bar-wrapper">
|
||||||
|
<div class="timeline-track-bg"></div>
|
||||||
|
<div class="timeline-bar timeline-bar-end is-all" id="timeline-bar-end"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Natural Language Summary Box -->
|
||||||
|
<div class="timeline-summary-box p-2 rounded border bg-white small mb-2" id="timeline-summary-box">
|
||||||
|
<div class="d-flex align-items-start gap-2">
|
||||||
|
<i class="bi bi-info-circle text-primary mt-1 flex-shrink-0" id="timeline-summary-icon"></i>
|
||||||
|
<div id="timeline-summary-text" class="text-muted" style="font-size: 0.8rem; line-height: 1.35;">
|
||||||
|
{% translate "Aucune restriction de date : toutes les interventions sont incluses." %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Pedagogy diagram / Lifecycle explainer -->
|
||||||
|
<div class="p-2 rounded bg-white bg-opacity-75 border border-dashed mt-2" style="font-size: 0.72rem;">
|
||||||
|
<div class="text-muted fw-semibold mb-1">
|
||||||
|
<i class="bi bi-lightbulb text-warning me-1"></i>{% translate "Comment fonctionnent ces deux intervalles ?" %}
|
||||||
|
</div>
|
||||||
|
<div class="text-muted" style="line-height: 1.3;">
|
||||||
|
<div class="d-flex align-items-center gap-1 mb-1">
|
||||||
|
<span class="badge bg-success p-1 rounded-circle"></span>
|
||||||
|
<span><strong>{% translate "Date de début" %} [Min → Max]</strong> : {% translate "sélectionne les interventions dont le démarrage a lieu dans cette période." %}</span>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex align-items-center gap-1">
|
||||||
|
<span class="badge bg-primary p-1 rounded-circle"></span>
|
||||||
|
<span><strong>{% translate "Date de fin" %} [Min → Max]</strong> : {% translate "sélectionne les interventions achevées (statut Terminé) dans cette période." %}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Options supplémentaires (En retard / Suivies) -->
|
||||||
|
<div class="row mt-3 pt-3 border-top">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
@ -774,10 +1030,12 @@
|
||||||
{% if overdue == '1' %}checked{% endif %}
|
{% if overdue == '1' %}checked{% endif %}
|
||||||
>
|
>
|
||||||
<label class="form-check-label" for="overdue">
|
<label class="form-check-label" for="overdue">
|
||||||
{% translate "Interventions non traitées (en retard)" %}
|
<i class="bi bi-clock-history text-danger me-1"></i>{% translate "Interventions non traitées (en retard)" %}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check mt-2">
|
</div>
|
||||||
|
<div class="col-md-6">
|
||||||
|
<div class="form-check">
|
||||||
<input
|
<input
|
||||||
class="form-check-input"
|
class="form-check-input"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
|
|
@ -787,12 +1045,10 @@
|
||||||
{% if followed_only == '1' %}checked{% endif %}
|
{% if followed_only == '1' %}checked{% endif %}
|
||||||
>
|
>
|
||||||
<label class="form-check-label" for="followed_only">
|
<label class="form-check-label" for="followed_only">
|
||||||
{% translate "Interventions suivies uniquement" %}
|
<i class="bi bi-star-fill text-warning me-1"></i>{% translate "Interventions suivies uniquement" %}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-6"></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if to_be_planned_soon %}
|
{% if to_be_planned_soon %}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue