From ac3be7c94b3633bcd3dae43eb930ddc74c80dfb5 Mon Sep 17 00:00:00 2001 From: tristan Date: Tue, 21 Apr 2026 16:49:43 +0200 Subject: [PATCH 1/7] feat : UiDataTable avec pagination server-side et loader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- config/packages/api_platform.yaml | 2 + frontend/components/ui/UiDataTable.vue | 230 ++++++++++++++++++ .../composables/useDataTableServerState.ts | 102 ++++++++ frontend/pages/reception/finish-reception.vue | 70 +++--- 4 files changed, 372 insertions(+), 32 deletions(-) create mode 100644 frontend/components/ui/UiDataTable.vue create mode 100644 frontend/composables/useDataTableServerState.ts diff --git a/config/packages/api_platform.yaml b/config/packages/api_platform.yaml index 4b2b414..c686cc6 100644 --- a/config/packages/api_platform.yaml +++ b/config/packages/api_platform.yaml @@ -5,6 +5,8 @@ api_platform: stateless: true cache_headers: vary: ['Content-Type', 'Authorization', 'Origin'] + pagination_client_items_per_page: true + pagination_maximum_items_per_page: 100 formats: json: ['application/json'] jsonld: ['application/ld+json'] diff --git a/frontend/components/ui/UiDataTable.vue b/frontend/components/ui/UiDataTable.vue new file mode 100644 index 0000000..fa3572e --- /dev/null +++ b/frontend/components/ui/UiDataTable.vue @@ -0,0 +1,230 @@ + + + diff --git a/frontend/composables/useDataTableServerState.ts b/frontend/composables/useDataTableServerState.ts new file mode 100644 index 0000000..36697fd --- /dev/null +++ b/frontend/composables/useDataTableServerState.ts @@ -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>( + endpoint: string, + initialFilters: Record = {}, + options: UseDataTableServerStateOptions = {} +) { + const api = useApi() + + const debounceMs = options.debounceMs ?? 300 + const initialPerPage = options.initialPerPage ?? 10 + + const items = ref([]) as { value: T[] } + const totalItems = ref(0) + const page = ref(1) + const perPage = ref(initialPerPage) + const filters = ref>({ ...initialFilters }) + const loading = ref(false) + + let debounceTimer: ReturnType | null = null + let requestToken = 0 + + const buildQueryParams = (): Record => { + const params: Record = { + 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 => { + 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 + } +} diff --git a/frontend/pages/reception/finish-reception.vue b/frontend/pages/reception/finish-reception.vue index abf79a0..999dead 100644 --- a/frontend/pages/reception/finish-reception.vue +++ b/frontend/pages/reception/finish-reception.vue @@ -5,42 +5,50 @@
-
-
-
Numéro
-
Date et heure
-
Fournisseur
-
Adresse
-
Type réception
-
Poids
-
-
+ -
{{ reception.identificationNumber}}
-
{{ formatDate(reception.receptionDate) }}
-
{{ reception.supplier?.name }}
-
{{ reception.address?.fullAddress }}
-
{{ reception.receptionType?.label }}
-
{{ formatWeighing(reception) }}
-
+ + +
-- 2.39.5 From a6be7fb6a427c1a167a058be16c04af7de3b143d Mon Sep 17 00:00:00 2001 From: tristan Date: Wed, 22 Apr 2026 11:17:10 +0200 Subject: [PATCH 2/7] feat : filtres tableau (texte, date, select) et largeurs de colonnes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - DateFilter + SearchFilter sur Reception (identificationNumber, supplier.name, receptionType.id, receptionDate) - Prop width sur les colonnes du UiDataTable - Prop size compact sur UiTextInput/UiSelect/UiDateInput - Option placeholder re-sélectionnable sur UiSelect (clear du filtre) - Loader inline quand no items, overlay quand refetch Co-Authored-By: Claude Opus 4.7 (1M context) --- frontend/components/ui/UiDataTable.vue | 17 +-- frontend/components/ui/UiDateInput.vue | 10 +- frontend/components/ui/UiSelect.vue | 12 ++- frontend/components/ui/UiTextInput.vue | 10 +- frontend/pages/reception/finish-reception.vue | 100 ++++++++++++++++-- src/Entity/Reception.php | 8 ++ 6 files changed, 135 insertions(+), 22 deletions(-) diff --git a/frontend/components/ui/UiDataTable.vue b/frontend/components/ui/UiDataTable.vue index fa3572e..0e08d1d 100644 --- a/frontend/components/ui/UiDataTable.vue +++ b/frontend/components/ui/UiDataTable.vue @@ -2,10 +2,10 @@
-
+
{{ col.label }}
Actions
@@ -16,7 +16,7 @@
-
+
{{ getNestedValue(item, col.key) }} @@ -71,7 +71,7 @@