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.
Files changed (54) hide show
  1. package/package.json +3 -2
  2. package/src/api/client.ts +1 -1
  3. package/src/api/user.ts +7 -8
  4. package/src/components/common/custom-hooks/use-breadcrumb.hook.ts +90 -0
  5. package/src/components/common/custom-hooks/use-check-element-conditions.hook.ts +3 -3
  6. package/src/components/common/custom-hooks/use-find-dynamic-form.hook.ts +30 -11
  7. package/src/components/common/custom-hooks/use-login-handler.ts +81 -4
  8. package/src/components/common/custom-hooks/use-many-to-many-connector.hook.ts +30 -0
  9. package/src/components/common/not-found.tsx +21 -0
  10. package/src/components/common/section-panel.tsx +42 -0
  11. package/src/components/companies/1-authenticated/change-password.tsx +110 -0
  12. package/src/components/companies/2-unauthenticated/reset-password.tsx +128 -0
  13. package/src/components/companies/index.tsx +2 -0
  14. package/src/components/form/1-list/index.tsx +37 -38
  15. package/src/components/form/1-list/table-header.tsx +6 -6
  16. package/src/components/form/1-list/table.tsx +10 -12
  17. package/src/components/form/2-details/index.tsx +63 -41
  18. package/src/components/form/layout-renderer/1-row/index.tsx +12 -5
  19. package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +59 -89
  20. package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-button-navigate.hook.tsx +88 -0
  21. package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-create-data.hook.ts +22 -23
  22. package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-generate-report.hook.tsx +3 -4
  23. package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-save-data.hook.ts +2 -2
  24. package/src/components/form/layout-renderer/3-element/11-breadcrumb.tsx +30 -0
  25. package/src/components/form/layout-renderer/3-element/2-field-element.tsx +4 -1
  26. package/src/components/form/layout-renderer/3-element/4-rich-text-editor.tsx +27 -2
  27. package/src/components/form/layout-renderer/3-element/6-signature.tsx +5 -1
  28. package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +115 -74
  29. package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +50 -110
  30. package/src/components/form/layout-renderer/3-element/index.tsx +27 -9
  31. package/src/components/modals/report-filters.modal/helper-functions.ts +3 -6
  32. package/src/constants.ts +62 -45
  33. package/src/enums/form.enum.ts +5 -3
  34. package/src/enums/index.ts +9 -3
  35. package/src/{global-helpers → functions}/cookie-handler.ts +9 -10
  36. package/src/functions/forms/breadcrumb-handlers.ts +21 -0
  37. package/src/functions/forms/create-form-rules.ts +4 -1
  38. package/src/functions/forms/data-render-functions.tsx +5 -4
  39. package/src/functions/forms/extended-json-handlers.ts +56 -0
  40. package/src/functions/forms/index.ts +17 -11
  41. package/src/functions/index.ts +2 -1
  42. package/src/functions/reports/index.tsx +2 -1
  43. package/src/types/companies/site-layout/authenticated/index.tsx +23 -14
  44. package/src/types/forms/data-list/index.ts +0 -7
  45. package/src/types/forms/index.ts +1 -0
  46. package/src/types/forms/layout-elements/button.ts +11 -3
  47. package/src/types/forms/layout-elements/data-render-config.ts +1 -0
  48. package/src/types/forms/layout-elements/index.ts +12 -2
  49. package/src/types/forms/layout-elements/sanitization.ts +6 -1
  50. package/src/types/forms/relationship/index.ts +12 -1
  51. package/src/types/index.ts +2 -0
  52. package/src/functions/forms/json-handlers.ts +0 -19
  53. package/src/global-helpers/constants.ts +0 -2
  54. 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
- IFormDataApiReqConfig,
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, linkedParentsDataIds } = formContext
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 addNewDataParentIds = useMemo(() => {
37
- // when create new data button is clicked from "Form Load Data" element header, it passes parentInfo
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
- return {}
45
- }, [locationState, formItem.name])
42
+ const breadcrumb = breadcrumbs[breadcrumbs.length - 2]
46
43
 
