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
@@ -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
+ }
@@ -1,4 +1,6 @@
1
1
  export * from './1-authenticated'
2
2
  export * from './2-unauthenticated'
3
3
  export * from './3-config-provider'
4
+ export * from './1-authenticated/change-password'
4
5
  export * from './2-unauthenticated/forgot-password'
6
+ export * from './2-unauthenticated/reset-password'
@@ -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 { IFormSchema, IFormJoin, IFormDataListData } from '../../../types'
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, formName, companyKey, onCustomFunctionCall }
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.get(`/api/form/${formId}`)
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 parsedData: IFormSchema | null = JSON.parse(res.data.data)
42
- if (parsedData) {
43
- const { columns, formJoins, pagination, defaultFilter, defaultSorter } = parsedData.dataListConfig
44
-
45
- if (Array.isArray(columns))
46
- dataProjectRef.current = columns.reduce(
47
- (curr, c) => (c.key ? { ...curr, [c.key.replace(/\./g, '_')]: `$${c.key}` } : curr),
48
- {},
49
- )
50
- if (Array.isArray(formJoins)) formJoinsRef.current = formJoins
51
-
52
- const defaultReqData: IDataListReqData = {}
53
- if (!pagination?.hasNoPagination) {
54
- defaultReqData.current = 1
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, formName, parentInfo, formDataId, onCustomFunctionCall } = headerLayoutContext
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, formName, formId, formDataId, linkedParentsDataIds: parentInfo?.dataIds }),
112
- [filtersFormRef, formName, formId, formDataId, parentInfo],
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, stateToPass: { parentInfo } }}
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({ $expr: { $eq: [`$${lastRel.formId}._id`, { $toObjectId: value }] } }),
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 { FormInstance, Table } from 'antd'
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 { IDataListParentInfo, IFormDataListConfig, IFormDataListData, IFormDataTableColumn } from '../../../types'
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
- const { formId } = headerLayoutContext
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
- [formId, headerLayoutContext, layoutsConfigs],
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
- formDataId?: string
176
- formName?: string
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 { extractDateFields, queryParamsToObject } from '../../../functions/forms'
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
- // Apply initialValues
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, dateFields: string[] = []) => {
64
- if (formDataId === NEW_FORM_DATA_IDENTIFIER) setLoadings((c) => ({ ...c, data: false }))
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 = parseJSON(jsonData)
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
- console.log('FORM DATA FETCH (PARSED)', parsedFormData)
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 = parseJSON(res.data.data)
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, formName, companyKey }),
128
- [formDataId, formDataRef, formName, formId, formKey, companyKey],
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
- <Form layout="vertical" name="dynamic_form_data_form" form={formDataRef}>
135
- {layout.map((row, rowIdx) => (
136
- <LayoutRendererRow
137
- key={rowIdx}
138
- rowData={row}
139
- formContext={formContext}
140
- elements={layoutConfig.elements}
141
- renderButton={(btnProps, conditions) => (
142
- <DynamicFormButtonRender
143
- displayStateProps={{ btnProps, conditions, defaultDisabled: true }}
144
- formContext={formContext}
145
- onCustomFunctionCall={onCustomFunctionCall}
146
- />
147
- )}
148
- />
149
- ))}
150
- </Form>
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 { IButtonElementProps, IDndLayoutElement, IFormLayoutElementConditions, IDndLayoutRow } from '../../../../types'
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
- linkedParentsDataIds?: { [key: string]: string[] }
105
+ parentFormJoins?: IFormJoin[]
106
+ manyToManyRelInfo?: { middleFormId: number; currentFormId: number; otherFormId: number } | null
100
107
  }