form-craft-package 1.8.1-dev.0 → 1.8.1-dev.2
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/api/client.ts +2 -2
- package/src/components/common/currency-field.tsx +2 -2
- package/src/components/common/custom-hooks/use-node-condition.hook/use-disabled-elements.hook.ts +64 -0
- package/src/components/common/custom-hooks/use-node-condition.hook/use-hidden-elements.hook.ts +111 -0
- package/src/components/common/custom-hooks/use-node-condition.hook/use-node-condition.hook.ts +18 -0
- package/src/components/common/custom-hooks/use-node-condition.hook/visibility-store.ts +103 -0
- package/src/components/common/custom-hooks/use-node-condition.hook/visibility-utils.ts +138 -0
- package/src/components/common/custom-hooks/use-window-width.hook.ts +6 -3
- package/src/components/form/1-list/table-header.tsx +14 -10
- package/src/components/form/1-list/table.tsx +11 -8
- package/src/components/form/2-details/index.tsx +20 -4
- package/src/components/form/3-preview/index.tsx +12 -8
- package/src/components/form/layout-renderer/1-row/index.tsx +55 -71
- package/src/components/form/layout-renderer/2-col/index.tsx +16 -46
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +13 -14
- package/src/components/form/layout-renderer/3-element/10-currency.tsx +9 -12
- package/src/components/form/layout-renderer/3-element/11-breadcrumb.tsx +2 -1
- package/src/components/form/layout-renderer/3-element/12-picker-field.tsx +53 -0
- package/src/components/form/layout-renderer/3-element/2-field-element.tsx +91 -75
- package/src/components/form/layout-renderer/3-element/3-read-field-data.tsx +2 -1
- package/src/components/form/layout-renderer/3-element/4-rich-text-editor.tsx +2 -1
- package/src/components/form/layout-renderer/3-element/5-re-captcha.tsx +2 -1
- package/src/components/form/layout-renderer/3-element/6-signature.tsx +3 -2
- package/src/components/form/layout-renderer/3-element/7-file-upload.tsx +5 -1
- package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +72 -123
- package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +2 -1
- package/src/components/form/layout-renderer/3-element/index.tsx +63 -70
- package/src/constants.ts +33 -0
- package/src/enums/index.ts +3 -2
- package/src/functions/forms/data-render-functions.tsx +3 -3
- package/src/functions/forms/get-data-list-option-value.ts +7 -1
- package/src/types/companies/site-layout/authenticated/index.tsx +1 -1
- package/src/types/forms/index.ts +1 -1
- package/src/types/forms/layout-elements/index.ts +4 -7
- package/src/types/index.ts +0 -2
- package/src/components/common/custom-hooks/use-check-element-conditions.hook.ts +0 -85
- package/src/components/form/index.tsx +0 -0
- package/src/functions/forms/conditional-rule-validator.ts +0 -79
package/package.json
CHANGED
package/src/api/client.ts
CHANGED
|
@@ -12,7 +12,7 @@ import axios, {
|
|
|
12
12
|
|
|
13
13
|
import { PageViewTypEnum, LOCAL_STORAGE_KEYS_ENUM, SHARED_COOKIE_KEYS } from '../enums'
|
|
14
14
|
import { IDynamicForm } from '../types'
|
|
15
|
-
import { constructDynamicFormHref, fetchDynamicForms } from '../functions'
|
|
15
|
+
import { constructDynamicFormHref, cookieHandler, fetchDynamicForms } from '../functions'
|
|
16
16
|
import { CLIENT_ID, CLIENT_SECRET } from '../constants'
|
|
17
17
|
import { breadcrumbStore } from '../components/common/custom-hooks/use-breadcrumb.hook'
|
|
18
18
|
|
|
@@ -120,7 +120,7 @@ apiClient.interceptors.response.use(
|
|
|
120
120
|
.catch((refreshErr) => {
|
|
121
121
|
processQueue(refreshErr, null)
|
|
122
122
|
window.location.href = '/login'
|
|
123
|
-
|
|
123
|
+
cookieHandler.empty()
|
|
124
124
|
})
|
|
125
125
|
.finally(() => {
|
|
126
126
|
isRefreshing = false
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { InputNumber } from 'antd'
|
|
2
2
|
import { useMemo, useRef } from 'react'
|
|
3
3
|
import { CountryEnum } from '../../enums'
|
|
4
|
-
import { INTERNATIONALIZATION_DATA } from '../../constants'
|
|
4
|
+
import { ELEMENTS_DEFAULT_CLASS, INTERNATIONALIZATION_DATA } from '../../constants'
|
|
5
5
|
|
|
6
6
|
export default function CurrencyField({
|
|
7
7
|
country = CountryEnum.US,
|
|
@@ -61,7 +61,7 @@ export default function CurrencyField({
|
|
|
61
61
|
: `${currencySymbol}${parseInt('0').toFixed(decimalPoint)}`
|
|
62
62
|
}}
|
|
63
63
|
parser={(value) => (value ? parseFloat(value.replace(/\p{Sc}\s?|(,*)/gu, '')) : 0)}
|
|
64
|
-
className=
|
|
64
|
+
className={`${ELEMENTS_DEFAULT_CLASS.Currency} w-full right-align`}
|
|
65
65
|
/>
|
|
66
66
|
)
|
|
67
67
|
}
|
package/src/components/common/custom-hooks/use-node-condition.hook/use-disabled-elements.hook.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useEffect, useCallback } from 'react'
|
|
2
|
+
import useGetCurrentBreakpoint from '../use-window-width.hook'
|
|
3
|
+
import { DeviceBreakpointEnum, FormElementConditionalKeyEnum } from '../../../../enums'
|
|
4
|
+
import { disabledStore } from './visibility-store'
|
|
5
|
+
import { isValidationsMet } from './visibility-utils'
|
|
6
|
+
import {
|
|
7
|
+
IDndLayoutElement,
|
|
8
|
+
IDndLayoutRow,
|
|
9
|
+
IDndLayoutStructure_Responsive,
|
|
10
|
+
IFormLayoutElementConditions,
|
|
11
|
+
} from '../../../../types'
|
|
12
|
+
|
|
13
|
+
export function useSetDisabledElements(
|
|
14
|
+
layoutConfig?: IDndLayoutStructure_Responsive,
|
|
15
|
+
formValues?: { [key: string]: any },
|
|
16
|
+
formDataId?: string,
|
|
17
|
+
): void {
|
|
18
|
+
const currentBreakpoint = useGetCurrentBreakpoint()
|
|
19
|
+
const layout: IDndLayoutRow[] =
|
|
20
|
+
layoutConfig?.layouts?.[currentBreakpoint] || layoutConfig?.layouts?.[DeviceBreakpointEnum.Default] || []
|
|
21
|
+
|
|
22
|
+
const elementsMap: Record<string, IDndLayoutElement> = layoutConfig?.elements || {}
|
|
23
|
+
const dataValues: { [key: string]: any } = formValues && typeof formValues === 'object' ? formValues : {}
|
|
24
|
+
|
|
25
|
+
const configsPerElement: Record<string, IFormLayoutElementConditions> = Object.values(elementsMap).reduce(
|
|
26
|
+
(acc, el) => {
|
|
27
|
+
const elConditions = el.conditions?.[FormElementConditionalKeyEnum.EnableIf]
|
|
28
|
+
if (elConditions && Array.isArray(elConditions) && elConditions.length > 0) acc[el.key] = el.conditions!
|
|
29
|
+
|
|
30
|
+
return acc
|
|
31
|
+
},
|
|
32
|
+
{} as Record<string, IFormLayoutElementConditions>,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const allInputsLoaded =
|
|
36
|
+
layout.length > 0 &&
|
|
37
|
+
Object.keys(elementsMap).length > 0 &&
|
|
38
|
+
Object.keys(configsPerElement).length > 0 &&
|
|
39
|
+
Object.values(dataValues).length > 0 &&
|
|
40
|
+
!!formDataId
|
|
41
|
+
|
|
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, {
|
|
49
|
+
currentBreakpoint,
|
|
50
|
+
formDataId,
|
|
51
|
+
})
|
|
52
|
+
|
|
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])
|
|
64
|
+
}
|
package/src/components/common/custom-hooks/use-node-condition.hook/use-hidden-elements.hook.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { useEffect, useMemo, useCallback } from 'react'
|
|
2
|
+
import useGetCurrentBreakpoint from '../use-window-width.hook'
|
|
3
|
+
import { DeviceBreakpointEnum, FormElementConditionalKeyEnum } from '../../../../enums'
|
|
4
|
+
import { hiddenStore } from './visibility-store'
|
|
5
|
+
import { isValidationsMet } from './visibility-utils'
|
|
6
|
+
import {
|
|
7
|
+
IDndLayoutCol,
|
|
8
|
+
IDndLayoutElement,
|
|
9
|
+
IDndLayoutRow,
|
|
10
|
+
IDndLayoutStructure_Responsive,
|
|
11
|
+
IFormLayoutElementConditions,
|
|
12
|
+
} from '../../../../types'
|
|
13
|
+
|
|
14
|
+
function computeHiddenIdsRecursive(layout: IDndLayoutRow[], individuallyHidden: Set<string>): string[] {
|
|
15
|
+
const hiddenCols = new Set<string>()
|
|
16
|
+
const hiddenRows = new Set<string>()
|
|
17
|
+
const rowHiddenCache = new Map<string, boolean>()
|
|
18
|
+
|
|
19
|
+
function isRowHidden(row: IDndLayoutRow): boolean {
|
|
20
|
+
if (rowHiddenCache.has(row.id)) return rowHiddenCache.get(row.id)!
|
|
21
|
+
|
|
22
|
+
const hiddenCols = row.children.map((col) => isColHidden(col as IDndLayoutCol))
|
|
23
|
+
const allColsHidden = hiddenCols.every((hidden) => hidden)
|
|
24
|
+
|
|
25
|
+
rowHiddenCache.set(row.id, allColsHidden)
|
|
26
|
+
return allColsHidden
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isColHidden(col: IDndLayoutCol): boolean {
|
|
30
|
+
const allChildrenHidden = col.children.every((child) => {
|
|
31
|
+
if (typeof child === 'string') return individuallyHidden.has(child)
|
|
32
|
+
else return isRowHidden(child)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
if (allChildrenHidden) hiddenCols.add(col.id)
|
|
36
|
+
|
|
37
|
+
return allChildrenHidden
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function traverseRows(rows: IDndLayoutRow[]) {
|
|
41
|
+
for (const row of rows) {
|
|
42
|
+
row.children.forEach((col) => {
|
|
43
|
+
col.children.forEach((child) => {
|
|
44
|
+
if (typeof child !== 'string') traverseRows([child])
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
if (isRowHidden(row)) hiddenRows.add(row.id)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
traverseRows(layout)
|
|
53
|
+
|
|
54
|
+
return [...individuallyHidden, ...Array.from(hiddenCols), ...Array.from(hiddenRows)]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function useSetHiddenNodes(
|
|
58
|
+
layoutConfig?: IDndLayoutStructure_Responsive,
|
|
59
|
+
formValues?: { [key: string]: any },
|
|
60
|
+
formDataId?: string,
|
|
61
|
+
): void {
|
|
62
|
+
const currentBreakpoint = useGetCurrentBreakpoint() || DeviceBreakpointEnum.Default
|
|
63
|
+
const layout: IDndLayoutRow[] =
|
|
64
|
+
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 : {}
|
|
67
|
+
const configsPerElement: Record<string, IFormLayoutElementConditions> = Object.values(elementsMap).reduce(
|
|
68
|
+
(curr, el) => {
|
|
69
|
+
const elConditions = el.conditions?.[FormElementConditionalKeyEnum.ShowIf]
|
|
70
|
+
|
|
71
|
+
if (Array.isArray(elConditions) && elConditions.length > 0) return { ...curr, [el.key]: el.conditions }
|
|
72
|
+
|
|
73
|
+
return curr
|
|
74
|
+
},
|
|
75
|
+
{},
|
|
76
|
+
)
|
|
77
|
+
|
|
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()
|
|
86
|
+
|
|
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)
|
|
97
|
+
})
|
|
98
|
+
|
|
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])
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (allInputsLoaded) hiddenStore.setIds(allHiddenArray)
|
|
110
|
+
}, [allHiddenArray, allInputsLoaded])
|
|
111
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useSyncExternalStore } from 'react'
|
|
2
|
+
import { disabledStore, hiddenStore } from './visibility-store'
|
|
3
|
+
|
|
4
|
+
export const useIsNodeHidden = (id: string): boolean =>
|
|
5
|
+
useSyncExternalStore(
|
|
6
|
+
(callback) => hiddenStore.subscribe(callback),
|
|
7
|
+
() => hiddenStore.getSnapshot().includes(id),
|
|
8
|
+
() => hiddenStore.getSnapshot().includes(id),
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
export const checkIsNodeHidden = (id: string): boolean => hiddenStore.getSnapshot().includes(id)
|
|
12
|
+
|
|
13
|
+
export const useIsNodeDisabled = (id: string): boolean =>
|
|
14
|
+
useSyncExternalStore(
|
|
15
|
+
(callback) => disabledStore.subscribe(callback),
|
|
16
|
+
() => disabledStore.getSnapshot().includes(id),
|
|
17
|
+
() => disabledStore.getSnapshot().includes(id),
|
|
18
|
+
)
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { LOCAL_STORAGE_KEYS_ENUM } from '../../../../enums'
|
|
2
|
+
|
|
3
|
+
type Subscriber = () => void
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Factory: create a LocalStorage-backed store for a list of string IDs.
|
|
7
|
+
* Internally holds:
|
|
8
|
+
* • key: the localStorage key (enum value)
|
|
9
|
+
* • ids: in-memory array of strings
|
|
10
|
+
* • subscribers: a Set of callbacks to notify whenever ids changes
|
|
11
|
+
*
|
|
12
|
+
* Exposes methods:
|
|
13
|
+
* - subscribe(callback): register for change notifications → returns unsubscribe()
|
|
14
|
+
* - getSnapshot(): return current array of IDs (shallow copy)
|
|
15
|
+
* - setIds(newIds): write new array to memory + localStorage + notify subscribers
|
|
16
|
+
*/
|
|
17
|
+
function createIdListStore(storageKey: LOCAL_STORAGE_KEYS_ENUM) {
|
|
18
|
+
// 1) Initialize in-memory array from localStorage (or empty array).
|
|
19
|
+
let ids: string[] = []
|
|
20
|
+
try {
|
|
21
|
+
const raw = localStorage.getItem(storageKey)
|
|
22
|
+
if (raw) {
|
|
23
|
+
const parsed = JSON.parse(raw)
|
|
24
|
+
if (Array.isArray(parsed)) {
|
|
25
|
+
ids = parsed
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} catch {
|
|
29
|
+
ids = []
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 2) Keep track of subscribers (callbacks)
|
|
33
|
+
const subscribers = new Set<Subscriber>()
|
|
34
|
+
|
|
35
|
+
// 3) Storage-event listener (to pick up changes from other tabs/windows).
|
|
36
|
+
window.addEventListener('storage', (e) => {
|
|
37
|
+
if (e.key === storageKey) {
|
|
38
|
+
try {
|
|
39
|
+
const newArr = e.newValue ? JSON.parse(e.newValue) : []
|
|
40
|
+
if (Array.isArray(newArr)) {
|
|
41
|
+
ids = newArr
|
|
42
|
+
// Notify all subscribers that our in-memory copy changed
|
|
43
|
+
subscribers.forEach((cb) => cb())
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
// ignore JSON errors
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
/**
|
|
53
|
+
* Subscribe to changes. Callback is called whenever setIds or a storage-event modifies this store.
|
|
54
|
+
* Returns an unsubscribe function.
|
|
55
|
+
*/
|
|
56
|
+
subscribe(callback: Subscriber): () => void {
|
|
57
|
+
subscribers.add(callback)
|
|
58
|
+
return () => {
|
|
59
|
+
subscribers.delete(callback)
|
|
60
|
+
}
|
|
61
|
+
},
|
|
62
|
+
|
|
63
|
+
/** Return a shallow copy of the current IDs array. */
|
|
64
|
+
getSnapshot(): string[] {
|
|
65
|
+
return [...ids]
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Overwrite the list of IDs (in memory + localStorage), then notify subscribers.
|
|
70
|
+
* If the new array is identical (same length & same items in order), do nothing.
|
|
71
|
+
*/
|
|
72
|
+
setIds(newIds: string[]) {
|
|
73
|
+
// Quick check: length mismatch → definitely changed
|
|
74
|
+
if (newIds.length !== ids.length) {
|
|
75
|
+
updateAndNotify(newIds)
|
|
76
|
+
return
|
|
77
|
+
}
|
|
78
|
+
// If same length, compare each item
|
|
79
|
+
for (let i = 0; i < newIds.length; i++) {
|
|
80
|
+
if (newIds[i] !== ids[i]) {
|
|
81
|
+
updateAndNotify(newIds)
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
// Otherwise, identical content → no update
|
|
86
|
+
},
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Internal helper: write to memory + localStorage + notify subscribers */
|
|
90
|
+
function updateAndNotify(newIdsArr: string[]) {
|
|
91
|
+
ids = [...newIdsArr]
|
|
92
|
+
try {
|
|
93
|
+
localStorage.setItem(storageKey, JSON.stringify(ids))
|
|
94
|
+
} catch {
|
|
95
|
+
// localStorage quota exceeded? we ignore for now.
|
|
96
|
+
}
|
|
97
|
+
subscribers.forEach((cb) => cb())
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Instantiate two separate stores:
|
|
102
|
+
export const hiddenStore = createIdListStore(LOCAL_STORAGE_KEYS_ENUM.HiddenElements)
|
|
103
|
+
export const disabledStore = createIdListStore(LOCAL_STORAGE_KEYS_ENUM.DisabledElements)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { NEW_FORM_DATA_IDENTIFIER, REGEX_PATTERNS } from '../../../../constants'
|
|
2
|
+
import { IFormContext } from '../../../form/layout-renderer/1-row'
|
|
3
|
+
import { isNewFormDataPage } from '../../../../functions'
|
|
4
|
+
import {
|
|
5
|
+
ConditionDependencyTypeEnum,
|
|
6
|
+
DeviceBreakpointEnum,
|
|
7
|
+
FieldValidationEnum,
|
|
8
|
+
FormElementConditionalKeyEnum,
|
|
9
|
+
FormStateEnum,
|
|
10
|
+
} from '../../../../enums'
|
|
11
|
+
import {
|
|
12
|
+
IConditionalValidation,
|
|
13
|
+
IConditionalValidation_AuthUser,
|
|
14
|
+
IConditionalValidation_Field,
|
|
15
|
+
IConditionalValidation_FormState,
|
|
16
|
+
IFormLayoutElementConditions,
|
|
17
|
+
} from '../../../../types'
|
|
18
|
+
|
|
19
|
+
const getValidationConfigs = (validationConfigs: IConditionalValidation[]) =>
|
|
20
|
+
validationConfigs.reduce<{
|
|
21
|
+
fieldValidations: IConditionalValidation_Field[]
|
|
22
|
+
authUserValidations: IConditionalValidation_AuthUser[]
|
|
23
|
+
hasDataIdValidation: IConditionalValidation_FormState | undefined
|
|
24
|
+
alwaysDisabled: boolean
|
|
25
|
+
hideScreens: DeviceBreakpointEnum[]
|
|
26
|
+
}>(
|
|
27
|
+
(curr, v) => {
|
|
28
|
+
if (v.dependentType === ConditionDependencyTypeEnum.Field)
|
|
29
|
+
return { ...curr, fieldValidations: [...curr.fieldValidations, v] }
|
|
30
|
+
else if (v.dependentType === ConditionDependencyTypeEnum.AuthUser)
|
|
31
|
+
return { ...curr, authUserValidations: [...curr.authUserValidations, v] }
|
|
32
|
+
else if (v.dependentType === ConditionDependencyTypeEnum.FormState) return { ...curr, hasDataIdValidation: v }
|
|
33
|
+
else if (v.dependentType === ConditionDependencyTypeEnum.AlwaysFalse) return { ...curr, alwaysDisabled: true }
|
|
34
|
+
else if (v.dependentType === ConditionDependencyTypeEnum.Screen) return { ...curr, hideScreens: v.hideScreens }
|
|
35
|
+
|
|
36
|
+
return curr
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
fieldValidations: [],
|
|
40
|
+
authUserValidations: [],
|
|
41
|
+
hasDataIdValidation: undefined,
|
|
42
|
+
alwaysDisabled: false,
|
|
43
|
+
hideScreens: [],
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
/** If any of the validations fails, the function returns false */
|
|
48
|
+
const validateConditions = (validations: IConditionalValidation_Field[], data: Record<string, any>): boolean => {
|
|
49
|
+
return validations.every((validation) => {
|
|
50
|
+
const { field, rule, value: configuredValue } = validation
|
|
51
|
+
const formValue = data[field]
|
|
52
|
+
|
|
53
|
+
switch (rule) {
|
|
54
|
+
case FieldValidationEnum.Required:
|
|
55
|
+
if (!!formValue) return true
|
|
56
|
+
return false
|
|
57
|
+
|
|
58
|
+
case FieldValidationEnum.DataLength:
|
|
59
|
+
return (
|
|
60
|
+
typeof formValue === 'string' && typeof configuredValue === 'number' && formValue.length === configuredValue
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
case FieldValidationEnum.MaxDataLength:
|
|
64
|
+
return (
|
|
65
|
+
typeof formValue === 'string' && typeof configuredValue === 'number' && formValue.length <= configuredValue
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
case FieldValidationEnum.MinDataLength:
|
|
69
|
+
return (
|
|
70
|
+
typeof formValue === 'string' && typeof configuredValue === 'number' && formValue.length >= configuredValue
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
case FieldValidationEnum.MinValue:
|
|
74
|
+
return typeof formValue === 'number' && typeof configuredValue === 'number' && formValue >= configuredValue
|
|
75
|
+
|
|
76
|
+
case FieldValidationEnum.MaxValue:
|
|
77
|
+
return typeof formValue === 'number' && typeof configuredValue === 'number' && formValue <= configuredValue
|
|
78
|
+
|
|
79
|
+
case FieldValidationEnum.Regex:
|
|
80
|
+
return (
|
|
81
|
+
typeof formValue === 'string' &&
|
|
82
|
+
typeof configuredValue === 'string' &&
|
|
83
|
+
new RegExp(configuredValue).test(formValue)
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
case FieldValidationEnum.Email:
|
|
87
|
+
return typeof formValue === 'string' && REGEX_PATTERNS.Email.test(formValue)
|
|
88
|
+
|
|
89
|
+
case FieldValidationEnum.StrictlyEquals:
|
|
90
|
+
if (!configuredValue) return !formValue || [null, undefined, ''].includes(formValue)
|
|
91
|
+
// equals no value
|
|
92
|
+
|
|
93
|
+
return formValue === configuredValue
|
|
94
|
+
|
|
95
|
+
default:
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export const isValidationsMet = (
|
|
102
|
+
type: FormElementConditionalKeyEnum,
|
|
103
|
+
dataValues: { [key: string]: any },
|
|
104
|
+
configs?: IFormLayoutElementConditions,
|
|
105
|
+
contextValues: { currentBreakpoint?: DeviceBreakpointEnum } & IFormContext = {},
|
|
106
|
+
) => {
|
|
107
|
+
const validations = configs?.[type] ?? []
|
|
108
|
+
|
|
109
|
+
if (validations.length === 0) return true
|
|
110
|
+
|
|
111
|
+
const { fieldValidations, authUserValidations, hasDataIdValidation, alwaysDisabled, hideScreens } =
|
|
112
|
+
getValidationConfigs(validations)
|
|
113
|
+
|
|
114
|
+
if (alwaysDisabled) return false
|
|
115
|
+
|
|
116
|
+
// Dependent on other fields
|
|
117
|
+
const isFieldsConditionsMet =
|
|
118
|
+
dataValues && typeof dataValues === 'object' && fieldValidations.length > 0
|
|
119
|
+
? validateConditions(fieldValidations as IConditionalValidation_Field[], dataValues)
|
|
120
|
+
: true
|
|
121
|
+
|
|
122
|
+
// Dependent on auth user
|
|
123
|
+
const isAuthUserConditionsMet = authUserValidations.length > 0 ? false : true
|
|
124
|
+
|
|
125
|
+
// Dependent on data id
|
|
126
|
+
const isDataIdConditionsMet = hasDataIdValidation
|
|
127
|
+
? hasDataIdValidation.state === FormStateEnum.New
|
|
128
|
+
? isNewFormDataPage(contextValues.formDataId)
|
|
129
|
+
: contextValues.formDataId !== NEW_FORM_DATA_IDENTIFIER
|
|
130
|
+
: true
|
|
131
|
+
|
|
132
|
+
// Dependent on screen
|
|
133
|
+
const isScreenConditionsMet = contextValues?.currentBreakpoint
|
|
134
|
+
? !hideScreens.includes(contextValues.currentBreakpoint)
|
|
135
|
+
: true
|
|
136
|
+
|
|
137
|
+
return isFieldsConditionsMet && isAuthUserConditionsMet && isDataIdConditionsMet && isScreenConditionsMet
|
|
138
|
+
}
|
|
@@ -8,16 +8,19 @@ const useGetCurrentBreakpoint = (): DeviceBreakpointEnum => {
|
|
|
8
8
|
)
|
|
9
9
|
|
|
10
10
|
useEffect(() => {
|
|
11
|
-
const handleResize = () =>
|
|
11
|
+
const handleResize = () => {
|
|
12
|
+
const current = window.visualViewport?.width || window.innerWidth
|
|
13
|
+
setWidth(current)
|
|
14
|
+
}
|
|
12
15
|
|
|
13
16
|
window.addEventListener('resize', handleResize)
|
|
14
17
|
window.addEventListener('orientationchange', handleResize)
|
|
15
|
-
window.visualViewport
|
|
18
|
+
if (window.visualViewport) window.visualViewport.addEventListener('resize', handleResize)
|
|
16
19
|
|
|
17
20
|
return () => {
|
|
18
21
|
window.removeEventListener('resize', handleResize)
|
|
19
22
|
window.removeEventListener('orientationchange', handleResize)
|
|
20
|
-
window.visualViewport
|
|
23
|
+
if (window.visualViewport) window.visualViewport.removeEventListener('resize', handleResize)
|
|
21
24
|
}
|
|
22
25
|
}, [])
|
|
23
26
|
|
|
@@ -1,17 +1,21 @@
|
|
|
1
1
|
import { Form } from 'antd'
|
|
2
|
-
import {
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
3
3
|
import dayjs from 'dayjs'
|
|
4
4
|
import { extractFiltersFromLayout } from '../../../functions/forms'
|
|
5
5
|
import { DeviceBreakpointEnum, FilterConfigTypeEnum, FormPreservedItemKeys } from '../../../enums'
|
|
6
6
|
import { LayoutRendererRow } from '../layout-renderer/1-row'
|
|
7
|
+
import useGetCurrentBreakpoint from '../../common/custom-hooks/use-window-width.hook'
|
|
8
|
+
import { IDataListHeaderLayoutContext } from './table'
|
|
7
9
|
import {
|
|
8
10
|
BSON_DATA_IDENTIFIER_PREFIXES,
|
|
11
|
+
ELEMENTS_DEFAULT_CLASS,
|
|
9
12
|
VALUE_REPLACEMENT_PLACEHOLDER,
|
|
10
13
|
VALUE_REPLACEMENT_PLACEHOLDER2,
|
|
11
14
|
} from '../../../constants'
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
import {
|
|
16
|
+
DynamicFormButtonRender,
|
|
17
|
+
IDynamicButton_DisplayStateProps,
|
|
18
|
+
} from '../layout-renderer/3-element/1-dynamic-button'
|
|
15
19
|
import {
|
|
16
20
|
IDndLayoutStructure_Responsive,
|
|
17
21
|
IFilterConfig,
|
|
@@ -22,7 +26,7 @@ import {
|
|
|
22
26
|
|
|
23
27
|
export default function FormDataListHeaderComponent({
|
|
24
28
|
layoutConfig,
|
|
25
|
-
|
|
29
|
+
dataCount,
|
|
26
30
|
updateDynamicFilter,
|
|
27
31
|
headerLayoutContext,
|
|
28
32
|
}: IFormDataListHeaderComponent) {
|
|
@@ -107,17 +111,17 @@ export default function FormDataListHeaderComponent({
|
|
|
107
111
|
)
|
|
108
112
|
|
|
109
113
|
return (
|
|
110
|
-
<Form layout="vertical" form={filtersFormRef}>
|
|
114
|
+
<Form layout="vertical" form={filtersFormRef} className={ELEMENTS_DEFAULT_CLASS.DataListHeaderForm}>
|
|
111
115
|
{dndLayout.map((row, rowIdx) => (
|
|
112
116
|
<LayoutRendererRow
|
|
113
117
|
key={rowIdx}
|
|
114
118
|
rowData={row}
|
|
115
|
-
|
|
119
|
+
dataCount={dataCount}
|
|
116
120
|
formContext={formContext}
|
|
117
121
|
elements={layoutConfig?.elements ?? {}}
|
|
118
|
-
renderButton={(btnProps
|
|
122
|
+
renderButton={(btnProps) => (
|
|
119
123
|
<DynamicFormButtonRender
|
|
120
|
-
displayStateProps={
|
|
124
|
+
displayStateProps={btnProps as IDynamicButton_DisplayStateProps}
|
|
121
125
|
formContext={formContext}
|
|
122
126
|
onCustomFunctionCall={onCustomFunctionCall}
|
|
123
127
|
/>
|
|
@@ -131,7 +135,7 @@ export default function FormDataListHeaderComponent({
|
|
|
131
135
|
type IFormDataListHeaderComponent = {
|
|
132
136
|
layoutConfig?: IDndLayoutStructure_Responsive
|
|
133
137
|
updateDynamicFilter: (match?: string) => void
|
|
134
|
-
|
|
138
|
+
dataCount?: 'pending' | number
|
|
135
139
|
headerLayoutContext: IDataListHeaderLayoutContext
|
|
136
140
|
}
|
|
137
141
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
2
2
|
import FormDataListHeaderComponent from './table-header'
|
|
3
3
|
import useDebounced from '../../common/custom-hooks/use-debounce.hook'
|
|
4
|
-
import {
|
|
4
|
+
import { Empty, Table } from 'antd'
|
|
5
5
|
import { ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
|
|
6
6
|
import { SorterResult } from 'antd/es/table/interface'
|
|
7
7
|
import { constructDynamicFormHref, renderTableColumns, revertProjectionKey } from '../../../functions/forms'
|
|
@@ -13,6 +13,7 @@ import { IFormDataListConfig, IFormDataListData, IFormDataTableColumn } from '..
|
|
|
13
13
|
import { processOptions } from '../../../functions/forms/get-data-list-option-value'
|
|
14
14
|
import { IFormContext } from '../layout-renderer/1-row'
|
|
15
15
|
import { useFindDynamiForm } from '../../common/custom-hooks'
|
|
16
|
+
import { ELEMENTS_DEFAULT_CLASS } from '../../../constants'
|
|
16
17
|
|
|
17
18
|
export default function FormDataListTableComponent({
|
|
18
19
|
layoutsConfigs,
|
|
@@ -88,7 +89,7 @@ export default function FormDataListTableComponent({
|
|
|
88
89
|
|
|
89
90
|
setLoading(false)
|
|
90
91
|
},
|
|
91
|
-
[headerLayoutContext
|
|
92
|
+
[headerLayoutContext],
|
|
92
93
|
)
|
|
93
94
|
|
|
94
95
|
useEffect(() => {
|
|
@@ -101,6 +102,7 @@ export default function FormDataListTableComponent({
|
|
|
101
102
|
)
|
|
102
103
|
|
|
103
104
|
// if (loading) return
|
|
105
|
+
console.log(loading, parentLoadings)
|
|
104
106
|
if ((loading || parentLoadings.initial) && loadingBlock) return loadingBlock
|
|
105
107
|
|
|
106
108
|
return (
|
|
@@ -111,16 +113,12 @@ export default function FormDataListTableComponent({
|
|
|
111
113
|
setParentLoading(true)
|
|
112
114
|
setFilterReqData((c) => ({ ...c, match, current: 1 }))
|
|
113
115
|
}}
|
|
114
|
-
|
|
115
|
-
<>
|
|
116
|
-
<span className="pr-1">{otherConfigs?.title}</span>
|
|
117
|
-
{otherConfigs?.showCount && <span>({parentLoadings.data ? <Spin size="small" /> : dataList.total})</span>}
|
|
118
|
-
</>
|
|
119
|
-
}
|
|
116
|
+
dataCount={otherConfigs?.showCount ? (parentLoadings.data ? 'pending' : dataList.total) : undefined}
|
|
120
117
|
headerLayoutContext={headerLayoutContext}
|
|
121
118
|
/>
|
|
122
119
|
{(!otherConfigs || otherConfigs.listType === FormDataListViewTypeEnum.Table) && (
|
|
123
120
|
<Table<IFormDataListData>
|
|
121
|
+
className={ELEMENTS_DEFAULT_CLASS.DataTable}
|
|
124
122
|
dataSource={dataList.data}
|
|
125
123
|
columns={tableColumnsFiltered}
|
|
126
124
|
rowKey={(record) => record._id}
|
|
@@ -145,6 +143,11 @@ export default function FormDataListTableComponent({
|
|
|
145
143
|
}
|
|
146
144
|
}
|
|
147
145
|
loading={parentLoadings.data}
|
|
146
|
+
locale={{
|
|
147
|
+
emptyText: (
|
|
148
|
+
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} description={otherConfigs?.noDataText || 'No Data'} />
|
|
149
|
+
),
|
|
150
|
+
}}
|
|
148
151
|
onChange={(cbPagination, _, sorter: SorterResult<IFormDataListData> | SorterResult<IFormDataListData>[]) => {
|
|
149
152
|
console.log('ON TABLE CHANGE', cbPagination, sorter)
|
|
150
153
|
setParentLoading(true)
|