@performant-software/semantic-components 0.5.1 → 0.5.4
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/build/index.js +1 -1
- package/build/index.js.map +1 -1
- package/build/main.css +5 -0
- package/package.json +5 -5
- package/src/components/DataList.js +10 -2
- package/src/components/DataTableColumnSelector.js +37 -33
- package/src/components/DataView.css +4 -0
- package/src/components/DataView.js +407 -0
- package/src/components/MenuBar.css +0 -0
- package/src/components/MenuBar.js +82 -0
- package/src/components/MenuSidebar.css +0 -0
- package/src/components/MenuSidebar.js +84 -0
- package/src/components/ReferenceCodeDropdown.css +0 -0
- package/src/components/ReferenceCodeDropdown.js +118 -0
- package/src/components/ReferenceCodeModal.css +0 -0
- package/src/components/ReferenceCodeModal.js +32 -0
- package/src/components/ReferenceTableModal.css +0 -0
- package/src/components/ReferenceTableModal.js +67 -0
- package/src/components/ReferenceTablesList.css +0 -0
- package/src/components/ReferenceTablesList.js +44 -0
- package/src/i18n/en.json +32 -0
- package/src/index.js +7 -0
- package/types/components/DataList.js.flow +10 -2
- package/types/components/DataTableColumnSelector.js.flow +37 -33
- package/types/components/DataView.js.flow +302 -20
- package/types/components/MenuBar.js.flow +9 -4
- package/types/components/MenuSidebar.js.flow +18 -6
- package/types/components/ReferenceCodeDropdown.js.flow +118 -0
- package/types/components/ReferenceCodeModal.js.flow +32 -0
- package/types/components/ReferenceTableModal.js.flow +67 -0
- package/types/components/ReferenceTablesList.js.flow +44 -0
- package/types/index.js.flow +5 -0
package/build/main.css
CHANGED
|
@@ -231,6 +231,11 @@
|
|
|
231
231
|
.data-table.ui.celled.table tr td.actions-cell {
|
|
232
232
|
white-space: nowrap;
|
|
233
233
|
}
|
|
234
|
+
.data-view-modal.ui.form .field span.label {
|
|
235
|
+
display: block;
|
|
236
|
+
font-weight: bold;
|
|
237
|
+
}
|
|
238
|
+
|
|
234
239
|
.date-input.ui.icon.input > i.icon.right {
|
|
235
240
|
cursor: pointer;
|
|
236
241
|
pointer-events: inherit;
|
package/package.json
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@performant-software/semantic-components",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "A package of shared components based on the Semantic UI Framework.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "./build/index.js",
|
|
7
7
|
"style": "./build/styles.css",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "webpack --mode production && flow-copy-source -v src types"
|
|
10
|
-
"postinstall": "yarn build"
|
|
9
|
+
"build": "webpack --mode production && flow-copy-source -v src types"
|
|
11
10
|
},
|
|
12
11
|
"dependencies": {
|
|
13
|
-
"@performant-software/shared-components": "^0.5.
|
|
12
|
+
"@performant-software/shared-components": "^0.5.4",
|
|
14
13
|
"@react-google-maps/api": "^2.8.1",
|
|
14
|
+
"axios": "^0.26.1",
|
|
15
15
|
"flow-copy-source": "^2.0.9",
|
|
16
16
|
"i18next": "^19.4.4",
|
|
17
17
|
"react-calendar": "^3.3.0",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"react-dom": ">= 16.13.1 < 18.0.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"@performant-software/webpack-config": "^0.5.
|
|
33
|
+
"@performant-software/webpack-config": "^0.5.4",
|
|
34
34
|
"react": "^17.0.2",
|
|
35
35
|
"react-dom": "^17.0.2"
|
|
36
36
|
}
|
|
@@ -19,10 +19,10 @@ type Props = {
|
|
|
19
19
|
props?: any,
|
|
20
20
|
onChange?: (filter: any) => Promise<any>
|
|
21
21
|
},
|
|
22
|
-
onDelete
|
|
22
|
+
onDelete?: (item: any) => Promise<any>,
|
|
23
23
|
onDeleteAll?: () => Promise<any>,
|
|
24
24
|
onLoad: (params: any) => Promise<any>,
|
|
25
|
-
onSave
|
|
25
|
+
onSave?: (item: any) => Promise<any>,
|
|
26
26
|
perPageOptions?: Array<number>,
|
|
27
27
|
polling?: number,
|
|
28
28
|
resolveErrors?: (error: any) => Array<string>,
|
|
@@ -251,6 +251,10 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
|
|
|
251
251
|
* @returns {Q.Promise<any> | Promise<R> | Promise<any> | void | *}
|
|
252
252
|
*/
|
|
253
253
|
onDelete(selectedItem: any) {
|
|
254
|
+
if (!this.props.onDelete) {
|
|
255
|
+
return Promise.resolve();
|
|
256
|
+
}
|
|
257
|
+
|
|
254
258
|
return this.props
|
|
255
259
|
.onDelete(selectedItem)
|
|
256
260
|
.then(this.afterDelete.bind(this))
|
|
@@ -333,6 +337,10 @@ const useDataList = (WrappedComponent: ComponentType<any>) => (
|
|
|
333
337
|
* @returns {Q.Promise<any> | Promise<R> | Promise<any> | void | *}
|
|
334
338
|
*/
|
|
335
339
|
onSave(item: any) {
|
|
340
|
+
if (!this.props.onSave) {
|
|
341
|
+
return Promise.resolve();
|
|
342
|
+
}
|
|
343
|
+
|
|
336
344
|
return Promise.resolve(this.props.onSave(item))
|
|
337
345
|
.then(() => this.setState({ saved: true }, this.fetchData.bind(this)));
|
|
338
346
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @flow
|
|
2
2
|
|
|
3
|
-
import React, { Component, type ComponentType } from 'react';
|
|
3
|
+
import React, { Component, type ComponentType, type Element } from 'react';
|
|
4
4
|
import { Checkbox, Dropdown, Icon } from 'semantic-ui-react';
|
|
5
5
|
import _ from 'underscore';
|
|
6
6
|
import Draggable from './Draggable';
|
|
@@ -10,7 +10,8 @@ import type { Column } from './DataTable';
|
|
|
10
10
|
|
|
11
11
|
type Props = {
|
|
12
12
|
className: string,
|
|
13
|
-
columns: Array<Column
|
|
13
|
+
columns: Array<Column>,
|
|
14
|
+
renderListHeader?: () => Element<any>
|
|
14
15
|
};
|
|
15
16
|
|
|
16
17
|
type State = {
|
|
@@ -107,37 +108,40 @@ const useColumnSelector = (WrappedComponent: ComponentType<any>) => (
|
|
|
107
108
|
*/
|
|
108
109
|
renderHeader() {
|
|
109
110
|
return (
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
111
|
+
<>
|
|
112
|
+
{ this.props.renderListHeader && this.props.renderListHeader() }
|
|
113
|
+
<Dropdown
|
|
114
|
+
basic
|
|
115
|
+
button
|
|
116
|
+
icon='cog'
|
|
117
|
+
className='icon configure-button open-right'
|
|
118
|
+
simple
|
|
119
|
+
>
|
|
120
|
+
<Dropdown.Menu>
|
|
121
|
+
{ this.state.columns
|
|
122
|
+
.filter((c) => c.label && c.label.length)
|
|
123
|
+
.map((c, index) => (
|
|
124
|
+
<Draggable
|
|
125
|
+
id={c.name}
|
|
126
|
+
index={index}
|
|
127
|
+
key={c.name}
|
|
128
|
+
onDrag={this.onDrag.bind(this)}
|
|
129
|
+
>
|
|
130
|
+
<Dropdown.Item>
|
|
131
|
+
<Icon
|
|
132
|
+
name='bars'
|
|
133
|
+
/>
|
|
134
|
+
<Checkbox
|
|
135
|
+
checked={!c.hidden}
|
|
136
|
+
label={c.label}
|
|
137
|
+
onClick={this.onColumnCheckbox.bind(this, c)}
|
|
138
|
+
/>
|
|
139
|
+
</Dropdown.Item>
|
|
140
|
+
</Draggable>
|
|
141
|
+
))}
|
|
142
|
+
</Dropdown.Menu>
|
|
143
|
+
</Dropdown>
|
|
144
|
+
</>
|
|
141
145
|
);
|
|
142
146
|
}
|
|
143
147
|
}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import axios from 'axios';
|
|
4
|
+
import React, {
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
useRef,
|
|
9
|
+
useState
|
|
10
|
+
} from 'react';
|
|
11
|
+
import { Form, Grid, Modal } from 'semantic-ui-react';
|
|
12
|
+
import _ from 'underscore';
|
|
13
|
+
import DataTable from './DataTable';
|
|
14
|
+
import DropdownButton from './DropdownButton';
|
|
15
|
+
import i18n from '../i18n/i18n';
|
|
16
|
+
import MenuBar from './MenuBar';
|
|
17
|
+
import MenuSidebar from './MenuSidebar';
|
|
18
|
+
import useDataList, { SORT_ASCENDING, SORT_DESCENDING } from './DataList';
|
|
19
|
+
import './DataView.css';
|
|
20
|
+
|
|
21
|
+
import type { Column } from './DataTable';
|
|
22
|
+
|
|
23
|
+
type Sort = {
|
|
24
|
+
label: string,
|
|
25
|
+
value: string
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
type Item = {
|
|
29
|
+
id: string,
|
|
30
|
+
label: string,
|
|
31
|
+
group?: string,
|
|
32
|
+
columns?: Array<Column>,
|
|
33
|
+
sorts?: Array<Sort>
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
type Props = {
|
|
37
|
+
columnCount?: number,
|
|
38
|
+
items: Array<Item>,
|
|
39
|
+
layout: 'top' | 'left',
|
|
40
|
+
title: string
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Sets up the List component used by the DataView component. This component is responsible for rendering the DataTable
|
|
45
|
+
* and handling sorting.
|
|
46
|
+
*/
|
|
47
|
+
const List = useDataList((props) => {
|
|
48
|
+
const [activeSort, setActiveSort] = useState();
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Sets the active sort object on the state.
|
|
52
|
+
*
|
|
53
|
+
* @type {(function(*): void)|*}
|
|
54
|
+
*/
|
|
55
|
+
const onSortChange = useCallback((sort) => {
|
|
56
|
+
const newSort = { ...sort };
|
|
57
|
+
|
|
58
|
+
if (activeSort && activeSort.value === sort.value) {
|
|
59
|
+
newSort.direction = activeSort.direction === SORT_DESCENDING ? SORT_ASCENDING : SORT_DESCENDING;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setActiveSort(newSort);
|
|
63
|
+
}, [props.sorts, activeSort]);
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Calls the onSort prop when the active sort is changed.
|
|
67
|
+
*/
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (activeSort) {
|
|
70
|
+
props.onSort(activeSort.value, activeSort.direction, props.page);
|
|
71
|
+
}
|
|
72
|
+
}, [activeSort]);
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Calls the onSort prop when the component is loaded to initialize the data set.
|
|
76
|
+
*/
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const { page } = props;
|
|
79
|
+
|
|
80
|
+
let { sortColumn = '', sortDirection = SORT_ASCENDING } = props;
|
|
81
|
+
|
|
82
|
+
if (!sortColumn) {
|
|
83
|
+
const defaultSort = _.first(props.sort);
|
|
84
|
+
|
|
85
|
+
if (defaultSort) {
|
|
86
|
+
sortColumn = defaultSort.value;
|
|
87
|
+
|
|
88
|
+
if (defaultSort.direction) {
|
|
89
|
+
sortDirection = defaultSort.direction;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
props.onSort(sortColumn, sortDirection, page);
|
|
95
|
+
}, []);
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<DataTable
|
|
99
|
+
{...props}
|
|
100
|
+
renderListHeader={() => props.sorts && (
|
|
101
|
+
<DropdownButton
|
|
102
|
+
basic
|
|
103
|
+
icon={activeSort && activeSort.direction === SORT_DESCENDING
|
|
104
|
+
? 'sort alphabet down'
|
|
105
|
+
: 'sort alphabet up'}
|
|
106
|
+
onChange={() => {}}
|
|
107
|
+
options={_.map(props.sorts, (sort) => ({
|
|
108
|
+
key: sort.value,
|
|
109
|
+
value: sort.value,
|
|
110
|
+
text: sort.label,
|
|
111
|
+
onClick: () => onSortChange(sort)
|
|
112
|
+
}))}
|
|
113
|
+
text={activeSort
|
|
114
|
+
? i18n.t('DataView.labels.sortBy', { column: activeSort.label })
|
|
115
|
+
: i18n.t('DataView.labels.noSort')}
|
|
116
|
+
value={activeSort && activeSort.value}
|
|
117
|
+
/>
|
|
118
|
+
)}
|
|
119
|
+
tableProps={{
|
|
120
|
+
...props.tableProps || {},
|
|
121
|
+
celled: true
|
|
122
|
+
}}
|
|
123
|
+
/>
|
|
124
|
+
);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
const DataView = (props: Props) => {
|
|
128
|
+
const [activeItem, setActiveItem] = useState();
|
|
129
|
+
const [selectedRecord, setSelectedRecord] = useState();
|
|
130
|
+
|
|
131
|
+
const [columns, setColumns] = useState([]);
|
|
132
|
+
const [menu, setMenu] = useState([]);
|
|
133
|
+
const [paddingLeft, setPaddingLeft] = useState();
|
|
134
|
+
const [key, setKey] = useState();
|
|
135
|
+
|
|
136
|
+
const menuRef = useRef();
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Sets the collection name based on the URL of the active item.
|
|
140
|
+
*
|
|
141
|
+
* @type {string}
|
|
142
|
+
*/
|
|
143
|
+
const collectionName = useMemo(() => {
|
|
144
|
+
let name = '';
|
|
145
|
+
|
|
146
|
+
if (activeItem) {
|
|
147
|
+
name = activeItem.url.substr(activeItem.url.lastIndexOf('/') + 1, activeItem.url.length);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return name;
|
|
151
|
+
}, [activeItem]);
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Merges the calculated columns with the columns passed with the active item. The idea is to allow customization
|
|
155
|
+
* of the columns by the implementing component, but not require it.
|
|
156
|
+
*
|
|
157
|
+
* @type {(function(*): ([]|*))|*}
|
|
158
|
+
*/
|
|
159
|
+
const mergeColumns = useCallback((items) => {
|
|
160
|
+
if (!activeItem) {
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Build the list of columns based on the unique set of properties
|
|
165
|
+
const keys = _.chain(items)
|
|
166
|
+
.map((item) => _.keys(item))
|
|
167
|
+
.flatten()
|
|
168
|
+
.uniq()
|
|
169
|
+
.value();
|
|
170
|
+
|
|
171
|
+
// Merge any columns provided by the implementing component by name
|
|
172
|
+
let cols = _.map(keys, (col, index) => {
|
|
173
|
+
const columnValue = _.findWhere(activeItem.columns, { name: col }) || {};
|
|
174
|
+
|
|
175
|
+
const defaultValue = {
|
|
176
|
+
name: col,
|
|
177
|
+
label: col.trim().replace(/^\w/, (c) => c.toUpperCase()).replaceAll('_', ' '),
|
|
178
|
+
sortable: false,
|
|
179
|
+
resolve: (item) => resolveValue(item, col),
|
|
180
|
+
hidden: index > props.columnCount,
|
|
181
|
+
index
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
return _.extend(defaultValue, columnValue);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Include any columns provided by the implementing component that do not exist on the object (i.e. extra columns)
|
|
188
|
+
cols = [
|
|
189
|
+
...cols,
|
|
190
|
+
..._.reject(activeItem.columns, (col) => _.contains(keys, col.name))
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
// Sort the columns by the index property
|
|
194
|
+
return _.sortBy(cols, 'index');
|
|
195
|
+
}, [activeItem]);
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Calls the "show" URL for the passed item. It's possible that the API does not implement a public "show" route. In
|
|
199
|
+
* this case and error is expected and the passed item will be set as the selected record.
|
|
200
|
+
*
|
|
201
|
+
* @type {(function(*): void)|*}
|
|
202
|
+
*/
|
|
203
|
+
const onItemSelection = useCallback((item) => {
|
|
204
|
+
if (activeItem) {
|
|
205
|
+
axios
|
|
206
|
+
.get(`${activeItem.url}/${item.id}`)
|
|
207
|
+
.then(({ data }) => {
|
|
208
|
+
const itemKey = _.first(_.keys(data));
|
|
209
|
+
setSelectedRecord(data[itemKey]);
|
|
210
|
+
})
|
|
211
|
+
.catch(() => {
|
|
212
|
+
setSelectedRecord(item);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
}, [activeItem]);
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Calls the "index" URL to initialize the data set.
|
|
219
|
+
*
|
|
220
|
+
* @type {function(*): *}
|
|
221
|
+
*/
|
|
222
|
+
const onLoad = useCallback((params) => {
|
|
223
|
+
let promise;
|
|
224
|
+
|
|
225
|
+
if (activeItem) {
|
|
226
|
+
promise = axios
|
|
227
|
+
.get(activeItem.url, { params })
|
|
228
|
+
.then((response) => {
|
|
229
|
+
const items = response.data[collectionName];
|
|
230
|
+
setColumns(mergeColumns(items));
|
|
231
|
+
|
|
232
|
+
return response;
|
|
233
|
+
});
|
|
234
|
+
} else {
|
|
235
|
+
promise = Promise.resolve();
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return promise;
|
|
239
|
+
}, [activeItem]);
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Returns the value of the passed attribute on the passed item. This function handles use cases for special
|
|
243
|
+
* types of objects.
|
|
244
|
+
*
|
|
245
|
+
* @type {function(*, *): *}
|
|
246
|
+
*/
|
|
247
|
+
const resolveValue = useCallback((item, attribute) => {
|
|
248
|
+
let value = item[attribute];
|
|
249
|
+
|
|
250
|
+
if (_.isArray(value)) {
|
|
251
|
+
value = _.size(value);
|
|
252
|
+
} else if (_.isBoolean(value)) {
|
|
253
|
+
value = Boolean(value).toString();
|
|
254
|
+
} else if (_.isObject(value)) {
|
|
255
|
+
value = value[_.first(_.keys(value))];
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return value;
|
|
259
|
+
}, []);
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Re-formats the passed items as a menu object. If we're grouping the menu items, items will be added as
|
|
263
|
+
* a child property of the group object.
|
|
264
|
+
*/
|
|
265
|
+
useEffect(() => {
|
|
266
|
+
const hasGroups = _.every(props.items, (item) => !!item.group);
|
|
267
|
+
|
|
268
|
+
if (!hasGroups) {
|
|
269
|
+
setMenu(props.items);
|
|
270
|
+
} else {
|
|
271
|
+
const menuItems = _.groupBy(props.items, 'group');
|
|
272
|
+
const headers = _.keys(menuItems);
|
|
273
|
+
|
|
274
|
+
setMenu(_.map(headers, (header) => ({
|
|
275
|
+
content: header,
|
|
276
|
+
items: _.map(menuItems[header], (item) => ({
|
|
277
|
+
active: activeItem && activeItem.id === item.id,
|
|
278
|
+
content: item.label,
|
|
279
|
+
onClick: () => setActiveItem(item)
|
|
280
|
+
}))
|
|
281
|
+
})));
|
|
282
|
+
}
|
|
283
|
+
}, [activeItem, props.items]);
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Set the active item as the first item in the list.
|
|
287
|
+
*/
|
|
288
|
+
useEffect(() => {
|
|
289
|
+
if (props.items && props.items.length) {
|
|
290
|
+
setActiveItem(_.first(props.items));
|
|
291
|
+
}
|
|
292
|
+
}, [props.items]);
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* If we're using the sidebar layout, set the padding so the fixed positioned menu does not overlap the content.
|
|
296
|
+
*/
|
|
297
|
+
useEffect(() => {
|
|
298
|
+
if (props.layout === 'left' && menuRef && menuRef.current) {
|
|
299
|
+
setPaddingLeft(menuRef.current.offsetWidth);
|
|
300
|
+
}
|
|
301
|
+
}, [menuRef, props.layout]);
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Calculates the key value when the active item or columns are changed. This is done to force re-rendering
|
|
305
|
+
* of the List component when the columns are loaded via the API.
|
|
306
|
+
*/
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
const keys = [];
|
|
309
|
+
|
|
310
|
+
if (activeItem) {
|
|
311
|
+
keys.push(activeItem.id);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (columns && columns.length) {
|
|
315
|
+
keys.push(..._.pluck(columns, 'name'));
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
setKey(keys.join(''));
|
|
319
|
+
}, [activeItem, columns]);
|
|
320
|
+
|
|
321
|
+
return (
|
|
322
|
+
<div
|
|
323
|
+
className='data-view'
|
|
324
|
+
>
|
|
325
|
+
{ props.layout === 'top' && (
|
|
326
|
+
<MenuBar
|
|
327
|
+
header={{
|
|
328
|
+
content: props.title
|
|
329
|
+
}}
|
|
330
|
+
items={menu}
|
|
331
|
+
/>
|
|
332
|
+
)}
|
|
333
|
+
{ props.layout === 'left' && (
|
|
334
|
+
<MenuSidebar
|
|
335
|
+
contextRef={menuRef}
|
|
336
|
+
header={{
|
|
337
|
+
content: props.title
|
|
338
|
+
}}
|
|
339
|
+
items={menu}
|
|
340
|
+
/>
|
|
341
|
+
)}
|
|
342
|
+
<div
|
|
343
|
+
style={{
|
|
344
|
+
marginLeft: paddingLeft
|
|
345
|
+
}}
|
|
346
|
+
>
|
|
347
|
+
{ activeItem && (
|
|
348
|
+
<List
|
|
349
|
+
actions={[{
|
|
350
|
+
name: 'details',
|
|
351
|
+
icon: 'info',
|
|
352
|
+
onClick: onItemSelection
|
|
353
|
+
}]}
|
|
354
|
+
collectionName={collectionName}
|
|
355
|
+
columns={columns}
|
|
356
|
+
key={key}
|
|
357
|
+
onLoad={onLoad}
|
|
358
|
+
perPageOptions={[10, 25, 50, 100]}
|
|
359
|
+
searchable
|
|
360
|
+
sorts={activeItem.sorts}
|
|
361
|
+
/>
|
|
362
|
+
)}
|
|
363
|
+
</div>
|
|
364
|
+
{ selectedRecord && (
|
|
365
|
+
<Modal
|
|
366
|
+
as={Form}
|
|
367
|
+
centered={false}
|
|
368
|
+
className='data-view-modal'
|
|
369
|
+
closeIcon
|
|
370
|
+
onClose={() => setSelectedRecord(null)}
|
|
371
|
+
open
|
|
372
|
+
>
|
|
373
|
+
<Modal.Header
|
|
374
|
+
content={i18n.t('DataView.labels.details')}
|
|
375
|
+
/>
|
|
376
|
+
<Modal.Content>
|
|
377
|
+
<Grid
|
|
378
|
+
columns={3}
|
|
379
|
+
doubling
|
|
380
|
+
>
|
|
381
|
+
{ _.map(mergeColumns([selectedRecord]), (column) => (
|
|
382
|
+
<Grid.Column
|
|
383
|
+
as={Form.Field}
|
|
384
|
+
key={column.name}
|
|
385
|
+
>
|
|
386
|
+
<span
|
|
387
|
+
className='label'
|
|
388
|
+
>
|
|
389
|
+
{ column.label }
|
|
390
|
+
</span>
|
|
391
|
+
{ resolveValue(selectedRecord, column.name) }
|
|
392
|
+
</Grid.Column>
|
|
393
|
+
))}
|
|
394
|
+
</Grid>
|
|
395
|
+
</Modal.Content>
|
|
396
|
+
</Modal>
|
|
397
|
+
)}
|
|
398
|
+
</div>
|
|
399
|
+
);
|
|
400
|
+
};
|
|
401
|
+
|
|
402
|
+
DataView.defaultProps = {
|
|
403
|
+
columnCount: 5,
|
|
404
|
+
layout: 'left'
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
export default DataView;
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import React, { useCallback } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Dropdown,
|
|
6
|
+
Menu,
|
|
7
|
+
type DropdownItemProps,
|
|
8
|
+
type HeaderProps,
|
|
9
|
+
type MenuProps
|
|
10
|
+
} from 'semantic-ui-react';
|
|
11
|
+
import _ from 'underscore';
|
|
12
|
+
import DropdownMenu from './DropdownMenu';
|
|
13
|
+
|
|
14
|
+
type Props = MenuProps & {
|
|
15
|
+
header: HeaderProps,
|
|
16
|
+
items: Array<DropdownItemProps | MenuProps>
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const MenuBar = ({ header, items, ...props }: Props) => {
|
|
20
|
+
/**
|
|
21
|
+
* Renders the passed item as a dropdown item.
|
|
22
|
+
*
|
|
23
|
+
* @param item
|
|
24
|
+
*
|
|
25
|
+
* @returns {JSX.Element}
|
|
26
|
+
*/
|
|
27
|
+
const renderDropdownItem = useCallback((item, index) => (
|
|
28
|
+
<Dropdown.Item
|
|
29
|
+
key={index}
|
|
30
|
+
{...item}
|
|
31
|
+
/>
|
|
32
|
+
), []);
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Renders the passed item as a menu item.
|
|
36
|
+
*
|
|
37
|
+
* @returns {JSX.Element}
|
|
38
|
+
*/
|
|
39
|
+
const renderMenuItem = useCallback((item, index) => (
|
|
40
|
+
<Menu.Item
|
|
41
|
+
key={index}
|
|
42
|
+
{...item}
|
|
43
|
+
/>
|
|
44
|
+
), []);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Renders the passed item as a dropdown menu.
|
|
48
|
+
*
|
|
49
|
+
* @returns {JSX.Element}
|
|
50
|
+
*/
|
|
51
|
+
const renderDropdown = useCallback((item, index) => (
|
|
52
|
+
<DropdownMenu
|
|
53
|
+
item
|
|
54
|
+
key={index}
|
|
55
|
+
text={item.content}
|
|
56
|
+
>
|
|
57
|
+
{ _.map(item.items, (i) => (i.items ? renderDropdown(i) : renderDropdownItem(i)))}
|
|
58
|
+
</DropdownMenu>
|
|
59
|
+
), []);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Renders the passed item.
|
|
63
|
+
*
|
|
64
|
+
* @returns {JSX.Element}
|
|
65
|
+
*/
|
|
66
|
+
const renderItem = useCallback((item) => (item.items ? renderDropdown(item) : renderMenuItem(item)), []);
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
<Menu
|
|
70
|
+
{...props}
|
|
71
|
+
className='menu-bar'
|
|
72
|
+
>
|
|
73
|
+
<Menu.Item
|
|
74
|
+
{...header}
|
|
75
|
+
header
|
|
76
|
+
/>
|
|
77
|
+
{ _.map(items, (item) => renderItem(item)) }
|
|
78
|
+
</Menu>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
export default MenuBar;
|
|
File without changes
|