dx: support objects for ArrayField.value
Massimo Melina committed
Jun 26, 2025 at 00:30 UTC
227a150e2d9c524b37ffeb53ce9359f0d524a7a9
1 file changed
+21
-22
admin/src/ArrayField.ts
+21
-22
@@ -1,18 +1,15 @@
1
-// This file is part of HFS - Copyright 2021-2023, Massimo Melina <a@rejetto.com> - License https://www.gnu.org/licenses/gpl-3.0.txt
2
-
3
-import { createElement as h, Fragment, useMemo, useState, isValidElement } from 'react'
4
-import { callable, DialogOptions, Dict, Functionable, isOrderedEqual, setHidden, swap } from './misc'
1
+import { createElement as h, Fragment, isValidElement, useMemo, useState } from 'react'
2
+import { callable, Dict, Functionable, isOrderedEqual, setHidden, swap } from './misc'
3
import { Add, Edit, Delete, ArrowUpward, ArrowDownward, Undo, Check } from '@mui/icons-material'
6
-import { FormDialog, formDialog } from './dialog'
4
+import { DialogOptions, FormDialog, formDialog } from './dialog'
5
import { GridActionsCellItem, GridAlignment, GridColDef } from '@mui/x-data-grid'
6
import { BoolField, FieldDescriptor, FieldProps, labelFromKey } from '@hfs/mui-grid-form'
7
import { Box, FormHelperText, FormLabel } from '@mui/material'
10
-import { DateTimeField } from './DateTimeField'
8
import _ from 'lodash'
9
import { Center, Flex, IconBtn, useBreakpoint } from './mui'
10
import { DataTable, DataTableColumn } from './DataTable'
11
15
-type ArrayFieldProps<T> = FieldProps<T[]> & {
12
+type ArrayFieldProps<T> = FieldProps<T[] | Dict<T>> & {
13
fields: Functionable<FieldDescriptor[] & {
14
$width?: number,
15
$column?: Partial<DataTableColumn>,
@@ -29,20 +26,22 @@ type ArrayFieldProps<T> = FieldProps<T[]> & {
26
form?: Partial<FormDialog<any>>,
27
dialog?: Partial<DialogOptions>,
28
details?: boolean
29
+ objectK?: string
30
}
33
-export function ArrayField<T extends object>({ label, helperText, fields, value, onChange, onError, setApi, reorder, prepend, noRows, valuesForAdd, autoRowHeight, dialog, form, details, ...rest }: ArrayFieldProps<T>) {
34
- if (!Array.isArray(value)) // avoid crash if non-array values are passed, especially developing plugins
35
- value = []
36
- const rows = useMemo(() => value!.map((x,$idx) =>
31
+export function ArrayField<T extends object>({ label, helperText, fields, value, onChange, onError, setApi, reorder, prepend, noRows, valuesForAdd, autoRowHeight, dialog, form, details, objectK, ...rest }: ArrayFieldProps<T>) {
32
+ const valueA = Array.isArray(value) ? value
33
+ : !objectK || !value ? [] // avoid crash if non-array values are passed, especially developing plugins
34
+ : Object.entries(value).map(([k, v]) => ({ [objectK]: k, ...v }))
35
+ const rows = useMemo(() => valueA!.map((x,$idx) =>
36
setHidden({ ...x } as any, x.hasOwnProperty('id') ? { $idx } : { id: $idx })),
38
- [JSON.stringify(value)]) //eslint-disable-line
37
+ [JSON.stringify(valueA)]) //eslint-disable-line
38
const formProp = (values: any) => ({
39
fields: callable(fields, values).map(({ $width, $column, $type, $hideUnder, showIf, $render, $mergeRender, ...rest }) =>
40
(!showIf || showIf(values)) && _.defaults(rest, byType[$type]?.field)),
41
...form,
42
})
43
setApi?.({ isEqual: isOrderedEqual }) // don't rely on stringify, as it wouldn't work with non-json values
45
- const [undo, setUndo] = useState<typeof value>()
44
+ const [undo, setUndo] = useState<typeof valueA>()
45
return h(Fragment, {},
46
h(Flex, { rowGap: 0, flexWrap: 'wrap' },
47
label && h(FormLabel, { sx: { ml: .5 } }, label),
@@ -102,7 +101,7 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
101
onClick: ev =>
102
formDialog<T>({ form: formProp, title, values: valuesForAdd, dialogProps: dialog }).then(x => {
103
if (!x) return
105
- const newValue = value?.slice() || []
104
+ const newValue = valueA.slice() || []
105
if (prepend) newValue.unshift(x)
106
else newValue.push(x)
107
set(newValue, ev)
@@ -133,7 +132,7 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
132
dialogProps: dialog
133
})
134
if (res)
136
- set(value!.map((oldRec, i) => i === $idx ? res : oldRec), ev)
135
+ set(valueA.map((oldRec, i) => i === $idx ? res : oldRec), ev)
136
}
137
}),
138
h(GridActionsCellItem as any, {
@@ -142,7 +141,7 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
141
showInMenu: reorder,
142
onClick: ev => {
143
ev.stopPropagation()
145
- set(value!.filter((_rec, i) => i !== $idx), ev)
144
+ set(valueA.filter((_rec, i) => i !== $idx), ev)
145
},
146
}),
147
reorder && $idx && h(GridActionsCellItem as any, {
@@ -151,7 +150,7 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
150
showInMenu: true,
151
onClick: ev => {
152
ev.stopPropagation()
154
- set(swap(value!.slice(), $idx, $idx - 1), ev)
153
+ set(swap(valueA.slice(), $idx, $idx - 1), ev)
154
},
155
}),
156
reorder && $idx < rows.length - 1 && h(GridActionsCellItem as any, {
@@ -160,7 +159,7 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
159
showInMenu: true,
160
onClick: ev => {
161
ev.stopPropagation()
163
- set(swap(value!.slice(), $idx, $idx + 1), ev)
162
+ set(swap(valueA.slice(), $idx, $idx + 1), ev)
163
},
164
}),
165
].filter(Boolean)
@@ -171,9 +170,10 @@ export function ArrayField<T extends object>({ label, helperText, fields, value,
170
)
171
)
172
174
- function set(newValue: NonNullable<typeof value>, event?: any) {
175
- onChange(newValue, { was: value, event })
176
- setUndo(value)
173
+ function set(newValue: typeof valueA, event?: any) {
174
+ onChange(!objectK ? newValue : Object.fromEntries((newValue as any).map(({ [objectK]: k, ...v }) => [k, v])),
175
+ { was: value, event })
176
+ setUndo(valueA)
177
}
178
179
}
@@ -184,7 +184,6 @@ const byType: Dict<{ field?: Partial<FieldDescriptor>, column?: Partial<GridColD
184
column: { renderCell: ({ value }) => value && h(Check) },
185
},
186
dateTime: {
187
- field: { comp: DateTimeField },
187
column: {
188
minWidth: 96, flex: 0.5,
189
renderCell: ({ value }) => value && new Date(value).toLocaleString(),