From ddce3ff3ae58c63c791b6d5ebf5749edbbaa464b Mon Sep 17 00:00:00 2001 From: matthieu Date: Wed, 14 Jan 2026 23:10:27 +0100 Subject: [PATCH] =?UTF-8?q?feat(tri):=20m=C3=A9moriser=20les=20pr=C3=A9f?= =?UTF-8?q?=C3=A9rences=20de=20tri?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/composables/usePersistedSort.ts | 53 ++++++++++++++++++++++++++++ app/composables/usePersistedValue.ts | 34 ++++++++++++++++++ app/pages/component-catalog.vue | 26 +++++++++++--- app/pages/constructeurs.vue | 3 +- app/pages/pieces-catalog.vue | 26 +++++++++++--- app/pages/product-catalog.vue | 25 ++++++++++--- 6 files changed, 154 insertions(+), 13 deletions(-) create mode 100644 app/composables/usePersistedSort.ts create mode 100644 app/composables/usePersistedValue.ts diff --git a/app/composables/usePersistedSort.ts b/app/composables/usePersistedSort.ts new file mode 100644 index 0000000..ea7c569 --- /dev/null +++ b/app/composables/usePersistedSort.ts @@ -0,0 +1,53 @@ +import { ref, watch } from 'vue' +import { useCookie } from '#imports' + +type SortCookie = { + field?: string + direction?: string +} + +const readSortCookie = (value: unknown): SortCookie | null => { + if (!value) { + return null + } + if (typeof value === 'object') { + return value as SortCookie + } + if (typeof value === 'string') { + try { + return JSON.parse(value) as SortCookie + } catch { + return null + } + } + return null +} + +export const usePersistedSort = < + TField extends string, + TDirection extends string, +>( + key: string, + defaults: { field: TField; direction: TDirection }, +) => { + const cookie = useCookie(`sort:${key}`, { + sameSite: 'lax', + }) + const stored = readSortCookie(cookie.value) + const sortField = ref((stored?.field as TField) || defaults.field) + const sortDirection = ref( + (stored?.direction as TDirection) || defaults.direction, + ) + + watch([sortField, sortDirection], () => { + cookie.value = JSON.stringify({ + field: sortField.value, + direction: sortDirection.value, + }) + }) + + return { + sortField, + sortDirection, + } +} diff --git a/app/composables/usePersistedValue.ts b/app/composables/usePersistedValue.ts new file mode 100644 index 0000000..3d9fe71 --- /dev/null +++ b/app/composables/usePersistedValue.ts @@ -0,0 +1,34 @@ +import { ref, watch } from 'vue' +import { useCookie } from '#imports' + +const parseValue = (value: unknown, fallback: T): T => { + if (value === null || value === undefined) { + return fallback + } + if (typeof value === 'string') { + try { + return JSON.parse(value) as T + } catch { + return value as unknown as T + } + } + return value as T +} + +export const usePersistedValue = (key: string, fallback: T) => { + const cookie = useCookie(`pref:${key}`, { + sameSite: 'lax', + }) + const initial = parseValue(cookie.value, fallback) + const state = ref(initial) + + watch( + state, + (value) => { + cookie.value = JSON.stringify(value) + }, + { deep: true }, + ) + + return state +} diff --git a/app/pages/component-catalog.vue b/app/pages/component-catalog.vue index 39b4615..83321ec 100644 --- a/app/pages/component-catalog.vue +++ b/app/pages/component-catalog.vue @@ -140,19 +140,34 @@ diff --git a/app/pages/constructeurs.vue b/app/pages/constructeurs.vue index 8d74f8a..ef62fff 100644 --- a/app/pages/constructeurs.vue +++ b/app/pages/constructeurs.vue @@ -122,6 +122,7 @@ import FieldEmail from '~/components/form/FieldEmail.vue' import FieldPhone from '~/components/form/FieldPhone.vue' import { useConstructeurs } from '~/composables/useConstructeurs' import { useToast } from '~/composables/useToast' +import { usePersistedValue } from '~/composables/usePersistedValue' import { formatPhone } from '~/utils/formatters/phone' import IconLucidePlus from '~icons/lucide/plus' @@ -129,7 +130,7 @@ const { constructeurs, loading, searchConstructeurs, createConstructeur, updateC const { showError, showSuccess } = useToast() const searchTerm = ref('') -const sortKey = ref('name') +const sortKey = usePersistedValue('constructeurs-sort', 'name') const modalOpen = ref(false) const saving = ref(false) const editingConstructeur = ref(null) diff --git a/app/pages/pieces-catalog.vue b/app/pages/pieces-catalog.vue index 1ca85a0..146aa54 100644 --- a/app/pages/pieces-catalog.vue +++ b/app/pages/pieces-catalog.vue @@ -162,19 +162,34 @@ diff --git a/app/pages/product-catalog.vue b/app/pages/product-catalog.vue index 648f663..19a421f 100644 --- a/app/pages/product-catalog.vue +++ b/app/pages/product-catalog.vue @@ -164,7 +164,9 @@ import { computed, onMounted, ref } from 'vue' import { useHead } from '#imports' import { useProducts } from '~/composables/useProducts' +import { useProductTypes } from '~/composables/useProductTypes' import { useToast } from '~/composables/useToast' +import { usePersistedSort } from '~/composables/usePersistedSort' import DocumentThumbnail from '~/components/DocumentThumbnail.vue' import { isImageDocument, isPdfDocument } from '~/utils/documentPreview' @@ -181,13 +183,25 @@ const { loadProducts, deleteProduct, } = useProducts() +const { productTypes, loadProductTypes } = useProductTypes() const toast = useToast() const searchTerm = ref('') -const sortField = ref<'name' | 'createdAt'>('name') -const sortDirection = ref<'asc' | 'desc'>('asc') +const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>( + 'product-catalog', + { field: 'name', direction: 'asc' }, +) -const normalizedProducts = computed(() => (Array.isArray(products.value) ? products.value : [])) +// Enrichir les produits avec les types de produits complets +const normalizedProducts = computed(() => { + return (Array.isArray(products.value) ? products.value : []).map((product) => { + const typeProduct = productTypes.value.find(t => t.id === product.typeProductId) + return { + ...product, + typeProduct: typeProduct || product.typeProduct || null + } + }) +}) const hasLoaded = computed(() => loaded.value) const errorMessage = computed(() => (typeof error.value === 'string' && error.value.length ? error.value : null)) @@ -383,6 +397,9 @@ const confirmDelete = async (product: Record) => { } onMounted(async () => { - await loadProducts() + await Promise.all([ + loadProducts(), + loadProductTypes() + ]) })