fix(search) : préserver la recherche des listes via le fil d'Ariane
Auto Tag Develop / tag (push) Successful in 9s
Auto Tag Develop / tag (push) Successful in 9s
Le bouton Retour (cb49c69) restaurait l'état des listes via router.back(),
mais le fil d'Ariane faisait des liens en chemin nu (sans ?q=...), ce qui
réinitialisait recherche/tri/pagination en cliquant un crumb de liste depuis
une fiche.
- useListQueryMemory : singleton mémorisant la dernière query vue sur chaque
route-liste (SPA).
- AppBreadcrumb : mémorise la query des routes-listes et la réinjecte dans les
crumbs pointant vers une liste (helper listTo). Couvre composants, pièces,
produits et machines, y compris pages catégorie/création.
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<!-- First crumb (always visible) -->
|
<!-- First crumb (always visible) -->
|
||||||
<li>
|
<li>
|
||||||
<NuxtLink :to="crumbs[0].path" class="text-base-content/60 hover:text-primary transition-colors">
|
<NuxtLink :to="crumbs[0].to" class="text-base-content/60 hover:text-primary transition-colors">
|
||||||
{{ crumbs[0].label }}
|
{{ crumbs[0].label }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
:key="i"
|
:key="i"
|
||||||
class="hidden sm:list-item"
|
class="hidden sm:list-item"
|
||||||
>
|
>
|
||||||
<NuxtLink :to="crumb.path" class="text-base-content/60 hover:text-primary transition-colors">
|
<NuxtLink :to="crumb.to" class="text-base-content/60 hover:text-primary transition-colors">
|
||||||
{{ crumb.label }}
|
{{ crumb.label }}
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
@@ -32,15 +32,40 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { RouteLocationRaw } from 'vue-router'
|
||||||
|
import { useListQueryMemory } from '~/composables/useListQueryMemory'
|
||||||
|
|
||||||
interface Crumb {
|
interface Crumb {
|
||||||
label: string
|
label: string
|
||||||
path: string
|
to: RouteLocationRaw
|
||||||
}
|
}
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const { remember, recall } = useListQueryMemory()
|
||||||
|
|
||||||
|
// Routes-listes dont la recherche / tri / pagination doit survivre à une
|
||||||
|
// navigation par fil d'Ariane ou menu (qui ne passe pas par l'historique).
|
||||||
|
const LIST_PATHS = ['/machines', '/catalogues/composants', '/catalogues/pieces', '/catalogues/produits']
|
||||||
|
|
||||||
|
// On enregistre la query courante dès qu'on est sur une route-liste (et à chaque
|
||||||
|
// changement de recherche/tri/pagination, qui modifie fullPath).
|
||||||
|
watch(
|
||||||
|
() => route.fullPath,
|
||||||
|
() => {
|
||||||
|
if (LIST_PATHS.includes(route.path)) remember(route.path, route.query)
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
// Cible d'un crumb pointant vers une liste : on réinjecte la dernière query
|
||||||
|
// mémorisée pour restaurer l'état, sinon chemin nu (liste neuve).
|
||||||
|
const listTo = (path: string): RouteLocationRaw => {
|
||||||
|
const query = recall(path)
|
||||||
|
return query && Object.keys(query).length > 0 ? { path, query } : path
|
||||||
|
}
|
||||||
|
|
||||||
const crumbs = computed<Crumb[]>(() => {
|
const crumbs = computed<Crumb[]>(() => {
|
||||||
const result: Crumb[] = [{ label: 'Accueil', path: '/' }]
|
const result: Crumb[] = [{ label: 'Accueil', to: '/' }]
|
||||||
const path = route.path
|
const path = route.path
|
||||||
|
|
||||||
// Home page — no breadcrumb
|
// Home page — no breadcrumb
|
||||||
@@ -48,88 +73,88 @@ const crumbs = computed<Crumb[]>(() => {
|
|||||||
|
|
||||||
// Machine context from query param (when navigating from a machine detail page)
|
// Machine context from query param (when navigating from a machine detail page)
|
||||||
if (route.query.from === 'machine' && route.query.machineId) {
|
if (route.query.from === 'machine' && route.query.machineId) {
|
||||||
result.push({ label: 'Parc machines', path: '/machines' })
|
result.push({ label: 'Parc machines', to: listTo('/machines') })
|
||||||
result.push({ label: 'Machine', path: `/machine/${route.query.machineId}` })
|
result.push({ label: 'Machine', to: `/machine/${route.query.machineId}` })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Machines
|
// Machines
|
||||||
if (path === '/machines') {
|
if (path === '/machines') {
|
||||||
result.push({ label: 'Parc machines', path: '/machines' })
|
result.push({ label: 'Parc machines', to: listTo('/machines') })
|
||||||
} else if (path.startsWith('/machine/') && !route.query.from) {
|
} else if (path.startsWith('/machine/') && !route.query.from) {
|
||||||
result.push({ label: 'Parc machines', path: '/machines' })
|
result.push({ label: 'Parc machines', to: listTo('/machines') })
|
||||||
result.push({ label: 'Machine', path })
|
result.push({ label: 'Machine', to: path })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Catalogs
|
// Catalogs
|
||||||
else if (path.startsWith('/catalogues/composants')) {
|
else if (path.startsWith('/catalogues/composants')) {
|
||||||
result.push({ label: 'Composants', path: '/catalogues/composants' })
|
result.push({ label: 'Composants', to: listTo('/catalogues/composants') })
|
||||||
} else if (path.startsWith('/catalogues/pieces')) {
|
} else if (path.startsWith('/catalogues/pieces')) {
|
||||||
result.push({ label: 'Pièces', path: '/catalogues/pieces' })
|
result.push({ label: 'Pièces', to: listTo('/catalogues/pieces') })
|
||||||
} else if (path.startsWith('/catalogues/produits')) {
|
} else if (path.startsWith('/catalogues/produits')) {
|
||||||
result.push({ label: 'Produits', path: '/catalogues/produits' })
|
result.push({ label: 'Produits', to: listTo('/catalogues/produits') })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entity detail pages (when NOT from machine context)
|
// Entity detail pages (when NOT from machine context)
|
||||||
else if (path.startsWith('/component/') && !route.query.from) {
|
else if (path.startsWith('/component/') && !route.query.from) {
|
||||||
result.push({ label: 'Composants', path: '/catalogues/composants' })
|
result.push({ label: 'Composants', to: listTo('/catalogues/composants') })
|
||||||
result.push({ label: 'Composant', path })
|
result.push({ label: 'Composant', to: path })
|
||||||
} else if (path.startsWith('/piece/') && !route.query.from) {
|
} else if (path.startsWith('/piece/') && !route.query.from) {
|
||||||
result.push({ label: 'Pièces', path: '/catalogues/pieces' })
|
result.push({ label: 'Pièces', to: listTo('/catalogues/pieces') })
|
||||||
result.push({ label: 'Pièce', path })
|
result.push({ label: 'Pièce', to: path })
|
||||||
} else if (path.startsWith('/product/') && !route.query.from) {
|
} else if (path.startsWith('/product/') && !route.query.from) {
|
||||||
result.push({ label: 'Produits', path: '/catalogues/produits' })
|
result.push({ label: 'Produits', to: listTo('/catalogues/produits') })
|
||||||
result.push({ label: 'Produit', path })
|
result.push({ label: 'Produit', to: path })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Entity detail pages WITH machine context — add entity as last crumb
|
// Entity detail pages WITH machine context — add entity as last crumb
|
||||||
else if (path.startsWith('/component/') && route.query.from === 'machine') {
|
else if (path.startsWith('/component/') && route.query.from === 'machine') {
|
||||||
result.push({ label: 'Composant', path })
|
result.push({ label: 'Composant', to: path })
|
||||||
} else if (path.startsWith('/piece/') && route.query.from === 'machine') {
|
} else if (path.startsWith('/piece/') && route.query.from === 'machine') {
|
||||||
result.push({ label: 'Pièce', path })
|
result.push({ label: 'Pièce', to: path })
|
||||||
} else if (path.startsWith('/product/') && route.query.from === 'machine') {
|
} else if (path.startsWith('/product/') && route.query.from === 'machine') {
|
||||||
result.push({ label: 'Produit', path })
|
result.push({ label: 'Produit', to: path })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin pages
|
// Admin pages
|
||||||
else if (path.startsWith('/sites')) {
|
else if (path.startsWith('/sites')) {
|
||||||
result.push({ label: 'Sites', path: '/sites' })
|
result.push({ label: 'Sites', to: '/sites' })
|
||||||
} else if (path.startsWith('/constructeurs')) {
|
} else if (path.startsWith('/constructeurs')) {
|
||||||
result.push({ label: 'Fournisseurs', path: '/constructeurs' })
|
result.push({ label: 'Fournisseurs', to: '/constructeurs' })
|
||||||
} else if (path.startsWith('/activity-log')) {
|
} else if (path.startsWith('/activity-log')) {
|
||||||
result.push({ label: 'Journal d\'activité', path: '/activity-log' })
|
result.push({ label: 'Journal d\'activité', to: '/activity-log' })
|
||||||
} else if (path.startsWith('/admin')) {
|
} else if (path.startsWith('/admin')) {
|
||||||
result.push({ label: 'Administration', path: '/admin' })
|
result.push({ label: 'Administration', to: '/admin' })
|
||||||
} else if (path.startsWith('/documents')) {
|
} else if (path.startsWith('/documents')) {
|
||||||
result.push({ label: 'Documents', path: '/documents' })
|
result.push({ label: 'Documents', to: '/documents' })
|
||||||
} else if (path.startsWith('/comments')) {
|
} else if (path.startsWith('/comments')) {
|
||||||
result.push({ label: 'Commentaires', path: '/comments' })
|
result.push({ label: 'Commentaires', to: '/comments' })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Category pages
|
// Category pages
|
||||||
else if (path.startsWith('/component-category')) {
|
else if (path.startsWith('/component-category')) {
|
||||||
result.push({ label: 'Composants', path: '/catalogues/composants' })
|
result.push({ label: 'Composants', to: listTo('/catalogues/composants') })
|
||||||
result.push({ label: 'Catégorie', path })
|
result.push({ label: 'Catégorie', to: path })
|
||||||
} else if (path.startsWith('/piece-category')) {
|
} else if (path.startsWith('/piece-category')) {
|
||||||
result.push({ label: 'Pièces', path: '/catalogues/pieces' })
|
result.push({ label: 'Pièces', to: listTo('/catalogues/pieces') })
|
||||||
result.push({ label: 'Catégorie', path })
|
result.push({ label: 'Catégorie', to: path })
|
||||||
} else if (path.startsWith('/product-category')) {
|
} else if (path.startsWith('/product-category')) {
|
||||||
result.push({ label: 'Produits', path: '/catalogues/produits' })
|
result.push({ label: 'Produits', to: listTo('/catalogues/produits') })
|
||||||
result.push({ label: 'Catégorie', path })
|
result.push({ label: 'Catégorie', to: path })
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create pages
|
// Create pages
|
||||||
else if (path.startsWith('/pieces/create')) {
|
else if (path.startsWith('/pieces/create')) {
|
||||||
result.push({ label: 'Pièces', path: '/catalogues/pieces' })
|
result.push({ label: 'Pièces', to: listTo('/catalogues/pieces') })
|
||||||
result.push({ label: 'Nouvelle pièce', path })
|
result.push({ label: 'Nouvelle pièce', to: path })
|
||||||
} else if (path.startsWith('/component/create')) {
|
} else if (path.startsWith('/component/create')) {
|
||||||
result.push({ label: 'Composants', path: '/catalogues/composants' })
|
result.push({ label: 'Composants', to: listTo('/catalogues/composants') })
|
||||||
result.push({ label: 'Nouveau composant', path })
|
result.push({ label: 'Nouveau composant', to: path })
|
||||||
} else if (path.startsWith('/product/create')) {
|
} else if (path.startsWith('/product/create')) {
|
||||||
result.push({ label: 'Produits', path: '/catalogues/produits' })
|
result.push({ label: 'Produits', to: listTo('/catalogues/produits') })
|
||||||
result.push({ label: 'Nouveau produit', path })
|
result.push({ label: 'Nouveau produit', to: path })
|
||||||
} else if (path === '/machines/new') {
|
} else if (path === '/machines/new') {
|
||||||
result.push({ label: 'Parc machines', path: '/machines' })
|
result.push({ label: 'Parc machines', to: listTo('/machines') })
|
||||||
result.push({ label: 'Nouvelle machine', path })
|
result.push({ label: 'Nouvelle machine', to: path })
|
||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { reactive } from 'vue'
|
||||||
|
import type { LocationQuery } from 'vue-router'
|
||||||
|
|
||||||
|
// Singleton module-level : mémorise la dernière query (recherche / tri /
|
||||||
|
// pagination / filtres) vue sur chaque route-liste. Permet aux navigations qui
|
||||||
|
// ne passent PAS par l'historique du navigateur (fil d'Ariane, menu) de
|
||||||
|
// restaurer l'état de la liste, là où router.back() le ferait pour le bouton
|
||||||
|
// Retour. SPA only (SSR off) — pas de fuite d'état entre requêtes.
|
||||||
|
const memory = reactive<Record<string, LocationQuery>>({})
|
||||||
|
|
||||||
|
export function useListQueryMemory() {
|
||||||
|
const remember = (path: string, query: LocationQuery) => {
|
||||||
|
memory[path] = { ...query }
|
||||||
|
}
|
||||||
|
const recall = (path: string): LocationQuery | undefined => memory[path]
|
||||||
|
return { remember, recall }
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user