form-craft-package 1.7.9-dev.0 → 1.7.9-dev.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 +3 -2
- package/src/api/client.ts +1 -1
- package/src/api/user.ts +7 -8
- package/src/components/common/custom-hooks/use-breadcrumb.hook.ts +90 -0
- package/src/components/common/custom-hooks/use-check-element-conditions.hook.ts +3 -3
- package/src/components/common/custom-hooks/use-find-dynamic-form.hook.ts +30 -11
- package/src/components/common/custom-hooks/use-login-handler.ts +81 -4
- package/src/components/common/custom-hooks/use-many-to-many-connector.hook.ts +30 -0
- package/src/components/common/not-found.tsx +21 -0
- package/src/components/common/section-panel.tsx +42 -0
- package/src/components/companies/1-authenticated/change-password.tsx +110 -0
- package/src/components/companies/2-unauthenticated/reset-password.tsx +128 -0
- package/src/components/companies/index.tsx +2 -0
- package/src/components/form/1-list/index.tsx +37 -38
- package/src/components/form/1-list/table-header.tsx +6 -6
- package/src/components/form/1-list/table.tsx +10 -12
- package/src/components/form/2-details/index.tsx +63 -41
- package/src/components/form/layout-renderer/1-row/index.tsx +12 -5
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +59 -89
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-button-navigate.hook.tsx +88 -0
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-create-data.hook.ts +22 -23
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-generate-report.hook.tsx +3 -4
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-save-data.hook.ts +2 -2
- package/src/components/form/layout-renderer/3-element/11-breadcrumb.tsx +30 -0
- package/src/components/form/layout-renderer/3-element/2-field-element.tsx +4 -1
- package/src/components/form/layout-renderer/3-element/4-rich-text-editor.tsx +27 -2
- package/src/components/form/layout-renderer/3-element/6-signature.tsx +5 -1
- package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +115 -74
- package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +50 -110
- package/src/components/form/layout-renderer/3-element/index.tsx +27 -9
- package/src/components/modals/report-filters.modal/helper-functions.ts +3 -6
- package/src/constants.ts +62 -45
- package/src/enums/form.enum.ts +5 -3
- package/src/enums/index.ts +9 -3
- package/src/{global-helpers → functions}/cookie-handler.ts +9 -10
- package/src/functions/forms/breadcrumb-handlers.ts +21 -0
- package/src/functions/forms/create-form-rules.ts +4 -1
- package/src/functions/forms/data-render-functions.tsx +5 -4
- package/src/functions/forms/extended-json-handlers.ts +56 -0
- package/src/functions/forms/index.ts +17 -11
- package/src/functions/index.ts +2 -1
- package/src/functions/reports/index.tsx +2 -1
- package/src/types/companies/site-layout/authenticated/index.tsx +23 -14
- package/src/types/forms/data-list/index.ts +0 -7
- package/src/types/forms/index.ts +1 -0
- package/src/types/forms/layout-elements/button.ts +11 -3
- package/src/types/forms/layout-elements/data-render-config.ts +1 -0
- package/src/types/forms/layout-elements/index.ts +12 -2
- package/src/types/forms/layout-elements/sanitization.ts +6 -1
- package/src/types/forms/relationship/index.ts +12 -1
- package/src/types/index.ts +2 -0
- package/src/functions/forms/json-handlers.ts +0 -19
- package/src/global-helpers/constants.ts +0 -2
- package/src/global-helpers/enums.ts +0 -12
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { Button, Col, Form, Input, Row, Spin } from 'antd'
|
|
2
|
+
import { useEffect, useState } from 'react'
|
|
3
|
+
import { useNavigate, useLocation } from 'react-router-dom'
|
|
4
|
+
import { useLoginHandler } from '../../common/custom-hooks/use-login-handler'
|
|
5
|
+
import { useConfigContext } from '../context'
|
|
6
|
+
import { useRef } from 'react'
|
|
7
|
+
|
|
8
|
+
export const ResetPassword = () => {
|
|
9
|
+
const { config } = useConfigContext()
|
|
10
|
+
const [loading, setLoading] = useState<boolean>(false)
|
|
11
|
+
const location = useLocation()
|
|
12
|
+
const { handleVerifyToken, handleResetPassword } = useLoginHandler()
|
|
13
|
+
const searchParams = new URLSearchParams(location.search)
|
|
14
|
+
const token = searchParams.get('token') || ''
|
|
15
|
+
const domain = searchParams.get('domain') || ''
|
|
16
|
+
const email = searchParams.get('email') || ''
|
|
17
|
+
const [error, setError] = useState<string>('')
|
|
18
|
+
const navigate = useNavigate()
|
|
19
|
+
const [tokenValid, setTokenValid] = useState(false)
|
|
20
|
+
|
|
21
|
+
const formPadding = config?.loginLayout?.form?.padding
|
|
22
|
+
const titleColor = config?.loginLayout.form.color
|
|
23
|
+
const background = config?.loginLayout?.background
|
|
24
|
+
const logoUrl = config?.siteIdentity?.logoUrl
|
|
25
|
+
const primaryColor = config?.siteLayout?.siteConfigs?.colors?.primary
|
|
26
|
+
const isFirstLoad = useRef(true)
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
const checkToken = async () => {
|
|
29
|
+
setLoading(true)
|
|
30
|
+
if (isFirstLoad.current) {
|
|
31
|
+
isFirstLoad.current = false
|
|
32
|
+
const isValid = await handleVerifyToken(domain!, email!, token.replaceAll(' ', '+'))
|
|
33
|
+
setTokenValid(!!isValid)
|
|
34
|
+
setLoading(false)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (token) {
|
|
39
|
+
checkToken()
|
|
40
|
+
}
|
|
41
|
+
}, [domain, email, token, handleVerifyToken])
|
|
42
|
+
|
|
43
|
+
const onReset = async (values: { password: string; confirm: string }) => {
|
|
44
|
+
setLoading(true)
|
|
45
|
+
if (values.password !== values.confirm) {
|
|
46
|
+
return setError('Passwords do not match')
|
|
47
|
+
}
|
|
48
|
+
const resetResponse = await handleResetPassword(domain!, email!, token.replaceAll(' ', '+'), values.password)
|
|
49
|
+
if (resetResponse) {
|
|
50
|
+
setError('')
|
|
51
|
+
setLoading(false)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<Spin spinning={!config && loading}>
|
|
57
|
+
<div className="h-screen w-full flex items-center justify-center overflow-hidden" style={{ background }}>
|
|
58
|
+
<div
|
|
59
|
+
className="overflow-auto text-center shadow"
|
|
60
|
+
style={{
|
|
61
|
+
padding: formPadding,
|
|
62
|
+
background: '#fff',
|
|
63
|
+
width: 400,
|
|
64
|
+
maxWidth: '100%',
|
|
65
|
+
}}
|
|
66
|
+
>
|
|
67
|
+
{logoUrl && (
|
|
68
|
+
<div className="flex justify-center mb-4">
|
|
69
|
+
<img src={logoUrl} alt="Logo" className="h-10 object-contain" />
|
|
70
|
+
</div>
|
|
71
|
+
)}
|
|
72
|
+
|
|
73
|
+
<div className="font-bold mb-2" style={{ color: titleColor }}>
|
|
74
|
+
Reset Password
|
|
75
|
+
</div>
|
|
76
|
+
|
|
77
|
+
{tokenValid ? (
|
|
78
|
+
<>
|
|
79
|
+
<Form layout="vertical" onFinish={onReset}>
|
|
80
|
+
<Row gutter={[0, 10]}>
|
|
81
|
+
<Col span={24}>
|
|
82
|
+
<Form.Item
|
|
83
|
+
label="New Password"
|
|
84
|
+
name="password"
|
|
85
|
+
rules={[{ required: true, message: 'Enter your new password' }]}
|
|
86
|
+
>
|
|
87
|
+
<Input.Password />
|
|
88
|
+
</Form.Item>
|
|
89
|
+
</Col>
|
|
90
|
+
<Col span={24}>
|
|
91
|
+
<Form.Item
|
|
92
|
+
label="Confirm Password"
|
|
93
|
+
name="confirm"
|
|
94
|
+
rules={[{ required: true, message: 'Confirm your new password' }]}
|
|
95
|
+
>
|
|
96
|
+
<Input.Password />
|
|
97
|
+
</Form.Item>
|
|
98
|
+
</Col>
|
|
99
|
+
<Col span={24}>{error && <div className="text-danger">{error}</div>}</Col>
|
|
100
|
+
</Row>
|
|
101
|
+
|
|
102
|
+
<div className="flex justify-center mt-5">
|
|
103
|
+
<Button
|
|
104
|
+
htmlType="submit"
|
|
105
|
+
className="w-full"
|
|
106
|
+
style={{
|
|
107
|
+
color: '#fff',
|
|
108
|
+
backgroundColor: primaryColor,
|
|
109
|
+
}}
|
|
110
|
+
>
|
|
111
|
+
Reset Password
|
|
112
|
+
</Button>
|
|
113
|
+
</div>
|
|
114
|
+
</Form>
|
|
115
|
+
</>
|
|
116
|
+
) : (
|
|
117
|
+
<div className="flex flex-col items-center gap-2">
|
|
118
|
+
<div className="text-red-500">Token is invalid or has expired. Please request a new reset link.</div>
|
|
119
|
+
<div>
|
|
120
|
+
<Button onClick={() => navigate('/forgot-password')}>Reset Link</Button>
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
</div>
|
|
126
|
+
</Spin>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
@@ -3,10 +3,10 @@ import { cancelableClient } from '../../../api/client'
|
|
|
3
3
|
import FormDataListSkeleton_Table from '../../common/loading-skeletons/table'
|
|
4
4
|
import { ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
|
|
5
5
|
import FormDataListTableComponent, { IDataListLayoutConfig } from './table'
|
|
6
|
-
import {
|
|
6
|
+
import { IFormJoin, IFormDataListData, IFormDataListConfig } from '../../../types'
|
|
7
|
+
import { getProjectionKey } from '../../../functions'
|
|
7
8
|
|
|
8
9
|
function FormDataListComponent({
|
|
9
|
-
formName,
|
|
10
10
|
formId,
|
|
11
11
|
userId,
|
|
12
12
|
companyKey,
|
|
@@ -23,7 +23,7 @@ function FormDataListComponent({
|
|
|
23
23
|
const defaultFilterReqDataRef = useRef<IDataListReqData | undefined>(undefined)
|
|
24
24
|
|
|
25
25
|
const attachmentBaseUrl = useMemo(() => `${baseServerUrl}/api/attachment/${companyKey}`, [baseServerUrl, companyKey])
|
|
26
|
-
const headerLayoutContext = { formId, userId, attachmentBaseUrl,
|
|
26
|
+
const headerLayoutContext = { formId, userId, attachmentBaseUrl, companyKey, onCustomFunctionCall }
|
|
27
27
|
|
|
28
28
|
useEffect(() => {
|
|
29
29
|
setLoadings({ initial: true, data: true })
|
|
@@ -35,43 +35,43 @@ function FormDataListComponent({
|
|
|
35
35
|
|
|
36
36
|
useEffect(() => {
|
|
37
37
|
if (!formId) return
|
|
38
|
-
const { request, cancel } = cancelableClient.
|
|
38
|
+
const { request, cancel } = cancelableClient.post(`/api/form/${formId}`, {
|
|
39
|
+
project: JSON.stringify({ 'Data.dataListConfig': 1 }),
|
|
40
|
+
})
|
|
39
41
|
request.then(async (res) => {
|
|
40
42
|
if (res.status === 200) {
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
defaultReqData.limit = pagination?.defaultPageSize ?? 10
|
|
56
|
-
}
|
|
57
|
-
if (defaultSorter) defaultReqData.sort = JSON.stringify({ [defaultSorter.field]: defaultSorter.order })
|
|
58
|
-
if (defaultFilter?.config && Object.values(defaultFilter.config).length)
|
|
59
|
-
// 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 $.
|
|
60
|
-
defaultReqData.match = JSON.stringify(defaultFilter.config).replaceAll('@', '$')
|
|
61
|
-
|
|
62
|
-
defaultFilterReqDataRef.current = defaultReqData
|
|
63
|
-
|
|
64
|
-
fetchFormDataList(defaultReqData)
|
|
65
|
-
|
|
66
|
-
// configForFormId is passed to ensure that the table shows the correct data list when the forms are switched quickly
|
|
67
|
-
setListLayoutConfig({
|
|
68
|
-
configForFormId: formId,
|
|
69
|
-
dataListConfig: {
|
|
70
|
-
...parsedData.dataListConfig,
|
|
71
|
-
columns: columns.map((c) => (c.key ? { ...c, key: c.key.replace(/\./g, '_') } : c)),
|
|
72
|
-
},
|
|
73
|
-
})
|
|
43
|
+
const dataListConfig: IFormDataListConfig = res.data.Data.dataListConfig
|
|
44
|
+
const { columns, formJoins, pagination, defaultFilter, defaultSorter } = dataListConfig
|
|
45
|
+
|
|
46
|
+
if (Array.isArray(columns))
|
|
47
|
+
dataProjectRef.current = columns.reduce(
|
|
48
|
+
(curr, c) => (c.key ? { ...curr, [getProjectionKey(c.key)]: `$${c.key}` } : curr),
|
|
49
|
+
{},
|
|
50
|
+
)
|
|
51
|
+
if (Array.isArray(formJoins)) formJoinsRef.current = formJoins
|
|
52
|
+
|
|
53
|
+
const defaultReqData: IDataListReqData = {}
|
|
54
|
+
if (!pagination?.hasNoPagination) {
|
|
55
|
+
defaultReqData.current = 1
|
|
56
|
+
defaultReqData.limit = pagination?.defaultPageSize ?? 10
|
|
74
57
|
}
|
|
58
|
+
if (defaultSorter) defaultReqData.sort = JSON.stringify({ [defaultSorter.field]: defaultSorter.order })
|
|
59
|
+
if (defaultFilter?.config && Object.values(defaultFilter.config).length)
|
|
60
|
+
// 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 $.
|
|
61
|
+
defaultReqData.match = JSON.stringify(defaultFilter.config).replaceAll('@', '$')
|
|
62
|
+
|
|
63
|
+
defaultFilterReqDataRef.current = defaultReqData
|
|
64
|
+
|
|
65
|
+
fetchFormDataList(defaultReqData)
|
|
66
|
+
|
|
67
|
+
// configForFormId is passed to ensure that the table shows the correct data list when the forms are switched quickly
|
|
68
|
+
setListLayoutConfig({
|
|
69
|
+
configForFormId: formId,
|
|
70
|
+
dataListConfig: {
|
|
71
|
+
...dataListConfig,
|
|
72
|
+
columns: columns.map((c) => (c.key ? { ...c, key: getProjectionKey(c.key) } : c)),
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
75
|
}
|
|
76
76
|
})
|
|
77
77
|
|
|
@@ -127,7 +127,6 @@ function FormDataListComponent({
|
|
|
127
127
|
export { FormDataListComponent }
|
|
128
128
|
|
|
129
129
|
type IFormDataListComponent = {
|
|
130
|
-
formName?: string
|
|
131
130
|
baseServerUrl?: string
|
|
132
131
|
companyKey?: string
|
|
133
132
|
formId?: number
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Form } from 'antd'
|
|
2
2
|
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
3
3
|
import dayjs from 'dayjs'
|
|
4
|
-
import { extractFiltersFromLayout } from '../../../functions/forms'
|
|
4
|
+
import { extractFiltersFromLayout, getIdEqualsQuery } from '../../../functions/forms'
|
|
5
5
|
import { DeviceBreakpointEnum, FilterConfigTypeEnum, FormPreservedItemKeys } from '../../../enums'
|
|
6
6
|
import { LayoutRendererRow } from '../layout-renderer/1-row'
|
|
7
7
|
import { VALUE_REPLACEMENT_PLACEHOLDER } from '../../../constants'
|
|
@@ -26,7 +26,7 @@ export default function FormDataListHeaderComponent({
|
|
|
26
26
|
}: IFormDataListHeaderComponent) {
|
|
27
27
|
const [filtersFormRef] = Form.useForm()
|
|
28
28
|
const [filterConfigs, setFilterConfigs] = useState<IFilterNested>({})
|
|
29
|
-
const { userId, formId,
|
|
29
|
+
const { userId, formId, parentFormJoins, formDataId, manyToManyRelInfo, onCustomFunctionCall } = headerLayoutContext
|
|
30
30
|
const isInitialFetchRef = useRef(true)
|
|
31
31
|
|
|
32
32
|
const currentBreakpoint = useGetCurrentBreakpoint()
|
|
@@ -108,8 +108,8 @@ export default function FormDataListHeaderComponent({
|
|
|
108
108
|
}, [filterValues])
|
|
109
109
|
|
|
110
110
|
const formContext = useMemo(
|
|
111
|
-
() => ({ formRef: filtersFormRef,
|
|
112
|
-
[filtersFormRef,
|
|
111
|
+
() => ({ formRef: filtersFormRef, formId, formDataId, parentFormJoins, manyToManyRelInfo }),
|
|
112
|
+
[filtersFormRef, formId, formDataId, parentFormJoins, manyToManyRelInfo],
|
|
113
113
|
)
|
|
114
114
|
|
|
115
115
|
return (
|
|
@@ -123,7 +123,7 @@ export default function FormDataListHeaderComponent({
|
|
|
123
123
|
elements={layoutConfig?.elements ?? {}}
|
|
124
124
|
renderButton={(btnProps, conditions) => (
|
|
125
125
|
<DynamicFormButtonRender
|
|
126
|
-
displayStateProps={{ btnProps, conditions
|
|
126
|
+
displayStateProps={{ btnProps, conditions }}
|
|
127
127
|
formContext={formContext}
|
|
128
128
|
onCustomFunctionCall={onCustomFunctionCall}
|
|
129
129
|
/>
|
|
@@ -172,7 +172,7 @@ const handleFilterValues = async (
|
|
|
172
172
|
|
|
173
173
|
return {
|
|
174
174
|
customFilter: {},
|
|
175
|
-
dynamicFilter: JSON.stringify(
|
|
175
|
+
dynamicFilter: JSON.stringify(getIdEqualsQuery(lastRel.formId, value)),
|
|
176
176
|
}
|
|
177
177
|
}
|
|
178
178
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
2
2
|
import FormDataListHeaderComponent from './table-header'
|
|
3
3
|
import useDebounced from '../../common/custom-hooks/use-debounce.hook'
|
|
4
|
-
import {
|
|
4
|
+
import { Table } from 'antd'
|
|
5
5
|
import { ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
|
|
6
6
|
import { SorterResult } from 'antd/es/table/interface'
|
|
7
7
|
import { generateTableColumns } from '../../../functions/forms'
|
|
@@ -9,8 +9,9 @@ import { useLocation } from 'react-router-dom'
|
|
|
9
9
|
import useGetCurrentBreakpoint from '../../common/custom-hooks/use-window-width.hook'
|
|
10
10
|
import { DeviceBreakpointEnum, FormDataListViewTypeEnum, MongoDbSortOrderEnum } from '../../../enums'
|
|
11
11
|
import { IDataListReqData } from '.'
|
|
12
|
-
import {
|
|
12
|
+
import { IFormDataListConfig, IFormDataListData, IFormDataTableColumn } from '../../../types'
|
|
13
13
|
import { processOptions } from '../../../functions/forms/get-data-list-option-value'
|
|
14
|
+
import { IFormContext } from '../layout-renderer/1-row'
|
|
14
15
|
|
|
15
16
|
export default function FormDataListTableComponent({
|
|
16
17
|
layoutsConfigs,
|
|
@@ -33,7 +34,9 @@ export default function FormDataListTableComponent({
|
|
|
33
34
|
)
|
|
34
35
|
const [otherConfigs, setOtherConfigs] = useState<Partial<IFormDataListConfig> | undefined>(undefined)
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
setFilterReqData(undefined)
|
|
39
|
+
}, [location.pathname])
|
|
37
40
|
|
|
38
41
|
useEffect(() => {
|
|
39
42
|
if (Array.isArray(layoutsConfigs?.dataListConfig.columns)) {
|
|
@@ -64,7 +67,7 @@ export default function FormDataListTableComponent({
|
|
|
64
67
|
async (layoutsConfigs: IDataListLayoutConfig) => {
|
|
65
68
|
const { configForFormId, dataListConfig } = layoutsConfigs!
|
|
66
69
|
|
|
67
|
-
if (configForFormId !== formId) {
|
|
70
|
+
if (configForFormId !== headerLayoutContext.formId) {
|
|
68
71
|
setLoading(false)
|
|
69
72
|
return
|
|
70
73
|
}
|
|
@@ -82,7 +85,7 @@ export default function FormDataListTableComponent({
|
|
|
82
85
|
|
|
83
86
|
setLoading(false)
|
|
84
87
|
},
|
|
85
|
-
[
|
|
88
|
+
[headerLayoutContext, layoutsConfigs],
|
|
86
89
|
)
|
|
87
90
|
|
|
88
91
|
useEffect(() => {
|
|
@@ -170,14 +173,9 @@ type IFormDataListTableComponent = {
|
|
|
170
173
|
|
|
171
174
|
export type IDataListHeaderLayoutContext = {
|
|
172
175
|
userId?: string | number
|
|
173
|
-
formId?: number
|
|
174
176
|
attachmentBaseUrl?: string
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
parentInfo?: IDataListParentInfo
|
|
178
|
-
formRef?: FormInstance
|
|
179
|
-
companyKey?: string
|
|
180
|
-
} & ICustomFunctionCall
|
|
177
|
+
} & ICustomFunctionCall &
|
|
178
|
+
IFormContext
|
|
181
179
|
|
|
182
180
|
export type IDataListLayoutConfig =
|
|
183
181
|
| {
|
|
@@ -2,16 +2,17 @@ import { Form } from 'antd'
|
|
|
2
2
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
3
3
|
import { IDndLayoutStructure_Responsive, IFormSchema } from '../../../types'
|
|
4
4
|
import { NEW_FORM_DATA_IDENTIFIER } from '../../../constants'
|
|
5
|
-
import { parseJSON } from '../../../functions/forms/json-handlers'
|
|
6
5
|
import { LayoutRendererRow } from '../layout-renderer/1-row'
|
|
7
6
|
import { DynamicFormButtonRender, ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
|
|
8
7
|
import FormDataListSkeleton_Details from '../../common/loading-skeletons/details'
|
|
9
8
|
import { useLocation } from 'react-router-dom'
|
|
10
|
-
import {
|
|
11
|
-
import { DeviceBreakpointEnum, FormPreservedItemKeys } from '../../../enums'
|
|
12
|
-
import dayjs from 'dayjs'
|
|
9
|
+
import { fromMongoDbExtendedJSON, isValidMongoDbId, queryParamsToObject } from '../../../functions/forms'
|
|
10
|
+
import { DeviceBreakpointEnum, FormPreservedItemKeys, LOCAL_STORAGE_KEYS_ENUM } from '../../../enums'
|
|
13
11
|
import useGetCurrentBreakpoint from '../../common/custom-hooks/use-window-width.hook'
|
|
14
12
|
import client from '../../../api/client'
|
|
13
|
+
import { useBreadcrumb } from '../../common/custom-hooks/use-breadcrumb.hook'
|
|
14
|
+
import NotFound from '../../common/not-found'
|
|
15
|
+
import { useManyToManyConnector } from '../../common/custom-hooks/use-many-to-many-connector.hook'
|
|
15
16
|
|
|
16
17
|
export default function FormDataDetailsComponent({
|
|
17
18
|
isPublic,
|
|
@@ -21,21 +22,44 @@ export default function FormDataDetailsComponent({
|
|
|
21
22
|
formName,
|
|
22
23
|
companyKey,
|
|
23
24
|
baseServerUrl,
|
|
24
|
-
onCustomFunctionCall,
|
|
25
25
|
initialValues,
|
|
26
|
+
onCustomFunctionCall,
|
|
26
27
|
}: IFormDataDetailsComponent) {
|
|
28
|
+
const { push } = useBreadcrumb()
|
|
27
29
|
const location = useLocation()
|
|
28
30
|
const [formDataRef] = Form.useForm()
|
|
29
31
|
const [loadings, setLoadings] = useState({ layout: true, data: true })
|
|
30
32
|
const [layoutConfig, setLayoutConfig] = useState<IDndLayoutStructure_Responsive>({ elements: {}, layouts: {} })
|
|
33
|
+
const [isNotFound, setIsNotFound] = useState(false)
|
|
34
|
+
useManyToManyConnector(formDataRef)
|
|
31
35
|
|
|
32
36
|
const currentBreakpoint = useGetCurrentBreakpoint()
|
|
33
37
|
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
// for public forms, setting the details form into localStorage, so that buttons work correctly
|
|
40
|
+
if (isPublic)
|
|
41
|
+
localStorage.setItem(LOCAL_STORAGE_KEYS_ENUM.DynamicForms, JSON.stringify([{ id: formId, name: formName }]))
|
|
42
|
+
}, [isPublic, formId, formName])
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!formDataId || !isValidMongoDbId(formDataId)) setIsNotFound(true)
|
|
46
|
+
}, [formDataId])
|
|
47
|
+
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
const isNewDataPage = formDataId === NEW_FORM_DATA_IDENTIFIER
|
|
50
|
+
const splittedFormName = formName?.split(' ')?.[0] ?? ''
|
|
51
|
+
push({
|
|
52
|
+
label: isNewDataPage ? `New ${splittedFormName.toLowerCase()}` : `${splittedFormName} details`,
|
|
53
|
+
href: location.pathname,
|
|
54
|
+
})
|
|
55
|
+
}, [location.pathname, formName, formDataId])
|
|
56
|
+
|
|
34
57
|
const layout = useMemo(
|
|
35
58
|
() => layoutConfig.layouts[currentBreakpoint] ?? layoutConfig.layouts[DeviceBreakpointEnum.Default] ?? [],
|
|
36
59
|
[layoutConfig.layouts, currentBreakpoint],
|
|
37
60
|
)
|
|
38
|
-
|
|
61
|
+
|
|
62
|
+
// Apply initialValues from parent
|
|
39
63
|
useEffect(() => {
|
|
40
64
|
if (initialValues && formDataId === NEW_FORM_DATA_IDENTIFIER) {
|
|
41
65
|
formDataRef.setFieldsValue(initialValues)
|
|
@@ -60,8 +84,9 @@ export default function FormDataDetailsComponent({
|
|
|
60
84
|
}, [formDataRef, baseServerUrl, isPublic])
|
|
61
85
|
|
|
62
86
|
const fetchFormData = useCallback(
|
|
63
|
-
(dFormId?: number
|
|
64
|
-
if (formDataId === NEW_FORM_DATA_IDENTIFIER
|
|
87
|
+
(dFormId?: number) => {
|
|
88
|
+
if (formDataId === NEW_FORM_DATA_IDENTIFIER || !isValidMongoDbId(formDataId))
|
|
89
|
+
setLoadings((c) => ({ ...c, data: false }))
|
|
65
90
|
else {
|
|
66
91
|
if (!dFormId) {
|
|
67
92
|
console.error('Form ID is required to fetch form data')
|
|
@@ -72,18 +97,14 @@ export default function FormDataDetailsComponent({
|
|
|
72
97
|
.then((res) => {
|
|
73
98
|
if (res.status === 200) {
|
|
74
99
|
const { data: jsonData, ...restFormData } = res.data
|
|
75
|
-
let parsedFormData =
|
|
100
|
+
let parsedFormData = JSON.parse(jsonData)
|
|
76
101
|
if (parsedFormData) {
|
|
77
|
-
const fieldsValue: { [key: string]: any } = parsedFormData
|
|
78
|
-
if (dateFields.length > 0)
|
|
79
|
-
Object.entries(parsedFormData).map(([field, value]) => {
|
|
80
|
-
if (dateFields.includes(field) && value) fieldsValue[field] = dayjs(value)
|
|
81
|
-
else fieldsValue[field] = value
|
|
82
|
-
})
|
|
102
|
+
const fieldsValue: { [key: string]: any } = fromMongoDbExtendedJSON(parsedFormData)
|
|
83
103
|
formDataRef.setFieldsValue({ ...restFormData, ...fieldsValue })
|
|
104
|
+
|
|
105
|
+
console.log('FORM DATA FETCH (PARSED)', fieldsValue)
|
|
84
106
|
}
|
|
85
|
-
|
|
86
|
-
}
|
|
107
|
+
} else setIsNotFound(true)
|
|
87
108
|
})
|
|
88
109
|
.finally(() => setLoadings((c) => ({ ...c, data: false })))
|
|
89
110
|
}
|
|
@@ -98,7 +119,7 @@ export default function FormDataDetailsComponent({
|
|
|
98
119
|
.get(endpoint)
|
|
99
120
|
.then((res) => {
|
|
100
121
|
if (res.status === 200) {
|
|
101
|
-
const parsedData: IFormSchema | null =
|
|
122
|
+
const parsedData: IFormSchema | null = JSON.parse(res.data.data)
|
|
102
123
|
if (parsedData) {
|
|
103
124
|
console.log('FORM LAYOUT FETCH (PARSED)', parsedData)
|
|
104
125
|
const { layouts: rawLayouts, elements } = parsedData.detailsConfig
|
|
@@ -110,11 +131,8 @@ export default function FormDataDetailsComponent({
|
|
|
110
131
|
parsedData.generateConfig.submissionPdf,
|
|
111
132
|
)
|
|
112
133
|
setLoadings((c) => ({ ...c, data: false }))
|
|
113
|
-
} else
|
|
114
|
-
const dateFields = extractDateFields(elements)
|
|
134
|
+
} else fetchFormData(formId)
|
|
115
135
|
|
|
116
|
-
fetchFormData(formId, dateFields)
|
|
117
|
-
}
|
|
118
136
|
setLayoutConfig({ elements, layouts: rawLayouts })
|
|
119
137
|
}
|
|
120
138
|
}
|
|
@@ -124,30 +142,34 @@ export default function FormDataDetailsComponent({
|
|
|
124
142
|
}, [formId, formKey, isPublic, formDataRef, fetchFormData])
|
|
125
143
|
|
|
126
144
|
const formContext = useMemo(
|
|
127
|
-
() => ({ formId, formKey, formDataId, formRef: formDataRef,
|
|
128
|
-
[formDataId, formDataRef,
|
|
145
|
+
() => ({ formId, formKey, formDataId, formRef: formDataRef, companyKey }),
|
|
146
|
+
[formDataId, formDataRef, formId, formKey, companyKey],
|
|
129
147
|
)
|
|
130
148
|
|
|
131
149
|
if (loadings.layout || loadings.data) return <FormDataListSkeleton_Details />
|
|
132
150
|
|
|
151
|
+
if (isNotFound) return <NotFound />
|
|
152
|
+
|
|
133
153
|
return (
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
154
|
+
<>
|
|
155
|
+
<Form layout="vertical" name="dynamic_form_data_form" form={formDataRef}>
|
|
156
|
+
{layout.map((row, rowIdx) => (
|
|
157
|
+
<LayoutRendererRow
|
|
158
|
+
key={rowIdx}
|
|
159
|
+
rowData={row}
|
|
160
|
+
formContext={formContext}
|
|
161
|
+
elements={layoutConfig.elements}
|
|
162
|
+
renderButton={(btnProps, conditions) => (
|
|
163
|
+
<DynamicFormButtonRender
|
|
164
|
+
displayStateProps={{ btnProps, conditions, defaultDisabled: true }}
|
|
165
|
+
formContext={formContext}
|
|
166
|
+
onCustomFunctionCall={onCustomFunctionCall}
|
|
167
|
+
/>
|
|
168
|
+
)}
|
|
169
|
+
/>
|
|
170
|
+
))}
|
|
171
|
+
</Form>
|
|
172
|
+
</>
|
|
151
173
|
)
|
|
152
174
|
}
|
|
153
175
|
|
|
@@ -4,7 +4,13 @@ import LayoutRendererCol from '../2-col'
|
|
|
4
4
|
import { memo, ReactElement, ReactNode, useEffect, useMemo, useRef, useState } from 'react'
|
|
5
5
|
import { LayoutRowConditionalHeaderRenderer } from './header-render'
|
|
6
6
|
import { LayoutRowRepeatableRenderer } from './repeatable-render'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
IButtonElementProps,
|
|
9
|
+
IDndLayoutElement,
|
|
10
|
+
IFormLayoutElementConditions,
|
|
11
|
+
IDndLayoutRow,
|
|
12
|
+
IFormJoin,
|
|
13
|
+
} from '../../../../types'
|
|
8
14
|
|
|
9
15
|
export const LayoutRendererRow = memo(
|
|
10
16
|
({ basePath = [], rowData, titleComponent, formContext, elements, renderButton, hideCol }: ILayoutRendererRow) => {
|
|
@@ -28,9 +34,9 @@ export const LayoutRendererRow = memo(
|
|
|
28
34
|
...styleConfig,
|
|
29
35
|
...getFlexContainerStyle(rowData.display),
|
|
30
36
|
}
|
|
31
|
-
|
|
37
|
+
|
|
32
38
|
if (hiddenElementCount === rowData.children.length) return <></>
|
|
33
|
-
|
|
39
|
+
|
|
34
40
|
return (
|
|
35
41
|
<div
|
|
36
42
|
style={{
|
|
@@ -93,8 +99,9 @@ export interface IFormContext {
|
|
|
93
99
|
formDataId?: string
|
|
94
100
|
formRef?: FormInstance
|
|
95
101
|
companyKey?: string
|
|
96
|
-
formName?: string
|
|
102
|
+
formName?: string // only passed in DynamicButton component
|
|
97
103
|
formKey?: string
|
|
98
104
|
formId?: number
|
|
99
|
-
|
|
105
|
+
parentFormJoins?: IFormJoin[]
|
|
106
|
+
manyToManyRelInfo?: { middleFormId: number; currentFormId: number; otherFormId: number } | null
|
|
100
107
|
}
|