Files
Inventory_frontend/app/shared/modelUtils.ts

333 lines
10 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 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 rawName = typeof piece?.name === 'string' ? piece.name.trim() : ''
const name = rawName || typePieceLabel || ''
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 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 rawTypeComposantId = typeof component?.typeComposantId === 'string'
? component.typeComposantId.trim()
: typeof component?.typeComposant?.id === 'string'
? component.typeComposant.id.trim()
: ''
const typeComposantId = rawTypeComposantId.length > 0 ? rawTypeComposantId : undefined
const rawTypeComposantLabel = typeof component?.typeComposantLabel === 'string'
? component.typeComposantLabel.trim()
: typeof component?.typeComposant?.name === 'string'
? component.typeComposant.name.trim()
: ''
const typeComposantLabel = rawTypeComposantLabel.length > 0 ? rawTypeComposantLabel : undefined
const rawName = typeof component?.name === 'string' ? component.name.trim() : ''
const name = rawName || typeComposantLabel || ''
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 result: Record<string, unknown> = {
name,
}
if (description !== undefined) {
result.description = description
}
if (normalizedQuantity !== undefined) {
result.quantity = normalizedQuantity
}
if (typeComposantId) {
result.typeComposantId = typeComposantId
}
if (typeComposantLabel) {
result.typeComposantLabel = typeComposantLabel
}
const nestedSubComponents = sanitizeSubComponents(component?.subComponents)
if (nestedSubComponents.length > 0) {
result.subComponents = nestedSubComponents
} else {
result.subComponents = []
}
// Sub components only carry structural info (they will be resolved via their own models)
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 ?? piece?.typePiece?.name ?? piece?.typePieceLabel ?? '',
reference: piece?.reference ?? '',
quantity: piece?.quantity ?? piece?.quantite ?? 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 ?? component?.typeComposant?.name ?? component?.typeComposantLabel ?? '',
description: component?.description ?? '',
quantity: component?.quantity ?? component?.quantite ?? undefined,
typeComposantId: component?.typeComposantId ?? component?.typeComposant?.id ?? '',
typeComposantLabel: component?.typeComposantLabel ?? component?.typeComposant?.name ?? '',
customFields: [],
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 ?? piece?.typePiece?.name ?? '',
reference: piece?.reference ?? '',
quantity: piece?.quantity ?? piece?.quantite ?? undefined,
typePieceId: piece?.typePieceId ?? piece?.typePiece?.id ?? '',
typePieceLabel: piece?.typePieceLabel ?? piece?.typePiece?.name ?? '',
}))
}
const mapSubComponents = (components: any[]): any[] => {
if (!Array.isArray(components)) {
return []
}
return components.map((component) => ({
name: component?.name ?? component?.typeComposant?.name ?? '',
description: component?.description ?? '',
quantity: component?.quantity ?? component?.quantite ?? undefined,
typeComposantId: component?.typeComposantId ?? component?.typeComposant?.id ?? '',
typeComposantLabel: component?.typeComposantLabel ?? component?.typeComposant?.name ?? '',
customFields: [],
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(' • ')
}