Extract assignment normalization utils to shared/utils/assignmentUtils.ts. Extract selection state management to composables/useMachineCreateSelections.ts. Extract preview computation and validation to composables/useMachineCreatePreview.ts. Wire machines/new.vue to use extracted modules (-47% LOC). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
221 lines
6.7 KiB
TypeScript
221 lines
6.7 KiB
TypeScript
/**
|
||
* Entity assignment normalization and display utilities.
|
||
*
|
||
* Extracted from pages/machines/new.vue – these pure functions resolve
|
||
* machine / component / piece assignments from nested API payloads.
|
||
*/
|
||
|
||
type AnyRecord = Record<string, unknown>
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Primitive helpers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
const isPlainObject = (value: unknown): value is AnyRecord =>
|
||
value !== null && typeof value === 'object' && !Array.isArray(value)
|
||
|
||
const toTrimmedString = (value: unknown): string | null => {
|
||
if (typeof value === 'string') {
|
||
const trimmed = value.trim()
|
||
return trimmed.length > 0 ? trimmed : null
|
||
}
|
||
if (typeof value === 'number' && Number.isFinite(value)) {
|
||
return String(value)
|
||
}
|
||
return null
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Dedup
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export const dedupeAssignments = (
|
||
assignments: AnyRecord[],
|
||
): AnyRecord[] => {
|
||
const seen = new Set<string>()
|
||
return assignments.filter((assignment) => {
|
||
if (!assignment) return false
|
||
const id = assignment.id != null ? String(assignment.id) : ''
|
||
const name = assignment.name != null ? String(assignment.name) : ''
|
||
const key = `${id}::${name}`
|
||
if (!id && !name) return false
|
||
if (seen.has(key)) return false
|
||
seen.add(key)
|
||
return true
|
||
})
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Machine assignments
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export const normalizeMachineAssignment = (input: unknown): AnyRecord | null => {
|
||
if (!input) return null
|
||
if (typeof input === 'string') {
|
||
const name = toTrimmedString(input)
|
||
return name ? { id: name, name } : null
|
||
}
|
||
if (typeof input === 'number' && Number.isFinite(input)) {
|
||
const value = String(input)
|
||
return { id: value, name: value }
|
||
}
|
||
|
||
const container = (input as AnyRecord).machine || (input as AnyRecord).machineData || input
|
||
if (!isPlainObject(container)) return null
|
||
|
||
const id =
|
||
container.id ?? (input as AnyRecord).machineId ?? (input as AnyRecord).id ?? null
|
||
const name =
|
||
container.name ||
|
||
(input as AnyRecord).machineName ||
|
||
container.label ||
|
||
container.title ||
|
||
(typeof id === 'string' ? id : null) ||
|
||
(typeof id === 'number' ? String(id) : null)
|
||
|
||
if (id == null && name == null) return null
|
||
|
||
return {
|
||
id: id != null ? id : null,
|
||
name: name != null ? name : null,
|
||
}
|
||
}
|
||
|
||
export const collectMachineAssignments = (source: unknown): AnyRecord[] => {
|
||
if (!isPlainObject(source)) return []
|
||
|
||
const candidates = [
|
||
source.machines,
|
||
source.machineLinks,
|
||
source.machineAssignments,
|
||
source.machinesAssignments,
|
||
source.linkedMachines,
|
||
]
|
||
|
||
const assignments: AnyRecord[] = []
|
||
|
||
candidates.forEach((list) => {
|
||
if (Array.isArray(list)) {
|
||
list.forEach((item) => {
|
||
const normalized = normalizeMachineAssignment(item)
|
||
if (normalized) assignments.push(normalized)
|
||
})
|
||
}
|
||
})
|
||
|
||
if (!assignments.length) {
|
||
const direct = normalizeMachineAssignment(source.machine)
|
||
if (direct) assignments.push(direct)
|
||
}
|
||
|
||
if (!assignments.length) {
|
||
const idCandidate = source.machineId ?? source.machineID ?? null
|
||
const nameCandidate = source.machineName ?? null
|
||
const normalized = normalizeMachineAssignment(nameCandidate || idCandidate)
|
||
if (normalized) assignments.push(normalized)
|
||
}
|
||
|
||
return dedupeAssignments(assignments)
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Component assignments
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export const normalizeComponentAssignment = (input: unknown): AnyRecord | null => {
|
||
if (!input) return null
|
||
if (typeof input === 'string') {
|
||
const value = toTrimmedString(input)
|
||
return value ? { id: value, name: value } : null
|
||
}
|
||
if (typeof input === 'number' && Number.isFinite(input)) {
|
||
const value = String(input)
|
||
return { id: value, name: value }
|
||
}
|
||
|
||
const container =
|
||
(input as AnyRecord).component || (input as AnyRecord).composant || input
|
||
if (!isPlainObject(container)) return null
|
||
|
||
const id =
|
||
container.id ??
|
||
(input as AnyRecord).componentId ??
|
||
(input as AnyRecord).composantId ??
|
||
(input as AnyRecord).id ??
|
||
null
|
||
const name =
|
||
container.name ||
|
||
(input as AnyRecord).componentName ||
|
||
(input as AnyRecord).composantName ||
|
||
container.label ||
|
||
(typeof id === 'string' ? id : null) ||
|
||
(typeof id === 'number' ? String(id) : null)
|
||
|
||
if (id == null && name == null) return null
|
||
|
||
return {
|
||
id: id != null ? id : null,
|
||
name: name != null ? name : null,
|
||
}
|
||
}
|
||
|
||
export const collectComponentAssignments = (source: unknown): AnyRecord[] => {
|
||
if (!isPlainObject(source)) return []
|
||
|
||
const candidates = [
|
||
source.components,
|
||
source.composants,
|
||
source.componentLinks,
|
||
source.linkedComponents,
|
||
]
|
||
|
||
const assignments: AnyRecord[] = []
|
||
|
||
candidates.forEach((list) => {
|
||
if (Array.isArray(list)) {
|
||
list.forEach((item) => {
|
||
const normalized = normalizeComponentAssignment(item)
|
||
if (normalized) assignments.push(normalized)
|
||
})
|
||
}
|
||
})
|
||
|
||
if (!assignments.length) {
|
||
const direct = normalizeComponentAssignment(source.component || source.composant)
|
||
if (direct) assignments.push(direct)
|
||
}
|
||
|
||
if (!assignments.length) {
|
||
const idCandidate = source.componentId ?? source.composantId ?? null
|
||
const normalized = normalizeComponentAssignment(idCandidate)
|
||
if (normalized) assignments.push(normalized)
|
||
}
|
||
|
||
return dedupeAssignments(assignments)
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Convenience wrappers
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export const getComponentMachineAssignments = (component: unknown): AnyRecord[] =>
|
||
collectMachineAssignments(component || {})
|
||
|
||
export const getPieceMachineAssignments = (piece: unknown): AnyRecord[] =>
|
||
collectMachineAssignments(piece || {})
|
||
|
||
export const getPieceComponentAssignments = (piece: unknown): AnyRecord[] =>
|
||
collectComponentAssignments(piece || {})
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// Display
|
||
// ---------------------------------------------------------------------------
|
||
|
||
export const formatAssignmentList = (assignments: AnyRecord[]): string => {
|
||
if (!Array.isArray(assignments) || assignments.length === 0) return ''
|
||
return assignments
|
||
.map((assignment) => (assignment?.name || assignment?.id) as string)
|
||
.filter(Boolean)
|
||
.join(', ')
|
||
}
|