form-craft-package 1.5.1 → 1.5.3

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.5.1",
3
+ "version": "1.5.3",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -10,7 +10,6 @@ export const useFormPreservedItemValues = (formRef?: FormInstance): IFormValues
10
10
  const isGeneratingPdf = Form.useWatch(FormPreservedItemKeys.IsGeneratingPDF, { form: formRef, preserve: true })
11
11
  const hasSignature = Form.useWatch(FormPreservedItemKeys.HasSignature, { form: formRef, preserve: true })
12
12
  const inPreviewMode = Form.useWatch(FormPreservedItemKeys.InPreviewMode, { form: formRef, preserve: true })
13
- const templateReports = Form.useWatch(FormPreservedItemKeys.TemplateReports, { form: formRef, preserve: true })
14
13
  const isPublic = Form.useWatch(FormPreservedItemKeys.IsPublic, { form: formRef, preserve: true })
15
14
  const submissionPdfConfig = Form.useWatch(FormPreservedItemKeys.SubmissionPdfConfig, {
16
15
  form: formRef,
@@ -27,7 +26,6 @@ export const useFormPreservedItemValues = (formRef?: FormInstance): IFormValues
27
26
  [FormPreservedItemKeys.IsGeneratingPDF]: isGeneratingPdf,
28
27
  [FormPreservedItemKeys.HasSignature]: hasSignature,
29
28
  [FormPreservedItemKeys.InPreviewMode]: inPreviewMode,
30
- [FormPreservedItemKeys.TemplateReports]: templateReports,
31
29
  [FormPreservedItemKeys.IsPublic]: isPublic,
32
30
  }
33
31
  }
@@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
2
2
  import { IDataListParentInfo, IFormData, IFormDataListConfig, IFormLayoutRow, ITableColumn } from '../../../types'
3
3
  import FormDataListHeaderComponent from './table-header'
4
4
  import useDebounced from '../../common/custom-hooks/use-debounce.hook'
5
- import { Table } from 'antd'
5
+ import { FormInstance, Table } from 'antd'
6
6
  import { FilterConfigTypeEnum, FormDataListViewTypeEnum, MongoDbSortOrderEnum } from '../../../enums'
