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"
|
:key="entry.id"
|
||||||
class="px-2 py-1"
|
class="px-2 py-1"
|
||||||
>
|
>
|
||||||
<button
|
<div
|
||||||
type="button"
|
class="flex w-full items-center justify-between gap-2 rounded-lg px-2 py-2 hover:bg-base-200"
|
||||||
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)"
|
|
||||||
>
|
>
|
||||||
<span class="font-medium text-base-content">{{ entry.name }}</span>
|
<div class="flex min-w-0 flex-col gap-0.5">
|
||||||
<span v-if="entry.reference" class="text-xs text-base-content/60">
|
<NuxtLink
|
||||||
Référence: {{ entry.reference }}
|
:to="itemDetailPath(entry)"
|
||||||
</span>
|
class="font-medium hover:underline hover:text-primary transition-colors"
|
||||||
</button>
|
@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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -57,14 +69,13 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from 'vue'
|
import { computed, ref, watch } from 'vue'
|
||||||
import { useApi } from '~/composables/useApi'
|
import { useApi } from '~/composables/useApi'
|
||||||
import { extractCollection } from '~/shared/utils/apiHelpers'
|
|
||||||
import { humanizeError } from '~/shared/utils/errorMessages'
|
|
||||||
import type { ModelCategory, ModelType } from '~/services/modelTypes'
|
import type { ModelCategory, ModelType } from '~/services/modelTypes'
|
||||||
|
|
||||||
type RelatedEntry = {
|
type RelatedEntry = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
reference?: string | null
|
reference?: string | null
|
||||||
|
machineCount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@@ -104,73 +115,37 @@ const modalSubtitle = computed(() => {
|
|||||||
return `${count} ${labels.plural} liés.`
|
return `${count} ${labels.plural} liés.`
|
||||||
})
|
})
|
||||||
|
|
||||||
const resolveRelatedConfig = (category: ModelCategory) => {
|
const itemDetailPath = (item: RelatedEntry) => {
|
||||||
if (category === 'COMPONENT') return { endpoint: '/composants', filterKey: 'typeComposant' }
|
if (!props.modelType) return '#'
|
||||||
if (category === 'PIECE') return { endpoint: '/pieces', filterKey: 'typePiece' }
|
const category = props.modelType.category
|
||||||
return { endpoint: '/products', filterKey: 'typeProduct' }
|
if (category === 'COMPONENT') return `/component/${item.id}`
|
||||||
}
|
if (category === 'PIECE') return `/piece/${item.id}`
|
||||||
|
return `/product/${item.id}`
|
||||||
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 loadRelatedItems = async (modelType: ModelType) => {
|
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
|
loading.value = true
|
||||||
error.value = null
|
error.value = null
|
||||||
items.value = []
|
items.value = []
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await get(`${endpoint}?${params.toString()}`)
|
const result = await get(`/model_types/${modelType.id}/related-items`)
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
error.value = result.error ?? 'Impossible de charger les éléments liés.'
|
error.value = result.error ?? 'Impossible de charger les éléments liés.'
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
const collection = extractCollection(result.data)
|
if (Array.isArray(result.data)) {
|
||||||
items.value = collection
|
items.value = result.data as RelatedEntry[]
|
||||||
.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
|
|
||||||
}
|
}
|
||||||
error.value = humanizeError(raw)
|
}
|
||||||
|
catch {
|
||||||
|
error.value = 'Impossible de charger les éléments liés.'
|
||||||
}
|
}
|
||||||
finally {
|
finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const onOpenEdit = (entry: RelatedEntry) => {
|
|
||||||
emit('open-edit', entry)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.open,
|
() => props.open,
|
||||||
(isOpen) => {
|
(isOpen) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user