feat(navigation) : preserve list state in URL and use browser history for back buttons

Add useUrlState composable to sync page, search, sort and filter state
with URL query params. Back/forward navigation now restores the exact
list position. Replace hardcoded NuxtLink back buttons with
router.back() across all create/edit pages. Fix documents attachment
filter that checked non-existent ID fields instead of relation objects.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matthieu
2026-02-11 16:48:40 +01:00
parent 185af65519
commit 480aaa24b2
18 changed files with 228 additions and 77 deletions

View File

@@ -198,11 +198,11 @@
</template>
<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted } from 'vue'
import { usePieces } from '~/composables/usePieces'
import { usePieceTypes } from '~/composables/usePieceTypes'
import { useToast } from '~/composables/useToast'
import { usePersistedSort } from '~/composables/usePersistedSort'
import { useUrlState } from '~/composables/useUrlState'
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
import Pagination from '~/components/common/Pagination.vue'
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
@@ -212,15 +212,28 @@ const { pieces, total, loadPieces, loading: loadingPiecesRef, deletePiece } = us
const { pieceTypes, loadPieceTypes } = usePieceTypes()
const loadingPieces = computed(() => loadingPiecesRef.value)
// Pagination state
const currentPage = ref(1)
const itemsPerPage = ref(30)
// State synced with URL query params (preserved on back/forward navigation)
const {
page: currentPage,
perPage: itemsPerPage,
q: searchTerm,
sort: sortField,
dir: sortDirection,
} = useUrlState({
page: { default: 1, type: 'number' },
perPage: { default: 20, type: 'number' },
q: { default: '', debounce: 300 },
sort: { default: 'name' },
dir: { default: 'asc' },
}, {
onRestore: () => fetchPieces(),
})
const piecesTotal = computed(() => total.value)
const piecesOnPage = computed(() => pieces.value.length)
const totalPages = computed(() => Math.ceil(piecesTotal.value / itemsPerPage.value) || 1)
// Search state with debounce
const searchTerm = ref('')
// Search debounce for API calls
let searchTimeout: ReturnType<typeof setTimeout> | null = null
const debouncedSearch = () => {
@@ -233,12 +246,6 @@ const debouncedSearch = () => {
}, 300)
}
// Sort state
const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>(
'pieces-catalog',
{ field: 'name', direction: 'asc' },
)
// Enrichir les pièces avec les types de pièces complets
const piecesList = computed(() => {
return (pieces.value || []).map((piece) => {
@@ -256,7 +263,7 @@ const fetchPieces = async () => {
page: currentPage.value,
itemsPerPage: itemsPerPage.value,
orderBy: sortField.value,
orderDir: sortDirection.value,
orderDir: sortDirection.value as 'asc' | 'desc',
force: true
})
}