47
- const fetchFormData = useCallback(
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
- setOptions(
58
- apiOptions.map((formData) => ({
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 fetchLinkedFormData = useCallback(
68
- (relationshipPath: IFilterByLinkedFormRelPath[] = []) => {
69
- const lastPath = relationshipPath[relationshipPath.length - 1]
70
-
71
- if (lastPath && lastPath.field)
72
- fetchFormData(lastPath.formId, lastPath.field, {
73
- dynamicFilter: JSON.stringify(
74
- linkedParentsDataIds && Array.isArray(linkedParentsDataIds[lastPath.formId])
75
- ? {
76
- $expr: {
77
- $in: [
78
- '$_id',
79
- linkedParentsDataIds[lastPath.formId].map((formDataId) => ({ $toObjectId: formDataId })),
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
- [linkedParentsDataIds],
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
- else if (!inPreviewMode) {
124
- const deletedDateFilter = { DeletedDate: null }
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
- <LayoutRenderer_FieldElement
147
- fields={[
148
- {
149
- ...props,
150
- type: elementData.elementType,
151
- name: formItem.name,
152
- validations: elementData.validations,
153
- options,
154
- disabled: isDisabled,
155
- },
156
- ]}
157
- formRef={formRef}
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, useMemo, useRef, useState } from 'react'
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; parentInfo: IDataListParentInfo }>({
19
+ const { joins, baseFormId, displayConfigFormId, displayConfigId } = elementProps
20
+ const lastChildInfo = useRef<{ formName: string; parentFormJoins: IFormJoin[] }>({
23
21
  formName: '',
24
- parentInfo: { foreignKey: '', dataIds: {} },
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 (!baseForm) return
33
+ if (!baseFormId) return
41
34
  setLoadings((c) => ({ ...c, data: true }))
42
35
 
43
- const matchFilters: { [key: string]: any }[] = [...defaultFilter]
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/${baseForm.formId}`, {
47
- joins: defaultJoins,
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
- ...defaultJoins.reduce((curr, j) => ({ ...curr, [`${j.formId}_id`]: `$${j.formId}._id` }), {}),
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
- [defaultJoins, baseForm, defaultFilter],
59
+ [joins, baseFormId],
92
60
  )
93
61
 
94
62
  useEffect(() => {
95
- if (!baseForm || !formDataId || formDataId === NEW_FORM_DATA_IDENTIFIER) return
63
+ if (!displayConfigFormId || !formDataId || formDataId === NEW_FORM_DATA_IDENTIFIER) return
96
64
 
97
- const { request, cancel } = cancelableClient.get(`/api/form/${baseForm.formId}`)
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
- (r: IFormRelationshipConfig) => r.foreignKey === baseForm.foreignKey,
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
- lastChildInfo.current = {
112
- formName: res.data.name,
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
- (curr, c) => (c.key ? { ...curr, [c.key.replace(/\./g, '_')]: `$${c.key}` } : curr),
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: baseForm.formId,
105
+ configForFormId: baseFormId,
125
106
  dataListConfig: {
126
107
  ...displayConfig.config,
127
- columns: displayConfig.config.columns.map((c) => (c.key ? { ...c, key: c.key.replace(/\./g, '_') } : c)),
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
- }, [baseForm, formDataId, fetchDataList])
121
+ }, [displayConfigFormId, baseFormId, displayConfigId, formDataId, fetchDataList])
141
122
 
142
123
  const dataListHeaderContext = {
143
- formId: baseForm?.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 <LayoutRenderer_RichEditor elementProps={props} formContext={formContext} {...elementBaseProps} />
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 <LayoutRenderer_Signature elementProps={props} formContext={formContext} {...elementBaseProps} />
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.replace(/\./g, '_')
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).replace(
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)) {