7
7
  import { ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
8
8
  import { SorterResult } from 'antd/es/table/interface'
@@ -39,7 +39,7 @@ export default function FormDataListTableComponent({
39
39
  pagination: DEFAULT_PAGINATION,
40
40
  })
41
41
 
42
- const { attachmentBaseUrl, userId, formId, formName } = constantValues
42
+ const { attachmentBaseUrl, userId, formId, formName, formRef } = constantValues
43
43
 
44
44
  useEffect(() => {
45
45
  setIsHandlingSchema(true)
@@ -68,7 +68,7 @@ export default function FormDataListTableComponent({
68
68
  if (elementsWithOptions_key.length > 0)
69
69
  optionKeyValuePair = await extractElementsOptions(layout, elementsWithOptions_key)
70
70
 
71
- setTableColumns(generateTableColumns(elements, optionKeyValuePair, { attachmentBaseUrl, formName }))
71
+ setTableColumns(generateTableColumns(elements, optionKeyValuePair, { attachmentBaseUrl, formName, formRef }))
72
72
  setHeaderLayout(header.layout)
73
73
  setOtherConfigs({ ...restConfigs, hasNoPagination: pagination?.hasNoPagination ?? false })
74
74
 
@@ -95,7 +95,7 @@ export default function FormDataListTableComponent({
95
95
  setIsHandlingSchema(false)
96
96
  isFinishedHandlingRef.current = true
97
97
  },
98
- [attachmentBaseUrl, formName, userId, formId],
98
+ [attachmentBaseUrl, formName, userId, formId, formRef],
99
99
  )
100
100
 
101
101
  useEffect(() => {
@@ -207,4 +207,5 @@ export interface IConstantValues {
207
207
  formDataId?: string
208
208
  formName?: string
209
209
  parentInfo?: IDataListParentInfo
210
+ formRef?: FormInstance
210
211
  }
@@ -17,6 +17,7 @@ export default function FormDataDetailsComponent({
17
17
  formId,
18
18
  formKey,
19
19
  formDataId,
20
+ formName,
20
21
  companyKey,
21
22
  baseServerUrl,
22
23
  onCustomFunctionCall,
@@ -98,10 +99,6 @@ export default function FormDataDetailsComponent({
98
99
  )
99
100
  setLoadings((c) => ({ ...c, data: false }))
100
101
  } else {
101
- formDataRef.setFieldValue(
102
- FormPreservedItemKeys.TemplateReports,
103
- parsedData.generateConfig.templateReports,
104
- )
105
102
  const dateFields = extractDateFields(layout)
106
103
 
107
104
  fetchFormData(formId, dateFields)
@@ -129,6 +126,7 @@ export default function FormDataDetailsComponent({
129
126
  conditions={conditions}
130
127
  formDataId={formDataId}
131
128
  formRef={formDataRef}
129
+ formName={formName}
132
130
  defaulDisabled
133
131
  onCustomFunctionCall={onCustomFunctionCall}
134
132
  />
@@ -143,6 +141,7 @@ type IFormDataDetailsComponent = {
143
141
  baseServerUrl?: string
144
142
  companyKey?: string
145
143
  formDataId: string
144
+ formName?: string
146
145
  } & (IDataDetailsPublicProps | IDataDetailsPrivateProps) &
147
146
  ICustomFunctionCall
148
147
 
@@ -3,7 +3,7 @@ import { ResponsivenessDeviceEnum } from '../../../../enums'
3
3
  import { getFlexContainerStyle, getFlexItemStyle, kebabCaseToCamelCase } from '../../../../functions'
4
4
  import { IDataRender_ButtonProps, IFormLayoutElementConditions, IFormLayoutRow } from '../../../../types'
5
5
  import LayoutRendererCol from '../2-col'
6
- import { memo, ReactElement, ReactNode, useMemo } from 'react'
6
+ import { memo, ReactElement, ReactNode, useMemo, useState } from 'react'
7
7
  import { LayoutRowConditionalHeaderRenderer } from './header-render'
8
8
  import { LayoutRowRepeatableRenderer } from './repeatable-render'
9
9
 
@@ -16,6 +16,8 @@ export const LayoutRendererRow = memo(
16
16
  breakpointDevice = ResponsivenessDeviceEnum.Default,
17
17
  renderButton,
18
18
  }: ILayoutRendererRow) => {
19
+ const [hiddenElementCount, setHiddenElementCount] = useState(0)
20
+
19
21
  const styleConfig = useMemo(() => {
20
22
  if (!rowData.style || !rowData.style[breakpointDevice]) return {}
21
23
  return kebabCaseToCamelCase(rowData.style[breakpointDevice])
@@ -25,23 +27,40 @@ export const LayoutRendererRow = memo(
25
27
  <>
26
28
  <LayoutRowConditionalHeaderRenderer header={rowData.props?.header}>
27
29
  <LayoutRowRepeatableRenderer basePath={basePath} repeatingSection={rowData.props?.repeatingSection}>
28
- {(formListItemProps) => (
29
- <div style={{ ...styleConfig, ...getFlexContainerStyle(breakpointDevice, rowData.responsiveness) }}>
30
- {rowData.children.map((col, colIdx) => (
31
- <LayoutRendererCol
32
- key={colIdx}
33
- basePath={formListItemProps ? formListItemProps.updatedBasePath : basePath}
34
- colData={col}
35
- formRef={formRef}
36
- titleComponent={titleComponent}
37
- breakpointDevice={breakpointDevice}
38
- renderButton={renderButton}
39
- colStyle={getFlexItemStyle(breakpointDevice, colIdx, rowData.responsiveness)}
40
- />
41
- ))}
42
- {formListItemProps?.removeButton}
43
- </div>
44
- )}
30
+ {(formListItemProps) => {
31
+ const style: { [key: string]: any } = {
32
+ ...styleConfig,
33
+ ...getFlexContainerStyle(breakpointDevice, rowData.responsiveness),
34
+ }
35
+ if (hiddenElementCount === rowData.children.length) style.display = 'none'
36
+
37
+ return (
38
+ <div style={style}>
39
+ {rowData.children.map((col, colIdx) => (
40
+ <LayoutRendererCol
41
+ key={colIdx}
42
+ basePath={formListItemProps ? formListItemProps.updatedBasePath : basePath}
43
+ colData={col}
44
+ formRef={formRef}
45
+ titleComponent={titleComponent}
46
+ breakpointDevice={breakpointDevice}
47
+ renderButton={renderButton}
48
+ hideRow={(isHidden) =>
49
+ setHiddenElementCount((c) => {
50
+ if (isHidden) return c + 1
51
+ else {
52
+ if (c - 1 < 0) return 0
53
+ return c - 1
54
+ }
55
+ })
56
+ }
57
+ colStyle={getFlexItemStyle(breakpointDevice, colIdx, rowData.responsiveness)}
58
+ />
59
+ ))}
60
+ {formListItemProps?.removeButton}
61
+ </div>
62
+ )
63
+ }}
45
64
  </LayoutRowRepeatableRenderer>
46
65
  </LayoutRowConditionalHeaderRenderer>
47
66
  </>
@@ -1,4 +1,4 @@
1
- import { memo, ReactElement, ReactNode, useState } from 'react'
1
+ import { memo, ReactElement, ReactNode, useEffect, useMemo, useState } from 'react'
2
2
  import { LayoutRendererRow } from '../1-row'
3
3
  import {
4
4
  IDataRender_ButtonProps,
@@ -18,15 +18,26 @@ function LayoutRendererCol({
18
18
  titleComponent,
19
19
  breakpointDevice,
20
20
  colStyle,
21
+ hideRow,
21
22
  renderButton,
22
23
  }: ILayoutRendererCol) {
23
24
  const [hiddenElementCount, setHiddenElementCount] = useState(0)
24
25
 
26
+ const isAllChildrenHidden = useMemo(
27
+ () => colData.children.length === hiddenElementCount,
28
+ [colData.children, hiddenElementCount],
29
+ )
30
+
31
+ useEffect(() => {
32
+ console.log(isAllChildrenHidden)
33
+ hideRow(isAllChildrenHidden)
34
+ }, [isAllChildrenHidden])
35
+
25
36
  return (
26
37
  <div
27
38
  style={{
28
39
  ...colStyle,
29
- display: colData.children.length === hiddenElementCount ? 'none' : 'flex',
40
+ display: isAllChildrenHidden ? 'none' : 'flex',
30
41
  flexDirection: FlexDirection.Col,
31
42
  }}
32
43
  >
@@ -53,8 +64,10 @@ function LayoutRendererCol({
53
64
  hideElement={(isHidden) =>
54
65
  setHiddenElementCount((c) => {
55
66
  if (isHidden) return c + 1
56
- if (c - 1 < 0) return 0
57
- return c - 1
67
+ else {
68
+ if (c - 1 < 0) return 0
69
+ return c - 1
70
+ }
58
71
  })
59
72
  }
60
73
  />
@@ -72,5 +85,6 @@ interface ILayoutRendererCol {
72
85
  titleComponent?: ReactNode
73
86
  breakpointDevice: ResponsivenessDeviceEnum
74
87
  colStyle: { [key: string]: number | string }
88
+ hideRow: (bool: boolean) => void
75
89
  renderButton?: (props: IDataRender_ButtonProps, conditions?: IFormLayoutElementConditions) => ReactElement
76
90
  }
@@ -39,7 +39,7 @@ export const DynamicFormButtonRender = memo((props: IDynamicButton) => {
39
39
  const [dataLoadingType, setDataLoadingType] = useState<FormLoadingModalTypeEnum | undefined>()
40
40
  const { inPreviewMode, isPublic = false } = useFormPreservedItemValues(formRef)
41
41
 
42
- const baseDynamicUrl = useMemo(() => constructDynamicFormHref(formName), [formName])
42
+ const baseDynamicUrl = useMemo(() => constructDynamicFormHref(formName) ?? '', [formName])
43
43
 
44
44
  useEffect(() => {
45
45
  formRef?.setFieldValue(FormPreservedItemKeys.FormDataId, formDataId)
@@ -76,6 +76,7 @@ export const DynamicFormButtonRender = memo((props: IDynamicButton) => {
76
76
  })
77
77
  const { onCreateNewData, onGeneratePdfProof } = useCreateDataWithPdfActions({
78
78
  formRef,
79
+ baseDynamicUrl,
79
80
  setDataLoadingType,
80
81
  onGenerateError: () => {
81
82
  setDataLoadingType(undefined)
@@ -168,7 +169,11 @@ export const DynamicFormButtonRender = memo((props: IDynamicButton) => {
168
169
  break
169
170
 
170
171
  case ButtonActionCategoryEnum.GenerateReport:
171
- onGenerateReport()
172
+ if (!formDataId) {
173
+ warning({ message: 'Data id was not found!' })
174
+ break
175
+ }
176
+ onGenerateReport(formName, formDataId)
172
177
  setTimeout(() => setLoading(false), 500)
173
178
  break
174
179
 
@@ -189,6 +194,7 @@ export const DynamicFormButtonRender = memo((props: IDynamicButton) => {
189
194
  baseDynamicUrl,
190
195
  formDataId,
191
196
  isPublic,
197
+ formName,
192
198
  onCustomFunctionCall,
193
199
  onDuplicateData,
194
200
  onDeleteData,
@@ -1,11 +1,11 @@
1
- import { useCallback, useMemo } from 'react'
1
+ import { useCallback } from 'react'
2
2
  import { FormLoadingModalTypeEnum } from '../../../../../enums'
3
3
  import { useFormPreservedItemValues } from '../../../../common/custom-hooks/use-preserved-form-items.hook'
4
4
  import { stringifyJSON } from '../../../../../functions/json-handlers'
5
5
  import client from '../../../../../functions/axios-handler'
6
- import { extractDynamicFormHref, objectToQueryParams } from '../../../../../functions'
6
+ import { objectToQueryParams } from '../../../../../functions'
7
7
  import { useNotification } from '../../../../common/custom-hooks'
8
- import { useLocation, useNavigate } from 'react-router-dom'
8
+ import { useNavigate } from 'react-router-dom'
9
9
  import { FormInstance } from 'antd'
10
10
 
11
11
  /* --------------------------------------------------------------------------
@@ -14,6 +14,7 @@ import { FormInstance } from 'antd'
14
14
  -------------------------------------------------------------------------- */
15
15
  export const useCreateDataWithPdfActions = ({
16
16
  formRef,
17
+ baseDynamicUrl,
17
18
  setDataLoadingType,
18
19
  onCreateSuccess,
19
20
  onCreateError,
@@ -21,6 +22,7 @@ export const useCreateDataWithPdfActions = ({
21
22
  onGenerateError,
22
23
  }: {
23
24
  formRef: FormInstance | undefined
25
+ baseDynamicUrl: string
24
26
  setDataLoadingType: (type: FormLoadingModalTypeEnum) => void
25
27
  onCreateSuccess: () => void
26
28
  onCreateError: () => void
@@ -28,13 +30,11 @@ export const useCreateDataWithPdfActions = ({
28
30
  onGenerateError: () => void
29
31
  }) => {
30
32
  const navigate = useNavigate()
31
- const location = useLocation()
33
+ // const { isModalOpen: isSuccessModalOpen, isPendingTransition: isSuccessModalPending, openModal: openSuccessModal, closeModal: closeSuccessModal } = useLazyModalOpener()
32
34
  const { errorModal, confirmModal } = useNotification()
33
35
  const { formId, formKey, baseServerUrl, companyKey, submissionPdfConfig, isPublic } =
34
36
  useFormPreservedItemValues(formRef)
35
37
 
36
- const baseDynamicUrl = useMemo(() => extractDynamicFormHref(location.pathname), [location.pathname])
37
-
38
38
  const onCreateNewData = useCallback(
39
39
  (generatedPdfBlobName?: string) => {
40
40
  try {
@@ -1,5 +1,4 @@
1
- import { useCallback, useMemo } from 'react'
2
- import { extractDynamicFormHref } from '../../../../../functions'
1
+ import { useCallback } from 'react'
3
2
  import { useFormPreservedItemValues } from '../../../../common/custom-hooks/use-preserved-form-items.hook'
4
3
  import client from '../../../../../functions/axios-handler'
5
4
  import { FormInstance } from 'antd'
@@ -18,7 +17,6 @@ export const useDeleteDataAction = ({
18
17
  onError: () => void
19
18
  onFinal: () => void
20
19
  }) => {
21
- const baseDynamicUrl = useMemo(() => extractDynamicFormHref(location.pathname), [location.pathname])
22
20
  const { formId, formDataId } = useFormPreservedItemValues(formRef)
23
21
 
24
22
  const onDeleteData = useCallback(async () => {
@@ -31,7 +29,7 @@ export const useDeleteDataAction = ({
31
29
  } finally {
32
30
  onFinal()
33
31
  }
34
- }, [formId, formDataId, baseDynamicUrl])
32
+ }, [formId, formDataId])
35
33
 
36
34
  return onDeleteData
37
35
  }
@@ -1,12 +1,13 @@
1
- import { useCallback, useState } from 'react'
2
- import { useFormPreservedItemValues } from '../../../../common/custom-hooks/use-preserved-form-items.hook'
3
- import { FormInstance, Modal, Select } from 'antd'
1
+ import { useCallback, useRef, useState } from 'react'
2
+ import { FormInstance, Modal, Select, Spin } from 'antd'
4
3
  import { FaCaretDown } from 'react-icons/fa6'
5
- import { IFormTemplateReport } from '../../../../../types'
4
+ import { IDynamicForm, IFormSchema, IFormTemplateReport } from '../../../../../types'
6
5
  import { Button_FillerPortal } from '../../../../common/button'
7
6
  import { useNotification } from '../../../../common/custom-hooks'
8
7
  import { fetchFormDataAsLookup, renderData } from '../../../../../functions'
9
- import { FieldElementOptionSourceEnum } from '../../../../../enums'
8
+ import client from '../../../../../functions/axios-handler'
9
+ import { parseJSON } from '../../../../../functions/json-handlers'
10
+ import { FieldElementOptionSourceEnum, FormPreservedItemKeys, LOCAL_STORAGE_KEYS_ENUM } from '../../../../../enums'
10
11
 
11
12
  /* --------------------------------------------------------------------------
12
13
  Generates a report by logging current form values.
@@ -17,30 +18,76 @@ export const useGenerateReportAction = ({
17
18
  onError,
18
19
  onFinal,
19
20
  }: {
20
- formRef: FormInstance | undefined
21
+ formRef?: FormInstance
21
22
  onSuccess: () => void
22
23
  onError: () => void
23
24
  onFinal: () => void
24
25
  }) => {
25
26
  const { warning } = useNotification()
26
- const { templateReports } = useFormPreservedItemValues(formRef)
27
27
  const [isModalOpen, setIsModalOpen] = useState(false)
28
28
  const [selectedTemplate, setSelectedTemplate] = useState(undefined)
29
+ const [templateReports, setTemplateReports] = useState<IFormTemplateReport[]>([])
29
30
  const [loading, setLoading] = useState(false)
31
+ const [formData, setFormData] = useState<{ [key: string]: string | number }>({})
32
+ const companyKeyRef = useRef('')
30
33
 
31
- const onGenerateReport = useCallback(() => {
32
- if (Array.isArray(templateReports) && templateReports.length > 0) setIsModalOpen(true)
34
+ const onGenerateReport = useCallback(
35
+ async (formName: string, formDataId: string) => {
36
+ setLoading(true)
37
+ setIsModalOpen(true)
38
+ try {
39
+ const storedData = localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.DynamicForms)
40
+ if (storedData) {
41
+ const parsedData: IDynamicForm[] = JSON.parse(storedData)
42
+ const form = parsedData.find((entry) => entry.name === formName)
33
43
 
34
- console.log('Template Reports:', templateReports)
35
- }, [formRef, templateReports])
44
+ if (form) {
45
+ client
46
+ .get(`/api/form/${form.id}`)
47
+ .then((res) => {
48
+ if (res.status === 200) {
49
+ const parsedFormData: IFormSchema | null = parseJSON(res.data.data)
50
+ if (parsedFormData) {
51
+ const preservedFormValues = formRef?.getFieldsValue(true) ?? {}
52
+ companyKeyRef.current = preservedFormValues.companyKey ?? ''
53
+
54
+ const formDataListFormKey =
55
+ preservedFormValues[`${FormPreservedItemKeys.FormDataListElementKey}_${form.id}`]
56
+
57
+ if (formDataListFormKey) {
58
+ const formDataList = preservedFormValues[formDataListFormKey] ?? []
59
+ const formDataRowFormData =
60
+ formDataList.find((d: { [key: string]: string | number }) => d.id === formDataId) ?? {}
61
+
62
+ setFormData(formDataRowFormData)
63
+ } else {
64
+ const mainFormData = formRef?.getFieldsValue() ?? {}
65
+ setFormData(mainFormData)
66
+ }
67
+
68
+ const reports = parsedFormData.generateConfig.templateReports ?? []
69
+ setTemplateReports(reports)
70
+ }
71
+ }
72
+ })
73
+ .finally(() => setLoading(false))
74
+ } else setLoading(false)
75
+ } else setLoading(false)
76
+ } catch (error) {
77
+ console.error('Error reading or parsing localStorage data', error)
78
+ setLoading(false)
79
+ }
80
+ },
81
+ [templateReports, formRef],
82
+ )
36
83
 
37
84
  const ChooseTemplateReportModal = isModalOpen ? (
38
85
  <Modal
39
86
  open
40
- closable={false}
41
87
  maskClosable={false}
42
88
  width={400}
43
89
  title="Please select a template!"
90
+ onCancel={() => setIsModalOpen(false)}
44
91
  footer={
45
92
  <div className="flex justify-between">
46
93
  <Button_FillerPortal outline onClick={() => setIsModalOpen(false)}>
@@ -52,14 +99,16 @@ export const useGenerateReportAction = ({
52
99
  title={selectedTemplate ? '' : 'Please select a template to continue!'}
53
100
  loading={loading}
54
101
  onClick={async () => {
102
+ if (!companyKeyRef.current) {
103
+ warning({ message: 'Company was not found!' })
104
+ return
105
+ }
106
+
55
107
  setLoading(true)
56
108
  const selectedTemplateInfo: IFormTemplateReport = templateReports.find(
57
109
  (t: IFormTemplateReport) => t.fileBlobName === selectedTemplate,
58
- )
59
- const formValues = formRef?.getFieldsValue()
60
- console.log('Form Values:', formValues)
110
+ )!
61
111
 
62
- console.log(selectedTemplateInfo)
63
112
  const replacements = await Promise.all(
64
113
  selectedTemplateInfo.replacements.map(async (rep) => {
65
114
  if (!!rep.optionSource) {
@@ -67,7 +116,7 @@ export const useGenerateReportAction = ({
67
116
  return {
68
117
  placeholder: rep.placeholder,
69
118
  value: renderData(
70
- rep.optionSource.options.find((op) => op.id === rep.option)?.value,
119
+ rep.optionSource.options.find((op) => op.id === formData[rep.field])?.value,
71
120
  rep.renderConfig,
72
121
  ),
73
122
  }
@@ -76,7 +125,7 @@ export const useGenerateReportAction = ({
76
125
  return {
77
126
  placeholder: rep.placeholder,
78
127
  value: renderData(
79
- formDataResData.find((data) => data.id === formValues[rep.field])?.[
128
+ formDataResData.find((data) => data.id === formData[rep.field])?.[
80
129
  rep.optionSource.form.field
81
130
  ],
82
131
  rep.renderConfig,
@@ -86,18 +135,39 @@ export const useGenerateReportAction = ({
86
135
  }
87
136
  return {
88
137
  placeholder: rep.placeholder,
89
- value: renderData(formValues[rep.field], rep.renderConfig),
138
+ value: renderData(formData[rep.field], rep.renderConfig),
139
+ type: rep.type,
90
140
  }
91
141
  }),
92
142
  )
93
143
 
94
- console.log('Replacement values', replacements)
95
-
96
- onSuccess()
97
- onError()
98
- onFinal()
99
- warning({ message: 'Waiting for an API!' })
100
- setLoading(false)
144
+ client
145
+ .post(
146
+ `/api/attachment/template/${companyKeyRef.current}/${selectedTemplateInfo.templateName}`,
147
+ replacements.map((r) => ({
148
+ key: r.placeholder,
149
+ value: r.value,
150
+ type: r.type,
151
+ })),
152
+ { responseType: 'blob' },
153
+ )
154
+ .then((res) => {
155
+ if (res.status === 200) {
156
+ const url = window.URL.createObjectURL(new Blob([res.data]))
157
+ const link = document.createElement('a')
158
+ link.href = url
159
+ link.setAttribute('download', `${selectedTemplateInfo.templateName}.pdf`) //or any other extension
160
+ document.body.appendChild(link)
161
+ link.click()
162
+ onSuccess()
163
+ }
164
+ })
165
+ .catch(() => onError())
166
+ .finally(() => {
167
+ onFinal()
168
+ setLoading(false)
169
+ setIsModalOpen(false)
170
+ })
101
171
  }}
102
172
  >
103
173
  Continue
@@ -106,22 +176,24 @@ export const useGenerateReportAction = ({
106
176
  }
107
177
  >
108
178
  <span>Template</span>
109
- <Select
110
- placeholder="Please select a template"
111
- suffixIcon={<FaCaretDown />}
112
- options={templateReports.map((t: IFormTemplateReport) => ({ value: t.fileBlobName, label: t.templateName }))}
113
- value={selectedTemplate}
114
- onChange={setSelectedTemplate}
115
- showSearch
116
- optionFilterProp="label"
117
- filterOption={(input, option) =>
118
- option && typeof option.label === 'string'
119
- ? option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
120
- : false
121
- }
122
- allowClear={false}
123
- className="w-full"
124
- />
179
+ <Spin spinning={loading}>
180
+ <Select
181
+ placeholder="Please select a template"
182
+ suffixIcon={<FaCaretDown />}
183
+ options={templateReports.map((t: IFormTemplateReport) => ({ value: t.fileBlobName, label: t.templateName }))}
184
+ value={selectedTemplate}
185
+ onChange={setSelectedTemplate}
186
+ showSearch
187
+ optionFilterProp="label"
188
+ filterOption={(input, option) =>
189
+ option && typeof option.label === 'string'
190
+ ? option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0
191
+ : false
192
+ }
193
+ allowClear={false}
194
+ className="w-full"
195
+ />
196
+ </Spin>
125
197
  </Modal>
126
198
  ) : (
127
199
  <></>
@@ -1,17 +1,25 @@
1
1
  import { Form, FormInstance } from 'antd'
2
- import { IReadFieldDataElementProps } from '../../../../types'
3
2
  import { renderData } from '../../../../functions'
4
3
  import { useFormPreservedItemValues } from '../../../common/custom-hooks/use-preserved-form-items.hook'
5
4
  import { useMemo } from 'react'
6
- import { DataRenderTypeEnum } from '../../../../enums'
5
+ import { DataRenderTypeEnum, EvaluationOperatorsEnum, EvaluationValueTypeEnum } from '../../../../enums'
7
6
  import { IFormItemPath } from '.'
7
+ import {
8
+ IEvaluationCondition,
9
+ IEvaluationConfig,
10
+ IEvaluationOnField,
11
+ IEvaluationOnList,
12
+ IReadFieldDataElementProps,
13
+ } from '../../../../types'
8
14
 
9
15
  export default function LayoutRenderer_ReadFieldData({
10
16
  formRef,
11
17
  elementProps,
12
18
  style = {},
13
19
  }: ILayoutRenderer_ReadFieldData) {
14
- const fieldValue = Form.useWatch(elementProps.field, { form: formRef, preserve: true })
20
+ const fieldValue = elementProps.isValueEvaluated
21
+ ? undefined
22
+ : Form.useWatch(elementProps.field, { form: formRef, preserve: true })
15
23
  const { baseServerUrl, companyKey } = useFormPreservedItemValues(formRef)
16
24
 
17
25
  const formValues = Form.useWatch([], { form: formRef, preserve: true })
@@ -21,6 +29,14 @@ export default function LayoutRenderer_ReadFieldData({
21
29
  [baseServerUrl, companyKey, fieldValue],
22
30
  )
23
31
 
32
+ const evaluatedValue = useMemo(() => {
33
+ if (elementProps.isValueEvaluated && formValues) {
34
+ const config = elementProps.evaluationConfig
35
+ return evaluateConfig(config, formValues)
36
+ }
37
+ return 0
38
+ }, [formValues, elementProps])
39
+
24
40
  return (
25
41
  <div className="flex items-center gap-2">
26
42
  {elementProps.label && <span>{elementProps.label}: </span>}
@@ -30,6 +46,8 @@ export default function LayoutRenderer_ReadFieldData({
30
46
  ? attachmentUrl
31
47
  : elementProps.renderConfig?.type === DataRenderTypeEnum.Conditional
32
48
  ? formValues
49
+ : elementProps.isValueEvaluated
50
+ ? evaluatedValue
33
51
  : fieldValue,
34
52
  elementProps.renderConfig ?? { type: DataRenderTypeEnum.Default },
35
53
  ) ?? 'N/A'}
@@ -43,3 +61,102 @@ type ILayoutRenderer_ReadFieldData = {
43
61
  elementProps: IReadFieldDataElementProps
44
62
  style?: { [key: string]: any }
45
63
  } & IFormItemPath
64
+
65
+ const evaluateConfig = (config: IEvaluationConfig, data: { [key: string]: any }): number => {
66
+ // Helper to check an evaluation’s condition (if any)
67
+ function checkConditions(
68
+ dataToCheckConditions: { [key: string]: any },
69
+ conditions?: IEvaluationCondition[],
70
+ ): boolean {
71
+ if (!conditions || conditions.length === 0) return true
72
+ // As stated, at most one condition is supported.
73
+ const condition = conditions[0]
74
+
75
+ // Currently only the Equal operator is supported.
76
+ return dataToCheckConditions[condition.field] === condition.value
77
+ }
78
+
79
+ // Helper to combine values using the operator.
80
+ function applyOperator(operator: EvaluationOperatorsEnum, values: number[]): number {
81
+ if (values.length === 0) return 0
82
+ let result = values[0]
83
+ for (let i = 1; i < values.length; i++) {
84
+ const value = values[i]
85
+ switch (operator) {
86
+ case EvaluationOperatorsEnum.Add:
87
+ result += value
88
+ break
89
+ case EvaluationOperatorsEnum.Subtract:
90
+ result -= value
91
+ break
92
+ case EvaluationOperatorsEnum.Multiply:
93
+ result *= value
94
+ break
95
+ case EvaluationOperatorsEnum.Divide:
96
+ result /= value
97
+ break
98
+ default:
99
+ throw new Error(`Unsupported operator: ${operator}`)
100
+ }
101
+ }
102
+ return result
103
+ }
104
+
105
+ // Object to store results from each evaluation by its id.
106
+ const evaluationResults: { [id: string]: number } = {}
107
+
108
+ // Process each evaluation in the config.
109
+ for (const evaluation of config.evaluations) {
110
+ // Evaluation based on fields (IEvaluationOnField)
111
+ if (evaluation.type === EvaluationValueTypeEnum.Fields) {
112
+ // If a condition exists and fails, treat the result as 0.
113
+ if (!checkConditions(data, evaluation.conditions)) {
114
+ evaluationResults[evaluation.id] = 0
115
+ continue
116
+ }
117
+
118
+ const evalFields = (evaluation as IEvaluationOnField).fields
119
+ // For each field, take the value from data (using default if missing)
120
+ const values = evalFields.map((fieldConfig) => {
121
+ const value = data[fieldConfig.field]
122
+ return value !== undefined && value !== null ? value : fieldConfig.defaultValue ?? 0
123
+ })
124
+ evaluationResults[evaluation.id] = applyOperator(evaluation.operator, values)
125
+ }
126
+ // Evaluation based on a list (IEvaluationOnList)
127
+ else if (evaluation.type === EvaluationValueTypeEnum.List) {
128
+
129
+ const listData = data[(evaluation as IEvaluationOnList).list]
130
+ if (Array.isArray(listData)) {
131
+ const values = listData.map((item, itemIdx) => {
132
+ const listItemData = listData[itemIdx]
133
+ if (!checkConditions(listItemData, evaluation.conditions)) return 0
134
+
135
+ const fieldValue = item[(evaluation as IEvaluationOnList).listField.field]
136
+
137
+ return fieldValue !== undefined && fieldValue !== null
138
+ ? fieldValue
139
+ : (evaluation as IEvaluationOnList).listField.defaultValue ?? 0
140
+ })
141
+ evaluationResults[evaluation.id] = applyOperator(evaluation.operator, values)
142
+ } else {
143
+ evaluationResults[evaluation.id] = 0
144
+ }
145
+ }
146
+ }
147
+
148
+ // Determine and return the final result.
149
+ // If evaluationGroups is provided, use the last one as the final evaluation.
150
+ if (config.evaluationGroups && config.evaluationGroups.length > 0) {
151
+ const finalGroup = config.evaluationGroups[config.evaluationGroups.length - 1]
152
+ // Retrieve results for the evaluation IDs in the group (defaulting to 0 if missing)
153
+ const groupValues = finalGroup.evaluationIds.map((id) => evaluationResults[id] ?? 0)
154
+ return applyOperator(finalGroup.operator, groupValues)
155
+ }
156
+ // Otherwise, return the result from the last evaluation.
157
+ else if (config.evaluations.length > 0) {
158
+ const lastEvaluation = config.evaluations[config.evaluations.length - 1]
159
+ return evaluationResults[lastEvaluation.id]
160
+ }
161
+ return 0
162
+ }
@@ -17,8 +17,9 @@ import {
17
17
  IFormLayoutRow,
18
18
  IFormRelationshipConfig,
19
19
  } from '../../../../types'
20
+ import { FormPreservedItemKeys } from '../../../../enums'
20
21
 
21
- export default function LayoutRenderer_LoadFormData({ formRef, elementProps }: ILayoutRenderer_LoadFormData) {
22
+ export default function LayoutRenderer_LoadFormData({ fullPath, formRef, elementProps }: ILayoutRenderer_LoadFormData) {
22
23
  const { form, filterConfig, sorterConfig } = elementProps
23
24
  const { formDataId } = useFormPreservedItemValues(formRef)
24
25
  const recursiveCallCount = useRef(0)
@@ -52,6 +53,7 @@ export default function LayoutRenderer_LoadFormData({ formRef, elementProps }: I
52
53
  layout: IFormLayoutRow[]
53
54
  formName: string
54
55
  parentInfo: { foreignKey: string; dataIds: string[] }
56
+ rootFormId: number
55
57
  data: { data: IFormData[]; total: number }
56
58
  }
57
59
  | undefined
@@ -125,6 +127,7 @@ export default function LayoutRenderer_LoadFormData({ formRef, elementProps }: I
125
127
  layout: detailsConfig.layout,
126
128
  formName: formDetailsRes.data.name,
127
129
  parentInfo: { foreignKey: relationship.foreignKey, dataIds: foreignKeyIds },
130
+ rootFormId: childFormId,
128
131
  data: {
129
132
  ...formData,
130
133
  data: formData.data.map((d: { [key: string]: any }) => ({
@@ -178,19 +181,24 @@ export default function LayoutRenderer_LoadFormData({ formRef, elementProps }: I
178
181
  (filterConfig as IFilterByAuthUser | IFilterCustom)?.config,
179
182
  sorterConfig,
180
183
  ).then((resData) => {
181
- const { config, data, layout = [], formName = '', parentInfo } = resData ?? {}
184
+ const { config, data, layout = [], formName = '', parentInfo, rootFormId } = resData ?? {}
182
185
 
183
186
  if (config) setDisplayConfig({ dataListConfig: config, detailsLayout: layout })
184
- if (data) setDataList(data)
187
+ if (data) {
188
+ setDataList(data)
189
+ if (rootFormId)
190
+ formRef.setFieldValue(`${FormPreservedItemKeys.FormDataListElementKey}_${rootFormId}`, fullPath)
191
+ formRef.setFieldValue(fullPath, data.data)
192
+ }
185
193
  setCurrentFormInfo({ formName, parentInfo })
186
194
  })
187
195
  hasInitialFetched.current = true
188
196
  }
189
- }, [form, filterConfig, sorterConfig, fetchFormDetails, formDataId, formValues])
197
+ }, [form, filterConfig, sorterConfig, fetchFormDetails, formDataId, formValues, fullPath])
190
198
 
191
199
  if (!form || !form.relationshipConfig?.formId) return <></>
192
200
 
193
- const constantValues = { formDataId, ...currentFormInfo }
201
+ const constantValues = { formDataId, formRef, ...currentFormInfo }
194
202
 
195
203
  return (
196
204
  <FormDataListTableComponent
@@ -1,6 +1,6 @@
1
1
  import { Divider, FormInstance } from 'antd'
2
2
  import { memo, ReactElement, ReactNode, useEffect, useMemo } from 'react'
3
- import { IDataRender_ButtonProps, IFormLayoutElement } from '../../../../types'
3
+ import { IDataRender_ButtonProps, IFormLayoutElement, IReadFieldDataElementProps } from '../../../../types'
4
4
  import { getElementGeneralizedProps } from '../../../../functions/get-element-props'
5
5
  import { ElementTypeEnum, ResponsivenessDeviceEnum, TextElementTypeEnum } from '../../../../enums'
6
6
  import { LayoutRenderer_FieldElement } from './2-field-element'
@@ -119,7 +119,11 @@ function LayoutRendererElement({
119
119
 
120
120
  case ElementTypeEnum.ReadFieldData:
121
121
  return (
122
- <LayoutRenderer_ReadFieldData formRef={formRef} elementProps={props} style={elementData.style?.[breakpointDevice]} />
122
+ <LayoutRenderer_ReadFieldData
123
+ formRef={formRef}
124
+ elementProps={props as IReadFieldDataElementProps}
125
+ style={elementData.style?.[breakpointDevice]}
126
+ />
123
127
  )
124
128
 
125
129
  case ElementTypeEnum.Divider:
@@ -159,7 +163,7 @@ function LayoutRendererElement({
159
163
  )
160
164
 
161
165
  case ElementTypeEnum.LoadFormData:
162
- return <LayoutRenderer_LoadFormData elementProps={props} formRef={formRef} />
166
+ return <LayoutRenderer_LoadFormData fullPath={fullPath} elementProps={props} formRef={formRef} />
163
167
 
164
168
  default:
165
169
  return (
package/src/enums.ts CHANGED
@@ -221,8 +221,8 @@ export enum FormPreservedItemKeys {
221
221
  IsGeneratingPDF = 'isGeneratingPdf',
222
222
  HasSignature = 'hasSignature',
223
223
  InPreviewMode = 'inPreviewMode',
224
- TemplateReports = 'templateReports',
225
224
  IsPublic = 'isPublic',
225
+ FormDataListElementKey = 'formDataListElementKey',
226
226
  }
227
227
  export enum MongoDbSortOrderEnum {
228
228
  Ascending = 1,
@@ -234,6 +234,7 @@ export enum DataCategoryEnum {
234
234
  Boolean = 'boolean',
235
235
  Null = 'null',
236
236
  Undefined = 'undefined',
237
+ Option = 'option',
237
238
  Array = 'array',
238
239
  Function = 'function',
239
240
  UserInput = 'user-input',
@@ -254,6 +255,17 @@ export enum RelationalOperatorsEnum {
254
255
  In = '@in',
255
256
  NotIn = '@nin',
256
257
  }
258
+ export enum EvaluationOperatorsEnum {
259
+ Add = 'add',
260
+ Subtract = 'subtract',
261
+ Multiply = 'multiply',
262
+ Divide = 'divide',
263
+ }
264
+ export enum EvaluationValueTypeEnum {
265
+ Fields = 'fields',
266
+ List = 'list',
267
+ Group = 'group', // for nested evaluation
268
+ }
257
269
  export enum FormRelationshipEnum {
258
270
  ManyToOne = 'many-to-one',
259
271
  OneToOne = 'one-to-one',
@@ -279,3 +291,10 @@ export enum PatternPlacholdersEnum {
279
291
  CyrillicLowercase = 'c',
280
292
  Digit = 'D',
281
293
  }
294
+ export enum TemplateDataReplacementFieldTypesEnum {
295
+ Text = 1,
296
+ Html,
297
+ Image,
298
+ Table,
299
+ TableRow,
300
+ }
@@ -1,7 +1,7 @@
1
1
  import dayjs from 'dayjs'
2
2
  import { AlignTypeEnum, CountryEnum, DataRenderTypeEnum, PatternPlacholdersEnum } from '../enums'
3
3
  import { DynamicFormButtonRender } from '../components/form/layout-renderer/3-element/1-dynamic-button'
4
- import { Dropdown, Image } from 'antd'
4
+ import { Dropdown, FormInstance, Image } from 'antd'
5
5
  import { FaCaretDown } from 'react-icons/fa6'
6
6
  import { INTERNATIONALIZATION_DATA } from '../constants'
7
7
  import { evaluateCondition } from './evaluate-condition'
@@ -22,9 +22,9 @@ import {
22
22
  export const generateTableColumns = (
23
23
  elements: IFormDataListElement[],
24
24
  optionKeyValuePair: { [key: string]: string },
25
- contextValues: { attachmentBaseUrl?: string; formName?: string } = {},
25
+ contextValues: { attachmentBaseUrl?: string; formName?: string; formRef?: FormInstance } = {},
26
26
  ) => {
27
- const { attachmentBaseUrl, formName } = contextValues
27
+ const { attachmentBaseUrl, formName, formRef } = contextValues
28
28
 
29
29
  return elements.map((el) => {
30
30
  const col: ITableColumn = {
@@ -62,7 +62,12 @@ export const generateTableColumns = (
62
62
  .map((btnProps, btnKey) => ({
63
63
  key: btnKey,
64
64
  label: (
65
- <DynamicFormButtonRender btnProps={btnProps} formDataId={rowData?.id} formName={formName} />
65
+ <DynamicFormButtonRender
66
+ formRef={formRef}
67
+ btnProps={btnProps}
68
+ formDataId={rowData?.id}
69
+ formName={formName}
70
+ />
66
71
  ),
67
72
  })),
68
73
  }}
@@ -76,6 +81,7 @@ export const generateTableColumns = (
76
81
  </Dropdown>
77
82
  ) : (
78
83
  <DynamicFormButtonRender
84
+ formRef={formRef}
79
85
  btnProps={(el.renderConfig as IDataRender_Buttons).buttons[0]}
80
86
  formDataId={rowData?.id}
81
87
  formName={formName}
@@ -92,7 +98,7 @@ export const generateTableColumns = (
92
98
  }
93
99
 
94
100
  export const renderData = (data?: any, renderConfig?: IDataRenderConfig): ReactNode => {
95
- if (!data || typeof data === 'object' || !renderConfig) return 'N/A'
101
+ if ([null, undefined, ''].includes(data) || typeof data === 'object' || !renderConfig) return 'N/A'
96
102
 
97
103
  switch (renderConfig.type) {
98
104
  case DataRenderTypeEnum.Date:
@@ -3,7 +3,7 @@ import { HiMenu } from 'react-icons/hi'
3
3
  import { ILayoutTemplateProps } from '../..'
4
4
  import { Link } from 'react-router-dom'
5
5
  import { IDynamicForm } from '../../../data-list'
6
- import { isEncodedURI } from '../../../../functions'
6
+ import { constructDynamicFormHref, isEncodedURI } from '../../../../functions'
7
7
  import { useState } from 'react'
8
8
  import { Button, Drawer, Dropdown, Menu } from 'antd'
9
9
 
@@ -473,7 +473,3 @@ const isActive = (path: string, href: string) => {
473
473
 
474
474
  return path.startsWith(href)
475
475
  }
476
-
477
- const constructDynamicFormHref = (formName: string): string => {
478
- return `/forms/${formName.toLowerCase().replace(/ /g, '-')}`
479
- }
@@ -1,3 +1,4 @@
1
+ import { TemplateDataReplacementFieldTypesEnum } from '../../enums'
1
2
  import { IDataRenderConfig, IFieldElementOptionSource } from '../layout-elements'
2
3
 
3
4
  export interface IFormSubmissionPdf {
@@ -19,5 +20,5 @@ interface IFormTemplateReportsReplacement {
19
20
  field: string
20
21
  renderConfig: IDataRenderConfig
21
22
  optionSource?: IFieldElementOptionSource
22
- option?: string
23
+ type: TemplateDataReplacementFieldTypesEnum
23
24
  }
@@ -0,0 +1,86 @@
1
+ import {
2
+ DataCategoryEnum,
3
+ EvaluationOperatorsEnum,
4
+ EvaluationValueTypeEnum,
5
+ RelationalOperatorsEnum,
6
+ } from '../../enums'
7
+
8
+ export interface IEvaluationConfig {
9
+ evaluations: (IEvaluationOnField | IEvaluationOnList)[]
10
+ evaluationGroups?: IEvaluationGroup[]
11
+ }
12
+
13
+ interface IEvaluationConfigBase {
14
+ id: string
15
+ operator: EvaluationOperatorsEnum
16
+ }
17
+
18
+ export interface IEvaluationGroup extends IEvaluationConfigBase {
19
+ type: EvaluationValueTypeEnum.Group
20
+ evaluationIds: string[] // ids
21
+ }
22
+
23
+ export interface IEvaluationOnField extends IEvaluationConfigBase {
24
+ type: EvaluationValueTypeEnum.Fields
25
+ fields: IEvaluationField[]
26
+ conditions?: IEvaluationCondition[]
27
+ }
28
+
29
+ export interface IEvaluationOnList extends IEvaluationConfigBase {
30
+ type: EvaluationValueTypeEnum.List
31
+ list: string
32
+ listField: IEvaluationField
33
+ conditions?: IEvaluationCondition[]
34
+ }
35
+
36
+ interface IEvaluationField {
37
+ field: string
38
+ defaultValue: number
39
+ }
40
+
41
+ export interface IEvaluationCondition {
42
+ id: string
43
+ field: string
44
+ operator: RelationalOperatorsEnum
45
+ valueType: DataCategoryEnum
46
+ value: string | number | boolean
47
+ }
48
+
49
+ // {
50
+ // id: string
51
+ // type: 'evaluation',
52
+ // operator: 'plus',
53
+ // evaluations: [
54
+ // {
55
+ // id: string,
56
+ // type: 'list',
57
+ // list: 'dDataList',
58
+ // listField: 'dDataAmount',
59
+ // operator: 'plus',
60
+ // },
61
+ // {
62
+ // id: string,
63
+ // type: 'fields',
64
+ // fields: ['amount1', 'amount2'],
65
+ // },
66
+ // {
67
+ // id: string,
68
+ // type: 'evaluation',
69
+ // operator: 'plus',
70
+ // evaluations: [
71
+ // {
72
+ // id: string
73
+ // type: 'list',
74
+ // list: 'dDataList',
75
+ // listField: 'dDataAmount',
76
+ // operator: 'plus',
77
+ // },
78
+ // {
79
+ // id: string
80
+ // type: 'fields',
81
+ // fields: ['amount1', 'amount2'],
82
+ // }
83
+ // ]
84
+ // }
85
+ // ]
86
+ // }
@@ -3,7 +3,8 @@ export * from './field-option-source'
3
3
  export * from './style'
4
4
  export * from './conditions'
5
5
  export * from './sanitization'
6
- import { ICssStyle, IDataListSorter, IFilterConfig, IRelationshipForm } from '..'
6
+ export * from './evaluation-config'
7
+ import { ICssStyle, IDataListSorter, IEvaluationConfig, IFilterConfig, IRelationshipForm } from '..'
7
8
  import { IFieldElementOptionSource } from './field-option-source'
8
9
  import { IDataRenderConfig } from './data-render-config'
9
10
  import {
@@ -243,12 +244,11 @@ export interface IReadFieldDataElement extends BaseFormLayoutElement {
243
244
  style?: ICssStyle
244
245
  props: IReadFieldDataElementProps
245
246
  }
246
- export interface IReadFieldDataElementProps {
247
+ export type IReadFieldDataElementProps = {
247
248
  label?: string
248
249
  hasNoLabel?: boolean
249
- field?: string
250
250
  renderConfig?: IDataRenderConfig
251
- }
251
+ } & ({ isValueEvaluated: true; evaluationConfig: IEvaluationConfig } | { isValueEvaluated: false; field: string })
252
252
 
253
253
  /** -------------------------------------------------------------------------------------------- */
254
254