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.
Files changed (46) hide show
  1. package/package.json +1 -1
  2. package/src/components/common/custom-hooks/index.ts +2 -0
  3. package/src/components/common/custom-hooks/use-login-handler.ts +3 -3
  4. package/src/components/common/custom-hooks/use-node-condition.hook/use-disabled-elements.hook.ts +62 -22
  5. package/src/components/common/custom-hooks/use-node-condition.hook/use-hidden-elements.hook.ts +54 -42
  6. package/src/components/common/custom-hooks/use-set-persistent-domain.hook.ts +20 -0
  7. package/src/components/common/custom-hooks/use-translation.hook/hook.ts +28 -0
  8. package/src/components/common/custom-hooks/use-translation.hook/store.ts +130 -0
  9. package/src/components/companies/3-config-provider/index.tsx +27 -2
  10. package/src/components/form/1-list/index.tsx +7 -0
  11. package/src/components/form/2-details/index.tsx +21 -5
  12. package/src/components/form/layout-renderer/1-row/header-render.tsx +12 -2
  13. package/src/components/form/layout-renderer/1-row/header.tsx +1 -0
  14. package/src/components/form/layout-renderer/1-row/index.tsx +12 -2
  15. package/src/components/form/layout-renderer/1-row/repeatable-render.tsx +83 -28
  16. package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +32 -14
  17. package/src/components/form/layout-renderer/3-element/10-currency.tsx +16 -3
  18. package/src/components/form/layout-renderer/3-element/11-breadcrumb.tsx +17 -7
  19. package/src/components/form/layout-renderer/3-element/12-picker-field.tsx +14 -13
  20. package/src/components/form/layout-renderer/3-element/13-language-selector.tsx +42 -0
  21. package/src/components/form/layout-renderer/3-element/2-field-element.tsx +223 -231
  22. package/src/components/form/layout-renderer/3-element/3-read-field-data.tsx +12 -5
  23. package/src/components/form/layout-renderer/3-element/4-rich-text-editor.tsx +14 -10
  24. package/src/components/form/layout-renderer/3-element/5-re-captcha.tsx +4 -7
  25. package/src/components/form/layout-renderer/3-element/6-signature.tsx +11 -6
  26. package/src/components/form/layout-renderer/3-element/7-file-upload.tsx +118 -32
  27. package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +29 -8
  28. package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +11 -2
  29. package/src/components/form/layout-renderer/3-element/index.tsx +145 -86
  30. package/src/constants.ts +2 -1
  31. package/src/enums/form.enum.ts +4 -0
  32. package/src/enums/index.ts +29 -0
  33. package/src/functions/companies/index.tsx +12 -0
  34. package/src/functions/companies/use-company-config.tsx +27 -12
  35. package/src/functions/forms/create-form-rules.ts +18 -6
  36. package/src/functions/forms/data-render-functions.tsx +44 -24
  37. package/src/functions/forms/get-element-props.ts +2 -2
  38. package/src/functions/forms/index.ts +3 -1
  39. package/src/types/companies/site-layout/authenticated/index.tsx +5 -2
  40. package/src/types/forms/data-list/index.ts +1 -0
  41. package/src/types/forms/index.ts +17 -5
  42. package/src/types/forms/layout-elements/button.ts +3 -12
  43. package/src/types/forms/layout-elements/index.ts +10 -42
  44. package/src/types/forms/layout-elements/read-field-data-props.ts +0 -1
  45. package/src/types/forms/layout-elements/validation.ts +0 -1
  46. package/src/types/index.ts +2 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "form-craft-package",
3
- "version": "1.8.2-dev.1",
3
+ "version": "1.8.2-dev.3",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -3,3 +3,5 @@ export * from './use-notification.hook'
3
3
  export * from './use-lazy-modal-opener.hook'
4
4
  export * from './use-breadcrumb.hook'
5
5
  export * from './use-dayjs-extender.hook'
6
+ export * from './use-set-persistent-domain.hook'
7
+ export * from './use-translation.hook/hook'
@@ -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 = localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.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
- localStorage.getItem(LOCAL_STORAGE_KEYS_ENUM.Domain) ?? '',
30
+ getLocalStorageDomain(),
31
31
  values.type || 'password',
32
32
  )
33
33
  if (authRes.status === 200) {
@@ -1,8 +1,9 @@
1
- import { useEffect, useCallback } from 'react'
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
- formValues?: { [key: string]: any },
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(dataValues).length > 0 &&
88
+ Object.values(allValues).length > 0 &&
40
89
  !!formDataId
41
90
 
42
- const computeIndividuallyDisabled = useCallback((): Set<string> => {
43
- if (!allInputsLoaded) return new Set()
44
-
45
- const disabledSet = new Set<string>()
46
- Object.values(elementsMap).forEach(({ key }) => {
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
- if (!shouldEnable) disabledSet.add(key)
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
  }
@@ -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
- export function useSetHiddenNodes(
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
- ): void {
62
- const currentBreakpoint = useGetCurrentBreakpoint() || DeviceBreakpointEnum.Default
63
- const layout: IDndLayoutRow[] =
95
+ ): string[] {
96
+ const layout =
64
97
  layoutConfig?.layouts?.[currentBreakpoint] || layoutConfig?.layouts?.[DeviceBreakpointEnum.Default] || []
65
- const elementsMap: Record<string, IDndLayoutElement> = layoutConfig?.elements || {}
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 allInputsLoaded =
79
- layout.length > 0 &&
80
- Object.keys(elementsMap).length > 0 &&
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 hiddenSet = new Set<string>()
88
-
89
- Object.values(elementsMap).forEach(({ key }) => {
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
- return hiddenSet
100
- }, [elementsMap, dataValues, configsPerElement, currentBreakpoint, formDataId, allInputsLoaded])
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
- useEffect(() => {
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 { config, theme } = useCompanyConfig()
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}>{children}</ConfigProvider>
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 { useSetHiddenNodes } from '../../common/custom-hooks/use-node-condition.hook/use-hidden-elements.hook'
16
- import { useSetDisabledElements } from '../../common/custom-hooks/use-node-condition.hook/use-disabled-elements.hook'
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, detailsData, formDataId)
56
- useSetDisabledElements(cachedConfig?.detailsConfig, detailsData, formDataId)
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 (header?.name)
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
  )
@@ -35,6 +35,7 @@ export default function RowHeader({
35
35
  }
36
36
 
37
37
  interface IRowHeader extends IFormDndLayoutRowHeader {
38
+ name: string
38
39
  isCollapsed?: boolean
39
40
  setIsCollapsed?: () => void
40
41
  }
@@ -27,8 +27,18 @@ export const LayoutRendererRow = memo(
27
27
  id={rowData.id}
28
28
  className={ELEMENTS_DEFAULT_CLASS.LayoutRowContainer}
29
29
  >
30
- <LayoutRowConditionalHeaderRenderer header={rowData.props?.header}>
31
- <LayoutRowRepeatableRenderer basePath={basePath} repeatingSection={rowData.props?.repeatingSection}>
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(