304 lines
8.7 KiB
TypeScript
304 lines
8.7 KiB
TypeScript
export const isPlainObject = (value: unknown): value is Record<string, unknown> => {
|
|
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
}
|
|
|
|
export interface ModelStructurePreview {
|
|
customFields: number
|
|
pieces: number
|
|
subComponents: number
|
|
}
|
|
|
|
export const defaultStructure = () => ({
|
|
customFields: [],
|
|
pieces: [],
|
|
subComponents: [],
|
|
})
|
|
|
|
export const cloneStructure = (input: any) => {
|
|
try {
|
|
return JSON.parse(JSON.stringify(input ?? defaultStructure()))
|
|
} catch (error) {
|
|
return defaultStructure()
|
|
}
|
|
}
|
|
|
|
const sanitizeCustomFields = (fields: any[]): any[] => {
|
|
if (!Array.isArray(fields)) {
|
|
return []
|
|
}
|
|
|
|
return fields
|
|
.map((field) => {
|
|
const name = typeof field?.name === 'string' ? field.name.trim() : ''
|
|
if (!name) {
|
|
return null
|
|
}
|
|
|
|
const type = typeof field?.type === 'string' && field.type ? field.type : 'text'
|
|
const required = !!field?.required
|
|
|
|
let options: string[] | undefined
|
|
if (type === 'select') {
|
|
const rawOptions = typeof field?.optionsText === 'string'
|
|
? field.optionsText
|
|
: Array.isArray(field?.options)
|
|
? field.options.join('\n')
|
|
: ''
|
|
const parsed = rawOptions
|
|
.split(/\r?\n/)
|
|
.map((option) => option.trim())
|
|
.filter((option) => option.length > 0)
|
|
options = parsed.length > 0 ? parsed : undefined
|
|
}
|
|
|
|
const result: Record<string, unknown> = { name, type, required }
|
|
if (options) {
|
|
result.options = options
|
|
}
|
|
return result
|
|
})
|
|
.filter(Boolean)
|
|
}
|
|
|
|
const sanitizePieces = (pieces: any[]): any[] => {
|
|
if (!Array.isArray(pieces)) {
|
|
return []
|
|
}
|
|
|
|
return pieces
|
|
.map((piece) => {
|
|
const name = typeof piece?.name === 'string' ? piece.name.trim() : ''
|
|
if (!name) {
|
|
return null
|
|
}
|
|
|
|
const reference = typeof piece?.reference === 'string' && piece.reference.trim().length > 0
|
|
? piece.reference.trim()
|
|
: undefined
|
|
|
|
const quantity = Number(piece?.quantity)
|
|
const normalizedQuantity = Number.isFinite(quantity) && quantity > 0 ? quantity : undefined
|
|
|
|
const rawTypePieceId = typeof piece?.typePieceId === 'string'
|
|
? piece.typePieceId.trim()
|
|
: typeof piece?.typePiece?.id === 'string'
|
|
? piece.typePiece.id.trim()
|
|
: ''
|
|
const typePieceId = rawTypePieceId.length > 0 ? rawTypePieceId : undefined
|
|
|
|
const rawTypePieceLabel = typeof piece?.typePieceLabel === 'string'
|
|
? piece.typePieceLabel.trim()
|
|
: typeof piece?.typePiece?.name === 'string'
|
|
? piece.typePiece.name.trim()
|
|
: ''
|
|
const typePieceLabel = rawTypePieceLabel.length > 0 ? rawTypePieceLabel : undefined
|
|
|
|
const result: Record<string, unknown> = { name }
|
|
if (reference !== undefined) {
|
|
result.reference = reference
|
|
}
|
|
if (normalizedQuantity !== undefined) {
|
|
result.quantity = normalizedQuantity
|
|
}
|
|
if (typePieceId) {
|
|
result.typePieceId = typePieceId
|
|
}
|
|
if (typePieceLabel) {
|
|
result.typePieceLabel = typePieceLabel
|
|
}
|
|
return result
|
|
})
|
|
.filter(Boolean)
|
|
}
|
|
|
|
const sanitizeSubComponents = (components: any[]): any[] => {
|
|
if (!Array.isArray(components)) {
|
|
return []
|
|
}
|
|
|
|
return components
|
|
.map((component) => {
|
|
const name = typeof component?.name === 'string' ? component.name.trim() : ''
|
|
if (!name) {
|
|
return null
|
|
}
|
|
|
|
const description = typeof component?.description === 'string' && component.description.trim().length > 0
|
|
? component.description.trim()
|
|
: undefined
|
|
|
|
const quantity = Number(component?.quantity)
|
|
const normalizedQuantity = Number.isFinite(quantity) && quantity > 0 ? quantity : undefined
|
|
|
|
const customFields = sanitizeCustomFields(component?.customFields)
|
|
const pieces = sanitizePieces(component?.pieces)
|
|
const subComponents = sanitizeSubComponents(component?.subComponents)
|
|
|
|
const result: Record<string, unknown> = {
|
|
name,
|
|
customFields,
|
|
pieces,
|
|
subComponents,
|
|
}
|
|
|
|
if (description !== undefined) {
|
|
result.description = description
|
|
}
|
|
if (normalizedQuantity !== undefined) {
|
|
result.quantity = normalizedQuantity
|
|
}
|
|
|
|
return result
|
|
})
|
|
.filter(Boolean)
|
|
}
|
|
|
|
export const normalizeStructureForSave = (input: any) => {
|
|
const source = cloneStructure(input)
|
|
|
|
return {
|
|
customFields: sanitizeCustomFields(source.customFields),
|
|
pieces: sanitizePieces(source.pieces),
|
|
subComponents: sanitizeSubComponents(source.subComponents),
|
|
}
|
|
}
|
|
|
|
const hydrateCustomFields = (fields: any[]): any[] => {
|
|
if (!Array.isArray(fields)) {
|
|
return []
|
|
}
|
|
|
|
return fields.map((field) => ({
|
|
name: field?.name ?? '',
|
|
type: field?.type ?? 'text',
|
|
required: !!field?.required,
|
|
options: Array.isArray(field?.options) ? field.options : [],
|
|
optionsText: Array.isArray(field?.options) ? field.options.join('\n') : (field?.optionsText ?? ''),
|
|
}))
|
|
}
|
|
|
|
const hydratePieces = (pieces: any[]): any[] => {
|
|
if (!Array.isArray(pieces)) {
|
|
return []
|
|
}
|
|
|
|
return pieces.map((piece) => ({
|
|
name: piece?.name ?? '',
|
|
reference: piece?.reference ?? '',
|
|
quantity: piece?.quantity ?? undefined,
|
|
typePieceId: piece?.typePieceId ?? piece?.typePiece?.id ?? '',
|
|
typePieceLabel: piece?.typePieceLabel ?? piece?.typePiece?.name ?? '',
|
|
}))
|
|
}
|
|
|
|
const hydrateSubComponents = (components: any[]): any[] => {
|
|
if (!Array.isArray(components)) {
|
|
return []
|
|
}
|
|
|
|
return components.map((component) => ({
|
|
name: component?.name ?? '',
|
|
description: component?.description ?? '',
|
|
quantity: component?.quantity ?? undefined,
|
|
customFields: hydrateCustomFields(component?.customFields),
|
|
pieces: hydratePieces(component?.pieces),
|
|
subComponents: hydrateSubComponents(component?.subComponents),
|
|
}))
|
|
}
|
|
|
|
export const hydrateStructureForEditor = (input: any) => {
|
|
const source = cloneStructure(input)
|
|
return {
|
|
customFields: hydrateCustomFields(source.customFields),
|
|
pieces: hydratePieces(source.pieces),
|
|
subComponents: hydrateSubComponents(source.subComponents),
|
|
}
|
|
}
|
|
|
|
const toOptionsText = (field: any) => {
|
|
if (typeof field?.optionsText === 'string') {
|
|
return field.optionsText
|
|
}
|
|
if (Array.isArray(field?.options)) {
|
|
return field.options.join('\n')
|
|
}
|
|
return ''
|
|
}
|
|
|
|
const mapComponentCustomFields = (fields: any[]) => {
|
|
if (!Array.isArray(fields)) {
|
|
return []
|
|
}
|
|
return fields.map((field) => ({
|
|
name: field?.name ?? '',
|
|
type: field?.type ?? 'text',
|
|
required: !!field?.required,
|
|
options: Array.isArray(field?.options) ? field.options : [],
|
|
optionsText: toOptionsText(field),
|
|
}))
|
|
}
|
|
|
|
const mapComponentPieces = (pieces: any[]) => {
|
|
if (!Array.isArray(pieces)) {
|
|
return []
|
|
}
|
|
return pieces.map((piece) => ({
|
|
name: piece?.name ?? '',
|
|
reference: piece?.reference ?? '',
|
|
quantity: piece?.quantity ?? piece?.quantite ?? undefined,
|
|
}))
|
|
}
|
|
|
|
const mapSubComponents = (components: any[]): any[] => {
|
|
if (!Array.isArray(components)) {
|
|
return []
|
|
}
|
|
return components.map((component) => ({
|
|
name: component?.name ?? '',
|
|
description: component?.description ?? '',
|
|
quantity: component?.quantity ?? component?.quantite ?? undefined,
|
|
customFields: mapComponentCustomFields(component?.customFields),
|
|
pieces: mapComponentPieces(component?.pieces),
|
|
subComponents: mapSubComponents(component?.subComponents),
|
|
}))
|
|
}
|
|
|
|
export const extractStructureFromComponent = (component: any) => {
|
|
if (!component) {
|
|
return defaultStructure()
|
|
}
|
|
|
|
const raw = {
|
|
customFields: mapComponentCustomFields(component.customFields),
|
|
pieces: mapComponentPieces(component.pieces),
|
|
subComponents: mapSubComponents(component.subComponents),
|
|
}
|
|
|
|
return normalizeStructureForSave(raw)
|
|
}
|
|
|
|
export const computeStructureStats = (structure: any): ModelStructurePreview => {
|
|
if (!structure || typeof structure !== 'object') {
|
|
return { customFields: 0, pieces: 0, subComponents: 0 }
|
|
}
|
|
|
|
return {
|
|
customFields: Array.isArray(structure.customFields) ? structure.customFields.length : 0,
|
|
pieces: Array.isArray(structure.pieces) ? structure.pieces.length : 0,
|
|
subComponents: Array.isArray(structure.subComponents) ? structure.subComponents.length : 0,
|
|
}
|
|
}
|
|
|
|
export const formatStructurePreview = (structure: any) => {
|
|
const stats = computeStructureStats(structure)
|
|
if (!stats.customFields && !stats.pieces && !stats.subComponents) {
|
|
return 'Structure vide'
|
|
}
|
|
|
|
const segments: string[] = []
|
|
if (stats.customFields) segments.push(`${stats.customFields} champ(s)`)
|
|
if (stats.pieces) segments.push(`${stats.pieces} pièce(s)`)
|
|
if (stats.subComponents) segments.push(`${stats.subComponents} sous-composant(s)`)
|
|
return segments.join(' • ')
|
|
}
|