diff --git a/loko/assets/locale/en/LC_MESSAGES/django.mo b/loko/assets/locale/en/LC_MESSAGES/django.mo index b1308bd..21486c6 100644 Binary files a/loko/assets/locale/en/LC_MESSAGES/django.mo and b/loko/assets/locale/en/LC_MESSAGES/django.mo differ diff --git a/loko/assets/locale/en/LC_MESSAGES/django.po b/loko/assets/locale/en/LC_MESSAGES/django.po index 372bfaf..b088fca 100644 --- a/loko/assets/locale/en/LC_MESSAGES/django.po +++ b/loko/assets/locale/en/LC_MESSAGES/django.po @@ -8764,3 +8764,30 @@ msgstr "Management zone documents" msgid "Si coché, cet équipement est exclu de la zone même si sa géométrie intersecte le périmètre." msgstr "If checked, this asset is excluded from the zone even if its geometry intersects the perimeter." + +msgid "Filtres favoris" +msgstr "Favorite filters" + +msgid "Favoris" +msgstr "Favorites" + +msgid "Mes filtres favoris" +msgstr "My favorite filters" + +msgid "Enregistrer le filtre actuel..." +msgstr "Save current filter..." + +msgid "Enregistrer comme favori" +msgstr "Save as favorite" + +msgid "Nom du filtre favori" +msgstr "Favorite filter name" + +msgid "Ex. Mobilier urbain et voirie" +msgstr "Ex. Street furniture and roads" + +msgid "Donnez un nom clair pour retrouver facilement cette sélection de filtres." +msgstr "Give a clear name to easily find this selection of filters." + +msgid "Critères enregistrés :" +msgstr "Saved criteria:" diff --git a/loko/assets/locale/en/LC_MESSAGES/djangojs.mo b/loko/assets/locale/en/LC_MESSAGES/djangojs.mo index 1b58bb0..70be09a 100644 Binary files a/loko/assets/locale/en/LC_MESSAGES/djangojs.mo and b/loko/assets/locale/en/LC_MESSAGES/djangojs.mo differ diff --git a/loko/assets/locale/en/LC_MESSAGES/djangojs.po b/loko/assets/locale/en/LC_MESSAGES/djangojs.po index 26058a0..e7111d8 100644 --- a/loko/assets/locale/en/LC_MESSAGES/djangojs.po +++ b/loko/assets/locale/en/LC_MESSAGES/djangojs.po @@ -744,3 +744,84 @@ msgstr "Instructions & points of attention" msgid "Centrer sur la zone" msgstr "Center on zone" + +msgid "Favoris" +msgstr "Favorites" + +msgid "Aucun favori enregistré" +msgstr "No favorite saved" + +msgid "Mettre à jour avec les filtres actuels" +msgstr "Update with current filters" + +msgid "Supprimer" +msgstr "Delete" + +msgid "Filtre favori enregistré avec succès." +msgstr "Favorite filter saved successfully." + +msgid "Erreur lors de la sauvegarde du favori." +msgstr "Error while saving favorite." + +msgid "Erreur de communication avec le serveur." +msgstr "Server communication error." + +msgid "Voulez-vous vraiment supprimer le favori '%s' ?" +msgstr "Are you sure you want to delete favorite '%s'?" + +msgid "Filtre favori supprimé." +msgstr "Favorite filter deleted." + +msgid "Erreur lors de la suppression du favori." +msgstr "Error while deleting favorite." + +msgid "Mettre à jour le favori '%s' avec les filtres actuellement actifs ?" +msgstr "Update favorite '%s' with currently active filters?" + +msgid "Favori '%s' mis à jour." +msgstr "Favorite '%s' updated." + +msgid "Erreur lors de la mise à jour du favori." +msgstr "Error while updating favorite." + +msgid "Filtre '%s' appliqué." +msgstr "Filter '%s' applied." + +msgid "Aucun filtre sélectionné." +msgstr "No filter selected." + +msgid "Thématique(s) :" +msgstr "Thematic(s):" + +msgid "Type d'élément :" +msgstr "Element type:" + +msgid "Assets" +msgstr "Assets" + +msgid "Localisations" +msgstr "Locations" + +msgid "Catégorie(s) :" +msgstr "Category(ies):" + +msgid "Modèle(s) :" +msgstr "Model(s):" + +msgid "Statut(s) :" +msgstr "Status(es):" + +msgid "Validation" +msgstr "Validation" + +msgid "Sans géométrie :" +msgstr "Without geometry:" + +msgid "Exclus" +msgstr "Excluded" + +msgid "Filtres par défaut." +msgstr "Default filters." + +msgid "sélectionnée(s)" +msgstr "selected" diff --git a/loko/assets/locale/fr/LC_MESSAGES/django.mo b/loko/assets/locale/fr/LC_MESSAGES/django.mo index ab40c87..4406dde 100644 Binary files a/loko/assets/locale/fr/LC_MESSAGES/django.mo and b/loko/assets/locale/fr/LC_MESSAGES/django.mo differ diff --git a/loko/assets/locale/fr/LC_MESSAGES/django.po b/loko/assets/locale/fr/LC_MESSAGES/django.po index 4ee7d6d..1f1c883 100644 --- a/loko/assets/locale/fr/LC_MESSAGES/django.po +++ b/loko/assets/locale/fr/LC_MESSAGES/django.po @@ -8817,3 +8817,30 @@ msgstr "Documents de zones de gestion" msgid "Si coché, cet équipement est exclu de la zone même si sa géométrie intersecte le périmètre." msgstr "Si coché, cet équipement est exclu de la zone même si sa géométrie intersecte le périmètre." + +msgid "Filtres favoris" +msgstr "Filtres favoris" + +msgid "Favoris" +msgstr "Favoris" + +msgid "Mes filtres favoris" +msgstr "Mes filtres favoris" + +msgid "Enregistrer le filtre actuel..." +msgstr "Enregistrer le filtre actuel..." + +msgid "Enregistrer comme favori" +msgstr "Enregistrer comme favori" + +msgid "Nom du filtre favori" +msgstr "Nom du filtre favori" + +msgid "Ex. Mobilier urbain et voirie" +msgstr "Ex. Mobilier urbain et voirie" + +msgid "Donnez un nom clair pour retrouver facilement cette sélection de filtres." +msgstr "Donnez un nom clair pour retrouver facilement cette sélection de filtres." + +msgid "Critères enregistrés :" +msgstr "Critères enregistrés :" diff --git a/loko/assets/locale/fr/LC_MESSAGES/djangojs.mo b/loko/assets/locale/fr/LC_MESSAGES/djangojs.mo index abe73c3..e94a313 100644 Binary files a/loko/assets/locale/fr/LC_MESSAGES/djangojs.mo and b/loko/assets/locale/fr/LC_MESSAGES/djangojs.mo differ diff --git a/loko/assets/locale/fr/LC_MESSAGES/djangojs.po b/loko/assets/locale/fr/LC_MESSAGES/djangojs.po index 79e39f8..cb107e9 100644 --- a/loko/assets/locale/fr/LC_MESSAGES/djangojs.po +++ b/loko/assets/locale/fr/LC_MESSAGES/djangojs.po @@ -749,3 +749,84 @@ msgstr "Consignes & points d'attention" msgid "Centrer sur la zone" msgstr "Centrer sur la zone" + +msgid "Favoris" +msgstr "Favoris" + +msgid "Aucun favori enregistré" +msgstr "Aucun favori enregistré" + +msgid "Mettre à jour avec les filtres actuels" +msgstr "Mettre à jour avec les filtres actuels" + +msgid "Supprimer" +msgstr "Supprimer" + +msgid "Filtre favori enregistré avec succès." +msgstr "Filtre favori enregistré avec succès." + +msgid "Erreur lors de la sauvegarde du favori." +msgstr "Erreur lors de la sauvegarde du favori." + +msgid "Erreur de communication avec le serveur." +msgstr "Erreur de communication avec le serveur." + +msgid "Voulez-vous vraiment supprimer le favori '%s' ?" +msgstr "Voulez-vous vraiment supprimer le favori '%s' ?" + +msgid "Filtre favori supprimé." +msgstr "Filtre favori supprimé." + +msgid "Erreur lors de la suppression du favori." +msgstr "Erreur lors de la suppression du favori." + +msgid "Mettre à jour le favori '%s' avec les filtres actuellement actifs ?" +msgstr "Mettre à jour le favori '%s' avec les filtres actuellement actifs ?" + +msgid "Favori '%s' mis à jour." +msgstr "Favori '%s' mis à jour." + +msgid "Erreur lors de la mise à jour du favori." +msgstr "Erreur lors de la mise à jour du favori." + +msgid "Filtre '%s' appliqué." +msgstr "Filtre '%s' appliqué." + +msgid "Aucun filtre sélectionné." +msgstr "Aucun filtre sélectionné." + +msgid "Thématique(s) :" +msgstr "Thématique(s) :" + +msgid "Type d'élément :" +msgstr "Type d'élément :" + +msgid "Assets" +msgstr "Assets" + +msgid "Localisations" +msgstr "Localisations" + +msgid "Catégorie(s) :" +msgstr "Catégorie(s) :" + +msgid "Modèle(s) :" +msgstr "Modèle(s) :" + +msgid "Statut(s) :" +msgstr "Statut(s) :" + +msgid "Validation" +msgstr "Validation" + +msgid "Sans géométrie :" +msgstr "Sans géométrie :" + +msgid "Exclus" +msgstr "Exclus" + +msgid "Filtres par défaut." +msgstr "Filtres par défaut." + +msgid "sélectionnée(s)" +msgstr "sélectionnée(s)" diff --git a/loko/assets/locale/nl/LC_MESSAGES/django.mo b/loko/assets/locale/nl/LC_MESSAGES/django.mo index 2d6a183..e6267d1 100644 Binary files a/loko/assets/locale/nl/LC_MESSAGES/django.mo and b/loko/assets/locale/nl/LC_MESSAGES/django.mo differ diff --git a/loko/assets/locale/nl/LC_MESSAGES/django.po b/loko/assets/locale/nl/LC_MESSAGES/django.po index 118829c..c5fcbdb 100644 --- a/loko/assets/locale/nl/LC_MESSAGES/django.po +++ b/loko/assets/locale/nl/LC_MESSAGES/django.po @@ -8765,3 +8765,30 @@ msgstr "Beheerszonedocumenten" msgid "Si coché, cet équipement est exclu de la zone même si sa géométrie intersecte le périmètre." msgstr "Indien aangevinkt, wordt dit asset uitgesloten van de zone, zelfs als de geometrie de perimeter doorsnijdt." + +msgid "Filtres favoris" +msgstr "Favoriete filters" + +msgid "Favoris" +msgstr "Favorieten" + +msgid "Mes filtres favoris" +msgstr "Mijn favoriete filters" + +msgid "Enregistrer le filtre actuel..." +msgstr "Huidige filter opslaan..." + +msgid "Enregistrer comme favori" +msgstr "Opslaan als favoriet" + +msgid "Nom du filtre favori" +msgstr "Naam van favoriet filter" + +msgid "Ex. Mobilier urbain et voirie" +msgstr "Bijv. Straatmeubilair en wegen" + +msgid "Donnez un nom clair pour retrouver facilement cette sélection de filtres." +msgstr "Geef een duidelijke naam om deze filterselectie gemakkelijk terug te vinden." + +msgid "Critères enregistrés :" +msgstr "Opgeslagen criteria:" diff --git a/loko/assets/locale/nl/LC_MESSAGES/djangojs.mo b/loko/assets/locale/nl/LC_MESSAGES/djangojs.mo index ed4ecd7..4fba208 100644 Binary files a/loko/assets/locale/nl/LC_MESSAGES/djangojs.mo and b/loko/assets/locale/nl/LC_MESSAGES/djangojs.mo differ diff --git a/loko/assets/locale/nl/LC_MESSAGES/djangojs.po b/loko/assets/locale/nl/LC_MESSAGES/djangojs.po index 82c08a3..a1bec78 100644 --- a/loko/assets/locale/nl/LC_MESSAGES/djangojs.po +++ b/loko/assets/locale/nl/LC_MESSAGES/djangojs.po @@ -746,3 +746,84 @@ msgstr "Instructies & aandachtspunten" msgid "Centrer sur la zone" msgstr "Centreren op zone" + +msgid "Favoris" +msgstr "Favorieten" + +msgid "Aucun favori enregistré" +msgstr "Geen favoriet opgeslagen" + +msgid "Mettre à jour avec les filtres actuels" +msgstr "Bijwerken met huidige filters" + +msgid "Supprimer" +msgstr "Verwijderen" + +msgid "Filtre favori enregistré avec succès." +msgstr "Favoriet filter succesvol opgeslagen." + +msgid "Erreur lors de la sauvegarde du favori." +msgstr "Fout bij het opslaan van favoriet." + +msgid "Erreur de communication avec le serveur." +msgstr "Communicatiefout met de server." + +msgid "Voulez-vous vraiment supprimer le favori '%s' ?" +msgstr "Weet u zeker dat u favoriet '%s' wilt verwijderen?" + +msgid "Filtre favori supprimé." +msgstr "Favoriet filter verwijderd." + +msgid "Erreur lors de la suppression du favori." +msgstr "Fout bij het verwijderen van favoriet." + +msgid "Mettre à jour le favori '%s' avec les filtres actuellement actifs ?" +msgstr "Favoriet '%s' bijwerken met momenteel actieve filters?" + +msgid "Favori '%s' mis à jour." +msgstr "Favoriet '%s' bijgewerkt." + +msgid "Erreur lors de la mise à jour du favori." +msgstr "Fout bij het bijwerken van favoriet." + +msgid "Filtre '%s' appliqué." +msgstr "Filter '%s' toegepast." + +msgid "Aucun filtre sélectionné." +msgstr "Geen filter geselecteerd." + +msgid "Thématique(s) :" +msgstr "Thema('s):" + +msgid "Type d'élément :" +msgstr "Elementtype:" + +msgid "Assets" +msgstr "Assets" + +msgid "Localisations" +msgstr "Locaties" + +msgid "Catégorie(s) :" +msgstr "Categorie(ën):" + +msgid "Modèle(s) :" +msgstr "Model(len):" + +msgid "Statut(s) :" +msgstr "Status(sen):" + +msgid "Validation" +msgstr "Validatie" + +msgid "Sans géométrie :" +msgstr "Zonder geometrie:" + +msgid "Exclus" +msgstr "Uitgesloten" + +msgid "Filtres par défaut." +msgstr "Standaardfilters." + +msgid "sélectionnée(s)" +msgstr "geselecteerd" diff --git a/loko/assets/static/assets/unified_index.js b/loko/assets/static/assets/unified_index.js index 55c7c25..cf42478 100644 --- a/loko/assets/static/assets/unified_index.js +++ b/loko/assets/static/assets/unified_index.js @@ -1346,6 +1346,7 @@ $(document).ready(function () { refreshData(); updateFilterBadge(); schedulePreferencesSave(); + checkActiveFavoriteMatch(); const modal = bootstrap.Modal.getInstance(document.getElementById('filterModal')); if (modal) modal.hide(); }); @@ -1373,6 +1374,7 @@ $(document).ready(function () { refreshData(); updateFilterBadge(); schedulePreferencesSave(); + checkActiveFavoriteMatch(); const modal = bootstrap.Modal.getInstance(document.getElementById('filterModal')); if (modal) modal.hide(); }); @@ -1438,6 +1440,7 @@ $(document).ready(function () { renderModelFilters(); renderConditionalFilters(); updateFilterBadge(); + initFavoriteFilters(); if (activeThematics.size > 0) { refreshData(); @@ -4615,3 +4618,457 @@ function initTotalsOverlay() { } document.addEventListener('DOMContentLoaded', initTotalsOverlay); + +// ==================== Favorite Filters Management ==================== + +function showToast(message, type = 'success') { + const toastEl = document.getElementById('app-toast'); + const toastBody = document.getElementById('app-toast-body'); + if (!toastEl || !toastBody) return; + + toastEl.className = 'toast align-items-center text-white border-0'; + toastEl.classList.add(`bg-${type}`); + toastBody.textContent = message; + + const toast = new bootstrap.Toast(toastEl); + toast.show(); +} + +function initFavoriteFilters() { + renderFavoriteFiltersUI(); + checkActiveFavoriteMatch(); + + // Open save modal button + const btnOpenSave = document.getElementById('btn-open-save-favorite-modal'); + if (btnOpenSave) { + btnOpenSave.addEventListener('click', function(e) { + e.preventDefault(); + openSaveFavoriteModal(); + }); + } + + // Confirm save button + const btnConfirmSave = document.getElementById('btn-confirm-save-favorite'); + if (btnConfirmSave) { + btnConfirmSave.addEventListener('click', function() { + confirmSaveFavorite(); + }); + } + + // Submit on Enter in save favorite modal + const saveNameInput = document.getElementById('save-favorite-name'); + if (saveNameInput) { + saveNameInput.addEventListener('keydown', function(e) { + if (e.key === 'Enter') { + e.preventDefault(); + confirmSaveFavorite(); + } + }); + } +} + +function renderFavoriteFiltersUI() { + const countBadge = document.getElementById('favorite-filters-count'); + const itemsContainer = document.getElementById('favorite-filters-items'); + + if (countBadge) { + countBadge.textContent = (typeof userFavoriteFilters !== 'undefined' && Array.isArray(userFavoriteFilters)) ? userFavoriteFilters.length : 0; + } + + if (itemsContainer) { + if (!userFavoriteFilters || !userFavoriteFilters.length) { + itemsContainer.innerHTML = ` +
  • + ${gettext('Aucun favori enregistré')} +
  • + `; + } else { + let html = ''; + userFavoriteFilters.forEach(fav => { + html += ` + + `; + }); + itemsContainer.innerHTML = html; + } + } +} + +function getCurrentFilterValues() { + const statusFilters = Array.from( + document.querySelectorAll('[name="filter-status"]:checked') + ).map(cb => cb.value).sort(); + const validationStatusFilters = Array.from( + document.querySelectorAll('[name="filter-validation-status"]:checked') + ).map(cb => cb.value).sort(); + const includeNoGeomCheckbox = document.getElementById('include-no-geom'); + const includeNoGeom = includeNoGeomCheckbox ? includeNoGeomCheckbox.checked : true; + + return { + thematics: Array.from(activeThematics || []).sort(), + type_filter: getActiveTypeFilters().sort(), + include_no_geom: includeNoGeom, + status_filters: statusFilters, + validation_status_filters: validationStatusFilters, + categories: Array.from(activeCategories || []).sort(), + models: Array.from(activeModels || []).sort(), + conditional_filters: activeConditionalFilters || {} + }; +} + +function areFilterObjectsEqual(f1, f2) { + if (!f1 || !f2) return false; + const arrayKeys = ['thematics', 'type_filter', 'status_filters', 'validation_status_filters', 'categories', 'models']; + for (const key of arrayKeys) { + const arr1 = Array.isArray(f1[key]) ? f1[key].map(String).sort() : []; + const arr2 = Array.isArray(f2[key]) ? f2[key].map(String).sort() : []; + if (arr1.length !== arr2.length) return false; + for (let i = 0; i < arr1.length; i++) { + if (arr1[i] !== arr2[i]) return false; + } + } + const noGeom1 = f1.include_no_geom !== false; + const noGeom2 = f2.include_no_geom !== false; + if (noGeom1 !== noGeom2) return false; + + const cf1 = f1.conditional_filters || {}; + const cf2 = f2.conditional_filters || {}; + if (JSON.stringify(cf1) !== JSON.stringify(cf2)) return false; + + return true; +} + +function checkActiveFavoriteMatch() { + const currentFilters = getCurrentFilterValues(); + let matchedFav = null; + + if (typeof userFavoriteFilters !== 'undefined' && Array.isArray(userFavoriteFilters)) { + for (const fav of userFavoriteFilters) { + if (areFilterObjectsEqual(fav.filters, currentFilters)) { + matchedFav = fav; + break; + } + } + } + + const activeLabel = document.getElementById('active-favorite-label'); + + // Reset all star icons and active classes + document.querySelectorAll('.dropdown-item-favorite').forEach(el => el.classList.remove('active')); + document.querySelectorAll('.favorite-star-icon').forEach(icon => { + icon.className = 'bi bi-star favorite-star-icon text-muted'; + }); + + if (matchedFav) { + if (activeLabel) { + activeLabel.textContent = matchedFav.name; + } + const activeItem = document.getElementById(`fav-item-${matchedFav.id}`); + if (activeItem) { + activeItem.classList.add('active'); + } + const activeStar = document.getElementById(`fav-star-${matchedFav.id}`); + if (activeStar) { + activeStar.className = 'bi bi-star-fill favorite-star-icon text-warning'; + } + } else { + if (activeLabel) { + activeLabel.textContent = gettext('Favoris'); + } + } +} + +function generateFilterSummary(filters) { + if (!filters || Object.keys(filters).length === 0) { + return `${gettext('Aucun filtre sélectionné.')}`; + } + const badges = []; + + if (filters.thematics && filters.thematics.length) { + const names = filters.thematics.map(code => { + return (THEMATIC_CONFIG && THEMATIC_CONFIG[code] && THEMATIC_CONFIG[code].name) || code; + }); + badges.push(`${gettext('Thématique(s)')} : ${names.join(', ')}`); + } + + if (filters.type_filter && filters.type_filter.length) { + const typeLabels = { + 'assets': gettext('Assets'), + 'locations': gettext('Localisations'), + 'management_zones': gettext('Zones de gestion') + }; + const names = filters.type_filter.map(t => typeLabels[t] || t); + badges.push(`${gettext("Type d'élément")} : ${names.join(', ')}`); + } + + if (filters.categories && filters.categories.length) { + badges.push(`${gettext('Catégorie(s)')} : ${filters.categories.length} ${gettext('sélectionnée(s)')}`); + } + + if (filters.models && filters.models.length) { + badges.push(`${gettext('Modèle(s)')} : ${filters.models.length} ${gettext('sélectionné(s)')}`); + } + + if (filters.status_filters && filters.status_filters.length) { + badges.push(`${gettext('Statut(s)')} : ${filters.status_filters.join(', ')}`); + } + + if (filters.validation_status_filters && filters.validation_status_filters.length) { + badges.push(`${gettext('Validation')} : ${filters.validation_status_filters.join(', ')}`); + } + + if (filters.include_no_geom === false) { + badges.push(`${gettext('Sans géométrie')} : ${gettext('Exclus')}`); + } + + if (badges.length === 0) { + return `${gettext('Filtres par défaut.')}`; + } + + return badges.map(b => `
    ${b}
    `).join(''); +} + +function openSaveFavoriteModal(existingId = null) { + const currentFilters = getCurrentFilterValues(); + const modalEl = document.getElementById('saveFavoriteModal'); + if (!modalEl) return; + + const idInput = document.getElementById('save-favorite-id'); + const nameInput = document.getElementById('save-favorite-name'); + const summaryEl = document.getElementById('save-favorite-summary'); + + if (existingId) { + const existing = userFavoriteFilters.find(f => f.id === existingId); + if (existing) { + idInput.value = existing.id; + nameInput.value = existing.name; + } + } else { + idInput.value = ''; + nameInput.value = ''; + } + + if (summaryEl) { + summaryEl.innerHTML = generateFilterSummary(currentFilters); + } + + nameInput.classList.remove('is-invalid'); + const modal = bootstrap.Modal.getOrCreateInstance(modalEl); + modal.show(); + + setTimeout(() => { + nameInput.focus(); + }, 400); +} + +async function confirmSaveFavorite() { + const nameInput = document.getElementById('save-favorite-name'); + const idInput = document.getElementById('save-favorite-id'); + const name = nameInput.value.trim(); + if (!name) { + nameInput.classList.add('is-invalid'); + return; + } + nameInput.classList.remove('is-invalid'); + + const favoriteId = idInput.value || null; + const filters = getCurrentFilterValues(); + + try { + const response = await fetch(saveFavoriteUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCsrfToken() + }, + body: JSON.stringify({ + id: favoriteId, + name: name, + filters: filters + }) + }); + + const data = await response.json(); + if (data.success) { + userFavoriteFilters = data.favorites; + renderFavoriteFiltersUI(); + checkActiveFavoriteMatch(); + + const modalEl = document.getElementById('saveFavoriteModal'); + const modal = bootstrap.Modal.getInstance(modalEl); + if (modal) modal.hide(); + + showToast(gettext('Filtre favori enregistré avec succès.'), 'success'); + } else { + alert(data.error || gettext('Erreur lors de la sauvegarde du favori.')); + } + } catch (err) { + alert(gettext('Erreur de communication avec le serveur.')); + } +} + +async function deleteFavoriteFilter(favoriteId, event) { + if (event) event.stopPropagation(); + const fav = userFavoriteFilters.find(f => f.id === favoriteId); + if (!fav) return; + + const confirmMsg = gettext("Voulez-vous vraiment supprimer le favori '%s' ?").replace('%s', fav.name); + if (!confirm(confirmMsg)) return; + + try { + const response = await fetch(deleteFavoriteUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCsrfToken() + }, + body: JSON.stringify({ id: favoriteId }) + }); + + const data = await response.json(); + if (data.success) { + userFavoriteFilters = data.favorites; + renderFavoriteFiltersUI(); + checkActiveFavoriteMatch(); + showToast(gettext('Filtre favori supprimé.'), 'info'); + } else { + alert(data.error || gettext('Erreur lors de la suppression du favori.')); + } + } catch (err) { + alert(gettext('Erreur de communication avec le serveur.')); + } +} + +async function updateFavoriteWithCurrentFilters(favoriteId, event) { + if (event) event.stopPropagation(); + const fav = userFavoriteFilters.find(f => f.id === favoriteId); + if (!fav) return; + + const confirmMsg = gettext("Mettre à jour le favori '%s' avec les filtres actuellement actifs ?").replace('%s', fav.name); + if (!confirm(confirmMsg)) return; + + const currentFilters = getCurrentFilterValues(); + + try { + const response = await fetch(saveFavoriteUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': getCsrfToken() + }, + body: JSON.stringify({ + id: favoriteId, + name: fav.name, + filters: currentFilters + }) + }); + + const data = await response.json(); + if (data.success) { + userFavoriteFilters = data.favorites; + renderFavoriteFiltersUI(); + checkActiveFavoriteMatch(); + showToast(gettext("Favori '%s' mis à jour.").replace('%s', fav.name), 'success'); + } else { + alert(data.error || gettext('Erreur lors de la mise à jour du favori.')); + } + } catch (err) { + alert(gettext('Erreur de communication avec le serveur.')); + } +} + +function applyFavoriteFilter(favoriteId) { + if (!favoriteId) return; + const fav = userFavoriteFilters.find(f => f.id === favoriteId); + if (!fav) return; + + const filters = fav.filters || {}; + + // 1. Thematics + activeThematics.clear(); + const favThematics = Array.isArray(filters.thematics) ? filters.thematics : []; + favThematics.forEach(t => { + if (t in THEMATIC_CONFIG) activeThematics.add(t); + }); + document.querySelectorAll('.thematic-checkbox').forEach(cb => { + cb.checked = activeThematics.has(cb.value); + }); + + // 2. Types + const cbA = document.getElementById('type-cb-assets'); + const cbL = document.getElementById('type-cb-locations'); + const cbZ = document.getElementById('type-cb-management-zones'); + const favTypes = Array.isArray(filters.type_filter) ? filters.type_filter : ['assets', 'locations', 'management_zones']; + if (cbA) cbA.checked = favTypes.includes('assets'); + if (cbL) cbL.checked = favTypes.includes('locations'); + if (cbZ) cbZ.checked = favTypes.includes('management_zones'); + + // 3. Include no geom + const includeNoGeomCheckbox = document.getElementById('include-no-geom'); + if (includeNoGeomCheckbox) { + includeNoGeomCheckbox.checked = filters.include_no_geom !== false; + } + + // 4. Status + const favStatus = Array.isArray(filters.status_filters) ? filters.status_filters : []; + document.querySelectorAll('[name="filter-status"]').forEach(cb => { + cb.checked = favStatus.includes(cb.value); + }); + + // 5. Validation status + const favValidation = Array.isArray(filters.validation_status_filters) ? filters.validation_status_filters : []; + document.querySelectorAll('[name="filter-validation-status"]').forEach(cb => { + cb.checked = favValidation.includes(cb.value); + }); + + // 6. Categories + activeCategories = new Set(Array.isArray(filters.categories) ? filters.categories : []); + + // 7. Models + activeModels = new Set(Array.isArray(filters.models) ? filters.models : []); + + // 8. Conditional filters + activeConditionalFilters = (filters.conditional_filters && typeof filters.conditional_filters === 'object') + ? JSON.parse(JSON.stringify(filters.conditional_filters)) + : {}; + + // 9. Re-render filters UI + renderCategoryFilters(); + renderModelFilters(); + renderConditionalFilters(); + updateFilterBadge(); + + // 10. Save current active preferences + savePreferences(); + + // 11. Reload data + if (activeThematics.size > 0) { + refreshData(); + } else { + showEmptyState(); + } + + // 12. Update active favorite display + checkActiveFavoriteMatch(); + + showToast(gettext("Filtre '%s' appliqué.").replace('%s', fav.name), 'info'); +} + +// Make functions globally available for inline onclicks +window.applyFavoriteFilter = applyFavoriteFilter; +window.deleteFavoriteFilter = deleteFavoriteFilter; +window.updateFavoriteWithCurrentFilters = updateFavoriteWithCurrentFilters; +window.openSaveFavoriteModal = openSaveFavoriteModal; diff --git a/loko/assets/templates/assets/unified_index.html b/loko/assets/templates/assets/unified_index.html index 1547545..3909810 100644 --- a/loko/assets/templates/assets/unified_index.html +++ b/loko/assets/templates/assets/unified_index.html @@ -398,12 +398,36 @@ height: 1.25rem; padding: 0 4px !important; line-height: 1.25rem; + z-index: 5 !important; } #filter-badge:not(.d-none) { display: inline-flex !important; align-items: center; justify-content: center; } + + .dropdown-item-favorite { + transition: background-color 0.15s ease; + border-radius: 4px; + margin: 2px 4px; + } + .dropdown-item-favorite:hover { + background-color: #f1f5f9; + } + .dropdown-item-favorite.active { + background-color: #e7f1ff; + } + .dropdown-item-favorite.active .fav-name { + color: #0c5cc9; + font-weight: 600 !important; + } + .favorite-actions button { + opacity: 0.6; + transition: opacity 0.15s; + } + .dropdown-item-favorite:hover .favorite-actions button { + opacity: 1; + } #map .maplibregl-popup-close-button { font-size: 1.35rem; line-height: 1; @@ -949,6 +973,42 @@ +{# ─── Modal pour enregistrer un filtre favori ─────────────────────────────── #} + + {# ─── Add chooser modal ────────────────────────────────────────────────────── #}