feat: ajouter le tri par nom sur les catalogues
This commit is contained in:
@@ -68,8 +68,8 @@ const props = withDefaults(
|
||||
const selectedCategory = ref<ModelCategory>(props.category);
|
||||
const searchInput = ref("");
|
||||
const searchTerm = ref("");
|
||||
const sort = ref<"name" | "createdAt">("createdAt");
|
||||
const dir = ref<"asc" | "desc">("desc");
|
||||
const sort = ref<"name" | "createdAt">("name");
|
||||
const dir = ref<"asc" | "desc">("asc");
|
||||
const limit = ref(20);
|
||||
const offset = ref(0);
|
||||
|
||||
|
||||
@@ -26,18 +26,52 @@
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||
<label class="w-full sm:w-64">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-base-content/70">Recherche</span>
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
type="text"
|
||||
class="input input-bordered input-sm w-full mt-1"
|
||||
placeholder="Nom, référence ou catégorie…"
|
||||
/>
|
||||
</label>
|
||||
<p class="text-xs text-base-content/50 sm:text-right">
|
||||
{{ filteredComposants.length }} / {{ composantsTotal }} résultat{{ filteredComposants.length > 1 ? 's' : '' }}
|
||||
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center">
|
||||
<label class="w-full sm:w-72">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-base-content/70">Recherche</span>
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
type="text"
|
||||
class="input input-bordered input-sm w-full mt-1"
|
||||
placeholder="Nom, référence ou catégorie…"
|
||||
/>
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<label
|
||||
class="text-xs font-semibold uppercase tracking-wide text-base-content/70"
|
||||
for="component-catalog-sort"
|
||||
>
|
||||
Trier par
|
||||
</label>
|
||||
<select
|
||||
id="component-catalog-sort"
|
||||
v-model="sortField"
|
||||
class="select select-bordered select-sm"
|
||||
>
|
||||
<option value="name">Nom</option>
|
||||
<option value="createdAt">Date de création</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label
|
||||
class="text-xs font-semibold uppercase tracking-wide text-base-content/70"
|
||||
for="component-catalog-dir"
|
||||
>
|
||||
Ordre
|
||||
</label>
|
||||
<select
|
||||
id="component-catalog-dir"
|
||||
v-model="sortDirection"
|
||||
class="select select-bordered select-sm"
|
||||
>
|
||||
<option value="asc">Ascendant</option>
|
||||
<option value="desc">Descendant</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-base-content/50 lg:text-right">
|
||||
{{ visibleComposants.length }} / {{ composantsTotal }} résultat{{ visibleComposants.length > 1 ? 's' : '' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -49,7 +83,7 @@
|
||||
Aucun composant n'a encore été créé.
|
||||
</p>
|
||||
|
||||
<p v-else-if="!filteredComposants.length" class="text-sm text-base-content/70">
|
||||
<p v-else-if="!visibleComposants.length" class="text-sm text-base-content/70">
|
||||
Aucun composant ne correspond à votre recherche.
|
||||
</p>
|
||||
|
||||
@@ -64,7 +98,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="component in filteredComposants" :key="component.id">
|
||||
<tr v-for="component in visibleComposants" :key="component.id">
|
||||
<td>{{ component.name || 'Composant sans nom' }}</td>
|
||||
<td>{{ component.typeComposant?.name || '—' }}</td>
|
||||
<td>{{ component.reference || '—' }}</td>
|
||||
@@ -108,20 +142,60 @@ const composantsList = computed(() => composants.value || [])
|
||||
const composantsTotal = computed(() => composantsList.value.length)
|
||||
|
||||
const searchTerm = ref('')
|
||||
const filteredComposants = computed(() => {
|
||||
const term = searchTerm.value.trim().toLowerCase()
|
||||
if (!term) {
|
||||
return composantsList.value
|
||||
const sortField = ref<'name' | 'createdAt'>('name')
|
||||
const sortDirection = ref<'asc' | 'desc'>('asc')
|
||||
|
||||
const resolveComparableName = (component: Record<string, any>) => {
|
||||
const toComparable = (value?: string | null) =>
|
||||
(value ?? '').toString().trim().toLowerCase()
|
||||
|
||||
return (
|
||||
toComparable(component?.name) ||
|
||||
toComparable(component?.reference) ||
|
||||
toComparable(component?.id)
|
||||
)
|
||||
}
|
||||
|
||||
const resolveComparableDate = (component: Record<string, any>) => {
|
||||
const raw = component?.createdAt ?? component?.created_at ?? null
|
||||
if (!raw) {
|
||||
return 0
|
||||
}
|
||||
return composantsList.value.filter((component) => {
|
||||
const name = (component?.name || '').toLowerCase()
|
||||
const reference = (component?.reference || '').toLowerCase()
|
||||
const category = (component?.typeComposant?.name || '').toLowerCase()
|
||||
return (
|
||||
name.includes(term) ||
|
||||
reference.includes(term) ||
|
||||
category.includes(term)
|
||||
)
|
||||
const parsed = new Date(raw).getTime()
|
||||
return Number.isNaN(parsed) ? 0 : parsed
|
||||
}
|
||||
|
||||
const visibleComposants = computed(() => {
|
||||
const term = searchTerm.value.trim().toLowerCase()
|
||||
const source = composantsList.value || []
|
||||
|
||||
const filtered = term
|
||||
? source.filter((component) => {
|
||||
const name = (component?.name || '').toLowerCase()
|
||||
const reference = (component?.reference || '').toLowerCase()
|
||||
const category = (component?.typeComposant?.name || '').toLowerCase()
|
||||
return (
|
||||
name.includes(term) ||
|
||||
reference.includes(term) ||
|
||||
category.includes(term)
|
||||
)
|
||||
})
|
||||
: [...source]
|
||||
|
||||
const direction = sortDirection.value === 'asc' ? 1 : -1
|
||||
|
||||
return filtered.sort((a, b) => {
|
||||
if (sortField.value === 'name') {
|
||||
return (
|
||||
resolveComparableName(a).localeCompare(
|
||||
resolveComparableName(b),
|
||||
'fr',
|
||||
{ sensitivity: 'base' }
|
||||
) * direction
|
||||
)
|
||||
}
|
||||
|
||||
return (resolveComparableDate(a) - resolveComparableDate(b)) * direction
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -25,18 +25,52 @@
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3">
|
||||
<label class="w-full sm:w-64">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-base-content/70">Recherche</span>
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
type="text"
|
||||
class="input input-bordered input-sm w-full mt-1"
|
||||
placeholder="Nom, référence ou catégorie…"
|
||||
/>
|
||||
</label>
|
||||
<p class="text-xs text-base-content/50 sm:text-right">
|
||||
{{ filteredPieces.length }} / {{ piecesTotal }} résultat{{ filteredPieces.length > 1 ? 's' : '' }}
|
||||
<div class="flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:flex-wrap sm:items-center">
|
||||
<label class="w-full sm:w-72">
|
||||
<span class="text-xs font-semibold uppercase tracking-wide text-base-content/70">Recherche</span>
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
type="text"
|
||||
class="input input-bordered input-sm w-full mt-1"
|
||||
placeholder="Nom, référence ou catégorie…"
|
||||
/>
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<label
|
||||
class="text-xs font-semibold uppercase tracking-wide text-base-content/70"
|
||||
for="piece-catalog-sort"
|
||||
>
|
||||
Trier par
|
||||
</label>
|
||||
<select
|
||||
id="piece-catalog-sort"
|
||||
v-model="sortField"
|
||||
class="select select-bordered select-sm"
|
||||
>
|
||||
<option value="name">Nom</option>
|
||||
<option value="createdAt">Date de création</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<label
|
||||
class="text-xs font-semibold uppercase tracking-wide text-base-content/70"
|
||||
for="piece-catalog-dir"
|
||||
>
|
||||
Ordre
|
||||
</label>
|
||||
<select
|
||||
id="piece-catalog-dir"
|
||||
v-model="sortDirection"
|
||||
class="select select-bordered select-sm"
|
||||
>
|
||||
<option value="asc">Ascendant</option>
|
||||
<option value="desc">Descendant</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-base-content/50 lg:text-right">
|
||||
{{ visiblePieces.length }} / {{ piecesTotal }} résultat{{ visiblePieces.length > 1 ? 's' : '' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +82,7 @@
|
||||
Aucune pièce n'a encore été créée.
|
||||
</p>
|
||||
|
||||
<p v-else-if="!filteredPieces.length" class="text-sm text-base-content/70">
|
||||
<p v-else-if="!visiblePieces.length" class="text-sm text-base-content/70">
|
||||
Aucune pièce ne correspond à votre recherche.
|
||||
</p>
|
||||
|
||||
@@ -63,7 +97,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="piece in filteredPieces" :key="piece.id">
|
||||
<tr v-for="piece in visiblePieces" :key="piece.id">
|
||||
<td>{{ piece.name || 'Pièce sans nom' }}</td>
|
||||
<td>{{ piece.typePiece?.name || '—' }}</td>
|
||||
<td>{{ piece.reference || '—' }}</td>
|
||||
@@ -107,20 +141,60 @@ const piecesList = computed(() => pieces.value || [])
|
||||
const piecesTotal = computed(() => piecesList.value.length)
|
||||
|
||||
const searchTerm = ref('')
|
||||
const filteredPieces = computed(() => {
|
||||
const term = searchTerm.value.trim().toLowerCase()
|
||||
if (!term) {
|
||||
return piecesList.value
|
||||
const sortField = ref<'name' | 'createdAt'>('name')
|
||||
const sortDirection = ref<'asc' | 'desc'>('asc')
|
||||
|
||||
const resolveComparableName = (piece: Record<string, any>) => {
|
||||
const normalise = (value?: string | null) =>
|
||||
(value ?? '').toString().trim().toLowerCase()
|
||||
|
||||
return (
|
||||
normalise(piece?.name) ||
|
||||
normalise(piece?.reference) ||
|
||||
normalise(piece?.id)
|
||||
)
|
||||
}
|
||||
|
||||
const resolveComparableDate = (piece: Record<string, any>) => {
|
||||
const raw = piece?.createdAt ?? piece?.created_at ?? null
|
||||
if (!raw) {
|
||||
return 0
|
||||
}
|
||||
return piecesList.value.filter((piece) => {
|
||||
const name = (piece?.name || '').toLowerCase()
|
||||
const reference = (piece?.reference || '').toLowerCase()
|
||||
const category = (piece?.typePiece?.name || '').toLowerCase()
|
||||
return (
|
||||
name.includes(term) ||
|
||||
reference.includes(term) ||
|
||||
category.includes(term)
|
||||
)
|
||||
const timestamp = new Date(raw).getTime()
|
||||
return Number.isNaN(timestamp) ? 0 : timestamp
|
||||
}
|
||||
|
||||
const visiblePieces = computed(() => {
|
||||
const term = searchTerm.value.trim().toLowerCase()
|
||||
const source = piecesList.value || []
|
||||
|
||||
const filtered = term
|
||||
? source.filter((piece) => {
|
||||
const name = (piece?.name || '').toLowerCase()
|
||||
const reference = (piece?.reference || '').toLowerCase()
|
||||
const category = (piece?.typePiece?.name || '').toLowerCase()
|
||||
return (
|
||||
name.includes(term) ||
|
||||
reference.includes(term) ||
|
||||
category.includes(term)
|
||||
)
|
||||
})
|
||||
: [...source]
|
||||
|
||||
const direction = sortDirection.value === 'asc' ? 1 : -1
|
||||
|
||||
return filtered.sort((a, b) => {
|
||||
if (sortField.value === 'name') {
|
||||
return (
|
||||
resolveComparableName(a).localeCompare(
|
||||
resolveComparableName(b),
|
||||
'fr',
|
||||
{ sensitivity: 'base' }
|
||||
) * direction
|
||||
)
|
||||
}
|
||||
|
||||
return (resolveComparableDate(a) - resolveComparableDate(b)) * direction
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user