refactor(model): split modelUtils.ts into 3 thematic modules (F5.1)
Split 1017 LOC monolith into: - shared/model/componentStructure.ts (~590 LOC) - shared/model/pieceProductStructure.ts (~155 LOC) - shared/model/definitionOverrides.ts (~50 LOC) Rewrite modelUtils.ts as 37 LOC barrel re-export for backward compat. All 11 consumer files unchanged. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
177
app/shared/model/pieceProductStructure.ts
Normal file
177
app/shared/model/pieceProductStructure.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
import {
|
||||
createEmptyPieceModelStructure,
|
||||
createEmptyProductModelStructure,
|
||||
type PieceModelCustomField,
|
||||
type PieceModelProduct,
|
||||
type PieceModelStructure,
|
||||
type PieceModelStructureEditorField,
|
||||
type PieceModelStructureForEditor,
|
||||
type ProductModelStructure,
|
||||
} from '../types/inventory'
|
||||
import { isPlainObject, sanitizeProducts, hydrateProducts } from './componentStructure'
|
||||
|
||||
export const defaultPieceStructure = (): PieceModelStructure => ({
|
||||
...createEmptyPieceModelStructure(),
|
||||
})
|
||||
|
||||
export const defaultProductStructure = (): ProductModelStructure => ({
|
||||
...createEmptyProductModelStructure(),
|
||||
})
|
||||
|
||||
const ensurePieceStructureShape = (input: any): PieceModelStructure => {
|
||||
const base = createEmptyPieceModelStructure()
|
||||
if (!isPlainObject(input)) {
|
||||
return base
|
||||
}
|
||||
|
||||
const clone: PieceModelStructure = {
|
||||
...base,
|
||||
customFields: Array.isArray((input as any).customFields) ? (input as any).customFields : [],
|
||||
products: Array.isArray((input as any).products) ? (input as any).products : [],
|
||||
}
|
||||
|
||||
for (const [key, value] of Object.entries(input as Record<string, unknown>)) {
|
||||
if (key === 'customFields' || key === 'products') {
|
||||
continue
|
||||
}
|
||||
clone[key] = value
|
||||
}
|
||||
|
||||
return clone
|
||||
}
|
||||
|
||||
export const clonePieceStructure = (input: any): PieceModelStructure => {
|
||||
try {
|
||||
const cloned = JSON.parse(JSON.stringify(input ?? defaultPieceStructure()))
|
||||
return ensurePieceStructureShape(cloned)
|
||||
} catch (_error) {
|
||||
return defaultPieceStructure()
|
||||
}
|
||||
}
|
||||
|
||||
export const cloneProductStructure = (input: any): ProductModelStructure => {
|
||||
return clonePieceStructure(input)
|
||||
}
|
||||
|
||||
const sanitizePieceCustomFields = (fields: any[]): PieceModelCustomField[] => {
|
||||
if (!Array.isArray(fields)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return fields
|
||||
.map((field, index) => {
|
||||
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: string) => option.trim())
|
||||
.filter((option: string) => option.length > 0)
|
||||
options = parsed.length > 0 ? parsed : undefined
|
||||
}
|
||||
|
||||
const result: PieceModelCustomField = { name, type, required }
|
||||
if (options) {
|
||||
result.options = options
|
||||
}
|
||||
const orderIndex = typeof field?.orderIndex === 'number' ? field.orderIndex : index
|
||||
result.orderIndex = orderIndex
|
||||
return result
|
||||
})
|
||||
.filter((field): field is PieceModelCustomField => !!field)
|
||||
}
|
||||
|
||||
const sanitizePieceProducts = (products: any[]): PieceModelProduct[] => {
|
||||
return sanitizeProducts(products) as PieceModelProduct[]
|
||||
}
|
||||
|
||||
export const normalizePieceStructureForSave = (input: any): PieceModelStructure => {
|
||||
const source = clonePieceStructure(input)
|
||||
const restEntries = Object.entries(source).filter(
|
||||
([key]) => key !== 'customFields' && key !== 'products',
|
||||
)
|
||||
return {
|
||||
...Object.fromEntries(restEntries),
|
||||
products: sanitizePieceProducts(source.products || []),
|
||||
customFields: sanitizePieceCustomFields(source.customFields),
|
||||
}
|
||||
}
|
||||
|
||||
const hydratePieceCustomFields = (fields: any[]): PieceModelStructureEditorField[] => {
|
||||
if (!Array.isArray(fields)) {
|
||||
return []
|
||||
}
|
||||
|
||||
return fields.map((field, index) => ({
|
||||
name: field?.name ?? '',
|
||||
type: field?.type ?? 'text',
|
||||
required: !!field?.required,
|
||||
options: Array.isArray(field?.options) ? field.options : undefined,
|
||||
optionsText: typeof field?.optionsText === 'string'
|
||||
? field.optionsText
|
||||
: Array.isArray(field?.options)
|
||||
? field.options.join('\n')
|
||||
: '',
|
||||
orderIndex: typeof field?.orderIndex === 'number' ? field.orderIndex : index,
|
||||
}))
|
||||
}
|
||||
|
||||
export const hydratePieceStructureForEditor = (input: any): PieceModelStructureForEditor => {
|
||||
const source = clonePieceStructure(input)
|
||||
const payload: PieceModelStructureForEditor = {
|
||||
...Object.fromEntries(
|
||||
Object.entries(source).filter(([key]) => key !== 'customFields' && key !== 'products'),
|
||||
),
|
||||
products: hydrateProducts(source.products || []) as PieceModelProduct[],
|
||||
customFields: hydratePieceCustomFields(source.customFields),
|
||||
}
|
||||
return payload
|
||||
}
|
||||
|
||||
export const formatPieceStructurePreview = (structure: any) => {
|
||||
if (!structure || typeof structure !== 'object') {
|
||||
return 'Aucun champ personnalisé'
|
||||
}
|
||||
|
||||
const customFields = Array.isArray((structure as any).customFields)
|
||||
? (structure as any).customFields.length
|
||||
: 0
|
||||
const products = Array.isArray((structure as any).products)
|
||||
? (structure as any).products.length
|
||||
: 0
|
||||
|
||||
if (!customFields && !products) {
|
||||
return 'Aucun produit ni champ personnalisé'
|
||||
}
|
||||
|
||||
const segments: string[] = []
|
||||
if (products) {
|
||||
segments.push(`${products} produit(s)`)
|
||||
}
|
||||
if (customFields) {
|
||||
segments.push(`${customFields} champ(s) personnalisé(s)`)
|
||||
}
|
||||
|
||||
return segments.join(' · ')
|
||||
}
|
||||
|
||||
export const normalizeProductStructureForSave = (input: any): ProductModelStructure =>
|
||||
normalizePieceStructureForSave(input)
|
||||
|
||||
export const hydrateProductStructureForEditor = (input: any) =>
|
||||
hydratePieceStructureForEditor(input)
|
||||
|
||||
export const formatProductStructurePreview = (structure: any) =>
|
||||
formatPieceStructurePreview(structure)
|
||||
Reference in New Issue
Block a user