Files
Inventory/frontend/app/composables/usePieces.ts
r-dev 201485552a fix(ui) : remove legacy edit pages and history composables, unify create/edit forms
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>
2026-04-06 11:19:50 +02:00

280 lines
8.6 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 Piece {
id: string
name: string
reference?: string | null
referenceAuto?: string | null
description?: string | null
typePieceId?: string | null
typePiece?: { id: string; name?: string } | null
productId?: string | null
productIds?: string[]
product?: { id: string; name?: string } | null
constructeurs?: Constructeur[]
constructeurIds?: string[]
documents?: unknown[]
createdAt?: string | null
updatedAt?: string | null
[key: string]: unknown
}
interface PieceListResult {
success: boolean
data?: { items: Piece[]; total: number; page: number; itemsPerPage: number }
error?: string
}
interface PieceSingleResult {
success: boolean
data?: Piece
error?: string
}
interface LoadPiecesOptions {
search?: string
page?: number
itemsPerPage?: number
orderBy?: string
orderDir?: 'asc' | 'desc'
typeName?: string
typePieceId?: string
force?: boolean
}
const pieces = ref<Piece[]>([])
const total = ref(0)
const loading = ref(false)
const loaded = ref(false)
export function usePieces() {
const { showSuccess } = useToast()
const { get, post, patch, delete: del } = useApi()
const { ensureConstructeurs } = useConstructeurs()
const withResolvedConstructeurs = async (piece: Piece): Promise<Piece> => {
if (!piece || typeof piece !== 'object') {
return piece
}
if (!piece.typePieceId) {
const typePieceId = extractRelationId(piece.typePiece)
if (typePieceId) {
piece.typePieceId = typePieceId
}
}
if (!piece.productId) {
const productId = extractRelationId(piece.product)
if (productId) {
piece.productId = productId
}
}
const productIds = Array.isArray(piece.productIds) ? piece.productIds.filter(Boolean) : []
if (productIds.length === 0 && piece.productId) {
piece.productIds = [piece.productId]
} else if (productIds.length > 0) {
piece.productIds = productIds.map((id) => String(id))
if (!piece.productId) {
piece.productId = piece.productIds[0] || null
}
}
const ids = uniqueConstructeurIds(
piece.constructeurIds,
piece.constructeurs,
)
const hasResolvedConstructeurs =
Array.isArray(piece.constructeurs) &&
piece.constructeurs.length > 0 &&
piece.constructeurs.every((item) => item && typeof item === 'object')
if (ids.length && !hasResolvedConstructeurs) {
const resolved = await ensureConstructeurs(ids)
if (resolved.length) {
piece.constructeurs = resolved
piece.constructeurIds = ids
}
}
return piece
}
const loadPieces = async (options: LoadPiecesOptions = {}): Promise<PieceListResult> => {
const {
search = '',
page = 1,
itemsPerPage = 30,
orderBy = 'name',
orderDir = 'asc',
typeName,
typePieceId,
force = false,
} = options
// Only use cache for unfiltered full-catalog loads
if (!force && loaded.value && !search && !typeName && !typePieceId && page === 1) {
return {
success: true,
data: { items: pieces.value, total: total.value, page, itemsPerPage },
}
}
// For filtered queries, don't block on global loading state
if (!typePieceId && loading.value) {
return {
success: true,
data: { items: pieces.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('typePiece.name', typeName.trim())
}
if (typePieceId) {
params.set('typePiece', typePieceId)
}
params.set(`order[${orderBy}]`, orderDir)
const result = await get(`/pieces?${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)
// Only update global cache for unfiltered queries
if (!typePieceId) {
pieces.value = enrichedItems
total.value = resultTotal
loaded.value = true
}
return {
success: true,
data: {
items: enrichedItems,
total: resultTotal,
page,
itemsPerPage,
},
}
}
return result as PieceListResult
} catch (error) {
console.error('Erreur lors du chargement des pièces:', error)
return { success: false, error: (error as Error).message }
} finally {
loading.value = false
}
}
const createPiece = async (pieceData: Partial<Piece>): Promise<PieceSingleResult> => {
loading.value = true
try {
const { constructeurIds, constructeurs, constructeurId, constructeur, ...cleanPayload } = pieceData as any
const normalizedPayload = normalizeRelationIds(cleanPayload)
const result = await post('/pieces', normalizedPayload)
if (result.success && result.data) {
const enriched = await withResolvedConstructeurs(result.data as Piece)
pieces.value.unshift(enriched)
total.value += 1
const definition = (pieceData as Record<string, unknown>)?.definition as Record<string, unknown> | undefined
const displayName =
(result.data as Piece)?.name ||
(definition?.name as string | undefined) ||
pieceData?.name ||
'Pièce'
showSuccess(`Pièce "${displayName}" créée avec succès`)
return { success: true, data: enriched }
}
return { success: false, error: result.error }
} catch (error) {
console.error('Erreur lors de la création de la pièce:', error)
return { success: false, error: (error as Error).message }
} finally {
loading.value = false
}
}
const updatePieceData = async (id: string, pieceData: Partial<Piece>): Promise<PieceSingleResult> => {
loading.value = true
try {
const { constructeurIds, constructeurs, constructeurId, constructeur, ...cleanPayload } = pieceData as any
const normalizedPayload = normalizeRelationIds(cleanPayload)
const result = await patch(`/pieces/${id}`, normalizedPayload)
if (result.success && result.data) {
const updated = await withResolvedConstructeurs(result.data as Piece)
const index = pieces.value.findIndex((piece) => piece.id === id)
if (index !== -1) {
pieces.value[index] = updated
}
showSuccess(`Pièce "${updated?.name || pieceData.name || ''}" mise à 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 de la pièce:', error)
return { success: false, error: (error as Error).message }
} finally {
loading.value = false
}
}
const deletePiece = async (id: string): Promise<PieceSingleResult> => {
loading.value = true
try {
const result = await del(`/pieces/${id}`)
if (result.success) {
const deletedPiece = pieces.value.find((piece) => piece.id === id)
pieces.value = pieces.value.filter((piece) => piece.id !== id)
total.value = Math.max(0, total.value - 1)
showSuccess(`Pièce "${deletedPiece?.name || 'inconnu'}" supprimée avec succès`)
return { success: true }
}
return { success: false, error: result.error }
} catch (error) {
console.error('Erreur lors de la suppression de la pièce:', error)
return { success: false, error: (error as Error).message }
} finally {
loading.value = false
}
}
const getPieces = () => pieces.value
const isLoading = () => loading.value
const clearPiecesCache = () => {
pieces.value = []
total.value = 0
loaded.value = false
}
return {
pieces,
total,
loading,
loaded,
loadPieces,
createPiece,
updatePiece: updatePieceData,
deletePiece,
getPieces,
isLoading,
clearPiecesCache,
}
}