form-craft-package 1.1.9 → 1.1.11

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