PieceSelect, ProductSelect and ComposantSelect were loading up to 200 items then filtering client-side by typeId. If the matching items were not in the first 200, the dropdown appeared empty. Now each select component uses API Platform filters (typePiece, typeProduct, typeComposant) to fetch only relevant items server-side, with local state to avoid overwriting the global catalog cache. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
275 lines
8.5 KiB
TypeScript
275 lines
8.5 KiB
TypeScript
import { ref } from 'vue'
|
|
import { useToast } from './useToast'
|
|
import { useApi } from './useApi'
|
|
import { buildConstructeurRequestPayload, uniqueConstructeurIds } from '~/shared/constructeurUtils'
|
|
import { useConstructeurs, type Constructeur } from './useConstructeurs'
|
|
import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations'
|
|
import { extractCollection } from '~/shared/utils/apiHelpers'
|
|
|
|
export interface Composant {
|
|
id: string
|
|
name: string
|
|
reference?: string | null
|
|
description?: string | null
|
|
typeComposantId?: string | null
|
|
typeComposant?: { id: string; name?: string } | null
|
|
productId?: string | null
|
|
product?: { id: string; name?: string } | null
|
|
constructeurs?: Constructeur[]
|
|
constructeurIds?: string[]
|
|
documents?: unknown[]
|
|
createdAt?: string | null
|
|
updatedAt?: string | null
|
|
[key: string]: unknown
|
|
}
|
|
|
|
interface ComposantListResult {
|
|
success: boolean
|
|
data?: { items: Composant[]; total: number; page: number; itemsPerPage: number }
|
|
error?: string
|
|
}
|
|
|
|
interface ComposantSingleResult {
|
|
success: boolean
|
|
data?: Composant
|
|
error?: string
|
|
}
|
|
|
|
interface LoadComposantsOptions {
|
|
search?: string
|
|
page?: number
|
|
itemsPerPage?: number
|
|
orderBy?: string
|
|
orderDir?: 'asc' | 'desc'
|
|
typeName?: string
|
|
typeComposantId?: string
|
|
force?: boolean
|
|
}
|
|
|
|
const composants = ref<Composant[]>([])
|
|
const total = ref(0)
|
|
const loading = ref(false)
|
|
const loaded = ref(false)
|
|
|
|
const extractTotal = (payload: unknown, fallbackLength: number): number => {
|
|
const p = payload as Record<string, unknown> | null
|
|
if (typeof p?.totalItems === 'number') {
|
|
return p.totalItems
|
|
}
|
|
if (typeof p?.['hydra:totalItems'] === 'number') {
|
|
return p['hydra:totalItems']
|
|
}
|
|
return fallbackLength
|
|
}
|
|
|
|
export function useComposants() {
|
|
const { showSuccess } = useToast()
|
|
const { get, post, patch, delete: del } = useApi()
|
|
const { ensureConstructeurs } = useConstructeurs()
|
|
|
|
const withResolvedConstructeurs = async (composant: Composant): Promise<Composant> => {
|
|
if (!composant || typeof composant !== 'object') {
|
|
return composant
|
|
}
|
|
if (!composant.typeComposantId) {
|
|
const typeComposantId = extractRelationId(composant.typeComposant)
|
|
if (typeComposantId) {
|
|
composant.typeComposantId = typeComposantId
|
|
}
|
|
}
|
|
if (!composant.productId) {
|
|
const productId = extractRelationId(composant.product)
|
|
if (productId) {
|
|
composant.productId = productId
|
|
}
|
|
}
|
|
const ids = uniqueConstructeurIds(
|
|
composant.constructeurIds,
|
|
composant.constructeurs,
|
|
)
|
|
const hasResolvedConstructeurs =
|
|
Array.isArray(composant.constructeurs) &&
|
|
composant.constructeurs.length > 0 &&
|
|
composant.constructeurs.every((item) => item && typeof item === 'object')
|
|
|
|
if (ids.length && !hasResolvedConstructeurs) {
|
|
const resolved = await ensureConstructeurs(ids)
|
|
if (resolved.length) {
|
|
composant.constructeurs = resolved
|
|
composant.constructeurIds = ids
|
|
}
|
|
}
|
|
return composant
|
|
}
|
|
|
|
const loadComposants = async (options: LoadComposantsOptions = {}): Promise<ComposantListResult> => {
|
|
const {
|
|
search = '',
|
|
page = 1,
|
|
itemsPerPage = 30,
|
|
orderBy = 'name',
|
|
orderDir = 'asc',
|
|
typeName,
|
|
typeComposantId,
|
|
force = false,
|
|
} = options
|
|
|
|
if (!force && loaded.value && !search && !typeName && !typeComposantId && page === 1) {
|
|
return {
|
|
success: true,
|
|
data: { items: composants.value, total: total.value, page, itemsPerPage },
|
|
}
|
|
}
|
|
|
|
if (!typeComposantId && loading.value) {
|
|
return {
|
|
success: true,
|
|
data: { items: composants.value, total: total.value, page, itemsPerPage },
|
|
}
|
|
}
|
|
|
|
loading.value = true
|
|
try {
|
|
const params = new URLSearchParams()
|
|
params.set('itemsPerPage', String(itemsPerPage))
|
|
params.set('page', String(page))
|
|
|
|
if (search && search.trim()) {
|
|
params.set('name', search.trim())
|
|
}
|
|
|
|
if (typeName && typeName.trim()) {
|
|
params.set('typeComposant.name', typeName.trim())
|
|
}
|
|
|
|
if (typeComposantId) {
|
|
params.set('typeComposant', typeComposantId)
|
|
}
|
|
|
|
params.set(`order[${orderBy}]`, orderDir)
|
|
|
|
const result = await get(`/composants?${params.toString()}`)
|
|
if (result.success) {
|
|
const items = extractCollection(result.data)
|
|
const enrichedItems = await Promise.all(items.map((item) => withResolvedConstructeurs(item)))
|
|
const resultTotal = extractTotal(result.data, items.length)
|
|
|
|
if (!typeComposantId) {
|
|
composants.value = enrichedItems
|
|
total.value = resultTotal
|
|
loaded.value = true
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
data: {
|
|
items: enrichedItems,
|
|
total: resultTotal,
|
|
page,
|
|
itemsPerPage,
|
|
},
|
|
}
|
|
}
|
|
return result as ComposantListResult
|
|
} catch (error) {
|
|
console.error('Erreur lors du chargement des composants:', error)
|
|
return { success: false, error: (error as Error).message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const createComposant = async (composantData: Partial<Composant>): Promise<ComposantSingleResult> => {
|
|
loading.value = true
|
|
try {
|
|
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(composantData))
|
|
const result = await post('/composants', normalizedPayload)
|
|
if (result.success && result.data) {
|
|
const enriched = await withResolvedConstructeurs(result.data as Composant)
|
|
composants.value.unshift(enriched)
|
|
total.value += 1
|
|
const definition = (composantData as Record<string, unknown>)?.definition as Record<string, unknown> | undefined
|
|
const displayName =
|
|
(result.data as Composant)?.name ||
|
|
(definition?.name as string | undefined) ||
|
|
composantData?.name ||
|
|
'Composant'
|
|
showSuccess(`Composant "${displayName}" créé avec succès`)
|
|
return { success: true, data: enriched }
|
|
}
|
|
return { success: false, error: result.error }
|
|
} catch (error) {
|
|
console.error('Erreur lors de la création du composant:', error)
|
|
return { success: false, error: (error as Error).message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const updateComposantData = async (id: string, composantData: Partial<Composant>): Promise<ComposantSingleResult> => {
|
|
loading.value = true
|
|
try {
|
|
const normalizedPayload = normalizeRelationIds(buildConstructeurRequestPayload(composantData))
|
|
const result = await patch(`/composants/${id}`, normalizedPayload)
|
|
if (result.success && result.data) {
|
|
const updated = await withResolvedConstructeurs(result.data as Composant)
|
|
const index = composants.value.findIndex((comp) => comp.id === id)
|
|
if (index !== -1) {
|
|
composants.value[index] = updated
|
|
}
|
|
showSuccess(`Composant "${updated?.name || composantData.name || ''}" mis à jour avec succès`)
|
|
return { success: true, data: updated }
|
|
}
|
|
return { success: false, error: result.error }
|
|
} catch (error) {
|
|
console.error('Erreur lors de la mise à jour du composant:', error)
|
|
return { success: false, error: (error as Error).message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const deleteComposant = async (id: string): Promise<ComposantSingleResult> => {
|
|
loading.value = true
|
|
try {
|
|
const result = await del(`/composants/${id}`)
|
|
if (result.success) {
|
|
const deletedComposant = composants.value.find((comp) => comp.id === id)
|
|
composants.value = composants.value.filter((comp) => comp.id !== id)
|
|
total.value = Math.max(0, total.value - 1)
|
|
showSuccess(`Composant "${deletedComposant?.name || 'inconnu'}" supprimé avec succès`)
|
|
return { success: true }
|
|
}
|
|
return { success: false, error: result.error }
|
|
} catch (error) {
|
|
console.error('Erreur lors de la suppression du composant:', error)
|
|
return { success: false, error: (error as Error).message }
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
const getComposants = () => composants.value
|
|
const isLoading = () => loading.value
|
|
|
|
const clearComposantsCache = () => {
|
|
composants.value = []
|
|
total.value = 0
|
|
loaded.value = false
|
|
}
|
|
|
|
return {
|
|
composants,
|
|
total,
|
|
loading,
|
|
loaded,
|
|
loadComposants,
|
|
createComposant,
|
|
updateComposant: updateComposantData,
|
|
deleteComposant,
|
|
getComposants,
|
|
isLoading,
|
|
clearComposantsCache,
|
|
}
|
|
}
|