- Nouveau composant DataTable réutilisable avec tri par en-têtes, pagination, filtres colonnes - Nouveau composable useDataTable (sort/page/search/perPage/columnFilters + persistance URL) - Migration des 9 tables : constructeurs, comments, admin, pieces-catalog, component-catalog, product-catalog, documents, activity-log, ManagementView (catégories) - Filtres "Type de" server-side (ipartial) pour pièces, composants, produits Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
187 lines
5.3 KiB
TypeScript
187 lines
5.3 KiB
TypeScript
import { ref, computed, type Ref, type ComputedRef } from 'vue'
|
|
import { useUrlState } from './useUrlState'
|
|
import type { DataTableSort, DataTablePagination, DataTableColumnFilters, SortDirection } from '~/shared/types/dataTable'
|
|
|
|
export interface UseDataTableDeps {
|
|
/** Called whenever sort/page/search/perPage/filter changes. The composable does NOT fetch data itself. */
|
|
fetchData: () => void | Promise<void>
|
|
}
|
|
|
|
export interface UseDataTableOptions {
|
|
/** Default sort field */
|
|
defaultSort?: string
|
|
/** Default sort direction */
|
|
defaultDirection?: SortDirection
|
|
/** Default items per page */
|
|
defaultPerPage?: number
|
|
/** Available per-page options */
|
|
perPageOptions?: number[]
|
|
/** Search debounce in ms. Default: 300 */
|
|
searchDebounceMs?: number
|
|
/** Whether to persist state to URL. Default: true */
|
|
persistToUrl?: boolean
|
|
/** Extra URL state params for page-specific filters */
|
|
extraParams?: Record<string, { default: string | number; type?: 'string' | 'number' }>
|
|
}
|
|
|
|
export interface UseDataTableReturn {
|
|
searchTerm: Ref<string>
|
|
sortField: Ref<string>
|
|
sortDirection: Ref<SortDirection>
|
|
currentPage: Ref<number>
|
|
itemsPerPage: Ref<number>
|
|
columnFilters: Ref<DataTableColumnFilters>
|
|
filters: Record<string, Ref<string | number>>
|
|
sort: ComputedRef<DataTableSort>
|
|
pagination: (total: Ref<number>, pageItems: Ref<number>) => ComputedRef<DataTablePagination>
|
|
handleSort: (newSort: DataTableSort) => void
|
|
handlePageChange: (page: number) => void
|
|
handlePerPageChange: (perPage: number) => void
|
|
handleFilterChange: () => void
|
|
handleColumnFiltersChange: (filters: DataTableColumnFilters) => void
|
|
debouncedSearch: () => void
|
|
refresh: () => void
|
|
perPageOptions: number[]
|
|
}
|
|
|
|
export function useDataTable(
|
|
deps: UseDataTableDeps,
|
|
options: UseDataTableOptions = {},
|
|
): UseDataTableReturn {
|
|
const {
|
|
defaultSort = 'name',
|
|
defaultDirection = 'asc',
|
|
defaultPerPage = 20,
|
|
perPageOptions = [20, 50, 100],
|
|
searchDebounceMs = 300,
|
|
persistToUrl = true,
|
|
extraParams = {},
|
|
} = options
|
|
|
|
let searchTerm: Ref<string>
|
|
let sortField: Ref<string>
|
|
let sortDirection: Ref<SortDirection>
|
|
let currentPage: Ref<number>
|
|
let itemsPerPage: Ref<number>
|
|
const filters: Record<string, Ref<string | number>> = {}
|
|
|
|
if (persistToUrl) {
|
|
const paramDefs: Record<string, { default: string | number; type?: 'string' | 'number'; debounce?: number }> = {
|
|
page: { default: 1, type: 'number' },
|
|
perPage: { default: defaultPerPage, type: 'number' },
|
|
q: { default: '', debounce: searchDebounceMs },
|
|
sort: { default: defaultSort },
|
|
dir: { default: defaultDirection },
|
|
...extraParams,
|
|
}
|
|
|
|
const state = useUrlState(paramDefs, {
|
|
onRestore: () => deps.fetchData(),
|
|
})
|
|
|
|
searchTerm = state.q as Ref<string>
|
|
sortField = state.sort as Ref<string>
|
|
sortDirection = state.dir as unknown as Ref<SortDirection>
|
|
currentPage = state.page as unknown as Ref<number>
|
|
itemsPerPage = state.perPage as unknown as Ref<number>
|
|
|
|
for (const key of Object.keys(extraParams)) {
|
|
filters[key] = (state as Record<string, Ref<string | number>>)[key]!
|
|
}
|
|
}
|
|
else {
|
|
searchTerm = ref('')
|
|
sortField = ref(defaultSort)
|
|
sortDirection = ref(defaultDirection) as Ref<SortDirection>
|
|
currentPage = ref(1)
|
|
itemsPerPage = ref(defaultPerPage)
|
|
|
|
for (const [key, def] of Object.entries(extraParams)) {
|
|
filters[key] = ref(def.default)
|
|
}
|
|
}
|
|
|
|
// Search debounce
|
|
let searchTimeout: ReturnType<typeof setTimeout> | null = null
|
|
|
|
const debouncedSearch = () => {
|
|
if (searchTimeout) clearTimeout(searchTimeout)
|
|
searchTimeout = setTimeout(() => {
|
|
currentPage.value = 1
|
|
deps.fetchData()
|
|
}, searchDebounceMs)
|
|
}
|
|
|
|
// Sort
|
|
const sort = computed<DataTableSort>(() => ({
|
|
field: sortField.value,
|
|
direction: sortDirection.value,
|
|
}))
|
|
|
|
const handleSort = (newSort: DataTableSort) => {
|
|
sortField.value = newSort.field
|
|
sortDirection.value = newSort.direction
|
|
currentPage.value = 1
|
|
deps.fetchData()
|
|
}
|
|
|
|
// Pagination
|
|
const handlePageChange = (page: number) => {
|
|
currentPage.value = page
|
|
deps.fetchData()
|
|
}
|
|
|
|
const handlePerPageChange = (perPage: number) => {
|
|
itemsPerPage.value = perPage
|
|
currentPage.value = 1
|
|
deps.fetchData()
|
|
}
|
|
|
|
// Column filters
|
|
const columnFilters = ref<DataTableColumnFilters>({})
|
|
|
|
const handleColumnFiltersChange = (newFilters: DataTableColumnFilters) => {
|
|
columnFilters.value = newFilters
|
|
currentPage.value = 1
|
|
deps.fetchData()
|
|
}
|
|
|
|
// Generic filter change handler (resets page and refetches)
|
|
const handleFilterChange = () => {
|
|
currentPage.value = 1
|
|
deps.fetchData()
|
|
}
|
|
|
|
const pagination = (total: Ref<number>, pageItems: Ref<number>): ComputedRef<DataTablePagination> =>
|
|
computed(() => ({
|
|
currentPage: currentPage.value,
|
|
totalPages: Math.ceil(total.value / itemsPerPage.value) || 1,
|
|
totalItems: total.value,
|
|
pageItems: pageItems.value,
|
|
perPageOptions,
|
|
perPage: itemsPerPage.value,
|
|
}))
|
|
|
|
const refresh = () => deps.fetchData()
|
|
|
|
return {
|
|
searchTerm,
|
|
sortField,
|
|
sortDirection,
|
|
currentPage,
|
|
itemsPerPage,
|
|
columnFilters,
|
|
filters,
|
|
sort,
|
|
pagination,
|
|
handleSort,
|
|
handlePageChange,
|
|
handlePerPageChange,
|
|
handleFilterChange,
|
|
handleColumnFiltersChange,
|
|
debouncedSearch,
|
|
refresh,
|
|
perPageOptions,
|
|
}
|
|
}
|