form-craft-package 1.7.9-dev.2 → 1.7.10-dev.0
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/index.ts +4 -1
- package/package.json +4 -4
- package/src/api/client.ts +10 -0
- package/src/components/common/countdown.tsx +44 -0
- package/src/components/common/custom-hooks/index.ts +2 -0
- package/src/components/common/custom-hooks/use-breadcrumb.hook.ts +18 -0
- package/src/components/common/custom-hooks/use-check-element-conditions.hook.ts +3 -2
- package/src/components/common/custom-hooks/use-dayjs-extender.hook.ts +8 -0
- package/src/components/common/custom-hooks/use-many-to-many-connector.hook.ts +2 -3
- package/src/components/common/custom-hooks/use-notification.hook.tsx +1 -1
- package/src/components/common/custom-hooks/use-window-width.hook.ts +6 -4
- package/src/components/common/loading-skeletons/details.tsx +61 -6
- package/src/components/common/loading-skeletons/index.tsx +10 -2
- package/src/components/form/1-list/index.tsx +32 -17
- package/src/components/form/1-list/table-header.tsx +29 -55
- package/src/components/form/1-list/table.tsx +3 -5
- package/src/components/form/2-details/index.tsx +26 -26
- package/src/components/form/layout-renderer/1-row/index.tsx +9 -7
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +25 -19
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-button-navigate.hook.tsx +11 -5
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-custom-function-call.hook.ts +22 -0
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-generate-report.hook.tsx +2 -2
- package/src/components/form/layout-renderer/3-element/2-field-element.tsx +40 -6
- package/src/components/form/layout-renderer/3-element/5-re-captcha.tsx +1 -1
- package/src/components/form/layout-renderer/3-element/6-signature.tsx +2 -3
- package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +142 -61
- package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +34 -27
- package/src/components/form/layout-renderer/3-element/index.tsx +3 -0
- package/src/components/index.tsx +2 -0
- package/src/components/modals/pdf-preview.modal.tsx +41 -0
- package/src/constants.ts +1 -0
- package/src/enums/form.enum.ts +8 -7
- package/src/enums/index.ts +1 -0
- package/src/functions/forms/conditional-rule-validator.ts +2 -0
- package/src/functions/forms/data-render-functions.tsx +21 -6
- package/src/functions/forms/get-data-list-option-value.ts +3 -3
- package/src/functions/forms/index.ts +33 -4
- package/src/functions/forms/linked-form-joins.ts +19 -0
- package/src/functions/reports/create-blob-url.ts +16 -0
- package/src/functions/reports/index.tsx +1 -0
- package/src/types/forms/data-list/filter-config.ts +5 -16
- package/src/types/forms/data-list/index.ts +2 -0
- package/src/types/forms/index.ts +0 -1
- package/src/types/forms/layout-elements/data-render-config.ts +5 -5
- package/src/types/forms/layout-elements/field-option-source.ts +25 -10
- package/src/types/forms/layout-elements/index.ts +7 -1
- package/src/types/forms/layout-elements/style.ts +0 -4
- package/src/types/forms/relationship/index.ts +6 -6
- package/src/types/index.ts +2 -0
- package/src/functions/forms/breadcrumb-handlers.ts +0 -21
package/index.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import client from './src/api/client'
|
|
2
|
+
|
|
1
3
|
export * from './src/enums'
|
|
2
4
|
export * from './src/types'
|
|
3
5
|
export * from './src/constants'
|
|
@@ -5,4 +7,5 @@ export * from './src/functions'
|
|
|
5
7
|
export * from './src/components'
|
|
6
8
|
export * from './src/components/common/custom-hooks'
|
|
7
9
|
export * from './src/api/client'
|
|
8
|
-
export * from './src/api/user'
|
|
10
|
+
export * from './src/api/user'
|
|
11
|
+
export { client }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "form-craft-package",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.10-dev.0",
|
|
4
4
|
"main": "index.ts",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -11,13 +11,11 @@
|
|
|
11
11
|
"license": "ISC",
|
|
12
12
|
"description": "",
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@types/react-google-recaptcha": "^2.1.9",
|
|
15
|
-
"@types/react-signature-canvas": "^1.0.7",
|
|
16
14
|
"ajv": "^8.17.1",
|
|
17
|
-
"bson": "^4.7.2",
|
|
18
15
|
"pdfmake": "^0.2.18",
|
|
19
16
|
"qs": "^6.14.0",
|
|
20
17
|
"react-google-recaptcha": "^3.1.0",
|
|
18
|
+
"react-pdf": "^9.2.1",
|
|
21
19
|
"react-signature-canvas": "^1.0.7"
|
|
22
20
|
},
|
|
23
21
|
"peerDependencies": {
|
|
@@ -39,6 +37,8 @@
|
|
|
39
37
|
"@types/qs": "^6.9.18",
|
|
40
38
|
"@types/react": "^18.3.18",
|
|
41
39
|
"@types/react-dom": "^18.3.5",
|
|
40
|
+
"@types/react-google-recaptcha": "^2.1.9",
|
|
41
|
+
"@types/react-signature-canvas": "^1.0.7",
|
|
42
42
|
"react": "^18.3.1",
|
|
43
43
|
"react-dom": "^18.3.1",
|
|
44
44
|
"ts-node": "^10.9.2",
|
package/src/api/client.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { LOCAL_STORAGE_KEYS_ENUM, SHARED_COOKIE_KEYS } from '../enums'
|
|
|
7
7
|
import { IDynamicForm } from '../types'
|
|
8
8
|
import { constructDynamicFormHref, fetchDynamicForms } from '../functions'
|
|
9
9
|
import { CLIENT_ID, CLIENT_SECRET } from '../constants'
|
|
10
|
+
import { breadcrumbStore } from '../components/common/custom-hooks/use-breadcrumb.hook'
|
|
10
11
|
|
|
11
12
|
const baseURL = import.meta.env.VITE_API_BASE_URL
|
|
12
13
|
|
|
@@ -70,6 +71,15 @@ const auth = async (
|
|
|
70
71
|
if (!loginAuthRes.data.required2FA || type === 'refresh_token') {
|
|
71
72
|
Cookies.set(SHARED_COOKIE_KEYS.Authorized, 'true')
|
|
72
73
|
forms = await fetchDynamicForms()
|
|
74
|
+
|
|
75
|
+
breadcrumbStore.reset()
|
|
76
|
+
if (forms.length) {
|
|
77
|
+
const firstForm = forms[0]
|
|
78
|
+
const splittedFormName = firstForm.name.split(' ')?.[0] ?? ''
|
|
79
|
+
|
|
80
|
+
breadcrumbStore.push({ label: splittedFormName, href: constructDynamicFormHref(firstForm.name) })
|
|
81
|
+
}
|
|
82
|
+
|
|
73
83
|
localStorage.setItem(LOCAL_STORAGE_KEYS_ENUM.DynamicForms, JSON.stringify(forms))
|
|
74
84
|
localStorage.setItem(LOCAL_STORAGE_KEYS_ENUM.Domain, domain)
|
|
75
85
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useState, useEffect, useRef } from 'react'
|
|
2
|
+
|
|
3
|
+
export default function Countdown_FillerPortal({ initialSeconds, label = '', onComplete }: Countdown_FillerPortal) {
|
|
4
|
+
const [secondsLeft, setSecondsLeft] = useState(initialSeconds)
|
|
5
|
+
const intervalRef = useRef<NodeJS.Timeout | null>(null)
|
|
6
|
+
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
intervalRef.current = setInterval(() => {
|
|
9
|
+
setSecondsLeft((prev) => {
|
|
10
|
+
if (prev <= 1) {
|
|
11
|
+
clearInterval(intervalRef.current!)
|
|
12
|
+
onComplete()
|
|
13
|
+
return 0
|
|
14
|
+
}
|
|
15
|
+
return prev - 1
|
|
16
|
+
})
|
|
17
|
+
}, 1000)
|
|
18
|
+
|
|
19
|
+
return () => {
|
|
20
|
+
if (intervalRef.current) clearInterval(intervalRef.current)
|
|
21
|
+
}
|
|
22
|
+
}, [onComplete])
|
|
23
|
+
|
|
24
|
+
const formatTime = (secs: number) => {
|
|
25
|
+
const minutes = Math.floor(secs / 60)
|
|
26
|
+
const seconds = secs % 60
|
|
27
|
+
const minutesString = String(minutes).padStart(2, '0')
|
|
28
|
+
const secondsString = String(seconds)
|
|
29
|
+
|
|
30
|
+
return `${minutes > 0 ? `${minutesString}:` : ''}${secondsString}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div className="flex items-center gap-1 text-gray-400 text-[13px] italic">
|
|
35
|
+
{label} {formatTime(secondsLeft)} sec
|
|
36
|
+
</div>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface Countdown_FillerPortal {
|
|
41
|
+
initialSeconds: number
|
|
42
|
+
label?: string
|
|
43
|
+
onComplete: () => void
|
|
44
|
+
}
|
|
@@ -31,6 +31,17 @@ function handlePopState() {
|
|
|
31
31
|
const idx = crumbs.findIndex((c) => c.href === href)
|
|
32
32
|
breadcrumbStore.sliceAt(idx >= 0 ? idx : 0)
|
|
33
33
|
}
|
|
34
|
+
function dedupeAdjacentCrumbs(crumbs: IBreadcrumb[]): IBreadcrumb[] {
|
|
35
|
+
if (crumbs.length === 0) return []
|
|
36
|
+
|
|
37
|
+
const result: IBreadcrumb[] = [crumbs[0]]
|
|
38
|
+
for (let i = 1; i < crumbs.length; i++) {
|
|
39
|
+
if (crumbs[i].href !== crumbs[i - 1].href) {
|
|
40
|
+
result.push(crumbs[i])
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return result
|
|
44
|
+
}
|
|
34
45
|
|
|
35
46
|
if (typeof window !== 'undefined') {
|
|
36
47
|
window.addEventListener('popstate', handlePopState)
|
|
@@ -50,6 +61,8 @@ export const breadcrumbStore = {
|
|
|
50
61
|
if (last?.href.endsWith(NEW_FORM_DATA_IDENTIFIER)) crumbs = [...crumbs.slice(0, -1), c]
|
|
51
62
|
else crumbs = [...crumbs, c]
|
|
52
63
|
|
|
64
|
+
crumbs = dedupeAdjacentCrumbs(crumbs)
|
|
65
|
+
|
|
53
66
|
saveToStorage()
|
|
54
67
|
notify()
|
|
55
68
|
},
|
|
@@ -63,6 +76,8 @@ export const breadcrumbStore = {
|
|
|
63
76
|
return c
|
|
64
77
|
})
|
|
65
78
|
|
|
79
|
+
crumbs = dedupeAdjacentCrumbs(crumbs)
|
|
80
|
+
|
|
66
81
|
if (changed) {
|
|
67
82
|
saveToStorage()
|
|
68
83
|
notify()
|
|
@@ -70,6 +85,9 @@ export const breadcrumbStore = {
|
|
|
70
85
|
},
|
|
71
86
|
sliceAt: (i: number) => {
|
|
72
87
|
crumbs = crumbs.slice(0, i + 1)
|
|
88
|
+
|
|
89
|
+
crumbs = dedupeAdjacentCrumbs(crumbs)
|
|
90
|
+
|
|
73
91
|
saveToStorage()
|
|
74
92
|
notify()
|
|
75
93
|
},
|
|
@@ -3,9 +3,10 @@ import { useEffect, useState } from 'react'
|
|
|
3
3
|
import { IConditionalValidation_Field, IFormLayoutElementConditions } from '../../../types'
|
|
4
4
|
import { DeviceBreakpointEnum, FormElementConditionalKeyEnum, FormStateEnum } from '../../../enums'
|
|
5
5
|
import { getValidationConfigs, validateConditions } from '../../../functions/forms/conditional-rule-validator'
|
|
6
|
-
import { NEW_FORM_DATA_IDENTIFIER } from '../../../constants'
|
|
7
6
|
import useGetCurrentBreakpoint from './use-window-width.hook'
|
|
8
7
|
import { IFormContext } from '../../form/layout-renderer/1-row'
|
|
8
|
+
import { isNewFormDataPage } from '../../../functions'
|
|
9
|
+
import { NEW_FORM_DATA_IDENTIFIER } from '../../../constants'
|
|
9
10
|
|
|
10
11
|
export const useCheckElementConditions = ({
|
|
11
12
|
formRef,
|
|
@@ -71,7 +72,7 @@ const isValidationsMet = (
|
|
|
71
72
|
// Dependent on data id
|
|
72
73
|
const isDataIdConditionsMet = hasDataIdValidation
|
|
73
74
|
? hasDataIdValidation.state === FormStateEnum.New
|
|
74
|
-
?
|
|
75
|
+
? isNewFormDataPage(contextValues.formDataId)
|
|
75
76
|
: contextValues.formDataId !== NEW_FORM_DATA_IDENTIFIER
|
|
76
77
|
: true
|
|
77
78
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { useEffect, useMemo } from 'react'
|
|
2
2
|
import { useBreadcrumb } from './use-breadcrumb.hook'
|
|
3
3
|
import { FormInstance } from 'antd'
|
|
4
|
-
import { getForeignKeysFromBreadcrumb } from '../../../functions/forms/breadcrumb-handlers'
|
|
5
4
|
|
|
6
5
|
export const useManyToManyConnector = (formRef: FormInstance) => {
|
|
7
6
|
const { breadcrumbs } = useBreadcrumb()
|
|
@@ -21,9 +20,9 @@ export const useManyToManyConnector = (formRef: FormInstance) => {
|
|
|
21
20
|
useEffect(() => {
|
|
22
21
|
if (!prevPageBreadcrumb) return
|
|
23
22
|
|
|
24
|
-
const {
|
|
23
|
+
const { currentFormFK, otherFormFK } = prevPageBreadcrumb.manyToManyRelInfo
|
|
25
24
|
|
|
26
|
-
if (
|
|
25
|
+
if (currentFormFK && otherFormFK) formRef.setFieldValue(otherFormFK, prevPageBreadcrumb.formDataId)
|
|
27
26
|
}, [prevPageBreadcrumb, formRef])
|
|
28
27
|
|
|
29
28
|
return
|
|
@@ -15,15 +15,17 @@ const useGetCurrentBreakpoint = (): DeviceBreakpointEnum => {
|
|
|
15
15
|
|
|
16
16
|
if (!width) return DeviceBreakpointEnum.Default
|
|
17
17
|
|
|
18
|
-
if (width
|
|
18
|
+
if (width <= parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.Mobile])) return DeviceBreakpointEnum.Mobile
|
|
19
19
|
|
|
20
|
-
if (width
|
|
20
|
+
if (width <= parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.TabletPortrait]))
|
|
21
21
|
return DeviceBreakpointEnum.TabletPortrait
|
|
22
22
|
|
|
23
|
-
if (width
|
|
23
|
+
if (width <= parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.TabletLandscape]))
|
|
24
24
|
return DeviceBreakpointEnum.TabletLandscape
|
|
25
25
|
|
|
26
|
-
if (width
|
|
26
|
+
if (width <= parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.Laptop])) return DeviceBreakpointEnum.Laptop
|
|
27
|
+
|
|
28
|
+
if (width <= parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.LaptopLarge])) return DeviceBreakpointEnum.LaptopLarge
|
|
27
29
|
|
|
28
30
|
return DeviceBreakpointEnum.Default
|
|
29
31
|
}
|
|
@@ -1,11 +1,66 @@
|
|
|
1
|
-
import { Spin } from 'antd'
|
|
1
|
+
import { Col, Row, Spin } from 'antd'
|
|
2
|
+
import SkeletonBlock from '.'
|
|
3
|
+
import SectionPanel from '../section-panel'
|
|
2
4
|
|
|
3
5
|
export default function FormDataListSkeleton_Details() {
|
|
4
6
|
return (
|
|
5
|
-
<
|
|
6
|
-
<
|
|
7
|
-
<
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
<Row justify="center" className="mt-10">
|
|
8
|
+
<Col sm={24} lg={18} xl={14} xxl={12}>
|
|
9
|
+
<SectionPanel
|
|
10
|
+
className="border"
|
|
11
|
+
header={{
|
|
12
|
+
title: (
|
|
13
|
+
<div className="flex items-center gap-2 p-3">
|
|
14
|
+
<SkeletonBlock width="150px" />
|
|
15
|
+
<SkeletonBlock width={32} height={32} radius={10} />
|
|
16
|
+
</div>
|
|
17
|
+
),
|
|
18
|
+
action: (
|
|
19
|
+
<div className="pr-3">
|
|
20
|
+
<SkeletonBlock width="150px" />
|
|
21
|
+
</div>
|
|
22
|
+
),
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
<div className="p-3 border-t space-y-4">
|
|
26
|
+
<Row gutter={[20, 0]}>
|
|
27
|
+
<Col span={12}>{field}</Col>
|
|
28
|
+
<Col span={12}>{field}</Col>
|
|
29
|
+
</Row>
|
|
30
|
+
<Row gutter={[20, 0]}>
|
|
31
|
+
<Col span={12}>{field}</Col>
|
|
32
|
+
<Col span={12}>{field}</Col>
|
|
33
|
+
</Row>
|
|
34
|
+
<div className="flex items-center justify-center gap-2 text-16 italic text-primary my-2">
|
|
35
|
+
<Spin /> Loading layout & fetching data..
|
|
36
|
+
</div>
|
|
37
|
+
<Row gutter={[20, 0]}>
|
|
38
|
+
<Col span={12}>{field}</Col>
|
|
39
|
+
<Col span={12}>{field}</Col>
|
|
40
|
+
</Row>
|
|
41
|
+
<Row gutter={[20, 0]}>
|
|
42
|
+
<Col span={8}>{field}</Col>
|
|
43
|
+
<Col span={8}>{field}</Col>
|
|
44
|
+
<Col span={8}>{field}</Col>
|
|
45
|
+
</Row>
|
|
46
|
+
<Row gutter={[20, 0]}>
|
|
47
|
+
<Col span={8}>{field}</Col>
|
|
48
|
+
<Col span={8}>{field}</Col>
|
|
49
|
+
<Col span={8}>{field}</Col>
|
|
50
|
+
</Row>
|
|
51
|
+
<div className="flex justify-end">
|
|
52
|
+
<SkeletonBlock width={200} />
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</SectionPanel>
|
|
56
|
+
</Col>
|
|
57
|
+
</Row>
|
|
10
58
|
)
|
|
11
59
|
}
|
|
60
|
+
|
|
61
|
+
const field = (
|
|
62
|
+
<div className="flex flex-col gap-1">
|
|
63
|
+
<SkeletonBlock width={80} height={14} />
|
|
64
|
+
<SkeletonBlock width="100%" />
|
|
65
|
+
</div>
|
|
66
|
+
)
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
export default function SkeletonBlock({
|
|
2
|
-
|
|
1
|
+
export default function SkeletonBlock({
|
|
2
|
+
height = 32,
|
|
3
|
+
width = '100%',
|
|
4
|
+
radius = 4,
|
|
5
|
+
}: {
|
|
6
|
+
height?: number
|
|
7
|
+
width?: number | string
|
|
8
|
+
radius?: number | string
|
|
9
|
+
}) {
|
|
10
|
+
return <div style={{ height, width, borderRadius: radius }} className="skeleton-loader " />
|
|
3
11
|
}
|
|
@@ -4,26 +4,20 @@ import FormDataListSkeleton_Table from '../../common/loading-skeletons/table'
|
|
|
4
4
|
import { ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
|
|
5
5
|
import FormDataListTableComponent, { IDataListLayoutConfig } from './table'
|
|
6
6
|
import { IFormJoin, IFormDataListData, IFormDataListConfig } from '../../../types'
|
|
7
|
-
import { getProjectionKey } from '../../../functions'
|
|
8
|
-
|
|
9
|
-
function FormDataListComponent({
|
|
10
|
-
formId,
|
|
11
|
-
userId,
|
|
12
|
-
companyKey,
|
|
13
|
-
baseServerUrl,
|
|
14
|
-
onCustomFunctionCall,
|
|
15
|
-
}: IFormDataListComponent) {
|
|
7
|
+
import { getProjectionKey, mergeJoins } from '../../../functions'
|
|
8
|
+
|
|
9
|
+
function FormDataListComponent({ formId, companyKey, baseServerUrl, onCustomFunctionCall }: IFormDataListComponent) {
|
|
16
10
|
const [loadings, setLoadings] = useState({ initial: true, data: true })
|
|
17
11
|
const [dataList, setDataList] = useState<{ data: IFormDataListData[]; total: number }>({ data: [], total: 0 })
|
|
18
12
|
const [listLayoutConfig, setListLayoutConfig] = useState<IDataListLayoutConfig>()
|
|
19
13
|
|
|
20
|
-
const reportDataApiCancelFuncRef = useRef<() => void | undefined>(
|
|
14
|
+
const reportDataApiCancelFuncRef = useRef<() => void | undefined>()
|
|
21
15
|
const dataProjectRef = useRef<{ [key: string]: string }>({})
|
|
22
16
|
const formJoinsRef = useRef<IFormJoin[]>([])
|
|
23
|
-
const defaultFilterReqDataRef = useRef<IDataListReqData | undefined>(
|
|
17
|
+
const defaultFilterReqDataRef = useRef<IDataListReqData | undefined>()
|
|
24
18
|
|
|
25
19
|
const attachmentBaseUrl = useMemo(() => `${baseServerUrl}/api/attachment/${companyKey}`, [baseServerUrl, companyKey])
|
|
26
|
-
const headerLayoutContext = { formId,
|
|
20
|
+
const headerLayoutContext = { formId, attachmentBaseUrl, companyKey, onCustomFunctionCall }
|
|
27
21
|
|
|
28
22
|
useEffect(() => {
|
|
29
23
|
setLoadings({ initial: true, data: true })
|
|
@@ -41,14 +35,24 @@ function FormDataListComponent({
|
|
|
41
35
|
request.then(async (res) => {
|
|
42
36
|
if (res.status === 200) {
|
|
43
37
|
const dataListConfig: IFormDataListConfig = res.data.Data.dataListConfig
|
|
44
|
-
const { columns,
|
|
38
|
+
const { columns, pagination, defaultFilter, defaultSorter, header } = dataListConfig
|
|
45
39
|
|
|
46
40
|
if (Array.isArray(columns))
|
|
47
41
|
dataProjectRef.current = columns.reduce(
|
|
48
42
|
(curr, c) => (c.key ? { ...curr, [getProjectionKey(c.key)]: `$${c.key}` } : curr),
|
|
49
43
|
{},
|
|
50
44
|
)
|
|
51
|
-
|
|
45
|
+
|
|
46
|
+
if (Array.isArray(columns))
|
|
47
|
+
formJoinsRef.current = mergeJoins([
|
|
48
|
+
...columns.map((c) => c.joins),
|
|
49
|
+
...Object.values(header.elements).reduce((c: IFormJoin[], el) => {
|
|
50
|
+
if (el.props && 'filter' in el.props && Array.isArray(el.props.filter?.joins)) {
|
|
51
|
+
return [...c, el.props.filter.joins]
|
|
52
|
+
}
|
|
53
|
+
return c
|
|
54
|
+
}, []),
|
|
55
|
+
])
|
|
52
56
|
|
|
53
57
|
const defaultReqData: IDataListReqData = {}
|
|
54
58
|
if (!pagination?.hasNoPagination) {
|
|
@@ -96,8 +100,13 @@ function FormDataListComponent({
|
|
|
96
100
|
|
|
97
101
|
const { request, cancel } = cancelableClient.post(`/api/report/data/${formId}`, {
|
|
98
102
|
joins: formJoinsRef.current,
|
|
99
|
-
|
|
103
|
+
// group: JSON.stringify({
|
|
104
|
+
// _id: '$_id',
|
|
105
|
+
// ...Object.entries(dataProjectRef.current).reduce((c, n) => ({ ...c, [n[0]]: { $first: n[1] } }), {}),
|
|
106
|
+
// }),
|
|
107
|
+
// project: JSON.stringify(Object.keys(dataProjectRef.current).reduce((c, n) => ({ ...c, [n]: 1 }), {})),
|
|
100
108
|
project: JSON.stringify(dataProjectRef.current),
|
|
109
|
+
...restReqData,
|
|
101
110
|
})
|
|
102
111
|
reportDataApiCancelFuncRef.current = cancel
|
|
103
112
|
|
|
@@ -105,7 +114,10 @@ function FormDataListComponent({
|
|
|
105
114
|
.then((res) => {
|
|
106
115
|
if (res.status === 200) setDataList({ data: res.data.data, total: res.data.totalRecords })
|
|
107
116
|
})
|
|
108
|
-
.finally(() =>
|
|
117
|
+
.finally(() => {
|
|
118
|
+
setLoadings({ initial: false, data: false })
|
|
119
|
+
reportDataApiCancelFuncRef.current = undefined
|
|
120
|
+
})
|
|
109
121
|
},
|
|
110
122
|
[formId],
|
|
111
123
|
)
|
|
@@ -115,6 +127,10 @@ function FormDataListComponent({
|
|
|
115
127
|
layoutsConfigs={listLayoutConfig}
|
|
116
128
|
dataList={dataList}
|
|
117
129
|
updateDataList={(reqData) => {
|
|
130
|
+
if (reportDataApiCancelFuncRef.current)
|
|
131
|
+
// prev request
|
|
132
|
+
reportDataApiCancelFuncRef.current()
|
|
133
|
+
|
|
118
134
|
fetchFormDataList(reqData)
|
|
119
135
|
}}
|
|
120
136
|
parentLoadings={loadings}
|
|
@@ -130,7 +146,6 @@ type IFormDataListComponent = {
|
|
|
130
146
|
baseServerUrl?: string
|
|
131
147
|
companyKey?: string
|
|
132
148
|
formId?: number
|
|
133
|
-
userId: string | number
|
|
134
149
|
} & ICustomFunctionCall
|
|
135
150
|
|
|
136
151
|
export interface IDataListReqData {
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import { Form } from 'antd'
|
|
2
2
|
import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
3
3
|
import dayjs from 'dayjs'
|
|
4
|
-
import { extractFiltersFromLayout
|
|
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 { VALUE_REPLACEMENT_PLACEHOLDER } from '../../../constants'
|
|
7
|
+
import { BSON_DATA_IDENTIFIER_PREFIXES, VALUE_REPLACEMENT_PLACEHOLDER } from '../../../constants'
|
|
8
8
|
import { DynamicFormButtonRender } from '../layout-renderer/3-element/1-dynamic-button'
|
|
9
9
|
import useGetCurrentBreakpoint from '../../common/custom-hooks/use-window-width.hook'
|
|
10
10
|
import { IDataListHeaderLayoutContext } from './table'
|
|
11
11
|
import {
|
|
12
12
|
IDndLayoutStructure_Responsive,
|
|
13
|
-
IFilterByAuthUser,
|
|
14
13
|
IFilterConfig,
|
|
15
14
|
IFilterCustom,
|
|
16
15
|
IFilterNested,
|
|
@@ -20,13 +19,13 @@ import {
|
|
|
20
19
|
export default function FormDataListHeaderComponent({
|
|
21
20
|
layoutConfig,
|
|
22
21
|
titleComponent,
|
|
23
|
-
startLoading,
|
|
24
22
|
updateDynamicFilter,
|
|
25
23
|
headerLayoutContext,
|
|
26
24
|
}: IFormDataListHeaderComponent) {
|
|
27
25
|
const [filtersFormRef] = Form.useForm()
|
|
28
26
|
const [filterConfigs, setFilterConfigs] = useState<IFilterNested>({})
|
|
29
|
-
const {
|
|
27
|
+
const { formId, detailPageFormId, parentFormJoins, formDataId, manyToManyRelInfo, onCustomFunctionCall } =
|
|
28
|
+
headerLayoutContext
|
|
30
29
|
const isInitialFetchRef = useRef(true)
|
|
31
30
|
|
|
32
31
|
const currentBreakpoint = useGetCurrentBreakpoint()
|
|
@@ -36,11 +35,11 @@ export default function FormDataListHeaderComponent({
|
|
|
36
35
|
}, [layoutConfig])
|
|
37
36
|
|
|
38
37
|
const dndLayout = useMemo(() => {
|
|
39
|
-
const breakpoint = currentBreakpoint
|
|
38
|
+
const breakpoint = currentBreakpoint
|
|
40
39
|
|
|
41
|
-
if (!layoutConfig || !layoutConfig.layouts
|
|
40
|
+
if (!layoutConfig || !layoutConfig.layouts) return []
|
|
42
41
|
|
|
43
|
-
return layoutConfig.layouts[breakpoint]
|
|
42
|
+
return layoutConfig.layouts[breakpoint] ?? layoutConfig.layouts[DeviceBreakpointEnum.Default] ?? []
|
|
44
43
|
}, [layoutConfig, currentBreakpoint])
|
|
45
44
|
|
|
46
45
|
const filterValues = Form.useWatch([], filtersFormRef)
|
|
@@ -62,33 +61,23 @@ export default function FormDataListHeaderComponent({
|
|
|
62
61
|
const config = filterConfigs[field]
|
|
63
62
|
|
|
64
63
|
if ('type' in config) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const filterData = await handleFilterValues(config as IFilterConfig, value, { userId })
|
|
68
|
-
if (filterData) filtersToApply.push(filterData)
|
|
64
|
+
const stringifiedFilter = await handleFilterValues(config as IFilterConfig, value)
|
|
65
|
+
if (stringifiedFilter) filtersToApply.push(stringifiedFilter)
|
|
69
66
|
} else {
|
|
70
67
|
// cases like radio buttons, where each has its own filter, but only 1 needs to apply
|
|
71
68
|
const nestedConfig = config as IFilterSimple
|
|
72
69
|
const eachConfig = nestedConfig[value as string]
|
|
73
70
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
const filterData = await handleFilterValues(eachConfig as IFilterConfig, value, { userId })
|
|
77
|
-
if (filterData) filtersToApply.push(filterData)
|
|
71
|
+
const stringifiedFilter = await handleFilterValues(eachConfig as IFilterConfig, value)
|
|
72
|
+
if (stringifiedFilter) filtersToApply.push(stringifiedFilter)
|
|
78
73
|
}
|
|
79
74
|
}
|
|
80
75
|
|
|
81
|
-
const
|
|
82
|
-
(
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
customFilter: { ...curr.customFilter, ...next.customFilter },
|
|
87
|
-
dynamicFilters: [...curr.dynamicFilters, parsedDFilter],
|
|
88
|
-
}
|
|
89
|
-
},
|
|
90
|
-
{ customFilter: {}, dynamicFilters: [] },
|
|
91
|
-
)
|
|
76
|
+
const dynamicFilters = filtersToApply.reduce((curr: { [key: string]: any }[], next) => {
|
|
77
|
+
if (!next) return curr
|
|
78
|
+
const parsedDFilter = JSON.parse(next)
|
|
79
|
+
return [...curr, parsedDFilter]
|
|
80
|
+
}, [])
|
|
92
81
|
|
|
93
82
|
const updatedDynamicFilter = JSON.stringify(
|
|
94
83
|
dynamicFilters.length === 1 ? dynamicFilters[0] : { $and: dynamicFilters },
|
|
@@ -108,8 +97,8 @@ export default function FormDataListHeaderComponent({
|
|
|
108
97
|
}, [filterValues])
|
|
109
98
|
|
|
110
99
|
const formContext = useMemo(
|
|
111
|
-
() => ({ formRef: filtersFormRef, formId, formDataId, parentFormJoins, manyToManyRelInfo }),
|
|
112
|
-
[filtersFormRef, formId, formDataId, parentFormJoins, manyToManyRelInfo],
|
|
100
|
+
() => ({ formRef: filtersFormRef, detailPageFormId, formId, formDataId, parentFormJoins, manyToManyRelInfo }),
|
|
101
|
+
[filtersFormRef, formId, detailPageFormId, formDataId, parentFormJoins, manyToManyRelInfo],
|
|
113
102
|
)
|
|
114
103
|
|
|
115
104
|
return (
|
|
@@ -138,41 +127,26 @@ type IFormDataListHeaderComponent = {
|
|
|
138
127
|
layoutConfig?: IDndLayoutStructure_Responsive
|
|
139
128
|
updateDynamicFilter: (match?: string) => void
|
|
140
129
|
titleComponent?: ReactNode
|
|
141
|
-
startLoading: () => void
|
|
142
130
|
headerLayoutContext: IDataListHeaderLayoutContext
|
|
143
131
|
}
|
|
144
132
|
|
|
145
|
-
const handleFilterValues = async (
|
|
146
|
-
config: IFilterConfig,
|
|
147
|
-
value: any,
|
|
148
|
-
contextValues: { userId?: string | number; isDateFilter?: boolean },
|
|
149
|
-
) => {
|
|
133
|
+
const handleFilterValues = async (config: IFilterConfig, value: any) => {
|
|
150
134
|
if (config.type === FilterConfigTypeEnum.NoFilter) return null
|
|
151
|
-
const { userId } = contextValues
|
|
152
135
|
|
|
153
|
-
if ((config as IFilterCustom).
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
136
|
+
if ((config as IFilterCustom).bsonDataType === BSON_DATA_IDENTIFIER_PREFIXES.Date) {
|
|
137
|
+
value = { $date: dayjs(value as Date).toISOString() }
|
|
138
|
+
} else if ((config as IFilterCustom).bsonDataType === BSON_DATA_IDENTIFIER_PREFIXES.Number) {
|
|
139
|
+
value = { $numberDecimal: value }
|
|
140
|
+
} else if ((config as IFilterCustom).bsonDataType === BSON_DATA_IDENTIFIER_PREFIXES.ObjectId) {
|
|
141
|
+
value = { $oid: value }
|
|
142
|
+
}
|
|
157
143
|
|
|
158
|
-
if (
|
|
159
|
-
|
|
160
|
-
(config as IFilterByAuthUser | IFilterCustom).config
|
|
161
|
-
) {
|
|
162
|
-
let stringifiedFilter = JSON.stringify((config as IFilterByAuthUser | IFilterCustom).config)
|
|
144
|
+
if (config.type === FilterConfigTypeEnum.Custom && (config as IFilterCustom).config) {
|
|
145
|
+
let stringifiedFilter = JSON.stringify((config as IFilterCustom).config)
|
|
163
146
|
stringifiedFilter = stringifiedFilter
|
|
164
147
|
.replaceAll(VALUE_REPLACEMENT_PLACEHOLDER, (value as string).trim())
|
|
165
148
|
.replaceAll('@', '$')
|
|
166
149
|
|
|
167
|
-
return
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
if (config.type === FilterConfigTypeEnum.ByLinkedForm && value) {
|
|
171
|
-
const lastRel = config.relationshipPath[config.relationshipPath.length - 1]
|
|
172
|
-
|
|
173
|
-
return {
|
|
174
|
-
customFilter: {},
|
|
175
|
-
dynamicFilter: JSON.stringify(getIdEqualsQuery(lastRel.formId, value)),
|
|
176
|
-
}
|
|
150
|
+
return stringifiedFilter
|
|
177
151
|
}
|
|
178
152
|
}
|
|
@@ -4,7 +4,7 @@ import useDebounced from '../../common/custom-hooks/use-debounce.hook'
|
|
|
4
4
|
import { 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
|
-
import {
|
|
7
|
+
import { renderTableColumns, revertProjectionKey } from '../../../functions/forms'
|
|
8
8
|
import { useLocation } from 'react-router-dom'
|
|
9
9
|
import useGetCurrentBreakpoint from '../../common/custom-hooks/use-window-width.hook'
|
|
10
10
|
import { DeviceBreakpointEnum, FormDataListViewTypeEnum, MongoDbSortOrderEnum } from '../../../enums'
|
|
@@ -80,7 +80,7 @@ export default function FormDataListTableComponent({
|
|
|
80
80
|
...restConfigs
|
|
81
81
|
} = dataListConfig
|
|
82
82
|
|
|
83
|
-
setTableColumns(
|
|
83
|
+
setTableColumns(renderTableColumns(columns, headerLayoutContext))
|
|
84
84
|
setOtherConfigs(restConfigs)
|
|
85
85
|
|
|
86
86
|
setLoading(false)
|
|
@@ -108,7 +108,6 @@ export default function FormDataListTableComponent({
|
|
|
108
108
|
setParentLoading(true)
|
|
109
109
|
setFilterReqData((c) => ({ ...c, match, current: 1 }))
|
|
110
110
|
}}
|
|
111
|
-
startLoading={() => setParentLoading(true)}
|
|
112
111
|
titleComponent={
|
|
113
112
|
<>
|
|
114
113
|
<span className="pr-1">{otherConfigs?.title}</span>
|
|
@@ -144,7 +143,7 @@ export default function FormDataListTableComponent({
|
|
|
144
143
|
sort:
|
|
145
144
|
sorter && (sorter as SorterResult<IFormDataListData>).field
|
|
146
145
|
? JSON.stringify({
|
|
147
|
-
[(sorter as SorterResult<IFormDataListData>).field as string]:
|
|
146
|
+
[revertProjectionKey((sorter as SorterResult<IFormDataListData>).field as string)]:
|
|
148
147
|
(sorter as SorterResult<IFormDataListData>).order === 'ascend'
|
|
149
148
|
? MongoDbSortOrderEnum.Ascending
|
|
150
149
|
: MongoDbSortOrderEnum.Descending,
|
|
@@ -172,7 +171,6 @@ type IFormDataListTableComponent = {
|
|
|
172
171
|
}
|
|
173
172
|
|
|
174
173
|
export type IDataListHeaderLayoutContext = {
|
|
175
|
-
userId?: string | number
|
|
176
174
|
attachmentBaseUrl?: string
|
|
177
175
|
} & ICustomFunctionCall &
|
|
178
176
|
IFormContext
|