form-craft-package 1.0.4 → 1.1.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/index.ts +2 -1
- package/package.json +6 -2
- package/src/components/common/button.tsx +1 -1
- package/src/components/common/custom-hooks/index.ts +2 -0
- package/src/components/common/custom-hooks/use-find-dynamic-form.ts +1 -8
- package/src/components/common/disabled-field-indicator.tsx +1 -1
- package/src/components/common/loading-skeletons/details.tsx +11 -0
- package/src/components/common/loading-skeletons/index.tsx +3 -0
- package/src/components/common/loading-skeletons/table.tsx +30 -0
- package/src/components/form/1-list/header.tsx +118 -0
- package/src/components/form/1-list/index.tsx +122 -0
- package/src/components/form/2-details/index.tsx +124 -0
- package/src/components/form/layout-renderer/1-row/index.tsx +40 -15
- package/src/components/form/layout-renderer/2-col/index.tsx +43 -21
- package/src/components/form/layout-renderer/3-element/1-dynamic-button.tsx +288 -248
- package/src/components/form/layout-renderer/3-element/2-field-element.tsx +0 -4
- package/src/components/form/layout-renderer/3-element/3-read-only.tsx +29 -0
- package/src/components/form/layout-renderer/3-element/4-rich-text-editor.tsx +37 -0
- package/src/components/form/layout-renderer/3-element/5-re-captcha.tsx +42 -0
- package/src/components/form/layout-renderer/3-element/6-signature.tsx +93 -0
- package/src/components/form/layout-renderer/3-element/7-file-upload.tsx +147 -0
- package/src/components/form/layout-renderer/3-element/index.tsx +66 -32
- package/src/components/index.tsx +3 -0
- package/src/components/modals/form-data-loading.modal.tsx +4 -4
- package/src/constants.ts +1 -1
- package/src/enums.ts +12 -5
- package/src/functions/data-render-functions.tsx +90 -0
- package/src/functions/index.ts +82 -2
- package/src/types/{form-data-list → form}/index.ts +8 -0
- package/src/types/index.ts +6 -11
- package/src/types/layout-elements/element-data-render-logic.ts +2 -1
- package/src/types/layout-elements/index.ts +28 -20
- package/src/types/layout-elements/style.ts +8 -3
- package/src/functions/data-list-functions.tsx +0 -41
package/index.ts
CHANGED
|
@@ -5,4 +5,5 @@ export * from './src/functions'
|
|
|
5
5
|
export * from './src/functions/json-handlers'
|
|
6
6
|
export * from './src/functions/form-schema-validator'
|
|
7
7
|
export * from './src/functions/get-element-props'
|
|
8
|
-
export * from './src/components'
|
|
8
|
+
export * from './src/components'
|
|
9
|
+
export * from './src/components/common/custom-hooks'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "form-craft-package",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"description": "",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"
|
|
14
|
+
"@types/react-google-recaptcha": "^2.1.9",
|
|
15
|
+
"ajv": "^8.17.1",
|
|
16
|
+
"react-google-recaptcha": "^3.1.0",
|
|
17
|
+
"react-signature-canvas": "^1.0.7"
|
|
15
18
|
},
|
|
16
19
|
"peerDependencies": {
|
|
17
20
|
"antd": ">=5.21.6",
|
|
@@ -29,6 +32,7 @@
|
|
|
29
32
|
"@types/js-cookie": "^3.0.6",
|
|
30
33
|
"@types/react": "^18.3.18",
|
|
31
34
|
"@types/react-dom": "^18.3.5",
|
|
35
|
+
"@types/react-signature-canvas": "^1.0.6",
|
|
32
36
|
"react": "^18.3.1",
|
|
33
37
|
"react-dom": "^18.3.1",
|
|
34
38
|
"ts-node": "^10.9.2",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react'
|
|
2
2
|
import { useParams } from 'react-router-dom'
|
|
3
3
|
import { LOCAL_STORAGE_KEYS_ENUM } from '../../../constants'
|
|
4
|
+
import { IDynamicForm } from '../../../types'
|
|
4
5
|
|
|
5
6
|
export const useFindDynamiForm = () => {
|
|
6
7
|
const { formName } = useParams<{ formName: string }>()
|
|
@@ -23,11 +24,3 @@ export const useFindDynamiForm = () => {
|
|
|
23
24
|
|
|
24
25
|
return foundItem
|
|
25
26
|
}
|
|
26
|
-
|
|
27
|
-
interface IDynamicForm {
|
|
28
|
-
id: number
|
|
29
|
-
keepVersionHistory: boolean
|
|
30
|
-
logo: string | null
|
|
31
|
-
name: string
|
|
32
|
-
version: number
|
|
33
|
-
}
|
|
@@ -9,7 +9,7 @@ export const DisabledFieldIndicator = ({
|
|
|
9
9
|
right?: number
|
|
10
10
|
left?: number
|
|
11
11
|
}) => (
|
|
12
|
-
<div style={{
|
|
12
|
+
<div className="absolute cursor-nodrop" style={{ top, right, left, color: '#a1a1a1' }}>
|
|
13
13
|
<div className="absolute">
|
|
14
14
|
<FaPencilAlt />
|
|
15
15
|
</div>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Spin } from 'antd'
|
|
2
|
+
|
|
3
|
+
export default function FormDataListSkeleton_Details() {
|
|
4
|
+
return (
|
|
5
|
+
<div className="p-3">
|
|
6
|
+
<div className="text-primary italic h-[300px] text-30 rounded-md bg-white flex items-center justify-center gap-2">
|
|
7
|
+
<Spin size="large" /> Loading data...
|
|
8
|
+
</div>
|
|
9
|
+
</div>
|
|
10
|
+
)
|
|
11
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import SkeletonBlock from '.'
|
|
2
|
+
|
|
3
|
+
export default function FormDataListSkeleton_Table() {
|
|
4
|
+
return (
|
|
5
|
+
<>
|
|
6
|
+
<div className="bg-white p-3 flex items-center justify-between rounded-md mb-2">
|
|
7
|
+
<SkeletonBlock width="200px" />
|
|
8
|
+
<div className="flex items-center gap-2">
|
|
9
|
+
<SkeletonBlock width="150px" />
|
|
10
|
+
<SkeletonBlock width="150px" />
|
|
11
|
+
<SkeletonBlock width="150px" />
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
<div className="border border-[#d4d4d8] rounded bg-white">
|
|
15
|
+
<div className="flex justify-between p-3 border-b border-[#d4d4d8]">
|
|
16
|
+
{[...Array(5)].map((_, elIdx) => (
|
|
17
|
+
<div key={elIdx}>
|
|
18
|
+
<SkeletonBlock height={20} width="150px" />
|
|
19
|
+
</div>
|
|
20
|
+
))}
|
|
21
|
+
</div>
|
|
22
|
+
<div className="px-3 py-2 flex flex-col gap-3">
|
|
23
|
+
{[...Array(5)].map((_, elIdx) => (
|
|
24
|
+
<SkeletonBlock key={elIdx} />
|
|
25
|
+
))}
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
</>
|
|
29
|
+
)
|
|
30
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Form } from 'antd'
|
|
2
|
+
import { ReactNode, useEffect, useState } from 'react'
|
|
3
|
+
import { IReqDataConfig } from '.'
|
|
4
|
+
import dayjs from 'dayjs'
|
|
5
|
+
import { IFilterConfig, IFilterNested, IFilterSimple, IFormLayoutRow } from '../../../types'
|
|
6
|
+
import { extractFiltersFromLayout } from '../../../functions'
|
|
7
|
+
import { FilterConfigTypeEnum } from '../../../enums'
|
|
8
|
+
import { LayoutRendererRow } from '../layout-renderer/1-row'
|
|
9
|
+
import { DynamicFormButtonRender, ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
|
|
10
|
+
|
|
11
|
+
export default function FormDataListHeaderComponent({
|
|
12
|
+
layout,
|
|
13
|
+
userId,
|
|
14
|
+
formId,
|
|
15
|
+
titleComponent,
|
|
16
|
+
setPaginatedReqData,
|
|
17
|
+
onCustomFunctionCall,
|
|
18
|
+
}: IFormDataListHeaderComponent) {
|
|
19
|
+
const [dataListHeaderFormRef] = Form.useForm()
|
|
20
|
+
const [appliedFilters, setAppliedFilters] = useState<IFilterSimple>({})
|
|
21
|
+
const [filterConfigs, setFilterConfigs] = useState<IFilterNested>({})
|
|
22
|
+
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
if (dataListHeaderFormRef) dataListHeaderFormRef.setFieldValue('formId', formId)
|
|
25
|
+
}, [dataListHeaderFormRef, formId])
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
setFilterConfigs(extractFiltersFromLayout(layout))
|
|
29
|
+
}, [layout])
|
|
30
|
+
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
const allCombinedFilters = {
|
|
33
|
+
customFilter: {},
|
|
34
|
+
dynamicFilter: {},
|
|
35
|
+
}
|
|
36
|
+
Object.values(appliedFilters).forEach((config) => {
|
|
37
|
+
if (config.type === FilterConfigTypeEnum.NoFilter) return
|
|
38
|
+
|
|
39
|
+
if (config.config?.customFilter)
|
|
40
|
+
allCombinedFilters.customFilter = { ...allCombinedFilters.customFilter, ...config.config.customFilter }
|
|
41
|
+
if (config.config?.dynamicFilter)
|
|
42
|
+
allCombinedFilters.dynamicFilter = { ...allCombinedFilters.dynamicFilter, ...config.config.dynamicFilter }
|
|
43
|
+
|
|
44
|
+
if (config.type === FilterConfigTypeEnum.ByMe)
|
|
45
|
+
allCombinedFilters.customFilter = { ...allCombinedFilters.customFilter, createdById: userId }
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
setPaginatedReqData((c) => ({
|
|
49
|
+
...c,
|
|
50
|
+
customFilter: allCombinedFilters.customFilter,
|
|
51
|
+
dynamicFilter: JSON.stringify(allCombinedFilters.dynamicFilter),
|
|
52
|
+
}))
|
|
53
|
+
}, [appliedFilters])
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Form
|
|
57
|
+
layout="vertical"
|
|
58
|
+
form={dataListHeaderFormRef}
|
|
59
|
+
onValuesChange={(changedValues) => {
|
|
60
|
+
Object.entries(changedValues).forEach(([key, value]) => {
|
|
61
|
+
const config = filterConfigs[key]
|
|
62
|
+
if (!config) {
|
|
63
|
+
console.warn(`Key '${key}' does not have a filter config`)
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
if ('type' in config) {
|
|
67
|
+
if (typeof value !== 'string') value = dayjs(value as Date).toISOString()
|
|
68
|
+
|
|
69
|
+
if (config.config && 'dynamicFilter' in config.config && config.config.dynamicFilter) {
|
|
70
|
+
let stringifiedDynamicFilter = JSON.stringify(config.config.dynamicFilter)
|
|
71
|
+
stringifiedDynamicFilter = stringifiedDynamicFilter.replaceAll('{{VALUE}}', value as string)
|
|
72
|
+
|
|
73
|
+
config.config.dynamicFilter = JSON.parse(stringifiedDynamicFilter)
|
|
74
|
+
}
|
|
75
|
+
if (config.config && 'customFilter' in config.config && config.config.customFilter) {
|
|
76
|
+
let stringifiedCustomFilter = JSON.stringify(config.config.customFilter)
|
|
77
|
+
stringifiedCustomFilter = stringifiedCustomFilter.replaceAll('{{VALUE}}', value as string)
|
|
78
|
+
|
|
79
|
+
config.config.customFilter = JSON.parse(stringifiedCustomFilter)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
setAppliedFilters((c) => ({ ...c, [key]: config as IFilterConfig }))
|
|
83
|
+
} else {
|
|
84
|
+
const nestedConfig = config as IFilterSimple
|
|
85
|
+
if (typeof value === 'string' && value in nestedConfig)
|
|
86
|
+
setAppliedFilters((c) => ({ ...c, [key]: nestedConfig[value as string] as IFilterConfig }))
|
|
87
|
+
else console.warn(`Invalid key '${value}' for nestedConfig`)
|
|
88
|
+
}
|
|
89
|
+
})
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
{layout.map((row, rowIdx) => (
|
|
93
|
+
<LayoutRendererRow
|
|
94
|
+
key={rowIdx}
|
|
95
|
+
rowData={row}
|
|
96
|
+
formRef={dataListHeaderFormRef}
|
|
97
|
+
titleComponent={titleComponent}
|
|
98
|
+
renderButton={(btnProps, conditions) => (
|
|
99
|
+
<DynamicFormButtonRender
|
|
100
|
+
btnProps={btnProps}
|
|
101
|
+
conditions={conditions}
|
|
102
|
+
formRef={dataListHeaderFormRef}
|
|
103
|
+
onCustomFunctionCall={onCustomFunctionCall}
|
|
104
|
+
/>
|
|
105
|
+
)}
|
|
106
|
+
/>
|
|
107
|
+
))}
|
|
108
|
+
</Form>
|
|
109
|
+
)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
type IFormDataListHeaderComponent = {
|
|
113
|
+
layout: IFormLayoutRow[]
|
|
114
|
+
setPaginatedReqData: React.Dispatch<React.SetStateAction<IReqDataConfig>>
|
|
115
|
+
titleComponent?: ReactNode
|
|
116
|
+
formId?: number
|
|
117
|
+
userId?: string | number
|
|
118
|
+
} & ICustomFunctionCall
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
2
|
+
import client from '../../../functions/axios-handler'
|
|
3
|
+
import { parseJSON } from '../../../functions/json-handlers'
|
|
4
|
+
import { generateTableColumns } from '../../../functions'
|
|
5
|
+
import FormDataListSkeleton_Table from '../../common/loading-skeletons/table'
|
|
6
|
+
import FormDataListHeaderComponent from './header'
|
|
7
|
+
import { FormDataListViewTypeEnum } from '../../../enums'
|
|
8
|
+
import { Table } from 'antd'
|
|
9
|
+
import { IFormData, IFormDataListConfig, IFormLayoutRow, IFormSchema, ITableColumn } from '../../../types'
|
|
10
|
+
import { ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
|
|
11
|
+
|
|
12
|
+
function FormDataListComponent({ formId, userId, onCustomFunctionCall }: IFormDataListComponent) {
|
|
13
|
+
const [loadings, setLoadings] = useState({ schema: true, data: true })
|
|
14
|
+
const [tableColumns, setTableColumns] = useState<ITableColumn[]>([])
|
|
15
|
+
const [paginatedReqData, setPaginatedReqData] = useState<IReqDataConfig>({
|
|
16
|
+
customFilter: { deletedDate: null },
|
|
17
|
+
dynamicFilter: JSON.stringify({}),
|
|
18
|
+
sort: {},
|
|
19
|
+
})
|
|
20
|
+
const [pagination, setPagination] = useState<IPagination | undefined>()
|
|
21
|
+
const [otherGenericConfigs, setOtherGenericConfigs] = useState<Partial<IFormDataListConfig> | undefined>(undefined)
|
|
22
|
+
const [formData, setFormData] = useState({ data: [], total: 0 })
|
|
23
|
+
const [headerLayout, setHeaderLayout] = useState<IFormLayoutRow[]>([])
|
|
24
|
+
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (formId) {
|
|
27
|
+
client
|
|
28
|
+
.get(`/api/form/${formId}`)
|
|
29
|
+
.then((res) => {
|
|
30
|
+
if (res.status === 200) {
|
|
31
|
+
const parsedData: IFormSchema | null = parseJSON(res.data.data)
|
|
32
|
+
if (parsedData) {
|
|
33
|
+
const { elements, header, pagination: apiPagination, ...restConfigs } = parsedData.dataListConfig
|
|
34
|
+
setTableColumns(generateTableColumns(elements))
|
|
35
|
+
setPagination({
|
|
36
|
+
current: 1,
|
|
37
|
+
pageSize: apiPagination?.defaultPageSize ?? 10,
|
|
38
|
+
showSizeChanger: apiPagination?.showSizeChanger ?? false,
|
|
39
|
+
pageSizeOptions: apiPagination?.pageSizeOptions ?? [],
|
|
40
|
+
})
|
|
41
|
+
setHeaderLayout(header.layout)
|
|
42
|
+
setOtherGenericConfigs(restConfigs)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
.finally(() => setLoadings((c) => ({ ...c, schema: false })))
|
|
47
|
+
}
|
|
48
|
+
}, [formId])
|
|
49
|
+
|
|
50
|
+
const fetchFormDataList = useCallback(
|
|
51
|
+
(dynamicFormId: number) => {
|
|
52
|
+
if (!pagination) {
|
|
53
|
+
setLoadings((c) => ({ ...c, data: false }))
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
client
|
|
58
|
+
.post(`/api/formdata/list/${dynamicFormId}`, paginatedReqData)
|
|
59
|
+
.then((res) => {
|
|
60
|
+
if (res.status === 200)
|
|
61
|
+
setFormData({ ...res.data, data: res.data.data.map((d: IFormData) => ({ ...d, ...JSON.parse(d.data) })) })
|
|
62
|
+
})
|
|
63
|
+
.finally(() => setLoadings((c) => ({ ...c, data: false })))
|
|
64
|
+
},
|
|
65
|
+
[paginatedReqData, pagination],
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (formId) fetchFormDataList(formId)
|
|
70
|
+
}, [formId, fetchFormDataList])
|
|
71
|
+
|
|
72
|
+
if (loadings.schema) return <FormDataListSkeleton_Table />
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div>
|
|
76
|
+
<FormDataListHeaderComponent
|
|
77
|
+
layout={headerLayout}
|
|
78
|
+
setPaginatedReqData={setPaginatedReqData}
|
|
79
|
+
userId={userId}
|
|
80
|
+
formId={formId}
|
|
81
|
+
titleComponent={
|
|
82
|
+
<div className="text-primary font-bold text-18 flex items-center gap-2">
|
|
83
|
+
<span>{otherGenericConfigs?.title}</span>
|
|
84
|
+
{otherGenericConfigs?.showCount && <span>({formData.total})</span>}
|
|
85
|
+
</div>
|
|
86
|
+
}
|
|
87
|
+
onCustomFunctionCall={onCustomFunctionCall}
|
|
88
|
+
/>
|
|
89
|
+
{otherGenericConfigs?.listType === FormDataListViewTypeEnum.Table && (
|
|
90
|
+
<Table<IFormData>
|
|
91
|
+
dataSource={formData.data}
|
|
92
|
+
columns={tableColumns}
|
|
93
|
+
rowKey={(record) => record.id}
|
|
94
|
+
pagination={pagination}
|
|
95
|
+
loading={loadings.data}
|
|
96
|
+
onChange={(pagination, _, sorter) => {
|
|
97
|
+
console.log('ON TABLE CHANGE', pagination, sorter)
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
)}
|
|
101
|
+
</div>
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
export { FormDataListComponent }
|
|
105
|
+
|
|
106
|
+
export interface IReqDataConfig {
|
|
107
|
+
customFilter: {}
|
|
108
|
+
dynamicFilter: {}
|
|
109
|
+
sort: {}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
interface IPagination {
|
|
113
|
+
current: number
|
|
114
|
+
pageSize: number
|
|
115
|
+
showSizeChanger?: boolean
|
|
116
|
+
pageSizeOptions?: number[]
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
type IFormDataListComponent = {
|
|
120
|
+
formId?: number
|
|
121
|
+
userId: string | number
|
|
122
|
+
} & ICustomFunctionCall
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { Form } from 'antd'
|
|
2
|
+
import { useCallback, useEffect, useState } from 'react'
|
|
3
|
+
import { IFormLayoutRow, IFormSchema, IMigrationRule } from '../../../types'
|
|
4
|
+
import { NEW_FORM_DATA_IDENTIFIER } from '../../../constants'
|
|
5
|
+
import client from '../../../functions/axios-handler'
|
|
6
|
+
import { parseJSON } from '../../../functions/json-handlers'
|
|
7
|
+
import { LayoutRendererRow } from '../layout-renderer/1-row'
|
|
8
|
+
import { DynamicFormButtonRender, ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
|
|
9
|
+
import FormDataListSkeleton_Details from '../../common/loading-skeletons/details'
|
|
10
|
+
import { useLocation } from 'react-router-dom'
|
|
11
|
+
import { queryParamsToObject } from '../../../functions'
|
|
12
|
+
|
|
13
|
+
export default function FormDataDetailsComponent({
|
|
14
|
+
isPublic,
|
|
15
|
+
formId,
|
|
16
|
+
formKey,
|
|
17
|
+
formDataId,
|
|
18
|
+
companyKey,
|
|
19
|
+
baseServerUrl,
|
|
20
|
+
onCustomFunctionCall,
|
|
21
|
+
}: IFormDataDetailsComponent) {
|
|
22
|
+
const [formDataRef] = Form.useForm()
|
|
23
|
+
const [loadings, setLoadings] = useState({ layout: true, data: true })
|
|
24
|
+
const [layout, setLayout] = useState<IFormLayoutRow[]>([])
|
|
25
|
+
const location = useLocation()
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
// this is for converting the screen to PDF
|
|
29
|
+
if (isPublic && location.search) {
|
|
30
|
+
const formValues = queryParamsToObject(location.search)
|
|
31
|
+
formDataRef.setFieldsValue(formValues)
|
|
32
|
+
}
|
|
33
|
+
}, [isPublic, location, formDataRef])
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (formDataRef) formDataRef.setFieldsValue({ formId, formKey, baseServerUrl, formDataId, companyKey })
|
|
37
|
+
}, [formDataRef, formId, formKey, baseServerUrl, formDataId, companyKey])
|
|
38
|
+
|
|
39
|
+
const fetchFormData = useCallback(
|
|
40
|
+
(dFormId: number, migrationRules: { [key: string]: IMigrationRule[] }) => {
|
|
41
|
+
if (formDataId === NEW_FORM_DATA_IDENTIFIER) setLoadings((c) => ({ ...c, data: false }))
|
|
42
|
+
else {
|
|
43
|
+
client
|
|
44
|
+
.get(`/api/formdata/${dFormId}/${formDataId}`)
|
|
45
|
+
.then((res) => {
|
|
46
|
+
if (res.status === 200) {
|
|
47
|
+
const { data: jsonData, ...restFormData } = res.data
|
|
48
|
+
const parsedFormData = parseJSON(jsonData)
|
|
49
|
+
if (parsedFormData) formDataRef.setFieldsValue({ ...restFormData, ...parsedFormData })
|
|
50
|
+
console.log({ parsedFormData })
|
|
51
|
+
console.log({ migrationRules })
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
.finally(() => setLoadings((c) => ({ ...c, data: false })))
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
[formDataId, formDataRef],
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (formId || formKey) {
|
|
62
|
+
const endpoint = isPublic ? `/api/site/${formKey}` : `/api/form/${formId}`
|
|
63
|
+
client
|
|
64
|
+
.get(endpoint)
|
|
65
|
+
.then((res) => {
|
|
66
|
+
if (res.status === 200) {
|
|
67
|
+
const parsedData: IFormSchema | null = parseJSON(res.data.data)
|
|
68
|
+
if (parsedData) {
|
|
69
|
+
const { layout, migrationRules, convertScreenToPdf = false } = parsedData.detailsConfig
|
|
70
|
+
|
|
71
|
+
if (isPublic) {
|
|
72
|
+
formDataRef.setFieldValue('convertScreenToPdf', convertScreenToPdf)
|
|
73
|
+
setLoadings((c) => ({ ...c, data: false }))
|
|
74
|
+
} else fetchFormData(formId, migrationRules)
|
|
75
|
+
setLayout(layout)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
.finally(() => setLoadings((c) => ({ ...c, layout: false })))
|
|
80
|
+
}
|
|
81
|
+
}, [formId, formKey, isPublic, formDataRef, fetchFormData])
|
|
82
|
+
|
|
83
|
+
if (loadings.layout || loadings.data) return <FormDataListSkeleton_Details />
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<Form layout="vertical" name="dynamic_form_data_form" form={formDataRef}>
|
|
87
|
+
{layout.map((row, rowIdx) => (
|
|
88
|
+
<LayoutRendererRow
|
|
89
|
+
key={rowIdx}
|
|
90
|
+
rowData={row}
|
|
91
|
+
formRef={formDataRef}
|
|
92
|
+
renderButton={(btnProps, conditions) => (
|
|
93
|
+
<DynamicFormButtonRender
|
|
94
|
+
isPublic={isPublic}
|
|
95
|
+
btnProps={btnProps}
|
|
96
|
+
conditions={conditions}
|
|
97
|
+
formDataId={formDataId}
|
|
98
|
+
formRef={formDataRef}
|
|
99
|
+
onCustomFunctionCall={onCustomFunctionCall}
|
|
100
|
+
/>
|
|
101
|
+
)}
|
|
102
|
+
/>
|
|
103
|
+
))}
|
|
104
|
+
</Form>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
type IFormDataDetailsComponent = {
|
|
109
|
+
baseServerUrl?: string
|
|
110
|
+
companyKey?: string
|
|
111
|
+
formDataId: string
|
|
112
|
+
} & (IDataDetailsPublicProps | IDataDetailsPrivateProps) &
|
|
113
|
+
ICustomFunctionCall
|
|
114
|
+
|
|
115
|
+
interface IDataDetailsPublicProps {
|
|
116
|
+
isPublic: true
|
|
117
|
+
formKey: string
|
|
118
|
+
formId?: number
|
|
119
|
+
}
|
|
120
|
+
interface IDataDetailsPrivateProps {
|
|
121
|
+
isPublic: false
|
|
122
|
+
formId: number
|
|
123
|
+
formKey?: string
|
|
124
|
+
}
|
|
@@ -1,27 +1,52 @@
|
|
|
1
|
+
import { FormInstance } from 'antd'
|
|
1
2
|
import { ResponsivenessDeviceEnum } from '../../../../enums'
|
|
2
|
-
import { getFlexContainerStyle, getFlexItemStyle } from '../../../../functions'
|
|
3
|
-
import { IDataRender_ButtonProps, IFormLayoutRow } from '../../../../types'
|
|
3
|
+
import { getFlexContainerStyle, getFlexItemStyle, kebabCaseToCamelCase } from '../../../../functions'
|
|
4
|
+
import { IDataRender_ButtonProps, IFormLayoutElementConditions, IFormLayoutRow } from '../../../../types'
|
|
4
5
|
import LayoutRendererCol from '../2-col'
|
|
5
|
-
import { ReactElement, ReactNode } from 'react'
|
|
6
|
+
import { ReactElement, ReactNode, useMemo } from 'react'
|
|
6
7
|
|
|
7
8
|
export const LayoutRendererRow = ({
|
|
8
9
|
rowData,
|
|
10
|
+
formRef,
|
|
9
11
|
titleComponent,
|
|
10
12
|
breakpointDevice = ResponsivenessDeviceEnum.Default,
|
|
11
13
|
renderButton,
|
|
12
|
-
}: {
|
|
14
|
+
}: ILayoutRendererRow) => {
|
|
15
|
+
const styleConfig = useMemo(() => {
|
|
16
|
+
if (!rowData.style || !rowData.style[breakpointDevice]) return {}
|
|
17
|
+
return kebabCaseToCamelCase(rowData.style[breakpointDevice])
|
|
18
|
+
}, [rowData.style, breakpointDevice])
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<>
|
|
22
|
+
{rowData.props?.name && <div>{rowData.props?.name}</div>}
|
|
23
|
+
<div
|
|
24
|
+
style={{
|
|
25
|
+
...styleConfig,
|
|
26
|
+
...getFlexContainerStyle(breakpointDevice, rowData.responsiveness),
|
|
27
|
+
}}
|
|
28
|
+
>
|
|
29
|
+
{rowData.children.map((col, colIdx) => (
|
|
30
|
+
<div key={colIdx} style={getFlexItemStyle(breakpointDevice, colIdx, rowData.responsiveness)}>
|
|
31
|
+
<LayoutRendererCol
|
|
32
|
+
key={colIdx}
|
|
33
|
+
colData={col}
|
|
34
|
+
formRef={formRef}
|
|
35
|
+
titleComponent={titleComponent}
|
|
36
|
+
breakpointDevice={breakpointDevice}
|
|
37
|
+
renderButton={renderButton}
|
|
38
|
+
/>
|
|
39
|
+
</div>
|
|
40
|
+
))}
|
|
41
|
+
</div>
|
|
42
|
+
</>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface ILayoutRendererRow {
|
|
13
47
|
rowData: IFormLayoutRow
|
|
48
|
+
formRef: FormInstance
|
|
14
49
|
titleComponent?: ReactNode
|
|
15
50
|
breakpointDevice?: ResponsivenessDeviceEnum
|
|
16
|
-
renderButton?: (props: IDataRender_ButtonProps) => ReactElement
|
|
17
|
-
}) => {
|
|
18
|
-
return (
|
|
19
|
-
<div style={{ ...(rowData.style ?? {}), ...getFlexContainerStyle(breakpointDevice, rowData.responsiveness) }}>
|
|
20
|
-
{rowData.children.map((col, colIdx) => (
|
|
21
|
-
<div key={colIdx} style={getFlexItemStyle(breakpointDevice, colIdx, rowData.responsiveness)}>
|
|
22
|
-
<LayoutRendererCol key={colIdx} colData={col} titleComponent={titleComponent} renderButton={renderButton} />
|
|
23
|
-
</div>
|
|
24
|
-
))}
|
|
25
|
-
</div>
|
|
26
|
-
)
|
|
51
|
+
renderButton?: (props: IDataRender_ButtonProps, conditions?: IFormLayoutElementConditions) => ReactElement
|
|
27
52
|
}
|
|
@@ -1,32 +1,54 @@
|
|
|
1
1
|
import { ReactElement, ReactNode } from 'react'
|
|
2
2
|
import { LayoutRendererRow } from '../1-row'
|
|
3
3
|
import LayoutRendererElement from '../3-element'
|
|
4
|
-
import {
|
|
5
|
-
|
|
4
|
+
import {
|
|
5
|
+
IDataRender_ButtonProps,
|
|
6
|
+
IFormLayoutCol,
|
|
7
|
+
IFormLayoutElement,
|
|
8
|
+
IFormLayoutElementConditions,
|
|
9
|
+
IFormLayoutRow,
|
|
10
|
+
} from '../../../../types'
|
|
11
|
+
import { FormLayoutNodeEnum, ResponsivenessDeviceEnum } from '../../../../enums'
|
|
12
|
+
import { FormInstance } from 'antd'
|
|
6
13
|
|
|
7
14
|
export default function LayoutRendererCol({
|
|
8
15
|
colData,
|
|
16
|
+
formRef,
|
|
9
17
|
titleComponent,
|
|
18
|
+
breakpointDevice,
|
|
10
19
|
renderButton,
|
|
11
|
-
}: {
|
|
20
|
+
}: ILayoutRendererCol) {
|
|
21
|
+
return colData.children.map((item: IFormLayoutRow | IFormLayoutElement) =>
|
|
22
|
+
item.nodeType === FormLayoutNodeEnum.Row ? (
|
|
23
|
+
<LayoutRendererRow
|
|
24
|
+
key={item.id}
|
|
25
|
+
rowData={item}
|
|
26
|
+
formRef={formRef}
|
|
27
|
+
titleComponent={titleComponent}
|
|
28
|
+
breakpointDevice={breakpointDevice}
|
|
29
|
+
renderButton={renderButton}
|
|
30
|
+
/>
|
|
31
|
+
) : (
|
|
32
|
+
<div className="flex flex-col" key={item.id}>
|
|
33
|
+
<LayoutRendererElement
|
|
34
|
+
elementData={item}
|
|
35
|
+
formRef={formRef}
|
|
36
|
+
titleComponent={titleComponent}
|
|
37
|
+
breakpointDevice={breakpointDevice}
|
|
38
|
+
renderButton={renderButton}
|
|
39
|
+
/>
|
|
40
|
+
{item.props && 'description' in item.props && (
|
|
41
|
+
<span className="text-neutral text-12 italic">{item.props.description}</span>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
),
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface ILayoutRendererCol {
|
|
12
49
|
colData: IFormLayoutCol
|
|
50
|
+
formRef: FormInstance
|
|
13
51
|
titleComponent?: ReactNode
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<div className="space-y-2">
|
|
18
|
-
{colData.children.map((item: IFormLayoutRow | IFormLayoutElement) =>
|
|
19
|
-
item.nodeType === FormLayoutNodeEnum.Row ? (
|
|
20
|
-
<LayoutRendererRow key={item.id} rowData={item} titleComponent={titleComponent} renderButton={renderButton} />
|
|
21
|
-
) : (
|
|
22
|
-
<div className="flex flex-col" key={item.id}>
|
|
23
|
-
<LayoutRendererElement elementData={item} titleComponent={titleComponent} renderButton={renderButton} />
|
|
24
|
-
{item.props && 'description' in item.props && (
|
|
25
|
-
<span className="text-neutral text-12 italic">{item.props.description}</span>
|
|
26
|
-
)}
|
|
27
|
-
</div>
|
|
28
|
-
),
|
|
29
|
-
)}
|
|
30
|
-
</div>
|
|
31
|
-
)
|
|
52
|
+
breakpointDevice: ResponsivenessDeviceEnum
|
|
53
|
+
renderButton?: (props: IDataRender_ButtonProps, conditions?: IFormLayoutElementConditions) => ReactElement
|
|
32
54
|
}
|