feat(ui) : enrich category related items modal with machine counts and navigation links
This commit is contained in:
@@ -31,16 +31,28 @@
|
||||
:key="entry.id"
|
||||
class="px-2 py-1"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full flex-col gap-1 rounded-lg px-2 py-2 text-left hover:bg-base-200 focus:bg-base-200 focus:outline-none"
|
||||
@click="onOpenEdit(entry)"
|
||||
<div
|
||||
class="flex w-full items-center justify-between gap-2 rounded-lg px-2 py-2 hover:bg-base-200"
|
||||
>
|
||||
<span class="font-medium text-base-content">{{ entry.name }}</span>
|
||||
<span v-if="entry.reference" class="text-xs text-base-content/60">
|
||||
Référence: {{ entry.reference }}
|
||||
</span>
|
||||
</button>
|
||||
<div class="flex min-w-0 flex-col gap-0.5">
|
||||
<NuxtLink
|
||||
:to="itemDetailPath(entry)"
|
||||
class="font-medium hover:underline hover:text-primary transition-colors"
|
||||
@click="emit('close')"
|
||||
>
|
||||
{{ entry.name }}
|
||||
</NuxtLink>
|
||||
<span v-if="entry.reference" class="text-xs text-base-content/60">
|
||||
Référence: {{ entry.reference }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="shrink-0">
|
||||
<span v-if="entry.machineCount > 0" class="badge badge-ghost badge-sm">
|
||||
{{ entry.machineCount }} machine{{ entry.machineCount > 1 ? 's' : '' }}
|
||||
</span>
|
||||
<span v-else class="text-xs text-base-content/30">Aucune machine</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -57,14 +69,13 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import { useApi } from '~/composables/useApi'
|
||||
import { extractCollection } from '~/shared/utils/apiHelpers'
|
||||
import { humanizeError } from '~/shared/utils/errorMessages'
|
||||
import type { ModelCategory, ModelType } from '~/services/modelTypes'
|
||||
|
||||
type RelatedEntry = {
|
||||
id: string
|
||||
name: string
|
||||
reference?: string | null
|
||||
machineCount: number
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -104,73 +115,37 @@ const modalSubtitle = computed(() => {
|
||||
return `${count} ${labels.plural} liés.`
|
||||
})
|
||||
|
||||
const resolveRelatedConfig = (category: ModelCategory) => {
|
||||
if (category === 'COMPONENT') return { endpoint: '/composants', filterKey: 'typeComposant' }
|
||||
if (category === 'PIECE') return { endpoint: '/pieces', filterKey: 'typePiece' }
|
||||
return { endpoint: '/products', filterKey: 'typeProduct' }
|
||||
}
|
||||
|
||||
const mapRelatedEntry = (item: unknown): RelatedEntry | null => {
|
||||
if (!item || typeof item !== 'object') return null
|
||||
const record = item as Record<string, unknown>
|
||||
if (typeof record.id !== 'string') return null
|
||||
const name = typeof record.name === 'string' && record.name.trim() ? record.name : 'Sans nom'
|
||||
const reference
|
||||
= typeof record.reference === 'string' && record.reference.trim()
|
||||
? record.reference
|
||||
: typeof record.code === 'string' && record.code.trim()
|
||||
? record.code
|
||||
: null
|
||||
return { id: record.id, name, reference }
|
||||
const itemDetailPath = (item: RelatedEntry) => {
|
||||
if (!props.modelType) return '#'
|
||||
const category = props.modelType.category
|
||||
if (category === 'COMPONENT') return `/component/${item.id}`
|
||||
if (category === 'PIECE') return `/piece/${item.id}`
|
||||
return `/product/${item.id}`
|
||||
}
|
||||
|
||||
const loadRelatedItems = async (modelType: ModelType) => {
|
||||
const { endpoint, filterKey } = resolveRelatedConfig(modelType.category)
|
||||
const params = new URLSearchParams()
|
||||
params.set('itemsPerPage', '200')
|
||||
params.set(filterKey, `/api/model_types/${modelType.id}`)
|
||||
params.set('order[name]', 'asc')
|
||||
|
||||
loading.value = true
|
||||
error.value = null
|
||||
items.value = []
|
||||
|
||||
try {
|
||||
const result = await get(`${endpoint}?${params.toString()}`)
|
||||
const result = await get(`/model_types/${modelType.id}/related-items`)
|
||||
if (!result.success) {
|
||||
error.value = result.error ?? 'Impossible de charger les éléments liés.'
|
||||
return
|
||||
}
|
||||
const collection = extractCollection(result.data)
|
||||
items.value = collection
|
||||
.map(mapRelatedEntry)
|
||||
.filter((entry): entry is RelatedEntry => Boolean(entry))
|
||||
}
|
||||
catch (err) {
|
||||
let raw: string | null = null
|
||||
if (err && typeof err === 'object') {
|
||||
const e = err as { data?: Record<string, unknown>, statusMessage?: string, message?: string }
|
||||
if (e.data) {
|
||||
const data = e.data
|
||||
if (typeof data['hydra:description'] === 'string') raw = data['hydra:description']
|
||||
else if (typeof data.detail === 'string') raw = data.detail
|
||||
else if (typeof data.message === 'string') raw = data.message
|
||||
else if (typeof data.error === 'string') raw = data.error
|
||||
}
|
||||
if (!raw && typeof e.statusMessage === 'string') raw = e.statusMessage
|
||||
if (!raw && typeof e.message === 'string') raw = e.message
|
||||
if (Array.isArray(result.data)) {
|
||||
items.value = result.data as RelatedEntry[]
|
||||
}
|
||||
error.value = humanizeError(raw)
|
||||
}
|
||||
catch {
|
||||
error.value = 'Impossible de charger les éléments liés.'
|
||||
}
|
||||
finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const onOpenEdit = (entry: RelatedEntry) => {
|
||||
emit('open-edit', entry)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.open,
|
||||
(isOpen) => {
|
||||
|
||||
Reference in New Issue
Block a user