form-craft-package 1.8.2-dev.1 → 1.8.2-dev.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 +1 -1
- package/src/components/common/custom-hooks/index.ts +2 -0
- package/src/components/common/custom-hooks/use-login-handler.ts +3 -3
- package/src/components/common/custom-hooks/use-node-condition.hook/use-disabled-elements.hook.ts +62 -22
- package/src/components/common/custom-hooks/use-node-condition.hook/use-hidden-elements.hook.ts +54 -42
- package/src/components/common/custom-hooks/use-set-persistent-domain.hook.ts +20 -0
- package/src/components/common/custom-hooks/use-translation.hook/hook.ts +28 -0
- package/src/components/common/custom-hooks/use-translation.hook/store.ts +130 -0
- package/src/components/companies/3-config-provider/index.tsx +27 -2
- package/src/components/form/1-list/index.tsx +7 -0
- package/src/components/form/2-details/index.tsx +21 -5
- package/src/components/form/layout-renderer/1-row/header-render.tsx +12 -2
- package/src/components/form/layout-renderer/1-row/header.tsx +1 -0
- package/src/components/form/layout-renderer/1-row/index.tsx +12 -2
- package/src/components/form/layout-renderer/1-row/repeatable-render.tsx +83 -28
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +32 -14
- package/src/components/form/layout-renderer/3-element/10-currency.tsx +16 -3
- package/src/components/form/layout-renderer/3-element/11-breadcrumb.tsx +17 -7
- package/src/components/form/layout-renderer/3-element/12-picker-field.tsx +14 -13
- package/src/components/form/layout-renderer/3-element/13-language-selector.tsx +42 -0
- package/src/components/form/layout-renderer/3-element/2-field-element.tsx +223 -231
- package/src/components/form/layout-renderer/3-element/3-read-field-data.tsx +12 -5
- package/src/components/form/layout-renderer/3-element/4-rich-text-editor.tsx +14 -10
- package/src/components/form/layout-renderer/3-element/5-re-captcha.tsx +4 -7
- package/src/components/form/layout-renderer/3-element/6-signature.tsx +11 -6
- package/src/components/form/layout-renderer/3-element/7-file-upload.tsx +118 -32
- package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +29 -8
- package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +11 -2
- package/src/components/form/layout-renderer/3-element/index.tsx +145 -86
- package/src/constants.ts +2 -1
- package/src/enums/form.enum.ts +4 -0
- package/src/enums/index.ts +29 -0
- package/src/functions/companies/index.tsx +12 -0
- package/src/functions/companies/use-company-config.tsx +27 -12
- package/src/functions/forms/create-form-rules.ts +18 -6
- package/src/functions/forms/data-render-functions.tsx +44 -24
- package/src/functions/forms/get-element-props.ts +2 -2
- package/src/functions/forms/index.ts +3 -1
- package/src/types/companies/site-layout/authenticated/index.tsx +5 -2
- package/src/types/forms/data-list/index.ts +1 -0
- package/src/types/forms/index.ts +17 -5
- package/src/types/forms/layout-elements/button.ts +3 -12
- package/src/types/forms/layout-elements/index.ts +10 -42
- package/src/types/forms/layout-elements/read-field-data-props.ts +0 -1
- package/src/types/forms/layout-elements/validation.ts +0 -1
- package/src/types/index.ts +2 -0
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { useReducer, useState } from 'react'
|
|
2
2
|
import { useNavigate } from 'react-router-dom'
|
|
3
3
|
import client, { auth } from '../../../api/client'
|
|
4
|
-
import { LOCAL_STORAGE_KEYS_ENUM } from '../../../enums'
|
|
5
4
|
import { cookieHandler } from '../../../functions/cookie-handler'
|
|
6
5
|
import { message } from 'antd'
|
|
6
|
+
import { getLocalStorageDomain } from '../../../functions'
|
|
7
7
|
export function useLoginHandler() {
|
|
8
8
|
const navigate = useNavigate()
|
|
9
|
-
const domain =
|
|
9
|
+
const domain = getLocalStorageDomain()
|
|
10
10
|
const [loading, updateLoading] = useReducer((currState: any, updated: any) => ({ ...currState, ...updated }), {
|
|
11
11
|
login: false,
|
|
12
12
|
})
|
|
@@ -27,7 +27,7 @@ export function useLoginHandler() {
|
|
|
27
27
|
const { authRes, initialPath } = await auth(
|
|
28
28
|
values.email,
|
|
29
29
|
values.password,
|
|
30
|
-
|
|
30
|
+
getLocalStorageDomain(),
|
|
31
31
|
values.type || 'password',
|
|
32
32
|
)
|
|
33
33
|
if (authRes.status === 200) {
|
package/src/components/common/custom-hooks/use-node-condition.hook/use-disabled-elements.hook.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { useEffect
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
2
|
import useGetCurrentBreakpoint from '../use-window-width.hook'
|
|
3
3
|
import { DeviceBreakpointEnum, FormElementConditionalKeyEnum } from '../../../../enums'
|
|
4
4
|
import { disabledStore } from './visibility-store'
|
|
5
5
|
import { isValidationsMet } from './visibility-utils'
|
|
6
|
+
import { FormInstance } from 'antd'
|
|
6
7
|
import {
|
|
7
8
|
IDndLayoutElement,
|
|
8
9
|
IDndLayoutRow,
|
|
@@ -12,16 +13,64 @@ import {
|
|
|
12
13
|
|
|
13
14
|
export function useSetDisabledElements(
|
|
14
15
|
layoutConfig?: IDndLayoutStructure_Responsive,
|
|
15
|
-
|
|
16
|
+
formDataRef?: FormInstance,
|
|
16
17
|
formDataId?: string,
|
|
17
18
|
): void {
|
|
18
19
|
const currentBreakpoint = useGetCurrentBreakpoint()
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
// during the initial renders, handle the initially disabled fields
|
|
23
|
+
if (!formDataRef) return
|
|
24
|
+
|
|
25
|
+
const allValues = formDataRef.getFieldsValue()
|
|
26
|
+
|
|
27
|
+
const layout: IDndLayoutRow[] =
|
|
28
|
+
layoutConfig?.layouts?.[currentBreakpoint] || layoutConfig?.layouts?.[DeviceBreakpointEnum.Default] || []
|
|
29
|
+
const elementsMap: Record<string, IDndLayoutElement> = layoutConfig?.elements || {}
|
|
30
|
+
const configsPerElement: Record<string, IFormLayoutElementConditions> = Object.values(elementsMap).reduce(
|
|
31
|
+
(acc, el) => {
|
|
32
|
+
const elConditions = el.conditions?.[FormElementConditionalKeyEnum.EnableIf]
|
|
33
|
+
if (elConditions && Array.isArray(elConditions) && elConditions.length > 0) acc[el.key] = el.conditions!
|
|
34
|
+
|
|
35
|
+
return acc
|
|
36
|
+
},
|
|
37
|
+
{} as Record<string, IFormLayoutElementConditions>,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
const allInputsLoaded =
|
|
41
|
+
layout.length > 0 &&
|
|
42
|
+
Object.keys(elementsMap).length > 0 &&
|
|
43
|
+
Object.keys(configsPerElement).length > 0 &&
|
|
44
|
+
Object.values(allValues).filter(Boolean).length > 0
|
|
45
|
+
|
|
46
|
+
if (!allInputsLoaded) return
|
|
47
|
+
|
|
48
|
+
handleDisabledSet(allValues, currentBreakpoint, layoutConfig, formDataId)
|
|
49
|
+
}, [formDataRef, layoutConfig, currentBreakpoint, formDataId])
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function handleDisabledSet(
|
|
53
|
+
allValues: Record<string, any>,
|
|
54
|
+
currentBreakpoint: DeviceBreakpointEnum,
|
|
55
|
+
layoutConfig?: IDndLayoutStructure_Responsive,
|
|
56
|
+
formDataId?: string,
|
|
57
|
+
) {
|
|
58
|
+
const disabledSet = computeDisabledSet(allValues, currentBreakpoint, layoutConfig, formDataId)
|
|
59
|
+
disabledStore.setIds([...disabledSet])
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function computeDisabledSet(
|
|
63
|
+
allValues: Record<string, any>,
|
|
64
|
+
currentBreakpoint: DeviceBreakpointEnum,
|
|
65
|
+
layoutConfig?: IDndLayoutStructure_Responsive,
|
|
66
|
+
formDataId?: string,
|
|
67
|
+
): Set<string> {
|
|
19
68
|
const layout: IDndLayoutRow[] =
|
|
20
69
|
layoutConfig?.layouts?.[currentBreakpoint] || layoutConfig?.layouts?.[DeviceBreakpointEnum.Default] || []
|
|
21
70
|
|
|
22
71
|
const elementsMap: Record<string, IDndLayoutElement> = layoutConfig?.elements || {}
|
|
23
|
-
const dataValues: { [key: string]: any } = formValues && typeof formValues === 'object' ? formValues : {}
|
|
24
72
|
|
|
73
|
+
// Pull out only those elements which have EnableIf conditions
|
|
25
74
|
const configsPerElement: Record<string, IFormLayoutElementConditions> = Object.values(elementsMap).reduce(
|
|
26
75
|
(acc, el) => {
|
|
27
76
|
const elConditions = el.conditions?.[FormElementConditionalKeyEnum.EnableIf]
|
|
@@ -36,29 +85,20 @@ export function useSetDisabledElements(
|
|
|
36
85
|
layout.length > 0 &&
|
|
37
86
|
Object.keys(elementsMap).length > 0 &&
|
|
38
87
|
Object.keys(configsPerElement).length > 0 &&
|
|
39
|
-
Object.values(
|
|
88
|
+
Object.values(allValues).length > 0 &&
|
|
40
89
|
!!formDataId
|
|
41
90
|
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const elementConfigs = configsPerElement[key]
|
|
48
|
-
const shouldEnable = isValidationsMet(FormElementConditionalKeyEnum.EnableIf, dataValues, elementConfigs, {
|
|
91
|
+
const disabled = new Set<string>()
|
|
92
|
+
if (allInputsLoaded) {
|
|
93
|
+
for (const el of Object.values(elementsMap)) {
|
|
94
|
+
const conds = configsPerElement[el.key]
|
|
95
|
+
const shouldEnable = isValidationsMet(FormElementConditionalKeyEnum.EnableIf, allValues, conds, {
|
|
49
96
|
currentBreakpoint,
|
|
50
97
|
formDataId,
|
|
51
98
|
})
|
|
99
|
+
if (!shouldEnable) disabled.add(el.key)
|
|
100
|
+
}
|
|
101
|
+
}
|
|
52
102
|
|
|
53
|
-
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
return disabledSet
|
|
57
|
-
}, [elementsMap, dataValues, configsPerElement, currentBreakpoint, formDataId, allInputsLoaded])
|
|
58
|
-
|
|
59
|
-
const individuallyDisabled = computeIndividuallyDisabled()
|
|
60
|
-
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
if (allInputsLoaded) disabledStore.setIds([...individuallyDisabled])
|
|
63
|
-
}, [allInputsLoaded, individuallyDisabled])
|
|
103
|
+
return disabled
|
|
64
104
|
}
|
package/src/components/common/custom-hooks/use-node-condition.hook/use-hidden-elements.hook.ts
CHANGED
|
@@ -1,16 +1,49 @@
|
|
|
1
|
-
import { useEffect, useMemo, useCallback } from 'react'
|
|
2
|
-
import useGetCurrentBreakpoint from '../use-window-width.hook'
|
|
3
1
|
import { DeviceBreakpointEnum, FormElementConditionalKeyEnum } from '../../../../enums'
|
|
4
|
-
import { hiddenStore } from './visibility-store'
|
|
5
2
|
import { isValidationsMet } from './visibility-utils'
|
|
6
|
-
import {
|
|
3
|
+
import { useEffect } from 'react'
|
|
4
|
+
import useGetCurrentBreakpoint from '../use-window-width.hook'
|
|
5
|
+
import { FormInstance } from 'antd'
|
|
6
|
+
import { hiddenStore } from './visibility-store'
|
|
7
|
+
import type {
|
|
7
8
|
IDndLayoutCol,
|
|
8
|
-
IDndLayoutElement,
|
|
9
9
|
IDndLayoutRow,
|
|
10
10
|
IDndLayoutStructure_Responsive,
|
|
11
11
|
IFormLayoutElementConditions,
|
|
12
12
|
} from '../../../../types'
|
|
13
13
|
|
|
14
|
+
export function useSetHiddenNodes(
|
|
15
|
+
layoutConfig?: IDndLayoutStructure_Responsive,
|
|
16
|
+
formDataRef?: FormInstance,
|
|
17
|
+
formDataId?: string,
|
|
18
|
+
): void {
|
|
19
|
+
const currentBreakpoint = useGetCurrentBreakpoint()
|
|
20
|
+
|
|
21
|
+
useEffect(() => {
|
|
22
|
+
// during the initial renders, handle the initially hidden nodes
|
|
23
|
+
if (!formDataRef) return
|
|
24
|
+
|
|
25
|
+
const allValues = formDataRef.getFieldsValue() || {}
|
|
26
|
+
const layout =
|
|
27
|
+
layoutConfig?.layouts?.[currentBreakpoint] || layoutConfig?.layouts?.[DeviceBreakpointEnum.Default] || []
|
|
28
|
+
|
|
29
|
+
const allInputsLoaded = layout.length > 0 && Object.values(allValues).filter(Boolean).length > 0 && !!formDataId
|
|
30
|
+
|
|
31
|
+
if (!allInputsLoaded) return
|
|
32
|
+
|
|
33
|
+
handleHiddenSet(allValues, currentBreakpoint, layoutConfig, formDataId)
|
|
34
|
+
}, [formDataRef, layoutConfig, currentBreakpoint, formDataId])
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function handleHiddenSet(
|
|
38
|
+
allValues: Record<string, any>,
|
|
39
|
+
currentBreakpoint: DeviceBreakpointEnum,
|
|
40
|
+
layoutConfig?: IDndLayoutStructure_Responsive,
|
|
41
|
+
formDataId?: string,
|
|
42
|
+
) {
|
|
43
|
+
const hiddenIds = computeHiddenIds(allValues, currentBreakpoint, layoutConfig, formDataId)
|
|
44
|
+
hiddenStore.setIds(hiddenIds)
|
|
45
|
+
}
|
|
46
|
+
|
|
14
47
|
function computeHiddenIdsRecursive(layout: IDndLayoutRow[], individuallyHidden: Set<string>): string[] {
|
|
15
48
|
const hiddenCols = new Set<string>()
|
|
16
49
|
const hiddenRows = new Set<string>()
|
|
@@ -54,16 +87,15 @@ function computeHiddenIdsRecursive(layout: IDndLayoutRow[], individuallyHidden:
|
|
|
54
87
|
return [...individuallyHidden, ...Array.from(hiddenCols), ...Array.from(hiddenRows)]
|
|
55
88
|
}
|
|
56
89
|
|
|
57
|
-
|
|
90
|
+
function computeHiddenIds(
|
|
91
|
+
allValues: Record<string, any>,
|
|
92
|
+
currentBreakpoint: DeviceBreakpointEnum,
|
|
58
93
|
layoutConfig?: IDndLayoutStructure_Responsive,
|
|
59
|
-
formValues?: { [key: string]: any },
|
|
60
94
|
formDataId?: string,
|
|
61
|
-
):
|
|
62
|
-
const
|
|
63
|
-
const layout: IDndLayoutRow[] =
|
|
95
|
+
): string[] {
|
|
96
|
+
const layout =
|
|
64
97
|
layoutConfig?.layouts?.[currentBreakpoint] || layoutConfig?.layouts?.[DeviceBreakpointEnum.Default] || []
|
|
65
|
-
const elementsMap
|
|
66
|
-
const dataValues: { [key: string]: any } = formValues && typeof formValues === 'object' ? formValues : {}
|
|
98
|
+
const elementsMap = layoutConfig?.elements || {}
|
|
67
99
|
const configsPerElement: Record<string, IFormLayoutElementConditions> = Object.values(elementsMap).reduce(
|
|
68
100
|
(curr, el) => {
|
|
69
101
|
const elConditions = el.conditions?.[FormElementConditionalKeyEnum.ShowIf]
|
|
@@ -72,40 +104,20 @@ export function useSetHiddenNodes(
|
|
|
72
104
|
|
|
73
105
|
return curr
|
|
74
106
|
},
|
|
75
|
-
{},
|
|
107
|
+
{} as any,
|
|
76
108
|
)
|
|
77
109
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
Object.keys(configsPerElement).length > 0 &&
|
|
82
|
-
!!formDataId
|
|
83
|
-
|
|
84
|
-
const computeIndividuallyHidden = useCallback((): Set<string> => {
|
|
85
|
-
if (!allInputsLoaded) return new Set()
|
|
110
|
+
const individuallyHidden = new Set<string>()
|
|
111
|
+
for (const el of Object.values(elementsMap)) {
|
|
112
|
+
const elementConfigs = configsPerElement[el.key]
|
|
86
113
|
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
const elementConfigs = configsPerElement[key]
|
|
91
|
-
|
|
92
|
-
const shouldShow = isValidationsMet(FormElementConditionalKeyEnum.ShowIf, dataValues, elementConfigs, {
|
|
93
|
-
currentBreakpoint,
|
|
94
|
-
formDataId,
|
|
95
|
-
})
|
|
96
|
-
if (!shouldShow) hiddenSet.add(key)
|
|
114
|
+
const shouldShow = isValidationsMet(FormElementConditionalKeyEnum.ShowIf, allValues, elementConfigs, {
|
|
115
|
+
currentBreakpoint,
|
|
116
|
+
formDataId,
|
|
97
117
|
})
|
|
98
118
|
|
|
99
|
-
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
const allHiddenArray = useMemo(() => {
|
|
103
|
-
const individuallyHidden = computeIndividuallyHidden()
|
|
104
|
-
|
|
105
|
-
return computeHiddenIdsRecursive(layout, individuallyHidden)
|
|
106
|
-
}, [layout, computeIndividuallyHidden])
|
|
119
|
+
if (!shouldShow) individuallyHidden.add(el.key)
|
|
120
|
+
}
|
|
107
121
|
|
|
108
|
-
|
|
109
|
-
if (allInputsLoaded) hiddenStore.setIds(allHiddenArray)
|
|
110
|
-
}, [allHiddenArray, allInputsLoaded])
|
|
122
|
+
return computeHiddenIdsRecursive(layout, individuallyHidden)
|
|
111
123
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
import { useLocation } from 'react-router-dom'
|
|
3
|
+
import { LOCAL_STORAGE_KEYS_ENUM } from '../../../enums'
|
|
4
|
+
|
|
5
|
+
export const useSetPeristentDomain = (domain: string) => {
|
|
6
|
+
const location = useLocation()
|
|
7
|
+
const isLogin = location.pathname === '/login'
|
|
8
|
+
|
|
9
|
+
useEffect(() => {
|
|
10
|
+
if (!isLogin) return
|
|
11
|
+
|
|
12
|
+
if (typeof window === 'undefined') return
|
|
13
|
+
|
|
14
|
+
const current = window.localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.PersistDomain)
|
|
15
|
+
if (current !== domain) {
|
|
16
|
+
window.localStorage.setItem(LOCAL_STORAGE_KEYS_ENUM.PersistDomain, domain)
|
|
17
|
+
console.info(`[useSetCompanyDomain] "${LOCAL_STORAGE_KEYS_ENUM.PersistDomain}" set to "${domain}"`)
|
|
18
|
+
}
|
|
19
|
+
}, [isLogin])
|
|
20
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useCallback } from 'react'
|
|
2
|
+
import { useSyncExternalStore } from 'react'
|
|
3
|
+
import { translationStore } from './store'
|
|
4
|
+
import { TranslationTextTypeEnum } from '../../../../enums'
|
|
5
|
+
|
|
6
|
+
export interface ITranslateFuncParam {
|
|
7
|
+
key?: string
|
|
8
|
+
type: TranslationTextTypeEnum
|
|
9
|
+
subType?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useTranslation(formId?: number) {
|
|
13
|
+
const { selectedLanguage, translations } = useSyncExternalStore(
|
|
14
|
+
translationStore.subscribe.bind(translationStore),
|
|
15
|
+
translationStore.getSnapshot.bind(translationStore),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
const t = useCallback(
|
|
19
|
+
(params: ITranslateFuncParam | ITranslateFuncParam[]) => {
|
|
20
|
+
if (Array.isArray(params)) return params.map((p) => (p.key ? translationStore.translate(p, formId) : ''))
|
|
21
|
+
|
|
22
|
+
return params.key ? translationStore.translate(params, formId) : ''
|
|
23
|
+
},
|
|
24
|
+
[selectedLanguage, translations, formId],
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return { t, selectedLanguage }
|
|
28
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { CountryEnum, LOCAL_STORAGE_KEYS_ENUM } from '../../../../enums'
|
|
2
|
+
import { ITranslations } from '../../../../types'
|
|
3
|
+
import { ITranslateFuncParam } from './hook'
|
|
4
|
+
|
|
5
|
+
interface Snapshot {
|
|
6
|
+
selectedLanguage: string
|
|
7
|
+
translations: ITranslations
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type Listener = () => void
|
|
11
|
+
const listeners = new Set<Listener>()
|
|
12
|
+
|
|
13
|
+
// —— Monkey-patch localStorage.setItem to fire translationUpdate & storage events ——
|
|
14
|
+
;(function () {
|
|
15
|
+
const originalSetItem = localStorage.setItem
|
|
16
|
+
localStorage.setItem = function (key: string, value: string) {
|
|
17
|
+
const oldValue = localStorage.getItem(key)
|
|
18
|
+
originalSetItem.call(this, key, value)
|
|
19
|
+
if (key === LOCAL_STORAGE_KEYS_ENUM.SelectedLanguage || key === LOCAL_STORAGE_KEYS_ENUM.FormTranslations) {
|
|
20
|
+
// fire native StorageEvent for useSyncExternalStore cross-tab compatibility
|
|
21
|
+
window.dispatchEvent(
|
|
22
|
+
new StorageEvent('storage', {
|
|
23
|
+
key,
|
|
24
|
+
oldValue,
|
|
25
|
+
newValue: value,
|
|
26
|
+
storageArea: localStorage,
|
|
27
|
+
url: window.location.href,
|
|
28
|
+
}),
|
|
29
|
+
)
|
|
30
|
+
// fire custom update for same-tab subscribers
|
|
31
|
+
window.dispatchEvent(new Event('translationUpdate'))
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})()
|
|
35
|
+
|
|
36
|
+
// —— Internal cache for memoized snapshots ——
|
|
37
|
+
let lastLanguage = ''
|
|
38
|
+
let lastRaw = ''
|
|
39
|
+
let lastTranslations: ITranslations = {}
|
|
40
|
+
let lastSnapshot: Snapshot = { selectedLanguage: '', translations: {} }
|
|
41
|
+
|
|
42
|
+
function readLanguage(): string {
|
|
43
|
+
return localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.SelectedLanguage) ?? ''
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readRawTranslations(): string {
|
|
47
|
+
return localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.FormTranslations) ?? ''
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function parseTranslations(raw: string): ITranslations {
|
|
51
|
+
try {
|
|
52
|
+
return raw ? JSON.parse(raw) : {}
|
|
53
|
+
} catch {
|
|
54
|
+
console.error('[translationStore] invalid JSON in FormTranslations')
|
|
55
|
+
return {}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function notifyAll() {
|
|
60
|
+
listeners.forEach((fn) => fn())
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Cross-tab storage events
|
|
64
|
+
window.addEventListener('storage', (e) => {
|
|
65
|
+
if (
|
|
66
|
+
e.storageArea === localStorage &&
|
|
67
|
+
(e.key === LOCAL_STORAGE_KEYS_ENUM.SelectedLanguage || e.key === LOCAL_STORAGE_KEYS_ENUM.FormTranslations)
|
|
68
|
+
) {
|
|
69
|
+
notifyAll()
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
// Same-tab custom events
|
|
73
|
+
window.addEventListener('translationUpdate', notifyAll)
|
|
74
|
+
|
|
75
|
+
export const translationStore = {
|
|
76
|
+
getSnapshot(): Snapshot {
|
|
77
|
+
const currentLanguage = readLanguage()
|
|
78
|
+
const currentRaw = readRawTranslations()
|
|
79
|
+
|
|
80
|
+
if (currentLanguage !== lastLanguage || currentRaw !== lastRaw) {
|
|
81
|
+
lastLanguage = currentLanguage
|
|
82
|
+
lastRaw = currentRaw
|
|
83
|
+
lastTranslations = parseTranslations(currentRaw)
|
|
84
|
+
lastSnapshot = { selectedLanguage: lastLanguage, translations: lastTranslations }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return lastSnapshot
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
subscribe(fn: Listener): () => void {
|
|
91
|
+
listeners.add(fn)
|
|
92
|
+
return () => {
|
|
93
|
+
listeners.delete(fn)
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
translate({ key = '', type, subType }: ITranslateFuncParam, formId?: number): string {
|
|
98
|
+
const { selectedLanguage, translations } = this.getSnapshot()
|
|
99
|
+
const formTranslations = formId ? (translations[formId] as ITranslations) : undefined
|
|
100
|
+
const lookup = `${key}__${type}${subType ? `__${subType}` : ''}`
|
|
101
|
+
|
|
102
|
+
if (formTranslations && selectedLanguage) {
|
|
103
|
+
const lookupTranslation = formTranslations[lookup]
|
|
104
|
+
const inSelected = lookupTranslation?.[selectedLanguage as CountryEnum]
|
|
105
|
+
if (inSelected) return inSelected
|
|
106
|
+
|
|
107
|
+
// fallback to default language
|
|
108
|
+
const defaultLang = localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.DefaultLanguage)
|
|
109
|
+
return lookupTranslation?.[defaultLang as CountryEnum] ?? ''
|
|
110
|
+
}
|
|
111
|
+
return ''
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
getLanguages() {
|
|
115
|
+
const languages = localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.Languages)
|
|
116
|
+
|
|
117
|
+
if (languages) return JSON.parse(languages)
|
|
118
|
+
return []
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
setTranslations(formId: number, obj: ITranslations) {
|
|
122
|
+
const raw = localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.FormTranslations) || ''
|
|
123
|
+
const parsed = raw ? JSON.parse(raw) : {}
|
|
124
|
+
localStorage.setItem(LOCAL_STORAGE_KEYS_ENUM.FormTranslations, JSON.stringify({ ...parsed, [formId]: obj }))
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
setLanguage(lang: CountryEnum) {
|
|
128
|
+
localStorage.setItem(LOCAL_STORAGE_KEYS_ENUM.SelectedLanguage, lang)
|
|
129
|
+
},
|
|
130
|
+
}
|
|
@@ -3,14 +3,39 @@ import { ConfigProvider } from 'antd'
|
|
|
3
3
|
import { ConfigContext } from '../context'
|
|
4
4
|
import useSiteMetadata from '../../common/custom-hooks/use-site-meta-data'
|
|
5
5
|
import { ReactNode } from 'react'
|
|
6
|
+
import { Button_FillerPortal } from '../../common/button'
|
|
7
|
+
import { LOCAL_STORAGE_KEYS_ENUM } from '../../../enums'
|
|
8
|
+
import { useLocation, useNavigate } from 'react-router-dom'
|
|
6
9
|
|
|
7
10
|
export function ConfigProviderLayout({ children }: { children: ReactNode }): JSX.Element {
|
|
8
|
-
const
|
|
11
|
+
const navigate = useNavigate()
|
|
12
|
+
const location = useLocation()
|
|
13
|
+
const { config, theme, canSwitchDomain } = useCompanyConfig()
|
|
9
14
|
useSiteMetadata(config?.siteIdentity)
|
|
10
15
|
|
|
16
|
+
const showSwitchCompany =
|
|
17
|
+
canSwitchDomain &&
|
|
18
|
+
!localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.PersistDomain) &&
|
|
19
|
+
location.pathname.startsWith('/login')
|
|
20
|
+
|
|
11
21
|
return (
|
|
12
22
|
<ConfigContext.Provider value={{ config }}>
|
|
13
|
-
<ConfigProvider theme={theme}>
|
|
23
|
+
<ConfigProvider theme={theme}>
|
|
24
|
+
{showSwitchCompany && (
|
|
25
|
+
<div className="absolute top-2 right-2 z-20">
|
|
26
|
+
<Button_FillerPortal
|
|
27
|
+
outline
|
|
28
|
+
onClick={() => {
|
|
29
|
+
localStorage.removeItem(LOCAL_STORAGE_KEYS_ENUM.Domain)
|
|
30
|
+
navigate(0)
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
Switch Project
|
|
34
|
+
</Button_FillerPortal>
|
|
35
|
+
</div>
|
|
36
|
+
)}
|
|
37
|
+
{children}
|
|
38
|
+
</ConfigProvider>
|
|
14
39
|
</ConfigContext.Provider>
|
|
15
40
|
)
|
|
16
41
|
}
|
|
@@ -7,6 +7,7 @@ import { IFormJoin, IFormDataListData, IFormDataListConfig } from '../../../type
|
|
|
7
7
|
import { getProjectionKey, mergeJoins } from '../../../functions'
|
|
8
8
|
import { useCacheFormLayoutConfig } from '../../common/custom-hooks/use-cache-form-layout-config.hook'
|
|
9
9
|
import { useNotification } from '../../common/custom-hooks'
|
|
10
|
+
import { translationStore } from '../../common/custom-hooks/use-translation.hook/store'
|
|
10
11
|
|
|
11
12
|
export function FormDataListComponent({
|
|
12
13
|
formId,
|
|
@@ -41,6 +42,12 @@ export function FormDataListComponent({
|
|
|
41
42
|
defaultFilterReqDataRef.current = undefined
|
|
42
43
|
}, [location.pathname])
|
|
43
44
|
|
|
45
|
+
useEffect(() => {
|
|
46
|
+
if (!cachedConfig?.id || !cachedConfig?.translations) return
|
|
47
|
+
|
|
48
|
+
translationStore.setTranslations(cachedConfig.id, cachedConfig.translations)
|
|
49
|
+
}, [cachedConfig])
|
|
50
|
+
|
|
44
51
|
useEffect(() => {
|
|
45
52
|
if (!formId || !cachedConfig) return
|
|
46
53
|
|
|
@@ -12,8 +12,14 @@ import { useManyToManyConnector } from '../../common/custom-hooks/use-many-to-ma
|
|
|
12
12
|
import { PageViewTypEnum, DeviceBreakpointEnum, FormPreservedItemKeys, LOCAL_STORAGE_KEYS_ENUM } from '../../../enums'
|
|
13
13
|
import { UserAuth } from '../../../api/user'
|
|
14
14
|
import { useCacheFormLayoutConfig } from '../../common/custom-hooks/use-cache-form-layout-config.hook'
|
|
15
|
-
import {
|
|
16
|
-
|
|
15
|
+
import {
|
|
16
|
+
handleHiddenSet,
|
|
17
|
+
useSetHiddenNodes,
|
|
18
|
+
} from '../../common/custom-hooks/use-node-condition.hook/use-hidden-elements.hook'
|
|
19
|
+
import {
|
|
20
|
+
handleDisabledSet,
|
|
21
|
+
useSetDisabledElements,
|
|
22
|
+
} from '../../common/custom-hooks/use-node-condition.hook/use-disabled-elements.hook'
|
|
17
23
|
import { ELEMENTS_DEFAULT_CLASS } from '../../../constants'
|
|
18
24
|
import {
|
|
19
25
|
DynamicFormButtonRender,
|
|
@@ -27,6 +33,7 @@ import {
|
|
|
27
33
|
isValidMongoDbId,
|
|
28
34
|
queryParamsToObject,
|
|
29
35
|
} from '../../../functions/forms'
|
|
36
|
+
import { translationStore } from '../../common/custom-hooks/use-translation.hook/store'
|
|
30
37
|
|
|
31
38
|
export default function FormDataDetailsComponent({
|
|
32
39
|
isPublic,
|
|
@@ -50,10 +57,9 @@ export default function FormDataDetailsComponent({
|
|
|
50
57
|
const currentBreakpoint = useGetCurrentBreakpoint()
|
|
51
58
|
|
|
52
59
|
const { cachedConfig, isConfigLoading } = useCacheFormLayoutConfig(formId, formKey)
|
|
53
|
-
const detailsData = Form.useWatch([], formDataRef)
|
|
54
60
|
|
|
55
|
-
useSetHiddenNodes(cachedConfig?.detailsConfig,
|
|
56
|
-
useSetDisabledElements(cachedConfig?.detailsConfig,
|
|
61
|
+
useSetHiddenNodes(cachedConfig?.detailsConfig, formDataRef, formDataId)
|
|
62
|
+
useSetDisabledElements(cachedConfig?.detailsConfig, formDataRef, formDataId)
|
|
57
63
|
|
|
58
64
|
useEffect(() => {
|
|
59
65
|
// for public forms, setting the details form into localStorage, so that buttons work correctly
|
|
@@ -128,6 +134,12 @@ export default function FormDataDetailsComponent({
|
|
|
128
134
|
[formDataId, formDataRef],
|
|
129
135
|
)
|
|
130
136
|
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (!cachedConfig?.id || !cachedConfig?.translations) return
|
|
139
|
+
|
|
140
|
+
translationStore.setTranslations(cachedConfig.id, cachedConfig.translations)
|
|
141
|
+
}, [cachedConfig])
|
|
142
|
+
|
|
131
143
|
useEffect(() => {
|
|
132
144
|
if (!cachedConfig?.detailsConfig) return
|
|
133
145
|
|
|
@@ -176,6 +188,10 @@ export default function FormDataDetailsComponent({
|
|
|
176
188
|
name="dynamic_form_data_form"
|
|
177
189
|
form={formDataRef}
|
|
178
190
|
className={ELEMENTS_DEFAULT_CLASS.DataDetailsForm}
|
|
191
|
+
onValuesChange={(_changed, allValues) => {
|
|
192
|
+
handleDisabledSet(allValues, currentBreakpoint, cachedConfig?.detailsConfig, formDataId)
|
|
193
|
+
handleHiddenSet(allValues, currentBreakpoint, cachedConfig?.detailsConfig, formDataId)
|
|
194
|
+
}}
|
|
179
195
|
>
|
|
180
196
|
{layout.map((row, rowIdx) => (
|
|
181
197
|
<LayoutRendererRow
|
|
@@ -2,17 +2,26 @@ import { ReactNode, useState, useTransition } from 'react'
|
|
|
2
2
|
import { IFormDndLayoutRowHeader } from '../../../../types'
|
|
3
3
|
import { Collapse, Spin } from 'antd'
|
|
4
4
|
import RowHeader from './header'
|
|
5
|
+
import { useTranslation } from '../../../common/custom-hooks/use-translation.hook/hook'
|
|
6
|
+
import { TranslationTextTypeEnum } from '../../../../enums'
|
|
5
7
|
|
|
6
8
|
export const LayoutRowConditionalHeaderRenderer = ({
|
|
9
|
+
formId,
|
|
7
10
|
children,
|
|
8
11
|
header,
|
|
12
|
+
rowId,
|
|
9
13
|
}: {
|
|
14
|
+
formId?: number
|
|
10
15
|
children: ReactNode
|
|
11
16
|
header: IFormDndLayoutRowHeader | undefined
|
|
17
|
+
rowId: string
|
|
12
18
|
}) => {
|
|
19
|
+
const { t } = useTranslation(formId)
|
|
13
20
|
const [isCollapsed, setIsCollapsed] = useState(false)
|
|
14
21
|
const [isPendingTransition, startTransition] = useTransition()
|
|
15
22
|
|
|
23
|
+
const headerTitle = t({ key: rowId, type: TranslationTextTypeEnum.Label })
|
|
24
|
+
|
|
16
25
|
if (header?.isCollapsible)
|
|
17
26
|
return (
|
|
18
27
|
<Spin spinning={isPendingTransition}>
|
|
@@ -26,6 +35,7 @@ export const LayoutRowConditionalHeaderRenderer = ({
|
|
|
26
35
|
label: (
|
|
27
36
|
<RowHeader
|
|
28
37
|
{...header}
|
|
38
|
+
name={headerTitle as string}
|
|
29
39
|
isCollapsed={isCollapsed}
|
|
30
40
|
setIsCollapsed={() => startTransition(() => setIsCollapsed((c) => !c))}
|
|
31
41
|
/>
|
|
@@ -37,10 +47,10 @@ export const LayoutRowConditionalHeaderRenderer = ({
|
|
|
37
47
|
</Spin>
|
|
38
48
|
)
|
|
39
49
|
|
|
40
|
-
if (
|
|
50
|
+
if (headerTitle)
|
|
41
51
|
return (
|
|
42
52
|
<>
|
|
43
|
-
<RowHeader {...header} />
|
|
53
|
+
<RowHeader {...header} name={headerTitle as string} />
|
|
44
54
|
{children}
|
|
45
55
|
</>
|
|
46
56
|
)
|
|
@@ -27,8 +27,18 @@ export const LayoutRendererRow = memo(
|
|
|
27
27
|
id={rowData.id}
|
|
28
28
|
className={ELEMENTS_DEFAULT_CLASS.LayoutRowContainer}
|
|
29
29
|
>
|
|
30
|
-
<LayoutRowConditionalHeaderRenderer
|
|
31
|
-
|
|
30
|
+
<LayoutRowConditionalHeaderRenderer
|
|
31
|
+
formId={formContext.formId}
|
|
32
|
+
rowId={rowData.id}
|
|
33
|
+
header={rowData.props?.header}
|
|
34
|
+
>
|
|
35
|
+
<LayoutRowRepeatableRenderer
|
|
36
|
+
formId={formContext.formId}
|
|
37
|
+
rowId={rowData.id}
|
|
38
|
+
basePath={basePath}
|
|
39
|
+
formRef={formContext.formRef}
|
|
40
|
+
repeatingSection={rowData.props?.repeatingSection}
|
|
41
|
+
>
|
|
32
42
|
{(formListItemProps) => {
|
|
33
43
|
const style: { [key: string]: any } = { ...styleConfig, ...getGridContainerStyle(rowData.display) }
|
|
34
44
|
const hiddenColsIndices = rowData.children.reduce(
|