feat(tri): mémoriser les préférences de tri
This commit is contained in:
53
app/composables/usePersistedSort.ts
Normal file
53
app/composables/usePersistedSort.ts
Normal file
@@ -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<string | null>(`sort:${key}`, {
|
||||||
|
sameSite: 'lax',
|
||||||
|
})
|
||||||
|
const stored = readSortCookie(cookie.value)
|
||||||
|
const sortField = ref<TField>((stored?.field as TField) || defaults.field)
|
||||||
|
const sortDirection = ref<TDirection>(
|
||||||
|
(stored?.direction as TDirection) || defaults.direction,
|
||||||
|
)
|
||||||
|
|
||||||
|
watch([sortField, sortDirection], () => {
|
||||||
|
cookie.value = JSON.stringify({
|
||||||
|
field: sortField.value,
|
||||||
|
direction: sortDirection.value,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
sortField,
|
||||||
|
sortDirection,
|
||||||
|
}
|
||||||
|
}
|
||||||
34
app/composables/usePersistedValue.ts
Normal file
34
app/composables/usePersistedValue.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { ref, watch } from 'vue'
|
||||||
|
import { useCookie } from '#imports'
|
||||||
|
|
||||||
|
const parseValue = <T>(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 = <T>(key: string, fallback: T) => {
|
||||||
|
const cookie = useCookie<string | null>(`pref:${key}`, {
|
||||||
|
sameSite: 'lax',
|
||||||
|
})
|
||||||
|
const initial = parseValue(cookie.value, fallback)
|
||||||
|
const state = ref<T>(initial)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
state,
|
||||||
|
(value) => {
|
||||||
|
cookie.value = JSON.stringify(value)
|
||||||
|
},
|
||||||
|
{ deep: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
return state
|
||||||
|
}
|
||||||
@@ -140,19 +140,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useComposants } from '~/composables/useComposants'
|
import { useComposants } from '~/composables/useComposants'
|
||||||
|
import { useComponentTypes } from '~/composables/useComponentTypes'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
|
import { usePersistedSort } from '~/composables/usePersistedSort'
|
||||||
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
||||||
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
||||||
|
|
||||||
const { showError } = useToast()
|
const { showError } = useToast()
|
||||||
const { composants, loadComposants, loading: loadingComposantsRef, deleteComposant } = useComposants()
|
const { composants, loadComposants, loading: loadingComposantsRef, deleteComposant } = useComposants()
|
||||||
|
const { componentTypes, loadComponentTypes } = useComponentTypes()
|
||||||
const loadingComposants = computed(() => loadingComposantsRef.value)
|
const loadingComposants = computed(() => loadingComposantsRef.value)
|
||||||
const composantsList = computed(() => composants.value || [])
|
|
||||||
|
// Enrichir les composants avec les types de composants complets
|
||||||
|
const composantsList = computed(() => {
|
||||||
|
return (composants.value || []).map((composant) => {
|
||||||
|
const typeComposant = componentTypes.value.find(t => t.id === composant.typeComposantId)
|
||||||
|
return {
|
||||||
|
...composant,
|
||||||
|
typeComposant: typeComposant || composant.typeComposant || null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
const composantsTotal = computed(() => composantsList.value.length)
|
const composantsTotal = computed(() => composantsList.value.length)
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const sortField = ref<'name' | 'createdAt'>('name')
|
const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>(
|
||||||
const sortDirection = ref<'asc' | 'desc'>('asc')
|
'component-catalog',
|
||||||
|
{ field: 'name', direction: 'asc' },
|
||||||
|
)
|
||||||
|
|
||||||
const resolvePrimaryDocument = (component: Record<string, any>) => {
|
const resolvePrimaryDocument = (component: Record<string, any>) => {
|
||||||
const documents = Array.isArray(component?.documents) ? component.documents : []
|
const documents = Array.isArray(component?.documents) ? component.documents : []
|
||||||
@@ -298,6 +313,9 @@ const handleDeleteComponent = async (component: Record<string, any>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadComposants()
|
await Promise.all([
|
||||||
|
loadComposants(),
|
||||||
|
loadComponentTypes()
|
||||||
|
])
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ import FieldEmail from '~/components/form/FieldEmail.vue'
|
|||||||
import FieldPhone from '~/components/form/FieldPhone.vue'
|
import FieldPhone from '~/components/form/FieldPhone.vue'
|
||||||
import { useConstructeurs } from '~/composables/useConstructeurs'
|
import { useConstructeurs } from '~/composables/useConstructeurs'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
|
import { usePersistedValue } from '~/composables/usePersistedValue'
|
||||||
import { formatPhone } from '~/utils/formatters/phone'
|
import { formatPhone } from '~/utils/formatters/phone'
|
||||||
import IconLucidePlus from '~icons/lucide/plus'
|
import IconLucidePlus from '~icons/lucide/plus'
|
||||||
|
|
||||||
@@ -129,7 +130,7 @@ const { constructeurs, loading, searchConstructeurs, createConstructeur, updateC
|
|||||||
const { showError, showSuccess } = useToast()
|
const { showError, showSuccess } = useToast()
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const sortKey = ref('name')
|
const sortKey = usePersistedValue('constructeurs-sort', 'name')
|
||||||
const modalOpen = ref(false)
|
const modalOpen = ref(false)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
const editingConstructeur = ref(null)
|
const editingConstructeur = ref(null)
|
||||||
|
|||||||
@@ -162,19 +162,34 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { usePieces } from '~/composables/usePieces'
|
import { usePieces } from '~/composables/usePieces'
|
||||||
|
import { usePieceTypes } from '~/composables/usePieceTypes'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
|
import { usePersistedSort } from '~/composables/usePersistedSort'
|
||||||
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
||||||
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
||||||
|
|
||||||
const { showError } = useToast()
|
const { showError } = useToast()
|
||||||
const { pieces, loadPieces, loading: loadingPiecesRef, deletePiece } = usePieces()
|
const { pieces, loadPieces, loading: loadingPiecesRef, deletePiece } = usePieces()
|
||||||
|
const { pieceTypes, loadPieceTypes } = usePieceTypes()
|
||||||
const loadingPieces = computed(() => loadingPiecesRef.value)
|
const loadingPieces = computed(() => loadingPiecesRef.value)
|
||||||
const piecesList = computed(() => pieces.value || [])
|
|
||||||
|
// Enrichir les pièces avec les types de pièces complets
|
||||||
|
const piecesList = computed(() => {
|
||||||
|
return (pieces.value || []).map((piece) => {
|
||||||
|
const typePiece = pieceTypes.value.find(t => t.id === piece.typePieceId)
|
||||||
|
return {
|
||||||
|
...piece,
|
||||||
|
typePiece: typePiece || piece.typePiece || null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
const piecesTotal = computed(() => piecesList.value.length)
|
const piecesTotal = computed(() => piecesList.value.length)
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const sortField = ref<'name' | 'createdAt'>('name')
|
const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>(
|
||||||
const sortDirection = ref<'asc' | 'desc'>('asc')
|
'pieces-catalog',
|
||||||
|
{ field: 'name', direction: 'asc' },
|
||||||
|
)
|
||||||
|
|
||||||
const resolvePrimaryDocument = (piece: Record<string, any>) => {
|
const resolvePrimaryDocument = (piece: Record<string, any>) => {
|
||||||
const documents = Array.isArray(piece?.documents) ? piece.documents : []
|
const documents = Array.isArray(piece?.documents) ? piece.documents : []
|
||||||
@@ -413,6 +428,9 @@ const handleDeletePiece = async (piece: Record<string, any>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadPieces()
|
await Promise.all([
|
||||||
|
loadPieces(),
|
||||||
|
loadPieceTypes()
|
||||||
|
])
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -164,7 +164,9 @@
|
|||||||
import { computed, onMounted, ref } from 'vue'
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { useHead } from '#imports'
|
import { useHead } from '#imports'
|
||||||
import { useProducts } from '~/composables/useProducts'
|
import { useProducts } from '~/composables/useProducts'
|
||||||
|
import { useProductTypes } from '~/composables/useProductTypes'
|
||||||
import { useToast } from '~/composables/useToast'
|
import { useToast } from '~/composables/useToast'
|
||||||
|
import { usePersistedSort } from '~/composables/usePersistedSort'
|
||||||
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
||||||
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
||||||
|
|
||||||
@@ -181,13 +183,25 @@ const {
|
|||||||
loadProducts,
|
loadProducts,
|
||||||
deleteProduct,
|
deleteProduct,
|
||||||
} = useProducts()
|
} = useProducts()
|
||||||
|
const { productTypes, loadProductTypes } = useProductTypes()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const searchTerm = ref('')
|
const searchTerm = ref('')
|
||||||
const sortField = ref<'name' | 'createdAt'>('name')
|
const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>(
|
||||||
const sortDirection = ref<'asc' | 'desc'>('asc')
|
'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 hasLoaded = computed(() => loaded.value)
|
||||||
const errorMessage = computed(() => (typeof error.value === 'string' && error.value.length ? error.value : null))
|
const errorMessage = computed(() => (typeof error.value === 'string' && error.value.length ? error.value : null))
|
||||||
|
|
||||||
@@ -383,6 +397,9 @@ const confirmDelete = async (product: Record<string, any>) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await loadProducts()
|
await Promise.all([
|
||||||
|
loadProducts(),
|
||||||
|
loadProductTypes()
|
||||||
|
])
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user