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.
Files changed (50) hide show
  1. package/index.ts +4 -1
  2. package/package.json +4 -4
  3. package/src/api/client.ts +10 -0
  4. package/src/components/common/countdown.tsx +44 -0
  5. package/src/components/common/custom-hooks/index.ts +2 -0
  6. package/src/components/common/custom-hooks/use-breadcrumb.hook.ts +18 -0
  7. package/src/components/common/custom-hooks/use-check-element-conditions.hook.ts +3 -2
  8. package/src/components/common/custom-hooks/use-dayjs-extender.hook.ts +8 -0
  9. package/src/components/common/custom-hooks/use-many-to-many-connector.hook.ts +2 -3
  10. package/src/components/common/custom-hooks/use-notification.hook.tsx +1 -1
  11. package/src/components/common/custom-hooks/use-window-width.hook.ts +6 -4
  12. package/src/components/common/loading-skeletons/details.tsx +61 -6
  13. package/src/components/common/loading-skeletons/index.tsx +10 -2
  14. package/src/components/form/1-list/index.tsx +32 -17
  15. package/src/components/form/1-list/table-header.tsx +29 -55
  16. package/src/components/form/1-list/table.tsx +3 -5
  17. package/src/components/form/2-details/index.tsx +26 -26
  18. package/src/components/form/layout-renderer/1-row/index.tsx +9 -7
  19. package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +25 -19
  20. package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-button-navigate.hook.tsx +11 -5
  21. package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-custom-function-call.hook.ts +22 -0
  22. package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-generate-report.hook.tsx +2 -2
  23. package/src/components/form/layout-renderer/3-element/2-field-element.tsx +40 -6
  24. package/src/components/form/layout-renderer/3-element/5-re-captcha.tsx +1 -1
  25. package/src/components/form/layout-renderer/3-element/6-signature.tsx +2 -3
  26. package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +142 -61
  27. package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +34 -27
  28. package/src/components/form/layout-renderer/3-element/index.tsx +3 -0
  29. package/src/components/index.tsx +2 -0
  30. package/src/components/modals/pdf-preview.modal.tsx +41 -0
  31. package/src/constants.ts +1 -0
  32. package/src/enums/form.enum.ts +8 -7
  33. package/src/enums/index.ts +1 -0
  34. package/src/functions/forms/conditional-rule-validator.ts +2 -0
  35. package/src/functions/forms/data-render-functions.tsx +21 -6
  36. package/src/functions/forms/get-data-list-option-value.ts +3 -3
  37. package/src/functions/forms/index.ts +33 -4
  38. package/src/functions/forms/linked-form-joins.ts +19 -0
  39. package/src/functions/reports/create-blob-url.ts +16 -0
  40. package/src/functions/reports/index.tsx +1 -0
  41. package/src/types/forms/data-list/filter-config.ts +5 -16
  42. package/src/types/forms/data-list/index.ts +2 -0
  43. package/src/types/forms/index.ts +0 -1
  44. package/src/types/forms/layout-elements/data-render-config.ts +5 -5
  45. package/src/types/forms/layout-elements/field-option-source.ts +25 -10
  46. package/src/types/forms/layout-elements/index.ts +7 -1
  47. package/src/types/forms/layout-elements/style.ts +0 -4
  48. package/src/types/forms/relationship/index.ts +6 -6
  49. package/src/types/index.ts +2 -0
  50. 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.9-dev.2",
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
+ }
@@ -1,3 +1,5 @@
1
1
  export * from './use-find-dynamic-form.hook'
2
2
  export * from './use-notification.hook'
3
3
  export * from './use-lazy-modal-opener.hook'
4
+ export * from './use-breadcrumb.hook'
5
+ export * from './use-dayjs-extender.hook'
@@ -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
- ? !contextValues.formDataId || contextValues.formDataId === NEW_FORM_DATA_IDENTIFIER
75
+ ? isNewFormDataPage(contextValues.formDataId)
75
76
  : contextValues.formDataId !== NEW_FORM_DATA_IDENTIFIER
76
77
  : true
77
78
 
@@ -0,0 +1,8 @@
1
+ import dayjs from 'dayjs'
2
+ import localeData from 'dayjs/plugin/localeData'
3
+ import weekday from 'dayjs/plugin/weekday'
4
+
5
+ dayjs.extend(localeData)
6
+ dayjs.extend(weekday)
7
+
8
+ export const useDayjsExtender = () => {}
@@ -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 { foreignKey, foreignKey2 } = getForeignKeysFromBreadcrumb(prevPageBreadcrumb)
23
+ const { currentFormFK, otherFormFK } = prevPageBreadcrumb.manyToManyRelInfo
25
24
 
