- Extract CustomFieldInputGrid.vue from 6 duplicated template blocks (~70 lines each) - Extract EntityHistorySection.vue from 3 identical history sections in edit pages - Extract useDragReorder composable from 4 identical drag-and-drop implementations in StructureNodeEditor (~330 lines → ~30) - Extract catalogDisplayUtils.ts (resolvePrimaryDocument, resolveSupplierNames, buildSuppliersDisplay) - Remove redundant computed wrappers (historyEntries, loadingTypes, selectedFiles) - Remove unused imports (fieldKey, historyActionLabel, formatHistoryDate, *HistoryEntry types) - Move Intl.DateTimeFormat to module-level in date.ts Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
88 lines
3.4 KiB
TypeScript
88 lines
3.4 KiB
TypeScript
import { isImageDocument, isPdfDocument } from '~/utils/documentPreview'
|
||
|
||
/**
|
||
* Selects the best document for thumbnail preview from an entity's documents array.
|
||
* Default priority: PDF first, then images. Use `preferImages` to reverse.
|
||
*/
|
||
export const resolvePrimaryDocument = (entity: Record<string, any>, preferImages = false): any | null => {
|
||
const documents = Array.isArray(entity?.documents) ? entity.documents : []
|
||
if (!documents.length) return null
|
||
const normalized = documents.filter((doc: any) => doc && typeof doc === 'object')
|
||
const withPath = normalized.filter((doc: any) => doc?.fileUrl || doc?.path)
|
||
if (!withPath.length) return normalized[0] ?? null
|
||
const first = preferImages ? isImageDocument : isPdfDocument
|
||
const second = preferImages ? isPdfDocument : isImageDocument
|
||
const a = withPath.find((doc: any) => first(doc))
|
||
if (a) return a
|
||
const b = withPath.find((doc: any) => second(doc))
|
||
if (b) return b
|
||
return withPath[0]
|
||
}
|
||
|
||
/**
|
||
* Builds alt text for a document preview thumbnail.
|
||
*/
|
||
export const resolvePreviewAlt = (entity: Record<string, any>): string => {
|
||
const parts = [entity?.name, entity?.reference].filter(Boolean)
|
||
return parts.length ? `Aperçu du document de ${parts.join(' – ')}` : 'Aperçu du document'
|
||
}
|
||
|
||
/**
|
||
* Supplier name resolution: extracts unique supplier names from entity relations.
|
||
*/
|
||
export const resolveSupplierNames = (entity: Record<string, any>, nestedKey?: string): string[] => {
|
||
const names: string[] = []
|
||
const seen = new Set<string>()
|
||
|
||
const pushName = (maybeName: unknown) => {
|
||
if (typeof maybeName !== 'string') return
|
||
const normalized = maybeName.trim().replace(/\s+/g, ' ')
|
||
if (!normalized.length) return
|
||
const key = normalized.toLowerCase()
|
||
if (seen.has(key)) return
|
||
seen.add(key)
|
||
names.push(normalized)
|
||
}
|
||
|
||
const collectConstructeurs = (value: unknown): void => {
|
||
if (!value) return
|
||
if (Array.isArray(value)) { value.forEach(collectConstructeurs); return }
|
||
if (typeof value === 'string') { pushName(value); return }
|
||
if (typeof value === 'object') {
|
||
const record = value as Record<string, any>
|
||
pushName(record?.name ?? record?.label ?? record?.companyName ?? record?.company ?? null)
|
||
if (record?.constructeur) collectConstructeurs(record.constructeur)
|
||
if (Array.isArray(record?.constructeurs)) collectConstructeurs(record.constructeurs)
|
||
}
|
||
}
|
||
|
||
const collectFromLabel = (value: unknown): void => {
|
||
if (typeof value !== 'string') return
|
||
value.split(/[,;\\/•·|]+/).map(part => part.trim()).filter(Boolean).forEach(pushName)
|
||
}
|
||
|
||
collectConstructeurs(entity?.constructeurs)
|
||
collectConstructeurs(entity?.constructeur)
|
||
collectFromLabel(entity?.constructeursLabel)
|
||
collectFromLabel(entity?.supplierLabel)
|
||
collectFromLabel(entity?.suppliers)
|
||
|
||
if (nestedKey && entity?.[nestedKey]) {
|
||
const nested = entity[nestedKey]
|
||
collectConstructeurs(nested?.constructeurs)
|
||
collectConstructeurs(nested?.constructeur)
|
||
collectFromLabel(nested?.constructeursLabel)
|
||
collectFromLabel(nested?.supplierLabel)
|
||
}
|
||
|
||
return names
|
||
}
|
||
|
||
const MAX_VISIBLE_SUPPLIERS = 3
|
||
|
||
export const buildSuppliersDisplay = (suppliers: string[]) => {
|
||
const visible = suppliers.slice(0, MAX_VISIBLE_SUPPLIERS)
|
||
const overflow = Math.max(suppliers.length - visible.length, 0)
|
||
return { suppliers, visible, overflow, tooltip: suppliers.length ? suppliers.join(', ') : '' }
|
||
}
|