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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "form-craft-package",
3
- "version": "1.1.12",
3
+ "version": "1.1.13",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",
@@ -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 { defaultReqDataConfig, IReqDataConfig } from '.'
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
- // needs to be called when there is a change in form values
34
- if (!appliedFilters) return
35
-
36
- let dynamicFilters = {}
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 (config.config) dynamicFilters = config.config
40
+ if (filteredValues.length > 0) {
41
+ const filtersToApply = filteredValues.map(([field, value]) => {
42
+ const config = filterConfigs[field]
43
43
 
44
- if (config.type === FilterConfigTypeEnum.ByAuthUser) customFilters = { ...customFilters, createdById: userId }
45
- })
46
-
47
- setFilterReqData((c) =>
48
- c ? { ...c, customFilter: customFilters, dynamicFilter: JSON.stringify(dynamicFilters) } : defaultReqDataConfig,
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
- return (
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
- if ('type' in config) {
65
- if (typeof value !== 'string') value = dayjs(value as Date).toISOString()
66
-
67
- if (config.config) {
68
- let stringifiedFilter = JSON.stringify(config.config)
69
- stringifiedFilter = stringifiedFilter.replaceAll(VALUE_REPLACEMENT_PLACEHOLDER, value as string)
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
- setAppliedFilters((c) => ({ ...c, [key]: config as IFilterConfig }))
75
- } else {
76
- // nested applies to radio button, where each option has its own filter, but only one needs to apply
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 [filterReqData, setFilterReqData] = useState<IReqDataConfig | undefined>()
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 (!defaultFilter || defaultFilter.type === FilterConfigTypeEnum.NoFilter)
53
- setFilterReqData(defaultReqDataConfig)
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
- setLoadings((c) => ({ ...c, data: true }))
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
- ...filterReqData,
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
- [filterReqData, paginationConfig],
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={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
- : defaultReqDataConfig,
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 defaultReqDataConfig = {
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