form-craft-package 1.11.5 → 1.11.6-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.
- package/package.json +1 -1
- package/src/components/form/1-list/index.tsx +58 -9
- package/src/components/form/1-list/table-header.tsx +73 -15
- package/src/components/form/1-list/table.tsx +12 -3
- package/src/components/form/layout-renderer/1-row/index.tsx +2 -1
- package/src/components/form/layout-renderer/3-element/18-gallery/delete.modal.tsx +1 -1
- package/src/components/form/layout-renderer/3-element/18-gallery/index.tsx +10 -4
- package/src/components/form/layout-renderer/3-element/8-fields-with-options.tsx +17 -1
- package/src/enums/form.enum.ts +2 -0
- package/src/enums/index.ts +1 -0
- package/src/functions/forms/data-list-cache.ts +3 -1
- package/src/functions/forms/index.ts +16 -5
- package/src/types/forms/data-list/filter-config.ts +5 -1
- package/src/types/forms/layout-elements/field-option-source.ts +5 -0
package/package.json
CHANGED
|
@@ -53,9 +53,14 @@ function FormDataListComponentChild({
|
|
|
53
53
|
() => `${baseServerUrl}/api/attachment/${companyKey}`,
|
|
54
54
|
[baseServerUrl, companyKey],
|
|
55
55
|
)
|
|
56
|
-
const headerLayoutContext = { formId, attachmentBaseUrl, companyKey, onCustomFunctionCall }
|
|
57
|
-
|
|
58
56
|
const { cachedConfig, isConfigLoading } = useCacheFormLayoutConfig(formId)
|
|
57
|
+
const headerLayoutContext = {
|
|
58
|
+
formId,
|
|
59
|
+
attachmentBaseUrl,
|
|
60
|
+
companyKey,
|
|
61
|
+
onCustomFunctionCall,
|
|
62
|
+
formRelationships: cachedConfig?.relationships,
|
|
63
|
+
}
|
|
59
64
|
|
|
60
65
|
useEffect(() => {
|
|
61
66
|
reportDataApiCancelFuncRef.current?.()
|
|
@@ -116,6 +121,8 @@ function FormDataListComponentChild({
|
|
|
116
121
|
const cacheKey = buildDataListCacheKey('form-data-list', [
|
|
117
122
|
formId,
|
|
118
123
|
nextReqData.match ?? '',
|
|
124
|
+
nextReqData.pipeline ?? '',
|
|
125
|
+
nextReqData.joins ?? [],
|
|
119
126
|
nextReqData.sort ?? '',
|
|
120
127
|
nextReqData.skip ?? 0,
|
|
121
128
|
nextReqData.limit ?? '',
|
|
@@ -133,13 +140,49 @@ function FormDataListComponentChild({
|
|
|
133
140
|
setCheckingForUpdates(true)
|
|
134
141
|
apiCallCounterRef.current += 1
|
|
135
142
|
|
|
136
|
-
const { current, ...restReqData } = nextReqData
|
|
137
|
-
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
+
const { current, joins, pipeline, ...restReqData } = nextReqData
|
|
144
|
+
|
|
145
|
+
const resolvedJoins = mergeJoins([formJoinsRef.current, joins || []])
|
|
146
|
+
const { request, cancel } = pipeline
|
|
147
|
+
? cancelableClient.post(`/api/report/dataDynamic/${formId}`, {
|
|
148
|
+
joins: resolvedJoins,
|
|
149
|
+
pipeline: JSON.stringify([
|
|
150
|
+
...(JSON.parse(pipeline) as { [key: string]: any }[]),
|
|
151
|
+
...(restReqData.sort
|
|
152
|
+
? [
|
|
153
|
+
{
|
|
154
|
+
$sort: Object.entries(JSON.parse(restReqData.sort)).reduce(
|
|
155
|
+
(curr, [field, value]) => ({ ...curr, [`doc.${field}`]: value }),
|
|
156
|
+
{},
|
|
157
|
+
),
|
|
158
|
+
},
|
|
159
|
+
]
|
|
160
|
+
: []),
|
|
161
|
+
...(Object.values(dataProjectRef.current).length
|
|
162
|
+
? [
|
|
163
|
+
{
|
|
164
|
+
$project: Object.entries(dataProjectRef.current).reduce(
|
|
165
|
+
(curr, [key, value]) => ({
|
|
166
|
+
...curr,
|
|
167
|
+
[key]:
|
|
168
|
+
typeof value === 'string' && value.startsWith('$')
|
|
169
|
+
? `$doc.${value.slice(1)}`
|
|
170
|
+
: value,
|
|
171
|
+
}),
|
|
172
|
+
{},
|
|
173
|
+
),
|
|
174
|
+
},
|
|
175
|
+
]
|
|
176
|
+
: []),
|
|
177
|
+
]),
|
|
178
|
+
skip: restReqData.skip,
|
|
179
|
+
limit: restReqData.limit,
|
|
180
|
+
})
|
|
181
|
+
: cancelableClient.post(`/api/report/data/${formId}`, {
|
|
182
|
+
joins: resolvedJoins,
|
|
183
|
+
project: JSON.stringify(dataProjectRef.current),
|
|
184
|
+
...restReqData,
|
|
185
|
+
})
|
|
143
186
|
reportDataApiCancelFuncRef.current = cancel
|
|
144
187
|
|
|
145
188
|
request
|
|
@@ -150,6 +193,8 @@ function FormDataListComponentChild({
|
|
|
150
193
|
setCachedDataList(cacheKey, payload, {
|
|
151
194
|
formId,
|
|
152
195
|
match: nextReqData.match,
|
|
196
|
+
pipeline: nextReqData.pipeline,
|
|
197
|
+
joins: nextReqData.joins,
|
|
153
198
|
sort: nextReqData.sort,
|
|
154
199
|
skip: nextReqData.skip,
|
|
155
200
|
limit: nextReqData.limit,
|
|
@@ -250,6 +295,8 @@ function FormDataListComponentChild({
|
|
|
250
295
|
const initialCacheKey = buildDataListCacheKey('form-data-list', [
|
|
251
296
|
formId,
|
|
252
297
|
initialReqData.match ?? '',
|
|
298
|
+
initialReqData.pipeline ?? '',
|
|
299
|
+
initialReqData.joins ?? [],
|
|
253
300
|
initialReqData.sort ?? '',
|
|
254
301
|
initialSkip ?? 0,
|
|
255
302
|
initialReqData.limit ?? '',
|
|
@@ -304,4 +351,6 @@ export interface IDataListReqData {
|
|
|
304
351
|
limit?: number
|
|
305
352
|
sort?: string
|
|
306
353
|
match?: string
|
|
354
|
+
pipeline?: string
|
|
355
|
+
joins?: IFormJoin[]
|
|
307
356
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { Form } from 'antd'
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
3
3
|
import dayjs from 'dayjs'
|
|
4
|
-
import { extractFiltersFromLayout } from '../../../functions/forms'
|
|
5
|
-
import { DeviceBreakpointEnum, FilterConfigTypeEnum } from '../../../enums'
|
|
4
|
+
import { extractFiltersFromLayout, mergeJoins } from '../../../functions/forms'
|
|
5
|
+
import { DeviceBreakpointEnum, FilterConfigTypeEnum, FormRelationshipEnum } from '../../../enums'
|
|
6
6
|
import { LayoutRendererRow } from '../layout-renderer/1-row'
|
|
7
7
|
import useGetCurrentBreakpoint from '../../common/custom-hooks/use-window-width.hook'
|
|
8
8
|
import { IDataListHeaderLayoutContext } from './table'
|
|
@@ -23,6 +23,8 @@ import {
|
|
|
23
23
|
IFilterCustom,
|
|
24
24
|
IFilterNested,
|
|
25
25
|
IFilterSimple,
|
|
26
|
+
IFormJoin,
|
|
27
|
+
IFormRelationshipConfig,
|
|
26
28
|
} from '../../../types'
|
|
27
29
|
|
|
28
30
|
export default function FormDataListHeaderComponent({
|
|
@@ -62,20 +64,35 @@ export default function FormDataListHeaderComponent({
|
|
|
62
64
|
|
|
63
65
|
if (filtersWithConfig.length > 0) {
|
|
64
66
|
const filtersToApply = []
|
|
67
|
+
const joinsToApply: IFormJoin[][] = []
|
|
68
|
+
let pipelineToApply: string | undefined
|
|
69
|
+
|
|
65
70
|
for (const filterConfig of filtersWithConfig) {
|
|
66
71
|
const [field, value] = filterConfig
|
|
67
72
|
const config = filterConfigs[field]
|
|
68
73
|
|
|
69
74
|
if ('type' in config) {
|
|
70
|
-
const
|
|
71
|
-
|
|
75
|
+
const filterData = await handleFilterValues(
|
|
76
|
+
config as IFilterConfig,
|
|
77
|
+
value,
|
|
78
|
+
headerLayoutContext.formRelationships,
|
|
79
|
+
)
|
|
80
|
+
if (filterData?.match) filtersToApply.push(filterData.match)
|
|
81
|
+
if (filterData?.pipeline && !pipelineToApply) pipelineToApply = filterData.pipeline
|
|
82
|
+
if (Array.isArray(filterData?.joins) && filterData.joins.length) joinsToApply.push(filterData.joins)
|
|
72
83
|
} else {
|
|
73
84
|
// cases like radio buttons, where each has its own filter, but only 1 needs to apply
|
|
74
85
|
const nestedConfig = config as IFilterSimple
|
|
75
86
|
const eachConfig = nestedConfig[value as string]
|
|
76
87
|
|
|
77
|
-
const
|
|
78
|
-
|
|
88
|
+
const filterData = await handleFilterValues(
|
|
89
|
+
eachConfig as IFilterConfig,
|
|
90
|
+
value,
|
|
91
|
+
headerLayoutContext.formRelationships,
|
|
92
|
+
)
|
|
93
|
+
if (filterData?.match) filtersToApply.push(filterData.match)
|
|
94
|
+
if (filterData?.pipeline && !pipelineToApply) pipelineToApply = filterData.pipeline
|
|
95
|
+
if (Array.isArray(filterData?.joins) && filterData.joins.length) joinsToApply.push(filterData.joins)
|
|
79
96
|
}
|
|
80
97
|
}
|
|
81
98
|
|
|
@@ -85,13 +102,21 @@ export default function FormDataListHeaderComponent({
|
|
|
85
102
|
return { ...curr, ...parsedDFilter }
|
|
86
103
|
}, {})
|
|
87
104
|
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
105
|
+
const match = Object.values(dynamicFilters).length ? JSON.stringify(dynamicFilters) : undefined
|
|
106
|
+
|
|
107
|
+
if (pipelineToApply) {
|
|
108
|
+
const parsedPipeline = JSON.parse(pipelineToApply) as { $match?: { [key: string]: any } }[]
|
|
109
|
+
const pipeline = JSON.stringify([
|
|
110
|
+
...(match ? [{ $match: JSON.parse(match) }] : []),
|
|
111
|
+
...(parsedPipeline[0]?.$match ? parsedPipeline.slice(1) : parsedPipeline),
|
|
112
|
+
])
|
|
113
|
+
|
|
114
|
+
updateDynamicFilter({ match, pipeline, joins: mergeJoins(joinsToApply) })
|
|
115
|
+
return
|
|
116
|
+
}
|
|
92
117
|
|
|
93
|
-
updateDynamicFilter(
|
|
94
|
-
} else updateDynamicFilter()
|
|
118
|
+
updateDynamicFilter({ match })
|
|
119
|
+
} else updateDynamicFilter({})
|
|
95
120
|
},
|
|
96
121
|
[filterConfigs],
|
|
97
122
|
)
|
|
@@ -158,12 +183,16 @@ export default function FormDataListHeaderComponent({
|
|
|
158
183
|
|
|
159
184
|
type IFormDataListHeaderComponent = {
|
|
160
185
|
layoutConfig?: IDndLayoutStructure_Responsive
|
|
161
|
-
updateDynamicFilter: (match?: string) => void
|
|
186
|
+
updateDynamicFilter: (data?: { match?: string; pipeline?: string; joins?: IFormJoin[] }) => void
|
|
162
187
|
dataCount?: 'pending' | number
|
|
163
188
|
headerLayoutContext: IDataListHeaderLayoutContext
|
|
164
189
|
}
|
|
165
190
|
|
|
166
|
-
const handleFilterValues = async (
|
|
191
|
+
const handleFilterValues = async (
|
|
192
|
+
config: IFilterConfig,
|
|
193
|
+
value: any,
|
|
194
|
+
formRelationships?: IFormRelationshipConfig[],
|
|
195
|
+
) => {
|
|
167
196
|
const isNumber = typeof value === 'number'
|
|
168
197
|
const isBoolean = typeof value === 'boolean'
|
|
169
198
|
|
|
@@ -171,6 +200,31 @@ const handleFilterValues = async (config: IFilterConfig, value: any) => {
|
|
|
171
200
|
|
|
172
201
|
if (config.type === FilterConfigTypeEnum.NoFilter) return null
|
|
173
202
|
|
|
203
|
+
if (config.type === FilterConfigTypeEnum.RelatedChildForm) {
|
|
204
|
+
const childFormId = typeof value === 'number' ? value : parseInt(value, 10)
|
|
205
|
+
const relationship = formRelationships?.find(
|
|
206
|
+
(rel) => rel.type === FormRelationshipEnum.OneToMany && rel.formId === childFormId,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if (!childFormId || !relationship) return null
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
match: JSON.stringify({
|
|
213
|
+
[`${childFormId}._id`]: { $ne: null },
|
|
214
|
+
[`${childFormId}.DeletedDate`]: null,
|
|
215
|
+
}),
|
|
216
|
+
joins: [
|
|
217
|
+
{
|
|
218
|
+
formId: childFormId,
|
|
219
|
+
localField: '_id',
|
|
220
|
+
foreignField: `Data.${relationship.foreignKey}`,
|
|
221
|
+
alias: `${childFormId}`,
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
pipeline: JSON.stringify([{ $group: { _id: '$_id', doc: { $first: '$$ROOT' } } }]),
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
174
228
|
if ((config as IFilterCustom).bsonDataType === BSON_DATA_IDENTIFIER_PREFIXES.Date) {
|
|
175
229
|
const isRangeFilter =
|
|
176
230
|
Array.isArray(value) && value.every((v) => [null, undefined].includes(v) || dayjs(v).isValid())
|
|
@@ -204,6 +258,10 @@ const handleFilterValues = async (config: IFilterConfig, value: any) => {
|
|
|
204
258
|
|
|
205
259
|
if (value2) stringifiedFilter = stringifiedFilter.replaceAll(VALUE_REPLACEMENT_PLACEHOLDER2, value2)
|
|
206
260
|
|
|
207
|
-
return
|
|
261
|
+
return {
|
|
262
|
+
match: stringifiedFilter,
|
|
263
|
+
pipeline: (config as IFilterCustom).pipeline,
|
|
264
|
+
joins: (config as IFilterCustom).joins,
|
|
265
|
+
}
|
|
208
266
|
}
|
|
209
267
|
}
|
|
@@ -201,9 +201,14 @@ export default function FormDataListTableComponent({
|
|
|
201
201
|
<>
|
|
202
202
|
<FormDataListHeaderComponent
|
|
203
203
|
layoutConfig={layoutsConfigs?.dataListConfig.header}
|
|
204
|
-
updateDynamicFilter={(
|
|
204
|
+
updateDynamicFilter={(data) => {
|
|
205
205
|
setParentLoading(true)
|
|
206
|
-
setFilterReqData((c) => ({
|
|
206
|
+
setFilterReqData((c) => ({
|
|
207
|
+
...c,
|
|
208
|
+
match: data?.match,
|
|
209
|
+
pipeline: data?.pipeline,
|
|
210
|
+
joins: data?.joins,
|
|
211
|
+
}))
|
|
207
212
|
}}
|
|
208
213
|
dataCount={dataListOtherConfigs?.showCount ? (parentLoadings.data ? 'pending' : dataList.total) : undefined}
|
|
209
214
|
headerLayoutContext={headerLayoutContext}
|
|
@@ -281,6 +286,8 @@ function normalizeFilter(filter?: IDataListReqData & { skip?: number }) {
|
|
|
281
286
|
skip: typeof filter?.skip === 'number' ? filter.skip : undefined,
|
|
282
287
|
sort: filter?.sort ?? undefined,
|
|
283
288
|
match: filter?.match ?? undefined,
|
|
289
|
+
pipeline: filter?.pipeline ?? undefined,
|
|
290
|
+
joins: filter?.joins ? JSON.stringify(filter.joins) : undefined,
|
|
284
291
|
}
|
|
285
292
|
}
|
|
286
293
|
|
|
@@ -293,7 +300,9 @@ function areFiltersEqual(a?: IDataListReqData & { skip?: number }, b?: IDataList
|
|
|
293
300
|
normalizedA.limit === normalizedB.limit &&
|
|
294
301
|
normalizedA.skip === normalizedB.skip &&
|
|
295
302
|
normalizedA.sort === normalizedB.sort &&
|
|
296
|
-
normalizedA.match === normalizedB.match
|
|
303
|
+
normalizedA.match === normalizedB.match &&
|
|
304
|
+
normalizedA.pipeline === normalizedB.pipeline &&
|
|
305
|
+
normalizedA.joins === normalizedB.joins
|
|
297
306
|
)
|
|
298
307
|
}
|
|
299
308
|
|
|
@@ -4,7 +4,7 @@ import LayoutRendererCol from '../2-col'
|
|
|
4
4
|
import { memo, ReactElement, useMemo } from 'react'
|
|
5
5
|
import { LayoutRowConditionalHeaderRenderer } from './header-render'
|
|
6
6
|
import { LayoutRowRepeatableRenderer } from './repeatable-render'
|
|
7
|
-
import { IDndLayoutElement, IDndLayoutRow, IFormJoin } from '../../../../types'
|
|
7
|
+
import { IDndLayoutElement, IDndLayoutRow, IFormJoin, IFormRelationshipConfig } from '../../../../types'
|
|
8
8
|
import { IDynamicButton_DisplayStateProps } from '../3-element/1-dynamic-button'
|
|
9
9
|
import { ELEMENTS_DEFAULT_CLASS } from '../../../../constants'
|
|
10
10
|
import { useHiddenIds } from '../../../common/custom-hooks/use-node-condition.hook/use-node-condition.hook'
|
|
@@ -108,5 +108,6 @@ export interface IFormContext {
|
|
|
108
108
|
formId?: number
|
|
109
109
|
detailPageFormId?: number
|
|
110
110
|
parentFormJoins?: IFormJoin[]
|
|
111
|
+
formRelationships?: IFormRelationshipConfig[]
|
|
111
112
|
manyToManyRelInfo?: { middleFormId: number; currentFormId: number; otherFormId: number } | null
|
|
112
113
|
}
|
|
@@ -115,7 +115,7 @@ export default function GalleryDeleteModal({
|
|
|
115
115
|
{discardText || 'Discard'}
|
|
116
116
|
</Button_FillerPortal>
|
|
117
117
|
<Button_FillerPortal primary loading={isDeleting} onClick={deletePictures}>
|
|
118
|
-
{deleteText || 'Delete
|
|
118
|
+
{deleteText || 'Delete selected'}
|
|
119
119
|
</Button_FillerPortal>
|
|
120
120
|
</div>
|
|
121
121
|
}
|
|
@@ -3,7 +3,12 @@ import { memo, useMemo, useState } from 'react'
|
|
|
3
3
|
import { FaPlus, FaTrashAlt } from 'react-icons/fa'
|
|
4
4
|
import { FaUpload } from 'react-icons/fa6'
|
|
5
5
|
import { isNewFormDataPage, mapToFormItemRules, resolveConditionalText } from '../../../../../functions'
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
DeviceBreakpointEnum,
|
|
8
|
+
FormElementConditionalKeyEnum,
|
|
9
|
+
TranslationTextSubTypeEnum,
|
|
10
|
+
TranslationTextTypeEnum,
|
|
11
|
+
} from '../../../../../enums'
|
|
7
12
|
import { IGalleryElementProps, IValidationRule } from '../../../../../types'
|
|
8
13
|
import { Button_FillerPortal } from '../../../../common/button'
|
|
9
14
|
import { useTranslation } from '../../../../common/custom-hooks'
|
|
@@ -66,10 +71,11 @@ function LayoutRenderer_Gallery({
|
|
|
66
71
|
}),
|
|
67
72
|
[allValues, currentBreakpoint, elementKey, formDataId, t, textConditions],
|
|
68
73
|
)
|
|
69
|
-
const [emptyDescription
|
|
74
|
+
const [emptyDescription, deleteButtonText] = useMemo(
|
|
70
75
|
() =>
|
|
71
76
|
t([
|
|
72
77
|
{ key: elementKey, type: TranslationTextTypeEnum.Description, subType: TranslationTextSubTypeEnum.EmptyState },
|
|
78
|
+
{ key: elementKey, type: TranslationTextTypeEnum.Label, subType: TranslationTextSubTypeEnum.DeleteButton },
|
|
73
79
|
]),
|
|
74
80
|
[elementKey, t],
|
|
75
81
|
)
|
|
@@ -156,7 +162,7 @@ function LayoutRenderer_Gallery({
|
|
|
156
162
|
className="text-danger underline hover:text-danger"
|
|
157
163
|
>
|
|
158
164
|
<FaTrashAlt />
|
|
159
|
-
Delete
|
|
165
|
+
{deleteButtonText || 'Delete selected'}
|
|
160
166
|
</Button_FillerPortal>
|
|
161
167
|
)}
|
|
162
168
|
{!isUploadButtonHidden && (
|
|
@@ -215,7 +221,7 @@ function LayoutRenderer_Gallery({
|
|
|
215
221
|
</div>
|
|
216
222
|
</Image.PreviewGroup>
|
|
217
223
|
) : (
|
|
218
|
-
<Empty description={emptyDescription ||
|
|
224
|
+
<Empty description={emptyDescription || 'No pictures uploaded'} />
|
|
219
225
|
)}
|
|
220
226
|
</Form.Item>
|
|
221
227
|
<GalleryUploadModal
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
PageViewTypEnum,
|
|
25
25
|
FieldElementOptionSourceEnum,
|
|
26
26
|
FilterConfigTypeEnum,
|
|
27
|
+
FormRelationshipEnum,
|
|
27
28
|
IdMatchFieldTypeEnum,
|
|
28
29
|
TranslationTextTypeEnum,
|
|
29
30
|
TranslationTextSubTypeEnum,
|
|
@@ -61,6 +62,8 @@ function LayoutRenderer_FieldsWithOptions({
|
|
|
61
62
|
const { cachedConfig, isConfigLoading } = useCacheFormLayoutConfig(
|
|
62
63
|
props.optionSource.type === FieldElementOptionSourceEnum.ReadFromDetails
|
|
63
64
|
? props.optionSource?.baseFormId
|
|
65
|
+
: props.optionSource.type === FieldElementOptionSourceEnum.RelatedChildForm
|
|
66
|
+
? formContext.formId
|
|
64
67
|
: undefined,
|
|
65
68
|
)
|
|
66
69
|
|
|
@@ -318,9 +321,22 @@ function LayoutRenderer_FieldsWithOptions({
|
|
|
318
321
|
}),
|
|
319
322
|
)
|
|
320
323
|
setLoading(false)
|
|
324
|
+
} else if (props.optionSource.type === FieldElementOptionSourceEnum.RelatedChildForm) {
|
|
325
|
+
setRawOptions(
|
|
326
|
+
(cachedConfig?.relationships ?? [])
|
|
327
|
+
.filter((rel) => rel.type === FormRelationshipEnum.OneToMany)
|
|
328
|
+
.filter((rel, idx, arr) => arr.findIndex((eachRel) => eachRel.formId === rel.formId) === idx)
|
|
329
|
+
.map((rel) => {
|
|
330
|
+
const childFormId = rel.formId
|
|
331
|
+
const label = getFormById(childFormId)?.name || `Form #${childFormId}`
|
|
332
|
+
|
|
333
|
+
return { value: childFormId, label, fullLabel: label }
|
|
334
|
+
}),
|
|
335
|
+
)
|
|
336
|
+
setLoading(false)
|
|
321
337
|
} else if (props.optionSource.type === FieldElementOptionSourceEnum.ReadFromDetails) fetchDetailsStaticOptions()
|
|
322
338
|
else setLoading(false)
|
|
323
|
-
}, [props.optionSource, fetchDetailsStaticOptions, t])
|
|
339
|
+
}, [props.optionSource, fetchDetailsStaticOptions, t, cachedConfig?.relationships, getFormById])
|
|
324
340
|
|
|
325
341
|
useEffect(() => {
|
|
326
342
|
return () => {
|
package/src/enums/form.enum.ts
CHANGED
|
@@ -79,6 +79,7 @@ export enum FieldElementOptionSourceEnum {
|
|
|
79
79
|
Any = 'Any',
|
|
80
80
|
Static = 'Static',
|
|
81
81
|
DynamicForm = 'DynamicForm',
|
|
82
|
+
RelatedChildForm = 'RelatedChildForm',
|
|
82
83
|
ReadFromDetails = 'ReadFromDetails', // used in data list header
|
|
83
84
|
Roles = 'Roles',
|
|
84
85
|
}
|
|
@@ -124,6 +125,7 @@ export enum JustifyAndAlignContent {
|
|
|
124
125
|
export enum FilterConfigTypeEnum {
|
|
125
126
|
NoFilter = 'NoFilter',
|
|
126
127
|
Custom = 'Custom',
|
|
128
|
+
RelatedChildForm = 'RelatedChildForm',
|
|
127
129
|
}
|
|
128
130
|
export enum DeviceBreakpointEnum {
|
|
129
131
|
Default = 'default',
|
package/src/enums/index.ts
CHANGED
|
@@ -136,6 +136,7 @@ export enum TranslationTextSubTypeEnum {
|
|
|
136
136
|
RepeatButtonRemove = 'RepeatRemove',
|
|
137
137
|
Secondary = '2',
|
|
138
138
|
EmptyState = 'EmptyState',
|
|
139
|
+
DeleteButton = 'DeleteButton',
|
|
139
140
|
DeleteModalTitle = 'DeleteModalTitle',
|
|
140
141
|
DeleteModalDiscard = 'DeleteModalDiscard',
|
|
141
142
|
DeleteModalDelete = 'DeleteModalDelete',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LOCAL_STORAGE_KEYS_ENUM } from '../../enums'
|
|
2
|
-
import { IFormDataListData } from '../../types'
|
|
2
|
+
import { IFormDataListData, IFormJoin } from '../../types'
|
|
3
3
|
|
|
4
4
|
type DataListSnapshot = { data: IFormDataListData[]; total: number }
|
|
5
5
|
|
|
@@ -91,6 +91,8 @@ export type IDataListFilterState = {
|
|
|
91
91
|
limit?: number
|
|
92
92
|
sort?: string
|
|
93
93
|
match?: string
|
|
94
|
+
pipeline?: string
|
|
95
|
+
joins?: IFormJoin[]
|
|
94
96
|
skip?: number
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
DataRenderTypeEnum,
|
|
24
24
|
ElementTypeEnum,
|
|
25
25
|
FieldElementOptionSourceEnum,
|
|
26
|
+
FilterConfigTypeEnum,
|
|
26
27
|
LOCAL_STORAGE_KEYS_ENUM,
|
|
27
28
|
NotificationTypeEnum,
|
|
28
29
|
TemplateDataReplacementFieldTypesEnum,
|
|
@@ -32,6 +33,7 @@ import {
|
|
|
32
33
|
IEmail_Attachment,
|
|
33
34
|
IFilterNested,
|
|
34
35
|
IDndLayoutElement,
|
|
36
|
+
IFormLayoutFieldOption,
|
|
35
37
|
IGridContainerConfig,
|
|
36
38
|
IDynamicForm,
|
|
37
39
|
IFormDataApiReqConfig,
|
|
@@ -45,13 +47,22 @@ export const extractFiltersFromLayout = (elements: { [key: string]: IDndLayoutEl
|
|
|
45
47
|
|
|
46
48
|
Object.values(elements).forEach((el) => {
|
|
47
49
|
if (
|
|
48
|
-
|
|
50
|
+
[ElementTypeEnum.Radio, ElementTypeEnum.Select].includes(el.elementType) &&
|
|
51
|
+
el.props &&
|
|
52
|
+
'optionSource' in el.props &&
|
|
49
53
|
el.props.optionSource?.type === FieldElementOptionSourceEnum.Static
|
|
50
54
|
) {
|
|
51
|
-
el.props.optionSource.options.forEach((op) => {
|
|
55
|
+
el.props.optionSource.options.forEach((op: IFormLayoutFieldOption) => {
|
|
52
56
|
if (op.filter)
|
|
53
|
-
filters[el.key] = filters[el.key] ? { ...filters[el.key], [op.
|
|
57
|
+
filters[el.key] = filters[el.key] ? { ...filters[el.key], [op.id]: op.filter } : { [op.id]: op.filter }
|
|
54
58
|
})
|
|
59
|
+
} else if (
|
|
60
|
+
el.elementType === ElementTypeEnum.Select &&
|
|
61
|
+
el.props &&
|
|
62
|
+
'optionSource' in el.props &&
|
|
63
|
+
el.props.optionSource?.type === FieldElementOptionSourceEnum.RelatedChildForm
|
|
64
|
+
) {
|
|
65
|
+
filters[el.key] = { type: FilterConfigTypeEnum.RelatedChildForm }
|
|
55
66
|
} else if (el.props && 'filter' in el.props && el.props.filter) {
|
|
56
67
|
filters[el.key] = {
|
|
57
68
|
...el.props.filter,
|
|
@@ -427,8 +438,8 @@ const emailFieldKeys: Array<'to' | 'from' | 'cc' | 'bcc'> = ['to', 'from', 'cc',
|
|
|
427
438
|
const getValueFromPath = (path: string, data: Record<string, any>) => {
|
|
428
439
|
if (!path) return undefined
|
|
429
440
|
return path.split('.').reduce((acc: any, key: string) => {
|
|
430
|
-
if (
|
|
431
|
-
return acc[key]
|
|
441
|
+
if (acc === undefined || acc === null) return undefined
|
|
442
|
+
return acc[key]
|
|
432
443
|
}, data)
|
|
433
444
|
}
|
|
434
445
|
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import { IFormJoin } from '..'
|
|
2
2
|
import { FilterConfigTypeEnum } from '../../../enums'
|
|
3
3
|
|
|
4
|
-
export type IFilterConfig = INoFilterConfig | IFilterCustom
|
|
4
|
+
export type IFilterConfig = INoFilterConfig | IFilterCustom | IFilterRelatedChildForm
|
|
5
5
|
export interface INoFilterConfig {
|
|
6
6
|
type: FilterConfigTypeEnum.NoFilter
|
|
7
7
|
}
|
|
8
|
+
export interface IFilterRelatedChildForm {
|
|
9
|
+
type: FilterConfigTypeEnum.RelatedChildForm
|
|
10
|
+
}
|
|
8
11
|
export interface IFilterCustom {
|
|
9
12
|
type: FilterConfigTypeEnum.Custom
|
|
10
13
|
config: { [key: string]: any }
|
|
14
|
+
pipeline?: string
|
|
11
15
|
bsonDataType?: string
|
|
12
16
|
joins?: IFormJoin[]
|
|
13
17
|
conditionFormBranches?: string[]
|
|
@@ -3,6 +3,7 @@ import { FieldElementOptionSourceEnum } from '../../../enums'
|
|
|
3
3
|
|
|
4
4
|
export type IFieldElementOptionSource =
|
|
5
5
|
| IOptionSourceDynamicForm
|
|
6
|
+
| IOptionSourceRelatedChildForm
|
|
6
7
|
| IOptionSourceConstant // constant options - used in details page
|
|
7
8
|
| IOptionSourceReadFromDetails // reading constant options (above) in list header
|
|
8
9
|
| IOptionSourceAny // no options configured, but users can enter any options for informational purpose only
|
|
@@ -27,6 +28,10 @@ export type IOptionSourceDynamicForm = {
|
|
|
27
28
|
}
|
|
28
29
|
} & IDataFetchConfig
|
|
29
30
|
|
|
31
|
+
export interface IOptionSourceRelatedChildForm {
|
|
32
|
+
type: FieldElementOptionSourceEnum.RelatedChildForm
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
export interface IOptionSourceReadFromDetails {
|
|
31
36
|
type: FieldElementOptionSourceEnum.ReadFromDetails
|
|
32
37
|
formId: number
|