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>
280 lines
8.6 KiB
TypeScript
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,
|
|
}
|
|
}
|