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.
Files changed (33) hide show
  1. package/package.json +2 -1
  2. package/src/components/common/custom-hooks/use-find-dynamic-form.hook.ts +30 -11
  3. package/src/components/common/custom-hooks/use-many-to-many-connector.hook.ts +30 -0
  4. package/src/components/common/not-found.tsx +21 -0
  5. package/src/components/companies/1-authenticated/change-password.tsx +1 -1
  6. package/src/components/form/1-list/index.tsx +4 -5
  7. package/src/components/form/1-list/table-header.tsx +5 -5
  8. package/src/components/form/1-list/table.tsx +10 -12
  9. package/src/components/form/2-details/index.tsx +53 -40
  10. package/src/components/form/layout-renderer/1-row/index.tsx +2 -1
  11. package/src/components/form/layout-renderer/3-element/1-dynamic-button/index.tsx +57 -87
  12. package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-button-navigate.hook.tsx +88 -0
  13. package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-create-data.hook.ts +22 -23
  14. package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-generate-report.hook.tsx +3 -4
  15. package/src/components/form/layout-renderer/3-element/1-dynamic-button/use-save-data.hook.ts +2 -2
  16. package/src/components/form/layout-renderer/3-element/11-breadcrumb.tsx +3 -2
  17. package/src/components/form/layout-renderer/3-element/2-field-element.tsx +4 -1
  18. package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +9 -12
  19. package/src/components/form/layout-renderer/3-element/9-form-data-render.tsx +48 -79
  20. package/src/components/modals/report-filters.modal/helper-functions.ts +3 -6
  21. package/src/constants.ts +7 -1
  22. package/src/enums/form.enum.ts +5 -3
  23. package/src/functions/forms/breadcrumb-handlers.ts +21 -0
  24. package/src/functions/forms/data-render-functions.tsx +1 -0
  25. package/src/functions/forms/extended-json-handlers.ts +56 -0
  26. package/src/functions/forms/index.ts +17 -11
  27. package/src/functions/reports/index.tsx +2 -1
  28. package/src/types/forms/index.ts +1 -0
  29. package/src/types/forms/layout-elements/button.ts +11 -3
  30. package/src/types/forms/layout-elements/index.ts +6 -2
  31. package/src/types/forms/layout-elements/sanitization.ts +6 -1
  32. package/src/types/forms/relationship/index.ts +12 -1
  33. package/src/functions/forms/json-handlers.ts +0 -19
@@ -1,9 +1,9 @@
1
1
  import { IElementBaseProps } from '.'
2
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
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 (!baseForm) return
33
+ if (!baseFormId) return
40
34
  setLoadings((c) => ({ ...c, data: true }))
41
35
 
42
- const matchFilters: { [key: string]: any }[] = [...defaultFilter]
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/${baseForm.formId}`, {
46
- joins: defaultJoins,
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
- ...defaultJoins.reduce((curr, j) => ({ ...curr, [`${j.formId}_id`]: `$${j.formId}._id` }), {}),
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
- [defaultJoins, baseForm, defaultFilter],
59
+ [joins, baseFormId],
66
60
  )
67
61
 
68
62
  useEffect(() => {
69
- if (!baseForm || !formDataId || formDataId === NEW_FORM_DATA_IDENTIFIER) return
63
+ if (!displayConfigFormId || !formDataId || formDataId === NEW_FORM_DATA_IDENTIFIER) return
70
64
 
71
- const { request, cancel } = cancelableClient.get(`/api/form/${baseForm.formId}`)
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
- (r: IFormRelationshipConfig) => r.foreignKey === baseForm.foreignKey,
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
- lastChildInfo.current = { formName: res.data.name, parentFormJoins: defaultJoins }
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
- (curr, c) => (c.key ? { ...curr, [c.key.replace(/\./g, '_')]: `$${c.key}` } : curr),
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: baseForm.formId,
105
+ configForFormId: baseFormId,
96
106
  dataListConfig: {
97
107
  ...displayConfig.config,
98
- columns: displayConfig.config.columns.map((c) => (c.key ? { ...c, key: c.key.replace(/\./g, '_') } : c)),
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
- }, [baseForm, formDataId, fetchDataList])
121
+ }, [displayConfigFormId, baseFormId, displayConfigId, formDataId, fetchDataList])
112
122
 
113
123
  const dataListHeaderContext = {
114
- formId: baseForm?.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.replace(/\./g, '_')
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).replace(
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ӨөҮү._-\s]/g,
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
  }
@@ -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
- ReturnToList = 'ReturnToList',
93
- ReturnOneBack = 'ReturnOneBack',
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.replace(/\./g, '_') : ''
50
+ const fieldKey = element.props.field ? getProjectionKey(element.props.field) : ''
50
51
  return {
51
52
  elementType: element.elementType,
52
53
  renderConfig: element.props.renderConfig,
@@ -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 extends IButtonPropsBase {
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 extends IButtonPropsBase {
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 = IButtonProps_Navigate | IButtonProps_CustomFunctionCall | IButtonProps_Other
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
- joins: IRelationshipForm[]
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
- }