@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.
Files changed (33) hide show
  1. package/dist/components/forms/dynamicForm/fields/AIGridField.js +1 -1
  2. package/dist/components/forms/dynamicForm/fields/CurrencyField.js +3 -3
  3. package/dist/components/forms/dynamicForm/fields/DateField.js +1 -1
  4. package/dist/components/forms/dynamicForm/fields/GroupField.js +1 -1
  5. package/dist/components/forms/dynamicForm/fields/LabeledField.d.ts +6 -0
  6. package/dist/components/forms/dynamicForm/fields/LabeledField.js +8 -0
  7. package/dist/components/forms/dynamicForm/fields/NumberField.js +15 -15
  8. package/dist/components/forms/dynamicForm/fields/SelectField.js +5 -5
  9. package/dist/components/forms/dynamicForm/fields/TextAreaField.js +6 -5
  10. package/dist/components/forms/dynamicForm/fields/TextField.js +5 -5
  11. package/dist/components/forms/dynamicForm/fields/index.d.ts +1 -0
  12. package/dist/components/forms/dynamicForm/fields/index.js +1 -0
  13. package/dist/components/inputs/DatePicker.js +3 -3
  14. package/dist/components/inputs/SelectInput.js +3 -3
  15. package/dist/components/inputs/TextInput.js +3 -2
  16. package/dist/stories/forms/dynamicForm/DynamicForm.stories.js +6 -2
  17. package/dist/utils/DynamicForm.utils.js +84 -35
  18. package/package.json +1 -1
  19. package/src/components/forms/dynamicForm/fields/AIGridField.tsx +12 -10
  20. package/src/components/forms/dynamicForm/fields/CurrencyField.tsx +3 -3
  21. package/src/components/forms/dynamicForm/fields/DateField.tsx +1 -1
  22. package/src/components/forms/dynamicForm/fields/GroupField.tsx +1 -1
  23. package/src/components/forms/dynamicForm/fields/LabeledField.tsx +22 -0
  24. package/src/components/forms/dynamicForm/fields/NumberField.tsx +5 -5
  25. package/src/components/forms/dynamicForm/fields/SelectField.tsx +2 -7
  26. package/src/components/forms/dynamicForm/fields/TextAreaField.tsx +16 -15
  27. package/src/components/forms/dynamicForm/fields/TextField.tsx +0 -1
  28. package/src/components/forms/dynamicForm/fields/index.ts +1 -0
  29. package/src/components/inputs/DatePicker.tsx +5 -5
  30. package/src/components/inputs/SelectInput.tsx +9 -3
  31. package/src/components/inputs/TextInput.tsx +4 -3
  32. package/src/stories/forms/dynamicForm/DynamicForm.stories.tsx +17 -3
  33. 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, Tooltip } from '@chakra-ui/react';
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(Tooltip, { label: placeholder, children: _jsx(Input, { onChange: handleChange, placeholder: placeholder, textAlign: "right", value: value, _placeholder: { color: 'gray.2' } }) }));
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: "base", flex: "1", children: _jsx(SingleFields, { fields: fields }) }) }));
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,6 @@
1
+ interface LabeledFieldProps {
2
+ children: React.ReactNode;
3
+ label: string;
4
+ }
5
+ export declare const LabeledField: ({ label, children }: LabeledFieldProps) => import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -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, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Controller, useFormContext } from 'react-hook-form';
3
- import { Flex, Input, Tooltip } from '@chakra-ui/react';
4
- import { FieldDescription } from '../../..';
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 (_jsxs(Flex, { align: "center", gap: "base", flex: "auto", children: [_jsx(FieldDescription, { description: description, icon: icon }), _jsx(Controller, { control: control, name: id, defaultValue: value, render: ({ field: { value, onChange } }) => {
8
- return (_jsx(Tooltip, { label: !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
- } })] }));
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, jsxs as _jsxs } from "react/jsx-runtime";
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 { FieldDescription, SelectInput } from '../../..';
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 (_jsxs(Flex, { align: "center", gap: "base", flex: "auto", w: description ? '100%' : '40%', children: [_jsx(FieldDescription, { description: description, icon: icon }), _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
- } }) })] }));
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, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useFormContext, Controller } from 'react-hook-form';
3
- import { Flex, Image, Tooltip } from '@chakra-ui/react';
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(Tooltip, { label: placeholder, children: _jsxs(Flex, { gap: "base", align: "start", flex: "auto", children: [icon && _jsx(Image, { h: "auto", w: "icon.md", src: icon, marginTop: "2" }), _jsx(Controller, { control: control, name: id, defaultValue: value, render: ({ field: { value, onChange } }) => {
10
- return (_jsx(QuillEditor, { theme: "snow", value: value, formats: formats, modules: { toolbar }, onChange: onChange, placeholder: placeholder }));
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, jsxs as _jsxs } from "react/jsx-runtime";
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 { FormIcon, TextInput } from '../../..';
4
+ import { TextInput } from '../../..';
5
5
  export const TextField = ({ id, icon, placeholder = '', type = 'text', value, }) => {
6
6
  const { control } = useFormContext();
7
- return (_jsxs(Flex, { align: "center", gap: "base", flex: "auto", children: [_jsx(FormIcon, { icon: icon }), _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
- } })] }));
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, Tooltip } from '@chakra-ui/react';
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(Tooltip, { label: placeholder, children: _jsx(Input, { placeholder: placeholder, 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 }))] }));
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, Tooltip, Box, } from '@chakra-ui/react';
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(Tooltip, { label: 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) => {
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, Tooltip } from '@chakra-ui/react';
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(Tooltip, { 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
+ 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
- return (_jsx(Box, { p: "base", bg: "neutral.white", w: ['full', '500px'], minH: "500px", children: _jsx(DynamicForm, Object.assign({}, args, { form: formFieldsMock, showTitle: false })) }));
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 miscEntries = Object.entries(misc);
13
- const rootEntries = Object.entries(rest);
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
- return value.flatMap((item, index) => {
80
- const itemLabel = `${currentLabel} [${index + 1}]`;
81
- return [
82
- {
83
- id: uuidv4(),
84
- name: itemLabel,
85
- description: '',
86
- comments: '',
87
- value: '',
88
- type: 'group',
89
- visible: true,
90
- children: mapApiObjectToFormFields({
91
- obj: item,
92
- depth: depth + 1,
93
- labelPrefix: itemLabel,
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 (Object.prototype.toString.call(value) === '[object Object]') {
100
- return mapApiObjectToFormFields({
101
- obj: value,
102
- depth: depth + 1,
103
- labelPrefix: currentLabel,
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) => str.charAt(0).toUpperCase() + str.slice(1);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@homefile/components-v2",
3
- "version": "2.13.0",
3
+ "version": "2.14.1",
4
4
  "author": "Homefile",
5
5
  "license": "UNLICENSED",
6
6
  "typings": "dist/index.d.ts",
@@ -59,16 +59,18 @@ export const AIGridField = ({
59
59
  }
60
60
 
61
61
  return (
62
- <Flex align="center" gap="base">
63
- <SingleImage
64
- {...imageProps}
65
- onUpload={handleUpload}
66
- onRemove={onRemove}
67
- value={imageField?.value as string}
68
- />
69
- <Text fontFamily="secondary" textAlign="center">
70
- OR
71
- </Text>
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
- <Tooltip label={placeholder}>
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
- </Tooltip>
33
+ </LabeledField>
34
34
  )
35
35
  }}
36
36
  />
@@ -34,7 +34,7 @@ export const DateField = ({
34
34
  <DatePicker
35
35
  onChange={onChange}
36
36
  showCalendarIcon={showCalendarIcon}
37
- placeholder={placeholder}
37
+ placeholder={name ? '' : placeholder}
38
38
  value={value}
39
39
  width={width}
40
40
  />
@@ -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="base" flex="1">
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, Tooltip } from '@chakra-ui/react'
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
- <Tooltip label={!description && placeholder}>
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
- </Tooltip>
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
- align="center"
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, Tooltip } from '@chakra-ui/react'
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
- <Tooltip label={placeholder}>
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 (
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
- </Flex>
37
- </Tooltip>
34
+ </LabeledField>
35
+ )
36
+ }}
37
+ />
38
+ </Flex>
38
39
  )
39
40
  }
40
41
 
@@ -13,7 +13,6 @@ export const TextField = ({
13
13
  const { control } = useFormContext()
14
14
  return (
15
15
  <Flex align="center" gap="base" flex="auto">
16
- <FormIcon icon={icon} />
17
16
  <Controller
18
17
  control={control}
19
18
  name={id}
@@ -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'
@@ -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, Tooltip } from '@chakra-ui/react'
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
- <Tooltip label={placeholder}>
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
- </Tooltip>
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 { SearchIcon, SelectButton, SelectItem, SelectList } from '@/components'
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
- <Tooltip label={placeholder}>
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
- </Tooltip>
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, Tooltip } from '@chakra-ui/react'
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
- <Tooltip label={placeholder}>
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
- </Tooltip>
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 { formFieldsMock, menuMock, socialLinksMock, tileUIMock } from '@/mocks'
6
- import { DynamicFormI } from '@/interfaces'
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 {...args} form={formFieldsMock} showTitle={false} />
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 miscEntries = Object.entries(misc)
28
- const rootEntries = Object.entries(rest)
27
+ const merged = { ...rest, ...misc }
29
28
 
30
- const allEntries = [
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
- return value.flatMap((item, index) => {
108
- const itemLabel = `${currentLabel} [${index + 1}]`
109
- return [
110
- {
111
- id: uuidv4(),
112
- name: itemLabel,
113
- description: '',
114
- comments: '',
115
- value: '',
116
- type: 'group',
117
- visible: true,
118
- children: mapApiObjectToFormFields({
119
- obj: item,
120
- depth: depth + 1,
121
- labelPrefix: itemLabel,
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 (Object.prototype.toString.call(value) === '[object Object]') {
129
- return mapApiObjectToFormFields({
130
- obj: value,
131
- depth: depth + 1,
132
- labelPrefix: currentLabel,
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) => str.charAt(0).toUpperCase() + str.slice(1)
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
+ }