1 Commits

Author SHA1 Message Date
Matthieu
9f7dd12b34 perf(edit-pages) : reduce blocking API calls on edit pages
- Remove redundant getCustomFieldValuesByEntity() calls (use entity response)
- Remove redundant refreshDocuments() from onMounted (docs already in entity)
- Make loadHistory() non-blocking (fire-and-forget)
- Defer bulk catalog loads on component edit (pieces/products/composants)
- Use pieceTypes cache instead of separate getModelType() call on piece edit
- Try embedded typeProduct from entity response on product edit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 15:58:36 +01:00
3 changed files with 64 additions and 44 deletions

View File

@@ -576,7 +576,7 @@ const { updateComposant, loadComposants, composants: componentCatalogRef } = use
const { pieces, loadPieces } = usePieces() const { pieces, loadPieces } = usePieces()
const { products, loadProducts } = useProducts() const { products, loadProducts } = useProducts()
const { ensureConstructeurs } = useConstructeurs() const { ensureConstructeurs } = useConstructeurs()
const { upsertCustomFieldValue, updateCustomFieldValue, getCustomFieldValuesByEntity } = useCustomFields() const { upsertCustomFieldValue, updateCustomFieldValue } = useCustomFields()
const toast = useToast() const toast = useToast()
const { loadDocumentsByComponent, uploadDocuments, deleteDocument } = useDocuments() const { loadDocumentsByComponent, uploadDocuments, deleteDocument } = useDocuments()
const { const {
@@ -764,12 +764,10 @@ const fetchComponent = async () => {
component.value = result.data component.value = result.data
componentDocuments.value = Array.isArray(result.data?.documents) ? result.data.documents : [] componentDocuments.value = Array.isArray(result.data?.documents) ? result.data.documents : []
const customValues = await getCustomFieldValuesByEntity('composant', result.data.id) const customValues = Array.isArray(result.data?.customFieldValues) ? result.data.customFieldValues : []
if (customValues.success && Array.isArray(customValues.data)) { refreshCustomFieldInputs(undefined, customValues)
component.value.customFieldValues = customValues.data
refreshCustomFieldInputs(undefined, customValues.data) loadHistory(result.data.id).catch(() => {})
}
await loadHistory(result.data.id)
} else { } else {
component.value = null component.value = null
componentDocuments.value = [] componentDocuments.value = []
@@ -1130,14 +1128,15 @@ onMounted(async () => {
loadComponentTypes(), loadComponentTypes(),
loadPieceTypes(), loadPieceTypes(),
loadProductTypes(), loadProductTypes(),
loadPieces({ itemsPerPage: 500 }),
loadProducts({ itemsPerPage: 500, force: true }),
loadComposants({ itemsPerPage: 500 }),
fetchComponent(), fetchComponent(),
]) ])
loading.value = false loading.value = false
if (component.value?.id) {
await refreshDocuments() // Defer bulk catalog loads — not needed for initial render
} Promise.allSettled([
loadPieces({ itemsPerPage: 500 }),
loadProducts({ itemsPerPage: 500 }),
loadComposants({ itemsPerPage: 500 }),
]).catch(() => {})
}) })
</script> </script>

View File

