form-craft-package 1.7.0 → 1.7.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 +1 -1
- package/src/components/common/custom-hooks/use-window-width.hook.ts +1 -1
- package/src/components/form/1-list/index.tsx +120 -43
- package/src/components/form/1-list/table.tsx +69 -72
- package/src/components/form/layout-renderer/1-row/index.tsx +39 -46
- package/src/constants.ts +19 -0
- package/src/enums/companies/index.ts +3 -2
- package/src/enums/index.ts +1 -0
- package/src/functions/forms/index.ts +4 -4
- package/src/types/companies/index.ts +45 -10
- package/src/types/companies/site-layout/authenticated/index.tsx +45 -32
- package/src/types/companies/site-layout/unauthenticated/index.tsx +25 -0
- package/src/types/forms/index.ts +4 -0
package/package.json
CHANGED
|
@@ -15,7 +15,7 @@ const useGetCurrentBreakpoint = (): DeviceBreakpointEnum => {
|
|
|
15
15
|
|
|
16
16
|
if (!width) return DeviceBreakpointEnum.Default
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
if (width < parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.Mobile])) return DeviceBreakpointEnum.Mobile
|
|
19
19
|
|
|
20
20
|
if (width < parseInt(DEVICE_BREAKPOINTS[DeviceBreakpointEnum.TabletPortrait]))
|
|
21
21
|
return DeviceBreakpointEnum.TabletPortrait
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { useCallback, useEffect, useMemo, useState } from 'react'
|
|
1
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
|
2
2
|
import client from '../../../functions/axios-handler'
|
|
3
3
|
import { parseJSON } from '../../../functions/forms/json-handlers'
|
|
4
4
|
import FormDataListSkeleton_Table from '../../common/loading-skeletons/table'
|
|
5
|
-
import { IFormData, IFormDataListConfig, IFormSchema, IDndLayoutStructure_Responsive } from '../../../types'
|
|
6
5
|
import { ICustomFunctionCall } from '../layout-renderer/3-element/1-dynamic-button'
|
|
7
|
-
import FormDataListTableComponent
|
|
6
|
+
import FormDataListTableComponent from './table'
|
|
7
|
+
import { IFormDataListConfig, IFormSchema, IFormJoin, IDndLayoutElement, IFormDataListData } from '../../../types'
|
|
8
8
|
|
|
9
9
|
function FormDataListComponent({
|
|
10
10
|
formName,
|
|
@@ -15,14 +15,12 @@ function FormDataListComponent({
|
|
|
15
15
|
onCustomFunctionCall,
|
|
16
16
|
}: IFormDataListComponent) {
|
|
17
17
|
const [loading, setLoading] = useState(true)
|
|
18
|
-
const [
|
|
19
|
-
const [
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
| undefined
|
|
25
|
-
>()
|
|
18
|
+
const [dataList, setDataList] = useState<{ data: IFormDataListData[]; total: number }>({ data: [], total: 0 })
|
|
19
|
+
const [listLayoutConfig, setListLayoutConfig] = useState<IDataListLayoutConfig>()
|
|
20
|
+
|
|
21
|
+
const dataProjectRef = useRef<{ [key: string]: string }>({})
|
|
22
|
+
const formJoinsRef = useRef<IFormJoin[]>([])
|
|
23
|
+
const defaultFilterReqDataRef = useRef<IDataListReqData | undefined>(undefined)
|
|
26
24
|
|
|
27
25
|
const attachmentBaseUrl = useMemo(() => `${baseServerUrl}/api/attachment/${companyKey}`, [baseServerUrl, companyKey])
|
|
28
26
|
const dataListHeaderContext = { formId, userId, attachmentBaseUrl, formName, companyKey }
|
|
@@ -33,50 +31,115 @@ function FormDataListComponent({
|
|
|
33
31
|
if (res.status === 200) {
|
|
34
32
|
const parsedData: IFormSchema | null = parseJSON(res.data.data)
|
|
35
33
|
if (parsedData) {
|
|
34
|
+
console.log(parsedData.dataListConfig)
|
|
35
|
+
const { columns, joins, pagination, defaultFilter, defaultSorter } = parsedData.dataListConfig
|
|
36
|
+
|
|
37
|
+
if (Array.isArray(columns))
|
|
38
|
+
dataProjectRef.current = columns.reduce(
|
|
39
|
+
(curr, c) => (c.key ? { ...curr, [c.key.replace(/\./g, '_')]: `$${c.key}` } : curr),
|
|
40
|
+
{},
|
|
41
|
+
)
|
|
42
|
+
if (Array.isArray(joins)) formJoinsRef.current = joins
|
|
43
|
+
|
|
44
|
+
const defaultReqData: IDataListReqData = {}
|
|
45
|
+
if (!pagination?.hasNoPagination) {
|
|
46
|
+
defaultReqData.skip = 0
|
|
47
|
+
defaultReqData.size = pagination?.defaultPageSize ?? 10
|
|
48
|
+
}
|
|
49
|
+
if (defaultSorter) defaultReqData.sort = JSON.stringify({ [defaultSorter.field]: defaultSorter.order })
|
|
50
|
+
if (defaultFilter?.config)
|
|
51
|
+
// The operators are stored using @, and here it replaces @ with $. Otherwise, MongoDB's JSON-to-BSON conversion driver was detecting certain operators, especially $regex, and automatically modified them. To prevent data manipulation by the MongoDB driver, @ is used instead of $.
|
|
52
|
+
defaultReqData.match = JSON.stringify(defaultFilter.config).replaceAll('@', '$')
|
|
53
|
+
|
|
54
|
+
defaultFilterReqDataRef.current = defaultReqData
|
|
55
|
+
|
|
56
|
+
fetchFormDataList(defaultReqData)
|
|
57
|
+
|
|
36
58
|
// configForFormId is passed to ensure that the table shows the correct data list when the forms are switched quickly
|
|
37
|
-
|
|
38
|
-
dataListConfig: { ...parsedData.dataListConfig, configForFormId: formId },
|
|
39
|
-
detailsLayoutConfig: {
|
|
40
|
-
elements: parsedData.detailsConfig.elements,
|
|
41
|
-
layouts: parsedData.detailsConfig.layouts,
|
|
42
|
-
},
|
|
43
|
-
})
|
|
59
|
+
setListLayoutConfig({ configForFormId: formId, dataListConfig: parsedData.dataListConfig })
|
|
44
60
|
}
|
|
45
61
|
}
|
|
46
62
|
})
|
|
47
63
|
}
|
|
48
64
|
}, [formId])
|
|
49
65
|
|
|
50
|
-
const fetchFormDataList = useCallback(
|
|
51
|
-
|
|
66
|
+
const fetchFormDataList = useCallback(
|
|
67
|
+
(reqData?: IDataListReqData) => {
|
|
68
|
+
if (!reqData || !formId) return
|
|
52
69
|
|
|
53
|
-
|
|
54
|
-
.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
if (!reqData.skip && defaultFilterReqDataRef.current?.skip) reqData.skip = defaultFilterReqDataRef.current.skip
|
|
71
|
+
if (!reqData.size && defaultFilterReqDataRef.current?.size) reqData.size = defaultFilterReqDataRef.current.size
|
|
72
|
+
if (!reqData.sort && defaultFilterReqDataRef.current?.sort) reqData.sort = defaultFilterReqDataRef.current.sort
|
|
73
|
+
if (!reqData.match && defaultFilterReqDataRef.current?.match)
|
|
74
|
+
reqData.match = defaultFilterReqDataRef.current.match
|
|
75
|
+
|
|
76
|
+
console.log({ reqData })
|
|
77
|
+
|
|
78
|
+
client
|
|
79
|
+
.post(`/api/report/data/${formId}`, {
|
|
80
|
+
joins: formJoinsRef.current,
|
|
81
|
+
...reqData,
|
|
82
|
+
project: JSON.stringify(dataProjectRef.current),
|
|
83
|
+
})
|
|
84
|
+
.then((res) => {
|
|
85
|
+
if (res.status === 200) setDataList({ data: res.data.data, total: res.data.totalRecords })
|
|
86
|
+
})
|
|
87
|
+
.finally(() => setLoading(false))
|
|
88
|
+
},
|
|
89
|
+
[formId],
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
// useEffect(() => {
|
|
93
|
+
// client.post('/api/report/data/86', {
|
|
94
|
+
// joins: [
|
|
95
|
+
// // {
|
|
96
|
+
// // formId: 85,
|
|
97
|
+
// // foreignField: '_id',
|
|
98
|
+
// // localField: 'Data.dId',
|
|
99
|
+
// // alias: '85',
|
|
100
|
+
// // },
|
|
101
|
+
// // {
|
|
102
|
+
// // formId: 84,
|
|
103
|
+
// // foreignField: '_id',
|
|
104
|
+
// // localField: '85.Data.cId',
|
|
105
|
+
// // alias: '84',
|
|
106
|
+
// // },
|
|
107
|
+
// // {
|
|
108
|
+
// // formId: 83,
|
|
109
|
+
// // foreignField: '_id',
|
|
110
|
+
// // localField: '84.Data.bId',
|
|
111
|
+
// // alias: '83',
|
|
112
|
+
// // },
|
|
113
|
+
// // {
|
|
114
|
+
// // formId: 82,
|
|
115
|
+
// // foreignField: '_id',
|
|
116
|
+
// // localField: '83.Data.aId',
|
|
117
|
+
// // alias: '82',
|
|
118
|
+
// // },
|
|
119
|
+
// ],
|
|
120
|
+
// // match: JSON.stringify({ $expr: { $eq: ['$82._id', { $toObjectId: '67b7c0a5e46d36ac56ea6548' }] } }),
|
|
121
|
+
// sort: JSON.stringify({
|
|
122
|
+
// '82.Data.field1': MongoDbSortOrderEnum.Descending,
|
|
123
|
+
// 'Data.field1': MongoDbSortOrderEnum.Ascending,
|
|
124
|
+
// }),
|
|
125
|
+
// project: JSON.stringify({
|
|
126
|
+
// Data_field1: '$Data.field1',
|
|
127
|
+
// '85_Data_field1': '$85.Data.field1',
|
|
128
|
+
// '84_Data_field1': '$84.Data.field1',
|
|
129
|
+
// '83_Data_field1': '$83.Data.field1',
|
|
130
|
+
// '82_Data_field1': '$82.Data.field1',
|
|
131
|
+
// }),
|
|
132
|
+
// skip: 0, // current
|
|
133
|
+
// limit: 10, // pageSize
|
|
134
|
+
// })
|
|
135
|
+
// }, [])
|
|
73
136
|
|
|
74
137
|
return (
|
|
75
138
|
<FormDataListTableComponent
|
|
76
|
-
{
|
|
77
|
-
dataList={
|
|
139
|
+
layoutsConfigs={listLayoutConfig}
|
|
140
|
+
dataList={dataList}
|
|
78
141
|
updateDataList={(reqData) => {
|
|
79
|
-
fetchFormDataList(reqData
|
|
142
|
+
fetchFormDataList(reqData)
|
|
80
143
|
}}
|
|
81
144
|
parentLoading={loading}
|
|
82
145
|
loadingBlock={<FormDataListSkeleton_Table />}
|
|
@@ -95,3 +158,17 @@ type IFormDataListComponent = {
|
|
|
95
158
|
formId?: number
|
|
96
159
|
userId: string | number
|
|
97
160
|
} & ICustomFunctionCall
|
|
161
|
+
|
|
162
|
+
export interface IDataListReqData {
|
|
163
|
+
skip?: number
|
|
164
|
+
size?: number
|
|
165
|
+
sort?: string
|
|
166
|
+
match?: string
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export type IDataListLayoutConfig =
|
|
170
|
+
| {
|
|
171
|
+
configForFormId: number
|
|
172
|
+
dataListConfig: IFormDataListConfig
|
|
173
|
+
}
|
|
174
|
+
| undefined
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
IDndLayoutStructure_Responsive,
|
|
5
5
|
IFormData,
|
|
6
6
|
IFormDataListConfig,
|
|
7
|
+
IFormDataListData,
|
|
7
8
|
IFormDataTableColumn,
|
|
8
9
|
} from '../../../types'
|
|
9
10
|
import FormDataListHeaderComponent from './table-header'
|
|
@@ -20,10 +21,10 @@ import {
|
|
|
20
21
|
FormDataListViewTypeEnum,
|
|
21
22
|
MongoDbSortOrderEnum,
|
|
22
23
|
} from '../../../enums'
|
|
24
|
+
import { IDataListReqData, IDataListLayoutConfig } from '.'
|
|
23
25
|
|
|
24
26
|
export default function FormDataListTableComponent({
|
|
25
|
-
|
|
26
|
-
detailsLayoutConfig,
|
|
27
|
+
layoutsConfigs,
|
|
27
28
|
parentLoading,
|
|
28
29
|
dataList,
|
|
29
30
|
loadingBlock,
|
|
@@ -63,16 +64,15 @@ export default function FormDataListTableComponent({
|
|
|
63
64
|
setIsHandlingSchema(true)
|
|
64
65
|
}, [location.pathname])
|
|
65
66
|
|
|
66
|
-
useEffect(() => {
|
|
67
|
-
|
|
68
|
-
}, [debouncedFilterReqData])
|
|
67
|
+
// useEffect(() => {
|
|
68
|
+
// if (debouncedFilterReqData) updateDataList(debouncedFilterReqData)
|
|
69
|
+
// }, [debouncedFilterReqData])
|
|
69
70
|
|
|
70
71
|
const handleDataListConfig = useCallback(
|
|
71
|
-
async (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (config.configForFormId && config.configForFormId !== formId) return
|
|
72
|
+
async (layoutsConfigs: IDataListLayoutConfig) => {
|
|
73
|
+
const { configForFormId, dataListConfig } = layoutsConfigs!
|
|
74
|
+
|
|
75
|
+
if (configForFormId !== formId) return
|
|
76
76
|
|
|
77
77
|
const {
|
|
78
78
|
columns = [],
|
|
@@ -81,42 +81,44 @@ export default function FormDataListTableComponent({
|
|
|
81
81
|
defaultFilter,
|
|
82
82
|
defaultSorter,
|
|
83
83
|
...restConfigs
|
|
84
|
-
} =
|
|
84
|
+
} = dataListConfig
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
86
|
+
console.log({ columns })
|
|
87
|
+
|
|
88
|
+
// TODO:
|
|
89
|
+
// const elementsWithOptions_key = columns.filter((op) => op.key && op.isSelectOption).map((op) => op.key)
|
|
89
90
|
|
|
90
91
|
let optionKeyValuePair = {}
|
|
91
|
-
if (elementsWithOptions_key.length > 0)
|
|
92
|
-
|
|
92
|
+
// if (elementsWithOptions_key.length > 0)
|
|
93
|
+
// optionKeyValuePair = await extractElementsOptions(elements ?? {}, elementsWithOptions_key)
|
|
93
94
|
|
|
94
95
|
setTableColumns(generateTableColumns(columns, optionKeyValuePair, dataListHeaderContext))
|
|
95
96
|
setHeaderLayoutConfig({
|
|
96
97
|
layouts: header.layouts,
|
|
97
|
-
elements:
|
|
98
|
+
elements: header.elements,
|
|
99
|
+
// elements: appendOptionsFromDetailsLayout(header.elements, layoutConfig?.elements),
|
|
98
100
|
})
|
|
99
101
|
setOtherConfigs({ ...restConfigs, hasNoPagination: pagination?.hasNoPagination ?? false })
|
|
100
102
|
|
|
101
|
-
if (defaultFilter && defaultFilter.type !== FilterConfigTypeEnum.NoFilter)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
defaultFilterRef.current = {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
103
|
+
// if (defaultFilter && defaultFilter.type !== FilterConfigTypeEnum.NoFilter)
|
|
104
|
+
// defaultFilterRef.current = {
|
|
105
|
+
// ...defaultFilterRef.current,
|
|
106
|
+
// customFilter: defaultFilter.type === FilterConfigTypeEnum.ByAuthUser ? { createdById: userId } : {},
|
|
107
|
+
// dynamicFilter: defaultFilter.config
|
|
108
|
+
// ? // The operators are stored using @, and here it replaces @ with $. Otherwise, MongoDB's JSON-to-BSON conversion driver was detecting certain operators, especially $regex, and automatically modified them. To prevent data manipulation by the MongoDB driver, @ is used instead of $.
|
|
109
|
+
// JSON.stringify(defaultFilter.config).replaceAll('@', '$')
|
|
110
|
+
// : JSON.stringify({}),
|
|
111
|
+
// }
|
|
112
|
+
// defaultFilterRef.current = {
|
|
113
|
+
// ...defaultFilterRef.current,
|
|
114
|
+
// sort: defaultSorter ? { [defaultSorter.field]: defaultSorter.order } : {},
|
|
115
|
+
// pagination: {
|
|
116
|
+
// current: 1,
|
|
117
|
+
// pageSize: pagination?.defaultPageSize ?? 10,
|
|
118
|
+
// showSizeChanger: pagination?.showSizeChanger ?? false,
|
|
119
|
+
// pageSizeOptions: pagination?.pageSizeOptions ?? [],
|
|
120
|
+
// },
|
|
121
|
+
// }
|
|
120
122
|
|
|
121
123
|
setIsHandlingSchema(false)
|
|
122
124
|
isFinishedHandlingRef.current = true
|
|
@@ -125,8 +127,8 @@ export default function FormDataListTableComponent({
|
|
|
125
127
|
)
|
|
126
128
|
|
|
127
129
|
useEffect(() => {
|
|
128
|
-
if (
|
|
129
|
-
}, [
|
|
130
|
+
if (layoutsConfigs) handleDataListConfig(layoutsConfigs)
|
|
131
|
+
}, [layoutsConfigs])
|
|
130
132
|
|
|
131
133
|
const tableColumnsFiltered = useMemo(
|
|
132
134
|
() => tableColumns.filter((op) => !Array.isArray(op.hideScreens) || !op.hideScreens.includes(currentBreakpoint)),
|
|
@@ -156,39 +158,35 @@ export default function FormDataListTableComponent({
|
|
|
156
158
|
onCustomFunctionCall={onCustomFunctionCall}
|
|
157
159
|
{...dataListHeaderContext}
|
|
158
160
|
/>
|
|
159
|
-
{
|
|
160
|
-
<Table<
|
|
161
|
+
{(!otherConfigs || otherConfigs.listType === FormDataListViewTypeEnum.Table) && (
|
|
162
|
+
<Table<IFormDataListData>
|
|
161
163
|
dataSource={dataList.data}
|
|
162
164
|
columns={tableColumnsFiltered}
|
|
163
|
-
rowKey={(record) => record.id}
|
|
164
|
-
pagination={
|
|
165
|
-
otherConfigs?.hasNoPagination
|
|
166
|
-
? false
|
|
167
|
-
: { ...defaultFilterRef.current.pagination, ...filterReqData?.pagination, total: dataList.total }
|
|
168
|
-
}
|
|
165
|
+
rowKey={(record) => record.id} // TODO: change this to _id
|
|
166
|
+
pagination={otherConfigs?.hasNoPagination ? false : { ...filterReqData?.pagination, total: dataList.total }}
|
|
169
167
|
loading={parentLoading}
|
|
170
|
-
onChange={(cbPagination, _, sorter: SorterResult<IFormData> | SorterResult<IFormData>[]) => {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}}
|
|
168
|
+
// onChange={(cbPagination, _, sorter: SorterResult<IFormData> | SorterResult<IFormData>[]) => {
|
|
169
|
+
// console.log('ON TABLE CHANGE', cbPagination, sorter)
|
|
170
|
+
// setParentLoading(true)
|
|
171
|
+
|
|
172
|
+
// setFilterReqData((c) => {
|
|
173
|
+
// if (!c) return c
|
|
174
|
+
|
|
175
|
+
// return {
|
|
176
|
+
// ...c,
|
|
177
|
+
// sort:
|
|
178
|
+
// sorter && (sorter as SorterResult<IFormData>).field
|
|
179
|
+
// ? {
|
|
180
|
+
// [(sorter as SorterResult<IFormData>).field as string]:
|
|
181
|
+
// (sorter as SorterResult<IFormData>).order === 'ascend'
|
|
182
|
+
// ? MongoDbSortOrderEnum.Ascending
|
|
183
|
+
// : MongoDbSortOrderEnum.Descending,
|
|
184
|
+
// }
|
|
185
|
+
// : {},
|
|
186
|
+
// pagination: cbPagination as IPagination,
|
|
187
|
+
// }
|
|
188
|
+
// })
|
|
189
|
+
// }}
|
|
192
190
|
/>
|
|
193
191
|
)}
|
|
194
192
|
</>
|
|
@@ -196,13 +194,12 @@ export default function FormDataListTableComponent({
|
|
|
196
194
|
}
|
|
197
195
|
|
|
198
196
|
type IFormDataListTableComponent = {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
dataList: { data: IFormData[]; total: number }
|
|
197
|
+
layoutsConfigs: IDataListLayoutConfig
|
|
198
|
+
dataList: { data: IFormDataListData[]; total: number }
|
|
202
199
|
parentLoading: boolean
|
|
203
200
|
loadingBlock?: JSX.Element
|
|
204
201
|
setParentLoading: React.Dispatch<React.SetStateAction<boolean>>
|
|
205
|
-
updateDataList: (reqData:
|
|
202
|
+
updateDataList: (reqData: IDataListReqData) => void
|
|
206
203
|
} & ICustomFunctionCall &
|
|
207
204
|
IDataListHeaderContext
|
|
208
205
|
|
|
@@ -4,63 +4,56 @@ import LayoutRendererCol from '../2-col'
|
|
|
4
4
|
import { memo, ReactElement, ReactNode, useMemo, useState } from 'react'
|
|
5
5
|
import { LayoutRowConditionalHeaderRenderer } from './header-render'
|
|
6
6
|
import { LayoutRowRepeatableRenderer } from './repeatable-render'
|
|
7
|
-
import {
|
|
8
|
-
IButtonElementProps,
|
|
9
|
-
IDndLayoutElement,
|
|
10
|
-
IFormLayoutElementConditions,
|
|
11
|
-
IDndLayoutRow,
|
|
12
|
-
} from '../../../../types'
|
|
7
|
+
import { IButtonElementProps, IDndLayoutElement, IFormLayoutElementConditions, IDndLayoutRow } from '../../../../types'
|
|
13
8
|
|
|
14
9
|
export const LayoutRendererRow = memo(
|
|
15
10
|
({ basePath = [], rowData, titleComponent, formContext, elements, renderButton }: ILayoutRendererRow) => {
|
|
16
11
|
const [hiddenElementCount, setHiddenElementCount] = useState(0)
|
|
17
12
|
|
|
18
13
|
const styleConfig = useMemo(() => {
|
|
19
|
-
if (!rowData.style
|
|
14
|
+
if (!rowData.style) return {}
|
|
20
15
|
return kebabCaseToCamelCase(rowData.style)
|
|
21
16
|
}, [rowData.style])
|
|
22
17
|
|
|
23
18
|
return (
|
|
24
|
-
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
{
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (hiddenElementCount === rowData.children.length) style.display = 'none'
|
|
19
|
+
<LayoutRowConditionalHeaderRenderer header={rowData.props?.header}>
|
|
20
|
+
<LayoutRowRepeatableRenderer basePath={basePath} repeatingSection={rowData.props?.repeatingSection}>
|
|
21
|
+
{(formListItemProps) => {
|
|
22
|
+
const style: { [key: string]: any } = {
|
|
23
|
+
...styleConfig,
|
|
24
|
+
...getFlexContainerStyle(rowData.display),
|
|
25
|
+
}
|
|
26
|
+
if (hiddenElementCount === rowData.children.length) style.display = 'none'
|
|
33
27
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
</>
|
|
28
|
+
return (
|
|
29
|
+
<div style={{ ...style, maxWidth: '100vw', overflowX: 'auto' }}>
|
|
30
|
+
{rowData.children.map((col, colIdx) => (
|
|
31
|
+
<LayoutRendererCol
|
|
32
|
+
key={colIdx}
|
|
33
|
+
basePath={formListItemProps ? formListItemProps.updatedBasePath : basePath}
|
|
34
|
+
colData={col}
|
|
35
|
+
titleComponent={titleComponent}
|
|
36
|
+
elements={elements}
|
|
37
|
+
formContext={formContext}
|
|
38
|
+
colStyle={getColumnStyle(colIdx, rowData.display)}
|
|
39
|
+
renderButton={renderButton}
|
|
40
|
+
hideRow={(isHidden) =>
|
|
41
|
+
setHiddenElementCount((c) => {
|
|
42
|
+
if (isHidden) return c + 1
|
|
43
|
+
else {
|
|
44
|
+
if (c - 1 < 0) return 0
|
|
45
|
+
return c - 1
|
|
46
|
+
}
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
/>
|
|
50
|
+
))}
|
|
51
|
+
{formListItemProps?.removeButton}
|
|
52
|
+
</div>
|
|
53
|
+
)
|
|
54
|
+
}}
|
|
55
|
+
</LayoutRowRepeatableRenderer>
|
|
56
|
+
</LayoutRowConditionalHeaderRenderer>
|
|
64
57
|
)
|
|
65
58
|
},
|
|
66
59
|
)
|
package/src/constants.ts
CHANGED
|
@@ -110,4 +110,23 @@ export const DEFAULT_CONFIG = {
|
|
|
110
110
|
siteIdentity: { logoUrl: '/favicon.png', iconUrl: '/favicon.png', title: 'Site Title' },
|
|
111
111
|
siteMenus: [],
|
|
112
112
|
siteLanguages: [{ value: 'en', label: 'English', defaultSelected: true }],
|
|
113
|
+
loginLayout: {
|
|
114
|
+
layout: 'center',
|
|
115
|
+
backgroundType: 'color',
|
|
116
|
+
background: '#f5f5f5',
|
|
117
|
+
backgroundImage: '',
|
|
118
|
+
formStyle: 'mini',
|
|
119
|
+
logoPosition: 'top',
|
|
120
|
+
form: {
|
|
121
|
+
title: 'Login',
|
|
122
|
+
color: '#245780',
|
|
123
|
+
background: '#ffffff',
|
|
124
|
+
size: 20,
|
|
125
|
+
width: 400,
|
|
126
|
+
weight: 700,
|
|
127
|
+
padding: 20,
|
|
128
|
+
spacing: 20,
|
|
129
|
+
fields: ['email', 'password', 'forgot-password']
|
|
130
|
+
}
|
|
131
|
+
}
|
|
113
132
|
}
|
package/src/enums/index.ts
CHANGED
|
@@ -94,11 +94,11 @@ export const appendOptionsFromDetailsLayout = (
|
|
|
94
94
|
[ElementTypeEnum.Select, ElementTypeEnum.Radio].includes(element.elementType) &&
|
|
95
95
|
element &&
|
|
96
96
|
(element as ISelectElement | IRadioElement).props?.optionSource?.type ===
|
|
97
|
-
|
|
97
|
+
FieldElementOptionSourceEnum.ReadFromDetails
|
|
98
98
|
) {
|
|
99
99
|
const detailsElement =
|
|
100
100
|
detailsLayoutElements[
|
|
101
|
-
|
|
101
|
+
((element as ISelectElement | IRadioElement).props.optionSource as IOptionSourceReadFromDetails)?.field ?? ''
|
|
102
102
|
]
|
|
103
103
|
updatedElements[elementKey] = {
|
|
104
104
|
...element,
|
|
@@ -108,7 +108,7 @@ export const appendOptionsFromDetailsLayout = (
|
|
|
108
108
|
type: FieldElementOptionSourceEnum.Static,
|
|
109
109
|
options: detailsElement
|
|
110
110
|
? ((detailsElement as ISelectElement | IRadioElement).props.optionSource as IOptionSourceConstant)
|
|
111
|
-
|
|
111
|
+
?.options ?? []
|
|
112
112
|
: [],
|
|
113
113
|
},
|
|
114
114
|
},
|
|
@@ -139,7 +139,7 @@ export const getColumnStyle = (itemIdx: number, display?: IGridContainerConfig):
|
|
|
139
139
|
/** --------------------------------------------------------------------------------------------------------- */
|
|
140
140
|
|
|
141
141
|
export const constructDynamicFormHref = (name: string) => '/d/' + name.split(' ').join('-').toLowerCase()
|
|
142
|
-
|
|
142
|
+
export const constructDynamicReportHref = (name: string) => '/d/reports/' + name.split(' ').join('-').toLowerCase()
|
|
143
143
|
/** --------------------------------------------------------------------------------------------------------- */
|
|
144
144
|
|
|
145
145
|
export const extractDynamicFormHref = (pathname: string = '') => {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface ICompanyViewModel {
|
|
2
2
|
id: number;
|
|
3
3
|
name?: string | null;
|
|
4
|
+
domain?: string;
|
|
4
5
|
blobSAS?: string | null;
|
|
5
6
|
defaultDataId?: string | null;
|
|
6
7
|
key?: string | null;
|
|
@@ -15,7 +16,29 @@ export interface ICompanyConfig {
|
|
|
15
16
|
siteLayout: ISiteLayout;
|
|
16
17
|
siteMenus: ISiteMenus;
|
|
17
18
|
siteLanguages: ISiteLanguages;
|
|
19
|
+
loginLayout: ILoginLayout;
|
|
20
|
+
}
|
|
21
|
+
export interface ILoginLayout {
|
|
22
|
+
layout: string;
|
|
23
|
+
backgroundType: string;
|
|
24
|
+
background: string;
|
|
25
|
+
backgroundImage: string;
|
|
26
|
+
formStyle: string;
|
|
27
|
+
logoPosition: string;
|
|
28
|
+
form: ILoginFormLayout;
|
|
29
|
+
}
|
|
30
|
+
export interface ILoginFormLayout {
|
|
31
|
+
width: number;
|
|
32
|
+
padding: number;
|
|
33
|
+
title: string;
|
|
34
|
+
background: string;
|
|
35
|
+
size: number;
|
|
36
|
+
weight: number;
|
|
37
|
+
color: string;
|
|
38
|
+
spacing: number;
|
|
39
|
+
fields: string[];
|
|
18
40
|
}
|
|
41
|
+
|
|
19
42
|
export type ISiteLanguages = ISiteLanguageItem[]
|
|
20
43
|
export interface ISiteLanguageItem {
|
|
21
44
|
value: string;
|
|
@@ -25,26 +48,37 @@ export interface ISiteLanguageItem {
|
|
|
25
48
|
export type ISiteMenus = ISiteMenuItem[]
|
|
26
49
|
|
|
27
50
|
export interface ISiteMenuItem {
|
|
28
|
-
id: number
|
|
29
|
-
name: string
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
51
|
+
id: number;
|
|
52
|
+
name: string;
|
|
53
|
+
displayName?: string;
|
|
54
|
+
publicAccess?: boolean;
|
|
55
|
+
icon?: string;
|
|
56
|
+
customLink?: string;
|
|
33
57
|
children?: ISiteMenuItem[]
|
|
34
58
|
}
|
|
59
|
+
export interface IProfileMenuItem {
|
|
60
|
+
id: number;
|
|
61
|
+
name: string;
|
|
62
|
+
displayName?: string;
|
|
63
|
+
customLink?: string;
|
|
64
|
+
icon?: string;
|
|
65
|
+
onClick?: () => void
|
|
66
|
+
}
|
|
35
67
|
export interface IExtendedSiteMenuItem extends ISiteMenuItem {
|
|
36
68
|
isMenuGroup?: boolean;
|
|
37
69
|
isReportGroup?: boolean;
|
|
70
|
+
menuType?: string;
|
|
38
71
|
}
|
|
39
72
|
export interface MenuBuilderProps {
|
|
40
|
-
reportItems: ISiteReportItem[]
|
|
41
|
-
menuItems: ISiteMenuItem[]
|
|
42
|
-
onChange?: (menu: ISiteMenuItem[]) => void
|
|
43
|
-
initialValue: ISiteMenuItem[]
|
|
73
|
+
reportItems: ISiteReportItem[];
|
|
74
|
+
menuItems: ISiteMenuItem[];
|
|
75
|
+
onChange?: (menu: ISiteMenuItem[]) => void;
|
|
76
|
+
initialValue: ISiteMenuItem[];
|
|
44
77
|
}
|
|
45
78
|
export interface ISiteReportItem {
|
|
46
79
|
id: number;
|
|
47
80
|
name: string;
|
|
81
|
+
displayName: string;
|
|
48
82
|
description: string;
|
|
49
83
|
isActive: boolean;
|
|
50
84
|
order: number;
|
|
@@ -130,4 +164,5 @@ export interface FontSelectorProps {
|
|
|
130
164
|
initialFont?: string;
|
|
131
165
|
}
|
|
132
166
|
|
|
133
|
-
export * from './site-layout/authenticated';
|
|
167
|
+
export * from './site-layout/authenticated';
|
|
168
|
+
export * from './site-layout/unauthenticated';
|
|
@@ -2,7 +2,7 @@ import { FaCaretDown, FaTimes, FaUser } from 'react-icons/fa'
|
|
|
2
2
|
import { HiMenu } from 'react-icons/hi'
|
|
3
3
|
import { ICompanyConfig, ILayoutTemplateProps, IExtendedSiteMenuItem } from '../..'
|
|
4
4
|
import { Link } from 'react-router-dom'
|
|
5
|
-
import { constructDynamicFormHref, isEncodedURI } from '../../../../functions/forms'
|
|
5
|
+
import { constructDynamicFormHref, constructDynamicReportHref, isEncodedURI } from '../../../../functions/forms'
|
|
6
6
|
import { useEffect, useState } from 'react'
|
|
7
7
|
import { Button, Drawer, Dropdown, Layout, Menu } from 'antd'
|
|
8
8
|
import * as FaIcons from 'react-icons/fa'
|
|
@@ -11,7 +11,7 @@ const { Header, Content, Sider } = Layout
|
|
|
11
11
|
export const layoutTemplates = [
|
|
12
12
|
{
|
|
13
13
|
name: 'template_1',
|
|
14
|
-
value: ({ children,
|
|
14
|
+
value: ({ children, menuItems, config, logoUrl, isPreview }: ILayoutTemplateProps) => {
|
|
15
15
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
16
16
|
const siteConfigs = config?.siteLayout?.siteConfigs
|
|
17
17
|
const navigationWidth = siteConfigs?.custom?.navigationWidth || 200
|
|
@@ -28,12 +28,17 @@ export const layoutTemplates = [
|
|
|
28
28
|
}, [config])
|
|
29
29
|
|
|
30
30
|
const renderMenuItem = (form: IExtendedSiteMenuItem, level = 0) => {
|
|
31
|
-
let href =
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
let href = ''
|
|
32
|
+
|
|
33
|
+
if (form.customLink === '' || form.isMenuGroup || form.isReportGroup) {
|
|
34
|
+
href = '#'
|
|
35
|
+
} else if (form.customLink) {
|
|
36
|
+
href = form.customLink
|
|
37
|
+
} else if (form.menuType && form.menuType === 'report') {
|
|
38
|
+
href = constructDynamicReportHref(form.name)
|
|
39
|
+
} else {
|
|
40
|
+
href = constructDynamicFormHref(form.name)
|
|
41
|
+
}
|
|
37
42
|
let isParentMenu = href === '#' && form.children && form.children.length > 0
|
|
38
43
|
const IconComponent = form.icon ? (FaIcons as any)[form.icon] : null
|
|
39
44
|
const isExpanded = expandedMenus[form.id]
|
|
@@ -81,7 +86,7 @@ export const layoutTemplates = [
|
|
|
81
86
|
<img alt="" src={form.icon ?? ''} className="w-4 h-4 object-contain" />
|
|
82
87
|
)}
|
|
83
88
|
</div>
|
|
84
|
-
<span className="flex-grow">{form.name}</span>
|
|
89
|
+
<span className="flex-grow">{form.displayName || form.name}</span>
|
|
85
90
|
{isParentMenu &&
|
|
86
91
|
(isExpanded ? <FaIcons.FaAngleUp className="w-4 h-4" /> : <FaIcons.FaAngleDown className="w-4 h-4" />)}
|
|
87
92
|
</Link>
|
|
@@ -108,7 +113,7 @@ export const layoutTemplates = [
|
|
|
108
113
|
)
|
|
109
114
|
return (
|
|
110
115
|
<Layout className="h-screen overflow-y-auto overflow-x-hidden">
|
|
111
|
-
<Header className="sticky top-0 z-40 flex items-center shadow-sm justify-between px-1">
|
|
116
|
+
<Header className="sticky top-0 z-40 flex items-center shadow-sm justify-between px-1 overflow-hidden">
|
|
112
117
|
{/* Logo */}
|
|
113
118
|
<div className="flex">
|
|
114
119
|
<a href="/">
|
|
@@ -217,30 +222,33 @@ export const layoutTemplates = [
|
|
|
217
222
|
},
|
|
218
223
|
{
|
|
219
224
|
name: 'template_2',
|
|
220
|
-
value: ({ children,
|
|
225
|
+
value: ({ children, menuItems, config, logoUrl, isPreview }: ILayoutTemplateProps) => {
|
|
221
226
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
222
227
|
const siteConfigs = config?.siteLayout?.siteConfigs
|
|
223
228
|
const contentPadding = siteConfigs?.custom?.contentPadding || 20
|
|
224
|
-
|
|
229
|
+
|
|
225
230
|
const renderMenuItem = (form: IExtendedSiteMenuItem, level = 0) => {
|
|
226
|
-
let href =
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
231
|
+
let href = ''
|
|
232
|
+
|
|
233
|
+
if (form.customLink === '' || form.isMenuGroup || form.isReportGroup) {
|
|
234
|
+
href = '#'
|
|
235
|
+
} else if (form.customLink) {
|
|
236
|
+
href = form.customLink
|
|
237
|
+
} else if (form.menuType && form.menuType === 'report') {
|
|
238
|
+
href = constructDynamicReportHref(form.name)
|
|
239
|
+
} else {
|
|
240
|
+
href = constructDynamicFormHref(form.name)
|
|
241
|
+
}
|
|
232
242
|
let isParentMenu = href === '#' && form.children && form.children.length > 0
|
|
233
|
-
|
|
243
|
+
|
|
234
244
|
const IconComponent = form.icon ? (FaIcons as any)[form.icon] : null
|
|
235
|
-
|
|
236
|
-
setExpandedMenus((prev) => ({ ...prev, [id]: !prev[id] }))
|
|
237
|
-
}
|
|
245
|
+
|
|
238
246
|
return (
|
|
239
247
|
<div key={form.id} className="relative group">
|
|
240
248
|
<Link
|
|
241
249
|
to={href}
|
|
242
250
|
target={form.customLink && '_blank'}
|
|
243
|
-
onClick={isParentMenu ? () =>
|
|
251
|
+
onClick={isParentMenu ? (e) => e.preventDefault() : undefined}
|
|
244
252
|
style={{
|
|
245
253
|
borderRadius: `${siteConfigs?.Link.borderRadius}px`,
|
|
246
254
|
backgroundColor: isActive(window.location.pathname, href)
|
|
@@ -274,7 +282,7 @@ export const layoutTemplates = [
|
|
|
274
282
|
<img alt="" src={form.icon ?? ''} className="w-4 h-4 object-contain" />
|
|
275
283
|
)}
|
|
276
284
|
</div>
|
|
277
|
-
<span className="flex-grow">{form.name}</span>
|
|
285
|
+
<span className="flex-grow">{form.displayName || form.name}</span>
|
|
278
286
|
</Link>
|
|
279
287
|
{isParentMenu && (
|
|
280
288
|
<div className="absolute left-0 top-full hidden group-hover:flex flex-col bg-white shadow-md rounded-md z-50 min-w-[180px]">
|
|
@@ -394,7 +402,7 @@ export const layoutTemplates = [
|
|
|
394
402
|
},
|
|
395
403
|
{
|
|
396
404
|
name: 'template_3',
|
|
397
|
-
value: ({ children,
|
|
405
|
+
value: ({ children, menuItems, logoUrl, config, isPreview }: ILayoutTemplateProps) => {
|
|
398
406
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
399
407
|
const siteConfigs = config?.siteLayout?.siteConfigs
|
|
400
408
|
const navigationWidth = siteConfigs?.custom?.navigationWidth || 200
|
|
@@ -411,12 +419,17 @@ export const layoutTemplates = [
|
|
|
411
419
|
setExpandedMenus(initialExpandedStates)
|
|
412
420
|
}, [config])
|
|
413
421
|
const renderMenuItem = (form: IExtendedSiteMenuItem, level = 0) => {
|
|
414
|
-
let href =
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
422
|
+
let href = ''
|
|
423
|
+
|
|
424
|
+
if (form.customLink === '' || form.isMenuGroup || form.isReportGroup) {
|
|
425
|
+
href = '#'
|
|
426
|
+
} else if (form.customLink) {
|
|
427
|
+
href = form.customLink
|
|
428
|
+
} else if (form.menuType && form.menuType === 'report') {
|
|
429
|
+
href = constructDynamicReportHref(form.name)
|
|
430
|
+
} else {
|
|
431
|
+
href = constructDynamicFormHref(form.name)
|
|
432
|
+
}
|
|
420
433
|
let isParentMenu = href === '#' && form.children && form.children.length > 0
|
|
421
434
|
const IconComponent = form.icon ? (FaIcons as any)[form.icon] : null
|
|
422
435
|
const isExpanded = expandedMenus[form.id]
|
|
@@ -464,7 +477,7 @@ export const layoutTemplates = [
|
|
|
464
477
|
<img alt="" src={form.icon ?? ''} className="w-4 h-4 object-contain" />
|
|
465
478
|
)}
|
|
466
479
|
</div>
|
|
467
|
-
<span className="flex-grow">{form.name}</span>
|
|
480
|
+
<span className="flex-grow">{form.displayName || form.name}</span>
|
|
468
481
|
{isParentMenu &&
|
|
469
482
|
(isExpanded ? <FaIcons.FaAngleUp className="w-4 h-4" /> : <FaIcons.FaAngleDown className="w-4 h-4" />)}
|
|
470
483
|
</Link>
|
|
@@ -10,3 +10,28 @@ export const unauthorizedLayoutTemplates = [
|
|
|
10
10
|
),
|
|
11
11
|
},
|
|
12
12
|
]
|
|
13
|
+
export const layoutOptions = [
|
|
14
|
+
{ label: 'Centered', value: 'center' },
|
|
15
|
+
{ label: 'Image Left / Form Right', value: 'split-left' },
|
|
16
|
+
{ label: 'Form Left / Image Right', value: 'split-right' },
|
|
17
|
+
]
|
|
18
|
+
export const logoOptions = [
|
|
19
|
+
{ label: 'Top', value: 'top' },
|
|
20
|
+
{ label: 'Bottom', value: 'bottom' },
|
|
21
|
+
{ label: 'None', value: 'none' },
|
|
22
|
+
]
|
|
23
|
+
export const formStyleOptions = [
|
|
24
|
+
{ label: 'Full', value: 'full' },
|
|
25
|
+
{ label: 'Mini', value: 'mini' },
|
|
26
|
+
]
|
|
27
|
+
export const backgroundTypeOptions = [
|
|
28
|
+
{ label: 'Color', value: 'color' },
|
|
29
|
+
{ label: 'Image', value: 'image' },
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
export const formFieldOptions = [
|
|
33
|
+
{ label: 'Username', value: 'username' },
|
|
34
|
+
{ label: 'Email', value: 'email' },
|
|
35
|
+
{ label: 'Password', value: 'password' },
|
|
36
|
+
{ label: 'Forgot Password', value: 'forgot-password' },
|
|
37
|
+
]
|
package/src/types/forms/index.ts
CHANGED
|
@@ -20,6 +20,7 @@ export interface IFormDataListConfig {
|
|
|
20
20
|
listType: FormDataListViewTypeEnum
|
|
21
21
|
columns: IFormDataListColumn[]
|
|
22
22
|
header: IDndLayoutStructure_Responsive
|
|
23
|
+
joins?: IFormJoin[]
|
|
23
24
|
pagination?: IFormDataListPagination
|
|
24
25
|
title?: string
|
|
25
26
|
showCount?: boolean
|
|
@@ -53,3 +54,6 @@ export interface IFormJoin {
|
|
|
53
54
|
localField: string
|
|
54
55
|
alias: string
|
|
55
56
|
}
|
|
57
|
+
export interface IFormDataListData {
|
|
58
|
+
[key: string]: any
|
|
59
|
+
}
|