feat: add product catalogue and product-aware UI
- introduce product catalogue pages, management view entries and shared product composables\n- wire product selection into component/piece flows and machine skeleton requirements\n- display linked product metadata and documents across machine, component and piece views\n- generalize model type tooling to handle PRODUCT category
This commit is contained in:
@@ -4,6 +4,23 @@ import {
|
||||
formatConstructeurContact,
|
||||
} from '~/shared/constructeurUtils'
|
||||
|
||||
const currencyFormatter = new Intl.NumberFormat('fr-FR', {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
currencyDisplay: 'narrowSymbol',
|
||||
})
|
||||
|
||||
const formatCurrency = (value) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return null
|
||||
}
|
||||
const number = Number(value)
|
||||
if (Number.isNaN(number)) {
|
||||
return null
|
||||
}
|
||||
return currencyFormatter.format(number)
|
||||
}
|
||||
|
||||
const formatSize = (size) => {
|
||||
if (size === undefined || size === null) { return '—' }
|
||||
if (size === 0) { return '0 B' }
|
||||
@@ -55,6 +72,49 @@ const renderPrintDocuments = (documents = [], title, sectionClass = 'print-secti
|
||||
`
|
||||
}
|
||||
|
||||
const renderPrintProductSummary = (product, title = 'Produit catalogue', sectionClass = 'print-piece-section') => {
|
||||
if (!product) { return '' }
|
||||
|
||||
const infoEntries = [
|
||||
{ label: 'Nom', value: product.name || '—' },
|
||||
{ label: 'Référence', value: product.reference || '—' },
|
||||
{ label: 'Catégorie', value: product.typeName || '—' },
|
||||
{
|
||||
label: 'Prix indicatif',
|
||||
value: product.supplierPrice || '—',
|
||||
},
|
||||
{
|
||||
label: 'Fournisseur(s)',
|
||||
value: product.constructeurs?.length
|
||||
? product.constructeurs.map((constructeur) => constructeur.name).filter(Boolean).join(', ') || '—'
|
||||
: '—',
|
||||
},
|
||||
]
|
||||
|
||||
const infoMarkup = infoEntries
|
||||
.map((field) => `<div class="print-field"><label>${field.label}</label><span>${field.value || '—'}</span></div>`)
|
||||
.join('')
|
||||
|
||||
const customFieldsBlock = product.customFields?.length
|
||||
? renderPrintCustomFields(product.customFields, 'Champs personnalisés du produit', 'print-subsection')
|
||||
: ''
|
||||
|
||||
const documentsBlock = product.documents?.length
|
||||
? renderPrintDocuments(product.documents, 'Documents du produit', 'print-subsection')
|
||||
: ''
|
||||
|
||||
return `
|
||||
<div class="${sectionClass}">
|
||||
<h4>${title}</h4>
|
||||
<div class="print-grid">
|
||||
${infoMarkup}
|
||||
</div>
|
||||
${customFieldsBlock}
|
||||
${documentsBlock}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
const renderPrintPieces = (
|
||||
pieces = [],
|
||||
title = 'Pièces indépendantes',
|
||||
@@ -94,6 +154,8 @@ const renderPrintPieces = (
|
||||
.join('')}</ul></div>`
|
||||
: ''
|
||||
|
||||
const productBlock = renderPrintProductSummary(piece.product, 'Produit catalogue')
|
||||
|
||||
return `
|
||||
<div class="print-piece-card">
|
||||
<div class="print-piece-header">
|
||||
@@ -122,6 +184,7 @@ const renderPrintPieces = (
|
||||
: '—'}</span>
|
||||
</div>
|
||||
</div>
|
||||
${productBlock}
|
||||
${customFieldsBlock}
|
||||
${documentsBlock}
|
||||
</div>
|
||||
@@ -154,6 +217,7 @@ const renderPrintComponents = (components = [], depth = 0, indexPath = []) => {
|
||||
const sectionClass = `print-section print-section--component print-section-depth-${Math.min(depth, 3)}`
|
||||
const currentIndex = [...indexPath, idx + 1]
|
||||
const indexLabel = currentIndex.join('.')
|
||||
const productBlock = renderPrintProductSummary(component.product, 'Produit catalogue', 'print-section print-subsection print-section--product')
|
||||
return `
|
||||
<div class="${sectionClass}">
|
||||
<h3>
|
||||
@@ -162,6 +226,7 @@ const renderPrintComponents = (components = [], depth = 0, indexPath = []) => {
|
||||
</h3>
|
||||
${component.description ? `<p class="print-muted">${component.description}</p>` : ''}
|
||||
${badges.length ? `<div class="badge-group">${badges.map(badge => `<span class="print-badge">${badge}</span>`).join('')}</div>` : ''}
|
||||
${productBlock}
|
||||
${renderPrintCustomFields(
|
||||
component.customFields,
|
||||
'Champs personnalisés',
|
||||
@@ -233,7 +298,28 @@ const normalizeConstructeurList = (...sources) => {
|
||||
.filter(Boolean)
|
||||
}
|
||||
|
||||
const normalizeProduct = (product) => {
|
||||
if (!product) { return null }
|
||||
const constructeurs = normalizeConstructeurList(
|
||||
product.constructeurs,
|
||||
product.constructeur,
|
||||
product.constructeurIds,
|
||||
product.constructeurId,
|
||||
)
|
||||
return {
|
||||
id: product.id || null,
|
||||
name: product.name || 'Produit sans nom',
|
||||
reference: product.reference || '',
|
||||
supplierPrice: formatCurrency(product.supplierPrice),
|
||||
typeName: product.typeProduct?.name || null,
|
||||
constructeurs,
|
||||
customFields: normalizeCustomFields(product.customFieldValues || []),
|
||||
documents: normalizeDocuments(product.documents || []),
|
||||
}
|
||||
}
|
||||
|
||||
const normalizePiece = piece => {
|
||||
const rawProduct = piece.product || null
|
||||
const constructeurs = normalizeConstructeurList(
|
||||
piece.constructeurs,
|
||||
piece.constructeur,
|
||||
@@ -241,7 +327,12 @@ const normalizePiece = piece => {
|
||||
piece.originalPiece?.constructeur,
|
||||
piece.constructeurIds,
|
||||
piece.constructeurId,
|
||||
rawProduct?.constructeurs,
|
||||
rawProduct?.constructeur,
|
||||
rawProduct?.constructeurIds,
|
||||
rawProduct?.constructeurId,
|
||||
)
|
||||
const product = normalizeProduct(rawProduct)
|
||||
|
||||
return {
|
||||
id: piece.id,
|
||||
@@ -252,11 +343,13 @@ const normalizePiece = piece => {
|
||||
documents: normalizeDocuments(piece.documents || []),
|
||||
constructeurs,
|
||||
constructeur: constructeurs[0] || null,
|
||||
product,
|
||||
indexPath: piece.indexPath || null
|
||||
}
|
||||
}
|
||||
|
||||
const normalizeComponent = component => {
|
||||
const rawProduct = component.product || null
|
||||
const constructeurs = normalizeConstructeurList(
|
||||
component.constructeurs,
|
||||
component.constructeur,
|
||||
@@ -264,7 +357,12 @@ const normalizeComponent = component => {
|
||||
component.originalComposant?.constructeur,
|
||||
component.constructeurIds,
|
||||
component.constructeurId,
|
||||
rawProduct?.constructeurs,
|
||||
rawProduct?.constructeur,
|
||||
rawProduct?.constructeurIds,
|
||||
rawProduct?.constructeurId,
|
||||
)
|
||||
const product = normalizeProduct(rawProduct)
|
||||
|
||||
return {
|
||||
id: component.id,
|
||||
@@ -276,6 +374,7 @@ const normalizeComponent = component => {
|
||||
subComponents: (component.sousComposants || component.subComponents || []).map(normalizeComponent),
|
||||
constructeurs,
|
||||
constructeur: constructeurs[0] || null,
|
||||
product,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user