@@ -516,7 +516,7 @@ const router = useRouter()
const { get } = useApi() const { get } = useApi()
const { pieceTypes, loadPieceTypes } = usePieceTypes() const { pieceTypes, loadPieceTypes } = usePieceTypes()
const { updatePiece } = usePieces() const { updatePiece } = usePieces()
const { upsertCustomFieldValue, updateCustomFieldValue, getCustomFieldValuesByEntity } = useCustomFields() const { upsertCustomFieldValue, updateCustomFieldValue } = useCustomFields()
const toast = useToast() const toast = useToast()
const { loadDocumentsByPiece, uploadDocuments, deleteDocument } = useDocuments() const { loadDocumentsByPiece, uploadDocuments, deleteDocument } = useDocuments()
const { ensureConstructeurs } = useConstructeurs() const { ensureConstructeurs } = useConstructeurs()
@@ -750,20 +750,23 @@ const fetchPiece = async () => {
if (result.success) { if (result.success) {
piece.value = result.data piece.value = result.data
pieceDocuments.value = Array.isArray(result.data?.documents) ? result.data.documents : [] pieceDocuments.value = Array.isArray(result.data?.documents) ? result.data.documents : []
const customValues = await getCustomFieldValuesByEntity('piece', result.data.id)
if (customValues.success && Array.isArray(customValues.data)) { // Use customFieldValues from entity response (enriched with customField definitions via serialization groups)
piece.value.customFieldValues = customValues.data const customValues = Array.isArray(result.data?.customFieldValues) ? result.data.customFieldValues : []
refreshCustomFieldInputs(undefined, customValues.data) refreshCustomFieldInputs(undefined, customValues)
}
await loadPieceTypeDetails(result.data) // Use cached type from loadPieceTypes() instead of separate getModelType() call
await loadHistory(result.data.id) loadPieceTypeDetailsFromCache(result.data)
// History is non-blocking — template handles its own loading state
loadHistory(result.data.id).catch(() => {})
} else { } else {
piece.value = null piece.value = null
pieceDocuments.value = [] pieceDocuments.value = []
} }
} }
const loadPieceTypeDetails = async (currentPiece: any) => { const loadPieceTypeDetailsFromCache = (currentPiece: any) => {
const typeId = currentPiece?.typePieceId const typeId = currentPiece?.typePieceId
|| extractRelationId(currentPiece?.typePiece) || extractRelationId(currentPiece?.typePiece)
|| '' || ''
@@ -771,15 +774,22 @@ const loadPieceTypeDetails = async (currentPiece: any) => {
pieceTypeDetails.value = null pieceTypeDetails.value = null
return return
} }
try { // Look up in the already-loaded pieceTypes cache (from loadPieceTypes in onMounted)
const type = await getModelType(typeId) const cachedType = (pieceTypes.value || []).find((t: any) => t.id === typeId) ?? null
if (cachedType) {
pieceTypeDetails.value = cachedType
refreshCustomFieldInputs((cachedType.structure as PieceModelStructure | null) ?? null, currentPiece?.customFieldValues ?? null)
return
}
// Fallback: fetch if not in cache (edge case)
getModelType(typeId).then((type) => {
if (type && typeof type === 'object') { if (type && typeof type === 'object') {
pieceTypeDetails.value = type pieceTypeDetails.value = type
refreshCustomFieldInputs((type.structure as PieceModelStructure | null) ?? null, currentPiece?.customFieldValues ?? null) refreshCustomFieldInputs((type.structure as PieceModelStructure | null) ?? null, currentPiece?.customFieldValues ?? null)
} }
} catch (_error) { }).catch(() => {
pieceTypeDetails.value = null pieceTypeDetails.value = null
} })
} }
let initialized = false let initialized = false
@@ -920,8 +930,5 @@ const submitEdition = async () => {
onMounted(async () => { onMounted(async () => {
await Promise.allSettled([loadPieceTypes(), fetchPiece()]) await Promise.allSettled([loadPieceTypes(), fetchPiece()])
loading.value = false loading.value = false
if (piece.value?.id) {
await refreshDocuments()
}
}) })
</script> </script>

View File

@@ -428,7 +428,7 @@ const route = useRoute()
const router = useRouter() const router = useRouter()
const toast = useToast() const toast = useToast()
const { getProduct, updateProduct } = useProducts() const { getProduct, updateProduct } = useProducts()
const { upsertCustomFieldValue, updateCustomFieldValue, getCustomFieldValuesByEntity } = useCustomFields() const { upsertCustomFieldValue, updateCustomFieldValue } = useCustomFields()
const { const {
loadDocumentsByProduct, loadDocumentsByProduct,
uploadDocuments: uploadProductDocuments, uploadDocuments: uploadProductDocuments,
@@ -520,15 +520,17 @@ const loadProduct = async () => {
if (result.success && result.data) { if (result.success && result.data) {
product.value = result.data product.value = result.data
productDocuments.value = Array.isArray(result.data.documents) ? result.data.documents : [] productDocuments.value = Array.isArray(result.data.documents) ? result.data.documents : []
await loadProductType() await loadProductType()
const customValues = await getCustomFieldValuesByEntity('product', result.data.id)
if (customValues.success && Array.isArray(customValues.data)) { // Use customFieldValues from entity response (enriched with customField definitions via serialization groups)
product.value.customFieldValues = customValues.data const customValues = Array.isArray(result.data?.customFieldValues) ? result.data.customFieldValues : []
refreshCustomFieldInputs(undefined, customValues.data) refreshCustomFieldInputs(undefined, customValues)
}
await hydrateForm() hydrateForm()
await refreshDocuments()
await loadHistory(result.data.id) // History is non-blocking — template handles its own loading state
loadHistory(result.data.id).catch(() => {})
} else { } else {
product.value = null product.value = null
} }
@@ -587,9 +589,20 @@ const handleFilesAdded = async (files: File[]) => {
} }
const loadProductType = async () => { const loadProductType = async () => {
// Try using the expanded typeProduct from entity response first
const embedded = product.value?.typeProduct
if (embedded && typeof embedded === 'object' && embedded.id) {
const embeddedStructure = embedded.structure ?? embedded.productSkeleton ?? null
if (embeddedStructure) {
productType.value = embedded
structure.value = normalizeProductStructureForSave(embeddedStructure)
return
}
}
if (!product.value?.typeProductId) { if (!product.value?.typeProductId) {
productType.value = product.value?.typeProduct ?? null productType.value = embedded ?? null
structure.value = normalizeProductStructureForSave(productType.value?.structure ?? null) structure.value = normalizeProductStructureForSave(embedded?.structure ?? null)
return return
} }
try { try {
@@ -598,12 +611,12 @@ const loadProductType = async () => {
structure.value = normalizeProductStructureForSave(type?.structure ?? type?.productSkeleton ?? null) structure.value = normalizeProductStructureForSave(type?.structure ?? type?.productSkeleton ?? null)
} catch (error) { } catch (error) {
console.error('Erreur lors du chargement du type de produit:', error) console.error('Erreur lors du chargement du type de produit:', error)
productType.value = product.value?.typeProduct ?? null productType.value = embedded ?? null
structure.value = normalizeProductStructureForSave(productType.value?.structure ?? null) structure.value = normalizeProductStructureForSave(embedded?.structure ?? null)
} }
} }
const hydrateForm = async () => { const hydrateForm = () => {
if (!product.value) { if (!product.value) {
return return
} }
@@ -618,7 +631,8 @@ const hydrateForm = async () => {
: '' : ''
refreshCustomFieldInputs(structure.value, product.value.customFieldValues) refreshCustomFieldInputs(structure.value, product.value.customFieldValues)
if (editionForm.constructeurIds.length) { if (editionForm.constructeurIds.length) {
await ensureConstructeurs(editionForm.constructeurIds) // Smart-cached + deduped — fire-and-forget, ConstructeurSelect handles its own loading
ensureConstructeurs(editionForm.constructeurIds).catch(() => {})
} }
} }