26
- if (foreignKey && foreignKey2) formRef.setFieldValue(foreignKey, prevPageBreadcrumb.formDataId)
25
+ if (currentFormFK && otherFormFK) formRef.setFieldValue(otherFormFK, prevPageBreadcrumb.formDataId)
27
26
  }, [prevPageBreadcrumb, formRef])
28
27
 
29
28
  return
@@ -2,7 +2,7 @@ import React, { createContext, ReactNode, useContext } from 'react'
2
2
  import { Modal, notification, NotificationArgsProps } from 'antd'
3
3
 
4
4
  type ModalOptions = {
5
- title?: string
5
+ title?: ReactNode
6
6
  content?: React.ReactNode
7
7
  okText?: string
8
8
  cancelText?: string
@@ -15,15 +15,17 @@ const useGetCurrentBreakpoint = (): DeviceBreakpointEnum => {
15
15
 
16
16
  if (!width) return DeviceBreakpointEnum.Default
17
17
 
18
- if (width < parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.Mobile])) return DeviceBreakpointEnum.Mobile
18
+ if (width <= parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.Mobile])) return DeviceBreakpointEnum.Mobile
19
19
 
20
- if (width < parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.TabletPortrait]))
20
+ if (width <= parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.TabletPortrait]))
21
21
  return DeviceBreakpointEnum.TabletPortrait
22
22
 
23
- if (width < parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.TabletLandscape]))
23
+ if (width <= parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.TabletLandscape]))
24
24
  return DeviceBreakpointEnum.TabletLandscape
25
25
 
26
- if (width < parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.Laptop])) return DeviceBreakpointEnum.Laptop
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
- <div className="p-3">
6
- <div className="text-primary italic h-[300px] text-30 rounded-md bg-white flex items-center justify-center gap-2">
7
- <Spin size="large" /> Loading data...
8
- </div>
9
- </div>
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({ height = 32, width = '100%' }: { height?: number; width?: number | string }) {
2
- return <div style={{ height, width }} className="skeleton-loader" />
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>(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>(undefined)
17
+ const defaultFilterReqDataRef = useRef<IDataListReqData | undefined>()
24
18
 
25
19
  const attachmentBaseUrl = useMemo(() => `${baseServerUrl}/api/attachment/${companyKey}`, [baseServerUrl, companyKey])
26
- const headerLayoutContext = { formId, userId, attachmentBaseUrl, companyKey, onCustomFunctionCall }
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, formJoins, pagination, defaultFilter, defaultSorter } = dataListConfig
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
- if (Array.isArray(formJoins)) formJoinsRef.current = formJoins
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
- ...restReqData,
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(() => setLoadings({ initial: false, data: false }))
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, getIdEqualsQuery } from '../../../functions/forms'
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 { userId, formId, parentFormJoins, formDataId, manyToManyRelInfo, onCustomFunctionCall } = headerLayoutContext
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 || DeviceBreakpointEnum.Default
38
+ const breakpoint = currentBreakpoint
40
39
 
41
- if (!layoutConfig || !layoutConfig.layouts[breakpoint]) return []
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
- if (config.type === FilterConfigTypeEnum.ByLinkedForm) startLoading()
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
- if (eachConfig.type === FilterConfigTypeEnum.ByLinkedForm) startLoading()
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 { dynamicFilters } = filtersToApply.reduce(
82
- (curr: { customFilter: { [key: string]: any }; dynamicFilters: { [key: string]: any }[] }, next) => {
83
- if (!next) return curr
84
- const parsedDFilter = JSON.parse(next.dynamicFilter)
85
- return {
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).isDateFilter) value = dayjs(value as Date).toISOString()
154
-
155
- let customFilters = {}
156
- if (config.type === FilterConfigTypeEnum.ByAuthUser) customFilters = { createdById: userId }
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
- [FilterConfigTypeEnum.ByAuthUser, FilterConfigTypeEnum.Custom].includes(config.type) &&
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 { customFilter: customFilters, dynamicFilter: stringifiedFilter }
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 { generateTableColumns } from '../../../functions/forms'
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(generateTableColumns(columns, headerLayoutContext))
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