form-craft-package 1.4.0 → 1.4.2
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/package.json +1 -1
- package/src/components/common/color-field.tsx +4 -1
- package/src/components/common/currency-field.tsx +68 -0
- package/src/components/common/custom-hooks/use-find-dynamic-form.hook.ts +2 -1
- package/src/components/common/loading-skeletons/table.tsx +1 -1
- package/src/components/form/1-list/index.tsx +3 -1
- package/src/components/form/1-list/{header.tsx → table-header.tsx} +14 -7
- package/src/components/form/1-list/table.tsx +15 -13
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +19 -7
- package/src/components/form/layout-renderer/3-element/2-field-element.tsx +18 -6
- package/src/components/form/layout-renderer/3-element/3-read-only.tsx +3 -2
- package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +28 -11
- package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +52 -18
- package/src/components/form/layout-renderer/3-element/index.tsx +5 -7
- package/src/constants.ts +2 -2
- package/src/enums.ts +13 -2
- package/src/functions/create-form-rules.ts +22 -22
- package/src/functions/data-render-functions.tsx +121 -43
- package/src/functions/index.ts +4 -1
- package/src/types/data-list/filter-config.ts +1 -0
- package/src/types/data-list/index.ts +12 -1
- package/src/types/index.ts +8 -10
- package/src/types/layout-elements/data-render-config.ts +5 -5
- package/src/types/layout-elements/index.ts +21 -3
- package/src/types/layout-elements/sanitization.ts +12 -0
- package/src/types/layout-elements/validation.ts +1 -2
package/package.json
CHANGED
|
@@ -15,7 +15,10 @@ export default function CustomColorField({ formRef, name, isPreview, disabled }:
|
|
|
15
15
|
className={`flex items-center gap-2 border bg-white rounded-lg w-full p-1 ${
|
|
16
16
|
isPreview || disabled ? 'cursor-not-allowed' : 'cursor-pointer'
|
|
17
17
|
}`}
|
|
18
|
-
onClick={() =>
|
|
18
|
+
onClick={() => {
|
|
19
|
+
colorInputRef.current?.click()
|
|
20
|
+
colorInputRef.current?.focus()
|
|
21
|
+
}}
|
|
19
22
|
>
|
|
20
23
|
<input
|
|
21
24
|
type="color"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { InputNumber } from 'antd'
|
|
2
|
+
import { useMemo, useRef } from 'react'
|
|
3
|
+
import { CountryEnum } from '../../enums'
|
|
4
|
+
import { INTERNATIONALIZATION_DATA } from '../../constants'
|
|
5
|
+
|
|
6
|
+
export default function CurrencyField({
|
|
7
|
+
country = CountryEnum.US,
|
|
8
|
+
decimalPoint = 0,
|
|
9
|
+
...restProps
|
|
10
|
+
}: ICurrencyField) {
|
|
11
|
+
const inputRef = useRef<HTMLInputElement>(null)
|
|
12
|
+
|
|
13
|
+
const currencySymbol = useMemo(() => {
|
|
14
|
+
const { locale, currency } = INTERNATIONALIZATION_DATA[country]
|
|
15
|
+
|
|
16
|
+
const formatter = new Intl.NumberFormat(locale, {
|
|
17
|
+
style: 'currency',
|
|
18
|
+
currency: currency,
|
|
19
|
+
minimumFractionDigits: 0,
|
|
20
|
+
maximumFractionDigits: 0,
|
|
21
|
+
})
|
|
22
|
+
const parts = formatter.formatToParts(0)
|
|
23
|
+
const currencyPart = parts.find((part) => part.type === 'currency')
|
|
24
|
+
return currencyPart?.value ?? '$'
|
|
25
|
+
}, [country])
|
|
26
|
+
|
|
27
|
+
const handleFocus = () => {
|
|
28
|
+
const input = inputRef.current
|
|
29
|
+
if (input) {
|
|
30
|
+
const decimal = input.value ? parseInt(input.value.replace(/\p{Sc}\s?|(,*)/gu, '')) : 0
|
|
31
|
+
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
const decimalPos = input.value.indexOf('.')
|
|
34
|
+
if (decimalPos !== -1) {
|
|
35
|
+
input.setSelectionRange(decimal === 0 ? decimalPos - 1 : decimalPos, decimalPos) // if the whole number is 0, then it highlights the 0 -> so that it can replace right away
|
|
36
|
+
}
|
|
37
|
+
}, 10)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<InputNumber
|
|
43
|
+
{...restProps}
|
|
44
|
+
ref={inputRef}
|
|
45
|
+
onFocus={handleFocus}
|
|
46
|
+
precision={decimalPoint}
|
|
47
|
+
formatter={(value) => {
|
|
48
|
+
if (!value) return `${currencySymbol}${parseInt('0').toFixed(decimalPoint)}`
|
|
49
|
+
|
|
50
|
+
return typeof value === 'string'
|
|
51
|
+
? currencySymbol +
|
|
52
|
+
parseFloat(value)
|
|
53
|
+
.toFixed(decimalPoint)
|
|
54
|
+
.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
55
|
+
: `${currencySymbol}${parseInt('0').toFixed(decimalPoint)}`
|
|
56
|
+
}}
|
|
57
|
+
parser={(value) => (value ? parseFloat(value.replace(/\p{Sc}\s?|(,*)/gu, '')) : 0)}
|
|
58
|
+
className="w-full right-align"
|
|
59
|
+
/>
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface ICurrencyField {
|
|
64
|
+
country?: CountryEnum
|
|
65
|
+
decimalPoint?: number
|
|
66
|
+
placeholder?: string
|
|
67
|
+
disabled?: boolean
|
|
68
|
+
}
|
|
@@ -15,6 +15,7 @@ export const useFindDynamiForm = () => {
|
|
|
15
15
|
if (storedData) {
|
|
16
16
|
const parsedData: IDynamicForm[] = JSON.parse(storedData)
|
|
17
17
|
const item = parsedData.find((entry) => entry.name.split(' ').join('-').toLocaleLowerCase() === formName)
|
|
18
|
+
|
|
18
19
|
setFoundItem(item ?? null)
|
|
19
20
|
}
|
|
20
21
|
} catch (error) {
|
|
@@ -22,5 +23,5 @@ export const useFindDynamiForm = () => {
|
|
|
22
23
|
}
|
|
23
24
|
}, [formName])
|
|
24
25
|
|
|
25
|
-
return foundItem
|
|
26
|
+
return foundItem?.name?.split(' ').join('-').toLowerCase() === formName ? foundItem : undefined
|
|
26
27
|
}
|
|
@@ -3,7 +3,7 @@ import SkeletonBlock from '.'
|
|
|
3
3
|
export default function FormDataListSkeleton_Table({ small = false }: { small?: boolean }) {
|
|
4
4
|
return (
|
|
5
5
|
<>
|
|
6
|
-
<div className={`bg-white flex items-center justify-between rounded-md ${small ? 'p-2' : '
|
|
6
|
+
<div className={`bg-white flex items-center justify-between rounded-md mb-2 ${small ? 'p-2' : 'p-3'}`}>
|
|
7
7
|
<SkeletonBlock width="200px" height={small ? 20 : undefined} />
|
|
8
8
|
<div className="flex items-center gap-2">
|
|
9
9
|
<SkeletonBlock width="150px" height={small ? 20 : undefined} />
|
|
@@ -7,6 +7,7 @@ import { ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-butt
|
|
|
7
7
|
import FormDataListTableComponent, { IReqDataConfig } from './table'
|
|
8
8
|
|
|
9
9
|
function FormDataListComponent({
|
|
10
|
+
formName,
|
|
10
11
|
formId,
|
|
11
12
|
userId,
|
|
12
13
|
companyKey,
|
|
@@ -24,7 +25,7 @@ function FormDataListComponent({
|
|
|
24
25
|
>()
|
|
25
26
|
|
|
26
27
|
const attachmentBaseUrl = useMemo(() => `${baseServerUrl}/api/attachment/${companyKey}`, [baseServerUrl, companyKey])
|
|
27
|
-
const constantValues = { formId, userId, attachmentBaseUrl }
|
|
28
|
+
const constantValues = { formId, userId, attachmentBaseUrl, formName }
|
|
28
29
|
|
|
29
30
|
useEffect(() => {
|
|
30
31
|
if (formId) {
|
|
@@ -84,6 +85,7 @@ function FormDataListComponent({
|
|
|
84
85
|
export { FormDataListComponent }
|
|
85
86
|
|
|
86
87
|
type IFormDataListComponent = {
|
|
88
|
+
formName?: string
|
|
87
89
|
baseServerUrl?: string
|
|
88
90
|
companyKey?: string
|
|
89
91
|
formId?: number
|
|
@@ -30,7 +30,7 @@ export default function FormDataListHeaderComponent({
|
|
|
30
30
|
}: IFormDataListHeaderComponent) {
|
|
31
31
|
const [dataListHeaderFormRef] = Form.useForm()
|
|
32
32
|
const [filterConfigs, setFilterConfigs] = useState<IFilterNested>({})
|
|
33
|
-
const { userId, formId, formDataId } = constantValues
|
|
33
|
+
const { userId, formId, formDataId, formName, parentInfo } = constantValues
|
|
34
34
|
|
|
35
35
|
const filterValues = Form.useWatch([], dataListHeaderFormRef)
|
|
36
36
|
|
|
@@ -60,7 +60,7 @@ export default function FormDataListHeaderComponent({
|
|
|
60
60
|
if ('type' in config) {
|
|
61
61
|
if (config.type === FilterConfigTypeEnum.ByLinkedForm) startLoading()
|
|
62
62
|
|
|
63
|
-
const filterData = await handleFilterValues(config as IFilterConfig, value, userId)
|
|
63
|
+
const filterData = await handleFilterValues(config as IFilterConfig, value, { userId })
|
|
64
64
|
filtersToApply.push(filterData)
|
|
65
65
|
} else {
|
|
66
66
|
// cases like radio buttons, where each has its own filter, but only 1 needs to apply
|
|
@@ -69,7 +69,7 @@ export default function FormDataListHeaderComponent({
|
|
|
69
69
|
|
|
70
70
|
if (eachConfig.type === FilterConfigTypeEnum.ByLinkedForm) startLoading()
|
|
71
71
|
|
|
72
|
-
const filterData = await handleFilterValues(eachConfig as IFilterConfig, value, userId)
|
|
72
|
+
const filterData = await handleFilterValues(eachConfig as IFilterConfig, value, { userId })
|
|
73
73
|
filtersToApply.push(filterData)
|
|
74
74
|
}
|
|
75
75
|
}
|
|
@@ -97,6 +97,7 @@ export default function FormDataListHeaderComponent({
|
|
|
97
97
|
|
|
98
98
|
return {
|
|
99
99
|
...c,
|
|
100
|
+
sort: defaultFilter.sort,
|
|
100
101
|
pagination: { ...defaultFilter.pagination, current: 1 },
|
|
101
102
|
customFilter,
|
|
102
103
|
dynamicFilter: JSON.stringify(dynamicFilter),
|
|
@@ -104,7 +105,7 @@ export default function FormDataListHeaderComponent({
|
|
|
104
105
|
})
|
|
105
106
|
} else updateReqData(defaultFilter)
|
|
106
107
|
},
|
|
107
|
-
[filterConfigs],
|
|
108
|
+
[filterConfigs, defaultFilter],
|
|
108
109
|
)
|
|
109
110
|
|
|
110
111
|
useEffect(() => {
|
|
@@ -125,6 +126,8 @@ export default function FormDataListHeaderComponent({
|
|
|
125
126
|
conditions={conditions}
|
|
126
127
|
formRef={dataListHeaderFormRef}
|
|
127
128
|
onCustomFunctionCall={onCustomFunctionCall}
|
|
129
|
+
formName={formName}
|
|
130
|
+
stateToPass={{ parentInfo }}
|
|
128
131
|
/>
|
|
129
132
|
)}
|
|
130
133
|
/>
|
|
@@ -162,11 +165,15 @@ const getLinkedFormFilter = async (
|
|
|
162
165
|
} else return { dataIds: parentDataIds, foreignKey: lastRel.foreignKey }
|
|
163
166
|
}
|
|
164
167
|
|
|
165
|
-
const handleFilterValues = async (
|
|
168
|
+
const handleFilterValues = async (
|
|
169
|
+
config: IFilterConfig,
|
|
170
|
+
value: any,
|
|
171
|
+
contextValues: { userId?: string | number; isDateFilter?: boolean },
|
|
172
|
+
) => {
|
|
166
173
|
if (config.type === FilterConfigTypeEnum.NoFilter) return DEFAULT_NO_FILTER
|
|
174
|
+
const { userId } = contextValues
|
|
167
175
|
|
|
168
|
-
|
|
169
|
-
if (isDate) value = dayjs(value as Date).toISOString()
|
|
176
|
+
if ((config as IFilterCustom).isDateFilter) value = dayjs(value as Date).toISOString()
|
|
170
177
|
|
|
171
178
|
let customFilters = {}
|
|
172
179
|
if (config.type === FilterConfigTypeEnum.ByAuthUser) customFilters = { createdById: userId }
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
2
|
-
import { IFormData, IFormDataListConfig, IFormLayoutRow, ITableColumn } from '../../../types'
|
|
3
|
-
import FormDataListHeaderComponent from './header'
|
|
2
|
+
import { IDataListParentInfo, IFormData, IFormDataListConfig, IFormLayoutRow, ITableColumn } from '../../../types'
|
|
3
|
+
import FormDataListHeaderComponent from './table-header'
|
|
4
4
|
import useDebounced from '../../common/custom-hooks/use-debounce.hook'
|
|
5
5
|
import { Table } from 'antd'
|
|
6
6
|
import { FilterConfigTypeEnum, FormDataListViewTypeEnum, MongoDbSortOrderEnum } from '../../../enums'
|
|
@@ -26,7 +26,9 @@ export default function FormDataListTableComponent({
|
|
|
26
26
|
undefined,
|
|
27
27
|
250,
|
|
28
28
|
)
|
|
29
|
-
const [otherConfigs, setOtherConfigs] = useState<
|
|
29
|
+
const [otherConfigs, setOtherConfigs] = useState<
|
|
30
|
+
(Partial<IFormDataListConfig> & { hasNoPagination: boolean }) | undefined
|
|
31
|
+
>(undefined)
|
|
30
32
|
const [headerLayout, setHeaderLayout] = useState<IFormLayoutRow[]>([])
|
|
31
33
|
const [isHandlingSchema, setIsHandlingSchema] = useState(true)
|
|
32
34
|
const isFinishedHandlingRef = useRef(false)
|
|
@@ -37,16 +39,14 @@ export default function FormDataListTableComponent({
|
|
|
37
39
|
pagination: DEFAULT_PAGINATION,
|
|
38
40
|
})
|
|
39
41
|
|
|
40
|
-
const { attachmentBaseUrl, userId, formId } = constantValues
|
|
42
|
+
const { attachmentBaseUrl, userId, formId, formName } = constantValues
|
|
41
43
|
|
|
42
44
|
useEffect(() => {
|
|
43
45
|
setIsHandlingSchema(true)
|
|
44
46
|
}, [location.pathname])
|
|
45
47
|
|
|
46
48
|
useEffect(() => {
|
|
47
|
-
if (debouncedFilterReqData)
|
|
48
|
-
updateDataList(debouncedFilterReqData)
|
|
49
|
-
}
|
|
49
|
+
if (debouncedFilterReqData) updateDataList(debouncedFilterReqData)
|
|
50
50
|
}, [debouncedFilterReqData])
|
|
51
51
|
|
|
52
52
|
const handleDataListConfig = useCallback(
|
|
@@ -68,22 +68,22 @@ export default function FormDataListTableComponent({
|
|
|
68
68
|
if (elementsWithOptions_key.length > 0)
|
|
69
69
|
optionKeyValuePair = await extractElementsOptions(layout, elementsWithOptions_key)
|
|
70
70
|
|
|
71
|
-
setTableColumns(generateTableColumns(elements, optionKeyValuePair, attachmentBaseUrl))
|
|
71
|
+
setTableColumns(generateTableColumns(elements, optionKeyValuePair, { attachmentBaseUrl, formName }))
|
|
72
72
|
setHeaderLayout(header.layout)
|
|
73
|
-
setOtherConfigs(restConfigs)
|
|
73
|
+
setOtherConfigs({ ...restConfigs, hasNoPagination: pagination?.hasNoPagination ?? false })
|
|
74
74
|
|
|
75
75
|
if (defaultFilter && defaultFilter.type !== FilterConfigTypeEnum.NoFilter)
|
|
76
76
|
defaultFilterRef.current = {
|
|
77
77
|
...defaultFilterRef.current,
|
|
78
78
|
customFilter: defaultFilter.type === FilterConfigTypeEnum.ByAuthUser ? { createdById: userId } : {},
|
|
79
79
|
dynamicFilter: defaultFilter.config
|
|
80
|
-
? // The operators are stored using @, and here it replaces @ with $. Otherwise, MongoDB's JSON-to-BSON conversion driver
|
|
80
|
+
? // The operators are stored using @, and here it replaces @ with $. Otherwise, MongoDB's JSON-to-BSON conversion driver was detecting certain operators, especially $regex, and automatically modified them. To prevent data manipulation by the MongoDB driver, @ is used instead of $.
|
|
81
81
|
JSON.stringify(defaultFilter.config).replaceAll('@', '$')
|
|
82
82
|
: JSON.stringify({}),
|
|
83
|
-
sort: defaultSorter ? { [defaultSorter.field]: defaultSorter.order } : {},
|
|
84
83
|
}
|
|
85
84
|
defaultFilterRef.current = {
|
|
86
85
|
...defaultFilterRef.current,
|
|
86
|
+
sort: defaultSorter ? { [defaultSorter.field]: defaultSorter.order } : {},
|
|
87
87
|
pagination: {
|
|
88
88
|
current: 1,
|
|
89
89
|
pageSize: pagination?.defaultPageSize ?? 10,
|
|
@@ -95,7 +95,7 @@ export default function FormDataListTableComponent({
|
|
|
95
95
|
setIsHandlingSchema(false)
|
|
96
96
|
isFinishedHandlingRef.current = true
|
|
97
97
|
},
|
|
98
|
-
[attachmentBaseUrl, userId, formId],
|
|
98
|
+
[attachmentBaseUrl, formName, userId, formId],
|
|
99
99
|
)
|
|
100
100
|
|
|
101
101
|
useEffect(() => {
|
|
@@ -131,7 +131,7 @@ export default function FormDataListTableComponent({
|
|
|
131
131
|
columns={tableColumns}
|
|
132
132
|
rowKey={(record) => record.id}
|
|
133
133
|
pagination={
|
|
134
|
-
|
|
134
|
+
otherConfigs?.hasNoPagination
|
|
135
135
|
? false
|
|
136
136
|
: { ...defaultFilterRef.current.pagination, ...filterReqData?.pagination, total: dataList.total }
|
|
137
137
|
}
|
|
@@ -205,4 +205,6 @@ export interface IConstantValues {
|
|
|
205
205
|
formId?: number
|
|
206
206
|
attachmentBaseUrl?: string
|
|
207
207
|
formDataId?: string
|
|
208
|
+
formName?: string
|
|
209
|
+
parentInfo?: IDataListParentInfo
|
|
208
210
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useNavigate } from 'react-router-dom'
|
|
2
2
|
import { useNotification } from '../../../../common/custom-hooks'
|
|
3
3
|
import { useCheckElementConditions } from '../../../../common/custom-hooks/use-check-element-conditions.hook'
|
|
4
4
|
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
|
5
5
|
import { ButtonActionCategoryEnum, FormLoadingModalTypeEnum, FormPreservedItemKeys } from '../../../../../enums'
|
|
6
6
|
import { useFormPreservedItemValues } from '../../../../common/custom-hooks/use-preserved-form-items.hook'
|
|
7
|
-
import {
|
|
7
|
+
import { constructDynamicFormHref } from '../../../../../functions'
|
|
8
8
|
import { useDuplicateDataAction } from './use-duplicate-data.hook'
|
|
9
9
|
import { useDeleteDataAction } from './use-delete-data.hook'
|
|
10
10
|
import { usePublishDataAction } from './use-publish-data.hook'
|
|
@@ -16,21 +16,30 @@ import { NEW_FORM_DATA_IDENTIFIER } from '../../../../../constants'
|
|
|
16
16
|
import { Button_FillerPortal } from '../../../../common/button'
|
|
17
17
|
import { getButtonRenderProps } from '../../../../../functions/get-element-props'
|
|
18
18
|
import WarningIcon from '../../../../common/warning-icon'
|
|
19
|
-
import { IDataRender_ButtonProps, IFormLayoutElementConditions } from '../../../../../types'
|
|
19
|
+
import { IButtonNavigateState, IDataRender_ButtonProps, IFormLayoutElementConditions } from '../../../../../types'
|
|
20
20
|
import { FormInstance } from 'antd'
|
|
21
21
|
import FormDataLoadingIndicatorModal from '../../../../modals/form-data-loading.modal'
|
|
22
22
|
|
|
23
23
|
export const DynamicFormButtonRender = memo((props: IDynamicButton) => {
|
|
24
|
-
const {
|
|
24
|
+
const {
|
|
25
|
+
btnProps,
|
|
26
|
+
conditions,
|
|
27
|
+
formDataId,
|
|
28
|
+
formRef,
|
|
29
|
+
defaulDisabled = false,
|
|
30
|
+
onCustomFunctionCall = () => {},
|
|
31
|
+
formName = '',
|
|
32
|
+
stateToPass = {},
|
|
33
|
+
} = props
|
|
25
34
|
|
|
26
35
|
const navigate = useNavigate()
|
|
27
|
-
const location = useLocation()
|
|
28
36
|
const { success, warning, error, confirmModal } = useNotification()
|
|
29
37
|
const { isElementDisabled, isElementHidden } = useCheckElementConditions(formRef, conditions, defaulDisabled)
|
|
30
38
|
const [loading, setLoading] = useState(false)
|
|
31
39
|
const [dataLoadingType, setDataLoadingType] = useState<FormLoadingModalTypeEnum | undefined>()
|
|
32
40
|
const { inPreviewMode, isPublic = false } = useFormPreservedItemValues(formRef)
|
|
33
|
-
|
|
41
|
+
|
|
42
|
+
const baseDynamicUrl = useMemo(() => constructDynamicFormHref(formName), [formName])
|
|
34
43
|
|
|
35
44
|
useEffect(() => {
|
|
36
45
|
formRef?.setFieldValue(FormPreservedItemKeys.FormDataId, formDataId)
|
|
@@ -102,7 +111,8 @@ export const DynamicFormButtonRender = memo((props: IDynamicButton) => {
|
|
|
102
111
|
const handleButtonClick = useCallback(async () => {
|
|
103
112
|
switch (btnProps.category) {
|
|
104
113
|
case ButtonActionCategoryEnum.CreateNewData:
|
|
105
|
-
|
|
114
|
+
// window.location.href = `${baseDynamicUrl}/${NEW_FORM_DATA_IDENTIFIER}`
|
|
115
|
+
navigate(`${baseDynamicUrl}/${NEW_FORM_DATA_IDENTIFIER}`, { state: stateToPass })
|
|
106
116
|
setLoading(false)
|
|
107
117
|
break
|
|
108
118
|
|
|
@@ -246,6 +256,8 @@ type IDynamicButton = {
|
|
|
246
256
|
formDataId?: string
|
|
247
257
|
formRef?: FormInstance
|
|
248
258
|
defaulDisabled?: boolean
|
|
259
|
+
formName?: string
|
|
260
|
+
stateToPass?: IButtonNavigateState
|
|
249
261
|
} & ICustomFunctionCall
|
|
250
262
|
|
|
251
263
|
export interface ICustomFunctionCall {
|
|
@@ -2,9 +2,12 @@ import { Key, ReactNode, Fragment } from 'react'
|
|
|
2
2
|
import { FaCaretDown } from 'react-icons/fa'
|
|
3
3
|
import dayjs, { Dayjs } from 'dayjs'
|
|
4
4
|
import { FaUpload } from 'react-icons/fa6'
|
|
5
|
-
import { CountryEnum, ElementTypeEnum } from '../../../../enums'
|
|
6
|
-
import { IValidationRule, mapToFormItemRules
|
|
5
|
+
import { CountryEnum, DataSanitizationTypeEnum, ElementTypeEnum } from '../../../../enums'
|
|
6
|
+
import { formatByPattern, IValidationRule, mapToFormItemRules } from '../../../../functions'
|
|
7
7
|
import { DisabledFieldIndicator } from '../../../common/disabled-field-indicator'
|
|
8
|
+
import CustomColorField from '../../../common/color-field'
|
|
9
|
+
import { IDataSanitization } from '../../../../types'
|
|
10
|
+
import CurrencyField from '../../../common/currency-field'
|
|
8
11
|
import {
|
|
9
12
|
Checkbox,
|
|
10
13
|
Form,
|
|
@@ -18,7 +21,6 @@ import {
|
|
|
18
21
|
Switch,
|
|
19
22
|
FormInstance,
|
|
20
23
|
} from 'antd'
|
|
21
|
-
import CustomColorField from '../../../common/color-field'
|
|
22
24
|
|
|
23
25
|
export const LayoutRenderer_FieldElement = ({
|
|
24
26
|
fields,
|
|
@@ -45,9 +47,11 @@ export const LayoutRenderer_FieldElement = ({
|
|
|
45
47
|
}
|
|
46
48
|
rules={mapToFormItemRules(field.validations ?? [], field.name as string)}
|
|
47
49
|
normalize={(value) => {
|
|
48
|
-
|
|
50
|
+
const sanitizationRule = field.sanitization
|
|
51
|
+
if (!sanitizationRule || sanitizationRule.type === DataSanitizationTypeEnum.Default) return value
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
if (sanitizationRule.type === DataSanitizationTypeEnum.Custom)
|
|
54
|
+
return formatByPattern(value, sanitizationRule.pattern)
|
|
51
55
|
}}
|
|
52
56
|
>
|
|
53
57
|
{field.type === ElementTypeEnum.ColorPicker ? (
|
|
@@ -74,6 +78,8 @@ const getField = ({
|
|
|
74
78
|
numberFieldMin,
|
|
75
79
|
numberFieldMax,
|
|
76
80
|
isCustom,
|
|
81
|
+
country = CountryEnum.US,
|
|
82
|
+
decimalPoint = 0,
|
|
77
83
|
disabledDate,
|
|
78
84
|
}: Partial<IMapperFieldObj>): JSX.Element => {
|
|
79
85
|
placeholder = placeholder && placeholder.length > 0 ? placeholder : typeof label === 'string' ? label : ''
|
|
@@ -146,6 +152,10 @@ const getField = ({
|
|
|
146
152
|
// parser={(value) => (value ? parseFloat(value.replace(/\D/g, '')) : undefined)}
|
|
147
153
|
/>
|
|
148
154
|
)
|
|
155
|
+
case ElementTypeEnum.CurrencyInput:
|
|
156
|
+
return (
|
|
157
|
+
<CurrencyField placeholder={placeholder} disabled={disabled} country={country} decimalPoint={decimalPoint} />
|
|
158
|
+
)
|
|
149
159
|
case ElementTypeEnum.Password:
|
|
150
160
|
return <Input.Password placeholder={placeholder} disabled={disabled} />
|
|
151
161
|
case ElementTypeEnum.DatePicker:
|
|
@@ -204,7 +214,9 @@ interface IMapperFieldObj {
|
|
|
204
214
|
numberFieldMin?: number
|
|
205
215
|
numberFieldMax?: number
|
|
206
216
|
isCustom?: boolean
|
|
207
|
-
|
|
217
|
+
sanitization?: IDataSanitization
|
|
218
|
+
country?: CountryEnum
|
|
219
|
+
decimalPoint?: number
|
|
208
220
|
disabledDate?: (date: Dayjs) => boolean
|
|
209
221
|
}
|
|
210
222
|
interface ISelectOption {
|
|
@@ -6,7 +6,7 @@ import { useMemo } from 'react'
|
|
|
6
6
|
import { DataRenderTypeEnum } from '../../../../enums'
|
|
7
7
|
import { IFormItemPath } from '.'
|
|
8
8
|
|
|
9
|
-
export default function LayoutRenderer_ReadOnly({ formRef, elementProps }: ILayoutRenderer_ReadOnly) {
|
|
9
|
+
export default function LayoutRenderer_ReadOnly({ formRef, elementProps, style = {} }: ILayoutRenderer_ReadOnly) {
|
|
10
10
|
const fieldValue = elementProps.isHardCoded
|
|
11
11
|
? elementProps.value
|
|
12
12
|
: Form.useWatch(elementProps.value, { form: formRef, preserve: true })
|
|
@@ -22,7 +22,7 @@ export default function LayoutRenderer_ReadOnly({ formRef, elementProps }: ILayo
|
|
|
22
22
|
return (
|
|
23
23
|
<div className="flex items-center gap-2">
|
|
24
24
|
{elementProps.label && <span>{elementProps.label}: </span>}
|
|
25
|
-
<span
|
|
25
|
+
<span style={style}>
|
|
26
26
|
{renderData(
|
|
27
27
|
elementProps.renderConfig?.type === DataRenderTypeEnum.Image
|
|
28
28
|
? attachmentUrl
|
|
@@ -45,4 +45,5 @@ type ILayoutRenderer_ReadOnly = {
|
|
|
45
45
|
value?: string
|
|
46
46
|
renderConfig?: IDataRenderConfig
|
|
47
47
|
}
|
|
48
|
+
style?: { [key: string]: any }
|
|
48
49
|
} & IFormItemPath
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { FormInstance } from 'antd'
|
|
2
|
-
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
3
3
|
import { IFilterByLinkedFormRelPath, IFormLayoutFieldOption, IRadioElement, ISelectElement } from '../../../../types'
|
|
4
4
|
import { FieldElementOptionSourceEnum } from '../../../../enums'
|
|
5
5
|
import { IFormItemPath } from '.'
|
|
@@ -8,6 +8,7 @@ import { fetchFormDataAsLookup } from '../../../../functions'
|
|
|
8
8
|
import { getElementGeneralizedProps } from '../../../../functions/get-element-props'
|
|
9
9
|
import { useFormPreservedItemValues } from '../../../common/custom-hooks/use-preserved-form-items.hook'
|
|
10
10
|
import { IReqDataConfig } from '../../1-list/table'
|
|
11
|
+
import { useLocation } from 'react-router-dom'
|
|
11
12
|
|
|
12
13
|
export default function LayoutRenderer_FieldsWithOptions({
|
|
13
14
|
formRef,
|
|
@@ -15,23 +16,39 @@ export default function LayoutRenderer_FieldsWithOptions({
|
|
|
15
16
|
elementData,
|
|
16
17
|
isElementDisabled,
|
|
17
18
|
}: ILayoutRenderer_FieldsWithOptions) {
|
|
19
|
+
const { state: locationState } = useLocation()
|
|
18
20
|
const [options, setOptions] = useState<{ value: string; label: string }[]>([])
|
|
19
21
|
const props = getElementGeneralizedProps(elementData.props)
|
|
20
22
|
|
|
21
23
|
const { inPreviewMode, formDataId } = useFormPreservedItemValues(formRef)
|
|
22
24
|
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
)
|
|
31
|
-
|
|
25
|
+
const potentialParentDataIds = useMemo(() => {
|
|
26
|
+
if (typeof formItemName === 'string' && locationState?.parentInfo?.foreignKey === formItemName)
|
|
27
|
+
return locationState.parentInfo.dataIds
|
|
28
|
+
return []
|
|
29
|
+
}, [locationState, formItemName])
|
|
30
|
+
|
|
31
|
+
const fetchFormData = useCallback(
|
|
32
|
+
async (formId: number, field: string, filter?: Partial<IReqDataConfig>) => {
|
|
33
|
+
const formDataResData = await fetchFormDataAsLookup(formId, filter)
|
|
34
|
+
let apiOptions = formDataResData
|
|
35
|
+
if (potentialParentDataIds.length) {
|
|
36
|
+
apiOptions = apiOptions.filter((formData) => potentialParentDataIds.includes(formData.id))
|
|
37
|
+
if (potentialParentDataIds.length === 1) formRef.setFieldValue(formItemName, potentialParentDataIds[0])
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
setOptions(
|
|
41
|
+
apiOptions.map((formData) => ({
|
|
42
|
+
value: formData.id,
|
|
43
|
+
label: formData[field],
|
|
44
|
+
})),
|
|
45
|
+
)
|
|
46
|
+
},
|
|
47
|
+
[potentialParentDataIds],
|
|
48
|
+
)
|
|
32
49
|
|
|
33
50
|
const fetchLinkedFormData = useCallback(
|
|
34
|
-
|
|
51
|
+
(relationshipPath: IFilterByLinkedFormRelPath[] = []) => {
|
|
35
52
|
const lastPath = relationshipPath[relationshipPath.length - 1]
|
|
36
53
|
|
|
37
54
|
if (lastPath && lastPath.field)
|