add sub componet in catego ske

This commit is contained in:
Matthieu
2025-10-16 16:48:36 +02:00
parent 761c5f559a
commit 42c788103a
11 changed files with 859 additions and 109 deletions

View File

@@ -1,5 +1,6 @@
import {
createEmptyComponentModelStructure,
type ComponentModelCustomFieldType,
type ComponentModelCustomField,
type ComponentModelPiece,
type ComponentModelStructure,
@@ -61,6 +62,31 @@ export const cloneStructure = (input: any): ComponentModelStructure => {
}
}
const toStringArray = (input: unknown): string[] | undefined => {
if (!Array.isArray(input)) {
return undefined
}
const parsed = input
.map((value) => {
if (typeof value === 'string') {
return value.trim()
}
if (value === null || value === undefined) {
return ''
}
return String(value).trim()
})
.filter((value) => value.length > 0)
return parsed.length ? parsed : undefined
}
const extractFieldValueObject = (field: any): Record<string, any> => {
if (isPlainObject(field?.value)) {
return field.value as Record<string, any>
}
return {}
}
const sanitizeCustomFields = (fields: any[]): ComponentModelCustomField[] => {
if (!Array.isArray(fields)) {
return []
@@ -68,32 +94,84 @@ const sanitizeCustomFields = (fields: any[]): ComponentModelCustomField[] => {
return fields
.map((field) => {
const name = typeof field?.name === 'string' ? field.name.trim() : ''
const rawName =
typeof field?.name === 'string'
? field.name
: typeof field?.key === 'string'
? field.key
: ''
const name = rawName.trim()
if (!name) {
return null
}
const type = typeof field?.type === 'string' && field.type ? field.type : 'text'
const required = !!field?.required
const valueObject = extractFieldValueObject(field)
const candidateType =
typeof field?.type === 'string' && field.type
? field.type
: typeof valueObject?.type === 'string'
? valueObject.type
: ''
const allowedTypes: ComponentModelCustomFieldType[] = ['text', 'number', 'select', 'boolean', 'date']
const type = allowedTypes.includes(candidateType as ComponentModelCustomFieldType)
? (candidateType as ComponentModelCustomFieldType)
: 'text'
const required =
typeof valueObject?.required === 'boolean' ? valueObject.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
options =
toStringArray(valueObject?.options) ||
toStringArray((valueObject as any)?.choices) ||
toStringArray(field?.options)
if (!options && typeof field?.optionsText === 'string') {
const parsedFromText = field.optionsText
.split(/\r?\n/)
.map((option) => option.trim())
.filter((option) => option.length > 0)
options = parsedFromText.length ? parsedFromText : undefined
}
}
const result: ComponentModelCustomField = { name, type, required }
if (options) {
result.options = options
}
const defaultCandidate =
field?.defaultValue ?? valueObject?.defaultValue ?? field?.value ?? field?.default ?? null
const resolvedDefault = (() => {
if (defaultCandidate === undefined || defaultCandidate === null) {
return undefined
}
if (typeof defaultCandidate === 'object') {
if (defaultCandidate === null) {
return undefined
}
if ('defaultValue' in (defaultCandidate as Record<string, any>)) {
return (defaultCandidate as Record<string, any>).defaultValue
}
if ('value' in (defaultCandidate as Record<string, any>)) {
return (defaultCandidate as Record<string, any>).value
}
return undefined
}
return defaultCandidate
})()
if (resolvedDefault !== undefined && resolvedDefault !== null && resolvedDefault !== '') {
result.defaultValue = String(resolvedDefault)
}
const id = typeof field?.id === 'string' ? field.id : undefined
if (id) {
result.id = id
}
const customFieldId = typeof field?.customFieldId === 'string' ? field.customFieldId : undefined
if (customFieldId) {
result.customFieldId = customFieldId
}
return result
})
.filter((field): field is ComponentModelCustomField => !!field)
@@ -207,13 +285,142 @@ const sanitizeSubcomponents = (components: any[]): ComponentModelStructureNode[]
.filter((component): component is ComponentModelStructureNode => !!component)
}
export const normalizeStructureForSave = (input: any): ComponentModelStructure => {
export const normalizeStructureForEditor = (input: any): ComponentModelStructure => {
const source = cloneStructure(input)
const sanitizedCustomFields = sanitizeCustomFields(source.customFields)
const customFields = sanitizedCustomFields.map((field) => {
const options = Array.isArray(field.options) ? [...field.options] : []
const optionsText = options.length ? options.join('\n') : ''
const defaultValue =
field.defaultValue !== undefined && field.defaultValue !== null && field.defaultValue !== ''
? String(field.defaultValue)
: null
const copy: ComponentModelCustomField = {
name: field.name,
type: field.type,
required: field.required,
options,
defaultValue,
optionsText,
id: field.id,
customFieldId: field.customFieldId,
}
return copy
})
const result: ComponentModelStructure = {
customFields: sanitizeCustomFields(source.customFields),
customFields: customFields as ComponentModelCustomField[],
pieces: sanitizePieces(source.pieces),
subcomponents: sanitizeSubcomponents(source.subcomponents),
subcomponents: hydrateSubcomponents(source.subcomponents),
}
if (typeof source.typeComposantId === 'string' && source.typeComposantId.length > 0) {
result.typeComposantId = source.typeComposantId
}
if (typeof source.typeComposantLabel === 'string' && source.typeComposantLabel.length > 0) {
result.typeComposantLabel = source.typeComposantLabel
}
if (typeof source.modelId === 'string' && source.modelId.length > 0) {
result.modelId = source.modelId
}
if (typeof source.familyCode === 'string' && source.familyCode.length > 0) {
result.familyCode = source.familyCode
}
if (typeof source.alias === 'string' && source.alias.length > 0) {
result.alias = source.alias
}
return result
}
export const normalizeStructureForSave = (input: any): any => {
const source = cloneStructure(input)
const sanitizedCustomFields = sanitizeCustomFields(source.customFields)
const backendCustomFields = sanitizedCustomFields.map((field) => {
const value: Record<string, any> = {
type: field.type,
required: !!field.required,
}
if (field.options && field.options.length) {
value.options = field.options
}
if (field.defaultValue !== undefined && field.defaultValue !== null && field.defaultValue !== '') {
value.defaultValue = field.defaultValue
}
const payload: Record<string, any> = {
key: field.name,
value,
}
if (field.id) {
payload.id = field.id
}
if (field.customFieldId) {
payload.customFieldId = field.customFieldId
}
return payload
}) as any
const backendPieces = sanitizePieces(source.pieces).map((piece) => {
const payload: Record<string, any> = {}
if ((piece as any).familyCode) {
payload.familyCode = (piece as any).familyCode
}
if (piece.typePieceId) {
payload.typePieceId = piece.typePieceId
}
if (piece.typePieceLabel) {
payload.typePieceLabel = piece.typePieceLabel
}
if (piece.reference) {
payload.reference = piece.reference
}
return payload
}) as any
const mapSubcomponentForSave = (subcomponent: ComponentModelStructureNode): any => {
const payload: Record<string, any> = {}
if (subcomponent.typeComposantId) {
payload.typeComposantId = subcomponent.typeComposantId
}
if (subcomponent.modelId) {
payload.modelId = subcomponent.modelId
}
if (subcomponent.familyCode) {
payload.familyCode = subcomponent.familyCode
}
if (subcomponent.alias) {
payload.alias = subcomponent.alias
}
if (Array.isArray(subcomponent.subcomponents) && subcomponent.subcomponents.length) {
payload.subcomponents = subcomponent.subcomponents.map(mapSubcomponentForSave)
}
return payload
}
const backendSubcomponents = sanitizeSubcomponents(source.subcomponents).map(mapSubcomponentForSave) as any
const result: ComponentModelStructure = {
customFields: backendCustomFields,
pieces: backendPieces,
subcomponents: backendSubcomponents,
}
if (typeof source.typeComposantId === 'string' && source.typeComposantId.length > 0) {
(result as any).typeComposantId = source.typeComposantId
}
if (typeof source.typeComposantLabel === 'string' && source.typeComposantLabel.length > 0) {
(result as any).typeComposantLabel = source.typeComposantLabel
}
if (typeof source.modelId === 'string' && source.modelId.length > 0) {
(result as any).modelId = source.modelId
}
if (typeof source.familyCode === 'string' && source.familyCode.length > 0) {
(result as any).familyCode = source.familyCode
}
if (typeof source.alias === 'string' && source.alias.length > 0) {
(result as any).alias = source.alias
}
return result
@@ -224,13 +431,83 @@ const hydrateCustomFields = (fields: any[]): any[] => {
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 ?? ''),
}))
return fields.map((field) => {
const valueObject = extractFieldValueObject(field)
const name = typeof field?.name === 'string'
? field.name
: typeof field?.key === 'string'
? field.key
: ''
const candidateType =
typeof field?.type === 'string' && field.type
? field.type
: typeof valueObject?.type === 'string'
? valueObject.type
: ''
const allowedTypes: ComponentModelCustomFieldType[] = ['text', 'number', 'select', 'boolean', 'date']
const type = allowedTypes.includes(candidateType as ComponentModelCustomFieldType)
? (candidateType as ComponentModelCustomFieldType)
: 'text'
const required =
typeof field?.required === 'boolean'
? field.required
: typeof valueObject?.required === 'boolean'
? valueObject.required
: false
const options =
toStringArray(field?.options) ||
toStringArray(valueObject?.options) ||
toStringArray((valueObject as any)?.choices) ||
[]
const optionsText = typeof field?.optionsText === 'string'
? field.optionsText
: options.length
? options.join('\n')
: ''
const defaultCandidate =
field?.defaultValue ?? valueObject?.defaultValue ?? field?.value ?? field?.default ?? null
const resolvedDefault = (() => {
if (defaultCandidate === undefined || defaultCandidate === null) {
return undefined
}
if (typeof defaultCandidate === 'object') {
if (defaultCandidate === null) {
return undefined
}
if ('defaultValue' in (defaultCandidate as Record<string, any>)) {
return (defaultCandidate as Record<string, any>).defaultValue
}
if ('value' in (defaultCandidate as Record<string, any>)) {
return (defaultCandidate as Record<string, any>).value
}
return undefined
}
return defaultCandidate
})()
const defaultValue =
resolvedDefault !== undefined && resolvedDefault !== null && resolvedDefault !== ''
? String(resolvedDefault)
: ''
const id = typeof field?.id === 'string' ? field.id : undefined
const customFieldId = typeof field?.customFieldId === 'string' ? field.customFieldId : undefined
return {
name,
type,
required,
options,
optionsText,
defaultValue,
id,
customFieldId,
}
})
}
const hydratePieces = (pieces: any[]): ComponentModelPiece[] => {
@@ -280,27 +557,29 @@ export const hydrateStructureForEditor = (input: any): ComponentModelStructure =
}
}
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),
}))
return hydrateCustomFields(fields).map((field) => {
const defaultValue =
field?.defaultValue !== undefined && field?.defaultValue !== null && field?.defaultValue !== ''
? field.defaultValue
: null
return {
name: typeof field?.name === 'string' ? field.name : '',
type: field?.type ?? 'text',
required: !!field?.required,
options: Array.isArray(field?.options) ? field.options : [],
optionsText: typeof field?.optionsText === 'string' ? field.optionsText : '',
defaultValue,
id: typeof (field as any)?.id === 'string' ? (field as any).id : undefined,
customFieldId:
typeof (field as any)?.customFieldId === 'string'
? (field as any).customFieldId
: undefined,
}
})
}
const mapComponentPieces = (pieces: any[]): ComponentModelPiece[] => {
@@ -352,7 +631,7 @@ export const extractStructureFromComponent = (component: any) => {
alias: component?.alias ?? component?.name ?? '',
}
return normalizeStructureForSave(raw)
return normalizeStructureForEditor(raw)
}
export const computeStructureStats = (structure: any): ModelStructurePreview => {
@@ -518,7 +797,11 @@ const hydratePieceCustomFields = (fields: any[]): PieceModelStructureEditorField
type: field?.type ?? 'text',
required: !!field?.required,
options: Array.isArray(field?.options) ? field.options : undefined,
optionsText: Array.isArray(field?.options) ? field.options.join('\n') : (field?.optionsText ?? ''),
optionsText: typeof field?.optionsText === 'string'
? field.optionsText
: Array.isArray(field?.options)
? field.options.join('\n')
: '',
}))
}

View File

@@ -5,6 +5,10 @@ export interface ComponentModelCustomField {
type: ComponentModelCustomFieldType
required: boolean
options?: string[]
defaultValue?: string | null
optionsText?: string
id?: string
customFieldId?: string
}
export interface ComponentModelPiece {