@homefile/components-v2 2.13.0 → 2.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/forms/dynamicForm/fields/AIGridField.js +1 -1
- package/dist/components/forms/dynamicForm/fields/CurrencyField.js +3 -3
- package/dist/components/forms/dynamicForm/fields/DateField.js +1 -1
- package/dist/components/forms/dynamicForm/fields/GroupField.js +1 -1
- package/dist/components/forms/dynamicForm/fields/LabeledField.d.ts +6 -0
- package/dist/components/forms/dynamicForm/fields/LabeledField.js +8 -0
- package/dist/components/forms/dynamicForm/fields/NumberField.js +15 -15
- package/dist/components/forms/dynamicForm/fields/SelectField.js +5 -5
- package/dist/components/forms/dynamicForm/fields/TextAreaField.js +6 -5
- package/dist/components/forms/dynamicForm/fields/TextField.js +5 -5
- package/dist/components/forms/dynamicForm/fields/index.d.ts +1 -0
- package/dist/components/forms/dynamicForm/fields/index.js +1 -0
- package/dist/components/inputs/DatePicker.js +3 -3
- package/dist/components/inputs/SelectInput.js +3 -3
- package/dist/components/inputs/TextInput.js +3 -2
- package/dist/stories/forms/dynamicForm/DynamicForm.stories.js +6 -2
- package/dist/utils/DynamicForm.utils.js +84 -35
- package/package.json +1 -1
- package/src/components/forms/dynamicForm/fields/AIGridField.tsx +12 -10
- package/src/components/forms/dynamicForm/fields/CurrencyField.tsx +3 -3
- package/src/components/forms/dynamicForm/fields/DateField.tsx +1 -1
- package/src/components/forms/dynamicForm/fields/GroupField.tsx +1 -1
- package/src/components/forms/dynamicForm/fields/LabeledField.tsx +22 -0
- package/src/components/forms/dynamicForm/fields/NumberField.tsx +5 -5
- package/src/components/forms/dynamicForm/fields/SelectField.tsx +2 -7
- package/src/components/forms/dynamicForm/fields/TextAreaField.tsx +16 -15
- package/src/components/forms/dynamicForm/fields/TextField.tsx +0 -1
- package/src/components/forms/dynamicForm/fields/index.ts +1 -0
- package/src/components/inputs/DatePicker.tsx +5 -5
- package/src/components/inputs/SelectInput.tsx +9 -3
- package/src/components/inputs/TextInput.tsx +4 -3
- package/src/stories/forms/dynamicForm/DynamicForm.stories.tsx +17 -3
- package/src/utils/DynamicForm.utils.ts +93 -37
|
@@ -43,5 +43,5 @@ export const AIGridField = ({ children, onAISend, onRemove, onUpload, }) => {
|
|
|
43
43
|
}
|
|
44
44
|
onAISend === null || onAISend === void 0 ? void 0 : onAISend(form);
|
|
45
45
|
};
|
|
46
|
-
return (_jsxs(Flex, { align: "center", gap: "base", children: [_jsx(SingleImage, Object.assign({}, imageProps, { onUpload: handleUpload, onRemove: onRemove, value: imageField === null || imageField === void 0 ? void 0 : imageField.value })), _jsx(Text, { fontFamily: "secondary", textAlign: "center", children: "OR" }), _jsx(TextField, Object.assign({}, textProps)), _jsx(IconButton, { "aria-label": "Add new address line", variant: "iconOutlined", icon: _jsx(Plus, { size: 28, stroke: colors.blue[3] }), onClick: handleAISend, h: "input.md", disabled: !(model === null || model === void 0 ? void 0 : model.length) && !(imageField === null || imageField === void 0 ? void 0 : imageField.value) })] }));
|
|
46
|
+
return (_jsxs(Flex, { align: "flex-end", gap: "base", children: [_jsxs(Flex, { align: "center", gap: "base", children: [_jsx(SingleImage, Object.assign({}, imageProps, { onUpload: handleUpload, onRemove: onRemove, value: imageField === null || imageField === void 0 ? void 0 : imageField.value })), _jsx(Text, { fontFamily: "secondary", textAlign: "center", children: "OR" })] }), _jsx(TextField, Object.assign({}, textProps)), _jsx(IconButton, { "aria-label": "Add new address line", variant: "iconOutlined", icon: _jsx(Plus, { size: 28, stroke: colors.blue[3] }), onClick: handleAISend, h: "input.md", disabled: !(model === null || model === void 0 ? void 0 : model.length) && !(imageField === null || imageField === void 0 ? void 0 : imageField.value) })] }));
|
|
47
47
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Controller, useFormContext } from 'react-hook-form';
|
|
3
|
-
import { Flex, Input
|
|
4
|
-
import { FormIcon } from '../../..';
|
|
3
|
+
import { Flex, Input } from '@chakra-ui/react';
|
|
4
|
+
import { FormIcon, LabeledField } from '../../..';
|
|
5
5
|
import { formatCurrency } from '../../../../utils';
|
|
6
6
|
export const CurrencyField = ({ id, icon, placeholder, value }) => {
|
|
7
7
|
const { control } = useFormContext();
|
|
@@ -12,6 +12,6 @@ export const CurrencyField = ({ id, icon, placeholder, value }) => {
|
|
|
12
12
|
const currency = formatCurrency({ value: number });
|
|
13
13
|
onChange(currency);
|
|
14
14
|
};
|
|
15
|
-
return (_jsx(
|
|
15
|
+
return (_jsx(LabeledField, { label: String(placeholder), children: _jsx(Input, { onChange: handleChange, placeholder: placeholder, textAlign: "right", value: value, _placeholder: { color: 'gray.2' } }) }));
|
|
16
16
|
} })] }));
|
|
17
17
|
};
|
|
@@ -5,6 +5,6 @@ import { DatePicker, FormIcon } from '../../..';
|
|
|
5
5
|
export const DateField = ({ id, icon, name, placeholder, showCalendarIcon = true, value, width, }) => {
|
|
6
6
|
const { control } = useFormContext();
|
|
7
7
|
return (_jsxs(Flex, { align: "center", gap: "base", w: "full", children: [name && (_jsxs(Flex, { align: "center", gap: "base", flexShrink: 0, children: [_jsx(FormIcon, { icon: icon }), _jsx(Text, { fontFamily: "secondary", noOfLines: 1, overflow: "hidden", children: name })] })), _jsx(Controller, { control: control, name: id, defaultValue: value, render: ({ field: { value, onChange } }) => {
|
|
8
|
-
return (_jsx(DatePicker, { onChange: onChange, showCalendarIcon: showCalendarIcon, placeholder: placeholder, value: value, width: width }));
|
|
8
|
+
return (_jsx(DatePicker, { onChange: onChange, showCalendarIcon: showCalendarIcon, placeholder: name ? '' : placeholder, value: value, width: width }));
|
|
9
9
|
} })] }));
|
|
10
10
|
};
|
|
@@ -3,5 +3,5 @@ import { Stack } from '@chakra-ui/react';
|
|
|
3
3
|
import { FieldWithDelete } from '../../..';
|
|
4
4
|
import { SingleFields } from './SingleFields';
|
|
5
5
|
export const GroupField = ({ id, fields, onRemove, canBeHidden, }) => {
|
|
6
|
-
return (_jsx(FieldWithDelete, { id: id, onRemove: onRemove, canBeHidden: canBeHidden, children: _jsx(Stack, { spacing: "
|
|
6
|
+
return (_jsx(FieldWithDelete, { id: id, onRemove: onRemove, canBeHidden: canBeHidden, children: _jsx(Stack, { spacing: "1", flex: "1", children: _jsx(SingleFields, { fields: fields }) }) }));
|
|
7
7
|
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, Flex } from '@chakra-ui/react';
|
|
3
|
+
export const LabeledField = ({ label, children }) => {
|
|
4
|
+
if (!label) {
|
|
5
|
+
return _jsx(_Fragment, { children: children });
|
|
6
|
+
}
|
|
7
|
+
return (_jsxs(Box, { w: "100%", children: [_jsx(Flex, { gap: "1", align: "start", children: _jsx(Text, { fontSize: "xs", fontFamily: "secondary", mb: "1", children: label }) }), children] }));
|
|
8
|
+
};
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Controller, useFormContext } from 'react-hook-form';
|
|
3
|
-
import { Flex, Input
|
|
4
|
-
import {
|
|
3
|
+
import { Flex, Input } from '@chakra-ui/react';
|
|
4
|
+
import { LabeledField } from '../../..';
|
|
5
5
|
export const NumberField = ({ description, id, icon, placeholder, value, }) => {
|
|
6
6
|
const { control } = useFormContext();
|
|
7
|
-
return (
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
return (_jsx(Flex, { align: "center", gap: "base", flex: "auto", children: _jsx(Controller, { control: control, name: id, defaultValue: value, render: ({ field: { value, onChange } }) => {
|
|
8
|
+
return (_jsx(LabeledField, { label: String(!description && placeholder), children: _jsx(Input, { onChange: (e) => {
|
|
9
|
+
const value = e.target.valueAsNumber;
|
|
10
|
+
const isNumber = !isNaN(value);
|
|
11
|
+
if (!isNumber) {
|
|
12
|
+
onChange('');
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
onChange(value);
|
|
16
|
+
}
|
|
17
|
+
}, placeholder: placeholder, type: "number", pattern: "[1-9]", value: value, textAlign: "right", _placeholder: { color: 'gray.2' } }) }));
|
|
18
|
+
} }) }));
|
|
19
19
|
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Controller, useFormContext } from 'react-hook-form';
|
|
3
3
|
import { Box, Flex } from '@chakra-ui/react';
|
|
4
|
-
import {
|
|
4
|
+
import { SelectInput } from '../../..';
|
|
5
5
|
export const SelectField = ({ description, id, icon, options, placeholder, value, }) => {
|
|
6
6
|
var _a;
|
|
7
7
|
const { control } = useFormContext();
|
|
8
8
|
const stringOptions = (_a = options === null || options === void 0 ? void 0 : options.map((option) => String(option))) !== null && _a !== void 0 ? _a : [];
|
|
9
|
-
return (
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
return (_jsx(Flex, { align: "start", gap: "base", flex: "auto", w: description ? '100%' : '40%', children: _jsx(Box, { w: description ? '102px' : '100%', children: _jsx(Controller, { control: control, name: id, defaultValue: value, render: ({ field: { value, onChange } }) => {
|
|
10
|
+
return (_jsx(SelectInput, { handleClick: onChange, height: "md", initialValue: value !== null && value !== void 0 ? value : stringOptions[0], items: stringOptions, placeholder: !description ? placeholder : '', width: "100%" }));
|
|
11
|
+
} }) }) }));
|
|
12
12
|
};
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useFormContext, Controller } from 'react-hook-form';
|
|
3
|
-
import { Flex
|
|
3
|
+
import { Flex } from '@chakra-ui/react';
|
|
4
4
|
import QuillEditor from 'react-quill-new';
|
|
5
5
|
import 'react-quill-new/dist/quill.snow.css';
|
|
6
6
|
import '../../../../styles/quill.css';
|
|
7
|
+
import { LabeledField } from './LabeledField';
|
|
7
8
|
export const TextAreaField = ({ id, icon, placeholder = '', value, }) => {
|
|
8
9
|
const { control } = useFormContext();
|
|
9
|
-
return (_jsx(
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
return (_jsx(Flex, { gap: "base", align: "start", flex: "auto", children: _jsx(Controller, { control: control, name: id, defaultValue: value, render: ({ field: { value, onChange } }) => {
|
|
11
|
+
return (_jsx(LabeledField, { label: placeholder, children: _jsx(QuillEditor, { theme: "snow", value: value, formats: formats, modules: { toolbar }, onChange: onChange, placeholder: placeholder }) }));
|
|
12
|
+
} }) }));
|
|
12
13
|
};
|
|
13
14
|
const formats = [
|
|
14
15
|
'header',
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Controller, useFormContext } from 'react-hook-form';
|
|
3
3
|
import { Flex } from '@chakra-ui/react';
|
|
4
|
-
import {
|
|
4
|
+
import { TextInput } from '../../..';
|
|
5
5
|
export const TextField = ({ id, icon, placeholder = '', type = 'text', value, }) => {
|
|
6
6
|
const { control } = useFormContext();
|
|
7
|
-
return (
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
return (_jsx(Flex, { align: "center", gap: "base", flex: "auto", children: _jsx(Controller, { control: control, name: id, defaultValue: value, render: ({ field: { value, onChange } }) => {
|
|
8
|
+
return (_jsx(TextInput, { handleChange: onChange, placeholder: placeholder, type: type, value: value }));
|
|
9
|
+
} }) }));
|
|
10
10
|
};
|
|
@@ -9,6 +9,7 @@ export * from './FieldWithDelete';
|
|
|
9
9
|
export * from './FileField';
|
|
10
10
|
export * from './GridField';
|
|
11
11
|
export * from './GroupField';
|
|
12
|
+
export * from './LabeledField';
|
|
12
13
|
export * from './NumberField';
|
|
13
14
|
export * from './SwitchField';
|
|
14
15
|
export * from './RatingField';
|
|
@@ -9,6 +9,7 @@ export * from './FieldWithDelete';
|
|
|
9
9
|
export * from './FileField';
|
|
10
10
|
export * from './GridField';
|
|
11
11
|
export * from './GroupField';
|
|
12
|
+
export * from './LabeledField';
|
|
12
13
|
export * from './NumberField';
|
|
13
14
|
export * from './SwitchField';
|
|
14
15
|
export * from './RatingField';
|
|
@@ -3,8 +3,8 @@ import { useEffect, useState } from 'react';
|
|
|
3
3
|
import DatePickerComponent from 'react-datepicker';
|
|
4
4
|
import 'react-datepicker/dist/react-datepicker.css';
|
|
5
5
|
import '../../styles/calendar.css';
|
|
6
|
-
import { Flex, IconButton, Input
|
|
7
|
-
import { Calendar as CalendarIcon } from '..';
|
|
6
|
+
import { Flex, IconButton, Input } from '@chakra-ui/react';
|
|
7
|
+
import { Calendar as CalendarIcon, LabeledField } from '..';
|
|
8
8
|
import { colors } from '../../theme/colors';
|
|
9
9
|
import { extractDayMonthYear, joinDayMonthYear } from '../../utils';
|
|
10
10
|
export const DatePicker = ({ onChange, placeholder, showCalendarIcon, value, width = '290px', }) => {
|
|
@@ -38,7 +38,7 @@ export const DatePicker = ({ onChange, placeholder, showCalendarIcon, value, wid
|
|
|
38
38
|
}
|
|
39
39
|
}, [value]);
|
|
40
40
|
const renderCustomInput = () => {
|
|
41
|
-
return (_jsxs(Flex, { gap: "base", w: width, children: [_jsx(
|
|
41
|
+
return (_jsxs(Flex, { gap: "base", w: width, children: [_jsx(LabeledField, { label: String(placeholder), children: _jsx(Input, { placeholder: placeholder || 'MM/DD/YYYY', onChange: handleInputChange, _placeholder: { color: 'gray.2' }, value: value }) }), showCalendarIcon && (_jsx(IconButton, { "aria-label": "Calendar icon", variant: "iconOutlined", icon: _jsx(CalendarIcon, { size: 28, stroke: colors.blue[3] }), maxH: "input.md", flexShrink: 0 }))] }));
|
|
42
42
|
};
|
|
43
43
|
return (_jsx(DatePickerComponent, { selected: day, onChange: handleOnChange, customInput: renderCustomInput(), calendarClassName: "custom-calendar" }));
|
|
44
44
|
};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from 'react';
|
|
3
3
|
import { t } from 'i18next';
|
|
4
|
-
import { Menu, Text, Input, InputGroup, InputRightElement,
|
|
4
|
+
import { Menu, Text, Input, InputGroup, InputRightElement, Box, } from '@chakra-ui/react';
|
|
5
5
|
import { textOptionVariants } from '../../helpers';
|
|
6
|
-
import { SearchIcon, SelectButton, SelectItem, SelectList } from '..';
|
|
6
|
+
import { LabeledField, SearchIcon, SelectButton, SelectItem, SelectList, } from '..';
|
|
7
7
|
import { colors } from '../../theme/colors';
|
|
8
8
|
export const SelectInput = ({ filterValue = '', handleClick, handleFilter = () => null, hasFilter, height = 'sm', initialValue = 'All', isDisabled, items, placeholder, variant = 'primary', width = '10rem', }) => {
|
|
9
9
|
const [selectedValue, setSelectedValue] = useState(initialValue);
|
|
10
10
|
useEffect(() => {
|
|
11
11
|
setSelectedValue(initialValue);
|
|
12
12
|
}, [initialValue]);
|
|
13
|
-
return (_jsxs(Menu, { children: [_jsx(
|
|
13
|
+
return (_jsxs(Menu, { children: [_jsx(LabeledField, { label: String(placeholder), children: _jsx(Box, { w: "100%", children: _jsx(SelectButton, { selectedValue: selectedValue, height: height, isDisabled: isDisabled, variant: variant, width: width }) }) }), _jsxs(SelectList, { children: [hasFilter && (_jsxs(InputGroup, { size: "md", w: "100%", px: "2", pb: "2", children: [_jsx(Input, { variant: "filled", bg: "lightBlue.2", pr: "4.5rem", placeholder: t('forms.search'), value: String(filterValue), onChange: handleFilter }), _jsx(InputRightElement, { mr: "base", children: _jsx(SearchIcon, { size: 26, stroke: colors.gray[3] }) })] })), items === null || items === void 0 ? void 0 : items.map((item) => {
|
|
14
14
|
const isSelectItem = typeof item === 'object';
|
|
15
15
|
const id = isSelectItem ? item._id : item;
|
|
16
16
|
const name = isSelectItem ? item.name : item;
|
|
@@ -10,9 +10,10 @@ var __rest = (this && this.__rest) || function (s, e) {
|
|
|
10
10
|
return t;
|
|
11
11
|
};
|
|
12
12
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
13
|
-
import { FormControl, Text, Input
|
|
13
|
+
import { FormControl, Text, Input } from '@chakra-ui/react';
|
|
14
|
+
import { LabeledField } from '..';
|
|
14
15
|
export const TextInput = (_a) => {
|
|
15
16
|
var { autoCapitalize = 'on', autoCorrect = 'on', errorMessage, handleChange, hasError, id, isDisabled, placeholder, value = '', type = 'text' } = _a, props = __rest(_a, ["autoCapitalize", "autoCorrect", "errorMessage", "handleChange", "hasError", "id", "isDisabled", "placeholder", "value", "type"]);
|
|
16
17
|
const error = hasError && !isDisabled;
|
|
17
|
-
return (_jsxs(FormControl, { isInvalid: error, children: [_jsx(
|
|
18
|
+
return (_jsxs(FormControl, { isInvalid: error, children: [_jsx(LabeledField, { label: placeholder, children: _jsx(Input, Object.assign({}, props, { autoCapitalize: autoCapitalize, autoCorrect: autoCorrect, id: id, placeholder: placeholder, value: value, type: type, onChange: handleChange, isInvalid: error, isDisabled: isDisabled, _placeholder: { color: 'gray.2' } })) }), error && _jsx(Text, { variant: "error", children: errorMessage })] }));
|
|
18
19
|
};
|
|
@@ -2,8 +2,9 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
2
2
|
import { action } from '@storybook/addon-actions';
|
|
3
3
|
import { Box } from '@chakra-ui/react';
|
|
4
4
|
import { DynamicForm } from '../../../components';
|
|
5
|
-
import { formFieldsMock, menuMock, socialLinksMock, tileUIMock } from '../../../mocks';
|
|
5
|
+
import { formFieldsMock, unknownFormMock, menuMock, socialLinksMock, tileUIMock, } from '../../../mocks';
|
|
6
6
|
import { faker } from '@faker-js/faker';
|
|
7
|
+
import { mapApiObjectToFormFields } from '../../../utils';
|
|
7
8
|
export default {
|
|
8
9
|
title: 'Components/Forms/DynamicForm',
|
|
9
10
|
component: DynamicForm,
|
|
@@ -28,7 +29,10 @@ export default {
|
|
|
28
29
|
},
|
|
29
30
|
};
|
|
30
31
|
export const DynamicFormComponent = (args) => {
|
|
31
|
-
|
|
32
|
+
const unknownForm = mapApiObjectToFormFields({
|
|
33
|
+
obj: unknownFormMock,
|
|
34
|
+
});
|
|
35
|
+
return (_jsx(Box, { p: "base", bg: "neutral.white", w: ['full', '500px'], minH: "500px", children: _jsx(DynamicForm, Object.assign({}, args, { form: [...formFieldsMock, ...unknownForm], showTitle: false })) }));
|
|
32
36
|
};
|
|
33
37
|
export const DynamicUIFormComponent = (args) => {
|
|
34
38
|
return (_jsx(Box, { m: "base", w: ['full', '320px'], children: _jsx(DynamicForm, Object.assign({}, args, { form: tileUIMock, showTitle: false, callback: action('callback') })) }));
|
|
@@ -9,13 +9,8 @@ export const mapApiObjectToFormFields = ({ obj = {}, depth = 0, labelPrefix = ''
|
|
|
9
9
|
: {};
|
|
10
10
|
const rest = Object.assign({}, obj);
|
|
11
11
|
delete rest.misc;
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const allEntries = [
|
|
15
|
-
...rootEntries.map(([key, value]) => [key, value]),
|
|
16
|
-
...miscEntries.map(([key, value]) => [key, value]),
|
|
17
|
-
];
|
|
18
|
-
return allEntries
|
|
12
|
+
const merged = Object.assign(Object.assign({}, rest), misc);
|
|
13
|
+
return Object.entries(merged)
|
|
19
14
|
.filter(([key]) => !FIELDS_TO_IGNORE.includes(key))
|
|
20
15
|
.flatMap(([key, value]) => {
|
|
21
16
|
const currentLabel = `${labelPrefix ? `${labelPrefix} - ` : ''}${capitalize(key)}`;
|
|
@@ -39,7 +34,7 @@ export const mapApiObjectToFormFields = ({ obj = {}, depth = 0, labelPrefix = ''
|
|
|
39
34
|
name: currentLabel,
|
|
40
35
|
description: '',
|
|
41
36
|
comments: '',
|
|
42
|
-
value,
|
|
37
|
+
value: String(value),
|
|
43
38
|
type: 'number',
|
|
44
39
|
visible: true,
|
|
45
40
|
},
|
|
@@ -52,7 +47,7 @@ export const mapApiObjectToFormFields = ({ obj = {}, depth = 0, labelPrefix = ''
|
|
|
52
47
|
name: currentLabel,
|
|
53
48
|
description: '',
|
|
54
49
|
comments: '',
|
|
55
|
-
value,
|
|
50
|
+
value: String(value),
|
|
56
51
|
type: 'switch',
|
|
57
52
|
visible: true,
|
|
58
53
|
},
|
|
@@ -76,32 +71,58 @@ export const mapApiObjectToFormFields = ({ obj = {}, depth = 0, labelPrefix = ''
|
|
|
76
71
|
}
|
|
77
72
|
if (Array.isArray(value) &&
|
|
78
73
|
value.every((v) => Object.prototype.toString.call(v) === '[object Object]')) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
74
|
+
const htmlList = value
|
|
75
|
+
.map((item) => {
|
|
76
|
+
const flat = flattenObject(item);
|
|
77
|
+
return Object.entries(flat)
|
|
78
|
+
.map(([k, v]) => {
|
|
79
|
+
const label = capitalize(k);
|
|
80
|
+
return Array.isArray(v) &&
|
|
81
|
+
v.every((el) => typeof el === 'string')
|
|
82
|
+
? `<li><strong>${label}:</strong><ul>${v
|
|
83
|
+
.map((el) => `<li>${el}</li>`)
|
|
84
|
+
.join('')}</ul></li>`
|
|
85
|
+
: `<li><strong>${label}:</strong> ${formatHtmlValue(v)}</li>`;
|
|
86
|
+
})
|
|
87
|
+
.join('');
|
|
88
|
+
})
|
|
89
|
+
.join('<br/>');
|
|
90
|
+
return [
|
|
91
|
+
{
|
|
92
|
+
id: uuidv4(),
|
|
93
|
+
name: currentLabel,
|
|
94
|
+
description: '',
|
|
95
|
+
comments: '',
|
|
96
|
+
value: `<ul>${htmlList}</ul>`,
|
|
97
|
+
type: 'textarea',
|
|
98
|
+
visible: true,
|
|
99
|
+
},
|
|
100
|
+
];
|
|
98
101
|
}
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
if (isRecord(value)) {
|
|
103
|
+
const flat = flattenObject(value);
|
|
104
|
+
const lines = Object.entries(flat)
|
|
105
|
+
.map(([k, v]) => {
|
|
106
|
+
const label = capitalize(k);
|
|
107
|
+
return Array.isArray(v) && v.every((el) => typeof el === 'string')
|
|
108
|
+
? `<li><strong>${label}:</strong><ul>${v
|
|
109
|
+
.map((el) => `<li>${el}</li>`)
|
|
110
|
+
.join('')}</ul></li>`
|
|
111
|
+
: `<li><strong>${label}:</strong> ${formatHtmlValue(v)}</li>`;
|
|
112
|
+
})
|
|
113
|
+
.join('');
|
|
114
|
+
const htmlValue = `<ul>${lines}</ul>`;
|
|
115
|
+
return [
|
|
116
|
+
{
|
|
117
|
+
id: uuidv4(),
|
|
118
|
+
name: currentLabel,
|
|
119
|
+
description: '',
|
|
120
|
+
comments: '',
|
|
121
|
+
value: htmlValue,
|
|
122
|
+
type: 'textarea',
|
|
123
|
+
visible: true,
|
|
124
|
+
},
|
|
125
|
+
];
|
|
105
126
|
}
|
|
106
127
|
return [
|
|
107
128
|
{
|
|
@@ -116,4 +137,32 @@ export const mapApiObjectToFormFields = ({ obj = {}, depth = 0, labelPrefix = ''
|
|
|
116
137
|
];
|
|
117
138
|
});
|
|
118
139
|
};
|
|
119
|
-
const capitalize = (str) =>
|
|
140
|
+
const capitalize = (str) => {
|
|
141
|
+
return str
|
|
142
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
143
|
+
.replace(/_/g, ' ')
|
|
144
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
145
|
+
};
|
|
146
|
+
const isRecord = (val) => {
|
|
147
|
+
return typeof val === 'object' && val !== null && !Array.isArray(val);
|
|
148
|
+
};
|
|
149
|
+
const formatHtmlValue = (val) => {
|
|
150
|
+
if (Array.isArray(val) && val.every((v) => typeof v === 'string')) {
|
|
151
|
+
return `<ul>${val.map((v) => `<li>${v}</li>`).join('')}</ul>`;
|
|
152
|
+
}
|
|
153
|
+
return String(val);
|
|
154
|
+
};
|
|
155
|
+
const flattenObject = (obj, parentKey = '') => {
|
|
156
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
157
|
+
const fullKey = parentKey
|
|
158
|
+
? `${parentKey} - ${capitalize(key)}`
|
|
159
|
+
: capitalize(key);
|
|
160
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
161
|
+
Object.assign(acc, flattenObject(value, fullKey));
|
|
162
|
+
}
|
|
163
|
+
else {
|
|
164
|
+
acc[fullKey] = value;
|
|
165
|
+
}
|
|
166
|
+
return acc;
|
|
167
|
+
}, {});
|
|
168
|
+
};
|
package/package.json
CHANGED
|
@@ -59,16 +59,18 @@ export const AIGridField = ({
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
return (
|
|
62
|
-
<Flex align="
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
62
|
+
<Flex align="flex-end" gap="base">
|
|
63
|
+
<Flex align="center" gap="base">
|
|
64
|
+
<SingleImage
|
|
65
|
+
{...imageProps}
|
|
66
|
+
onUpload={handleUpload}
|
|
67
|
+
onRemove={onRemove}
|
|
68
|
+
value={imageField?.value as string}
|
|
69
|
+
/>
|
|
70
|
+
<Text fontFamily="secondary" textAlign="center">
|
|
71
|
+
OR
|
|
72
|
+
</Text>
|
|
73
|
+
</Flex>
|
|
72
74
|
<TextField {...textProps} />
|
|
73
75
|
<IconButton
|
|
74
76
|
aria-label="Add new address line"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Controller, useFormContext } from 'react-hook-form'
|
|
2
2
|
import { Flex, Input, Tooltip } from '@chakra-ui/react'
|
|
3
3
|
import { TextFieldI } from '@/interfaces'
|
|
4
|
-
import { FormIcon } from '@/components'
|
|
4
|
+
import { FormIcon, LabeledField } from '@/components'
|
|
5
5
|
import { formatCurrency } from '@/utils'
|
|
6
6
|
|
|
7
7
|
export const CurrencyField = ({ id, icon, placeholder, value }: TextFieldI) => {
|
|
@@ -22,7 +22,7 @@ export const CurrencyField = ({ id, icon, placeholder, value }: TextFieldI) => {
|
|
|
22
22
|
onChange(currency)
|
|
23
23
|
}
|
|
24
24
|
return (
|
|
25
|
-
<
|
|
25
|
+
<LabeledField label={String(placeholder)}>
|
|
26
26
|
<Input
|
|
27
27
|
onChange={handleChange}
|
|
28
28
|
placeholder={placeholder}
|
|
@@ -30,7 +30,7 @@ export const CurrencyField = ({ id, icon, placeholder, value }: TextFieldI) => {
|
|
|
30
30
|
value={value}
|
|
31
31
|
_placeholder={{ color: 'gray.2' }}
|
|
32
32
|
/>
|
|
33
|
-
</
|
|
33
|
+
</LabeledField>
|
|
34
34
|
)
|
|
35
35
|
}}
|
|
36
36
|
/>
|
|
@@ -11,7 +11,7 @@ export const GroupField = ({
|
|
|
11
11
|
}: GroupFieldI) => {
|
|
12
12
|
return (
|
|
13
13
|
<FieldWithDelete id={id} onRemove={onRemove} canBeHidden={canBeHidden}>
|
|
14
|
-
<Stack spacing="
|
|
14
|
+
<Stack spacing="1" flex="1">
|
|
15
15
|
<SingleFields fields={fields} />
|
|
16
16
|
</Stack>
|
|
17
17
|
</FieldWithDelete>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Box, Text, Flex, Image } from '@chakra-ui/react'
|
|
2
|
+
|
|
3
|
+
interface LabeledFieldProps {
|
|
4
|
+
children: React.ReactNode
|
|
5
|
+
label: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const LabeledField = ({ label, children }: LabeledFieldProps) => {
|
|
9
|
+
if (!label) {
|
|
10
|
+
return <>{children}</>
|
|
11
|
+
}
|
|
12
|
+
return (
|
|
13
|
+
<Box w="100%">
|
|
14
|
+
<Flex gap="1" align="start">
|
|
15
|
+
<Text fontSize="xs" fontFamily="secondary" mb="1">
|
|
16
|
+
{label}
|
|
17
|
+
</Text>
|
|
18
|
+
</Flex>
|
|
19
|
+
{children}
|
|
20
|
+
</Box>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Controller, useFormContext } from 'react-hook-form'
|
|
2
|
-
import { Flex, Input
|
|
2
|
+
import { Flex, Input } from '@chakra-ui/react'
|
|
3
3
|
import { TextFieldI } from '@/interfaces'
|
|
4
|
-
import { FieldDescription } from '@/components'
|
|
4
|
+
import { FieldDescription, LabeledField } from '@/components'
|
|
5
5
|
|
|
6
6
|
export const NumberField = ({
|
|
7
7
|
description,
|
|
@@ -13,14 +13,14 @@ export const NumberField = ({
|
|
|
13
13
|
const { control } = useFormContext()
|
|
14
14
|
return (
|
|
15
15
|
<Flex align="center" gap="base" flex="auto">
|
|
16
|
-
<FieldDescription description={description} icon={icon} />
|
|
16
|
+
{/* <FieldDescription description={description} icon={icon} /> */}
|
|
17
17
|
<Controller
|
|
18
18
|
control={control}
|
|
19
19
|
name={id}
|
|
20
20
|
defaultValue={value}
|
|
21
21
|
render={({ field: { value, onChange } }) => {
|
|
22
22
|
return (
|
|
23
|
-
<
|
|
23
|
+
<LabeledField label={String(!description && placeholder)}>
|
|
24
24
|
<Input
|
|
25
25
|
onChange={(e) => {
|
|
26
26
|
const value = e.target.valueAsNumber
|
|
@@ -38,7 +38,7 @@ export const NumberField = ({
|
|
|
38
38
|
textAlign="right"
|
|
39
39
|
_placeholder={{ color: 'gray.2' }}
|
|
40
40
|
/>
|
|
41
|
-
</
|
|
41
|
+
</LabeledField>
|
|
42
42
|
)
|
|
43
43
|
}}
|
|
44
44
|
/>
|
|
@@ -14,13 +14,8 @@ export const SelectField = ({
|
|
|
14
14
|
const { control } = useFormContext()
|
|
15
15
|
const stringOptions = options?.map((option) => String(option)) ?? []
|
|
16
16
|
return (
|
|
17
|
-
<Flex
|
|
18
|
-
|
|
19
|
-
gap="base"
|
|
20
|
-
flex="auto"
|
|
21
|
-
w={description ? '100%' : '40%'}
|
|
22
|
-
>
|
|
23
|
-
<FieldDescription description={description} icon={icon} />
|
|
17
|
+
<Flex align="start" gap="base" flex="auto" w={description ? '100%' : '40%'}>
|
|
18
|
+
{/* <FieldDescription description={description} icon={icon} /> */}
|
|
24
19
|
<Box w={description ? '102px' : '100%'}>
|
|
25
20
|
<Controller
|
|
26
21
|
control={control}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { useFormContext, Controller } from 'react-hook-form'
|
|
2
|
-
import { Flex, Image
|
|
2
|
+
import { Flex, Image } from '@chakra-ui/react'
|
|
3
3
|
import QuillEditor from 'react-quill-new'
|
|
4
4
|
import { TextAreaFieldI } from '@/interfaces'
|
|
5
5
|
import 'react-quill-new/dist/quill.snow.css'
|
|
6
6
|
import '@/styles/quill.css'
|
|
7
|
+
import { LabeledField } from './LabeledField'
|
|
7
8
|
|
|
8
9
|
export const TextAreaField = ({
|
|
9
10
|
id,
|
|
@@ -13,15 +14,15 @@ export const TextAreaField = ({
|
|
|
13
14
|
}: TextAreaFieldI) => {
|
|
14
15
|
const { control } = useFormContext()
|
|
15
16
|
return (
|
|
16
|
-
<
|
|
17
|
-
<
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
<Flex gap="base" align="start" flex="auto">
|
|
18
|
+
{/* {icon && <Image h="auto" w="icon.md" src={icon} marginTop="2" />} */}
|
|
19
|
+
<Controller
|
|
20
|
+
control={control}
|
|
21
|
+
name={id}
|
|
22
|
+
defaultValue={value}
|
|
23
|
+
render={({ field: { value, onChange } }) => {
|
|
24
|
+
return (
|
|
25
|
+
<LabeledField label={placeholder}>
|
|
25
26
|
<QuillEditor
|
|
26
27
|
theme="snow"
|
|
27
28
|
value={value}
|
|
@@ -30,11 +31,11 @@ export const TextAreaField = ({
|
|
|
30
31
|
onChange={onChange}
|
|
31
32
|
placeholder={placeholder}
|
|
32
33
|
/>
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
</
|
|
34
|
+
</LabeledField>
|
|
35
|
+
)
|
|
36
|
+
}}
|
|
37
|
+
/>
|
|
38
|
+
</Flex>
|
|
38
39
|
)
|
|
39
40
|
}
|
|
40
41
|
|
|
@@ -2,8 +2,8 @@ import { useEffect, useState } from 'react'
|
|
|
2
2
|
import DatePickerComponent from 'react-datepicker'
|
|
3
3
|
import 'react-datepicker/dist/react-datepicker.css'
|
|
4
4
|
import '@/styles/calendar.css'
|
|
5
|
-
import { Flex, IconButton, Input
|
|
6
|
-
import { Calendar as CalendarIcon } from '@/components'
|
|
5
|
+
import { Flex, IconButton, Input } from '@chakra-ui/react'
|
|
6
|
+
import { Calendar as CalendarIcon, LabeledField } from '@/components'
|
|
7
7
|
import { DatePickerI } from '@/interfaces'
|
|
8
8
|
import { colors } from '@/theme/colors'
|
|
9
9
|
import { extractDayMonthYear, joinDayMonthYear } from '@/utils'
|
|
@@ -56,14 +56,14 @@ export const DatePicker = ({
|
|
|
56
56
|
const renderCustomInput = () => {
|
|
57
57
|
return (
|
|
58
58
|
<Flex gap="base" w={width}>
|
|
59
|
-
<
|
|
59
|
+
<LabeledField label={String(placeholder)}>
|
|
60
60
|
<Input
|
|
61
|
-
placeholder={placeholder}
|
|
61
|
+
placeholder={placeholder || 'MM/DD/YYYY'}
|
|
62
62
|
onChange={handleInputChange}
|
|
63
63
|
_placeholder={{ color: 'gray.2' }}
|
|
64
64
|
value={value}
|
|
65
65
|
/>
|
|
66
|
-
</
|
|
66
|
+
</LabeledField>
|
|
67
67
|
{showCalendarIcon && (
|
|
68
68
|
<IconButton
|
|
69
69
|
aria-label="Calendar icon"
|
|
@@ -11,7 +11,13 @@ import {
|
|
|
11
11
|
} from '@chakra-ui/react'
|
|
12
12
|
import { SelectI } from '@/interfaces'
|
|
13
13
|
import { textOptionVariants } from '@/helpers'
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
LabeledField,
|
|
16
|
+
SearchIcon,
|
|
17
|
+
SelectButton,
|
|
18
|
+
SelectItem,
|
|
19
|
+
SelectList,
|
|
20
|
+
} from '@/components'
|
|
15
21
|
import { colors } from '@/theme/colors'
|
|
16
22
|
|
|
17
23
|
export const SelectInput = ({
|
|
@@ -35,7 +41,7 @@ export const SelectInput = ({
|
|
|
35
41
|
|
|
36
42
|
return (
|
|
37
43
|
<Menu>
|
|
38
|
-
<
|
|
44
|
+
<LabeledField label={String(placeholder)}>
|
|
39
45
|
<Box w="100%">
|
|
40
46
|
<SelectButton
|
|
41
47
|
selectedValue={selectedValue}
|
|
@@ -45,7 +51,7 @@ export const SelectInput = ({
|
|
|
45
51
|
width={width}
|
|
46
52
|
/>
|
|
47
53
|
</Box>
|
|
48
|
-
</
|
|
54
|
+
</LabeledField>
|
|
49
55
|
<SelectList>
|
|
50
56
|
{hasFilter && (
|
|
51
57
|
<InputGroup size="md" w="100%" px="2" pb="2">
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { FormControl, Text, Input
|
|
1
|
+
import { FormControl, Text, Input } from '@chakra-ui/react'
|
|
2
2
|
import { InputI } from '@/interfaces'
|
|
3
|
+
import { LabeledField } from '@/components'
|
|
3
4
|
|
|
4
5
|
export const TextInput = ({
|
|
5
6
|
autoCapitalize = 'on',
|
|
@@ -17,7 +18,7 @@ export const TextInput = ({
|
|
|
17
18
|
const error = hasError && !isDisabled
|
|
18
19
|
return (
|
|
19
20
|
<FormControl isInvalid={error}>
|
|
20
|
-
<
|
|
21
|
+
<LabeledField label={placeholder}>
|
|
21
22
|
<Input
|
|
22
23
|
{...props}
|
|
23
24
|
autoCapitalize={autoCapitalize}
|
|
@@ -31,7 +32,7 @@ export const TextInput = ({
|
|
|
31
32
|
isDisabled={isDisabled}
|
|
32
33
|
_placeholder={{ color: 'gray.2' }}
|
|
33
34
|
/>
|
|
34
|
-
</
|
|
35
|
+
</LabeledField>
|
|
35
36
|
{error && <Text variant="error">{errorMessage}</Text>}
|
|
36
37
|
</FormControl>
|
|
37
38
|
)
|
|
@@ -2,9 +2,16 @@ import { Meta } from '@storybook/react'
|
|
|
2
2
|
import { action } from '@storybook/addon-actions'
|
|
3
3
|
import { Box } from '@chakra-ui/react'
|
|
4
4
|
import { DynamicForm } from '@/components'
|
|
5
|
-
import {
|
|
6
|
-
|
|
5
|
+
import {
|
|
6
|
+
formFieldsMock,
|
|
7
|
+
unknownFormMock,
|
|
8
|
+
menuMock,
|
|
9
|
+
socialLinksMock,
|
|
10
|
+
tileUIMock,
|
|
11
|
+
} from '@/mocks'
|
|
12
|
+
import { DynamicFormI, ReportI } from '@/interfaces'
|
|
7
13
|
import { faker } from '@faker-js/faker'
|
|
14
|
+
import { mapApiObjectToFormFields } from '@/utils'
|
|
8
15
|
|
|
9
16
|
export default {
|
|
10
17
|
title: 'Components/Forms/DynamicForm',
|
|
@@ -31,9 +38,16 @@ export default {
|
|
|
31
38
|
} as Meta<DynamicFormI>
|
|
32
39
|
|
|
33
40
|
export const DynamicFormComponent = (args: DynamicFormI) => {
|
|
41
|
+
const unknownForm = mapApiObjectToFormFields({
|
|
42
|
+
obj: unknownFormMock,
|
|
43
|
+
})
|
|
34
44
|
return (
|
|
35
45
|
<Box p="base" bg="neutral.white" w={['full', '500px']} minH="500px">
|
|
36
|
-
<DynamicForm
|
|
46
|
+
<DynamicForm
|
|
47
|
+
{...args}
|
|
48
|
+
form={[...formFieldsMock, ...unknownForm]}
|
|
49
|
+
showTitle={false}
|
|
50
|
+
/>
|
|
37
51
|
</Box>
|
|
38
52
|
)
|
|
39
53
|
}
|
|
@@ -24,17 +24,11 @@ export const mapApiObjectToFormFields = ({
|
|
|
24
24
|
const rest = { ...obj }
|
|
25
25
|
delete rest.misc
|
|
26
26
|
|
|
27
|
-
const
|
|
28
|
-
const rootEntries = Object.entries(rest)
|
|
27
|
+
const merged = { ...rest, ...misc }
|
|
29
28
|
|
|
30
|
-
|
|
31
|
-
...rootEntries.map(([key, value]) => [key, value] as const),
|
|
32
|
-
...miscEntries.map(([key, value]) => [key, value] as const),
|
|
33
|
-
]
|
|
34
|
-
|
|
35
|
-
return allEntries
|
|
29
|
+
return Object.entries(merged)
|
|
36
30
|
.filter(([key]) => !FIELDS_TO_IGNORE.includes(key))
|
|
37
|
-
.flatMap(([key, value]) => {
|
|
31
|
+
.flatMap(([key, value]): any => {
|
|
38
32
|
const currentLabel = `${
|
|
39
33
|
labelPrefix ? `${labelPrefix} - ` : ''
|
|
40
34
|
}${capitalize(key)}`
|
|
@@ -60,7 +54,7 @@ export const mapApiObjectToFormFields = ({
|
|
|
60
54
|
name: currentLabel,
|
|
61
55
|
description: '',
|
|
62
56
|
comments: '',
|
|
63
|
-
value,
|
|
57
|
+
value: String(value),
|
|
64
58
|
type: 'number',
|
|
65
59
|
visible: true,
|
|
66
60
|
},
|
|
@@ -74,7 +68,7 @@ export const mapApiObjectToFormFields = ({
|
|
|
74
68
|
name: currentLabel,
|
|
75
69
|
description: '',
|
|
76
70
|
comments: '',
|
|
77
|
-
value,
|
|
71
|
+
value: String(value),
|
|
78
72
|
type: 'switch',
|
|
79
73
|
visible: true,
|
|
80
74
|
},
|
|
@@ -104,33 +98,62 @@ export const mapApiObjectToFormFields = ({
|
|
|
104
98
|
(v) => Object.prototype.toString.call(v) === '[object Object]'
|
|
105
99
|
)
|
|
106
100
|
) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
101
|
+
const htmlList = value
|
|
102
|
+
.map((item) => {
|
|
103
|
+
const flat = flattenObject(item)
|
|
104
|
+
return Object.entries(flat)
|
|
105
|
+
.map(([k, v]) => {
|
|
106
|
+
const label = capitalize(k)
|
|
107
|
+
return Array.isArray(v) &&
|
|
108
|
+
v.every((el) => typeof el === 'string')
|
|
109
|
+
? `<li><strong>${label}:</strong><ul>${v
|
|
110
|
+
.map((el) => `<li>${el}</li>`)
|
|
111
|
+
.join('')}</ul></li>`
|
|
112
|
+
: `<li><strong>${label}:</strong> ${formatHtmlValue(v)}</li>`
|
|
113
|
+
})
|
|
114
|
+
.join('')
|
|
115
|
+
})
|
|
116
|
+
.join('<br/>')
|
|
117
|
+
|
|
118
|
+
return [
|
|
119
|
+
{
|
|
120
|
+
id: uuidv4(),
|
|
121
|
+
name: currentLabel,
|
|
122
|
+
description: '',
|
|
123
|
+
comments: '',
|
|
124
|
+
value: `<ul>${htmlList}</ul>`,
|
|
125
|
+
type: 'textarea',
|
|
126
|
+
visible: true,
|
|
127
|
+
},
|
|
128
|
+
]
|
|
126
129
|
}
|
|
127
130
|
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
131
|
+
if (isRecord(value)) {
|
|
132
|
+
const flat = flattenObject(value)
|
|
133
|
+
|
|
134
|
+
const lines = Object.entries(flat)
|
|
135
|
+
.map(([k, v]) => {
|
|
136
|
+
const label = capitalize(k)
|
|
137
|
+
return Array.isArray(v) && v.every((el) => typeof el === 'string')
|
|
138
|
+
? `<li><strong>${label}:</strong><ul>${v
|
|
139
|
+
.map((el) => `<li>${el}</li>`)
|
|
140
|
+
.join('')}</ul></li>`
|
|
141
|
+
: `<li><strong>${label}:</strong> ${formatHtmlValue(v)}</li>`
|
|
142
|
+
})
|
|
143
|
+
.join('')
|
|
144
|
+
const htmlValue = `<ul>${lines}</ul>`
|
|
145
|
+
|
|
146
|
+
return [
|
|
147
|
+
{
|
|
148
|
+
id: uuidv4(),
|
|
149
|
+
name: currentLabel,
|
|
150
|
+
description: '',
|
|
151
|
+
comments: '',
|
|
152
|
+
value: htmlValue,
|
|
153
|
+
type: 'textarea',
|
|
154
|
+
visible: true,
|
|
155
|
+
},
|
|
156
|
+
]
|
|
134
157
|
}
|
|
135
158
|
|
|
136
159
|
return [
|
|
@@ -147,4 +170,37 @@ export const mapApiObjectToFormFields = ({
|
|
|
147
170
|
})
|
|
148
171
|
}
|
|
149
172
|
|
|
150
|
-
const capitalize = (str: string) =>
|
|
173
|
+
const capitalize = (str: string) => {
|
|
174
|
+
return str
|
|
175
|
+
.replace(/([a-z])([A-Z])/g, '$1 $2')
|
|
176
|
+
.replace(/_/g, ' ')
|
|
177
|
+
.replace(/\b\w/g, (c) => c.toUpperCase())
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const isRecord = (val: unknown): val is Record<string, any> => {
|
|
181
|
+
return typeof val === 'object' && val !== null && !Array.isArray(val)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const formatHtmlValue = (val: unknown): string => {
|
|
185
|
+
if (Array.isArray(val) && val.every((v) => typeof v === 'string')) {
|
|
186
|
+
return `<ul>${val.map((v) => `<li>${v}</li>`).join('')}</ul>`
|
|
187
|
+
}
|
|
188
|
+
return String(val)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const flattenObject = (
|
|
192
|
+
obj: Record<string, any>,
|
|
193
|
+
parentKey = ''
|
|
194
|
+
): Record<string, any> => {
|
|
195
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
196
|
+
const fullKey = parentKey
|
|
197
|
+
? `${parentKey} - ${capitalize(key)}`
|
|
198
|
+
: capitalize(key)
|
|
199
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
200
|
+
Object.assign(acc, flattenObject(value, fullKey))
|
|
201
|
+
} else {
|
|
202
|
+
acc[fullKey] = value
|
|
203
|
+
}
|
|
204
|
+
return acc
|
|
205
|
+
}, {} as Record<string, any>)
|
|
206
|
+
}
|