Files
Inventory/frontend/app/components/layout/AppBreadcrumb.vue
T
Matthieu 012d552ddc
Auto Tag Develop / tag (push) Successful in 9s
fix(search) : préserver la recherche des listes via le fil d'Ariane
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.
2026-05-29 16:36:17 +02:00

163 lines
6.5 KiB
Vue

<template>
<nav v-if="crumbs.length > 1" class="container mx-auto px-6 pt-4" aria-label="Fil d'Ariane">
<div class="text-sm breadcrumbs py-0">
<ul>
<!-- First crumb (always visible) -->
<li>
<NuxtLink :to="crumbs[0].to" class="text-base-content/60 hover:text-primary transition-colors">
{{ crumbs[0].label }}
</NuxtLink>
</li>
<!-- Ellipsis on mobile when there are middle crumbs -->
<li v-if="crumbs.length > 2" class="sm:hidden">
<span class="text-base-content/40"></span>
</li>
<!-- Middle crumbs: hidden on mobile, visible sm+ -->
<li
v-for="(crumb, i) in crumbs.slice(1, crumbs.length - 1)"
:key="i"
class="hidden sm:list-item"
>
<NuxtLink :to="crumb.to" class="text-base-content/60 hover:text-primary transition-colors">
{{ crumb.label }}
</NuxtLink>
</li>
<!-- Last crumb (always visible, current page) -->
<li v-if="crumbs.length > 1">
<span class="text-base-content font-medium">{{ crumbs[crumbs.length - 1].label }}</span>
</li>
</ul>
</div>
</nav>
</template>
<script setup lang="ts">
import type { RouteLocationRaw } from 'vue-router'
import { useListQueryMemory } from '~/composables/useListQueryMemory'
interface Crumb {
label: string
to: RouteLocationRaw
}
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 result: Crumb[] = [{ label: 'Accueil', to: '/' }]
const path = route.path
// Home page — no breadcrumb
if (path === '/') return []
// Machine context from query param (when navigating from a machine detail page)
if (route.query.from === 'machine' && route.query.machineId) {
result.push({ label: 'Parc machines', to: listTo('/machines') })
result.push({ label: 'Machine', to: `/machine/${route.query.machineId}` })
}
// Machines
if (path === '/machines') {
result.push({ label: 'Parc machines', to: listTo('/machines') })
} else if (path.startsWith('/machine/') && !route.query.from) {
result.push({ label: 'Parc machines', to: listTo('/machines') })
result.push({ label: 'Machine', to: path })
}
// Catalogs
else if (path.startsWith('/catalogues/composants')) {
result.push({ label: 'Composants', to: listTo('/catalogues/composants') })
} else if (path.startsWith('/catalogues/pieces')) {
result.push({ label: 'Pièces', to: listTo('/catalogues/pieces') })
} else if (path.startsWith('/catalogues/produits')) {
result.push({ label: 'Produits', to: listTo('/catalogues/produits') })
}
// Entity detail pages (when NOT from machine context)
else if (path.startsWith('/component/') && !route.query.from) {
result.push({ label: 'Composants', to: listTo('/catalogues/composants') })
result.push({ label: 'Composant', to: path })
} else if (path.startsWith('/piece/') && !route.query.from) {
result.push({ label: 'Pièces', to: listTo('/catalogues/pieces') })
result.push({ label: 'Pièce', to: path })
} else if (path.startsWith('/product/') && !route.query.from) {
result.push({ label: 'Produits', to: listTo('/catalogues/produits') })
result.push({ label: 'Produit', to: path })
}
// Entity detail pages WITH machine context — add entity as last crumb
else if (path.startsWith('/component/') && route.query.from === 'machine') {
result.push({ label: 'Composant', to: path })
} else if (path.startsWith('/piece/') && route.query.from === 'machine') {
result.push({ label: 'Pièce', to: path })
} else if (path.startsWith('/product/') && route.query.from === 'machine') {
result.push({ label: 'Produit', to: path })
}
// Admin pages
else if (path.startsWith('/sites')) {
result.push({ label: 'Sites', to: '/sites' })
} else if (path.startsWith('/constructeurs')) {
result.push({ label: 'Fournisseurs', to: '/constructeurs' })
} else if (path.startsWith('/activity-log')) {
result.push({ label: 'Journal d\'activité', to: '/activity-log' })
} else if (path.startsWith('/admin')) {
result.push({ label: 'Administration', to: '/admin' })
} else if (path.startsWith('/documents')) {
result.push({ label: 'Documents', to: '/documents' })
} else if (path.startsWith('/comments')) {
result.push({ label: 'Commentaires', to: '/comments' })
}
// Category pages
else if (path.startsWith('/component-category')) {
result.push({ label: 'Composants', to: listTo('/catalogues/composants') })
result.push({ label: 'Catégorie', to: path })
} else if (path.startsWith('/piece-category')) {
result.push({ label: 'Pièces', to: listTo('/catalogues/pieces') })
result.push({ label: 'Catégorie', to: path })
} else if (path.startsWith('/product-category')) {
result.push({ label: 'Produits', to: listTo('/catalogues/produits') })
result.push({ label: 'Catégorie', to: path })
}
// Create pages
else if (path.startsWith('/pieces/create')) {
result.push({ label: 'Pièces', to: listTo('/catalogues/pieces') })
result.push({ label: 'Nouvelle pièce', to: path })
} else if (path.startsWith('/component/create')) {
result.push({ label: 'Composants', to: listTo('/catalogues/composants') })
result.push({ label: 'Nouveau composant', to: path })
} else if (path.startsWith('/product/create')) {
result.push({ label: 'Produits', to: listTo('/catalogues/produits') })
result.push({ label: 'Nouveau produit', to: path })
} else if (path === '/machines/new') {
result.push({ label: 'Parc machines', to: listTo('/machines') })
result.push({ label: 'Nouvelle machine', to: path })
}
return result
})
</script>