form-craft-package 1.1.12 → 1.1.13
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
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
export function useDebounced<T>(initialValue: T, delay: number): [T, (value: T | ((prevValue: T) => T)) => void, T] {
|
|
4
|
+
const [value, setValue] = useState(initialValue)
|
|
5
|
+
const [debouncedValue, setDebouncedValue] = useState(initialValue)
|
|
6
|
+
|
|
7
|
+
const updateValue = (value: T | ((prevValue: T) => T)) => {
|
|
8
|
+
if (typeof value === 'function') {
|
|
9
|
+
setValue((prev) => (value as (prevValue: T) => T)(prev))
|
|
10
|
+
} else {
|
|
11
|
+
setValue(value)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
useEffect(() => {
|
|
16
|
+
const handler = setTimeout(() => {
|
|
17
|
+
setDebouncedValue(value)
|
|
18
|
+
}, delay)
|
|
19
|
+
|
|
20
|
+
return () => {
|
|
21
|
+
clearTimeout(handler)
|
|
22
|
+
}
|
|
23
|
+
}, [value, delay])
|
|
24
|
+
|
|
25
|
+
return [value, updateValue, debouncedValue]
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default useDebounced
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Form } from 'antd'
|
|
2
2
|
import { ReactNode, useEffect, useState } from 'react'
|
|
3
|
-
import {
|
|
3
|
+
import { DEFAULT_NO_FILTER, DEFAULT_NO_SORTER, IReqDataConfig } from '.'
|
|
4
4
|
import dayjs from 'dayjs'
|
|
5
5
|
import { IFilterConfig, IFilterNested, IFilterSimple, IFormLayoutRow } from '../../../types'
|
|
6
6
|
import { extractFiltersFromLayout } from '../../../functions'
|
|
@@ -14,13 +14,15 @@ export default function FormDataListHeaderComponent({
|
|
|
14
14
|
userId,
|
|
15
15
|
formId,
|
|
16
16
|
titleComponent,
|
|
17
|
+
defaultFilter,
|
|
17
18
|
setFilterReqData,
|
|
18
19
|
onCustomFunctionCall,
|
|
19
20
|
}: IFormDataListHeaderComponent) {
|
|
20
21
|
const [dataListHeaderFormRef] = Form.useForm()
|
|
21
|
-
const [appliedFilters, setAppliedFilters] = useState<IFilterSimple | undefined>(undefined)
|
|
22
22
|
const [filterConfigs, setFilterConfigs] = useState<IFilterNested>({})
|
|
23
23
|
|
|
24
|
+
const filterValues = Form.useWatch([], dataListHeaderFormRef)
|
|
25
|
+
|
|
24
26
|
useEffect(() => {
|
|
25
27
|
if (dataListHeaderFormRef) dataListHeaderFormRef.setFieldValue('formId', formId)
|
|
26
28
|
}, [dataListHeaderFormRef, formId])
|
|
@@ -30,58 +32,47 @@ export default function FormDataListHeaderComponent({
|
|
|
30
32
|
}, [layout])
|
|
31
33
|
|
|
32
34
|
useEffect(() => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
let customFilters = {}
|
|
38
|
-
|
|
39
|
-
Object.values(appliedFilters).forEach((config) => {
|
|
40
|
-
if (config.type === FilterConfigTypeEnum.NoFilter) return
|
|
35
|
+
if (filterValues) {
|
|
36
|
+
const filteredValues = Object.entries(filterValues).filter(([field, value]) => !!value && !!filterConfigs[field])
|
|
37
|
+
console.log({ filterValues })
|
|
38
|
+
console.log({ filteredValues })
|
|
41
39
|
|
|
42
|
-
if (
|
|
40
|
+
if (filteredValues.length > 0) {
|
|
41
|
+
const filtersToApply = filteredValues.map(([field, value]) => {
|
|
42
|
+
const config = filterConfigs[field]
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
)
|
|
50
|
-
}, [appliedFilters])
|
|
44
|
+
if ('type' in config) return handleFilterValues(config as IFilterConfig, value, userId)
|
|
45
|
+
else {
|
|
46
|
+
// cases like radio buttons, where each has its own filter, but only 1 needs to apply
|
|
47
|
+
const nestedConfig = config as IFilterSimple
|
|
48
|
+
const eachConfig = nestedConfig[value as string]
|
|
51
49
|
|
|
52
|
-
|
|
53
|
-
<Form
|
|
54
|
-
layout="vertical"
|
|
55
|
-
form={dataListHeaderFormRef}
|
|
56
|
-
onValuesChange={(changedValues) => {
|
|
57
|
-
Object.entries(changedValues).forEach(([key, value]) => {
|
|
58
|
-
const config = filterConfigs[key]
|
|
59
|
-
if (!config) {
|
|
60
|
-
console.warn(`Key '${key}' does not have a filter config`)
|
|
61
|
-
return
|
|
50
|
+
return handleFilterValues(eachConfig as IFilterConfig, value, userId)
|
|
62
51
|
}
|
|
52
|
+
})
|
|
63
53
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
config.config = JSON.parse(stringifiedFilter)
|
|
54
|
+
const { customFilter, dynamicFilter } = filtersToApply.reduce(
|
|
55
|
+
(curr, next) => {
|
|
56
|
+
if (!next) return curr
|
|
57
|
+
const parsedDFilter = JSON.parse(next.dynamicFilter)
|
|
58
|
+
return {
|
|
59
|
+
customFilter: { ...curr.customFilter, ...next.customFilter },
|
|
60
|
+
dynamicFilter: { ...curr.dynamicFilter, ...parsedDFilter },
|
|
72
61
|
}
|
|
62
|
+
},
|
|
63
|
+
{ customFilter: {}, dynamicFilter: {} },
|
|
64
|
+
)
|
|
73
65
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const nestedConfig = config as IFilterSimple
|
|
78
|
-
if (typeof value === 'string' && value in nestedConfig)
|
|
79
|
-
setAppliedFilters((c) => ({ ...c, [key]: nestedConfig[value as string] as IFilterConfig }))
|
|
80
|
-
else console.warn(`Invalid key '${value}' for nestedConfig`)
|
|
81
|
-
}
|
|
66
|
+
setFilterReqData((c) => {
|
|
67
|
+
if (!c) return { ...DEFAULT_NO_SORTER, customFilter, dynamicFilter: JSON.stringify(dynamicFilter) }
|
|
68
|
+
return { ...c, customFilter, dynamicFilter: JSON.stringify(dynamicFilter) }
|
|
82
69
|
})
|
|
83
|
-
}
|
|
84
|
-
|
|
70
|
+
} else setFilterReqData(defaultFilter)
|
|
71
|
+
}
|
|
72
|
+
}, [filterValues, filterConfigs])
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<Form layout="vertical" form={dataListHeaderFormRef}>
|
|
85
76
|
{layout.map((row, rowIdx) => (
|
|
86
77
|
<LayoutRendererRow
|
|
87
78
|
key={rowIdx}
|
|
@@ -108,4 +99,25 @@ type IFormDataListHeaderComponent = {
|
|
|
108
99
|
titleComponent?: ReactNode
|
|
109
100
|
formId?: number
|
|
110
101
|
userId?: string | number
|
|
102
|
+
defaultFilter: IReqDataConfig
|
|
111
103
|
} & ICustomFunctionCall
|
|
104
|
+
|
|
105
|
+
const handleFilterValues = (config: IFilterConfig, value: any, userId?: string | number) => {
|
|
106
|
+
if (config.type === FilterConfigTypeEnum.NoFilter) return DEFAULT_NO_FILTER
|
|
107
|
+
|
|
108
|
+
const isDate = dayjs(value as Date).isValid()
|
|
109
|
+
if (isDate) value = dayjs(value as Date).toISOString()
|
|
110
|
+
|
|
111
|
+
let customFilters = {}
|
|
112
|
+
if (config.type === FilterConfigTypeEnum.ByAuthUser) customFilters = { createdById: userId }
|
|
113
|
+
|
|
114
|
+
console.log(config.config)
|
|
115
|
+
if (config.config) {
|
|
116
|
+
let stringifiedFilter = JSON.stringify(config.config)
|
|
117
|
+
stringifiedFilter = stringifiedFilter
|
|
118
|
+
.replaceAll(VALUE_REPLACEMENT_PLACEHOLDER, (value as string).trim())
|
|
119
|
+
.replaceAll('@', '$')
|
|
120
|
+
|
|
121
|
+
return { customFilter: customFilters, dynamicFilter: stringifiedFilter }
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
import client from '../../../functions/axios-handler'
|
|
3
3
|
import { parseJSON } from '../../../functions/json-handlers'
|
|
4
4
|
import { generateTableColumns } from '../../../functions'
|
|
@@ -9,6 +9,7 @@ import { Table } from 'antd'
|
|
|
9
9
|
import { IFormData, IFormDataListConfig, IFormLayoutRow, IFormSchema, ITableColumn } from '../../../types'
|
|
10
10
|
import { ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
|
|
11
11
|
import { SorterResult } from 'antd/es/table/interface'
|
|
12
|
+
import useDebounced from '../../common/custom-hooks/use-debounce.hook'
|
|
12
13
|
|
|
13
14
|
function FormDataListComponent({
|
|
14
15
|
formId,
|
|
@@ -19,11 +20,12 @@ function FormDataListComponent({
|
|
|
19
20
|
}: IFormDataListComponent) {
|
|
20
21
|
const [loadings, setLoadings] = useState({ schema: true, data: true })
|
|
21
22
|
const [tableColumns, setTableColumns] = useState<ITableColumn[]>([])
|
|
22
|
-
const [
|
|
23
|
+
const [_, setFilterReqData, debouncedFilterReqData] = useDebounced<IReqDataConfig | undefined>(undefined, 500)
|
|
23
24
|
const [paginationConfig, setPaginationConfig] = useState<IPagination | undefined>()
|
|
24
25
|
const [dataListConfig, setDataListConfig] = useState<Partial<IFormDataListConfig> | undefined>(undefined)
|
|
25
26
|
const [formData, setFormData] = useState<{ data: IFormData[]; total: number }>({ data: [], total: 0 })
|
|
26
27
|
const [headerLayout, setHeaderLayout] = useState<IFormLayoutRow[]>([])
|
|
28
|
+
const defaultFilterRef = useRef({ ...DEFAULT_NO_FILTER, ...DEFAULT_NO_SORTER })
|
|
27
29
|
|
|
28
30
|
const attachmentBaseUrl = useMemo(() => `${baseServerUrl}/api/attachment/${companyKey}`, [baseServerUrl, companyKey])
|
|
29
31
|
|
|
@@ -49,17 +51,15 @@ function FormDataListComponent({
|
|
|
49
51
|
setHeaderLayout(header.layout)
|
|
50
52
|
setDataListConfig(restConfigs)
|
|
51
53
|
|
|
52
|
-
if (
|
|
53
|
-
|
|
54
|
-
else
|
|
55
|
-
setFilterReqData({
|
|
54
|
+
if (defaultFilter && defaultFilter.type !== FilterConfigTypeEnum.NoFilter)
|
|
55
|
+
defaultFilterRef.current = {
|
|
56
56
|
customFilter: defaultFilter.type === FilterConfigTypeEnum.ByAuthUser ? { createdById: userId } : {},
|
|
57
57
|
dynamicFilter: defaultFilter.config
|
|
58
58
|
? // The operators are stored using @, and here it replaces @ with $. Otherwise, MongoDB's JSON-to-BSON conversion driver may detect certain operators, especially $regex, and automatically modify them. To prevent data manipulation by the MongoDB driver, @ is used instead of $.
|
|
59
59
|
JSON.stringify(defaultFilter.config).replaceAll('@', '$')
|
|
60
60
|
: JSON.stringify({}),
|
|
61
61
|
sort: defaultSorter ? { [defaultSorter.field]: defaultSorter.order } : {},
|
|
62
|
-
}
|
|
62
|
+
}
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
65
|
})
|
|
@@ -69,9 +69,8 @@ function FormDataListComponent({
|
|
|
69
69
|
|
|
70
70
|
const fetchFormDataList = useCallback(
|
|
71
71
|
(dynamicFormId: number) => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
if (!filterReqData) return
|
|
72
|
+
if (!debouncedFilterReqData) return
|
|
73
|
+
console.log({ debouncedFilterReqData })
|
|
75
74
|
|
|
76
75
|
if (!paginationConfig) {
|
|
77
76
|
setLoadings((c) => ({ ...c, data: false }))
|
|
@@ -80,7 +79,7 @@ function FormDataListComponent({
|
|
|
80
79
|
|
|
81
80
|
client
|
|
82
81
|
.post(`/api/formdata/list/${dynamicFormId}`, {
|
|
83
|
-
...
|
|
82
|
+
...debouncedFilterReqData,
|
|
84
83
|
pagination: { number: paginationConfig.current, size: paginationConfig.pageSize },
|
|
85
84
|
})
|
|
86
85
|
.then((res) => {
|
|
@@ -89,7 +88,7 @@ function FormDataListComponent({
|
|
|
89
88
|
})
|
|
90
89
|
.finally(() => setLoadings((c) => ({ ...c, data: false })))
|
|
91
90
|
},
|
|
92
|
-
[
|
|
91
|
+
[debouncedFilterReqData, paginationConfig],
|
|
93
92
|
)
|
|
94
93
|
|
|
95
94
|
useEffect(() => {
|
|
@@ -102,7 +101,11 @@ function FormDataListComponent({
|
|
|
102
101
|
<div>
|
|
103
102
|
<FormDataListHeaderComponent
|
|
104
103
|
layout={headerLayout}
|
|
105
|
-
setFilterReqData={
|
|
104
|
+
setFilterReqData={(req) => {
|
|
105
|
+
setLoadings((c) => ({ ...c, data: true }))
|
|
106
|
+
setFilterReqData(req)
|
|
107
|
+
}}
|
|
108
|
+
defaultFilter={defaultFilterRef.current}
|
|
106
109
|
userId={userId}
|
|
107
110
|
formId={formId}
|
|
108
111
|
titleComponent={
|
|
@@ -122,6 +125,7 @@ function FormDataListComponent({
|
|
|
122
125
|
loading={loadings.data}
|
|
123
126
|
onChange={(cbPagination, _, sorter: SorterResult<IFormData> | SorterResult<IFormData>[]) => {
|
|
124
127
|
console.log('ON TABLE CHANGE', cbPagination, sorter)
|
|
128
|
+
setLoadings((c) => ({ ...c, data: true }))
|
|
125
129
|
const { total, ...restCbPagination } = cbPagination
|
|
126
130
|
setPaginationConfig(restCbPagination as IPagination)
|
|
127
131
|
|
|
@@ -139,7 +143,7 @@ function FormDataListComponent({
|
|
|
139
143
|
}
|
|
140
144
|
: {},
|
|
141
145
|
}
|
|
142
|
-
:
|
|
146
|
+
: defaultFilterRef.current,
|
|
143
147
|
)
|
|
144
148
|
}}
|
|
145
149
|
/>
|
|
@@ -155,9 +159,11 @@ export interface IReqDataConfig {
|
|
|
155
159
|
sort: object
|
|
156
160
|
}
|
|
157
161
|
|
|
158
|
-
export const
|
|
162
|
+
export const DEFAULT_NO_FILTER = {
|
|
159
163
|
customFilter: {},
|
|
160
164
|
dynamicFilter: JSON.stringify({}),
|
|
165
|
+
}
|
|
166
|
+
export const DEFAULT_NO_SORTER = {
|
|
161
167
|
sort: {},
|
|
162
168
|
}
|
|
163
169
|
|