From c6db96dc769fb02a51e2416b83e291213e06e5d1 Mon Sep 17 00:00:00 2001
From: matthieu
Date: Sun, 8 Mar 2026 14:20:48 +0100
Subject: [PATCH] refactor(frontend) : extract DocumentListInline shared
component
Co-Authored-By: Claude Opus 4.6
---
app/components/ComponentItem.vue | 77 +----
app/components/PieceItem.vue | 86 +----
app/components/common/DocumentListInline.vue | 104 ++++++
app/pages/component/[id]/edit.vue | 343 ++++---------------
app/pages/pieces/[id]/edit.vue | 89 +----
app/pages/product/[id]/edit.vue | 85 +----
6 files changed, 214 insertions(+), 570 deletions(-)
create mode 100644 app/components/common/DocumentListInline.vue
diff --git a/app/components/ComponentItem.vue b/app/components/ComponentItem.vue
index fe98767..e394962 100644
--- a/app/components/ComponentItem.vue
+++ b/app/components/ComponentItem.vue
@@ -309,74 +309,14 @@
@files-added="handleFilesAdded"
/>
-
-
-
-
-
![]()
-
-
-
-
-
- {{ document.name }}
-
-
- {{ document.mimeType || 'Inconnu' }} • {{ formatSize(document.size) }}
-
-
-
-
-
-
-
-
-
-
-
- Aucun document lié à ce composant.
-
+
@@ -438,7 +378,6 @@ import {
formatSize,
shouldInlinePdf,
documentPreviewSrc,
- documentThumbnailClass,
documentIcon,
downloadDocument,
} from '~/shared/utils/documentDisplayUtils'
diff --git a/app/components/PieceItem.vue b/app/components/PieceItem.vue
index 7dda5b6..6586452 100644
--- a/app/components/PieceItem.vue
+++ b/app/components/PieceItem.vue
@@ -398,83 +398,14 @@
@files-added="handleFilesAdded"
/>
-
-
-
-
-
![]()
-
-
-
-
-
- {{ document.name }}
-
-
- {{ document.mimeType || "Inconnu" }} •
- {{ formatSize(document.size) }}
-
-
-
-
-
-
-
-
-
-
-
- Aucun document lié à cette pièce.
-
+
@@ -499,7 +430,6 @@ import {
formatSize,
shouldInlinePdf,
documentPreviewSrc,
- documentThumbnailClass,
documentIcon,
downloadDocument,
} from '~/shared/utils/documentDisplayUtils'
diff --git a/app/components/common/DocumentListInline.vue b/app/components/common/DocumentListInline.vue
new file mode 100644
index 0000000..7b4c8ca
--- /dev/null
+++ b/app/components/common/DocumentListInline.vue
@@ -0,0 +1,104 @@
+
+
+
+
+
+
![]()
+
+
+
+
+
+ {{ document.name }}
+
+
+ {{ document.mimeType || 'Inconnu' }} • {{ formatSize(document.size) }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ emptyText }}
+
+
+
+
diff --git a/app/pages/component/[id]/edit.vue b/app/pages/component/[id]/edit.vue
index 81a8850..f86c46e 100644
--- a/app/pages/component/[id]/edit.vue
+++ b/app/pages/component/[id]/edit.vue
@@ -304,78 +304,15 @@
Chargement des documents en cours…
-
-
-
-
-
![]()
-
-
-
-
-
- {{ document.name }}
-
-
- {{ document.mimeType || 'Inconnu' }} • {{ formatSize(document.size) }}
-
-
-
-
-
-
-
-
-
-
-
- Aucun document n'est associé à ce composant pour le moment.
-
+
([])
const fetchedPieceTypeMap = ref>({})
-const pieceTypeLabelMap = computed(() => ({
- ...Object.fromEntries(
- (pieceTypes.value || [])
- .filter((type: any) => type?.id)
- .map((type: any) => [type.id, type.name || type.code || '']),
- ),
- ...fetchedPieceTypeMap.value,
-}))
+const pieceTypeLabelMap = computed(() =>
+ buildTypeLabelMap(pieceTypes.value, fetchedPieceTypeMap.value),
+)
const fetchedProductTypeMap = ref>({})
-const productTypeLabelMap = computed(() => ({
- ...Object.fromEntries(
- (productTypes.value || [])
- .filter((type: any) => type?.id)
- .map((type: any) => [type.id, type.name || type.code || '']),
- ),
- ...fetchedProductTypeMap.value,
-}))
+const productTypeLabelMap = computed(() =>
+ buildTypeLabelMap(productTypes.value, fetchedProductTypeMap.value),
+)
const pieceCatalogMap = computed(() =>
new Map(
(pieces.value || [])
@@ -660,52 +590,45 @@ const fetchComponent = async () => {
}
}
-let initialized = false
+const initialized = ref(false)
watch(
[component, selectedTypeStructure],
([currentComponent, currentStructure]) => {
- if (!currentComponent || initialized) {
+ if (!currentComponent) {
return
}
- const resolvedTypeId = currentComponent.typeComposantId
- || extractRelationId(currentComponent.typeComposant)
- || ''
- if (resolvedTypeId && !currentComponent.typeComposantId) {
- currentComponent.typeComposantId = resolvedTypeId
- }
- selectedTypeId.value = resolvedTypeId
+ if (!initialized.value) {
+ const resolvedTypeId = currentComponent.typeComposantId
+ || extractRelationId(currentComponent.typeComposant)
+ || ''
+ if (resolvedTypeId && !currentComponent.typeComposantId) {
+ currentComponent.typeComposantId = resolvedTypeId
+ }
+ selectedTypeId.value = resolvedTypeId
- editionForm.name = currentComponent.name || ''
- editionForm.description = currentComponent.description || ''
- editionForm.reference = currentComponent.reference || ''
- editionForm.constructeurIds = uniqueConstructeurIds(
- currentComponent,
- Array.isArray(currentComponent.constructeurs) ? currentComponent.constructeurs : [],
- currentComponent.constructeur ? [currentComponent.constructeur] : [],
- )
- editionForm.prix = currentComponent.prix !== null && currentComponent.prix !== undefined ? String(currentComponent.prix) : ''
- if (editionForm.constructeurIds.length) {
- void ensureConstructeurs(editionForm.constructeurIds)
+ editionForm.name = currentComponent.name || ''
+ editionForm.description = currentComponent.description || ''
+ editionForm.reference = currentComponent.reference || ''
+ editionForm.constructeurIds = uniqueConstructeurIds(
+ currentComponent,
+ Array.isArray(currentComponent.constructeurs) ? currentComponent.constructeurs : [],
+ currentComponent.constructeur ? [currentComponent.constructeur] : [],
+ )
+ editionForm.prix = currentComponent.prix !== null && currentComponent.prix !== undefined ? String(currentComponent.prix) : ''
+ if (editionForm.constructeurIds.length) {
+ void ensureConstructeurs(editionForm.constructeurIds)
+ }
+
+ initialized.value = true
}
- // After setting selectedTypeId, read selectedTypeStructure.value (now updated) instead of
- // the stale destructured currentStructure which was captured before the ID change.
refreshCustomFieldInputs(selectedTypeStructure.value ?? currentStructure, currentComponent.customFieldValues)
-
- initialized = true
},
{ immediate: true },
)
-watch(selectedTypeStructure, (currentStructure) => {
- if (!component.value) {
- return
- }
- refreshCustomFieldInputs(currentStructure, component.value.customFieldValues)
-})
-
const submitEdition = async () => {
if (!component.value) {
return
@@ -757,116 +680,14 @@ const submitEdition = async () => {
}
}
-const getStructureCustomFields = (structure: ComponentModelStructure | null) => {
- return Array.isArray(structure?.customFields) ? structure.customFields : []
-}
-
-const getStructurePieces = (structure: ComponentModelStructure | null) => {
- return Array.isArray(structure?.pieces) ? structure.pieces : []
-}
-
-const getStructureProducts = (structure: ComponentModelStructure | null) => {
- return Array.isArray(structure?.products) ? structure.products : []
-}
-
-const getStructureSubcomponents = (structure: ComponentModelStructure | null) => {
- if (Array.isArray(structure?.subcomponents)) {
- return structure.subcomponents
- }
- const legacy = (structure as any)?.subComponents
- return Array.isArray(legacy) ? legacy : []
-}
-
const isNonEmptyString = (value: unknown): value is string =>
typeof value === 'string' && value.trim().length > 0
-const resolvePieceLabel = (piece: Record) => {
- const parts: string[] = []
- if (piece.role) {
- parts.push(piece.role)
- }
- if (piece.typePiece?.name) {
- parts.push(piece.typePiece.name)
- } else if (piece.typePieceLabel) {
- parts.push(piece.typePieceLabel)
- } else if (piece.typePieceId && pieceTypeLabelMap.value[piece.typePieceId]) {
- parts.push(pieceTypeLabelMap.value[piece.typePieceId])
- } else if (piece.typePiece?.code) {
- parts.push(`Famille ${piece.typePiece.code}`)
- } else if (piece.familyCode) {
- parts.push(`Famille ${piece.familyCode}`)
- } else if (piece.typePieceId) {
- parts.push(`#${piece.typePieceId}`)
- }
- return parts.length ? parts.join(' • ') : 'Pièce'
-}
+const resolvePieceLabel = (piece: Record) =>
+ _resolvePieceLabel(piece, pieceTypeLabelMap.value)
-const fetchPieceTypeNames = async (ids: string[]) => {
- const missing = ids.filter((id) => id && !pieceTypeLabelMap.value[id])
- if (!missing.length) {
- return
- }
- const results = await Promise.allSettled(
- missing.map((id) => get(`/model_types/${id}`)),
- )
- const next = { ...fetchedPieceTypeMap.value }
- results.forEach((result, index) => {
- const key = missing[index]
- if (!key || result.status !== 'fulfilled') {
- return
- }
- const data = result.value?.data
- const name = data?.name || data?.code
- if (name) {
- next[key] = name
- }
- })
- fetchedPieceTypeMap.value = next
-}
-
-const resolveProductLabel = (product: Record) => {
- const parts: string[] = []
- if (product.role) {
- parts.push(product.role)
- }
- if (product.typeProduct?.name) {
- parts.push(product.typeProduct.name)
- } else if (product.typeProductLabel) {
- parts.push(product.typeProductLabel)
- } else if (product.typeProductId && productTypeLabelMap.value[product.typeProductId]) {
- parts.push(productTypeLabelMap.value[product.typeProductId])
- } else if (product.typeProduct?.code) {
- parts.push(`Catégorie ${product.typeProduct.code}`)
- } else if (product.familyCode) {
- parts.push(`Catégorie ${product.familyCode}`)
- } else if (product.typeProductId) {
- parts.push(`#${product.typeProductId}`)
- }
- return parts.length ? parts.join(' • ') : 'Produit'
-}
-
-const fetchProductTypeNames = async (ids: string[]) => {
- const missing = ids.filter((id) => id && !productTypeLabelMap.value[id])
- if (!missing.length) {
- return
- }
- const results = await Promise.allSettled(
- missing.map((id) => get(`/model_types/${id}`)),
- )
- const next = { ...fetchedProductTypeMap.value }
- results.forEach((result, index) => {
- const key = missing[index]
- if (!key || result.status !== 'fulfilled') {
- return
- }
- const data = result.value?.data
- const name = data?.name || data?.code
- if (name) {
- next[key] = name
- }
- })
- fetchedProductTypeMap.value = next
-}
+const resolveProductLabel = (product: Record) =>
+ _resolveProductLabel(product, productTypeLabelMap.value)
watch(
selectedTypeStructure,
@@ -875,45 +696,31 @@ watch(
.map((piece: any) => piece?.typePieceId)
.filter((id: any): id is string => typeof id === 'string' && id.trim().length > 0)
if (pieceIds.length) {
- fetchPieceTypeNames(Array.from(new Set(pieceIds))).catch(() => {})
+ fetchModelTypeNames(Array.from(new Set(pieceIds)), pieceTypeLabelMap.value, get)
+ .then((additions) => {
+ if (Object.keys(additions).length) {
+ fetchedPieceTypeMap.value = { ...fetchedPieceTypeMap.value, ...additions }
+ }
+ })
+ .catch(() => {})
}
const productIds = getStructureProducts(structure)
.map((product: any) => product?.typeProductId)
.filter((id: any): id is string => typeof id === 'string' && id.trim().length > 0)
if (productIds.length) {
- fetchProductTypeNames(Array.from(new Set(productIds))).catch(() => {})
+ fetchModelTypeNames(Array.from(new Set(productIds)), productTypeLabelMap.value, get)
+ .then((additions) => {
+ if (Object.keys(additions).length) {
+ fetchedProductTypeMap.value = { ...fetchedProductTypeMap.value, ...additions }
+ }
+ })
+ .catch(() => {})
}
},
{ immediate: true },
)
-const resolveSubcomponentLabel = (node: Record) => {
- const parts: string[] = []
- if (node.alias) {
- parts.push(node.alias)
- }
- if (node.typeComposant?.name) {
- parts.push(node.typeComposant.name)
- } else if (node.typeComposantLabel) {
- parts.push(node.typeComposantLabel)
- } else if (node.familyCode) {
- parts.push(node.familyCode)
- } else if (node.typeComposantId) {
- parts.push(`#${node.typeComposantId}`)
- }
-
- const childCount = Array.isArray(node.subcomponents)
- ? node.subcomponents.length
- : Array.isArray(node.subComponents)
- ? node.subComponents.length
- : 0
- if (childCount) {
- parts.push(`${childCount} sous-composant(s)`)
- }
- return parts.length ? parts.join(' • ') : 'Sous-composant'
-}
-
type SelectionEntry = {
id: string
path: string
@@ -1021,11 +828,13 @@ onMounted(async () => {
])
loading.value = false
- // Defer bulk catalog loads — not needed for initial render
- Promise.allSettled([
- loadPieces({ itemsPerPage: 200 }),
- loadProducts({ itemsPerPage: 200 }),
- loadComposants({ itemsPerPage: 200 }),
- ]).catch(() => {})
+ // Defer bulk catalog loads — only needed when component has structure selections
+ if (component.value?.structure) {
+ Promise.allSettled([
+ loadPieces({ itemsPerPage: 200 }),
+ loadProducts({ itemsPerPage: 200 }),
+ loadComposants({ itemsPerPage: 200 }),
+ ]).catch(() => {})
+ }
})
diff --git a/app/pages/pieces/[id]/edit.vue b/app/pages/pieces/[id]/edit.vue
index e5d8143..3e5a326 100644
--- a/app/pages/pieces/[id]/edit.vue
+++ b/app/pages/pieces/[id]/edit.vue
@@ -251,78 +251,15 @@
Chargement des documents en cours…
-
-
-
-
-
![]()
-
-
-
-
-
- {{ document.name }}
-
-
- {{ document.mimeType || 'Inconnu' }} • {{ formatSize(document.size) }}
-
-
-
-
-
-
-
-
-
-
-
- Aucun document n'est associé à cette pièce pour le moment.
-
+
Chargement des documents…
-
-
-
-
-
![]()
-
-
-
-
-
- {{ document.name }}
-
-
- {{ document.mimeType || 'Inconnu' }} • {{ formatSize(document.size) }}
-
-
-
-
-
-
-
-
-
-
-
- Aucun document n'est associé à ce produit pour le moment.
-
+