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 +1 -1
- package/src/components/common/custom-hooks/use-preserved-form-items.hook.ts +0 -2
- package/src/components/form/1-list/index.tsx +1 -1
- package/src/components/form/1-list/table.tsx +4 -3
- package/src/components/form/2-details/index.tsx +3 -4
- package/src/components/form/layout-renderer/1-row/index.tsx +1 -0
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +1 -2
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-create-data.hook.ts +4 -5
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-generate-report.hook.tsx +50 -59
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-save-signature.hook.ts +2 -2
- package/src/components/form/layout-renderer/3-element/3-read-field-data.tsx +2 -2
- package/src/components/form/layout-renderer/3-element/6-signature.tsx +2 -2
- package/src/components/form/layout-renderer/3-element/7-file-upload.tsx +1 -4
- package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +2 -6
- package/src/enums/form.enum.ts +0 -2
- package/src/functions/companies/FontSelector.tsx +14 -24
- package/src/functions/forms/data-render-functions.tsx +6 -5
- package/src/types/companies/index.ts +8 -1
- package/src/types/companies/site-layout/authenticated/index.tsx +81 -28
- package/src/types/forms/index.ts +8 -0
package/package.json
CHANGED
|
@@ -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
|
|
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,
|
|
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,
|
|
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,
|
|
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 />
|
|
@@ -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(
|
|
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,
|
package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-create-data.hook.ts
CHANGED
|
@@ -19,19 +19,18 @@ export const useCreateDataWithPdfActions = ({
|
|
|
19
19
|
onError: onCreateError,
|
|
20
20
|
onFinal: onCreateFinal,
|
|
21
21
|
onGenerateError,
|
|
22
|
-
|
|
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,
|
|
33
|
+
const { baseServerUrl, submissionPdfConfig, isPublic } = useFormPreservedItemValues(formRef)
|
|
35
34
|
|
|
36
35
|
const onCreateNewData = useCallback(
|
|
37
36
|
(generatedPdfBlobName?: string) => {
|
package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-generate-report.hook.tsx
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
73
|
-
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
.finally(() => setLoading(false))
|
|
74
66
|
} else setLoading(false)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
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 (!
|
|
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 ===
|
|
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 ===
|
|
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(
|
|
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/${
|
|
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' },
|
package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-save-signature.hook.ts
CHANGED
|
@@ -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 {
|
|
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
|
|
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,
|
|
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
|
|
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
|
}
|
package/src/enums/form.enum.ts
CHANGED
|
@@ -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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
28
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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:
|
|
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,
|
|
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:
|
|
26
|
+
contextValues: IDataListHeaderContext = {},
|
|
26
27
|
) => {
|
|
27
|
-
const { attachmentBaseUrl
|
|
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={{
|
|
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={{
|
|
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,
|
|
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
|
|
20
|
-
|
|
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
|
|
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
|
|
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
|
|
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'}
|
|
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` }}>
|
|
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
|
-
|
|
188
|
-
|
|
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
|
|
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
|
|
356
|
-
|
|
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
|
|
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
|
-
|
|
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:
|
|
610
|
+
renderMenuItem: (form: IExtendedSiteMenuItem, level?: number) => JSX.Element
|
|
558
611
|
}) => {
|
|
559
612
|
return (
|
|
560
613
|
<div className="flex flex-col">
|
package/src/types/forms/index.ts
CHANGED
|
@@ -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
|
+
}
|