refactor(frontend) : extract component create page logic into composable
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
257
app/shared/utils/structureAssignmentHelpers.ts
Normal file
257
app/shared/utils/structureAssignmentHelpers.ts
Normal file
@@ -0,0 +1,257 @@
|
||||
/**
|
||||
* Pure helper functions for building, validating and serializing
|
||||
* component structure assignment trees.
|
||||
*
|
||||
* Extracted from useComponentCreate composable to keep file sizes manageable.
|
||||
*/
|
||||
|
||||
import type { StructureAssignmentNode } from '~/components/ComponentStructureAssignmentNode.vue'
|
||||
import type {
|
||||
ComponentModelPiece,
|
||||
ComponentModelProduct,
|
||||
ComponentModelStructure,
|
||||
ComponentModelStructureNode,
|
||||
} from '~/shared/types/inventory'
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Extraction helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function extractSubcomponents(
|
||||
definition: ComponentModelStructure | ComponentModelStructureNode | null | undefined,
|
||||
): ComponentModelStructureNode[] {
|
||||
if (!definition || typeof definition !== 'object') {
|
||||
return []
|
||||
}
|
||||
const raw = Array.isArray((definition as any).subcomponents)
|
||||
? (definition as any).subcomponents
|
||||
: Array.isArray((definition as any).subComponents)
|
||||
? (definition as any).subComponents
|
||||
: []
|
||||
return raw.filter(
|
||||
(item: unknown): item is ComponentModelStructureNode =>
|
||||
!!item && typeof item === 'object',
|
||||
)
|
||||
}
|
||||
|
||||
export function extractPiecesFromNode(
|
||||
definition: ComponentModelStructure | ComponentModelStructureNode | null | undefined,
|
||||
): ComponentModelPiece[] {
|
||||
if (!definition || typeof definition !== 'object') {
|
||||
return []
|
||||
}
|
||||
const raw = Array.isArray((definition as any).pieces)
|
||||
? (definition as any).pieces
|
||||
: []
|
||||
return raw.filter(
|
||||
(item: unknown): item is ComponentModelPiece =>
|
||||
!!item && typeof item === 'object',
|
||||
)
|
||||
}
|
||||
|
||||
export function extractProductsFromNode(
|
||||
definition: ComponentModelStructure | ComponentModelStructureNode | null | undefined,
|
||||
): ComponentModelProduct[] {
|
||||
if (!definition || typeof definition !== 'object') {
|
||||
return []
|
||||
}
|
||||
const raw = Array.isArray((definition as any).products)
|
||||
? (definition as any).products
|
||||
: []
|
||||
return raw.filter(
|
||||
(item: unknown): item is ComponentModelProduct =>
|
||||
!!item && typeof item === 'object',
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Assignment tree building
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function buildAssignmentNode(
|
||||
definition: ComponentModelStructureNode | ComponentModelStructure,
|
||||
path: string,
|
||||
): StructureAssignmentNode {
|
||||
const pieces = extractPiecesFromNode(definition).map((piece, index) => ({
|
||||
path: `${path}:piece-${index}`,
|
||||
definition: piece,
|
||||
selectedPieceId: '',
|
||||
}))
|
||||
|
||||
const products = extractProductsFromNode(definition).map((product, index) => ({
|
||||
path: `${path}:product-${index}`,
|
||||
definition: product,
|
||||
selectedProductId: '',
|
||||
}))
|
||||
|
||||
const subcomponents = extractSubcomponents(definition).map(
|
||||
(child, index) => buildAssignmentNode(child, `${path}:sub-${index}`),
|
||||
)
|
||||
|
||||
return {
|
||||
path,
|
||||
definition,
|
||||
selectedComponentId: '',
|
||||
pieces,
|
||||
products,
|
||||
subcomponents,
|
||||
}
|
||||
}
|
||||
|
||||
export function initializeStructureAssignments(
|
||||
structure: ComponentModelStructure | null,
|
||||
): StructureAssignmentNode | null {
|
||||
if (!structure || typeof structure !== 'object') {
|
||||
return null
|
||||
}
|
||||
return buildAssignmentNode(structure, 'root')
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Validation
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function hasAssignments(node: StructureAssignmentNode | null): boolean {
|
||||
if (!node) {
|
||||
return false
|
||||
}
|
||||
if (node.pieces.length > 0 || node.products.length > 0 || node.subcomponents.length > 0) {
|
||||
return true
|
||||
}
|
||||
return node.subcomponents.some((child) => hasAssignments(child))
|
||||
}
|
||||
|
||||
export function isAssignmentNodeComplete(
|
||||
node: StructureAssignmentNode,
|
||||
isRootNode = false,
|
||||
): boolean {
|
||||
const piecesComplete = node.pieces.every(
|
||||
(piece) => !!piece.selectedPieceId && piece.selectedPieceId.length > 0,
|
||||
)
|
||||
const productsComplete = node.products.every(
|
||||
(product) => !!product.selectedProductId && product.selectedProductId.length > 0,
|
||||
)
|
||||
const subcomponentsComplete = node.subcomponents.every(
|
||||
(child) =>
|
||||
!!child.selectedComponentId
|
||||
&& child.selectedComponentId.length > 0
|
||||
&& isAssignmentNodeComplete(child, false),
|
||||
)
|
||||
return (
|
||||
piecesComplete
|
||||
&& productsComplete
|
||||
&& subcomponentsComplete
|
||||
&& (isRootNode || !!node.selectedComponentId)
|
||||
)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Serialization
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export function stripNullish(input: Record<string, any>) {
|
||||
return Object.fromEntries(
|
||||
Object.entries(input).filter(
|
||||
([, value]) => value !== null && value !== undefined && value !== '',
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
export function sanitizeStructureDefinition(
|
||||
definition: ComponentModelStructureNode,
|
||||
) {
|
||||
return stripNullish({
|
||||
alias: definition.alias ?? null,
|
||||
typeComposantId: definition.typeComposantId ?? null,
|
||||
typeComposantLabel: definition.typeComposantLabel ?? null,
|
||||
modelId: definition.modelId ?? null,
|
||||
familyCode: (definition as any).familyCode ?? null,
|
||||
})
|
||||
}
|
||||
|
||||
export function sanitizePieceDefinition(definition: ComponentModelPiece) {
|
||||
return stripNullish({
|
||||
role: (definition as any).role ?? null,
|
||||
typePieceId: definition.typePieceId ?? null,
|
||||
typePieceLabel: definition.typePieceLabel ?? null,
|
||||
reference: definition.reference ?? null,
|
||||
familyCode: (definition as any).familyCode ?? null,
|
||||
})
|
||||
}
|
||||
|
||||
export function sanitizeProductDefinition(definition: ComponentModelProduct) {
|
||||
return stripNullish({
|
||||
role: (definition as any).role ?? null,
|
||||
typeProductId: definition.typeProductId ?? null,
|
||||
typeProductLabel: (definition as any).typeProductLabel ?? null,
|
||||
reference: (definition as any).reference ?? null,
|
||||
familyCode: (definition as any).familyCode ?? null,
|
||||
})
|
||||
}
|
||||
|
||||
export function serializeStructureAssignments(
|
||||
root: StructureAssignmentNode | null,
|
||||
) {
|
||||
if (!root) {
|
||||
return null
|
||||
}
|
||||
|
||||
const serializeNode = (
|
||||
assignment: StructureAssignmentNode,
|
||||
isRootNode = false,
|
||||
): Record<string, any> => {
|
||||
const serializedPieces = assignment.pieces
|
||||
.filter((piece) => !!piece.selectedPieceId)
|
||||
.map((piece) =>
|
||||
stripNullish({
|
||||
path: piece.path,
|
||||
definition: sanitizePieceDefinition(piece.definition),
|
||||
selectedPieceId: piece.selectedPieceId,
|
||||
}),
|
||||
)
|
||||
|
||||
const serializedProducts = assignment.products
|
||||
.filter((product) => !!product.selectedProductId)
|
||||
.map((product) =>
|
||||
stripNullish({
|
||||
path: product.path,
|
||||
definition: sanitizeProductDefinition(product.definition),
|
||||
selectedProductId: product.selectedProductId,
|
||||
}),
|
||||
)
|
||||
|
||||
const serializedSubcomponents = assignment.subcomponents
|
||||
.map((child) => serializeNode(child, false))
|
||||
.filter((child) => Object.keys(child).length > 0)
|
||||
|
||||
const base: Record<string, any> = {
|
||||
path: assignment.path,
|
||||
definition: sanitizeStructureDefinition(assignment.definition),
|
||||
}
|
||||
|
||||
if (!isRootNode) {
|
||||
base.selectedComponentId = assignment.selectedComponentId
|
||||
}
|
||||
if (serializedPieces.length) {
|
||||
base.pieces = serializedPieces
|
||||
}
|
||||
if (serializedProducts.length) {
|
||||
base.products = serializedProducts
|
||||
}
|
||||
if (serializedSubcomponents.length) {
|
||||
base.subcomponents = serializedSubcomponents
|
||||
}
|
||||
|
||||
return stripNullish(base)
|
||||
}
|
||||
|
||||
const serializedRoot = serializeNode(root, true)
|
||||
if (
|
||||
(!serializedRoot.pieces || serializedRoot.pieces.length === 0)
|
||||
&& (!serializedRoot.products || serializedRoot.products.length === 0)
|
||||
&& (!serializedRoot.subcomponents || serializedRoot.subcomponents.length === 0)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
return serializedRoot
|
||||
}
|
||||
Reference in New Issue
Block a user