form-craft-package 1.1.9 → 1.1.11
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/custom-hooks/index.ts +1 -1
- package/src/components/common/custom-hooks/use-check-element-conditions.hook.ts +50 -0
- package/src/components/common/custom-hooks/use-preserved-form-items.hook.ts +31 -0
- package/src/components/form/1-list/header.tsx +21 -29
- package/src/components/form/1-list/index.tsx +69 -28
- package/src/components/form/2-details/index.tsx +11 -3
- package/src/components/form/layout-renderer/1-row/index.tsx +9 -10
- package/src/components/form/layout-renderer/2-col/index.tsx +46 -27
- package/src/components/form/layout-renderer/3-element/1-dynamic-button.tsx +60 -51
- package/src/components/form/layout-renderer/3-element/2-field-element.tsx +16 -34
- package/src/components/form/layout-renderer/3-element/4-rich-text-editor.tsx +8 -1
- package/src/components/form/layout-renderer/3-element/5-re-captcha.tsx +18 -3
- package/src/components/form/layout-renderer/3-element/6-signature.tsx +36 -3
- package/src/components/form/layout-renderer/3-element/7-file-upload.tsx +5 -3
- package/src/components/form/layout-renderer/3-element/index.tsx +57 -7
- package/src/enums.ts +6 -2
- package/src/types/{form → data-list}/index.ts +1 -5
- package/src/types/generate/index.ts +8 -0
- package/src/types/index.ts +12 -3
- /package/src/components/common/custom-hooks/{use-find-dynamic-form.ts → use-find-dynamic-form.hook.ts} +0 -0
package/package.json
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export * from './use-find-dynamic-form'
|
|
1
|
+
export * from './use-find-dynamic-form.hook'
|
|
2
2
|
export * from './use-notification.hook'
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { Form, FormInstance } from 'antd'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
import { IFormLayoutElementConditions } from '../../../types'
|
|
4
|
+
import { FormElementConditionalKeyEnum } from '../../../enums'
|
|
5
|
+
|
|
6
|
+
export const useCheckElementConditions = (
|
|
7
|
+
formRef: FormInstance | undefined,
|
|
8
|
+
conditionalConfigs: IFormLayoutElementConditions | undefined,
|
|
9
|
+
defaultDisabled: boolean = false,
|
|
10
|
+
) => {
|
|
11
|
+
const [isDisabled, setIsDisabled] = useState(defaultDisabled)
|
|
12
|
+
const [isHidden, setIsHidden] = useState(false)
|
|
13
|
+
|
|
14
|
+
const formValues = Form.useWatch([], { form: formRef, preserve: true })
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
if (!!formValues) {
|
|
18
|
+
// enableIf config
|
|
19
|
+
if (
|
|
20
|
+
conditionalConfigs?.[FormElementConditionalKeyEnum.EnableIf] &&
|
|
21
|
+
Object.keys(conditionalConfigs[FormElementConditionalKeyEnum.EnableIf]).length > 0
|
|
22
|
+
) {
|
|
23
|
+
const fieldsMeetConditions = Object.entries(conditionalConfigs[FormElementConditionalKeyEnum.EnableIf]).every(
|
|
24
|
+
([key, value]) => {
|
|
25
|
+
if (typeof value === 'boolean' && value) return !!formValues[key]
|
|
26
|
+
return formValues[key] === value
|
|
27
|
+
},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
setIsDisabled(!fieldsMeetConditions)
|
|
31
|
+
} else setIsDisabled(false)
|
|
32
|
+
|
|
33
|
+
// showIf config
|
|
34
|
+
if (
|
|
35
|
+
conditionalConfigs?.[FormElementConditionalKeyEnum.ShowIf] &&
|
|
36
|
+
Object.keys(conditionalConfigs[FormElementConditionalKeyEnum.ShowIf]).length > 0
|
|
37
|
+
) {
|
|
38
|
+
const fieldsMeetConditions = Object.entries(conditionalConfigs[FormElementConditionalKeyEnum.ShowIf]).every(
|
|
39
|
+
([key, value]) => {
|
|
40
|
+
if (typeof value === 'boolean' && value) return !!formValues[key]
|
|
41
|
+
return formValues[key] === value
|
|
42
|
+
},
|
|
43
|
+
)
|
|
44
|
+
setIsHidden(!fieldsMeetConditions)
|
|
45
|
+
}
|
|
46
|
+
} else setIsDisabled(false)
|
|
47
|
+
}, [formValues, conditionalConfigs])
|
|
48
|
+
|
|
49
|
+
return { isElementDisabled: isDisabled, isElementHidden: isHidden }
|
|
50
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Form, FormInstance } from 'antd'
|
|
2
|
+
import { FormPreservedItemKeys } from '../../../enums'
|
|
3
|
+
|
|
4
|
+
export const useFormPreservedItemValues = (formRef?: FormInstance): IFormValues => {
|
|
5
|
+
const formId = Form.useWatch(FormPreservedItemKeys.FormId, { form: formRef, preserve: true })
|
|
6
|
+
const formKey = Form.useWatch(FormPreservedItemKeys.FormKey, { form: formRef, preserve: true })
|
|
7
|
+
const formDataId = Form.useWatch(FormPreservedItemKeys.FormDataId, { form: formRef, preserve: true })
|
|
8
|
+
const baseServerUrl = Form.useWatch(FormPreservedItemKeys.BaseServerUrl, { form: formRef, preserve: true })
|
|
9
|
+
const companyKey = Form.useWatch(FormPreservedItemKeys.CompanyKey, { form: formRef, preserve: true })
|
|
10
|
+
const isGeneratingPdf = Form.useWatch(FormPreservedItemKeys.IsGeneratingPDF, { form: formRef, preserve: true })
|
|
11
|
+
const hasSignature = Form.useWatch(FormPreservedItemKeys.HasSignature, { form: formRef, preserve: true })
|
|
12
|
+
const submissionPdfConfig = Form.useWatch(FormPreservedItemKeys.SubmissionPdfConfig, {
|
|
13
|
+
form: formRef,
|
|
14
|
+
preserve: true,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
[FormPreservedItemKeys.FormId]: formId,
|
|
19
|
+
[FormPreservedItemKeys.FormKey]: formKey,
|
|
20
|
+
[FormPreservedItemKeys.FormDataId]: formDataId,
|
|
21
|
+
[FormPreservedItemKeys.BaseServerUrl]: baseServerUrl,
|
|
22
|
+
[FormPreservedItemKeys.CompanyKey]: companyKey,
|
|
23
|
+
[FormPreservedItemKeys.SubmissionPdfConfig]: submissionPdfConfig,
|
|
24
|
+
[FormPreservedItemKeys.IsGeneratingPDF]: isGeneratingPdf,
|
|
25
|
+
[FormPreservedItemKeys.HasSignature]: hasSignature,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
type IFormValues = {
|
|
30
|
+
[key in FormPreservedItemKeys]?: any
|
|
31
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Form } from 'antd'
|
|
2
2
|
import { ReactNode, useEffect, useState } from 'react'
|
|
3
|
-
import { IReqDataConfig } from '.'
|
|
3
|
+
import { defaultReqDataConfig, IReqDataConfig } from '.'
|
|
4
4
|
import dayjs from 'dayjs'
|
|
5
5
|
import { IFilterConfig, IFilterNested, IFilterSimple, IFormLayoutRow } from '../../../types'
|
|
6
6
|
import { extractFiltersFromLayout } from '../../../functions'
|
|
@@ -13,11 +13,11 @@ export default function FormDataListHeaderComponent({
|
|
|
13
13
|
userId,
|
|
14
14
|
formId,
|
|
15
15
|
titleComponent,
|
|
16
|
-
|
|
16
|
+
setFilterReqData,
|
|
17
17
|
onCustomFunctionCall,
|
|
18
18
|
}: IFormDataListHeaderComponent) {
|
|
19
19
|
const [dataListHeaderFormRef] = Form.useForm()
|
|
20
|
-
const [appliedFilters, setAppliedFilters] = useState<IFilterSimple>(
|
|
20
|
+
const [appliedFilters, setAppliedFilters] = useState<IFilterSimple | undefined>(undefined)
|
|
21
21
|
const [filterConfigs, setFilterConfigs] = useState<IFilterNested>({})
|
|
22
22
|
|
|
23
23
|
useEffect(() => {
|
|
@@ -29,27 +29,23 @@ export default function FormDataListHeaderComponent({
|
|
|
29
29
|
}, [layout])
|
|
30
30
|
|
|
31
31
|
useEffect(() => {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
32
|
+
// needs to be called when there is a change in form values
|
|
33
|
+
if (!appliedFilters) return
|
|
34
|
+
|
|
35
|
+
let dynamicFilters = {}
|
|
36
|
+
let customFilters = {}
|
|
37
|
+
|
|
36
38
|
Object.values(appliedFilters).forEach((config) => {
|
|
37
39
|
if (config.type === FilterConfigTypeEnum.NoFilter) return
|
|
38
40
|
|
|
39
|
-
if (config.config
|
|
40
|
-
allCombinedFilters.customFilter = { ...allCombinedFilters.customFilter, ...config.config.customFilter }
|
|
41
|
-
if (config.config?.dynamicFilter)
|
|
42
|
-
allCombinedFilters.dynamicFilter = { ...allCombinedFilters.dynamicFilter, ...config.config.dynamicFilter }
|
|
41
|
+
if (config.config) dynamicFilters = config.config
|
|
43
42
|
|
|
44
|
-
if (config.type === FilterConfigTypeEnum.
|
|
45
|
-
allCombinedFilters.customFilter = { ...allCombinedFilters.customFilter, createdById: userId }
|
|
43
|
+
if (config.type === FilterConfigTypeEnum.ByAuthUser) customFilters = { ...customFilters, createdById: userId }
|
|
46
44
|
})
|
|
47
45
|
|
|
48
|
-
|
|
49
|
-
...c,
|
|
50
|
-
|
|
51
|
-
dynamicFilter: JSON.stringify(allCombinedFilters.dynamicFilter),
|
|
52
|
-
}))
|
|
46
|
+
setFilterReqData((c) =>
|
|
47
|
+
c ? { ...c, customFilter: customFilters, dynamicFilter: JSON.stringify(dynamicFilters) } : defaultReqDataConfig,
|
|
48
|
+
)
|
|
53
49
|
}, [appliedFilters])
|
|
54
50
|
|
|
55
51
|
return (
|
|
@@ -63,24 +59,20 @@ export default function FormDataListHeaderComponent({
|
|
|
63
59
|
console.warn(`Key '${key}' does not have a filter config`)
|
|
64
60
|
return
|
|
65
61
|
}
|
|
62
|
+
|
|
66
63
|
if ('type' in config) {
|
|
67
64
|
if (typeof value !== 'string') value = dayjs(value as Date).toISOString()
|
|
68
65
|
|
|
69
|
-
if (config.config
|
|
70
|
-
let
|
|
71
|
-
|
|
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)
|
|
66
|
+
if (config.config) {
|
|
67
|
+
let stringifiedFilter = JSON.stringify(config.config)
|
|
68
|
+
stringifiedFilter = stringifiedFilter.replaceAll('{{VALUE}}', value as string)
|
|
78
69
|
|
|
79
|
-
config.config
|
|
70
|
+
config.config = JSON.parse(stringifiedFilter)
|
|
80
71
|
}
|
|
81
72
|
|
|
82
73
|
setAppliedFilters((c) => ({ ...c, [key]: config as IFilterConfig }))
|
|
83
74
|
} else {
|
|
75
|
+
// nested applies to radio button, where each option has its own filter, but only one needs to apply
|
|
84
76
|
const nestedConfig = config as IFilterSimple
|
|
85
77
|
if (typeof value === 'string' && value in nestedConfig)
|
|
86
78
|
setAppliedFilters((c) => ({ ...c, [key]: nestedConfig[value as string] as IFilterConfig }))
|
|
@@ -111,7 +103,7 @@ export default function FormDataListHeaderComponent({
|
|
|
111
103
|
|
|
112
104
|
type IFormDataListHeaderComponent = {
|
|
113
105
|
layout: IFormLayoutRow[]
|
|
114
|
-
|
|
106
|
+
setFilterReqData: React.Dispatch<React.SetStateAction<IReqDataConfig | undefined>>
|
|
115
107
|
titleComponent?: ReactNode
|
|
116
108
|
formId?: number
|
|
117
109
|
userId?: string | number
|
|
@@ -4,22 +4,19 @@ import { parseJSON } from '../../../functions/json-handlers'
|
|
|
4
4
|
import { generateTableColumns } from '../../../functions'
|
|
5
5
|
import FormDataListSkeleton_Table from '../../common/loading-skeletons/table'
|
|
6
6
|
import FormDataListHeaderComponent from './header'
|
|
7
|
-
import { FormDataListViewTypeEnum } from '../../../enums'
|
|
7
|
+
import { FilterConfigTypeEnum, FormDataListViewTypeEnum, MongoDbSortOrderEnum } from '../../../enums'
|
|
8
8
|
import { Table } from 'antd'
|
|
9
9
|
import { IFormData, IFormDataListConfig, IFormLayoutRow, IFormSchema, ITableColumn } from '../../../types'
|
|
10
10
|
import { ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
|
|
11
|
+
import { SorterResult } from 'antd/es/table/interface'
|
|
11
12
|
|
|
12
13
|
function FormDataListComponent({ formId, userId, onCustomFunctionCall }: IFormDataListComponent) {
|
|
13
14
|
const [loadings, setLoadings] = useState({ schema: true, data: true })
|
|
14
15
|
const [tableColumns, setTableColumns] = useState<ITableColumn[]>([])
|
|
15
|
-
const [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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 })
|
|
16
|
+
const [filterReqData, setFilterReqData] = useState<IReqDataConfig | undefined>()
|
|
17
|
+
const [paginationConfig, setPaginationConfig] = useState<IPagination | undefined>()
|
|
18
|
+
const [dataListConfig, setDataListConfig] = useState<Partial<IFormDataListConfig> | undefined>(undefined)
|
|
19
|
+
const [formData, setFormData] = useState<{ data: IFormData[]; total: number }>({ data: [], total: 0 })
|
|
23
20
|
const [headerLayout, setHeaderLayout] = useState<IFormLayoutRow[]>([])
|
|
24
21
|
|
|
25
22
|
useEffect(() => {
|
|
@@ -30,16 +27,28 @@ function FormDataListComponent({ formId, userId, onCustomFunctionCall }: IFormDa
|
|
|
30
27
|
if (res.status === 200) {
|
|
31
28
|
const parsedData: IFormSchema | null = parseJSON(res.data.data)
|
|
32
29
|
if (parsedData) {
|
|
33
|
-
|
|
30
|
+
console.log(parsedData.dataListConfig)
|
|
31
|
+
const { elements, header, pagination, defaultFilter, defaultSorter, ...restConfigs } =
|
|
32
|
+
parsedData.dataListConfig
|
|
33
|
+
|
|
34
34
|
setTableColumns(generateTableColumns(elements))
|
|
35
|
-
|
|
35
|
+
setPaginationConfig({
|
|
36
36
|
current: 1,
|
|
37
|
-
pageSize:
|
|
38
|
-
showSizeChanger:
|
|
39
|
-
pageSizeOptions:
|
|
37
|
+
pageSize: pagination?.defaultPageSize ?? 10,
|
|
38
|
+
showSizeChanger: pagination?.showSizeChanger ?? false,
|
|
39
|
+
pageSizeOptions: pagination?.pageSizeOptions ?? [],
|
|
40
40
|
})
|
|
41
41
|
setHeaderLayout(header.layout)
|
|
42
|
-
|
|
42
|
+
setDataListConfig(restConfigs)
|
|
43
|
+
|
|
44
|
+
if (!defaultFilter || defaultFilter.type === FilterConfigTypeEnum.NoFilter)
|
|
45
|
+
setFilterReqData(defaultReqDataConfig)
|
|
46
|
+
else
|
|
47
|
+
setFilterReqData({
|
|
48
|
+
customFilter: defaultFilter.type === FilterConfigTypeEnum.ByAuthUser ? { createdById: userId } : {},
|
|
49
|
+
dynamicFilter: defaultFilter.config ? JSON.stringify(defaultFilter.config) : JSON.stringify({}),
|
|
50
|
+
sort: defaultSorter ? { [defaultSorter.field]: defaultSorter.order } : {},
|
|
51
|
+
})
|
|
43
52
|
}
|
|
44
53
|
}
|
|
45
54
|
})
|
|
@@ -49,20 +58,27 @@ function FormDataListComponent({ formId, userId, onCustomFunctionCall }: IFormDa
|
|
|
49
58
|
|
|
50
59
|
const fetchFormDataList = useCallback(
|
|
51
60
|
(dynamicFormId: number) => {
|
|
52
|
-
|
|
61
|
+
setLoadings((c) => ({ ...c, data: true }))
|
|
62
|
+
|
|
63
|
+
if (!filterReqData) return
|
|
64
|
+
|
|
65
|
+
if (!paginationConfig) {
|
|
53
66
|
setLoadings((c) => ({ ...c, data: false }))
|
|
54
67
|
return
|
|
55
68
|
}
|
|
56
69
|
|
|
57
70
|
client
|
|
58
|
-
.post(`/api/formdata/list/${dynamicFormId}`,
|
|
71
|
+
.post(`/api/formdata/list/${dynamicFormId}`, {
|
|
72
|
+
...filterReqData,
|
|
73
|
+
pagination: { number: paginationConfig.current, size: paginationConfig.pageSize },
|
|
74
|
+
})
|
|
59
75
|
.then((res) => {
|
|
60
76
|
if (res.status === 200)
|
|
61
77
|
setFormData({ ...res.data, data: res.data.data.map((d: IFormData) => ({ ...d, ...JSON.parse(d.data) })) })
|
|
62
78
|
})
|
|
63
79
|
.finally(() => setLoadings((c) => ({ ...c, data: false })))
|
|
64
80
|
},
|
|
65
|
-
[
|
|
81
|
+
[filterReqData, paginationConfig],
|
|
66
82
|
)
|
|
67
83
|
|
|
68
84
|
useEffect(() => {
|
|
@@ -75,26 +91,45 @@ function FormDataListComponent({ formId, userId, onCustomFunctionCall }: IFormDa
|
|
|
75
91
|
<div>
|
|
76
92
|
<FormDataListHeaderComponent
|
|
77
93
|
layout={headerLayout}
|
|
78
|
-
|
|
94
|
+
setFilterReqData={setFilterReqData}
|
|
79
95
|
userId={userId}
|
|
80
96
|
formId={formId}
|
|
81
97
|
titleComponent={
|
|
82
98
|
<div className="text-primary font-bold text-18 flex items-center gap-2">
|
|
83
|
-
<span>{
|
|
84
|
-
{
|
|
99
|
+
<span>{dataListConfig?.title}</span>
|
|
100
|
+
{dataListConfig?.showCount && <span>({formData.total})</span>}
|
|
85
101
|
</div>
|
|
86
102
|
}
|
|
87
103
|
onCustomFunctionCall={onCustomFunctionCall}
|
|
88
104
|
/>
|
|
89
|
-
{
|
|
105
|
+
{dataListConfig?.listType === FormDataListViewTypeEnum.Table && (
|
|
90
106
|
<Table<IFormData>
|
|
91
107
|
dataSource={formData.data}
|
|
92
108
|
columns={tableColumns}
|
|
93
109
|
rowKey={(record) => record.id}
|
|
94
|
-
pagination={
|
|
110
|
+
pagination={{ ...paginationConfig, total: formData.total }}
|
|
95
111
|
loading={loadings.data}
|
|
96
|
-
onChange={(
|
|
97
|
-
console.log('ON TABLE CHANGE',
|
|
112
|
+
onChange={(cbPagination, _, sorter: SorterResult<IFormData> | SorterResult<IFormData>[]) => {
|
|
113
|
+
console.log('ON TABLE CHANGE', cbPagination, sorter)
|
|
114
|
+
const { total, ...restCbPagination } = cbPagination
|
|
115
|
+
setPaginationConfig(restCbPagination as IPagination)
|
|
116
|
+
|
|
117
|
+
if (sorter && Object.values(sorter).length > 0)
|
|
118
|
+
setFilterReqData((c) =>
|
|
119
|
+
c
|
|
120
|
+
? {
|
|
121
|
+
...c,
|
|
122
|
+
sort: (sorter as SorterResult<IFormData>).field
|
|
123
|
+
? {
|
|
124
|
+
[(sorter as SorterResult<IFormData>).field as string]:
|
|
125
|
+
(sorter as SorterResult<IFormData>).order === 'ascend'
|
|
126
|
+
? MongoDbSortOrderEnum.Ascending
|
|
127
|
+
: MongoDbSortOrderEnum.Descending,
|
|
128
|
+
}
|
|
129
|
+
: {},
|
|
130
|
+
}
|
|
131
|
+
: defaultReqDataConfig,
|
|
132
|
+
)
|
|
98
133
|
}}
|
|
99
134
|
/>
|
|
100
135
|
)}
|
|
@@ -104,9 +139,15 @@ function FormDataListComponent({ formId, userId, onCustomFunctionCall }: IFormDa
|
|
|
104
139
|
export { FormDataListComponent }
|
|
105
140
|
|
|
106
141
|
export interface IReqDataConfig {
|
|
107
|
-
customFilter:
|
|
108
|
-
dynamicFilter:
|
|
109
|
-
sort:
|
|
142
|
+
customFilter: object
|
|
143
|
+
dynamicFilter: string
|
|
144
|
+
sort: object
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const defaultReqDataConfig = {
|
|
148
|
+
customFilter: {},
|
|
149
|
+
dynamicFilter: JSON.stringify({}),
|
|
150
|
+
sort: {},
|
|
110
151
|
}
|
|
111
152
|
|
|
112
153
|
interface IPagination {
|
|
@@ -38,9 +38,13 @@ export default function FormDataDetailsComponent({
|
|
|
38
38
|
}, [formDataRef, formId, formKey, baseServerUrl, formDataId, companyKey])
|
|
39
39
|
|
|
40
40
|
const fetchFormData = useCallback(
|
|
41
|
-
(dFormId
|
|
41
|
+
(dFormId?: number, migrationRules?: { [key: string]: IMigrationRule[] }) => {
|
|
42
42
|
if (formDataId === NEW_FORM_DATA_IDENTIFIER) setLoadings((c) => ({ ...c, data: false }))
|
|
43
43
|
else {
|
|
44
|
+
if (!dFormId) {
|
|
45
|
+
console.error('Form ID is required to fetch form data')
|
|
46
|
+
return
|
|
47
|
+
}
|
|
44
48
|
client
|
|
45
49
|
.get(`/api/formdata/${dFormId}/${formDataId}`)
|
|
46
50
|
.then((res) => {
|
|
@@ -68,10 +72,14 @@ export default function FormDataDetailsComponent({
|
|
|
68
72
|
const parsedData: IFormSchema | null = parseJSON(res.data.data)
|
|
69
73
|
if (parsedData) {
|
|
70
74
|
console.log({ parsedData })
|
|
71
|
-
const { layout, migrationRules
|
|
75
|
+
const { layout, migrationRules } = parsedData.detailsConfig
|
|
72
76
|
|
|
73
77
|
if (isPublic) {
|
|
74
|
-
|
|
78
|
+
if (parsedData.generateConfig?.submissionPdf?.enabled)
|
|
79
|
+
formDataRef.setFieldValue(
|
|
80
|
+
FormPreservedItemKeys.SubmissionPdfConfig,
|
|
81
|
+
parsedData.generateConfig.submissionPdf,
|
|
82
|
+
)
|
|
75
83
|
setLoadings((c) => ({ ...c, data: false }))
|
|
76
84
|
} else fetchFormData(formId, migrationRules)
|
|
77
85
|
setLayout(layout)
|
|
@@ -27,16 +27,15 @@ export const LayoutRendererRow = ({
|
|
|
27
27
|
}}
|
|
28
28
|
>
|
|
29
29
|
{rowData.children.map((col, colIdx) => (
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
</div>
|
|
30
|
+
<LayoutRendererCol
|
|
31
|
+
key={colIdx}
|
|
32
|
+
colData={col}
|
|
33
|
+
formRef={formRef}
|
|
34
|
+
titleComponent={titleComponent}
|
|
35
|
+
breakpointDevice={breakpointDevice}
|
|
36
|
+
renderButton={renderButton}
|
|
37
|
+
colStyle={getFlexItemStyle(breakpointDevice, colIdx, rowData.responsiveness)}
|
|
38
|
+
/>
|
|
40
39
|
))}
|
|
41
40
|
</div>
|
|
42
41
|
</>
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { ReactElement, ReactNode } from 'react'
|
|
1
|
+
import { ReactElement, ReactNode, useState } from 'react'
|
|
2
2
|
import { LayoutRendererRow } from '../1-row'
|
|
3
|
-
import LayoutRendererElement from '../3-element'
|
|
4
3
|
import {
|
|
5
4
|
IDataRender_ButtonProps,
|
|
6
5
|
IFormLayoutCol,
|
|
@@ -8,40 +7,59 @@ import {
|
|
|
8
7
|
IFormLayoutElementConditions,
|
|
9
8
|
IFormLayoutRow,
|
|
10
9
|
} from '../../../../types'
|
|
11
|
-
import { FormLayoutNodeEnum, ResponsivenessDeviceEnum } from '../../../../enums'
|
|
10
|
+
import { FlexDirection, FormLayoutNodeEnum, ResponsivenessDeviceEnum } from '../../../../enums'
|
|
12
11
|
import { FormInstance } from 'antd'
|
|
12
|
+
import LayoutRendererElementWrapper from '../3-element'
|
|
13
13
|
|
|
14
14
|
export default function LayoutRendererCol({
|
|
15
15
|
colData,
|
|
16
16
|
formRef,
|
|
17
17
|
titleComponent,
|
|
18
18
|
breakpointDevice,
|
|
19
|
+
colStyle,
|
|
19
20
|
renderButton,
|
|
20
21
|
}: ILayoutRendererCol) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
22
|
+
const [hiddenElementCount, setHiddenElementCount] = useState(0)
|
|
23
|
+
|
|
24
|
+
// display: colData.children.length === hiddenElementCount ? 'none' : 'flex'
|
|
25
|
+
console.log(colStyle)
|
|
26
|
+
return (
|
|
27
|
+
<div
|
|
28
|
+
style={{
|
|
29
|
+
...colStyle,
|
|
30
|
+
display: colData.children.length === hiddenElementCount ? 'none' : 'flex',
|
|
31
|
+
flexDirection: FlexDirection.Col,
|
|
32
|
+
}}
|
|
33
|
+
>
|
|
34
|
+
{colData.children.map((item: IFormLayoutRow | IFormLayoutElement) =>
|
|
35
|
+
item.nodeType === FormLayoutNodeEnum.Row ? (
|
|
36
|
+
<LayoutRendererRow
|
|
37
|
+
key={item.id}
|
|
38
|
+
rowData={item}
|
|
39
|
+
formRef={formRef}
|
|
40
|
+
titleComponent={titleComponent}
|
|
41
|
+
breakpointDevice={breakpointDevice}
|
|
42
|
+
renderButton={renderButton}
|
|
43
|
+
/>
|
|
44
|
+
) : (
|
|
45
|
+
<LayoutRendererElementWrapper
|
|
46
|
+
key={item.id}
|
|
47
|
+
elementData={item}
|
|
48
|
+
formRef={formRef}
|
|
49
|
+
titleComponent={titleComponent}
|
|
50
|
+
breakpointDevice={breakpointDevice}
|
|
51
|
+
renderButton={renderButton}
|
|
52
|
+
hideElement={(isHidden) =>
|
|
53
|
+
setHiddenElementCount((c) => {
|
|
54
|
+
if (isHidden) return c + 1
|
|
55
|
+
if (c - 1 < 0) return 0
|
|
56
|
+
return c - 1
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
/>
|
|
60
|
+
),
|
|
61
|
+
)}
|
|
62
|
+
</div>
|
|
45
63
|
)
|
|
46
64
|
}
|
|
47
65
|
|
|
@@ -50,5 +68,6 @@ interface ILayoutRendererCol {
|
|
|
50
68
|
formRef: FormInstance
|
|
51
69
|
titleComponent?: ReactNode
|
|
52
70
|
breakpointDevice: ResponsivenessDeviceEnum
|
|
71
|
+
colStyle: { [key: string]: number | string }
|
|
53
72
|
renderButton?: (props: IDataRender_ButtonProps, conditions?: IFormLayoutElementConditions) => ReactElement
|
|
54
73
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { lazy, memo, useCallback, useEffect, useMemo, useState } from 'react'
|
|
2
2
|
import { useLocation, useNavigate } from 'react-router-dom'
|
|
3
|
-
import {
|
|
3
|
+
import { FormInstance } from 'antd'
|
|
4
4
|
import { getButtonRenderProps } from '../../../../functions/get-element-props'
|
|
5
5
|
import { stringifyJSON } from '../../../../functions/json-handlers'
|
|
6
6
|
import { ButtonActionCategoryEnum, FormLoadingModalTypeEnum, FormPreservedItemKeys } from '../../../../enums'
|
|
7
|
-
import { IDataRender_ButtonProps, IFormLayoutElementConditions } from '../../../../types'
|
|
7
|
+
import { IDataRender_ButtonProps, IFormLayoutElementConditions, IFormSubmissionPdf } from '../../../../types'
|
|
8
8
|
import { NEW_FORM_DATA_IDENTIFIER } from '../../../../constants'
|
|
9
9
|
import { Button_FillerPortal } from '../../../common/button'
|
|
10
10
|
import { base64ToBlob, extractDynamicFormHref, objectToQueryParams } from '../../../../functions'
|
|
@@ -13,6 +13,8 @@ import client from '../../../../functions/axios-handler'
|
|
|
13
13
|
import { useLazyModalOpener } from '../../../common/custom-hooks/use-lazy-modal-opener.hook'
|
|
14
14
|
import WarningIcon from '../../../common/warning-icon'
|
|
15
15
|
import { saveFile } from '../../../../functions/form'
|
|
16
|
+
import { useFormPreservedItemValues } from '../../../common/custom-hooks/use-preserved-form-items.hook'
|
|
17
|
+
import { useCheckElementConditions } from '../../../common/custom-hooks/use-check-element-conditions.hook'
|
|
16
18
|
|
|
17
19
|
const FormDataLoadingIndicatorModal = lazy(() => import('../../../modals/form-data-loading.modal'))
|
|
18
20
|
|
|
@@ -27,37 +29,17 @@ export const DynamicFormButtonRender = memo(
|
|
|
27
29
|
}: IDynamicButton) => {
|
|
28
30
|
const navigate = useNavigate()
|
|
29
31
|
const location = useLocation()
|
|
30
|
-
const { success, warning, error, confirmModal } = useNotification()
|
|
32
|
+
const { success, warning, error, confirmModal, errorModal } = useNotification()
|
|
31
33
|
const { isModalOpen, isPendingTransition, openModal, closeModal } = useLazyModalOpener()
|
|
34
|
+
const { isElementDisabled, isElementHidden } = useCheckElementConditions(formRef, conditions, true)
|
|
32
35
|
|
|
33
36
|
const [loading, setLoading] = useState(false)
|
|
34
|
-
const [isButtonDisabled, setIsButtonDisabled] = useState(true)
|
|
35
37
|
const [dataLoadingType, setDataLoadingType] = useState<FormLoadingModalTypeEnum | undefined>()
|
|
36
38
|
|
|
37
|
-
const
|
|
38
|
-
const formId = Form.useWatch(FormPreservedItemKeys.FormId, { form: formRef, preserve: true }) // for public use
|
|
39
|
-
const formKey = Form.useWatch(FormPreservedItemKeys.FormKey, { form: formRef, preserve: true }) // for public use
|
|
40
|
-
const companyKey = Form.useWatch(FormPreservedItemKeys.CompanyKey, { form: formRef, preserve: true }) // for public use
|
|
41
|
-
const convertScreenToPdf = Form.useWatch(FormPreservedItemKeys.ConvertScreenToPDF, {
|
|
42
|
-
form: formRef,
|
|
43
|
-
preserve: true,
|
|
44
|
-
}) // for public use
|
|
39
|
+
const { formId, formKey, baseServerUrl, companyKey, submissionPdfConfig } = useFormPreservedItemValues(formRef)
|
|
45
40
|
|
|
46
41
|
const baseDynamicUrl = useMemo(() => extractDynamicFormHref(location.pathname), [location.pathname])
|
|
47
42
|
|
|
48
|
-
useEffect(() => {
|
|
49
|
-
if (!!formValues) {
|
|
50
|
-
if (conditions?.enableIf && Object.keys(conditions.enableIf).length > 0) {
|
|
51
|
-
const fieldsMeetConditions = Object.entries(conditions.enableIf).every(([key, value]) => {
|
|
52
|
-
if (typeof value === 'boolean' && value) return !!formValues[key]
|
|
53
|
-
return formValues[key] === value
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
setIsButtonDisabled(!fieldsMeetConditions)
|
|
57
|
-
} else setIsButtonDisabled(false)
|
|
58
|
-
} else setIsButtonDisabled(false)
|
|
59
|
-
}, [formValues, conditions])
|
|
60
|
-
|
|
61
43
|
useEffect(() => {
|
|
62
44
|
if (dataLoadingType !== undefined) openModal()
|
|
63
45
|
}, [dataLoadingType])
|
|
@@ -126,7 +108,7 @@ export const DynamicFormButtonRender = memo(
|
|
|
126
108
|
}, [])
|
|
127
109
|
|
|
128
110
|
const onCreateNewData = useCallback(
|
|
129
|
-
(generatedPdfBlobName?: string) => {
|
|
111
|
+
(generatedPdfBlobName?: string, pdfConfig?: IFormSubmissionPdf) => {
|
|
130
112
|
formRef?.validateFields().then((values) => {
|
|
131
113
|
setDataLoadingType(FormLoadingModalTypeEnum.SavingChanges)
|
|
132
114
|
setLoading(true)
|
|
@@ -135,7 +117,7 @@ export const DynamicFormButtonRender = memo(
|
|
|
135
117
|
name: '', // TODO: maybe later, make it dynamic
|
|
136
118
|
data: stringifyJSON({ ...values, generatedPdfBlobName }),
|
|
137
119
|
}
|
|
138
|
-
if (isPublic) reqData.private =
|
|
120
|
+
if (isPublic) reqData.private = false // not used anymore, sending it as false by default
|
|
139
121
|
else reqData.version = 1
|
|
140
122
|
|
|
141
123
|
const endpoint = isPublic ? `/api/site/${formKey}` : `/api/formdata/${formId}`
|
|
@@ -148,34 +130,59 @@ export const DynamicFormButtonRender = memo(
|
|
|
148
130
|
closeModal()
|
|
149
131
|
displayResultMessage()
|
|
150
132
|
if (!isPublic) navigate(`${baseDynamicUrl}/${res.data}`)
|
|
133
|
+
else {
|
|
134
|
+
if (pdfConfig?.showDownloadModal)
|
|
135
|
+
confirmModal({
|
|
136
|
+
title: pdfConfig.successTitle ?? 'Successfully generated the PDF!',
|
|
137
|
+
content: pdfConfig.successContent ?? 'Would you like to download the PDF?',
|
|
138
|
+
centered: true,
|
|
139
|
+
okText: 'Download',
|
|
140
|
+
onOk: () => {
|
|
141
|
+
const href = `${baseServerUrl}/api/attachment/${companyKey}/${generatedPdfBlobName}`
|
|
142
|
+
window.open(href, '_blank')
|
|
143
|
+
window.location.reload()
|
|
144
|
+
},
|
|
145
|
+
})
|
|
146
|
+
}
|
|
151
147
|
}, 500)
|
|
152
148
|
}
|
|
153
149
|
})
|
|
154
|
-
.catch(() =>
|
|
150
|
+
.catch(() => {
|
|
151
|
+
if (pdfConfig?.errorMsg) errorModal({ title: pdfConfig.errorMsg })
|
|
152
|
+
displayResultMessage(false)
|
|
153
|
+
})
|
|
155
154
|
.finally(() => setLoading(false))
|
|
156
155
|
})
|
|
157
156
|
},
|
|
158
|
-
[formRef, formId, baseDynamicUrl, isPublic, formKey],
|
|
157
|
+
[formRef, formId, baseDynamicUrl, isPublic, formKey, baseServerUrl, companyKey],
|
|
159
158
|
)
|
|
160
159
|
|
|
161
|
-
const generatePdfProof = useCallback(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
160
|
+
const generatePdfProof = useCallback(
|
|
161
|
+
(pdfConfig: IFormSubmissionPdf) => {
|
|
162
|
+
formRef?.validateFields().then((values) => {
|
|
163
|
+
const queryParams = objectToQueryParams(values)
|
|
164
|
+
const feDomainUrl = window.location.origin
|
|
165
|
+
const fullUrl = encodeURIComponent(`${feDomainUrl}${location.pathname}?${queryParams}`)
|
|
166
166
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
167
|
+
const blobName =
|
|
168
|
+
pdfConfig.title?.values
|
|
169
|
+
.sort((a, b) => a.order - b.order)
|
|
170
|
+
.map((v) => (v.isDynamic ? values[v.value] : v.value))
|
|
171
|
+
.join(pdfConfig.title?.delimiter ?? '') ?? 'submission-pdf'
|
|
172
|
+
|
|
173
|
+
client
|
|
174
|
+
.post(`/api/attachment/pdf/${companyKey}/${blobName}?url=${fullUrl}`)
|
|
175
|
+
.then((res) => {
|
|
176
|
+
if (res.status < 300) onCreateNewData(res.data, pdfConfig)
|
|
177
|
+
})
|
|
178
|
+
.catch(() => {
|
|
179
|
+
closeModal()
|
|
180
|
+
error({ message: 'Error occured while generating PDF!' })
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
},
|
|
184
|
+
[formRef, location.pathname, companyKey],
|
|
185
|
+
)
|
|
179
186
|
|
|
180
187
|
const onSaveExistingData = useCallback(() => {
|
|
181
188
|
formRef!.validateFields().then((values) => {
|
|
@@ -186,7 +193,7 @@ export const DynamicFormButtonRender = memo(
|
|
|
186
193
|
name: 'Dynamic form data', // TODO: maybe later, make it dynamic
|
|
187
194
|
data: stringifyJSON(values),
|
|
188
195
|
version: 1, // FIXME: increment
|
|
189
|
-
private:
|
|
196
|
+
private: false, // not used anymore, sending it as false by default
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
client
|
|
@@ -237,10 +244,10 @@ export const DynamicFormButtonRender = memo(
|
|
|
237
244
|
}
|
|
238
245
|
|
|
239
246
|
if (formDataId === NEW_FORM_DATA_IDENTIFIER) {
|
|
240
|
-
if (isPublic &&
|
|
247
|
+
if (isPublic && submissionPdfConfig) {
|
|
241
248
|
// pdf generate does not work when auth-ed
|
|
242
249
|
setDataLoadingType(FormLoadingModalTypeEnum.GeneratingPdf)
|
|
243
|
-
generatePdfProof()
|
|
250
|
+
generatePdfProof(submissionPdfConfig)
|
|
244
251
|
} else onCreateNewData()
|
|
245
252
|
} else onSaveExistingData()
|
|
246
253
|
} else error({ message: CUSTOM_ERROR_MESSAGES.FormInstanceNotFound })
|
|
@@ -269,14 +276,16 @@ export const DynamicFormButtonRender = memo(
|
|
|
269
276
|
warning({ message: errorMessage })
|
|
270
277
|
return
|
|
271
278
|
}
|
|
272
|
-
}, [btnProps, baseDynamicUrl, formDataId,
|
|
279
|
+
}, [btnProps, baseDynamicUrl, formDataId, submissionPdfConfig, companyKey])
|
|
280
|
+
|
|
281
|
+
if (isElementHidden) return <></>
|
|
273
282
|
|
|
274
283
|
return (
|
|
275
284
|
<>
|
|
276
285
|
<Button_FillerPortal
|
|
277
286
|
{...getButtonRenderProps(btnProps)}
|
|
278
287
|
loading={loading || isPendingTransition}
|
|
279
|
-
disabled={
|
|
288
|
+
disabled={isElementDisabled}
|
|
280
289
|
className="w-full"
|
|
281
290
|
onClick={() => {
|
|
282
291
|
if (
|
|
@@ -1,18 +1,5 @@
|
|
|
1
1
|
import { Key, ReactNode, Fragment } from 'react'
|
|
2
|
-
import {
|
|
3
|
-
Checkbox,
|
|
4
|
-
Col,
|
|
5
|
-
Form,
|
|
6
|
-
Input,
|
|
7
|
-
Select,
|
|
8
|
-
Radio,
|
|
9
|
-
Row,
|
|
10
|
-
InputNumber,
|
|
11
|
-
DatePicker,
|
|
12
|
-
Modal,
|
|
13
|
-
Upload,
|
|
14
|
-
Switch,
|
|
15
|
-
} from 'antd'
|
|
2
|
+
import { Checkbox, Form, Input, Select, Radio, InputNumber, DatePicker, Modal, Upload, Switch } from 'antd'
|
|
16
3
|
import { FaCaretDown } from 'react-icons/fa'
|
|
17
4
|
import dayjs, { Dayjs } from 'dayjs'
|
|
18
5
|
import { FaUpload } from 'react-icons/fa6'
|
|
@@ -20,11 +7,7 @@ import { ElementTypeEnum } from '../../../../enums'
|
|
|
20
7
|
import { IValidationRule, mapToFormItemRules } from '../../../../functions'
|
|
21
8
|
import { DisabledFieldIndicator } from '../../../common/disabled-field-indicator'
|
|
22
9
|
|
|
23
|
-
|
|
24
|
-
fields: IMapperFieldObj[]
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const LayoutRenderer_FieldElement = ({ fields }: IFieldsMapperProps): JSX.Element => (
|
|
10
|
+
export const LayoutRenderer_FieldElement = ({ fields }: { fields: IMapperFieldObj[] }): JSX.Element => (
|
|
28
11
|
<>
|
|
29
12
|
{fields
|
|
30
13
|
.filter(({ isHidden = false }) => !isHidden)
|
|
@@ -42,10 +25,6 @@ export const LayoutRenderer_FieldElement = ({ fields }: IFieldsMapperProps): JSX
|
|
|
42
25
|
: 'value'
|
|
43
26
|
}
|
|
44
27
|
rules={mapToFormItemRules(field.validations ?? [])}
|
|
45
|
-
// rules={[
|
|
46
|
-
// { pattern: /^([0-9]{1,2}|100)$/, message: 'Max value exceeded' },
|
|
47
|
-
// { pattern: /^(18|[1-9][0-9]*)$/, message: 'Min value exceeded' },
|
|
48
|
-
// ]}
|
|
49
28
|
>
|
|
50
29
|
{getField(field)}
|
|
51
30
|
</Form.Item>
|
|
@@ -67,6 +46,7 @@ const getField = ({
|
|
|
67
46
|
minRows = 3,
|
|
68
47
|
numberFieldMin,
|
|
69
48
|
numberFieldMax,
|
|
49
|
+
isCustom,
|
|
70
50
|
disabledDate,
|
|
71
51
|
}: Partial<IMapperFieldObj>): JSX.Element => {
|
|
72
52
|
placeholder = placeholder && placeholder.length > 0 ? placeholder : typeof label === 'string' ? label : ''
|
|
@@ -105,24 +85,25 @@ const getField = ({
|
|
|
105
85
|
if (isMultiValue)
|
|
106
86
|
return (
|
|
107
87
|
<Checkbox.Group disabled={disabled}>
|
|
108
|
-
<
|
|
88
|
+
<div className="flex items-center gap-1">
|
|
109
89
|
{options?.map((o, oIdx) => (
|
|
110
|
-
<
|
|
111
|
-
|
|
112
|
-
</
|
|
90
|
+
<Checkbox key={oIdx} value={o.value}>
|
|
91
|
+
{o.label}
|
|
92
|
+
</Checkbox>
|
|
113
93
|
))}
|
|
114
|
-
</
|
|
94
|
+
</div>
|
|
115
95
|
</Checkbox.Group>
|
|
116
96
|
)
|
|
97
|
+
|
|
117
98
|
return (
|
|
118
|
-
<Radio.Group disabled={disabled} className=
|
|
119
|
-
<
|
|
99
|
+
<Radio.Group disabled={disabled} className={`w-full ${isCustom ? 'fc-custom' : ''}`}>
|
|
100
|
+
<div className="flex items-center gap-1">
|
|
120
101
|
{options?.map((o, oIdx) => (
|
|
121
|
-
<
|
|
122
|
-
|
|
123
|
-
</
|
|
102
|
+
<Radio key={oIdx} value={o.value}>
|
|
103
|
+
{o.label}
|
|
104
|
+
</Radio>
|
|
124
105
|
))}
|
|
125
|
-
</
|
|
106
|
+
</div>
|
|
126
107
|
</Radio.Group>
|
|
127
108
|
)
|
|
128
109
|
case ElementTypeEnum.NumberInput:
|
|
@@ -194,6 +175,7 @@ interface IMapperFieldObj {
|
|
|
194
175
|
allowClear?: boolean
|
|
195
176
|
numberFieldMin?: number
|
|
196
177
|
numberFieldMax?: number
|
|
178
|
+
isCustom?: boolean
|
|
197
179
|
disabledDate?: (date: Dayjs) => boolean
|
|
198
180
|
}
|
|
199
181
|
interface ISelectOption {
|
|
@@ -2,7 +2,12 @@ import { Form, FormInstance } from 'antd'
|
|
|
2
2
|
import ReactQuill from 'react-quill'
|
|
3
3
|
import 'react-quill/dist/quill.snow.css'
|
|
4
4
|
|
|
5
|
-
export default function LayoutRenderer_RichEditor({
|
|
5
|
+
export default function LayoutRenderer_RichEditor({
|
|
6
|
+
formRef,
|
|
7
|
+
fieldKey,
|
|
8
|
+
elementProps,
|
|
9
|
+
isElementDisabled,
|
|
10
|
+
}: ILayoutRenderer_RichEditor) {
|
|
6
11
|
const fieldValue = Form.useWatch(fieldKey, { form: formRef, preserve: true })
|
|
7
12
|
|
|
8
13
|
return (
|
|
@@ -10,6 +15,7 @@ export default function LayoutRenderer_RichEditor({ formRef, fieldKey, elementPr
|
|
|
10
15
|
<ReactQuill
|
|
11
16
|
theme="snow"
|
|
12
17
|
value={fieldValue}
|
|
18
|
+
readOnly={isElementDisabled}
|
|
13
19
|
onChange={(value) => formRef.setFieldValue(fieldKey, value)}
|
|
14
20
|
modules={modules}
|
|
15
21
|
formats={formats}
|
|
@@ -33,5 +39,6 @@ const modules = {
|
|
|
33
39
|
interface ILayoutRenderer_RichEditor {
|
|
34
40
|
formRef: FormInstance
|
|
35
41
|
fieldKey: string
|
|
42
|
+
isElementDisabled: boolean
|
|
36
43
|
elementProps: { label?: string; placeholder?: string; description?: string; hasNoLabel?: string }
|
|
37
44
|
}
|
|
@@ -4,7 +4,12 @@ import ReCAPTCHA from 'react-google-recaptcha'
|
|
|
4
4
|
import client from '../../../../functions/axios-handler'
|
|
5
5
|
import { FormPreservedItemKeys } from '../../../../enums'
|
|
6
6
|
|
|
7
|
-
export default function LayoutRenderer_ReCaptcha({
|
|
7
|
+
export default function LayoutRenderer_ReCaptcha({
|
|
8
|
+
formRef,
|
|
9
|
+
fieldKey,
|
|
10
|
+
elementProps,
|
|
11
|
+
isElementDisabled,
|
|
12
|
+
}: ILayoutRenderer_RichEditor) {
|
|
8
13
|
const [tokenVerificationFailed, setTokenVerificationFailed] = useState(false)
|
|
9
14
|
const formKey = Form.useWatch(FormPreservedItemKeys.FormKey, { form: formRef, preserve: true })
|
|
10
15
|
|
|
@@ -25,11 +30,20 @@ export default function LayoutRenderer_ReCaptcha({ formRef, fieldKey, elementPro
|
|
|
25
30
|
)
|
|
26
31
|
|
|
27
32
|
if (!elementProps.siteKey)
|
|
28
|
-
return <span className="text-warning">
|
|
33
|
+
return <span className="text-warning">ReCaptcha field could not render due to missing SITE KEY!</span>
|
|
29
34
|
|
|
30
35
|
return (
|
|
31
36
|
<>
|
|
32
|
-
<
|
|
37
|
+
<div className={isElementDisabled ? 'relative w-max' : ''}>
|
|
38
|
+
<ReCAPTCHA
|
|
39
|
+
sitekey={elementProps.siteKey ?? ''}
|
|
40
|
+
onChange={(value: string | null) => verifyReCaptcha(value)}
|
|
41
|
+
className="cursor-not-allowed"
|
|
42
|
+
/>
|
|
43
|
+
{isElementDisabled && (
|
|
44
|
+
<div className="absolute top-0 right-0 left-0 bottom-0 bg-transparent cursor-not-allowed" />
|
|
45
|
+
)}
|
|
46
|
+
</div>
|
|
33
47
|
{tokenVerificationFailed && <span className="text-danger">Warning: reCAPTCHA verification failed!</span>}
|
|
34
48
|
</>
|
|
35
49
|
)
|
|
@@ -38,5 +52,6 @@ export default function LayoutRenderer_ReCaptcha({ formRef, fieldKey, elementPro
|
|
|
38
52
|
interface ILayoutRenderer_RichEditor {
|
|
39
53
|
formRef: FormInstance
|
|
40
54
|
fieldKey: string
|
|
55
|
+
isElementDisabled: boolean
|
|
41
56
|
elementProps: { siteKey?: string; description?: string }
|
|
42
57
|
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import { Form, FormInstance } from 'antd'
|
|
2
|
-
import { useRef } from 'react'
|
|
2
|
+
import { useEffect, useRef } from 'react'
|
|
3
3
|
import SignatureCanvas from 'react-signature-canvas'
|
|
4
4
|
import { NEW_FORM_DATA_IDENTIFIER } from '../../../../constants'
|
|
5
5
|
import { FormPreservedItemKeys } from '../../../../enums'
|
|
6
|
+
import { Button_FillerPortal } from '../../../common/button'
|
|
6
7
|
|
|
7
|
-
export default function LayoutRenderer_Signature({
|
|
8
|
+
export default function LayoutRenderer_Signature({
|
|
9
|
+
formRef,
|
|
10
|
+
fieldKey,
|
|
11
|
+
elementProps,
|
|
12
|
+
isElementDisabled,
|
|
13
|
+
}: ILayoutRenderer_Signature) {
|
|
8
14
|
const sigCanvasRef = useRef<SignatureCanvas>(null)
|
|
9
15
|
const savedSignatureBlobName = Form.useWatch(fieldKey, formRef)
|
|
10
16
|
const formDataId = Form.useWatch(FormPreservedItemKeys.FormDataId, { form: formRef, preserve: true })
|
|
@@ -12,8 +18,34 @@ export default function LayoutRenderer_Signature({ formRef, fieldKey, elementPro
|
|
|
12
18
|
const companyKey = Form.useWatch(FormPreservedItemKeys.CompanyKey, { form: formRef, preserve: true })
|
|
13
19
|
const isGeneratingPdf = Form.useWatch(FormPreservedItemKeys.IsGeneratingPDF, { form: formRef, preserve: true })
|
|
14
20
|
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
if (isElementDisabled) sigCanvasRef.current?.off()
|
|
23
|
+
else sigCanvasRef.current?.on()
|
|
24
|
+
}, [isElementDisabled])
|
|
25
|
+
|
|
15
26
|
return (
|
|
16
|
-
<Form.Item
|
|
27
|
+
<Form.Item
|
|
28
|
+
name={fieldKey}
|
|
29
|
+
label={
|
|
30
|
+
elementProps.hasNoLabel ? (
|
|
31
|
+
''
|
|
32
|
+
) : (
|
|
33
|
+
<div>
|
|
34
|
+
<span>{elementProps.label}</span>
|
|
35
|
+
<Button_FillerPortal
|
|
36
|
+
link
|
|
37
|
+
onClick={() => {
|
|
38
|
+
sigCanvasRef.current?.clear()
|
|
39
|
+
formRef.setFieldsValue({ [fieldKey]: null, [FormPreservedItemKeys.HasSignature]: null })
|
|
40
|
+
}}
|
|
41
|
+
>
|
|
42
|
+
Clear
|
|
43
|
+
</Button_FillerPortal>
|
|
44
|
+
</div>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
labelAlign="left"
|
|
48
|
+
>
|
|
17
49
|
{formDataId !== NEW_FORM_DATA_IDENTIFIER || isGeneratingPdf ? (
|
|
18
50
|
<div className="h-[300px] rounded-md border-2 border-[#f0f0f0]">
|
|
19
51
|
<img alt="signature" src={`${baseServerUrl}/api/attachment/${companyKey}/${savedSignatureBlobName}`} />
|
|
@@ -40,5 +72,6 @@ export default function LayoutRenderer_Signature({ formRef, fieldKey, elementPro
|
|
|
40
72
|
interface ILayoutRenderer_Signature {
|
|
41
73
|
formRef: FormInstance
|
|
42
74
|
fieldKey: string
|
|
75
|
+
isElementDisabled: boolean
|
|
43
76
|
elementProps: { label?: string; hasNoLabel?: boolean; description?: string }
|
|
44
77
|
}
|
|
@@ -14,6 +14,7 @@ export default function LayoutRenderer_FileUpload({
|
|
|
14
14
|
fieldKey,
|
|
15
15
|
elementProps,
|
|
16
16
|
uploadRules,
|
|
17
|
+
isElementDisabled,
|
|
17
18
|
}: ILayoutRenderer_FileUpload) {
|
|
18
19
|
const { warningModal, confirmModal } = useNotification()
|
|
19
20
|
const [loading, setLoading] = useState(false)
|
|
@@ -87,7 +88,7 @@ export default function LayoutRenderer_FileUpload({
|
|
|
87
88
|
</Button_FillerPortal>
|
|
88
89
|
</div>
|
|
89
90
|
) : elementProps.isDragger ? (
|
|
90
|
-
<Upload.Dragger {...uploadProps} beforeUpload={(file) => beforeUpload(file)}>
|
|
91
|
+
<Upload.Dragger {...uploadProps} disabled={isElementDisabled} beforeUpload={(file) => beforeUpload(file)}>
|
|
91
92
|
<div className="flex flex-col items-center">
|
|
92
93
|
<FaUpload size={24} className="text-primary" />
|
|
93
94
|
<span className="font-semibold mt-2">{elementProps.placeholder}</span>
|
|
@@ -95,8 +96,8 @@ export default function LayoutRenderer_FileUpload({
|
|
|
95
96
|
</div>
|
|
96
97
|
</Upload.Dragger>
|
|
97
98
|
) : (
|
|
98
|
-
<Upload {...uploadProps} beforeUpload={(file) => beforeUpload(file)}>
|
|
99
|
-
<Button_FillerPortal outline onClick={() => {}}>
|
|
99
|
+
<Upload {...uploadProps} disabled={isElementDisabled} beforeUpload={(file) => beforeUpload(file)}>
|
|
100
|
+
<Button_FillerPortal disabled={isElementDisabled} outline onClick={() => {}}>
|
|
100
101
|
<FaUpload />
|
|
101
102
|
{elementProps.placeholder}
|
|
102
103
|
</Button_FillerPortal>
|
|
@@ -110,6 +111,7 @@ interface ILayoutRenderer_FileUpload {
|
|
|
110
111
|
formRef: FormInstance
|
|
111
112
|
fieldKey: string
|
|
112
113
|
uploadRules?: IFileUploadElementRules
|
|
114
|
+
isElementDisabled: boolean
|
|
113
115
|
elementProps: {
|
|
114
116
|
label?: string
|
|
115
117
|
hasNoLabel?: boolean
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Divider, FormInstance } from 'antd'
|
|
2
|
-
import { ReactElement, ReactNode, useMemo } from 'react'
|
|
2
|
+
import { ReactElement, ReactNode, useEffect, useMemo } from 'react'
|
|
3
3
|
import {
|
|
4
4
|
IDataRender_ButtonProps,
|
|
5
5
|
IFieldElementSourceManual,
|
|
@@ -15,14 +15,41 @@ import LayoutRenderer_RichEditor from './4-rich-text-editor'
|
|
|
15
15
|
import LayoutRenderer_ReCaptcha from './5-re-captcha'
|
|
16
16
|
import LayoutRenderer_Signature from './6-signature'
|
|
17
17
|
import LayoutRenderer_FileUpload from './7-file-upload'
|
|
18
|
+
import { useCheckElementConditions } from '../../../common/custom-hooks/use-check-element-conditions.hook'
|
|
19
|
+
|
|
20
|
+
export default function LayoutRendererElementWrapper({
|
|
21
|
+
hideElement,
|
|
22
|
+
...componentProps
|
|
23
|
+
}: ILayoutRendererElement & { hideElement: (isHidden: boolean) => void }) {
|
|
24
|
+
const { isElementDisabled, isElementHidden } = useCheckElementConditions(
|
|
25
|
+
componentProps.formRef,
|
|
26
|
+
componentProps.elementData.conditions,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
hideElement(isElementHidden)
|
|
31
|
+
}, [isElementHidden])
|
|
32
|
+
|
|
33
|
+
if (isElementHidden) return <></>
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div className="flex flex-col">
|
|
37
|
+
<LayoutRendererElement {...componentProps} isElementDisabled={isElementDisabled} />
|
|
38
|
+
{componentProps.elementData.props && 'description' in componentProps.elementData.props && (
|
|
39
|
+
<span className="text-neutral text-12 italic">{componentProps.elementData.props.description}</span>
|
|
40
|
+
)}
|
|
41
|
+
</div>
|
|
42
|
+
)
|
|
43
|
+
}
|
|
18
44
|
|
|
19
|
-
|
|
45
|
+
function LayoutRendererElement({
|
|
20
46
|
elementData,
|
|
21
47
|
formRef,
|
|
22
48
|
titleComponent,
|
|
23
49
|
breakpointDevice,
|
|
50
|
+
isElementDisabled,
|
|
24
51
|
renderButton,
|
|
25
|
-
}: ILayoutRendererElement) {
|
|
52
|
+
}: ILayoutRendererElement & { isElementDisabled: boolean }) {
|
|
26
53
|
const props = getElementGeneralizedProps(elementData.props)
|
|
27
54
|
const { elementType, key, validations, conditions } = elementData
|
|
28
55
|
const { options = [] } = (props.optionSource as IFieldElementSourceManual) ?? {}
|
|
@@ -32,17 +59,38 @@ export default function LayoutRendererElement({
|
|
|
32
59
|
case ElementTypeEnum.RichTextEditor:
|
|
33
60
|
if (!key) return <span className="text-warning">Rich text editor field could not render due to missing KEY!</span>
|
|
34
61
|
|
|
35
|
-
return
|
|
62
|
+
return (
|
|
63
|
+
<LayoutRenderer_RichEditor
|
|
64
|
+
formRef={formRef}
|
|
65
|
+
fieldKey={key}
|
|
66
|
+
elementProps={props}
|
|
67
|
+
isElementDisabled={isElementDisabled}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
36
70
|
|
|
37
71
|
case ElementTypeEnum.ReCaptcha:
|
|
38
|
-
if (!key) return <span className="text-warning">
|
|
72
|
+
if (!key) return <span className="text-warning">ReCaptcha field could not render due to missing field KEY!</span>
|
|
39
73
|
|
|
40
|
-
return
|
|
74
|
+
return (
|
|
75
|
+
<LayoutRenderer_ReCaptcha
|
|
76
|
+
formRef={formRef}
|
|
77
|
+
fieldKey={key}
|
|
78
|
+
elementProps={props}
|
|
79
|
+
isElementDisabled={isElementDisabled}
|
|
80
|
+
/>
|
|
81
|
+
)
|
|
41
82
|
|
|
42
83
|
case ElementTypeEnum.Signature:
|
|
43
84
|
if (!key) return <span className="text-warning">Signature field could not render due to missing KEY!</span>
|
|
44
85
|
|
|
45
|
-
return
|
|
86
|
+
return (
|
|
87
|
+
<LayoutRenderer_Signature
|
|
88
|
+
formRef={formRef}
|
|
89
|
+
fieldKey={key}
|
|
90
|
+
elementProps={props}
|
|
91
|
+
isElementDisabled={isElementDisabled}
|
|
92
|
+
/>
|
|
93
|
+
)
|
|
46
94
|
|
|
47
95
|
case ElementTypeEnum.FileUpload:
|
|
48
96
|
if (!key) return <span className="text-warning">File upload field could not render due to missing KEY!</span>
|
|
@@ -53,6 +101,7 @@ export default function LayoutRendererElement({
|
|
|
53
101
|
fieldKey={key}
|
|
54
102
|
elementProps={props}
|
|
55
103
|
uploadRules={elementData.uploadRules}
|
|
104
|
+
isElementDisabled={isElementDisabled}
|
|
56
105
|
/>
|
|
57
106
|
)
|
|
58
107
|
|
|
@@ -91,6 +140,7 @@ export default function LayoutRendererElement({
|
|
|
91
140
|
name: key ?? '',
|
|
92
141
|
validations,
|
|
93
142
|
options: options.map((op) => ({ value: op.value, label: op.value })),
|
|
143
|
+
disabled: isElementDisabled,
|
|
94
144
|
},
|
|
95
145
|
]}
|
|
96
146
|
/>
|
package/src/enums.ts
CHANGED
|
@@ -152,7 +152,7 @@ export enum FlexAlignItems {
|
|
|
152
152
|
}
|
|
153
153
|
export enum FilterConfigTypeEnum {
|
|
154
154
|
NoFilter = 'NoFilter',
|
|
155
|
-
|
|
155
|
+
ByAuthUser = 'ByAuthUser',
|
|
156
156
|
Custom = 'Custom',
|
|
157
157
|
}
|
|
158
158
|
export enum TitleElementTypeEnum {
|
|
@@ -192,7 +192,11 @@ export enum FormPreservedItemKeys {
|
|
|
192
192
|
FormDataId = 'formDataId',
|
|
193
193
|
BaseServerUrl = 'baseServerUrl',
|
|
194
194
|
CompanyKey = 'companyKey',
|
|
195
|
-
|
|
195
|
+
SubmissionPdfConfig = 'submissionPdfConfig',
|
|
196
196
|
IsGeneratingPDF = 'isGeneratingPdf',
|
|
197
197
|
HasSignature = 'hasSignature',
|
|
198
198
|
}
|
|
199
|
+
export enum MongoDbSortOrderEnum {
|
|
200
|
+
Ascending = 1,
|
|
201
|
+
Descending = -1,
|
|
202
|
+
}
|
|
@@ -9,7 +9,6 @@ export interface IFormDataListElement {
|
|
|
9
9
|
order: number
|
|
10
10
|
isStatic?: boolean
|
|
11
11
|
sortable?: boolean
|
|
12
|
-
defaultSorted?: boolean
|
|
13
12
|
align?: AlignTypeEnum
|
|
14
13
|
width?: string | number
|
|
15
14
|
renderConfig?: IElementDataRenderConfig
|
|
@@ -19,10 +18,7 @@ export interface IFormDataListHeader {
|
|
|
19
18
|
}
|
|
20
19
|
export interface IFilterConfig {
|
|
21
20
|
type: FilterConfigTypeEnum
|
|
22
|
-
config?: {
|
|
23
|
-
customFilter?: { [key: string]: any } | null
|
|
24
|
-
dynamicFilter?: { [key: string]: any } | null
|
|
25
|
-
}
|
|
21
|
+
config?: { [key: string]: any } | null
|
|
26
22
|
}
|
|
27
23
|
export interface IFilterSimple {
|
|
28
24
|
[key: string]: IFilterConfig
|
package/src/types/index.ts
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
export * from './layout-elements'
|
|
2
|
-
export * from './
|
|
2
|
+
export * from './data-list'
|
|
3
|
+
export * from './generate'
|
|
3
4
|
|
|
4
5
|
import {
|
|
5
6
|
FormDataListViewTypeEnum,
|
|
6
7
|
FormLayoutAddableNodeEnum,
|
|
7
8
|
FormLayoutNodeEnum,
|
|
8
9
|
MigrationReasonOperationEnum,
|
|
10
|
+
MongoDbSortOrderEnum,
|
|
9
11
|
} from '../enums'
|
|
10
|
-
import { IFilterConfig, IFormDataListElement, IFormDataListHeader, IFormDataListPagination } from './
|
|
12
|
+
import { IFilterConfig, IFormDataListElement, IFormDataListHeader, IFormDataListPagination } from './data-list'
|
|
13
|
+
import { IFormSubmissionPdf } from './generate'
|
|
11
14
|
import {
|
|
12
15
|
IPickerElement,
|
|
13
16
|
INumberInputElement,
|
|
@@ -31,11 +34,11 @@ import { ICssStyle, IFlexConfig } from './layout-elements/style'
|
|
|
31
34
|
export interface IFormSchema {
|
|
32
35
|
detailsConfig: IFormDetailsConfig
|
|
33
36
|
dataListConfig: IFormDataListConfig
|
|
37
|
+
generateConfig: IFormGenerateConfig
|
|
34
38
|
}
|
|
35
39
|
export interface IFormDetailsConfig {
|
|
36
40
|
layout: IFormLayoutRow[]
|
|
37
41
|
migrationRules: { [key: string]: IMigrationRule[] }
|
|
38
|
-
convertScreenToPdf?: boolean
|
|
39
42
|
}
|
|
40
43
|
// non-templates
|
|
41
44
|
export interface IFormDataListConfig {
|
|
@@ -46,6 +49,12 @@ export interface IFormDataListConfig {
|
|
|
46
49
|
title?: string
|
|
47
50
|
showCount?: boolean
|
|
48
51
|
usePopup?: boolean
|
|
52
|
+
defaultFilter?: { [key: string]: any } | null
|
|
53
|
+
defaultSorter?: { field: string; order: MongoDbSortOrderEnum }
|
|
54
|
+
}
|
|
55
|
+
export interface IFormGenerateConfig {
|
|
56
|
+
submissionPdf?: IFormSubmissionPdf
|
|
57
|
+
// reports: []
|
|
49
58
|
}
|
|
50
59
|
export interface IFormLayoutRow {
|
|
51
60
|
nodeType: FormLayoutNodeEnum.Row
|
|
File without changes
|