form-craft-package 1.7.9-dev.1 → 1.7.9-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 +2 -1
- package/src/components/common/custom-hooks/use-find-dynamic-form.hook.ts +30 -11
- package/src/components/common/custom-hooks/use-many-to-many-connector.hook.ts +30 -0
- package/src/components/common/not-found.tsx +21 -0
- package/src/components/companies/1-authenticated/change-password.tsx +1 -1
- package/src/components/form/1-list/index.tsx +4 -5
- package/src/components/form/1-list/table-header.tsx +5 -5
- package/src/components/form/1-list/table.tsx +10 -12
- package/src/components/form/2-details/index.tsx +53 -40
- package/src/components/form/layout-renderer/1-row/index.tsx +2 -1
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +57 -87
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-button-navigate.hook.tsx +88 -0
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-create-data.hook.ts +22 -23
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-generate-report.hook.tsx +3 -4
- package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-save-data.hook.ts +2 -2
- package/src/components/form/layout-renderer/3-element/11-breadcrumb.tsx +3 -2
- package/src/components/form/layout-renderer/3-element/2-field-element.tsx +4 -1
- package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +9 -12
- package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +48 -79
- package/src/components/modals/report-filters.modal/helper-functions.ts +3 -6
- package/src/constants.ts +7 -1
- package/src/enums/form.enum.ts +5 -3
- package/src/functions/forms/breadcrumb-handlers.ts +21 -0
- package/src/functions/forms/data-render-functions.tsx +1 -0
- package/src/functions/forms/extended-json-handlers.ts +56 -0
- package/src/functions/forms/index.ts +17 -11
- package/src/functions/reports/index.tsx +2 -1
- package/src/types/forms/index.ts +1 -0
- package/src/types/forms/layout-elements/button.ts +11 -3
- package/src/types/forms/layout-elements/index.ts +6 -2
- package/src/types/forms/layout-elements/sanitization.ts +6 -1
- package/src/types/forms/relationship/index.ts +12 -1
- package/src/functions/forms/json-handlers.ts +0 -19
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { IElementBaseProps } from '.'
|
|
2
|
-
import { useCallback, useEffect,
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
3
3
|
import FormDataListTableComponent, { IDataListLayoutConfig } from '../../1-list/table'
|
|
4
4
|
import FormDataListSkeleton_Table from '../../../common/loading-skeletons/table'
|
|
5
|
+
import { getIdEqualsQuery, getProjectionKey } from '../../../../functions'
|
|
5
6
|
import { NEW_FORM_DATA_IDENTIFIER } from '../../../../constants'
|
|
6
|
-
import { FilterConfigTypeEnum } from '../../../../enums'
|
|
7
7
|
import { cancelableClient } from '../../../../api/client'
|
|
8
8
|
import { IFormContext } from '../1-row'
|
|
9
9
|
import {
|
|
@@ -12,12 +12,11 @@ import {
|
|
|
12
12
|
IFormJoin,
|
|
13
13
|
IFormRelationshipConfig,
|
|
14
14
|
IFormSchema,
|
|
15
|
-
IRelationshipForm,
|
|
16
15
|
} from '../../../../types'
|
|
17
16
|
|
|
18
17
|
export default function LayoutRenderer_LoadFormData({ formContext, elementProps }: ILayoutRenderer_LoadFormData) {
|
|
19
18
|
const { formId, formRef, formDataId } = formContext
|
|
20
|
-
const { joins } = elementProps
|
|
19
|
+
const { joins, baseFormId, displayConfigFormId, displayConfigId } = elementProps
|
|
21
20
|
const lastChildInfo = useRef<{ formName: string; parentFormJoins: IFormJoin[] }>({
|
|
22
21
|
formName: '',
|
|
23
22
|
parentFormJoins: [],
|
|
@@ -29,27 +28,22 @@ export default function LayoutRenderer_LoadFormData({ formContext, elementProps
|
|
|
29
28
|
const dataProjectRef = useRef<{ [key: string]: string }>({})
|
|
30
29
|
const reportDataApiCancelFuncRef = useRef<() => void | undefined>(undefined)
|
|
31
30
|
|
|
32
|
-
const [defaultJoins, baseForm, defaultFilter] = useMemo(
|
|
33
|
-
() => getDefaultReqDataParams(joins, formId, formDataId),
|
|
34
|
-
[joins, formId, formDataId],
|
|
35
|
-
)
|
|
36
|
-
|
|
37
31
|
const fetchDataList = useCallback(
|
|
38
32
|
(headerAppliedFilters?: string) => {
|
|
39
|
-
if (!
|
|
33
|
+
if (!baseFormId) return
|
|
40
34
|
setLoadings((c) => ({ ...c, data: true }))
|
|
41
35
|
|
|
42
|
-
const matchFilters: { [key: string]: any }[] = [
|
|
36
|
+
const matchFilters: { [key: string]: any }[] = [getIdEqualsQuery(formId, formDataId)]
|
|
43
37
|
if (headerAppliedFilters) matchFilters.push(JSON.parse(headerAppliedFilters))
|
|
44
38
|
|
|
45
|
-
const { request, cancel } = cancelableClient.post(`/api/report/data/${
|
|
46
|
-
joins
|
|
39
|
+
const { request, cancel } = cancelableClient.post(`/api/report/data/${baseFormId}`, {
|
|
40
|
+
joins,
|
|
47
41
|
match: matchFilters.length
|
|
48
42
|
? JSON.stringify(matchFilters.length === 1 ? matchFilters[0] : { $and: matchFilters })
|
|
49
43
|
: '',
|
|
50
44
|
project: JSON.stringify({
|
|
51
45
|
...dataProjectRef.current,
|
|
52
|
-
...
|
|
46
|
+
...joins.reduce((curr, j) => ({ ...curr, [`${j.formId}_id`]: `$${j.formId}._id` }), {}),
|
|
53
47
|
}),
|
|
54
48
|
})
|
|
55
49
|
reportDataApiCancelFuncRef.current = cancel
|
|
@@ -62,40 +56,56 @@ export default function LayoutRenderer_LoadFormData({ formContext, elementProps
|
|
|
62
56
|
})
|
|
63
57
|
.finally(() => setLoadings({ initial: false, data: false }))
|
|
64
58
|
},
|
|
65
|
-
[
|
|
59
|
+
[joins, baseFormId],
|
|
66
60
|
)
|
|
67
61
|
|
|
68
62
|
useEffect(() => {
|
|
69
|
-
if (!
|
|
63
|
+
if (!displayConfigFormId || !formDataId || formDataId === NEW_FORM_DATA_IDENTIFIER) return
|
|
70
64
|
|
|
71
|
-
|
|
65
|
+
// fetching display config (base form can be different than display config form)
|
|
66
|
+
const { request, cancel } = cancelableClient.get(`/api/form/${displayConfigFormId}`)
|
|
72
67
|
request.then((res) => {
|
|
73
68
|
if (res.status === 200) {
|
|
74
69
|
const parsedData: IFormSchema | null = JSON.parse(res.data.data)
|
|
75
70
|
if (!parsedData) return
|
|
76
71
|
const { relationships = [] } = parsedData
|
|
77
72
|
|
|
78
|
-
const relationship: IFormRelationshipConfig | undefined = relationships.find(
|
|
79
|
-
(
|
|
73
|
+
const relationship: IFormRelationshipConfig | undefined = relationships.find((r: IFormRelationshipConfig) =>
|
|
74
|
+
r.displayConfigs?.some((dc) => dc.id === displayConfigId),
|
|
80
75
|
)
|
|
81
|
-
|
|
82
|
-
const displayConfig = relationship?.displayConfigs?.find((config) => config.id === baseForm.displayConfigId)
|
|
76
|
+
const displayConfig = relationship?.displayConfigs?.find((config) => config.id === displayConfigId)
|
|
83
77
|
if (!displayConfig || displayConfig.isRenderChildForm) return
|
|
84
78
|
|
|
85
|
-
|
|
79
|
+
const isManyToManyRelationship = displayConfigFormId !== baseFormId
|
|
80
|
+
lastChildInfo.current = { formName: res.data.name, parentFormJoins: joins }
|
|
86
81
|
|
|
87
82
|
if (displayConfig.config) {
|
|
88
83
|
if (Array.isArray(displayConfig.config.columns))
|
|
89
|
-
dataProjectRef.current = displayConfig.config.columns.reduce(
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
84
|
+
dataProjectRef.current = displayConfig.config.columns.reduce((curr, c) => {
|
|
85
|
+
if (!c.key) return curr
|
|
86
|
+
|
|
87
|
+
if (isManyToManyRelationship) {
|
|
88
|
+
// if the key starts with id, it means it's reading a field from other form joins that are already configured
|
|
89
|
+
// example: form id 145 one-to-many 145 many-to-many 146, and 149 is the rel form.
|
|
90
|
+
// the base form is 149, joined 145 (displayConfigFormId below) and 146. In this case, to read fields from 145, it appends 145 at front to make it 145.Data{...}
|
|
91
|
+
// However, to display data from 144, we can't append 145, whose keys already come with 144.Data.{...}. That's why it's checking if it starts with number or not
|
|
92
|
+
return {
|
|
93
|
+
...curr,
|
|
94
|
+
[getProjectionKey(c.key)]: c.key.match(/^\d/) ? `$${c.key}` : `$${displayConfigFormId}.${c.key}`,
|
|
95
|
+
// [getProjectionKey(c.key)]: `$${c.key}`,
|
|
96
|
+
relDataId: '$_id',
|
|
97
|
+
_id: `$${displayConfigFormId}._id`,
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return { ...curr, [getProjectionKey(c.key)]: `$${c.key}` }
|
|
102
|
+
}, {})
|
|
93
103
|
|
|
94
104
|
setListLayoutConfig({
|
|
95
|
-
configForFormId:
|
|
105
|
+
configForFormId: baseFormId,
|
|
96
106
|
dataListConfig: {
|
|
97
107
|
...displayConfig.config,
|
|
98
|
-
columns: displayConfig.config.columns.map((c) => (c.key ? { ...c, key: c.key
|
|
108
|
+
columns: displayConfig.config.columns.map((c) => (c.key ? { ...c, key: getProjectionKey(c.key) } : c)),
|
|
99
109
|
},
|
|
100
110
|
})
|
|
101
111
|
|
|
@@ -108,13 +118,21 @@ export default function LayoutRenderer_LoadFormData({ formContext, elementProps
|
|
|
108
118
|
cancel()
|
|
109
119
|
reportDataApiCancelFuncRef.current?.()
|
|
110
120
|
}
|
|
111
|
-
}, [
|
|
121
|
+
}, [displayConfigFormId, baseFormId, displayConfigId, formDataId, fetchDataList])
|
|
112
122
|
|
|
113
123
|
const dataListHeaderContext = {
|
|
114
|
-
formId:
|
|
124
|
+
formId: baseFormId,
|
|
115
125
|
formDataId,
|
|
116
126
|
formRef,
|
|
117
127
|
...lastChildInfo.current,
|
|
128
|
+
manyToManyRelInfo:
|
|
129
|
+
displayConfigFormId !== baseFormId && formId
|
|
130
|
+
? {
|
|
131
|
+
middleFormId: baseFormId,
|
|
132
|
+
currentFormId: formId,
|
|
133
|
+
otherFormId: displayConfigFormId,
|
|
134
|
+
}
|
|
135
|
+
: null,
|
|
118
136
|
onCustomFunctionCall: () => {},
|
|
119
137
|
}
|
|
120
138
|
|
|
@@ -139,52 +157,3 @@ type ILayoutRenderer_LoadFormData = {
|
|
|
139
157
|
formContext: IFormContext
|
|
140
158
|
elementProps: IFormDataLoadElementProps
|
|
141
159
|
} & IElementBaseProps
|
|
142
|
-
|
|
143
|
-
const getDefaultReqDataParams = (
|
|
144
|
-
joins: IRelationshipForm[],
|
|
145
|
-
formId?: number,
|
|
146
|
-
formDataId?: string,
|
|
147
|
-
): [IFormJoin[], IRelationshipForm | undefined, { [key: string]: any }[]] => {
|
|
148
|
-
if (!joins || !Array.isArray(joins) || joins.length === 0 || !formId) return [[], undefined, []]
|
|
149
|
-
|
|
150
|
-
const baseForm = joins[joins.length - 1]
|
|
151
|
-
const reversedJoins = [...joins].reverse()
|
|
152
|
-
|
|
153
|
-
const childrenJoins: IFormJoin[] = []
|
|
154
|
-
const filters: { [key: string]: any }[] = [{ $expr: { $eq: [`$${formId}._id`, { $toObjectId: formDataId }] } }]
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
baseForm.filterConfig &&
|
|
158
|
-
baseForm.filterConfig.type === FilterConfigTypeEnum.Custom &&
|
|
159
|
-
baseForm.filterConfig.config
|
|
160
|
-
)
|
|
161
|
-
filters.push(
|
|
162
|
-
JSON.parse(
|
|
163
|
-
JSON.stringify(baseForm.filterConfig.config).replaceAll(`${baseForm.formId}.`, '').replaceAll('@', '$'),
|
|
164
|
-
),
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
let prevJoinAlias = ''
|
|
168
|
-
reversedJoins.forEach((j, jIdx) => {
|
|
169
|
-
const nextJoin = reversedJoins[jIdx + 1]
|
|
170
|
-
const localField = `${prevJoinAlias}Data.${j.foreignKey}`
|
|
171
|
-
prevJoinAlias = `${nextJoin ? nextJoin.formId : formId}.`
|
|
172
|
-
|
|
173
|
-
const joinObj = {
|
|
174
|
-
formId: nextJoin ? nextJoin.formId : formId,
|
|
175
|
-
localField,
|
|
176
|
-
foreignField: '_id',
|
|
177
|
-
alias: nextJoin ? nextJoin.formId.toString() : formId.toString(),
|
|
178
|
-
}
|
|
179
|
-
childrenJoins.push(joinObj)
|
|
180
|
-
|
|
181
|
-
if (j.filterConfig && j.filterConfig.type === FilterConfigTypeEnum.Custom && j.filterConfig.config) {
|
|
182
|
-
let stringifiedFilter = JSON.stringify(j.filterConfig.config)
|
|
183
|
-
|
|
184
|
-
const filterConfig = JSON.parse(stringifiedFilter.replaceAll('@', '$'))
|
|
185
|
-
filters.push(filterConfig)
|
|
186
|
-
}
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
return [childrenJoins, baseForm, filters]
|
|
190
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { REPORT_EMPTY_CELL_CONTENT } from '../../../constants'
|
|
2
|
-
import { renderData } from '../../../functions'
|
|
2
|
+
import { getProjectionKey, renderData } from '../../../functions'
|
|
3
3
|
import {
|
|
4
4
|
AlignTypeEnum,
|
|
5
5
|
ElementTypeEnum,
|
|
@@ -67,7 +67,7 @@ function groupData(
|
|
|
67
67
|
const currentGroupConfig = groups[0]
|
|
68
68
|
const groupingType: ReportGroupingTypeEnum = currentGroupConfig.type || ReportGroupingTypeEnum.RowSpan
|
|
69
69
|
// Convert dependencyField (e.g. "81.Data.name") to underscore format (e.g. "81_Data_name").
|
|
70
|
-
const effectiveField = currentGroupConfig.dependencyField
|
|
70
|
+
const effectiveField = getProjectionKey(currentGroupConfig.dependencyField)
|
|
71
71
|
|
|
72
72
|
// Group data by the effective field.
|
|
73
73
|
const groupsMap = new Map<string, any[]>()
|
|
@@ -338,10 +338,7 @@ function evaluateSummary(dataSubset: any[], evaluationConfig: any): number {
|
|
|
338
338
|
const evaluation = evaluationConfig.evaluations[0]
|
|
339
339
|
let sum = evaluation.defaultValue || 0
|
|
340
340
|
if (evaluation.operator === EvaluationOperatorsEnum.Add) {
|
|
341
|
-
const fieldKey = (typeof evaluation.field === 'string' ? evaluation.field : evaluation.field.field)
|
|
342
|
-
/\./g,
|
|
343
|
-
'_',
|
|
344
|
-
)
|
|
341
|
+
const fieldKey = getProjectionKey(typeof evaluation.field === 'string' ? evaluation.field : evaluation.field.field)
|
|
345
342
|
dataSubset.forEach((item) => {
|
|
346
343
|
const num = parseFloat(item[fieldKey])
|
|
347
344
|
if (!isNaN(num)) {
|
package/src/constants.ts
CHANGED
|
@@ -153,7 +153,7 @@ export const DEFAULT_CONFIG = {
|
|
|
153
153
|
export const REGEX_PATTERNS = {
|
|
154
154
|
CamelCase: /^[a-z]+(?:[A-Z0-9][a-z0-9]*)*$/,
|
|
155
155
|
HtmlTag: /<[^>]*>/,
|
|
156
|
-
FormName: /[^A-Za-zА-Яа-яЁё0-9
|
|
156
|
+
FormName: /[^A-Za-zА-Яа-яЁё0-9ӨөҮү.-\s]/g,
|
|
157
157
|
BaseUrl: /[^A-Za-z0-9\-\._:#\/]+/g,
|
|
158
158
|
UrlConstantPath: /[^A-Za-z0-9\-\._~#]+/g,
|
|
159
159
|
DollarCurly: /\$\{(.*?)\}/g,
|
|
@@ -161,4 +161,10 @@ export const REGEX_PATTERNS = {
|
|
|
161
161
|
SquareBracket: /\[(.*?)\]/g,
|
|
162
162
|
JustLetterNumber: /[^a-zA-Z0-9]/g,
|
|
163
163
|
NonEmptyHtmlString: /^(?!(?:\s*|(?:<[^>]+>\s*)+)$)[\s\S]+$/,
|
|
164
|
+
RelationshipFormName: /^[\p{Script=Latin}\p{Script=Cyrillic}]+_[\p{Script=Latin}\p{Script=Cyrillic}]+$/u,
|
|
165
|
+
}
|
|
166
|
+
export const BSON_DATA_IDENTIFIER_PREFIXES = {
|
|
167
|
+
ObjectId: 'ObjectId__',
|
|
168
|
+
Date: 'Date__',
|
|
169
|
+
Number: 'Number__',
|
|
164
170
|
}
|
package/src/enums/form.enum.ts
CHANGED
|
@@ -59,7 +59,7 @@ export enum ButtonElementTypeEnum {
|
|
|
59
59
|
Danger = 'Danger',
|
|
60
60
|
DangerOutlined = 'DangerOutline',
|
|
61
61
|
Link = 'Link',
|
|
62
|
-
Custom = 'Custom'
|
|
62
|
+
Custom = 'Custom',
|
|
63
63
|
}
|
|
64
64
|
export enum ButtonElementSizeEnum {
|
|
65
65
|
Default = 'default',
|
|
@@ -89,8 +89,8 @@ export enum ButtonActionCategoryEnum {
|
|
|
89
89
|
export enum NavigateButtonTypesEnum {
|
|
90
90
|
NewDataPage = 'NewDataPage',
|
|
91
91
|
ViewDataDetails = 'ViewDataDetails',
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
GoToDataList = 'GoToDataList',
|
|
93
|
+
GoBack = 'GoBack',
|
|
94
94
|
Custom = 'Custom',
|
|
95
95
|
}
|
|
96
96
|
export enum NavigateButtonCustomComponentType {
|
|
@@ -164,6 +164,7 @@ export enum EvaluationValueTypeEnum {
|
|
|
164
164
|
Report = 'Report',
|
|
165
165
|
}
|
|
166
166
|
export enum FormRelationshipEnum {
|
|
167
|
+
ManyToMany = 'many-to-many',
|
|
167
168
|
ManyToOne = 'many-to-one',
|
|
168
169
|
OneToMany = 'one-to-many',
|
|
169
170
|
OneToOne = 'one-to-one',
|
|
@@ -175,6 +176,7 @@ export enum NewFormViewTypeEnum {
|
|
|
175
176
|
}
|
|
176
177
|
export enum DataSanitizationTypeEnum {
|
|
177
178
|
Default = 'Default',
|
|
179
|
+
Predefined = 'Predefined',
|
|
178
180
|
Custom = 'Custom',
|
|
179
181
|
}
|
|
180
182
|
export enum PatternPlacholdersEnum {
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IFormJoin } from '../../types'
|
|
2
|
+
|
|
3
|
+
export const getForeignKeysFromBreadcrumb = (breadcrumb: {
|
|
4
|
+
[key: string]: any
|
|
5
|
+
}): { foreignKey?: string; foreignKey2?: string } => {
|
|
6
|
+
if (!breadcrumb || !breadcrumb.manyToManyRelInfo) return {}
|
|
7
|
+
|
|
8
|
+
const form1Join = (breadcrumb.formJoins as IFormJoin[]).find(
|
|
9
|
+
(j) => j.formId === breadcrumb.manyToManyRelInfo.currentFormId,
|
|
10
|
+
)
|
|
11
|
+
const localFieldSplit = form1Join?.localField.split('.') ?? []
|
|
12
|
+
const foreignKey = localFieldSplit[localFieldSplit.length - 1]
|
|
13
|
+
|
|
14
|
+
const form2Join = (breadcrumb.formJoins as IFormJoin[]).find(
|
|
15
|
+
(j) => j.formId === breadcrumb.manyToManyRelInfo.otherFormId,
|
|
16
|
+
)
|
|
17
|
+
const localField2Split = form2Join?.localField.split('.') ?? []
|
|
18
|
+
const foreignKey2 = localField2Split[localField2Split.length - 1]
|
|
19
|
+
|
|
20
|
+
return { foreignKey, foreignKey2 }
|
|
21
|
+
}
|
|
@@ -33,6 +33,7 @@ export const generateTableColumns = (
|
|
|
33
33
|
title: el.label,
|
|
34
34
|
dataIndex: el.key ? (el.key.includes('.') ? el.key.split('.') : el.key) : '',
|
|
35
35
|
sorter: el.sortable,
|
|
36
|
+
render: (data) => (data ? data : '-'),
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
if (el.optionFieldPath) col.render = () => <Spin spinning size="small" />
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import dayjs from 'dayjs'
|
|
2
|
+
import { BSON_DATA_IDENTIFIER_PREFIXES } from '../../constants'
|
|
3
|
+
import { isValidMongoDbId } from '..'
|
|
4
|
+
|
|
5
|
+
export function toMongoDbExtendedJSON(input: Record<string, any>): Record<string, any> {
|
|
6
|
+
const out: Record<string, any> = {}
|
|
7
|
+
|
|
8
|
+
for (const [key, val] of Object.entries(input)) {
|
|
9
|
+
if (key.startsWith(BSON_DATA_IDENTIFIER_PREFIXES.ObjectId)) {
|
|
10
|
+
if (typeof val === 'string' && isValidMongoDbId(val)) out[key] = { $oid: val }
|
|
11
|
+
else {
|
|
12
|
+
console.warn(`Invalid ObjectId for "${key}":`, val)
|
|
13
|
+
out[key] = val
|
|
14
|
+
}
|
|
15
|
+
} else if (key.startsWith(BSON_DATA_IDENTIFIER_PREFIXES.Date)) {
|
|
16
|
+
const s = String(val)
|
|
17
|
+
if (!isNaN(Date.parse(s))) out[key] = { $date: s }
|
|
18
|
+
else {
|
|
19
|
+
console.warn(`Invalid Date for "${key}":`, val)
|
|
20
|
+
out[key] = val
|
|
21
|
+
}
|
|
22
|
+
} else if (key.startsWith(BSON_DATA_IDENTIFIER_PREFIXES.Number)) {
|
|
23
|
+
const num = typeof val === 'number' ? val : Number(val)
|
|
24
|
+
if (!isNaN(num)) out[key] = { $numberDecimal: num }
|
|
25
|
+
else {
|
|
26
|
+
console.warn(`Invalid Number for "${key}":`, val)
|
|
27
|
+
out[key] = val
|
|
28
|
+
}
|
|
29
|
+
} else out[key] = val
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return out
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function fromMongoDbExtendedJSON(input: Record<string, any>): Record<string, any> {
|
|
36
|
+
const out: Record<string, any> = {}
|
|
37
|
+
|
|
38
|
+
for (const [key, val] of Object.entries(input)) {
|
|
39
|
+
if (key.startsWith(BSON_DATA_IDENTIFIER_PREFIXES.ObjectId) && val && typeof val === 'object' && '$oid' in val) {
|
|
40
|
+
out[key] = (val as any).$oid
|
|
41
|
+
} else if (key.startsWith(BSON_DATA_IDENTIFIER_PREFIXES.Date) && val && typeof val === 'object' && '$date' in val) {
|
|
42
|
+
const date = (val as any).$date
|
|
43
|
+
out[key] = date ? dayjs(date) : date
|
|
44
|
+
} else if (
|
|
45
|
+
key.startsWith(BSON_DATA_IDENTIFIER_PREFIXES.Number) &&
|
|
46
|
+
val &&
|
|
47
|
+
typeof val === 'object' &&
|
|
48
|
+
'$numberDecimal' in val
|
|
49
|
+
) {
|
|
50
|
+
const n = (val as any).$numberDecimal
|
|
51
|
+
out[key] = typeof n === 'number' ? n : Number(n)
|
|
52
|
+
} else out[key] = val
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return out
|
|
56
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { DEFAULT_LAYOUT_CONFIG } from '../../constants'
|
|
1
|
+
import { DEFAULT_LAYOUT_CONFIG, NEW_FORM_DATA_IDENTIFIER } from '../../constants'
|
|
2
2
|
import { ElementTypeEnum, FieldElementOptionSourceEnum } from '../../enums'
|
|
3
3
|
export * from './create-form-rules'
|
|
4
4
|
export * from './data-render-functions'
|
|
5
|
-
export * from './json-handlers'
|
|
5
|
+
export * from './extended-json-handlers'
|
|
6
6
|
export * from './form-schema-validator'
|
|
7
7
|
export * from './get-element-props'
|
|
8
8
|
import { CancelToken } from 'axios'
|
|
@@ -37,15 +37,6 @@ export const extractFiltersFromLayout = (elements: { [key: string]: IDndLayoutEl
|
|
|
37
37
|
return filters
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
export const extractDateFields = (elements: { [key: string]: IDndLayoutElement }): string[] => {
|
|
41
|
-
const dateFieldKeys: string[] = []
|
|
42
|
-
Object.values(elements).forEach((el) => {
|
|
43
|
-
if ([ElementTypeEnum.DatePicker, ElementTypeEnum.TimePicker].includes(el.elementType)) dateFieldKeys.push(el.key)
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
return dateFieldKeys
|
|
47
|
-
}
|
|
48
|
-
|
|
49
40
|
/** --------------------------------------------------------------------------------------------------------- */
|
|
50
41
|
|
|
51
42
|
export const getFlexContainerStyle = (display?: IGridContainerConfig) => {
|
|
@@ -181,4 +172,19 @@ export async function fetchFormDataAsLookup(
|
|
|
181
172
|
|
|
182
173
|
/** --------------------------------------------------------------------------------------------------------- */
|
|
183
174
|
|
|
175
|
+
export const getProjectionKey = (key: string): string => key.replace(/\./g, '_')
|
|
176
|
+
|
|
177
|
+
/** --------------------------------------------------------------------------------------------------------- */
|
|
178
|
+
|
|
179
|
+
export const getIdEqualsQuery = (alias: number | string = '', id: string = '') =>
|
|
180
|
+
id
|
|
181
|
+
? {
|
|
182
|
+
$expr: { $eq: [`$${alias ? `${alias}.` : ''}_id`, { $toObjectId: id }] },
|
|
183
|
+
}
|
|
184
|
+
: {}
|
|
185
|
+
|
|
186
|
+
/** --------------------------------------------------------------------------------------------------------- */
|
|
187
|
+
|
|
188
|
+
export const isValidMongoDbId = (id?: string) => id && (id === NEW_FORM_DATA_IDENTIFIER || /^[0-9a-fA-F]{24}$/.test(id))
|
|
189
|
+
|
|
184
190
|
/** --------------------------------------------------------------------------------------------------------- */
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getProjectionKey } from '..'
|
|
1
2
|
import { ElementTypeEnum, ReportLayoutLineTypeEnum, ReportLayoutSectionTypeEnum } from '../../enums'
|
|
2
3
|
import { IDndLayoutCol, IDndLayoutRow, IDndLayoutStructure, IReportTableLayoutStyle } from '../../types'
|
|
3
4
|
|
|
@@ -46,7 +47,7 @@ function convertElement(element: any): any {
|
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
case ElementTypeEnum.ReadFieldData: {
|
|
49
|
-
const fieldKey = element.props.field ? element.props.field
|
|
50
|
+
const fieldKey = element.props.field ? getProjectionKey(element.props.field) : ''
|
|
50
51
|
return {
|
|
51
52
|
elementType: element.elementType,
|
|
52
53
|
renderConfig: element.props.renderConfig,
|
package/src/types/forms/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { IButtonElementProps } from './layout-elements'
|
|
|
11
11
|
import { IDndLayoutStructure_Responsive } from '..'
|
|
12
12
|
|
|
13
13
|
export interface IFormSchema {
|
|
14
|
+
isRelationshipForm?: boolean
|
|
14
15
|
detailsConfig: IDndLayoutStructure_Responsive
|
|
15
16
|
dataListConfig: IFormDataListConfig
|
|
16
17
|
generateConfig: IFormGenerateConfig
|
|
@@ -7,12 +7,14 @@ import {
|
|
|
7
7
|
} from '../../../enums'
|
|
8
8
|
|
|
9
9
|
interface IButtonPropsBase {
|
|
10
|
+
formId?: number
|
|
10
11
|
hasNoLabel?: boolean
|
|
11
12
|
label?: string
|
|
12
13
|
buttonType?: ButtonElementTypeEnum
|
|
13
14
|
className?: string
|
|
14
15
|
size?: ButtonElementSizeEnum
|
|
15
16
|
order?: number
|
|
17
|
+
secondaryAction?: IButtonProps_Navigate | IButtonProps_CustomFunctionCall
|
|
16
18
|
confirmation?: {
|
|
17
19
|
enabled: boolean
|
|
18
20
|
message?: string
|
|
@@ -24,16 +26,19 @@ interface IButtonPropsBase {
|
|
|
24
26
|
error?: string
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
|
-
export interface IButtonProps_Navigate
|
|
29
|
+
export interface IButtonProps_Navigate {
|
|
28
30
|
category: ButtonActionCategoryEnum.Navigate
|
|
29
31
|
navigateType: NavigateButtonTypesEnum
|
|
32
|
+
navigateBackCount?: number
|
|
30
33
|
urlComponents?: IButtonProps_NavigateCustomComponents[]
|
|
34
|
+
customUrl?: string
|
|
35
|
+
formId?: number
|
|
31
36
|
}
|
|
32
37
|
export interface IButtonProps_NavigateCustomComponents {
|
|
33
38
|
type: NavigateButtonCustomComponentType
|
|
34
39
|
value: string
|
|
35
40
|
}
|
|
36
|
-
export interface IButtonProps_CustomFunctionCall
|
|
41
|
+
export interface IButtonProps_CustomFunctionCall {
|
|
37
42
|
category: ButtonActionCategoryEnum.CustomFunction
|
|
38
43
|
functionName: string
|
|
39
44
|
}
|
|
@@ -46,4 +51,7 @@ export interface IButtonProps_Other extends IButtonPropsBase {
|
|
|
46
51
|
| ButtonActionCategoryEnum.SaveDataChanges
|
|
47
52
|
| ButtonActionCategoryEnum.ViewDataVersions
|
|
48
53
|
}
|
|
49
|
-
export type IButtonElementProps =
|
|
54
|
+
export type IButtonElementProps =
|
|
55
|
+
| (IButtonProps_Navigate & IButtonPropsBase)
|
|
56
|
+
| (IButtonProps_CustomFunctionCall & IButtonPropsBase)
|
|
57
|
+
| IButtonProps_Other
|
|
@@ -19,8 +19,8 @@ import {
|
|
|
19
19
|
ICssStylePerBreakpoint,
|
|
20
20
|
IEvaluationConfig,
|
|
21
21
|
IFilterConfig,
|
|
22
|
+
IFormJoin,
|
|
22
23
|
IReadFieldDataElementProps,
|
|
23
|
-
IRelationshipForm,
|
|
24
24
|
IReportTableElementProps,
|
|
25
25
|
} from '..'
|
|
26
26
|
import {
|
|
@@ -288,7 +288,11 @@ export interface IFormDataLoadElement extends BaseFormLayoutElement {
|
|
|
288
288
|
}
|
|
289
289
|
export interface IFormDataLoadElementProps {
|
|
290
290
|
hasNoLabel?: boolean
|
|
291
|
-
|
|
291
|
+
baseFormId: number
|
|
292
|
+
displayConfigFormId: number
|
|
293
|
+
displayConfigId: string
|
|
294
|
+
joins: IFormJoin[]
|
|
295
|
+
filter?: IFilterConfig
|
|
292
296
|
name: string
|
|
293
297
|
pageBreak?: ReportPageBreakEnum // used for report
|
|
294
298
|
}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
import { DataSanitizationTypeEnum } from '../../../enums'
|
|
2
2
|
|
|
3
|
-
export type IDataSanitization = IDataSanitization_Default | IDataSanitization_Custom
|
|
3
|
+
export type IDataSanitization = IDataSanitization_Default | IDataSanitization_Predefined | IDataSanitization_Custom
|
|
4
4
|
|
|
5
5
|
interface IDataSanitization_Default {
|
|
6
6
|
type: DataSanitizationTypeEnum.Default
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
export interface IDataSanitization_Predefined {
|
|
10
|
+
type: DataSanitizationTypeEnum.Predefined
|
|
11
|
+
pattern?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
9
14
|
export interface IDataSanitization_Custom {
|
|
10
15
|
type: DataSanitizationTypeEnum.Custom
|
|
11
16
|
pattern: string
|
|
@@ -1,13 +1,24 @@
|
|
|
1
1
|
import { IDataListSorter, IFilterConfig, IFormDataListConfig } from '..'
|
|
2
2
|
import { FormRelationshipEnum, NewFormViewTypeEnum } from '../../../enums'
|
|
3
3
|
|
|
4
|
-
export type IFormRelationshipConfig =
|
|
4
|
+
export type IFormRelationshipConfig = IFormRelationshipConfig_ManyToMany | IFormRelationshipConfig_Others
|
|
5
|
+
|
|
6
|
+
interface IFormRelationshipConfig_Base {
|
|
5
7
|
formId: number
|
|
6
8
|
type: FormRelationshipEnum
|
|
7
9
|
foreignKey: string
|
|
8
10
|
viewType?: NewFormViewTypeEnum
|
|
9
11
|
displayConfigs?: IRelationshipDisplayConfig[]
|
|
10
12
|
}
|
|
13
|
+
export interface IFormRelationshipConfig_ManyToMany extends IFormRelationshipConfig_Base {
|
|
14
|
+
type: FormRelationshipEnum.ManyToMany
|
|
15
|
+
foreignKey2: string
|
|
16
|
+
relationshipFormId: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface IFormRelationshipConfig_Others extends IFormRelationshipConfig_Base {
|
|
20
|
+
type: FormRelationshipEnum.ManyToOne | FormRelationshipEnum.OneToMany | FormRelationshipEnum.OneToOne
|
|
21
|
+
}
|
|
11
22
|
|
|
12
23
|
export type IRelationshipDisplayConfig = {
|
|
13
24
|
id: string
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
// TODO: add json validator
|
|
2
|
-
// TODO: pass the T and validate with it?
|
|
3
|
-
|
|
4
|
-
export const parseJSON = <T extends any>(jsonString: string): T | null => {
|
|
5
|
-
try {
|
|
6
|
-
return JSON.parse(jsonString) as T
|
|
7
|
-
} catch (error) {
|
|
8
|
-
console.error('Invalid JSON string:', error)
|
|
9
|
-
return null
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
export const stringifyJSON = (jsonObject: any): string => {
|
|
13
|
-
try {
|
|
14
|
-
return JSON.stringify(jsonObject)
|
|
15
|
-
} catch (error) {
|
|
16
|
-
console.error('Unable to stringify JSON:', error)
|
|
17
|
-
return ''
|
|
18
|
-
}
|
|
19
|
-
}
|