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
|
@@ -2,22 +2,23 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
|
2
2
|
import { FieldElementOptionSourceEnum } from '../../../../enums'
|
|
3
3
|
import { IElementBaseProps } from '.'
|
|
4
4
|
import { LayoutRenderer_FieldElement } from './2-field-element'
|
|
5
|
-
import { fetchFormDataAsLookup } from '../../../../functions/forms'
|
|
6
5
|
import { getElementGeneralizedProps } from '../../../../functions/forms/get-element-props'
|
|
7
6
|
import { useFormPreservedItemValues } from '../../../common/custom-hooks/use-preserved-form-items.hook'
|
|
8
|
-
import { useLocation } from 'react-router-dom'
|
|
9
7
|
import { IFormContext } from '../1-row'
|
|
10
8
|
import client, { clientCancelToken } from '../../../../api/client'
|
|
11
9
|
import { CancelToken } from 'axios'
|
|
10
|
+
import { useBreadcrumb } from '../../../common/custom-hooks/use-breadcrumb.hook'
|
|
11
|
+
import { Spin } from 'antd'
|
|
12
12
|
import {
|
|
13
13
|
IFilterByLinkedFormRelPath,
|
|
14
|
-
|
|
14
|
+
IFormJoin,
|
|
15
15
|
IFormLayoutFieldOption,
|
|
16
16
|
IOptionSourceConstant,
|
|
17
17
|
IOptionSourceReadFromDetails,
|
|
18
18
|
IRadioElement,
|
|
19
19
|
ISelectElement,
|
|
20
20
|
} from '../../../../types'
|
|
21
|
+
import { getIdEqualsQuery } from '../../../../functions'
|
|
21
22
|
|
|
22
23
|
export default function LayoutRenderer_FieldsWithOptions({
|
|
23
24
|
formContext,
|
|
@@ -25,77 +26,109 @@ export default function LayoutRenderer_FieldsWithOptions({
|
|
|
25
26
|
elementData,
|
|
26
27
|
isDisabled,
|
|
27
28
|
}: ILayoutRenderer_FieldsWithOptions) {
|
|
28
|
-
const { formRef,
|
|
29
|
-
const { state: locationState } = useLocation()
|
|
29
|
+
const { formRef, parentFormJoins, formId, formDataId } = formContext
|
|
30
30
|
const [options, setOptions] = useState<{ value: string; label: string }[]>([])
|
|
31
31
|
const props = getElementGeneralizedProps(elementData.props)
|
|
32
32
|
const cancelTokenRef = useRef<CancelToken | undefined>(undefined)
|
|
33
|
+
const { breadcrumbs } = useBreadcrumb()
|
|
34
|
+
const [loading, setLoading] = useState(true)
|
|
35
|
+
const isManyToManyPageRef = useRef(false)
|
|
33
36
|
|
|
34
37
|
const { inPreviewMode } = useFormPreservedItemValues(formRef)
|
|
35
38
|
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
// which has dataIds, which are the data of "Form Load Data" element.
|
|
39
|
-
// With these ids, it's filtering, so that the new data belongs to either of these.
|
|
40
|
-
// Example: Adding a Transaction from Case details, it filters parent (direct) data ids, which are Client ids. After filter, it ensures that the new transaction belongs to the Case it was in.
|
|
41
|
-
if (typeof formItem.name === 'string' && locationState?.parentInfo?.foreignKey === formItem.name)
|
|
42
|
-
return locationState.parentInfo.dataIds
|
|
39
|
+
const parentRelInfo = useMemo(() => {
|
|
40
|
+
if (breadcrumbs.length < 2) return { formJoins: [], formDataId: '' }
|
|
43
41
|
|
|
44
|
-
|
|
45
|
-
}, [locationState, formItem.name])
|
|
42
|
+
const breadcrumb = breadcrumbs[breadcrumbs.length - 2]
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
async (formId: number, field: string, filter?: Partial<IFormDataApiReqConfig>) => {
|
|
49
|
-
const formDataResData = await fetchFormDataAsLookup(formId, filter, cancelTokenRef.current)
|
|
50
|
-
let apiOptions = formDataResData
|
|
51
|
-
const parentDataIds = addNewDataParentIds[formId] ?? []
|
|
52
|
-
if (parentDataIds.length) {
|
|
53
|
-
apiOptions = apiOptions.filter((formData) => parentDataIds.includes(formData.id))
|
|
54
|
-
if (parentDataIds.length === 1) formRef?.setFieldValue(formItem.name, parentDataIds)
|
|
55
|
-
}
|
|
44
|
+
if (!!breadcrumb.manyToManyRelInfo) isManyToManyPageRef.current = true
|
|
56
45
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
value: formData.id,
|
|
60
|
-
label: formData[field],
|
|
61
|
-
})),
|
|
62
|
-
)
|
|
63
|
-
},
|
|
64
|
-
[addNewDataParentIds],
|
|
65
|
-
)
|
|
46
|
+
return { formJoins: breadcrumb.formJoins ?? [], formDataId: breadcrumb.formDataId ?? '' }
|
|
47
|
+
}, [breadcrumbs])
|
|
66
48
|
|
|
67
|
-
const
|
|
68
|
-
(
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
},
|
|
82
|
-
}
|
|
83
|
-
: {},
|
|
49
|
+
const fetchFormData = useCallback(
|
|
50
|
+
async (selectedFormId: number, selectedFormField: string) => {
|
|
51
|
+
const filteredJoins = (parentRelInfo.formJoins as IFormJoin[])
|
|
52
|
+
.filter((j) => j.formId !== selectedFormId)
|
|
53
|
+
.map((j, jIdx) => (jIdx === 0 ? { ...j, localField: j.localField.replace(`${selectedFormId}.`, '') } : j))
|
|
54
|
+
const lastJoin = filteredJoins.length ? filteredJoins[filteredJoins.length - 1] : null
|
|
55
|
+
|
|
56
|
+
client
|
|
57
|
+
.post(`/api/report/data/${selectedFormId}`, {
|
|
58
|
+
joins: filteredJoins,
|
|
59
|
+
match: JSON.stringify(
|
|
60
|
+
parentRelInfo.formDataId && !isManyToManyPageRef.current
|
|
61
|
+
? { DeletedDate: null, ...getIdEqualsQuery(lastJoin?.alias, parentRelInfo.formDataId) }
|
|
62
|
+
: { DeletedDate: null },
|
|
84
63
|
),
|
|
64
|
+
project: JSON.stringify({ value: '$_id', label: `$Data.${selectedFormField}` }),
|
|
85
65
|
})
|
|
66
|
+
.then((res) => {
|
|
67
|
+
if (res.status === 200) {
|
|
68
|
+
const { totalRecords, data } = res.data
|
|
69
|
+
if (totalRecords === 1) formRef?.setFieldValue(formItem.name, data[0]._id)
|
|
70
|
+
setOptions(data)
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
.finally(() => setLoading(false))
|
|
86
74
|
},
|
|
87
|
-
[
|
|
75
|
+
[parentRelInfo, formId, formRef],
|
|
88
76
|
)
|
|
89
77
|
|
|
78
|
+
const fetchLinkedFormData = useCallback((relationshipPath: IFilterByLinkedFormRelPath[] = []) => {
|
|
79
|
+
const lastPath = relationshipPath[relationshipPath.length - 1]
|
|
80
|
+
|
|
81
|
+
if (lastPath && lastPath.field) {
|
|
82
|
+
// parentInfo.formJoins is the DIRECT joins between the details page form and base form of the LoadFormData
|
|
83
|
+
const parentJoinOfLastPathIdx =
|
|
84
|
+
parentFormJoins?.findIndex((j) => j.localField.startsWith(lastPath.formId.toString())) ?? -1
|
|
85
|
+
|
|
86
|
+
if (parentJoinOfLastPathIdx > -1) {
|
|
87
|
+
const slicedJoinsForLastPath =
|
|
88
|
+
parentFormJoins
|
|
89
|
+
?.slice(parentJoinOfLastPathIdx)
|
|
90
|
+
.map((j, jIdx) =>
|
|
91
|
+
jIdx === 0 ? { ...j, localField: j.localField.replace(`${lastPath.formId}.`, '') } : j,
|
|
92
|
+
) ?? []
|
|
93
|
+
|
|
94
|
+
const lastJoin = slicedJoinsForLastPath.length
|
|
95
|
+
? slicedJoinsForLastPath[slicedJoinsForLastPath.length - 1]
|
|
96
|
+
: null
|
|
97
|
+
|
|
98
|
+
client
|
|
99
|
+
.post(`/api/report/data/${lastPath.formId}`, {
|
|
100
|
+
joins: slicedJoinsForLastPath,
|
|
101
|
+
match: JSON.stringify(
|
|
102
|
+
formDataId && lastJoin && !isManyToManyPageRef.current
|
|
103
|
+
? {
|
|
104
|
+
...getIdEqualsQuery(lastJoin.alias, formDataId),
|
|
105
|
+
DeletedDate: null,
|
|
106
|
+
}
|
|
107
|
+
: { DeletedDate: null },
|
|
108
|
+
),
|
|
109
|
+
project: slicedJoinsForLastPath.length
|
|
110
|
+
? JSON.stringify({ value: '$_id', label: `$Data.${lastPath.field}` })
|
|
111
|
+
: '',
|
|
112
|
+
})
|
|
113
|
+
.then((res) => {
|
|
114
|
+
if (res.status === 200) {
|
|
115
|
+
const { totalRecords, data } = res.data
|
|
116
|
+
if (totalRecords.length === 1) formRef?.setFieldValue(formItem.name, data[0]._id)
|
|
117
|
+
setOptions(data)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
.finally(() => setLoading(false))
|
|
121
|
+
} else setLoading(false)
|
|
122
|
+
} else setLoading(false)
|
|
123
|
+
}, [])
|
|
124
|
+
|
|
90
125
|
const fetchDetailsStaticOptions = useCallback((optionSource: IOptionSourceReadFromDetails) => {
|
|
91
126
|
const { formId, optionFieldPath } = optionSource
|
|
92
127
|
client
|
|
93
128
|
.post(
|
|
94
129
|
`/api/form/${formId}`,
|
|
95
130
|
{ project: JSON.stringify({ [optionFieldPath]: 1 }) },
|
|
96
|
-
{
|
|
97
|
-
cancelToken: cancelTokenRef.current,
|
|
98
|
-
},
|
|
131
|
+
{ cancelToken: cancelTokenRef.current },
|
|
99
132
|
)
|
|
100
133
|
.then((res) => {
|
|
101
134
|
if (res.status === 200) {
|
|
@@ -110,6 +143,7 @@ export default function LayoutRenderer_FieldsWithOptions({
|
|
|
110
143
|
setOptions(fieldOptionSource.options.map((op) => ({ value: op.id, label: op.value })))
|
|
111
144
|
}
|
|
112
145
|
})
|
|
146
|
+
.finally(() => setLoading(false))
|
|
113
147
|
}, [])
|
|
114
148
|
|
|
115
149
|
useEffect(() => {
|
|
@@ -118,44 +152,51 @@ export default function LayoutRenderer_FieldsWithOptions({
|
|
|
118
152
|
const axiosSource = clientCancelToken.source()
|
|
119
153
|
cancelTokenRef.current = axiosSource.token
|
|
120
154
|
|
|
121
|
-
if (props.optionSource.type === FieldElementOptionSourceEnum.Static)
|
|
155
|
+
if (props.optionSource.type === FieldElementOptionSourceEnum.Static) {
|
|
122
156
|
setOptions(props.optionSource.options.map((op: IFormLayoutFieldOption) => ({ value: op.id, label: op.value })))
|
|
123
|
-
|
|
124
|
-
|
|
157
|
+
setLoading(false)
|
|
158
|
+
} else if (!inPreviewMode) {
|
|
125
159
|
switch (props.optionSource.type) {
|
|
126
160
|
case FieldElementOptionSourceEnum.DynamicForm:
|
|
127
|
-
fetchFormData(props.optionSource.form.id, props.optionSource.form.field
|
|
128
|
-
dynamicFilter: JSON.stringify(deletedDateFilter),
|
|
129
|
-
})
|
|
161
|
+
fetchFormData(props.optionSource.form.id, props.optionSource.form.field)
|
|
130
162
|
return
|
|
131
163
|
case FieldElementOptionSourceEnum.LinkedForm:
|
|
132
164
|
fetchLinkedFormData(props.filter.relationshipPath)
|
|
165
|
+
|
|
133
166
|
return
|
|
134
167
|
case FieldElementOptionSourceEnum.ReadFromDetails:
|
|
135
168
|
fetchDetailsStaticOptions(props.optionSource)
|
|
136
169
|
return
|
|
137
170
|
default:
|
|
171
|
+
setLoading(false)
|
|
138
172
|
return
|
|
139
173
|
}
|
|
140
|
-
}
|
|
174
|
+
} else setLoading(false)
|
|
141
175
|
|
|
142
176
|
return () => axiosSource.cancel()
|
|
143
|
-
}, [props.optionSource, inPreviewMode])
|
|
177
|
+
}, [props.optionSource, inPreviewMode, fetchLinkedFormData, fetchFormData])
|
|
144
178
|
|
|
145
179
|
return (
|
|
146
|
-
<
|
|
147
|
-
|
|
148
|
-
{
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
180
|
+
<div className="relative">
|
|
181
|
+
<LayoutRenderer_FieldElement
|
|
182
|
+
fields={[
|
|
183
|
+
{
|
|
184
|
+
...props,
|
|
185
|
+
type: elementData.elementType,
|
|
186
|
+
name: formItem.name,
|
|
187
|
+
validations: elementData.validations,
|
|
188
|
+
options,
|
|
189
|
+
disabled: isDisabled,
|
|
190
|
+
},
|
|
191
|
+
]}
|
|
192
|
+
formRef={formRef}
|
|
193
|
+
/>
|
|
194
|
+
{loading && (
|
|
195
|
+
<div className="absolute bottom-0 h-[32px] w-full rounded-md bg-black bg-opacity-10 flex items-center justify-end pr-2 gap-2 text-12 italic">
|
|
196
|
+
<Spin size="small" />
|
|
197
|
+
</div>
|
|
198
|
+
)}
|
|
199
|
+
</div>
|
|
159
200
|
)
|
|
160
201
|
}
|
|
161
202
|
|
|
@@ -1,27 +1,25 @@
|
|
|
1
1
|
import { IElementBaseProps } from '.'
|
|
2
|
-
import { useCallback, useEffect,
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
3
3
|
import FormDataListTableComponent, { IDataListLayoutConfig } from '../../1-list/table'
|
|
4
4
|
import FormDataListSkeleton_Table from '../../../common/loading-skeletons/table'
|
|
5
|
+
import { getIdEqualsQuery, getProjectionKey } from '../../../../functions'
|
|
5
6
|
import { NEW_FORM_DATA_IDENTIFIER } from '../../../../constants'
|
|
6
|
-
import { FilterConfigTypeEnum } from '../../../../enums'
|
|
7
7
|
import { cancelableClient } from '../../../../api/client'
|
|
8
8
|
import { IFormContext } from '../1-row'
|
|
9
9
|
import {
|
|
10
|
-
IDataListParentInfo,
|
|
11
10
|
IFormDataListData,
|
|
12
11
|
IFormDataLoadElementProps,
|
|
13
12
|
IFormJoin,
|
|
14
13
|
IFormRelationshipConfig,
|
|
15
14
|
IFormSchema,
|
|
16
|
-
IRelationshipForm,
|
|
17
15
|
} from '../../../../types'
|
|
18
16
|
|
|
19
17
|
export default function LayoutRenderer_LoadFormData({ formContext, elementProps }: ILayoutRenderer_LoadFormData) {
|
|
20
18
|
const { formId, formRef, formDataId } = formContext
|
|
21
|
-
const { joins } = elementProps
|
|
22
|
-
const lastChildInfo = useRef<{ formName: string;
|
|
19
|
+
const { joins, baseFormId, displayConfigFormId, displayConfigId } = elementProps
|
|
20
|
+
const lastChildInfo = useRef<{ formName: string; parentFormJoins: IFormJoin[] }>({
|
|
23
21
|
formName: '',
|
|
24
|
-
|
|
22
|
+
parentFormJoins: [],
|
|
25
23
|
})
|
|
26
24
|
const [loadings, setLoadings] = useState({ initial: true, data: true })
|
|
27
25
|
const [listLayoutConfig, setListLayoutConfig] = useState<IDataListLayoutConfig>()
|
|
@@ -30,27 +28,22 @@ export default function LayoutRenderer_LoadFormData({ formContext, elementProps
|
|
|
30
28
|
const dataProjectRef = useRef<{ [key: string]: string }>({})
|
|
31
29
|
const reportDataApiCancelFuncRef = useRef<() => void | undefined>(undefined)
|
|
32
30
|
|
|
33
|
-
const [defaultJoins, baseForm, defaultFilter] = useMemo(
|
|
34
|
-
() => getDefaultReqDataParams(joins, formId, formDataId),
|
|
35
|
-
[joins, formId, formDataId],
|
|
36
|
-
)
|
|
37
|
-
|
|
38
31
|
const fetchDataList = useCallback(
|
|
39
32
|
(headerAppliedFilters?: string) => {
|
|
40
|
-
if (!
|
|
33
|
+
if (!baseFormId) return
|
|
41
34
|
setLoadings((c) => ({ ...c, data: true }))
|
|
42
35
|
|
|
43
|
-
const matchFilters: { [key: string]: any }[] = [
|
|
36
|
+
const matchFilters: { [key: string]: any }[] = [getIdEqualsQuery(formId, formDataId)]
|
|
44
37
|
if (headerAppliedFilters) matchFilters.push(JSON.parse(headerAppliedFilters))
|
|
45
38
|
|
|
46
|
-
const { request, cancel } = cancelableClient.post(`/api/report/data/${
|
|
47
|
-
joins
|
|
39
|
+
const { request, cancel } = cancelableClient.post(`/api/report/data/${baseFormId}`, {
|
|
40
|
+
joins,
|
|
48
41
|
match: matchFilters.length
|
|
49
42
|
? JSON.stringify(matchFilters.length === 1 ? matchFilters[0] : { $and: matchFilters })
|
|
50
43
|
: '',
|
|
51
44
|
project: JSON.stringify({
|
|
52
45
|
...dataProjectRef.current,
|
|
53
|
-
...
|
|
46
|
+
...joins.reduce((curr, j) => ({ ...curr, [`${j.formId}_id`]: `$${j.formId}._id` }), {}),
|
|
54
47
|
}),
|
|
55
48
|
})
|
|
56
49
|
reportDataApiCancelFuncRef.current = cancel
|
|
@@ -58,73 +51,61 @@ export default function LayoutRenderer_LoadFormData({ formContext, elementProps
|
|
|
58
51
|
request
|
|
59
52
|
.then((res) => {
|
|
60
53
|
if (res.status === 200) {
|
|
61
|
-
const resData = res.data.data
|
|
62
|
-
|
|
63
|
-
if (!headerAppliedFilters)
|
|
64
|
-
// this is used for filtering the linked form dropdown options, so that the linked form dropdowns only show the relevant data
|
|
65
|
-
lastChildInfo.current = {
|
|
66
|
-
...lastChildInfo.current,
|
|
67
|
-
parentInfo: {
|
|
68
|
-
...lastChildInfo.current.parentInfo,
|
|
69
|
-
dataIds: defaultJoins.reduce(
|
|
70
|
-
(curr, j) => ({
|
|
71
|
-
...curr,
|
|
72
|
-
[j.formId]: [
|
|
73
|
-
...new Set(
|
|
74
|
-
resData.reduce(
|
|
75
|
-
(dataCurr: string[], data: IFormDataListData) => [...dataCurr, data[`${j.formId}_id`]],
|
|
76
|
-
[],
|
|
77
|
-
),
|
|
78
|
-
),
|
|
79
|
-
],
|
|
80
|
-
}),
|
|
81
|
-
{},
|
|
82
|
-
),
|
|
83
|
-
},
|
|
84
|
-
}
|
|
85
|
-
|
|
86
54
|
setDataList({ data: res.data.data, total: res.data.totalRecords })
|
|
87
55
|
}
|
|
88
56
|
})
|
|
89
57
|
.finally(() => setLoadings({ initial: false, data: false }))
|
|
90
58
|
},
|
|
91
|
-
[
|
|
59
|
+
[joins, baseFormId],
|
|
92
60
|
)
|
|
93
61
|
|
|
94
62
|
useEffect(() => {
|
|
95
|
-
if (!
|
|
63
|
+
if (!displayConfigFormId || !formDataId || formDataId === NEW_FORM_DATA_IDENTIFIER) return
|
|
96
64
|
|
|
97
|
-
|
|
65
|
+
// fetching display config (base form can be different than display config form)
|
|
66
|
+
const { request, cancel } = cancelableClient.get(`/api/form/${displayConfigFormId}`)
|
|
98
67
|
request.then((res) => {
|
|
99
68
|
if (res.status === 200) {
|
|
100
69
|
const parsedData: IFormSchema | null = JSON.parse(res.data.data)
|
|
101
70
|
if (!parsedData) return
|
|
102
71
|
const { relationships = [] } = parsedData
|
|
103
72
|
|
|
104
|
-
const relationship: IFormRelationshipConfig | undefined = relationships.find(
|
|
105
|
-
(
|
|
73
|
+
const relationship: IFormRelationshipConfig | undefined = relationships.find((r: IFormRelationshipConfig) =>
|
|
74
|
+
r.displayConfigs?.some((dc) => dc.id === displayConfigId),
|
|
106
75
|
)
|
|
107
|
-
|
|
108
|
-
const displayConfig = relationship?.displayConfigs?.find((config) => config.id === baseForm.displayConfigId)
|
|
76
|
+
const displayConfig = relationship?.displayConfigs?.find((config) => config.id === displayConfigId)
|
|
109
77
|
if (!displayConfig || displayConfig.isRenderChildForm) return
|
|
110
78
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
parentInfo: { ...lastChildInfo.current.parentInfo, foreignKey: relationship!.foreignKey },
|
|
114
|
-
}
|
|
79
|
+
const isManyToManyRelationship = displayConfigFormId !== baseFormId
|
|
80
|
+
lastChildInfo.current = { formName: res.data.name, parentFormJoins: joins }
|
|
115
81
|
|
|
116
82
|
if (displayConfig.config) {
|
|
117
83
|
if (Array.isArray(displayConfig.config.columns))
|
|
118
|
-
dataProjectRef.current = displayConfig.config.columns.reduce(
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
84
|
+
dataProjectRef.current = displayConfig.config.columns.reduce((curr, c) => {
|
|
85
|
+
if (!c.key) return curr
|
|
86
|
+
|
|
87
|
+
if (isManyToManyRelationship) {
|
|
88
|
+
// if the key starts with id, it means it's reading a field from other form joins that are already configured
|
|
89
|
+
// example: form id 145 one-to-many 145 many-to-many 146, and 149 is the rel form.
|
|
90
|
+
// the base form is 149, joined 145 (displayConfigFormId below) and 146. In this case, to read fields from 145, it appends 145 at front to make it 145.Data{...}
|
|
91
|
+
// However, to display data from 144, we can't append 145, whose keys already come with 144.Data.{...}. That's why it's checking if it starts with number or not
|
|
92
|
+
return {
|
|
93
|
+
...curr,
|
|
94
|
+
[getProjectionKey(c.key)]: c.key.match(/^\d/) ? `$${c.key}` : `$${displayConfigFormId}.${c.key}`,
|
|
95
|
+
// [getProjectionKey(c.key)]: `$${c.key}`,
|
|
96
|
+
relDataId: '$_id',
|
|
97
|
+
_id: `$${displayConfigFormId}._id`,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { ...curr, [getProjectionKey(c.key)]: `$${c.key}` }
|
|
102
|
+
}, {})
|
|
122
103
|
|
|
123
104
|
setListLayoutConfig({
|
|
124
|
-
configForFormId:
|
|
105
|
+
configForFormId: baseFormId,
|
|
125
106
|
dataListConfig: {
|
|
126
107
|
...displayConfig.config,
|
|
127
|
-
columns: displayConfig.config.columns.map((c) => (c.key ? { ...c, key: c.key
|
|
108
|
+
columns: displayConfig.config.columns.map((c) => (c.key ? { ...c, key: getProjectionKey(c.key) } : c)),
|
|
128
109
|
},
|
|
129
110
|
})
|
|
130
111
|
|
|
@@ -137,13 +118,21 @@ export default function LayoutRenderer_LoadFormData({ formContext, elementProps
|
|
|
137
118
|
cancel()
|
|
138
119
|
reportDataApiCancelFuncRef.current?.()
|
|
139
120
|
}
|
|
140
|
-
}, [
|
|
121
|
+
}, [displayConfigFormId, baseFormId, displayConfigId, formDataId, fetchDataList])
|
|
141
122
|
|
|
142
123
|
const dataListHeaderContext = {
|
|
143
|
-
formId:
|
|
124
|
+
formId: baseFormId,
|
|
144
125
|
formDataId,
|
|
145
126
|
formRef,
|
|
146
127
|
...lastChildInfo.current,
|
|
128
|
+
manyToManyRelInfo:
|
|
129
|
+
displayConfigFormId !== baseFormId && formId
|
|
130
|
+
? {
|
|
131
|
+
middleFormId: baseFormId,
|
|
132
|
+
currentFormId: formId,
|
|
133
|
+
otherFormId: displayConfigFormId,
|
|
134
|
+
}
|
|
135
|
+
: null,
|
|
147
136
|
onCustomFunctionCall: () => {},
|
|
148
137
|
}
|
|
149
138
|
|
|
@@ -168,52 +157,3 @@ type ILayoutRenderer_LoadFormData = {
|
|
|
168
157
|
formContext: IFormContext
|
|
169
158
|
elementProps: IFormDataLoadElementProps
|
|
170
159
|
} & IElementBaseProps
|
|
171
|
-
|
|
172
|
-
const getDefaultReqDataParams = (
|
|
173
|
-
joins: IRelationshipForm[],
|
|
174
|
-
formId?: number,
|
|
175
|
-
formDataId?: string,
|
|
176
|
-
): [IFormJoin[], IRelationshipForm | undefined, { [key: string]: any }[]] => {
|
|
177
|
-
if (!joins || !Array.isArray(joins) || joins.length === 0 || !formId) return [[], undefined, []]
|
|
178
|
-
|
|
179
|
-
const baseForm = joins[joins.length - 1]
|
|
180
|
-
const reversedJoins = [...joins].reverse()
|
|
181
|
-
|
|
182
|
-
const childrenJoins: IFormJoin[] = []
|
|
183
|
-
const filters: { [key: string]: any }[] = [{ $expr: { $eq: [`$${formId}._id`, { $toObjectId: formDataId }] } }]
|
|
184
|
-
|
|
185
|
-
if (
|
|
186
|
-
baseForm.filterConfig &&
|
|
187
|
-
baseForm.filterConfig.type === FilterConfigTypeEnum.Custom &&
|
|
188
|
-
baseForm.filterConfig.config
|
|
189
|
-
)
|
|
190
|
-
filters.push(
|
|
191
|
-
JSON.parse(
|
|
192
|
-
JSON.stringify(baseForm.filterConfig.config).replaceAll(`${baseForm.formId}.`, '').replaceAll('@', '$'),
|
|
193
|
-
),
|
|
194
|
-
)
|
|
195
|
-
|
|
196
|
-
let prevJoinAlias = ''
|
|
197
|
-
reversedJoins.forEach((j, jIdx) => {
|
|
198
|
-
const nextJoin = reversedJoins[jIdx + 1]
|
|
199
|
-
const localField = `${prevJoinAlias}Data.${j.foreignKey}`
|
|
200
|
-
prevJoinAlias = `${nextJoin ? nextJoin.formId : formId}.`
|
|
201
|
-
|
|
202
|
-
const joinObj = {
|
|
203
|
-
formId: nextJoin ? nextJoin.formId : formId,
|
|
204
|
-
localField,
|
|
205
|
-
foreignField: '_id',
|
|
206
|
-
alias: nextJoin ? nextJoin.formId.toString() : formId.toString(),
|
|
207
|
-
}
|
|
208
|
-
childrenJoins.push(joinObj)
|
|
209
|
-
|
|
210
|
-
if (j.filterConfig && j.filterConfig.type === FilterConfigTypeEnum.Custom && j.filterConfig.config) {
|
|
211
|
-
let stringifiedFilter = JSON.stringify(j.filterConfig.config)
|
|
212
|
-
|
|
213
|
-
const filterConfig = JSON.parse(stringifiedFilter.replaceAll('@', '$'))
|
|
214
|
-
filters.push(filterConfig)
|
|
215
|
-
}
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
return [childrenJoins, baseForm, filters]
|
|
219
|
-
}
|
|
@@ -1,12 +1,5 @@
|
|
|
1
1
|
import { Divider } from 'antd'
|
|
2
2
|
import { memo, ReactElement, ReactNode, useEffect, useMemo } from 'react'
|
|
3
|
-
import {
|
|
4
|
-
IButtonElementProps,
|
|
5
|
-
IFormDataLoadElementProps,
|
|
6
|
-
IDndLayoutElement,
|
|
7
|
-
IFormLayoutElementConditions,
|
|
8
|
-
IReadFieldDataElementProps,
|
|
9
|
-
} from '../../../../types'
|
|
10
3
|
import { getElementGeneralizedProps } from '../../../../functions/forms/get-element-props'
|
|
11
4
|
import { ElementTypeEnum, TextElementTypeEnum } from '../../../../enums'
|
|
12
5
|
import { LayoutRenderer_FieldElement } from './2-field-element'
|
|
@@ -22,6 +15,14 @@ import LayoutRenderer_LoadFormData from './9-form-data-render'
|
|
|
22
15
|
import { IFormContext } from '../1-row'
|
|
23
16
|
import LayoutRenderer_CurrencyField from './10-currency'
|
|
24
17
|
import useGetCurrentBreakpoint from '../../../common/custom-hooks/use-window-width.hook'
|
|
18
|
+
import LayoutRenderer_Breadcrumb from './11-breadcrumb'
|
|
19
|
+
import {
|
|
20
|
+
IButtonElementProps,
|
|
21
|
+
IFormDataLoadElementProps,
|
|
22
|
+
IDndLayoutElement,
|
|
23
|
+
IFormLayoutElementConditions,
|
|
24
|
+
IReadFieldDataElementProps,
|
|
25
|
+
} from '../../../../types'
|
|
25
26
|
|
|
26
27
|
function LayoutRendererElementWrapper({
|
|
27
28
|
hideElement,
|
|
@@ -83,7 +84,14 @@ function LayoutRendererElement({
|
|
|
83
84
|
case ElementTypeEnum.RichTextEditor:
|
|
84
85
|
if (!key) return <span className="text-warning">Rich text editor field could not render due to missing KEY!</span>
|
|
85
86
|
|
|
86
|
-
return
|
|
87
|
+
return (
|
|
88
|
+
<LayoutRenderer_RichEditor
|
|
89
|
+
elementProps={props}
|
|
90
|
+
validations={validations}
|
|
91
|
+
formContext={formContext}
|
|
92
|
+
{...elementBaseProps}
|
|
93
|
+
/>
|
|
94
|
+
)
|
|
87
95
|
|
|
88
96
|
case ElementTypeEnum.ReCaptcha:
|
|
89
97
|
if (!key) return <span className="text-warning">ReCaptcha field could not render due to missing field KEY!</span>
|
|
@@ -93,7 +101,14 @@ function LayoutRendererElement({
|
|
|
93
101
|
case ElementTypeEnum.Signature:
|
|
94
102
|
if (!key) return <span className="text-warning">Signature field could not render due to missing KEY!</span>
|
|
95
103
|
|
|
96
|
-
return
|
|
104
|
+
return (
|
|
105
|
+
<LayoutRenderer_Signature
|
|
106
|
+
elementProps={props}
|
|
107
|
+
validations={validations}
|
|
108
|
+
formContext={formContext}
|
|
109
|
+
{...elementBaseProps}
|
|
110
|
+
/>
|
|
111
|
+
)
|
|
97
112
|
|
|
98
113
|
case ElementTypeEnum.FileUpload:
|
|
99
114
|
if (!key) return <span className="text-warning">File upload field could not render due to missing KEY!</span>
|
|
@@ -159,6 +174,9 @@ function LayoutRendererElement({
|
|
|
159
174
|
case ElementTypeEnum.CurrencyInput:
|
|
160
175
|
return <LayoutRenderer_CurrencyField elementData={elementData} formContext={formContext} {...elementBaseProps} />
|
|
161
176
|
|
|
177
|
+
case ElementTypeEnum.Breadcrumb:
|
|
178
|
+
return <LayoutRenderer_Breadcrumb />
|
|
179
|
+
|
|
162
180
|
default:
|
|
163
181
|
return (
|
|
164
182
|
<LayoutRenderer_FieldElement
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { REPORT_EMPTY_CELL_CONTENT } from '../../../constants'
|
|
2
|
-
import { renderData } from '../../../functions'
|
|
2
|
+
import { getProjectionKey, renderData } from '../../../functions'
|
|
3
3
|
import {
|
|
4
4
|
AlignTypeEnum,
|
|
5
5
|
ElementTypeEnum,
|
|
@@ -67,7 +67,7 @@ function groupData(
|
|
|
67
67
|
const currentGroupConfig = groups[0]
|
|
68
68
|
const groupingType: ReportGroupingTypeEnum = currentGroupConfig.type || ReportGroupingTypeEnum.RowSpan
|
|
69
69
|
// Convert dependencyField (e.g. "81.Data.name") to underscore format (e.g. "81_Data_name").
|
|
70
|
-
const effectiveField = currentGroupConfig.dependencyField
|
|
70
|
+
const effectiveField = getProjectionKey(currentGroupConfig.dependencyField)
|
|
71
71
|
|
|
72
72
|
// Group data by the effective field.
|
|
73
73
|
const groupsMap = new Map<string, any[]>()
|
|
@@ -338,10 +338,7 @@ function evaluateSummary(dataSubset: any[], evaluationConfig: any): number {
|
|
|
338
338
|
const evaluation = evaluationConfig.evaluations[0]
|
|
339
339
|
let sum = evaluation.defaultValue || 0
|
|
340
340
|
if (evaluation.operator === EvaluationOperatorsEnum.Add) {
|
|
341
|
-
const fieldKey = (typeof evaluation.field === 'string' ? evaluation.field : evaluation.field.field)
|
|
342
|
-
/\./g,
|
|
343
|
-
'_',
|
|
344
|
-
)
|
|
341
|
+
const fieldKey = getProjectionKey(typeof evaluation.field === 'string' ? evaluation.field : evaluation.field.field)
|
|
345
342
|
dataSubset.forEach((item) => {
|
|
346
343
|
const num = parseFloat(item[fieldKey])
|
|
347
344
|
if (!isNaN(num)) {
|