diff --git a/app/components/ComponentItem.vue b/app/components/ComponentItem.vue
index 8624d41..905feab 100644
--- a/app/components/ComponentItem.vue
+++ b/app/components/ComponentItem.vue
@@ -461,16 +461,39 @@ const extractStructureCustomFields = (structure) => {
}
function fieldKeyFromNameAndType(name, type) {
- const normalizedName = typeof name === 'string' ? name.trim() : ''
- const normalizedType = typeof type === 'string' ? type : ''
+ const normalizedName =
+ typeof name === 'string' ? name.trim().toLowerCase() : ''
+ const normalizedType =
+ typeof type === 'string' ? type.trim().toLowerCase() : ''
return normalizedName ? `${normalizedName}::${normalizedType}` : null
}
+function resolveOrderIndex(field) {
+ if (!field || typeof field !== 'object') {
+ return 0
+ }
+ if (typeof field.orderIndex === 'number') {
+ return field.orderIndex
+ }
+ if (
+ field.customField &&
+ typeof field.customField.orderIndex === 'number'
+ ) {
+ return field.customField.orderIndex
+ }
+ return 0
+}
+
function deduplicateFieldDefinitions(definitions) {
const result = []
const seen = new Set()
- ;(Array.isArray(definitions) ? definitions : []).forEach((field) => {
+ const orderedDefinitions = (Array.isArray(definitions)
+ ? definitions.slice()
+ : []
+ ).sort((a, b) => resolveOrderIndex(a) - resolveOrderIndex(b))
+
+ orderedDefinitions.forEach((field) => {
if (!field || typeof field !== 'object') {
return
}
@@ -490,6 +513,7 @@ function deduplicateFieldDefinitions(definitions) {
if (key) {
seen.add(key)
}
+ field.orderIndex = resolveOrderIndex(field)
result.push(field)
})
@@ -530,10 +554,16 @@ function mergeFieldDefinitionsWithValues(definitions, values) {
if (!matchedValue) {
return {
...field,
- value: field?.value ?? ''
+ value: field?.value ?? '',
+ orderIndex: resolveOrderIndex(field),
}
}
+ const resolvedOrder = Math.min(
+ resolveOrderIndex(field),
+ resolveOrderIndex(matchedValue.customField),
+ )
+
return {
...field,
customFieldValueId: matchedValue.id ?? field.customFieldValueId ?? null,
@@ -543,7 +573,8 @@ function mergeFieldDefinitionsWithValues(definitions, values) {
fieldId ??
null,
customField: matchedValue.customField ?? field.customField ?? null,
- value: matchedValue.value ?? field.value ?? ''
+ value: matchedValue.value ?? field.value ?? '',
+ orderIndex: resolvedOrder,
}
})
@@ -583,23 +614,30 @@ function mergeFieldDefinitionsWithValues(definitions, values) {
required: entry.customField?.required ?? false,
options: entry.customField?.options ?? [],
value: entry.value ?? '',
- customField: entry.customField ?? null
+ customField: entry.customField ?? null,
+ orderIndex: resolveOrderIndex(entry.customField),
})
}
})
- return merged
+ return merged.sort((a, b) => resolveOrderIndex(a) - resolveOrderIndex(b))
}
function dedupeMergedFields(fields) {
if (!Array.isArray(fields) || fields.length <= 1) {
- return Array.isArray(fields) ? fields : []
+ return Array.isArray(fields)
+ ? fields.slice().sort((a, b) => resolveOrderIndex(a) - resolveOrderIndex(b))
+ : []
}
const seen = new Map()
const result = []
- fields.forEach((field) => {
+ const orderedFields = fields
+ .slice()
+ .sort((a, b) => resolveOrderIndex(a) - resolveOrderIndex(b))
+
+ orderedFields.forEach((field) => {
if (!field || typeof field !== 'object') {
return
}
@@ -617,12 +655,14 @@ function dedupeMergedFields(fields) {
const key = fieldId || nameKey
if (!key) {
+ field.orderIndex = resolveOrderIndex(field)
result.push(field)
return
}
const existing = seen.get(key)
if (!existing) {
+ field.orderIndex = resolveOrderIndex(field)
seen.set(key, field)
result.push(field)
return
@@ -640,11 +680,15 @@ function dedupeMergedFields(fields) {
if (!existingHasValue && incomingHasValue) {
Object.assign(existing, field)
+ existing.orderIndex = Math.min(
+ resolveOrderIndex(existing),
+ resolveOrderIndex(field),
+ )
seen.set(key, existing)
}
})
- return result
+ return result.sort((a, b) => resolveOrderIndex(a) - resolveOrderIndex(b))
}
const componentDefinitionSources = computed(() => {
diff --git a/app/components/CustomFieldsDisplay.vue b/app/components/CustomFieldsDisplay.vue
index c75fb76..8f35667 100644
--- a/app/components/CustomFieldsDisplay.vue
+++ b/app/components/CustomFieldsDisplay.vue
@@ -5,7 +5,7 @@
@@ -81,7 +81,7 @@
diff --git a/app/components/TypeEditForm.vue b/app/components/TypeEditForm.vue
index 9bab4c5..33f1ec0 100644
--- a/app/components/TypeEditForm.vue
+++ b/app/components/TypeEditForm.vue
@@ -54,14 +54,33 @@ const emit = defineEmits(['update:modelValue', 'submit'])
const deepClone = value => JSON.parse(JSON.stringify(value))
+const createFieldKey = () => `cf-${Math.random().toString(16).slice(2)}-${Date.now()}`
+
+const withNormalizedOrder = (items = []) => {
+ if (!Array.isArray(items)) { return [] }
+ return items
+ .map((item, index) => {
+ const clone = deepClone(item)
+ const currentOrder =
+ typeof clone?.orderIndex === 'number' ? clone.orderIndex : index
+ clone.orderIndex = currentOrder
+ if (typeof clone?.__key !== 'string' || !clone.__key) {
+ clone.__key = createFieldKey()
+ }
+ return clone
+ })
+ .sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0))
+ .map((item, index) => ({ ...item, orderIndex: index }))
+}
+
const createDefaultForm = (source = {}) => ({
name: source.name || '',
description: source.description || '',
category: source.category || '',
maintenanceFrequency: source.maintenanceFrequency || '',
- customFields: deepClone(source.customFields || []),
- componentRequirements: deepClone(source.componentRequirements || []),
- pieceRequirements: deepClone(source.pieceRequirements || [])
+ customFields: withNormalizedOrder(source.customFields || []),
+ componentRequirements: withNormalizedOrder(source.componentRequirements || []),
+ pieceRequirements: withNormalizedOrder(source.pieceRequirements || [])
})
const formData = reactive(createDefaultForm(props.modelValue))
diff --git a/app/pages/component/[id]/edit.vue b/app/pages/component/[id]/edit.vue
index e57fa7c..d503b67 100644
--- a/app/pages/component/[id]/edit.vue
+++ b/app/pages/component/[id]/edit.vue
@@ -424,6 +424,7 @@ interface CustomFieldInput {
value: string
customFieldId: string | null
customFieldValueId: string | null
+ orderIndex: number
}
const route = useRoute()
@@ -756,6 +757,7 @@ const buildCustomFieldInputs = (
...definition,
customFieldId: definition.customFieldId || definition.id,
customFieldValueId: null,
+ orderIndex: definition.orderIndex,
}
}
@@ -765,8 +767,14 @@ const buildCustomFieldInputs = (
customFieldId: matched.customField?.id || definition.customFieldId || definition.id,
customFieldValueId: matched.id ?? null,
value: formatDefaultValue(definition.type, resolvedValue),
+ orderIndex: Math.min(
+ definition.orderIndex ?? 0,
+ typeof matched.customField?.orderIndex === 'number'
+ ? matched.customField.orderIndex
+ : definition.orderIndex ?? 0,
+ ),
}
- })
+ }).sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0))
return resolved
}
@@ -780,11 +788,12 @@ const normalizeCustomFieldInputs = (structure: ComponentModelStructure | null):
}
const fields = Array.isArray(structure.customFields) ? structure.customFields : []
return fields
- .map((field) => normalizeCustomField(field))
+ .map((field, index) => normalizeCustomField(field, index))
.filter((field): field is CustomFieldInput => field !== null)
+ .sort((a, b) => a.orderIndex - b.orderIndex)
}
-const normalizeCustomField = (rawField: any): CustomFieldInput | null => {
+const normalizeCustomField = (rawField: any, fallbackIndex = 0): CustomFieldInput | null => {
if (!rawField || typeof rawField !== 'object') {
return null
}
@@ -802,7 +811,8 @@ const normalizeCustomField = (rawField: any): CustomFieldInput | null => {
const customFieldValueId = typeof rawField.customFieldValueId === 'string'
? rawField.customFieldValueId
: null
- return { id, name, type, required, options, value, customFieldId, customFieldValueId }
+ const orderIndex = typeof rawField.orderIndex === 'number' ? rawField.orderIndex : fallbackIndex
+ return { id, name, type, required, options, value, customFieldId, customFieldValueId, orderIndex }
}
const resolveFieldName = (field: any): string => {
diff --git a/app/pages/component/create.vue b/app/pages/component/create.vue
index e381119..2d1bc6a 100644
--- a/app/pages/component/create.vue
+++ b/app/pages/component/create.vue
@@ -841,6 +841,7 @@ interface CustomFieldInput {
value: string
customFieldId: string | null
customFieldValueId: string | null
+ orderIndex: number
}
const fieldKey = (field: CustomFieldInput, index: number) =>
@@ -852,11 +853,12 @@ const normalizeCustomFieldInputs = (structure: ComponentModelStructure | null):
}
const fields = Array.isArray(structure.customFields) ? structure.customFields : []
return fields
- .map((field) => normalizeCustomField(field))
+ .map((field, index) => normalizeCustomField(field, index))
.filter((field): field is CustomFieldInput => field !== null)
+ .sort((a, b) => a.orderIndex - b.orderIndex)
}
-const normalizeCustomField = (rawField: any): CustomFieldInput | null => {
+const normalizeCustomField = (rawField: any, fallbackIndex = 0): CustomFieldInput | null => {
if (!rawField || typeof rawField !== 'object') {
return null
}
@@ -874,7 +876,8 @@ const normalizeCustomField = (rawField: any): CustomFieldInput | null => {
const customFieldValueId = typeof rawField.customFieldValueId === 'string'
? rawField.customFieldValueId
: null
- return { id, name, type, required, options, value, customFieldId, customFieldValueId }
+ const orderIndex = typeof rawField.orderIndex === 'number' ? rawField.orderIndex : fallbackIndex
+ return { id, name, type, required, options, value, customFieldId, customFieldValueId, orderIndex }
}
const resolveFieldName = (field: any): string => {
diff --git a/app/pages/machine-skeleton/new.vue b/app/pages/machine-skeleton/new.vue
index 49dc61b..26112cd 100644
--- a/app/pages/machine-skeleton/new.vue
+++ b/app/pages/machine-skeleton/new.vue
@@ -139,12 +139,15 @@ const parseOptions = (field = {}) => {
const normalizeCustomFields = (fields = []) =>
fields
.filter(field => field?.name && field.name.trim() !== '')
- .map(field => ({
+ .map((field, index) => ({
name: field.name,
type: field.type || '',
required: !!field.required,
- options: parseOptions(field)
+ options: parseOptions(field),
+ orderIndex: typeof field.orderIndex === 'number' ? field.orderIndex : index
}))
+ .sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0))
+ .map((field, index) => ({ ...field, orderIndex: index }))
const toIntegerOrNull = (value, fallback = null) => {
if (value === '' || value === undefined || value === null) {
diff --git a/app/pages/machine/[id].vue b/app/pages/machine/[id].vue
index a5f0d54..4fdb4c8 100644
--- a/app/pages/machine/[id].vue
+++ b/app/pages/machine/[id].vue
@@ -1853,6 +1853,12 @@ const visibleMachineCustomFields = computed(() => {
const summarizeCustomFields = (fields = []) => {
const seen = new Set()
return fields
+ .slice()
+ .sort((a, b) => {
+ const left = typeof a?.orderIndex === 'number' ? a.orderIndex : 0
+ const right = typeof b?.orderIndex === 'number' ? b.orderIndex : 0
+ return left - right
+ })
.filter(shouldDisplayCustomField)
.filter((field) => {
const key = field.customFieldId || field.id || field.name
@@ -2013,11 +2019,12 @@ const normalizeExistingCustomFieldDefinitions = (fields) => {
return []
}
return fields
- .map((field) => normalizeCustomFieldDefinitionEntry(field))
+ .map((field, index) => normalizeCustomFieldDefinitionEntry(field, index))
.filter((definition) => definition !== null)
+ .sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0))
}
-const normalizeCustomFieldDefinitionEntry = (definition = {}) => {
+const normalizeCustomFieldDefinitionEntry = (definition = {}, fallbackIndex = 0) => {
const name = extractDefinitionName(definition)
if (!name) {
return null
@@ -2028,6 +2035,7 @@ const normalizeCustomFieldDefinitionEntry = (definition = {}) => {
const defaultValue = extractDefinitionDefaultValue(definition)
const id = typeof definition?.id === 'string' ? definition.id : undefined
const customFieldId = typeof definition?.customFieldId === 'string' ? definition.customFieldId : id
+ const orderIndex = typeof definition?.orderIndex === 'number' ? definition.orderIndex : fallbackIndex
return {
id,
customFieldId,
@@ -2037,6 +2045,7 @@ const normalizeCustomFieldDefinitionEntry = (definition = {}) => {
options,
defaultValue,
readOnly: !!definition?.readOnly,
+ orderIndex,
}
}
diff --git a/app/pages/pieces/[id]/edit.vue b/app/pages/pieces/[id]/edit.vue
index 3e0fd9b..ce2e891 100644
--- a/app/pages/pieces/[id]/edit.vue
+++ b/app/pages/pieces/[id]/edit.vue
@@ -383,6 +383,7 @@ interface CustomFieldInput {
value: string
customFieldId: string | null
customFieldValueId: string | null
+ orderIndex: number
}
const route = useRoute()
@@ -706,6 +707,7 @@ const buildCustomFieldInputs = (
...definition,
customFieldId: definition.customFieldId || definition.id,
customFieldValueId: null,
+ orderIndex: definition.orderIndex,
}
}
@@ -715,8 +717,14 @@ const buildCustomFieldInputs = (
customFieldId: matched.customField?.id || definition.customFieldId || definition.id,
customFieldValueId: matched.id ?? null,
value: formatDefaultValue(definition.type, resolvedValue),
+ orderIndex: Math.min(
+ definition.orderIndex ?? 0,
+ typeof matched.customField?.orderIndex === 'number'
+ ? matched.customField.orderIndex
+ : definition.orderIndex ?? 0,
+ ),
}
- })
+ }).sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0))
return resolved
}
@@ -730,11 +738,12 @@ const normalizeCustomFieldInputs = (structure: PieceModelStructure | null): Cust
}
const fields = Array.isArray(structure.customFields) ? structure.customFields : []
return fields
- .map((field) => normalizeCustomField(field))
+ .map((field, index) => normalizeCustomField(field, index))
.filter((field): field is CustomFieldInput => field !== null)
+ .sort((a, b) => a.orderIndex - b.orderIndex)
}
-const normalizeCustomField = (rawField: any): CustomFieldInput | null => {
+const normalizeCustomField = (rawField: any, fallbackIndex = 0): CustomFieldInput | null => {
if (!rawField || typeof rawField !== 'object') {
return null
}
@@ -755,7 +764,9 @@ const normalizeCustomField = (rawField: any): CustomFieldInput | null => {
? rawField.customFieldValueId
: null
- return { id, name, type, required, options, value, customFieldId, customFieldValueId }
+ const orderIndex = typeof rawField.orderIndex === 'number' ? rawField.orderIndex : fallbackIndex
+
+ return { id, name, type, required, options, value, customFieldId, customFieldValueId, orderIndex }
}
const resolveFieldName = (field: any): string => {
diff --git a/app/pages/pieces/create.vue b/app/pages/pieces/create.vue
index ec2437b..7952368 100644
--- a/app/pages/pieces/create.vue
+++ b/app/pages/pieces/create.vue
@@ -467,6 +467,7 @@ interface CustomFieldInput {
value: string
customFieldId: string | null
customFieldValueId: string | null
+ orderIndex: number
}
const fieldKey = (field: CustomFieldInput, index: number) =>
@@ -478,11 +479,12 @@ const normalizeCustomFieldInputs = (structure: PieceModelStructure | null): Cust
}
const fields = Array.isArray(structure.customFields) ? structure.customFields : []
return fields
- .map((field) => normalizeCustomField(field))
+ .map((field, index) => normalizeCustomField(field, index))
.filter((field): field is CustomFieldInput => field !== null)
+ .sort((a, b) => a.orderIndex - b.orderIndex)
}
-const normalizeCustomField = (rawField: any): CustomFieldInput | null => {
+const normalizeCustomField = (rawField: any, fallbackIndex = 0): CustomFieldInput | null => {
if (!rawField || typeof rawField !== 'object') {
return null
}
@@ -503,7 +505,9 @@ const normalizeCustomField = (rawField: any): CustomFieldInput | null => {
? rawField.customFieldValueId
: null
- return { id, name, type, required, options, value, customFieldId, customFieldValueId }
+ const orderIndex = typeof rawField.orderIndex === 'number' ? rawField.orderIndex : fallbackIndex
+
+ return { id, name, type, required, options, value, customFieldId, customFieldValueId, orderIndex }
}
const resolveFieldName = (field: any): string => {
diff --git a/app/pages/type/edit/[id].vue b/app/pages/type/edit/[id].vue
index 15cc7f4..dcaba98 100644
--- a/app/pages/type/edit/[id].vue
+++ b/app/pages/type/edit/[id].vue
@@ -92,12 +92,15 @@ const parseOptions = (field = {}) => {
const normalizeCustomFields = (fields = []) =>
fields
.filter(field => field?.name && field.name.trim() !== '')
- .map(field => ({
+ .map((field, index) => ({
name: field.name,
type: field.type || '',
required: !!field.required,
- options: parseOptions(field)
+ options: parseOptions(field),
+ orderIndex: typeof field.orderIndex === 'number' ? field.orderIndex : index
}))
+ .sort((a, b) => (a.orderIndex ?? 0) - (b.orderIndex ?? 0))
+ .map((field, index) => ({ ...field, orderIndex: index }))
const toIntegerOrNull = (value, fallback = null) => {
if (value === '' || value === undefined || value === null) {
diff --git a/app/shared/modelUtils.ts b/app/shared/modelUtils.ts
index 68d6025..fbe1522 100644
--- a/app/shared/modelUtils.ts
+++ b/app/shared/modelUtils.ts
@@ -94,7 +94,7 @@ const sanitizeCustomFields = (fields: any[]): ComponentModelCustomField[] => {
}
return fields
- .map((field) => {
+ .map((field, index) => {
const rawName =
typeof field?.name === 'string'
? field.name
@@ -173,6 +173,8 @@ const sanitizeCustomFields = (fields: any[]): ComponentModelCustomField[] => {
if (customFieldId) {
result.customFieldId = customFieldId
}
+ const orderIndex = typeof field?.orderIndex === 'number' ? field.orderIndex : index
+ result.orderIndex = orderIndex
return result
})
.filter((field): field is ComponentModelCustomField => !!field)
@@ -448,7 +450,7 @@ const hydrateCustomFields = (fields: any[]): any[] => {
return []
}
- return fields.map((field) => {
+ return fields.map((field, index) => {
const valueObject = extractFieldValueObject(field)
const name = typeof field?.name === 'string'
? field.name
@@ -513,6 +515,7 @@ const hydrateCustomFields = (fields: any[]): any[] => {
const id = typeof field?.id === 'string' ? field.id : undefined
const customFieldId = typeof field?.customFieldId === 'string' ? field.customFieldId : undefined
+ const orderIndex = typeof field?.orderIndex === 'number' ? field.orderIndex : index
return {
name,
@@ -523,6 +526,7 @@ const hydrateCustomFields = (fields: any[]): any[] => {
defaultValue,
id,
customFieldId,
+ orderIndex,
}
})
}
@@ -580,7 +584,7 @@ const mapComponentCustomFields = (fields: any[]) => {
if (!Array.isArray(fields)) {
return []
}
- return hydrateCustomFields(fields).map((field) => {
+ return hydrateCustomFields(fields).map((field, index) => {
const defaultValue =
field?.defaultValue !== undefined && field?.defaultValue !== null && field?.defaultValue !== ''
? field.defaultValue
@@ -597,6 +601,7 @@ const mapComponentCustomFields = (fields: any[]) => {
typeof (field as any)?.customFieldId === 'string'
? (field as any).customFieldId
: undefined,
+ orderIndex: typeof field?.orderIndex === 'number' ? field.orderIndex : index,
}
})
}
@@ -772,7 +777,7 @@ const sanitizePieceCustomFields = (fields: any[]): PieceModelCustomField[] => {
}
return fields
- .map((field) => {
+ .map((field, index) => {
const name = typeof field?.name === 'string' ? field.name.trim() : ''
if (!name) {
return null
@@ -799,6 +804,8 @@ const sanitizePieceCustomFields = (fields: any[]): PieceModelCustomField[] => {
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)
@@ -819,7 +826,7 @@ const hydratePieceCustomFields = (fields: any[]): PieceModelStructureEditorField
return []
}
- return fields.map((field) => ({
+ return fields.map((field, index) => ({
name: field?.name ?? '',
type: field?.type ?? 'text',
required: !!field?.required,
@@ -829,6 +836,7 @@ const hydratePieceCustomFields = (fields: any[]): PieceModelStructureEditorField
: Array.isArray(field?.options)
? field.options.join('\n')
: '',
+ orderIndex: typeof field?.orderIndex === 'number' ? field.orderIndex : index,
}))
}
diff --git a/app/shared/types/inventory.ts b/app/shared/types/inventory.ts
index c09546e..d842b5b 100644
--- a/app/shared/types/inventory.ts
+++ b/app/shared/types/inventory.ts
@@ -9,6 +9,7 @@ export interface ComponentModelCustomField {
optionsText?: string
id?: string
customFieldId?: string
+ orderIndex?: number
}
export interface ComponentModelPiece {
@@ -40,6 +41,7 @@ export interface PieceModelCustomField {
type: PieceModelCustomFieldType
required: boolean
options?: string[]
+ orderIndex?: number
}
export interface PieceModelStructure {