feat : UiDataTable avec pagination server-side et loader
- Composant UiDataTable (pagination, slots header/cell/actions/empty) - Composable useDataTableServerState (token anti-race, debounce filtres) - Migration de la page réceptions finies sur le nouveau pattern - pagination_client_items_per_page activé globalement Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
102
frontend/composables/useDataTableServerState.ts
Normal file
102
frontend/composables/useDataTableServerState.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { ref, watch } from 'vue'
|
||||
import { useApi } from '~/composables/useApi'
|
||||
|
||||
type FilterValue = string | number | boolean | null
|
||||
|
||||
export interface UseDataTableServerStateOptions {
|
||||
initialPerPage?: number
|
||||
debounceMs?: number
|
||||
}
|
||||
|
||||
export function useDataTableServerState<T = Record<string, unknown>>(
|
||||
endpoint: string,
|
||||
initialFilters: Record<string, FilterValue> = {},
|
||||
options: UseDataTableServerStateOptions = {}
|
||||
) {
|
||||
const api = useApi()
|
||||
|
||||
const debounceMs = options.debounceMs ?? 300
|
||||
const initialPerPage = options.initialPerPage ?? 10
|
||||
|
||||
const items = ref<T[]>([]) as { value: T[] }
|
||||
const totalItems = ref(0)
|
||||
const page = ref(1)
|
||||
const perPage = ref(initialPerPage)
|
||||
const filters = ref<Record<string, FilterValue>>({ ...initialFilters })
|
||||
const loading = ref(false)
|
||||
|
||||
let debounceTimer: ReturnType<typeof setTimeout> | null = null
|
||||
let requestToken = 0
|
||||
|
||||
const buildQueryParams = (): Record<string, string | number | boolean> => {
|
||||
const params: Record<string, string | number | boolean> = {
|
||||
page: page.value,
|
||||
itemsPerPage: perPage.value
|
||||
}
|
||||
for (const [key, value] of Object.entries(filters.value)) {
|
||||
if (value === '' || value === null) continue
|
||||
params[key] = value as string | number | boolean
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
const fetchItems = async (): Promise<void> => {
|
||||
const currentToken = ++requestToken
|
||||
loading.value = true
|
||||
try {
|
||||
const data = await api.get<{ member: T[]; totalItems: number }>(
|
||||
endpoint,
|
||||
buildQueryParams(),
|
||||
{
|
||||
toast: false,
|
||||
headers: { Accept: 'application/ld+json' }
|
||||
}
|
||||
)
|
||||
if (currentToken !== requestToken) return
|
||||
items.value = data?.member ?? []
|
||||
totalItems.value = data?.totalItems ?? 0
|
||||
} finally {
|
||||
if (currentToken === requestToken) {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const reload = (): void => {
|
||||
if (debounceTimer) {
|
||||
clearTimeout(debounceTimer)
|
||||
debounceTimer = null
|
||||
}
|
||||
void fetchItems()
|
||||
}
|
||||
|
||||
const scheduleReload = (): void => {
|
||||
if (debounceTimer) clearTimeout(debounceTimer)
|
||||
debounceTimer = setTimeout(() => {
|
||||
debounceTimer = null
|
||||
void fetchItems()
|
||||
}, debounceMs)
|
||||
}
|
||||
|
||||
watch([page, perPage], () => {
|
||||
reload()
|
||||
})
|
||||
|
||||
watch(filters, () => {
|
||||
if (page.value !== 1) {
|
||||
page.value = 1
|
||||
return
|
||||
}
|
||||
scheduleReload()
|
||||
}, { deep: true })
|
||||
|
||||
return {
|
||||
items,
|
||||
totalItems,
|
||||
page,
|
||||
perPage,
|
||||
filters,
|
||||
loading,
|
||||
reload
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user