form-craft-package 1.6.0 → 1.6.1

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.6.0",
3
+ "version": "1.6.1",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -3,7 +3,6 @@ import { FormPreservedItemKeys } from '../../../enums'
3
3
 
4
4
  export const useFormPreservedItemValues = (formRef?: FormInstance): IFormValues => {
5
5
  const baseServerUrl = Form.useWatch(FormPreservedItemKeys.BaseServerUrl, { form: formRef, preserve: true })
6
- const companyKey = Form.useWatch(FormPreservedItemKeys.CompanyKey, { form: formRef, preserve: true })
7
6
  const isGeneratingPdf = Form.useWatch(FormPreservedItemKeys.IsGeneratingPDF, { form: formRef, preserve: true })
8
7
  const hasSignature = Form.useWatch(FormPreservedItemKeys.HasSignature, { form: formRef, preserve: true })
9
8
  const inPreviewMode = Form.useWatch(FormPreservedItemKeys.InPreviewMode, { form: formRef, preserve: true })
@@ -15,7 +14,6 @@ export const useFormPreservedItemValues = (formRef?: FormInstance): IFormValues
15
14
 
16
15
  return {
17
16
  [FormPreservedItemKeys.BaseServerUrl]: baseServerUrl,
18
- [FormPreservedItemKeys.CompanyKey]: companyKey,
19
17
  [FormPreservedItemKeys.SubmissionPdfConfig]: submissionPdfConfig,
20
18
  [FormPreservedItemKeys.IsGeneratingPDF]: isGeneratingPdf,
21
19
  [FormPreservedItemKeys.HasSignature]: hasSignature,
@@ -29,7 +29,7 @@ function FormDataListComponent({
29
29
  >()
30
30
 
31
31
  const attachmentBaseUrl = useMemo(() => `${baseServerUrl}/api/attachment/${companyKey}`, [baseServerUrl, companyKey])
32
- const dataListHeaderContext = { formId, userId, attachmentBaseUrl, formName }
32
+ const dataListHeaderContext = { formId, userId, attachmentBaseUrl, formName, companyKey }
33
33
 
34
34
  useEffect(() => {
35
35
  if (formId) {
@@ -46,7 +46,7 @@ export default function FormDataListTableComponent({
46
46
  pagination: DEFAULT_PAGINATION,
47
47
  })
48
48
 
49
- const { attachmentBaseUrl, userId, formId, formName, formRef } = dataListHeaderContext
49
+ const { attachmentBaseUrl, userId, formId } = dataListHeaderContext
50
50
 
51
51
  useEffect(() => {
52
52
  setIsHandlingSchema(true)
@@ -77,7 +77,7 @@ export default function FormDataListTableComponent({
77
77
  if (elementsWithOptions_key.length > 0)
78
78
  optionKeyValuePair = await extractElementsOptions(layoutConfig?.elements ?? {}, elementsWithOptions_key)
79
79
 
80
- setTableColumns(generateTableColumns(columns, optionKeyValuePair, { attachmentBaseUrl, formName, formRef }))
80
+ setTableColumns(generateTableColumns(columns, optionKeyValuePair, dataListHeaderContext))
81
81
  setHeaderLayoutConfig({
82
82
  layouts: header.layouts,
83
83
  elements: appendOptionsFromDetailsLayout(header.elements, layoutConfig?.elements),
@@ -107,7 +107,7 @@ export default function FormDataListTableComponent({
107
107
  setIsHandlingSchema(false)
108
108
  isFinishedHandlingRef.current = true
109
109
  },
110
- [attachmentBaseUrl, formName, userId, formId, formRef, currentBreakpoint],
110
+ [attachmentBaseUrl, userId, formId, dataListHeaderContext],
111
111
  )
112
112
 
113
113
  useEffect(() => {
@@ -225,4 +225,5 @@ export interface IDataListHeaderContext {
225
225
  formName?: string
226
226
  parentInfo?: IDataListParentInfo
227
227
  formRef?: FormInstance
228
+ companyKey?: string
228
229
  }
@@ -50,11 +50,10 @@ export default function FormDataDetailsComponent({
50
50
  if (formDataRef)
51
51
  formDataRef.setFieldsValue({
52
52
  [FormPreservedItemKeys.BaseServerUrl]: baseServerUrl,
53
- [FormPreservedItemKeys.CompanyKey]: companyKey,
54
53
  [FormPreservedItemKeys.IsPublic]: isPublic,
55
54
  [FormPreservedItemKeys.InPreviewMode]: false,
56
55
  })
57
- }, [formDataRef, baseServerUrl, companyKey, isPublic])
56
+ }, [formDataRef, baseServerUrl, isPublic])
58
57
 
59
58
  const fetchFormData = useCallback(
60
59
  (dFormId?: number, dateFields: string[] = []) => {
@@ -121,8 +120,8 @@ export default function FormDataDetailsComponent({
121
120
  }, [formId, formKey, isPublic, formDataRef, fetchFormData])
122
121
 
123
122
  const formContext = useMemo(
124
- () => ({ formId, formKey, formDataId, formRef: formDataRef, formName }),
125
- [formDataId, formDataRef, formName, formId, formKey],
123
+ () => ({ formId, formKey, formDataId, formRef: formDataRef, formName, companyKey }),
124
+ [formDataId, formDataRef, formName, formId, formKey, companyKey],
126
125
  )
127
126
 
128
127
  if (loadings.layout || loadings.data) return <FormDataListSkeleton_Details />
@@ -77,6 +77,7 @@ interface ILayoutRendererRow {
77
77
  export interface IFormContext {
78
78
  formDataId?: string
79
79
  formRef?: FormInstance
80
+ companyKey?: string
80
81
  formName?: string
81
82
  formKey?: string
82
83
  formId?: number
@@ -204,7 +204,7 @@ export const DynamicFormButtonRender = memo((props: IDynamicButton) => {
204
204
  warning({ message: 'Data id was not found!' })
205
205
  break
206
206
  }
207
- onGenerateReport(formName, formDataId)
207
+ onGenerateReport()
208
208
  setTimeout(() => setLoading(false), 500)
209
209
  break
210
210
 
@@ -227,7 +227,6 @@ export const DynamicFormButtonRender = memo((props: IDynamicButton) => {
227
227
  baseDynamicUrl,
228
228
  formDataId,
229
229
  isPublic,
230
- formName,
231
230
  onCustomFunctionCall,
232
231
  onDuplicateData,
233
232
  onDeleteData,
@@ -19,19 +19,18 @@ export const useCreateDataWithPdfActions = ({
19
19
  onError: onCreateError,
20
20
  onFinal: onCreateFinal,
21
21
  onGenerateError,
22
- formRef,
23
- formKey,
24
- formId,
25
- formName,
22
+ ...formContext
26
23
  }: {
27
24
  setDataLoadingType: (type: FormLoadingModalTypeEnum) => void
28
25
  onGenerateError: () => void
29
26
  } & IOnSuccessFunctions &
30
27
  IFormContext) => {
28
+ const { formRef, formKey, formId, formName, companyKey } = formContext
29
+
31
30
  const navigate = useNavigate()
32
31
  // const { isModalOpen: isSuccessModalOpen, isPendingTransition: isSuccessModalPending, openModal: openSuccessModal, closeModal: closeSuccessModal } = useLazyModalOpener()
33
32
  const { errorModal, confirmModal } = useNotification()
34
- const { baseServerUrl, companyKey, submissionPdfConfig, isPublic } = useFormPreservedItemValues(formRef)
33
+ const { baseServerUrl, submissionPdfConfig, isPublic } = useFormPreservedItemValues(formRef)
35
34
 
36
35
  const onCreateNewData = useCallback(
37
36
  (generatedPdfBlobName?: string) => {
@@ -7,7 +7,7 @@ import { useNotification } from '../../../../common/custom-hooks'
7
7
  import { fetchFormDataAsLookup, renderData } from '../../../../../functions/forms'
8
8
  import client from '../../../../../functions/axios-handler'
9
9
  import { parseJSON } from '../../../../../functions/forms/json-handlers'
10
- import { FieldElementOptionSourceEnum, FormPreservedItemKeys, LOCAL_STORAGE_KEYS_ENUM } from '../../../../../enums'
10
+ import { FieldElementOptionSourceEnum, LOCAL_STORAGE_KEYS_ENUM } from '../../../../../enums'
11
11
  import { IFormContext } from '../../1-row'
12
12
  import { IOnSuccessFunctions } from '.'
13
13
 
@@ -18,67 +18,58 @@ export const useGenerateReportAction = ({
18
18
  onSuccess,
19
19
  onError,
20
20
  onFinal,
21
- formRef,
21
+ ...formContext
22
22
  }: IOnSuccessFunctions & IFormContext) => {
23
+ const { companyKey, formName, formDataId } = formContext
23
24
  const { warning } = useNotification()
24
25
  const [isModalOpen, setIsModalOpen] = useState(false)
25
26
  const [selectedTemplate, setSelectedTemplate] = useState(undefined)
26
27
  const [templateReports, setTemplateReports] = useState<IFormTemplateReport[]>([])
27
28
  const [loading, setLoading] = useState(false)
28
29
  const [formData, setFormData] = useState<{ [key: string]: any }>({})
29
- const companyKeyRef = useRef('')
30
30
  const selectedDataFormIdRef = useRef<number | undefined>()
31
31
 
32
- const onGenerateReport = useCallback(
33
- async (formName: string, formDataId: string) => {
34
- setLoading(true)
35
- setIsModalOpen(true)
36
- try {
37
- const storedData = localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.DynamicForms)
38
- if (storedData) {
39
- const parsedData: IDynamicForm[] = JSON.parse(storedData)
40
- const form = parsedData.find((entry) => entry.name === formName)
32
+ const onGenerateReport = useCallback(async () => {
33
+ setLoading(true)
34
+ setIsModalOpen(true)
35
+ try {
36
+ const storedData = localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.DynamicForms)
37
+ if (storedData) {
38
+ const parsedData: IDynamicForm[] = JSON.parse(storedData)
39
+ const form = parsedData.find((entry) => entry.name.toLowerCase() === formName)
41
40
 
42
- if (form) {
43
- selectedDataFormIdRef.current = form.id
44
- client
45
- .get(`/api/form/${form.id}`)
46
- .then((res) => {
47
- if (res.status === 200) {
48
- const parsedFormData: IFormSchema | null = parseJSON(res.data.data)
49
- if (parsedFormData) {
50
- const preservedFormValues = formRef?.getFieldsValue(true) ?? {}
51
- companyKeyRef.current = preservedFormValues.companyKey ?? ''
52
-
53
- const formDataListFormKey =
54
- preservedFormValues[`${FormPreservedItemKeys.FormDataListElementKey}_${form.id}`]
55
-
56
- if (formDataListFormKey) {
57
- const formDataList = preservedFormValues[formDataListFormKey] ?? []
58
- const formDataRowFormData =
59
- formDataList.find((d: { [key: string]: string | number }) => d.id === formDataId) ?? {}
60
-
61
- setFormData(formDataRowFormData)
62
- } else {
63
- const mainFormData = formRef?.getFieldsValue() ?? {}
64
- setFormData(mainFormData)
65
- }
66
-
67
- const reports = parsedFormData.generateConfig.templateReports ?? []
68
- setTemplateReports(reports)
69
- }
41
+ if (form) {
42
+ selectedDataFormIdRef.current = form.id
43
+ client
44
+ .get(`/api/form/${form.id}`)
45
+ .then((res) => {
46
+ if (res.status === 200) {
47
+ const parsedFormData: IFormSchema | null = parseJSON(res.data.data)
48
+ if (parsedFormData) {
49
+ const reports = parsedFormData.generateConfig.templateReports ?? []
50
+ if (reports.length)
51
+ client
52
+ .post(`/api/report/data/${form.id}`, {
53
+ joins: parsedFormData.generateConfig.formJoins,
54
+ match: JSON.stringify({ $expr: { $eq: [`$_id`, { $toObjectId: formDataId }] } }),
55
+ })
56
+ .then((res) => {
57
+ if (res.status === 200) {
58
+ setFormData(res.data[0])
59
+ setTemplateReports(reports)
60
+ }
61
+ })
70
62
  }
71
- })
72
- .finally(() => setLoading(false))
73
- } else setLoading(false)
63
+ }
64
+ })
65
+ .finally(() => setLoading(false))
74
66
  } else setLoading(false)
75
- } catch (error) {
76
- console.error('Error reading or parsing localStorage data', error)
77
- setLoading(false)
78
- }
79
- },
80
- [templateReports, formRef],
81
- )
67
+ } else setLoading(false)
68
+ } catch (error) {
69
+ console.error('Error reading or parsing localStorage data', error)
70
+ setLoading(false)
71
+ }
72
+ }, [templateReports, formName, formDataId])
82
73
 
83
74
  const ChooseTemplateReportModal = isModalOpen ? (
84
75
  <Modal
@@ -98,14 +89,11 @@ export const useGenerateReportAction = ({
98
89
  title={selectedTemplate ? '' : 'Please select a template to continue!'}
99
90
  loading={loading}
100
91
  onClick={async () => {
101
- if (!companyKeyRef.current) {
92
+ if (!companyKey) {
102
93
  warning({ message: 'Company was not found!' })
103
94
  return
104
95
  }
105
96
 
106
- const { Data, ...restFormData } = formData
107
- const selectedFormData = { ...restFormData, ...Data }
108
-
109
97
  setLoading(true)
110
98
  const selectedTemplateInfo: IFormTemplateReport = templateReports.find(
111
99
  (t: IFormTemplateReport) => t.fileBlobName === selectedTemplate,
@@ -113,21 +101,24 @@ export const useGenerateReportAction = ({
113
101
 
114
102
  const replacements = await Promise.all(
115
103
  selectedTemplateInfo.replacements.map(async (rep) => {
104
+ const value = rep.field.split('.').reduce((curr, n) => curr[n] ?? {}, formData)
105
+
116
106
  if (!!rep.optionSource) {
117
107
  if (rep.optionSource.type === FieldElementOptionSourceEnum.Static) {
118
108
  return {
119
109
  placeholder: rep.placeholder,
120
110
  value: renderData(
121
- rep.optionSource.options.find((op) => op.id === selectedFormData[rep.field])?.value,
111
+ rep.optionSource.options.find((op) => op.id === (value as unknown as string))?.value,
122
112
  rep.renderConfig,
123
113
  ),
124
114
  }
125
115
  } else if (rep.optionSource.type === FieldElementOptionSourceEnum.DynamicForm) {
126
116
  const formDataResData = await fetchFormDataAsLookup(rep.optionSource.form.id)
117
+
127
118
  return {
128
119
  placeholder: rep.placeholder,
129
120
  value: renderData(
130
- formDataResData.find((data) => data.id === selectedFormData[rep.field])?.[
121
+ formDataResData.find((data) => data.id === (value as unknown as string))?.[
131
122
  rep.optionSource.form.field
132
123
  ],
133
124
  rep.renderConfig,
@@ -137,7 +128,7 @@ export const useGenerateReportAction = ({
137
128
  }
138
129
  return {
139
130
  placeholder: rep.placeholder,
140
- value: renderData(selectedFormData[rep.field], rep.renderConfig),
131
+ value: renderData(value, rep.renderConfig),
141
132
  type: rep.type,
142
133
  }
143
134
  }),
@@ -145,10 +136,10 @@ export const useGenerateReportAction = ({
145
136
 
146
137
  client
147
138
  .post(
148
- `/api/attachment/template/${companyKeyRef.current}/${selectedTemplateInfo.templateName}`,
139
+ `/api/attachment/template/${companyKey}/${selectedTemplateInfo.fileBlobName}`,
149
140
  replacements.map((r) => ({
150
141
  key: r.placeholder,
151
- value: r.value,
142
+ value: r.value?.toString() ?? '-',
152
143
  type: r.type,
153
144
  })),
154
145
  { responseType: 'blob' },
@@ -8,8 +8,8 @@ import { IFormContext } from '../../1-row'
8
8
  /* --------------------------------------------------------------------------
9
9
  Saves a signature by converting base64 data to a file.
10
10
  -------------------------------------------------------------------------- */
11
- export const useSaveSignatureAction = ({ formRef }: IFormContext) => {
12
- const { companyKey, isPublic } = useFormPreservedItemValues(formRef)
11
+ export const useSaveSignatureAction = ({ formRef, companyKey }: IFormContext) => {
12
+ const { isPublic } = useFormPreservedItemValues(formRef)
13
13
 
14
14
  const saveSignature = useCallback(async () => {
15
15
  if (!formRef) return
@@ -12,11 +12,11 @@ export default function LayoutRenderer_ReadFieldData({
12
12
  elementProps,
13
13
  style = {},
14
14
  }: ILayoutRenderer_ReadFieldData) {
15
- const { formRef } = formContext
15
+ const { formRef, companyKey } = formContext
16
16
  const fieldValue = elementProps.isValueEvaluated
17
17
  ? undefined
18
18
  : Form.useWatch(elementProps.field, { form: formRef, preserve: true })
19
- const { baseServerUrl, companyKey } = useFormPreservedItemValues(formRef)
19
+ const { baseServerUrl } = useFormPreservedItemValues(formRef)
20
20
 
21
21
  const formValues = Form.useWatch([], { form: formRef, preserve: true })
22
22
 
@@ -15,10 +15,10 @@ export default function LayoutRenderer_Signature({
15
15
  isDisabled,
16
16
  formContext,
17
17
  }: ILayoutRenderer_Signature) {
18
- const { formRef, formDataId } = formContext
18
+ const { formRef, formDataId, companyKey } = formContext
19
19
  const sigCanvasRef = useRef<SignatureCanvas>(null)
20
20
  const savedSignatureBlobName = Form.useWatch(formItem.path, formRef)
21
- const { isGeneratingPdf, baseServerUrl, companyKey, inPreviewMode } = useFormPreservedItemValues(formRef)
21
+ const { isGeneratingPdf, baseServerUrl, inPreviewMode } = useFormPreservedItemValues(formRef)
22
22
 
23
23
  useEffect(() => {
24
24
  if (isDisabled) sigCanvasRef.current?.off()
@@ -8,7 +8,6 @@ import { useNotification } from '../../../common/custom-hooks'
8
8
  import { FaTrashAlt } from 'react-icons/fa'
9
9
  import { saveFile } from '../../../../functions/forms/form'
10
10
  import { IElementBaseProps } from '.'
11
- import { useFormPreservedItemValues } from '../../../common/custom-hooks/use-preserved-form-items.hook'
12
11
  import { IFormContext } from '../1-row'
13
12
 
14
13
  export default function LayoutRenderer_FileUpload({
@@ -18,13 +17,11 @@ export default function LayoutRenderer_FileUpload({
18
17
  uploadRules,
19
18
  isDisabled,
20
19
  }: ILayoutRenderer_FileUpload) {
21
- const { formRef } = formContext
20
+ const { formRef, companyKey } = formContext
22
21
  const { warningModal, confirmModal } = useNotification()
23
22
  const [loading, setLoading] = useState(false)
24
23
  const savedSignatureBlobName = Form.useWatch(formItem.path, formRef)
25
24
 
26
- const { companyKey } = useFormPreservedItemValues(formRef)
27
-
28
25
  const deleteSavedFile = useCallback(
29
26
  (blobName: string) => {
30
27
  setLoading(true)
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef, useState } from 'react'
3
3
  import FormDataListTableComponent from '../../1-list/table'
4
4
  import FormDataListSkeleton_Table from '../../../common/loading-skeletons/table'
5
5
  import { NEW_FORM_DATA_IDENTIFIER } from '../../../../constants'
6
- import { FilterConfigTypeEnum, FormPreservedItemKeys } from '../../../../enums'
6
+ import { FilterConfigTypeEnum } from '../../../../enums'
7
7
  import client from '../../../../functions/axios-handler'
8
8
  import { IFormContext } from '../1-row'
9
9
  import {
@@ -135,10 +135,7 @@ export default function LayoutRenderer_LoadFormData({
135
135
  if (res.status === 200) {
136
136
  const resData = res.data
137
137
 
138
- if (!tableHeaderFilters) {
139
- formRef?.setFieldValue(`${FormPreservedItemKeys.FormDataListElementKey}_${lastChildId}`, formItem.path)
140
- formRef?.setFieldValue(formItem.path, resData)
141
-
138
+ if (!tableHeaderFilters)
142
139
  lastChildInfo.current = {
143
140
  ...lastChildInfo.current,
144
141
  parentInfo: {
@@ -159,7 +156,6 @@ export default function LayoutRenderer_LoadFormData({
159
156
  ),
160
157
  },
161
158
  }
162
- }
163
159
 
164
160
  setDataList({ data: resData, total: resData.length })
165
161
  }
@@ -178,13 +178,11 @@ export enum FormElementConditionalKeyEnum {
178
178
  }
179
179
  export enum FormPreservedItemKeys {
180
180
  BaseServerUrl = 'baseServerUrl',
181
- CompanyKey = 'companyKey',
182
181
  SubmissionPdfConfig = 'submissionPdfConfig',
183
182
  IsGeneratingPDF = 'isGeneratingPdf',
184
183
  HasSignature = 'hasSignature',
185
184
  InPreviewMode = 'inPreviewMode',
186
185
  IsPublic = 'isPublic',
187
- FormDataListElementKey = 'formDataListElementKey',
188
186
  }
189
187
  export enum DataCategoryEnum {
190
188
  Number = 'Number',
@@ -11,31 +11,22 @@ const FONTS = [
11
11
  ]
12
12
 
13
13
  export default function FontSelector({ onFontSelect, initialFont }: FontSelectorProps) {
14
- const [selectedFont, setSelectedFont] = useState(initialFont)
15
- const [isFontLoaded, setIsFontLoaded] = useState(false) // Track if font is loaded
16
-
14
+ const [selectedFont, setSelectedFont] = useState(initialFont || 'Inter')
17
15
  useEffect(() => {
18
- const selectedFontData = FONTS.find((f) => f.name === selectedFont)
19
- if (selectedFontData) {
20
- const fontUrl = `https://fonts.googleapis.com/css2?family=${encodeURIComponent(
21
- selectedFontData.importName,
22
- )}:wght@400;700&display=swap`
23
-
24
- // Remove previously loaded fonts
25
- document.querySelectorAll('link[data-font]').forEach((link) => link.remove())
16
+ setSelectedFont(initialFont || 'Inter')
17
+ }, [initialFont])
18
+ useEffect(() => {
19
+ const font = FONTS.find((f) => f.name === selectedFont)
20
+ if (!font) return
26
21
 
27
- // Append new font link
28
- const link = document.createElement('link')
29
- link.rel = 'stylesheet'
30
- link.href = fontUrl
31
- link.setAttribute('data-font', selectedFontData.name)
32
- document.head.appendChild(link)
22
+ const id = `google-font-${font.importName}`
23
+ if (document.getElementById(id)) return
33
24
 
34
- // Ensure the font is actually loaded before applying it
35
- document.fonts.ready.then(() => {
36
- setIsFontLoaded(true) // Mark font as loaded
37
- })
38
- }
25
+ const link = document.createElement('link')
26
+ link.id = id
27
+ link.rel = 'stylesheet'
28
+ link.href = `https://fonts.googleapis.com/css2?family=${font.importName}&display=swap`
29
+ document.head.appendChild(link)
39
30
  }, [selectedFont])
40
31
 
41
32
  return (
@@ -51,9 +42,8 @@ export default function FontSelector({ onFontSelect, initialFont }: FontSelector
51
42
  ? 'border-primary bg-primary text-white shadow-lg scale-105'
52
43
  : 'border-gray-300 bg-white'
53
44
  }`}
54
- style={{ fontFamily: isFontLoaded ? font.name : 'inherit' }}
45
+ style={{ fontFamily: font.name }}
55
46
  onClick={() => {
56
- setIsFontLoaded(false)
57
47
  setSelectedFont(font.name)
58
48
  onFontSelect(font.name)
59
49
  }}
@@ -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, FormInstance, Image } from 'antd'
4
+ import { Dropdown, 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'
@@ -18,13 +18,14 @@ import {
18
18
  IFormDataListColumn,
19
19
  ITableColumn,
20
20
  } from '../../types'
21
+ import { IDataListHeaderContext } from '../../components/form/1-list/table'
21
22
 
22
23
  export const generateTableColumns = (
23
24
  elements: IFormDataListColumn[],
24
25
  optionKeyValuePair: { [key: string]: string },
25
- contextValues: { attachmentBaseUrl?: string; formName?: string; formRef?: FormInstance } = {},
26
+ contextValues: IDataListHeaderContext = {},
26
27
  ) => {
27
- const { attachmentBaseUrl, formName, formRef } = contextValues
28
+ const { attachmentBaseUrl } = contextValues
28
29
 
29
30
  return elements.map((el) => {
30
31
  const col: ITableColumn = {
@@ -64,7 +65,7 @@ export const generateTableColumns = (
64
65
  label: (
65
66
  <DynamicFormButtonRender
66
67
  displayStateProps={{ btnProps }}
67
- formContext={{ formRef, formDataId: rowData?.id, formName }}
68
+ formContext={{ formDataId: rowData?.id, ...contextValues }}
68
69
  />
69
70
  ),
70
71
  })),
@@ -80,7 +81,7 @@ export const generateTableColumns = (
80
81
  ) : (
81
82
  <DynamicFormButtonRender
82
83
  displayStateProps={{ btnProps: (el.renderConfig as IDataRender_Buttons).buttons[0] }}
83
- formContext={{ formRef, formDataId: rowData?.id, formName }}
84
+ formContext={{ formDataId: rowData?.id, ...contextValues }}
84
85
  />
85
86
  )}
86
87
  </div>
@@ -23,7 +23,14 @@ export interface ISiteMenuItem {
23
23
  customLink?: string
24
24
  children?: ISiteMenuItem[]
25
25
  }
26
-
26
+ export interface IExtendedSiteMenuItem extends ISiteMenuItem {
27
+ isMenuGroup?: boolean;
28
+ }
29
+ export interface MenuBuilderProps {
30
+ items: ISiteMenuItem[]
31
+ onChange?: (menu: ISiteMenuItem[]) => void
32
+ initialValue: ISiteMenuItem[]
33
+ }
27
34
 
28
35
  export type ISiteMenus = ISiteMenuItem[]
29
36
  export interface IPrivateConfig {
@@ -1,9 +1,9 @@
1
1
  import { FaCaretDown, FaTimes, FaUser } from 'react-icons/fa'
2
2
  import { HiMenu } from 'react-icons/hi'
3
- import { ICompanyConfig, ILayoutTemplateProps, ISiteMenuItem } from '../..'
3
+ import { ICompanyConfig, ILayoutTemplateProps, IExtendedSiteMenuItem } from '../..'
4
4
  import { Link } from 'react-router-dom'
5
5
  import { constructDynamicFormHref, isEncodedURI } from '../../../../functions/forms'
6
- import { useState } from 'react'
6
+ import { useEffect, useState } from 'react'
7
7
  import { Button, Drawer, Dropdown, Layout, Menu } from 'antd'
8
8
  import * as FaIcons from 'react-icons/fa'
9
9
 
@@ -16,14 +16,31 @@ export const layoutTemplates = [
16
16
  const siteConfigs = config?.siteLayout?.siteConfigs
17
17
  const navigationWidth = siteConfigs?.custom?.navigationWidth || 200
18
18
  const contentPadding = siteConfigs?.custom?.contentPadding || 20
19
- const renderMenuItem = (form: ISiteMenuItem, level = 0) => {
20
- const href = constructDynamicFormHref(form.name)
19
+ const [expandedMenus, setExpandedMenus] = useState<{ [key: number]: boolean }>({})
20
+ useEffect(() => {
21
+ const initialExpandedStates: Record<number, boolean> = {}
22
+ config?.siteMenus?.forEach((item) => {
23
+ if (item.children && item.children.length > 0) {
24
+ initialExpandedStates[item.id] = true
25
+ }
26
+ })
27
+ setExpandedMenus(initialExpandedStates)
28
+ }, [config])
29
+
30
+ const renderMenuItem = (form: IExtendedSiteMenuItem, level = 0) => {
31
+ let href = form.customLink === '' ? '#' : form.customLink || constructDynamicFormHref(form.name)
32
+ let isParentMenu = href === '#' && form.children && form.children.length > 0
21
33
  const IconComponent = form.icon ? (FaIcons as any)[form.icon] : null
34
+ const isExpanded = expandedMenus[form.id]
22
35
 
36
+ const toggleMenu = (id: number) => {
37
+ setExpandedMenus((prev) => ({ ...prev, [id]: !prev[id] }))
38
+ }
23
39
  return (
24
- <div key={form.id}>
40
+ <div key={form.id} className="menu-item">
25
41
  <Link
26
42
  to={href}
43
+ onClick={isParentMenu ? () => toggleMenu(form.id) : undefined}
27
44
  style={{
28
45
  width: `${navigationWidth - 25}px`,
29
46
  borderRadius: `${siteConfigs?.Link.borderRadius}px`,
@@ -35,6 +52,9 @@ export const layoutTemplates = [
35
52
  : siteConfigs?.Link.colors.text,
36
53
  paddingLeft: `${level * 16 + 20}px`,
37
54
  }}
55
+ className={`flex items-center gap-x-3 py-2 px-5 text-16 leading-4 transition-all duration-200 ${
56
+ isActive(window.location.pathname, href) ? 'bg-opacity-25 text-opacity-25 font-semibold' : ''
57
+ }`}
38
58
  onMouseEnter={(e) => {
39
59
  e.currentTarget.style.backgroundColor = siteConfigs?.Link.colors.hoverBackground || ''
40
60
  e.currentTarget.style.color = siteConfigs?.Link.colors.hoverText || ''
@@ -47,24 +67,26 @@ export const layoutTemplates = [
47
67
  ? siteConfigs?.Link.colors.activeText || ''
48
68
  : siteConfigs?.Link.colors.text || ''
49
69
  }}
50
- className={`flex items-center gap-x-3 py-2 px-5 text-16 leading-4 transition-all duration-200 bg-white text-primary hover:bg-opacity-25 hover:text-opacity-25 ${
51
- isActive(window.location.pathname, href) ? `font-semibold` : ``
52
- }`}
53
70
  >
54
71
  <div className="w-6 h-6 flex items-center justify-center flex-shrink-0">
55
72
  {IconComponent ? (
56
- <IconComponent className="w-4 h-4 text-black" />
73
+ <IconComponent className="w-4 h-4" />
57
74
  ) : (
58
75
  <img alt="" src={form.icon ?? ''} className="w-4 h-4 object-contain" />
59
76
  )}
60
77
  </div>
61
- <span className="flex-grow ">{form.name}</span>
78
+ <span className="flex-grow">{form.name}</span>
79
+ {isParentMenu &&
80
+ (isExpanded ? <FaIcons.FaAngleUp className="w-4 h-4" /> : <FaIcons.FaAngleDown className="w-4 h-4" />)}
62
81
  </Link>
63
82
 
64
- {form?.children?.map((child) => renderMenuItem(child, level + 1))}
83
+ {form.children && expandedMenus[form.id] && (
84
+ <div className="submenu">{form.children.map((child) => renderMenuItem(child, level + 1))}</div>
85
+ )}
65
86
  </div>
66
87
  )
67
88
  }
89
+
68
90
  /* Profile Menu */
69
91
  const menu = (
70
92
  <Menu>
@@ -76,7 +98,7 @@ export const layoutTemplates = [
76
98
  </Menu>
77
99
  )
78
100
  return (
79
- <Layout>
101
+ <Layout className="h-screen overflow-y-auto overflow-x-hidden">
80
102
  <Header className="sticky top-0 z-40 flex items-center shadow-sm justify-between px-1">
81
103
  {/* Logo */}
82
104
  <div className="flex p-2">
@@ -105,7 +127,7 @@ export const layoutTemplates = [
105
127
  }
106
128
  >
107
129
  {config?.siteIdentity && config?.siteLayout && config?.siteMenus && (
108
- <div className="flex flex-col">
130
+ <div className="flex flex-col overflow-y-auto">
109
131
  {navigationTemplates
110
132
  .find((template) => template.name === 'navigation_1')
111
133
  ?.value({
@@ -131,9 +153,9 @@ export const layoutTemplates = [
131
153
  </Dropdown>
132
154
  </div>
133
155
  </Header>
134
- <Layout>
156
+ <Layout className="overflow-hidden">
135
157
  {/* Side Menu */}
136
- <Sider width={navigationWidth} className={`${isPreview ? 'flex' : 'hidden lg:flex'} h-screen`}>
158
+ <Sider width={navigationWidth} className={`${isPreview ? 'flex' : 'hidden lg:flex'}`}>
137
159
  <div className="flex flex-col px-1 gap-2 py-2">
138
160
  {config?.siteMenus ? (
139
161
  config?.siteMenus?.map((form) => renderMenuItem(form))
@@ -145,7 +167,9 @@ export const layoutTemplates = [
145
167
  </div>
146
168
  </Sider>
147
169
  {/* Content */}
148
- <Content style={{ padding: `${contentPadding}px` }}>{children}</Content>
170
+ <Content style={{ padding: `${contentPadding}px` }} className="overflow-y-auto">
171
+ {children}
172
+ </Content>
149
173
  </Layout>
150
174
  </Layout>
151
175
  )
@@ -184,8 +208,15 @@ export const layoutTemplates = [
184
208
  const siteConfigs = config?.siteLayout?.siteConfigs
185
209
  const contentPadding = siteConfigs?.custom?.contentPadding || 20
186
210
  const navigationWidth = siteConfigs?.custom?.navigationWidth || 200
187
- const renderMenuItem = (form: ISiteMenuItem, level = 0) => {
188
- const href = constructDynamicFormHref(form.name)
211
+
212
+ const renderMenuItem = (form: IExtendedSiteMenuItem, level = 0) => {
213
+ let href = ''
214
+ if (form.customLink) {
215
+ href = form.customLink
216
+ } else {
217
+ href = constructDynamicFormHref(form.name)
218
+ }
219
+
189
220
  const IconComponent = form.icon ? (FaIcons as any)[form.icon] : null
190
221
 
191
222
  const hasChildren = form.children && form.children.length > 0
@@ -193,6 +224,7 @@ export const layoutTemplates = [
193
224
  return (
194
225
  <div key={form.id} className="relative group">
195
226
  <Link
227
+ target={form.customLink ? '_blank' : '_self'}
196
228
  to={href}
197
229
  style={{
198
230
  width: `${navigationWidth - 25}px`,
@@ -223,7 +255,7 @@ export const layoutTemplates = [
223
255
  >
224
256
  <div className="w-6 h-6 flex items-center justify-center flex-shrink-0">
225
257
  {IconComponent ? (
226
- <IconComponent className="w-4 h-4 text-black" />
258
+ <IconComponent className="w-4 h-4 " />
227
259
  ) : (
228
260
  <img alt="" src={form.icon ?? ''} className="w-4 h-4 object-contain" />
229
261
  )}
@@ -352,14 +384,30 @@ export const layoutTemplates = [
352
384
  const navigationWidth = siteConfigs?.custom?.navigationWidth || 200
353
385
  const contentPadding = siteConfigs?.custom?.contentPadding || 20
354
386
  const isCollapsed = navigationWidth < 200
355
- const renderMenuItem = (form: ISiteMenuItem, level = 0) => {
356
- const href = constructDynamicFormHref(form.name)
387
+ const [expandedMenus, setExpandedMenus] = useState<{ [key: number]: boolean }>({})
388
+ useEffect(() => {
389
+ const initialExpandedStates: Record<number, boolean> = {}
390
+ config?.siteMenus?.forEach((item) => {
391
+ if (item.children && item.children.length > 0) {
392
+ initialExpandedStates[item.id] = true
393
+ }
394
+ })
395
+ setExpandedMenus(initialExpandedStates)
396
+ }, [config])
397
+ const renderMenuItem = (form: IExtendedSiteMenuItem, level = 0) => {
398
+ let href = form.customLink === '' ? '#' : form.customLink || constructDynamicFormHref(form.name)
399
+ let isParentMenu = href === '#' && form.children && form.children.length > 0
357
400
  const IconComponent = form.icon ? (FaIcons as any)[form.icon] : null
401
+ const isExpanded = expandedMenus[form.id]
358
402
 
403
+ const toggleMenu = (id: number) => {
404
+ setExpandedMenus((prev) => ({ ...prev, [id]: !prev[id] }))
405
+ }
359
406
  return (
360
- <div key={form.id}>
407
+ <div key={form.id} className="menu-item">
361
408
  <Link
362
409
  to={href}
410
+ onClick={isParentMenu ? () => toggleMenu(form.id) : undefined}
363
411
  style={{
364
412
  width: `${navigationWidth - 25}px`,
365
413
  borderRadius: `${siteConfigs?.Link.borderRadius}px`,
@@ -371,6 +419,9 @@ export const layoutTemplates = [
371
419
  : siteConfigs?.Link.colors.text,
372
420
  paddingLeft: `${level * 16 + 20}px`,
373
421
  }}
422
+ className={`flex items-center gap-x-3 py-2 px-5 text-16 leading-4 transition-all duration-200 ${
423
+ isActive(window.location.pathname, href) ? 'bg-opacity-25 text-opacity-25 font-semibold' : ''
424
+ }`}
374
425
  onMouseEnter={(e) => {
375
426
  e.currentTarget.style.backgroundColor = siteConfigs?.Link.colors.hoverBackground || ''
376
427
  e.currentTarget.style.color = siteConfigs?.Link.colors.hoverText || ''
@@ -383,20 +434,22 @@ export const layoutTemplates = [
383
434
  ? siteConfigs?.Link.colors.activeText || ''
384
435
  : siteConfigs?.Link.colors.text || ''
385
436
  }}
386
- className={`flex items-center gap-x-3 py-2 px-5 text-16 leading-4 transition-all duration-200 bg-white text-primary hover:bg-opacity-25 hover:text-opacity-25 ${
387
- isActive(window.location.pathname, href) ? `font-semibold` : ``
388
- }`}
389
437
  >
390
438
  <div className="w-6 h-6 flex items-center justify-center flex-shrink-0">
391
439
  {IconComponent ? (
392
- <IconComponent className="w-4 h-4 text-black" />
440
+ <IconComponent className="w-4 h-4" />
393
441
  ) : (
394
442
  <img alt="" src={form.icon ?? ''} className="w-4 h-4 object-contain" />
395
443
  )}
396
444
  </div>
397
445
  <span className="flex-grow">{form.name}</span>
446
+ {isParentMenu &&
447
+ (isExpanded ? <FaIcons.FaAngleUp className="w-4 h-4" /> : <FaIcons.FaAngleDown className="w-4 h-4" />)}
398
448
  </Link>
399
- {form?.children?.map((child) => renderMenuItem(child, level + 1))}
449
+
450
+ {form.children && expandedMenus[form.id] && (
451
+ <div className="submenu">{form.children.map((child) => renderMenuItem(child, level + 1))}</div>
452
+ )}
400
453
  </div>
401
454
  )
402
455
  }
@@ -554,7 +607,7 @@ export const navigationTemplates = [
554
607
  renderMenuItem,
555
608
  }: {
556
609
  config: ICompanyConfig
557
- renderMenuItem: (form: ISiteMenuItem, level?: number) => JSX.Element
610
+ renderMenuItem: (form: IExtendedSiteMenuItem, level?: number) => JSX.Element
558
611
  }) => {
559
612
  return (
560
613
  <div className="flex flex-col">
@@ -57,6 +57,7 @@ export interface IFormDataListConfig {
57
57
  export interface IFormGenerateConfig {
58
58
  submissionPdf?: IFormSubmissionPdf
59
59
  templateReports?: IFormTemplateReport[]
60
+ formJoins?: IFormJoin[]
60
61
  }
61
62
  export interface IFormLayoutRow {
62
63
  nodeType: FormLayoutNodeEnum.Row
@@ -106,3 +107,10 @@ export type IFormLayoutElement =
106
107
  | IReCaptchaElement
107
108
  | IFormDataLoadElement
108
109
  | IColorPickerElement
110
+
111
+ export interface IFormJoin {
112
+ formId: number
113
+ foreignField: string
114
+ localField: string
115
+ alias: string
116
+ }