feat: fait vivre les filtres d'analyse dans l'URL
Les filtres des pages Dépenses et Recettes n'existaient que dans l'état du composant : recharger la page les perdait, et une vue filtrée ne pouvait ni se partager, ni se mettre en signet, ni servir de destination à un lien. Chaque page déclare desormais comment ses filtres se lisent et s'écrivent dans la query (utils/filtresUrl.js). Ce qui vaut sa valeur par défaut n'est pas écrit : l'URL ne porte que ce qui a été choisi, et reste lisible. Ce qui en arrive est validé — un identifiant qui n'est pas un entier ou une date au mauvais format est ignoré plutôt qu'affiché de travers. Le changement de filtre remplace l'entrée d'historique au lieu d'en empiler une : le bouton Retour du navigateur quitte la page, il ne défait pas les filtres un par un. Côté Dépenses, la requête API dérive de cette même écriture : les paramètres sont les mêmes des deux côtés, la vue et la requête ne peuvent plus diverger. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<FilterPanel @filter-change="onFiltersChange" />
|
||||
<FilterPanel :filtres-initiaux="currentFilters" @filter-change="onFiltersChange" />
|
||||
|
||||
<!-- KPI Cards -->
|
||||
<KpiCards :summary="summary" />
|
||||
@@ -46,6 +46,8 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { SCHEMA_DEPENSES, ecrireFiltres, lireFiltres } from '../utils/filtresUrl.js'
|
||||
import FilterPanel from '../components/analytics/FilterPanel.vue'
|
||||
import KpiCards from '../components/analytics/KpiCards.vue'
|
||||
import CategoryChart from '../components/analytics/CategoryChart.vue'
|
||||
@@ -54,16 +56,12 @@ import MonthlyChart from '../components/analytics/MonthlyChart.vue'
|
||||
import TopFournisseursChart from '../components/analytics/TopFournisseursChart.vue'
|
||||
import DepensesTable from '../components/analytics/DepensesTable.vue'
|
||||
|
||||
// Current filters state
|
||||
let currentFilters = {
|
||||
immeuble_id: null,
|
||||
lot_id: null,
|
||||
categorie: null,
|
||||
tag_id: null,
|
||||
fournisseurs: [],
|
||||
date_debut: null,
|
||||
date_fin: null
|
||||
}
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
// Les filtres viennent de l'URL : ouvrir un lien filtre, recharger la page ou
|
||||
// revenir de l'editeur de document rend exactement la meme vue.
|
||||
const currentFilters = ref(lireFiltres(route.query, SCHEMA_DEPENSES))
|
||||
|
||||
const summary = ref({
|
||||
total_count: 0,
|
||||
@@ -86,8 +84,12 @@ const lastUpdate = ref(null)
|
||||
let debounceTimer = null
|
||||
|
||||
function onFiltersChange(newFilters) {
|
||||
currentFilters = { ...newFilters }
|
||||
|
||||
currentFilters.value = { ...newFilters }
|
||||
|
||||
// `replace` et non `push` : le bouton Retour du navigateur doit quitter la
|
||||
// page, pas defaire les filtres un par un.
|
||||
router.replace({ query: ecrireFiltres(currentFilters.value, SCHEMA_DEPENSES) })
|
||||
|
||||
// Debounce
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
debounceTimer = setTimeout(() => {
|
||||
@@ -95,18 +97,18 @@ function onFiltersChange(newFilters) {
|
||||
}, 300)
|
||||
}
|
||||
|
||||
/** L'API attend les memes parametres que l'URL de la page : une seule ecriture
|
||||
des filtres, donc aucun moyen que la vue et la requete divergent. */
|
||||
function buildQueryParams() {
|
||||
const params = new URLSearchParams()
|
||||
|
||||
if (currentFilters.immeuble_id !== null) params.append('immeuble_id', currentFilters.immeuble_id)
|
||||
if (currentFilters.lot_id !== null) params.append('lot_id', currentFilters.lot_id)
|
||||
if (currentFilters.categorie !== null) params.append('categorie', currentFilters.categorie)
|
||||
if (currentFilters.tag_id !== null) params.append('tag_id', currentFilters.tag_id)
|
||||
// Un parametre `fournisseur` par fournisseur retenu : l'API en fait un OR.
|
||||
for (const nom of currentFilters.fournisseurs || []) params.append('fournisseur', nom)
|
||||
if (currentFilters.date_debut) params.append('date_debut', currentFilters.date_debut)
|
||||
if (currentFilters.date_fin) params.append('date_fin', currentFilters.date_fin)
|
||||
|
||||
const query = ecrireFiltres(currentFilters.value, SCHEMA_DEPENSES)
|
||||
|
||||
for (const [cle, valeur] of Object.entries(query)) {
|
||||
for (const unitaire of Array.isArray(valeur) ? valeur : [valeur]) {
|
||||
params.append(cle, unitaire)
|
||||
}
|
||||
}
|
||||
|
||||
return params.toString()
|
||||
}
|
||||
|
||||
@@ -115,7 +117,7 @@ async function loadData() {
|
||||
const summaryUrl = queryString ? `/api/analytics/depenses/summary?${queryString}` : '/api/analytics/depenses/summary'
|
||||
const depensesUrl = queryString ? `/api/analytics/depenses?${queryString}&limit=500` : '/api/analytics/depenses?limit=500'
|
||||
|
||||
console.log('Loading data with filters:', currentFilters)
|
||||
console.log('Loading data with filters:', currentFilters.value)
|
||||
console.log('Summary URL:', summaryUrl)
|
||||
|
||||
isLoadingDepenses.value = true
|
||||
|
||||
Reference in New Issue
Block a user