feat(ui): ajoute la pagination et la recherche serveur
This commit is contained in:
@@ -35,6 +35,7 @@
|
||||
type="text"
|
||||
class="input input-bordered input-sm w-full mt-1"
|
||||
placeholder="Nom ou référence…"
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -48,6 +49,7 @@
|
||||
id="component-catalog-sort"
|
||||
v-model="sortField"
|
||||
class="select select-bordered select-sm"
|
||||
@change="handleSortChange"
|
||||
>
|
||||
<option value="name">Nom</option>
|
||||
<option value="createdAt">Date de création</option>
|
||||
@@ -64,14 +66,33 @@
|
||||
id="component-catalog-dir"
|
||||
v-model="sortDirection"
|
||||
class="select select-bordered select-sm"
|
||||
@change="handleSortChange"
|
||||
>
|
||||
<option value="asc">Ascendant</option>
|
||||
<option value="desc">Descendant</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-per-page"
|
||||
>
|
||||
Par page
|
||||
</label>
|
||||
<select
|
||||
id="component-catalog-per-page"
|
||||
v-model.number="itemsPerPage"
|
||||
class="select select-bordered select-sm"
|
||||
@change="handlePerPageChange"
|
||||
>
|
||||
<option :value="20">20</option>
|
||||
<option :value="50">50</option>
|
||||
<option :value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-base-content/50 lg:text-right">
|
||||
{{ visibleComposants.length }} / {{ composantsTotal }} résultat{{ visibleComposants.length > 1 ? 's' : '' }}
|
||||
{{ composantsOnPage }} / {{ composantsTotal }} résultat{{ composantsTotal > 1 ? 's' : '' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -83,54 +104,62 @@
|
||||
Aucun composant n'a encore été créé.
|
||||
</p>
|
||||
|
||||
<p v-else-if="!visibleComposants.length" class="text-sm text-base-content/70">
|
||||
<p v-else-if="!composantsList.length" class="text-sm text-base-content/70">
|
||||
Aucun composant ne correspond à votre recherche.
|
||||
</p>
|
||||
|
||||
<div v-else class="overflow-x-auto">
|
||||
<table class="table table-sm md:table-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-24">Aperçu</th>
|
||||
<th>Nom</th>
|
||||
<th>Référence</th>
|
||||
<th>Type de composant</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="component in visibleComposants" :key="component.id">
|
||||
<td class="align-middle">
|
||||
<DocumentThumbnail
|
||||
:document="resolvePrimaryDocument(component)"
|
||||
:alt="resolvePreviewAlt(component)"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ component.name || 'Composant sans nom' }}</td>
|
||||
<td>{{ component.reference || '—' }}</td>
|
||||
<td>{{ resolveComponentType(component) }}</td>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
<NuxtLink
|
||||
:to="`/component/${component.id}/edit`"
|
||||
class="btn btn-ghost btn-xs"
|
||||
>
|
||||
Modifier
|
||||
</NuxtLink>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-error btn-xs"
|
||||
:disabled="loadingComposants"
|
||||
@click="handleDeleteComponent(component)"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<template v-else>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-sm md:table-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-24">Aperçu</th>
|
||||
<th>Nom</th>
|
||||
<th>Référence</th>
|
||||
<th>Type de composant</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="component in composantsList" :key="component.id">
|
||||
<td class="align-middle">
|
||||
<DocumentThumbnail
|
||||
:document="resolvePrimaryDocument(component)"
|
||||
:alt="resolvePreviewAlt(component)"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ component.name || 'Composant sans nom' }}</td>
|
||||
<td>{{ component.reference || '—' }}</td>
|
||||
<td>{{ resolveComponentType(component) }}</td>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
<NuxtLink
|
||||
:to="`/component/${component.id}/edit`"
|
||||
class="btn btn-ghost btn-xs"
|
||||
>
|
||||
Modifier
|
||||
</NuxtLink>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-error btn-xs"
|
||||
:disabled="loadingComposants"
|
||||
@click="handleDeleteComponent(component)"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
:current-page="currentPage"
|
||||
:total-pages="totalPages"
|
||||
@update:current-page="handlePageChange"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
@@ -144,13 +173,41 @@ import { useComponentTypes } from '~/composables/useComponentTypes'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
import { usePersistedSort } from '~/composables/usePersistedSort'
|
||||
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
||||
import Pagination from '~/components/common/Pagination.vue'
|
||||
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
||||
|
||||
const { showError } = useToast()
|
||||
const { composants, loadComposants, loading: loadingComposantsRef, deleteComposant } = useComposants()
|
||||
const { composants, total, loadComposants, loading: loadingComposantsRef, deleteComposant } = useComposants()
|
||||
const { componentTypes, loadComponentTypes } = useComponentTypes()
|
||||
const loadingComposants = computed(() => loadingComposantsRef.value)
|
||||
|
||||
// Pagination state
|
||||
const currentPage = ref(1)
|
||||
const itemsPerPage = ref(30)
|
||||
const composantsTotal = computed(() => total.value)
|
||||
const composantsOnPage = computed(() => composants.value.length)
|
||||
const totalPages = computed(() => Math.ceil(composantsTotal.value / itemsPerPage.value) || 1)
|
||||
|
||||
// Search state with debounce
|
||||
const searchTerm = ref('')
|
||||
let searchTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const debouncedSearch = () => {
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout)
|
||||
}
|
||||
searchTimeout = setTimeout(() => {
|
||||
currentPage.value = 1
|
||||
fetchComposants()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// Sort state
|
||||
const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>(
|
||||
'component-catalog',
|
||||
{ field: 'name', direction: 'asc' },
|
||||
)
|
||||
|
||||
// Enrichir les composants avec les types de composants complets
|
||||
const composantsList = computed(() => {
|
||||
return (composants.value || []).map((composant) => {
|
||||
@@ -161,13 +218,31 @@ const composantsList = computed(() => {
|
||||
}
|
||||
})
|
||||
})
|
||||
const composantsTotal = computed(() => composantsList.value.length)
|
||||
|
||||
const searchTerm = ref('')
|
||||
const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>(
|
||||
'component-catalog',
|
||||
{ field: 'name', direction: 'asc' },
|
||||
)
|
||||
const fetchComposants = async () => {
|
||||
await loadComposants({
|
||||
search: searchTerm.value,
|
||||
page: currentPage.value,
|
||||
itemsPerPage: itemsPerPage.value,
|
||||
orderBy: sortField.value,
|
||||
orderDir: sortDirection.value
|
||||
})
|
||||
}
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
currentPage.value = page
|
||||
fetchComposants()
|
||||
}
|
||||
|
||||
const handleSortChange = () => {
|
||||
currentPage.value = 1
|
||||
fetchComposants()
|
||||
}
|
||||
|
||||
const handlePerPageChange = () => {
|
||||
currentPage.value = 1
|
||||
fetchComposants()
|
||||
}
|
||||
|
||||
const resolvePrimaryDocument = (component: Record<string, any>) => {
|
||||
const documents = Array.isArray(component?.documents) ? component.documents : []
|
||||
@@ -230,58 +305,6 @@ const resolveDeleteGuard = (component: Record<string, any>) => {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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()
|
||||
return (
|
||||
name.includes(term) ||
|
||||
reference.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
|
||||
})
|
||||
})
|
||||
|
||||
const handleDeleteComponent = async (component: Record<string, any>) => {
|
||||
const { blockingReasons, hasCustomFields } = resolveDeleteGuard(component)
|
||||
|
||||
@@ -310,11 +333,13 @@ const handleDeleteComponent = async (component: Record<string, any>) => {
|
||||
}
|
||||
|
||||
await deleteComposant(component.id)
|
||||
// Reload current page after deletion
|
||||
fetchComposants()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
loadComposants(),
|
||||
fetchComposants(),
|
||||
loadComponentTypes()
|
||||
])
|
||||
})
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
type="text"
|
||||
class="input input-bordered input-sm w-full mt-1"
|
||||
placeholder="Nom ou référence…"
|
||||
@input="debouncedSearch"
|
||||
/>
|
||||
</label>
|
||||
<div class="flex items-center gap-2">
|
||||
@@ -47,6 +48,7 @@
|
||||
id="piece-catalog-sort"
|
||||
v-model="sortField"
|
||||
class="select select-bordered select-sm"
|
||||
@change="handleSortChange"
|
||||
>
|
||||
<option value="name">Nom</option>
|
||||
<option value="createdAt">Date de création</option>
|
||||
@@ -63,14 +65,33 @@
|
||||
id="piece-catalog-dir"
|
||||
v-model="sortDirection"
|
||||
class="select select-bordered select-sm"
|
||||
@change="handleSortChange"
|
||||
>
|
||||
<option value="asc">Ascendant</option>
|
||||
<option value="desc">Descendant</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-per-page"
|
||||
>
|
||||
Par page
|
||||
</label>
|
||||
<select
|
||||
id="piece-catalog-per-page"
|
||||
v-model.number="itemsPerPage"
|
||||
class="select select-bordered select-sm"
|
||||
@change="handlePerPageChange"
|
||||
>
|
||||
<option :value="20">20</option>
|
||||
<option :value="50">50</option>
|
||||
<option :value="100">100</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-xs text-base-content/50 lg:text-right">
|
||||
{{ visiblePieces.length }} / {{ piecesTotal }} résultat{{ visiblePieces.length > 1 ? 's' : '' }}
|
||||
{{ piecesOnPage }} / {{ piecesTotal }} résultat{{ piecesTotal > 1 ? 's' : '' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -82,77 +103,85 @@
|
||||
Aucune pièce n'a encore été créée.
|
||||
</p>
|
||||
|
||||
<p v-else-if="!visiblePieces.length" class="text-sm text-base-content/70">
|
||||
<p v-else-if="!piecesList.length" class="text-sm text-base-content/70">
|
||||
Aucune pièce ne correspond à votre recherche.
|
||||
</p>
|
||||
|
||||
<div v-else class="overflow-x-auto">
|
||||
<table class="table table-sm md:table-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-24">Aperçu</th>
|
||||
<th>Nom</th>
|
||||
<th>Référence</th>
|
||||
<th>Fournisseurs</th>
|
||||
<th>Type de pièce</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in pieceRows" :key="row.piece.id">
|
||||
<td class="align-middle">
|
||||
<DocumentThumbnail
|
||||
:document="resolvePrimaryDocument(row.piece)"
|
||||
:alt="resolvePreviewAlt(row.piece)"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ row.piece.name || 'Pièce sans nom' }}</td>
|
||||
<td>{{ row.piece.reference || '—' }}</td>
|
||||
<td>
|
||||
<div
|
||||
v-if="row.suppliers.visible.length"
|
||||
class="flex max-w-[14rem] flex-wrap items-center gap-1"
|
||||
:title="row.suppliers.tooltip"
|
||||
>
|
||||
<span
|
||||
v-for="supplier in row.suppliers.visible"
|
||||
:key="supplier"
|
||||
class="badge badge-ghost badge-sm whitespace-nowrap"
|
||||
<template v-else>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="table table-sm md:table-md">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="w-24">Aperçu</th>
|
||||
<th>Nom</th>
|
||||
<th>Référence</th>
|
||||
<th>Fournisseurs</th>
|
||||
<th>Type de pièce</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in pieceRows" :key="row.piece.id">
|
||||
<td class="align-middle">
|
||||
<DocumentThumbnail
|
||||
:document="resolvePrimaryDocument(row.piece)"
|
||||
:alt="resolvePreviewAlt(row.piece)"
|
||||
/>
|
||||
</td>
|
||||
<td>{{ row.piece.name || 'Pièce sans nom' }}</td>
|
||||
<td>{{ row.piece.reference || '—' }}</td>
|
||||
<td>
|
||||
<div
|
||||
v-if="row.suppliers.visible.length"
|
||||
class="flex max-w-[14rem] flex-wrap items-center gap-1"
|
||||
:title="row.suppliers.tooltip"
|
||||
>
|
||||
{{ supplier }}
|
||||
</span>
|
||||
<span
|
||||
v-if="row.suppliers.overflow"
|
||||
class="badge badge-outline badge-sm"
|
||||
>
|
||||
+{{ row.suppliers.overflow }}
|
||||
</span>
|
||||
</div>
|
||||
<span v-else>—</span>
|
||||
</td>
|
||||
<td>{{ resolvePieceType(row.piece) }}</td>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
<NuxtLink
|
||||
:to="`/pieces/${row.piece.id}/edit`"
|
||||
class="btn btn-ghost btn-xs"
|
||||
>
|
||||
Modifier
|
||||
</NuxtLink>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-error btn-xs"
|
||||
:disabled="loadingPieces"
|
||||
@click="handleDeletePiece(row.piece)"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<span
|
||||
v-for="supplier in row.suppliers.visible"
|
||||
:key="supplier"
|
||||
class="badge badge-ghost badge-sm whitespace-nowrap"
|
||||
>
|
||||
{{ supplier }}
|
||||
</span>
|
||||
<span
|
||||
v-if="row.suppliers.overflow"
|
||||
class="badge badge-outline badge-sm"
|
||||
>
|
||||
+{{ row.suppliers.overflow }}
|
||||
</span>
|
||||
</div>
|
||||
<span v-else>—</span>
|
||||
</td>
|
||||
<td>{{ resolvePieceType(row.piece) }}</td>
|
||||
<td>
|
||||
<div class="flex items-center gap-2">
|
||||
<NuxtLink
|
||||
:to="`/pieces/${row.piece.id}/edit`"
|
||||
class="btn btn-ghost btn-xs"
|
||||
>
|
||||
Modifier
|
||||
</NuxtLink>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-error btn-xs"
|
||||
:disabled="loadingPieces"
|
||||
@click="handleDeletePiece(row.piece)"
|
||||
>
|
||||
Supprimer
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
:current-page="currentPage"
|
||||
:total-pages="totalPages"
|
||||
@update:current-page="handlePageChange"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
@@ -160,19 +189,47 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { usePieces } from '~/composables/usePieces'
|
||||
import { usePieceTypes } from '~/composables/usePieceTypes'
|
||||
import { useToast } from '~/composables/useToast'
|
||||
import { usePersistedSort } from '~/composables/usePersistedSort'
|
||||
import DocumentThumbnail from '~/components/DocumentThumbnail.vue'
|
||||
import Pagination from '~/components/common/Pagination.vue'
|
||||
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
||||
|
||||
const { showError } = useToast()
|
||||
const { pieces, loadPieces, loading: loadingPiecesRef, deletePiece } = usePieces()
|
||||
const { pieces, total, loadPieces, loading: loadingPiecesRef, deletePiece } = usePieces()
|
||||
const { pieceTypes, loadPieceTypes } = usePieceTypes()
|
||||
const loadingPieces = computed(() => loadingPiecesRef.value)
|
||||
|
||||
// Pagination state
|
||||
const currentPage = ref(1)
|
||||
const itemsPerPage = ref(30)
|
||||
const piecesTotal = computed(() => total.value)
|
||||
const piecesOnPage = computed(() => pieces.value.length)
|
||||
const totalPages = computed(() => Math.ceil(piecesTotal.value / itemsPerPage.value) || 1)
|
||||
|
||||
// Search state with debounce
|
||||
const searchTerm = ref('')
|
||||
let searchTimeout: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const debouncedSearch = () => {
|
||||
if (searchTimeout) {
|
||||
clearTimeout(searchTimeout)
|
||||
}
|
||||
searchTimeout = setTimeout(() => {
|
||||
currentPage.value = 1
|
||||
fetchPieces()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
// Sort state
|
||||
const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>(
|
||||
'pieces-catalog',
|
||||
{ field: 'name', direction: 'asc' },
|
||||
)
|
||||
|
||||
// Enrichir les pièces avec les types de pièces complets
|
||||
const piecesList = computed(() => {
|
||||
return (pieces.value || []).map((piece) => {
|
||||
@@ -183,13 +240,31 @@ const piecesList = computed(() => {
|
||||
}
|
||||
})
|
||||
})
|
||||
const piecesTotal = computed(() => piecesList.value.length)
|
||||
|
||||
const searchTerm = ref('')
|
||||
const { sortField, sortDirection } = usePersistedSort<'name' | 'createdAt', 'asc' | 'desc'>(
|
||||
'pieces-catalog',
|
||||
{ field: 'name', direction: 'asc' },
|
||||
)
|
||||
const fetchPieces = async () => {
|
||||
await loadPieces({
|
||||
search: searchTerm.value,
|
||||
page: currentPage.value,
|
||||
itemsPerPage: itemsPerPage.value,
|
||||
orderBy: sortField.value,
|
||||
orderDir: sortDirection.value
|
||||
})
|
||||
}
|
||||
|
||||
const handlePageChange = (page: number) => {
|
||||
currentPage.value = page
|
||||
fetchPieces()
|
||||
}
|
||||
|
||||
const handleSortChange = () => {
|
||||
currentPage.value = 1
|
||||
fetchPieces()
|
||||
}
|
||||
|
||||
const handlePerPageChange = () => {
|
||||
currentPage.value = 1
|
||||
fetchPieces()
|
||||
}
|
||||
|
||||
const resolvePrimaryDocument = (piece: Record<string, any>) => {
|
||||
const documents = Array.isArray(piece?.documents) ? piece.documents : []
|
||||
@@ -337,60 +412,8 @@ const resolveDeleteGuard = (piece: Record<string, any>) => {
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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()
|
||||
return (
|
||||
name.includes(term) ||
|
||||
reference.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
|
||||
})
|
||||
})
|
||||
|
||||
const pieceRows = computed(() =>
|
||||
visiblePieces.value.map((piece) => ({
|
||||
piecesList.value.map((piece) => ({
|
||||
piece,
|
||||
suppliers: buildPieceSuppliersDisplay(piece),
|
||||
})),
|
||||
@@ -425,11 +448,13 @@ const handleDeletePiece = async (piece: Record<string, any>) => {
|
||||
}
|
||||
|
||||
await deletePiece(piece.id)
|
||||
// Reload current page after deletion
|
||||
fetchPieces()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
loadPieces(),
|
||||
fetchPieces(),
|
||||
loadPieceTypes()
|
||||
])
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user