Consolidate create and edit pages into single create pages with edit mode support. Remove obsolete catalog pages, history composables, and fix remaining code review issues. Include migration to relink orphaned custom fields. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
266 lines
8.3 KiB
TypeScript
266 lines
8.3 KiB
TypeScript
import { ref } from 'vue'
|
|
import { useToast } from './useToast'
|
|
import { useApi } from './useApi'
|
|
import { uniqueConstructeurIds } from '~/shared/constructeurUtils'
|
|
import { useConstructeurs, type Constructeur } from './useConstructeurs'
|
|
import { extractRelationId, normalizeRelationIds } from '~/shared/apiRelations'
|
|
import { extractCollection, extractTotal } 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)
|
|
|
|
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('search', 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 { constructeurIds, constructeurs, constructeurId, constructeur, ...cleanPayload } = composantData as any
|
|
const normalizedPayload = normalizeRelationIds(cleanPayload)
|
|
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 { constructeurIds, constructeurs, constructeurId, constructeur, ...cleanPayload } = composantData as any
|
|
const normalizedPayload = normalizeRelationIds(cleanPayload)
|
|
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,
|
|
}
|
|
}
|