dce-reactkit 3.2.1 → 3.2.2-beta.10
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/.vscode/settings.json +3 -0
- package/dist/cjs/index.js +2195 -442
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/cjs/types/components/CSVDownloadButton.d.ts +19 -0
- package/dist/cjs/types/components/IntelliTable.d.ts +17 -0
- package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/cjs/types/components/LogReviewer.d.ts +13 -0
- package/dist/cjs/types/components/SimpleDateChooser.d.ts +1 -0
- package/dist/cjs/types/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.d.ts +6 -0
- package/dist/cjs/types/constants/LOG_REVIEW_STATUS_ROUTE.d.ts +7 -0
- package/dist/cjs/types/helpers/canReviewLogs.d.ts +7 -0
- package/dist/cjs/types/helpers/genCSV.d.ts +14 -0
- package/dist/cjs/types/helpers/getMonthName.d.ts +14 -0
- package/dist/cjs/types/index.d.ts +9 -1
- package/dist/cjs/types/server/initServer.d.ts +8 -0
- package/dist/cjs/types/types/IntelliTableColumn.d.ts +12 -0
- package/dist/cjs/types/types/LogBuiltInMetadata.d.ts +1 -0
- package/dist/cjs/types/types/LogMetadataType.d.ts +19 -0
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +2 -1
- package/dist/esm/index.js +2191 -444
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/esm/types/components/CSVDownloadButton.d.ts +19 -0
- package/dist/esm/types/components/IntelliTable.d.ts +17 -0
- package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/esm/types/components/LogReviewer.d.ts +13 -0
- package/dist/esm/types/components/SimpleDateChooser.d.ts +1 -0
- package/dist/esm/types/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.d.ts +6 -0
- package/dist/esm/types/constants/LOG_REVIEW_STATUS_ROUTE.d.ts +7 -0
- package/dist/esm/types/helpers/canReviewLogs.d.ts +7 -0
- package/dist/esm/types/helpers/genCSV.d.ts +14 -0
- package/dist/esm/types/helpers/getMonthName.d.ts +14 -0
- package/dist/esm/types/index.d.ts +9 -1
- package/dist/esm/types/server/initServer.d.ts +8 -0
- package/dist/esm/types/types/IntelliTableColumn.d.ts +12 -0
- package/dist/esm/types/types/LogBuiltInMetadata.d.ts +1 -0
- package/dist/esm/types/types/LogMetadataType.d.ts +19 -0
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +2 -1
- package/dist/index.d.ts +172 -47
- package/package.json +1 -1
- package/sandbox.js +78 -17
- package/src/components/AppWrapper.tsx +15 -0
- package/src/components/ButtonInputGroup.tsx +4 -1
- package/src/components/CSVDownloadButton.tsx +102 -0
- package/src/components/IntelliTable.tsx +610 -0
- package/src/components/ItemPicker/index.tsx +4 -0
- package/src/components/LogReviewer.tsx +2091 -0
- package/src/components/SimpleDateChooser.tsx +26 -27
- package/src/constants/LOG_REVIEW_ROUTE_PATH_PREFIX.ts +9 -0
- package/src/constants/LOG_REVIEW_STATUS_ROUTE.ts +10 -0
- package/src/helpers/canReviewLogs.ts +33 -0
- package/src/helpers/genCSV.ts +59 -0
- package/src/helpers/getHumanReadableDate.ts +6 -17
- package/src/helpers/getMonthName.ts +29 -0
- package/src/helpers/logClientEvent.tsx +5 -0
- package/src/index.ts +16 -0
- package/src/server/initServer.ts +125 -3
- package/src/types/IntelliTableColumn.ts +24 -0
- package/src/types/LogBuiltInMetadata.ts +1 -0
- package/src/types/LogMetadataType.ts +23 -0
- package/src/types/ReactKitErrorCode.tsx +2 -1
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Intelligent table
|
|
3
|
+
* @author Gabe Abrams
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import React
|
|
7
|
+
import React, { useReducer } from 'react';
|
|
8
|
+
|
|
9
|
+
// Import FontAwesome
|
|
10
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
11
|
+
import {
|
|
12
|
+
faSort,
|
|
13
|
+
faSortUp,
|
|
14
|
+
faSortDown,
|
|
15
|
+
faCheckCircle,
|
|
16
|
+
faXmarkCircle,
|
|
17
|
+
faMinus,
|
|
18
|
+
} from '@fortawesome/free-solid-svg-icons';
|
|
19
|
+
|
|
20
|
+
// Import shared types
|
|
21
|
+
import ParamType from '../types/ParamType';
|
|
22
|
+
import IntelliTableColumn from '../types/IntelliTableColumn';
|
|
23
|
+
|
|
24
|
+
// Import shared helpers
|
|
25
|
+
import roundToNumDecimals from '../helpers/roundToNumDecimals';
|
|
26
|
+
import genCSV from '../helpers/genCSV';
|
|
27
|
+
|
|
28
|
+
// Import shared components
|
|
29
|
+
import Modal from './Modal';
|
|
30
|
+
import ModalType from '../types/ModalType';
|
|
31
|
+
import CheckboxButton from './CheckboxButton';
|
|
32
|
+
import CSVDownloadButton from './CSVDownloadButton';
|
|
33
|
+
|
|
34
|
+
/*------------------------------------------------------------------------*/
|
|
35
|
+
/* Types */
|
|
36
|
+
/*------------------------------------------------------------------------*/
|
|
37
|
+
|
|
38
|
+
// Props definition
|
|
39
|
+
type Props = {
|
|
40
|
+
// Title of the table
|
|
41
|
+
title: string,
|
|
42
|
+
// Unique hyphenated id of the table (e.g. table-name)
|
|
43
|
+
id: string,
|
|
44
|
+
// Ordered list of data objects
|
|
45
|
+
data: {
|
|
46
|
+
// Required id column
|
|
47
|
+
id: string | number,
|
|
48
|
+
// Rest of data
|
|
49
|
+
[k: string]: any
|
|
50
|
+
}[],
|
|
51
|
+
// Column information
|
|
52
|
+
columns: IntelliTableColumn[],
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Sort types
|
|
56
|
+
enum SortType {
|
|
57
|
+
// Ascending
|
|
58
|
+
Ascending = 'ascending',
|
|
59
|
+
// Descending
|
|
60
|
+
Descending = 'descending',
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/*------------------------------------------------------------------------*/
|
|
64
|
+
/* State */
|
|
65
|
+
/*------------------------------------------------------------------------*/
|
|
66
|
+
|
|
67
|
+
/* -------- State Definition -------- */
|
|
68
|
+
|
|
69
|
+
type State = {
|
|
70
|
+
// Current sort column (or undefined for data order sort)
|
|
71
|
+
sortColumnParam: (undefined | string),
|
|
72
|
+
// Sort type
|
|
73
|
+
sortType: SortType,
|
|
74
|
+
// Map of which columns are visible
|
|
75
|
+
columnVisibilityMap: { // param => true if visible
|
|
76
|
+
[k: string]: boolean,
|
|
77
|
+
},
|
|
78
|
+
// If true, the column visibility customization modal is visible
|
|
79
|
+
columnVisibilityCustomizationModalVisible: boolean,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/* ------------- Actions ------------ */
|
|
83
|
+
|
|
84
|
+
// Types of actions
|
|
85
|
+
enum ActionType {
|
|
86
|
+
// Toggle sort column param
|
|
87
|
+
ToggleSortColumn = 'toggle-sort-column',
|
|
88
|
+
// Toggle the visibility of a column
|
|
89
|
+
ToggleColumnVisibility = 'toggle-column-visibility',
|
|
90
|
+
// Toggle the column visibility customization modal
|
|
91
|
+
ToggleColVisCusModalVisibility = 'toggle-col-vis-cus-modal-visibility',
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Action definitions
|
|
95
|
+
type Action = (
|
|
96
|
+
| {
|
|
97
|
+
// Action type
|
|
98
|
+
type: ActionType.ToggleSortColumn,
|
|
99
|
+
// Param for the column to toggle
|
|
100
|
+
param: string,
|
|
101
|
+
}
|
|
102
|
+
| {
|
|
103
|
+
// Action type
|
|
104
|
+
type: ActionType.ToggleColumnVisibility,
|
|
105
|
+
// Param for the column to toggle
|
|
106
|
+
param: string,
|
|
107
|
+
}
|
|
108
|
+
| {
|
|
109
|
+
// Action type
|
|
110
|
+
type: (
|
|
111
|
+
| ActionType.ToggleColVisCusModalVisibility
|
|
112
|
+
),
|
|
113
|
+
}
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Reducer that executes actions
|
|
118
|
+
* @author Gabe Abrams
|
|
119
|
+
* @param state current state
|
|
120
|
+
* @param action action to execute
|
|
121
|
+
*/
|
|
122
|
+
const reducer = (state: State, action: Action): State => {
|
|
123
|
+
switch (action.type) {
|
|
124
|
+
case ActionType.ToggleSortColumn: {
|
|
125
|
+
if (action.param !== state.sortColumnParam) {
|
|
126
|
+
// Different column param
|
|
127
|
+
return {
|
|
128
|
+
...state,
|
|
129
|
+
sortColumnParam: action.param,
|
|
130
|
+
sortType: SortType.Ascending,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
if (state.sortType === SortType.Ascending) {
|
|
134
|
+
// Switch to descending
|
|
135
|
+
return {
|
|
136
|
+
...state,
|
|
137
|
+
sortType: SortType.Descending,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// Stop sorting by column
|
|
141
|
+
return {
|
|
142
|
+
...state,
|
|
143
|
+
sortColumnParam: undefined,
|
|
144
|
+
sortType: SortType.Ascending,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
case ActionType.ToggleColumnVisibility: {
|
|
148
|
+
const { columnVisibilityMap } = state;
|
|
149
|
+
columnVisibilityMap[action.param] = !columnVisibilityMap[action.param];
|
|
150
|
+
return {
|
|
151
|
+
...state,
|
|
152
|
+
columnVisibilityMap,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
case ActionType.ToggleColVisCusModalVisibility: {
|
|
156
|
+
return {
|
|
157
|
+
...state,
|
|
158
|
+
columnVisibilityCustomizationModalVisible: !state.columnVisibilityCustomizationModalVisible,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
default: {
|
|
162
|
+
return state;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/*------------------------------------------------------------------------*/
|
|
168
|
+
/* Component */
|
|
169
|
+
/*------------------------------------------------------------------------*/
|
|
170
|
+
|
|
171
|
+
const IntelliTable: React.FC<Props> = (props) => {
|
|
172
|
+
/*------------------------------------------------------------------------*/
|
|
173
|
+
/* Setup */
|
|
174
|
+
/*------------------------------------------------------------------------*/
|
|
175
|
+
|
|
176
|
+
/* -------------- Props ------------- */
|
|
177
|
+
|
|
178
|
+
// Destructure all props
|
|
179
|
+
const {
|
|
180
|
+
title,
|
|
181
|
+
id,
|
|
182
|
+
data,
|
|
183
|
+
columns,
|
|
184
|
+
} = props;
|
|
185
|
+
|
|
186
|
+
/* -------------- State ------------- */
|
|
187
|
+
|
|
188
|
+
// Initial state
|
|
189
|
+
const initColumnVisibilityMap: {
|
|
190
|
+
[k: string]: boolean
|
|
191
|
+
} = {};
|
|
192
|
+
columns.forEach((column) => {
|
|
193
|
+
initColumnVisibilityMap[column.param] = !column.startsHidden;
|
|
194
|
+
});
|
|
195
|
+
const initialState: State = {
|
|
196
|
+
sortColumnParam: undefined,
|
|
197
|
+
sortType: SortType.Descending,
|
|
198
|
+
columnVisibilityMap: initColumnVisibilityMap,
|
|
199
|
+
columnVisibilityCustomizationModalVisible: false,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
// Initialize state
|
|
203
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
204
|
+
|
|
205
|
+
// Destructure common state
|
|
206
|
+
const {
|
|
207
|
+
sortColumnParam,
|
|
208
|
+
sortType,
|
|
209
|
+
columnVisibilityMap,
|
|
210
|
+
columnVisibilityCustomizationModalVisible,
|
|
211
|
+
} = state;
|
|
212
|
+
|
|
213
|
+
/*------------------------------------------------------------------------*/
|
|
214
|
+
/* Render */
|
|
215
|
+
/*------------------------------------------------------------------------*/
|
|
216
|
+
|
|
217
|
+
/*----------------------------------------*/
|
|
218
|
+
/* Modal */
|
|
219
|
+
/*----------------------------------------*/
|
|
220
|
+
|
|
221
|
+
// Modal that may be defined
|
|
222
|
+
let modal: React.ReactNode;
|
|
223
|
+
|
|
224
|
+
/* ------- Col Vis Customization Modal ------ */
|
|
225
|
+
|
|
226
|
+
if (columnVisibilityCustomizationModalVisible) {
|
|
227
|
+
// Create modal
|
|
228
|
+
modal = (
|
|
229
|
+
<Modal
|
|
230
|
+
type={ModalType.Okay}
|
|
231
|
+
title="Choose columns to show:"
|
|
232
|
+
onClose={() => {
|
|
233
|
+
dispatch({
|
|
234
|
+
type: ActionType.ToggleColVisCusModalVisibility,
|
|
235
|
+
});
|
|
236
|
+
}}
|
|
237
|
+
>
|
|
238
|
+
{
|
|
239
|
+
columns.map((column) => {
|
|
240
|
+
return (
|
|
241
|
+
<CheckboxButton
|
|
242
|
+
key={column.param}
|
|
243
|
+
id={`IntelliTable-${id}-toggle-visibility-${column.param}`}
|
|
244
|
+
text={column.title}
|
|
245
|
+
onChanged={() => {
|
|
246
|
+
dispatch({
|
|
247
|
+
type: ActionType.ToggleColumnVisibility,
|
|
248
|
+
param: column.param,
|
|
249
|
+
});
|
|
250
|
+
}}
|
|
251
|
+
checked={columnVisibilityMap[column.param]}
|
|
252
|
+
ariaLabel={`show "${column.title}" column`}
|
|
253
|
+
/>
|
|
254
|
+
);
|
|
255
|
+
})
|
|
256
|
+
}
|
|
257
|
+
</Modal>
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/*----------------------------------------*/
|
|
262
|
+
/* Main UI */
|
|
263
|
+
/*----------------------------------------*/
|
|
264
|
+
|
|
265
|
+
// Table header
|
|
266
|
+
const headerCells = (
|
|
267
|
+
columns
|
|
268
|
+
.filter((column) => {
|
|
269
|
+
return columnVisibilityMap[column.param];
|
|
270
|
+
})
|
|
271
|
+
.map((column) => {
|
|
272
|
+
// Custom info based on current sort type
|
|
273
|
+
let sortButtonAriaLabel;
|
|
274
|
+
let sortIcon = faSort;
|
|
275
|
+
if (!sortColumnParam) {
|
|
276
|
+
// Not being sorted yet
|
|
277
|
+
sortButtonAriaLabel = `sort ascending by ${column.title}`;
|
|
278
|
+
sortIcon = faSort;
|
|
279
|
+
} else if (column.param === sortColumnParam) {
|
|
280
|
+
// Already sorted by this column
|
|
281
|
+
if (sortType === SortType.Ascending) {
|
|
282
|
+
// Sorted ascending
|
|
283
|
+
sortButtonAriaLabel = `sort descending by ${column.title}`;
|
|
284
|
+
sortIcon = faSortDown;
|
|
285
|
+
} else {
|
|
286
|
+
// Sorted descending
|
|
287
|
+
sortButtonAriaLabel = `stop sorting by ${column.title}`;
|
|
288
|
+
sortIcon = faSortUp;
|
|
289
|
+
}
|
|
290
|
+
} else {
|
|
291
|
+
// Sorted by a different column
|
|
292
|
+
sortButtonAriaLabel = `sort ascending by ${column.title}`;
|
|
293
|
+
sortIcon = faSort;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Create the cell UI
|
|
297
|
+
return (
|
|
298
|
+
<th
|
|
299
|
+
key={column.param}
|
|
300
|
+
scope="col"
|
|
301
|
+
id={`IntelliTable-${id}-header-${column.param}`}
|
|
302
|
+
>
|
|
303
|
+
<div className="d-flex align-items-center justify-content-center flex-row h-100">
|
|
304
|
+
{/* Title */}
|
|
305
|
+
<span className="text-nowrap">
|
|
306
|
+
{column.title}
|
|
307
|
+
</span>
|
|
308
|
+
{/* Buttons */}
|
|
309
|
+
<div>
|
|
310
|
+
<button
|
|
311
|
+
type="button"
|
|
312
|
+
className="btn btn-light btn-sm ms-1"
|
|
313
|
+
aria-label={sortButtonAriaLabel}
|
|
314
|
+
onClick={() => {
|
|
315
|
+
dispatch({
|
|
316
|
+
type: ActionType.ToggleSortColumn,
|
|
317
|
+
param: column.param,
|
|
318
|
+
});
|
|
319
|
+
}}
|
|
320
|
+
>
|
|
321
|
+
<FontAwesomeIcon
|
|
322
|
+
icon={sortIcon}
|
|
323
|
+
/>
|
|
324
|
+
</button>
|
|
325
|
+
</div>
|
|
326
|
+
</div>
|
|
327
|
+
</th>
|
|
328
|
+
);
|
|
329
|
+
})
|
|
330
|
+
);
|
|
331
|
+
const tableHeader = (
|
|
332
|
+
<thead>
|
|
333
|
+
<tr>
|
|
334
|
+
{headerCells}
|
|
335
|
+
</tr>
|
|
336
|
+
</thead>
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
// Sort data
|
|
340
|
+
let sortedData = [...data];
|
|
341
|
+
const paramType = columns.find((column) => {
|
|
342
|
+
return (column.param === sortColumnParam);
|
|
343
|
+
})?.type;
|
|
344
|
+
const descending = (sortType === SortType.Descending);
|
|
345
|
+
if (sortColumnParam) {
|
|
346
|
+
sortedData.sort((a, b) => {
|
|
347
|
+
const aVal = a[sortColumnParam];
|
|
348
|
+
const bVal = b[sortColumnParam];
|
|
349
|
+
|
|
350
|
+
// Sort differently based on the data type
|
|
351
|
+
// > Boolean
|
|
352
|
+
if (paramType === ParamType.Boolean) {
|
|
353
|
+
if (aVal && !bVal) {
|
|
354
|
+
return (descending ? -1 : 1);
|
|
355
|
+
}
|
|
356
|
+
if (!aVal && bVal) {
|
|
357
|
+
return (descending ? 1 : -1);
|
|
358
|
+
}
|
|
359
|
+
return 0;
|
|
360
|
+
}
|
|
361
|
+
// > Number
|
|
362
|
+
if (
|
|
363
|
+
paramType === ParamType.Int
|
|
364
|
+
|| paramType === ParamType.Float
|
|
365
|
+
) {
|
|
366
|
+
return (
|
|
367
|
+
descending
|
|
368
|
+
? (bVal - aVal)
|
|
369
|
+
: (aVal - bVal)
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
// > String
|
|
373
|
+
if (paramType === ParamType.String) {
|
|
374
|
+
if (aVal < bVal) {
|
|
375
|
+
return (descending ? -1 : 1);
|
|
376
|
+
}
|
|
377
|
+
if (aVal > bVal) {
|
|
378
|
+
return (descending ? 1 : -1);
|
|
379
|
+
}
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
382
|
+
// > JSON
|
|
383
|
+
if (paramType === ParamType.JSON) {
|
|
384
|
+
const aSize = (
|
|
385
|
+
Array.isArray(aVal)
|
|
386
|
+
? aVal.length
|
|
387
|
+
: Object.keys(aVal).length
|
|
388
|
+
);
|
|
389
|
+
const bSize = (
|
|
390
|
+
Array.isArray(bVal)
|
|
391
|
+
? bVal.length
|
|
392
|
+
: Object.keys(bVal).length
|
|
393
|
+
);
|
|
394
|
+
return (
|
|
395
|
+
descending
|
|
396
|
+
? (bSize - aSize)
|
|
397
|
+
: (aSize - bSize)
|
|
398
|
+
);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// No sort
|
|
402
|
+
return 0;
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Table body
|
|
407
|
+
const rows = sortedData.map((datum) => {
|
|
408
|
+
// Build cells
|
|
409
|
+
const cells = (
|
|
410
|
+
columns
|
|
411
|
+
.filter((column) => {
|
|
412
|
+
return columnVisibilityMap[column.param];
|
|
413
|
+
})
|
|
414
|
+
.map((column) => {
|
|
415
|
+
// Get value
|
|
416
|
+
let value: any = datum;
|
|
417
|
+
const paramParts = column.param.split('.');
|
|
418
|
+
paramParts.forEach((paramPart) => {
|
|
419
|
+
value = (value ?? {})[paramPart];
|
|
420
|
+
});
|
|
421
|
+
let fullValue: React.ReactNode;
|
|
422
|
+
let visibleValue: React.ReactNode;
|
|
423
|
+
let title: string = '';
|
|
424
|
+
if (column.type === ParamType.Boolean) {
|
|
425
|
+
fullValue = !!(value);
|
|
426
|
+
const noValue = (
|
|
427
|
+
value === undefined
|
|
428
|
+
|| value === null
|
|
429
|
+
);
|
|
430
|
+
visibleValue = (
|
|
431
|
+
noValue
|
|
432
|
+
? (
|
|
433
|
+
<FontAwesomeIcon
|
|
434
|
+
icon={faCheckCircle}
|
|
435
|
+
/>
|
|
436
|
+
)
|
|
437
|
+
: (
|
|
438
|
+
<FontAwesomeIcon
|
|
439
|
+
icon={faXmarkCircle}
|
|
440
|
+
/>
|
|
441
|
+
)
|
|
442
|
+
);
|
|
443
|
+
title = (fullValue ? 'True' : 'False');
|
|
444
|
+
} else if (column.type === ParamType.Int) {
|
|
445
|
+
fullValue = Number.parseInt(value, 10);
|
|
446
|
+
const noValue = Number.isNaN(fullValue);
|
|
447
|
+
visibleValue = (
|
|
448
|
+
noValue
|
|
449
|
+
? (
|
|
450
|
+
<FontAwesomeIcon
|
|
451
|
+
icon={faMinus}
|
|
452
|
+
/>
|
|
453
|
+
)
|
|
454
|
+
: fullValue
|
|
455
|
+
);
|
|
456
|
+
title = String(fullValue);
|
|
457
|
+
} else if (column.type === ParamType.Float) {
|
|
458
|
+
fullValue = Number.parseFloat(value);
|
|
459
|
+
const noValue = Number.isNaN(fullValue);
|
|
460
|
+
visibleValue = (
|
|
461
|
+
noValue
|
|
462
|
+
? (
|
|
463
|
+
<FontAwesomeIcon
|
|
464
|
+
icon={faMinus}
|
|
465
|
+
/>
|
|
466
|
+
)
|
|
467
|
+
: roundToNumDecimals(fullValue as number, 2)
|
|
468
|
+
);
|
|
469
|
+
title = String(fullValue);
|
|
470
|
+
} else if (column.type === ParamType.String) {
|
|
471
|
+
fullValue = String(value).trim();
|
|
472
|
+
const noValue = (value.trim().length) === 0;
|
|
473
|
+
visibleValue = (
|
|
474
|
+
noValue
|
|
475
|
+
? (
|
|
476
|
+
<FontAwesomeIcon
|
|
477
|
+
icon={faMinus}
|
|
478
|
+
/>
|
|
479
|
+
)
|
|
480
|
+
: fullValue
|
|
481
|
+
);
|
|
482
|
+
title = `"${value}"`;
|
|
483
|
+
} else if (column.type === ParamType.JSON) {
|
|
484
|
+
fullValue = JSON.stringify(value);
|
|
485
|
+
const noValue = (
|
|
486
|
+
Array.isArray(value)
|
|
487
|
+
? (!value || value.length === 0)
|
|
488
|
+
: Object.keys(value ?? {}).length === 0
|
|
489
|
+
);
|
|
490
|
+
visibleValue = (
|
|
491
|
+
noValue
|
|
492
|
+
? (
|
|
493
|
+
<FontAwesomeIcon
|
|
494
|
+
icon={faMinus}
|
|
495
|
+
/>
|
|
496
|
+
)
|
|
497
|
+
: fullValue
|
|
498
|
+
);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// Create UI
|
|
502
|
+
return (
|
|
503
|
+
<td
|
|
504
|
+
key={`${datum.id}-${column.param}`}
|
|
505
|
+
title={title}
|
|
506
|
+
>
|
|
507
|
+
{visibleValue}
|
|
508
|
+
</td>
|
|
509
|
+
);
|
|
510
|
+
})
|
|
511
|
+
);
|
|
512
|
+
|
|
513
|
+
// Add cells to a row
|
|
514
|
+
return (
|
|
515
|
+
<tr key={datum.id}>
|
|
516
|
+
{cells}
|
|
517
|
+
</tr>
|
|
518
|
+
);
|
|
519
|
+
});
|
|
520
|
+
const tableBody = (
|
|
521
|
+
<tbody>
|
|
522
|
+
{rows}
|
|
523
|
+
</tbody>
|
|
524
|
+
);
|
|
525
|
+
|
|
526
|
+
// Build table
|
|
527
|
+
const table = (
|
|
528
|
+
<table className="table table-dark table-striped">
|
|
529
|
+
{tableHeader}
|
|
530
|
+
{tableBody}
|
|
531
|
+
</table>
|
|
532
|
+
);
|
|
533
|
+
|
|
534
|
+
// Count the number of hidden columns
|
|
535
|
+
const numHiddenCols = (
|
|
536
|
+
Object.values(columnVisibilityMap)
|
|
537
|
+
.filter((isVisible) => {
|
|
538
|
+
return !isVisible;
|
|
539
|
+
})
|
|
540
|
+
.length
|
|
541
|
+
);
|
|
542
|
+
|
|
543
|
+
// Build CSV
|
|
544
|
+
const csv = genCSV(
|
|
545
|
+
data,
|
|
546
|
+
columns,
|
|
547
|
+
);
|
|
548
|
+
|
|
549
|
+
// Build main UI
|
|
550
|
+
return (
|
|
551
|
+
<div className={`IntelliTable-container-${id}`}>
|
|
552
|
+
{/* Header */}
|
|
553
|
+
<div className="d-flex align-items-center justify-content-center">
|
|
554
|
+
{/* Title */}
|
|
555
|
+
<h3 className="m-0">
|
|
556
|
+
{title}
|
|
557
|
+
</h3>
|
|
558
|
+
{/* Buttons */}
|
|
559
|
+
<div className="flex-grow-1 text-end">
|
|
560
|
+
{/* Download Button */}
|
|
561
|
+
<CSVDownloadButton
|
|
562
|
+
aria-label={`download data as csv for ${title}`}
|
|
563
|
+
id={`IntelliTable-${id}-download-as-csv`}
|
|
564
|
+
filename={`${title}.csv`}
|
|
565
|
+
csv={csv}
|
|
566
|
+
/>
|
|
567
|
+
{/* Show/Hide Columns */}
|
|
568
|
+
<button
|
|
569
|
+
type="button"
|
|
570
|
+
className="btn btn-secondary ms-2"
|
|
571
|
+
aria-label={`show panel for customizing which columns show in table ${title}`}
|
|
572
|
+
id={`IntelliTable-${id}-show-column-customization-modal`}
|
|
573
|
+
onClick={() => {
|
|
574
|
+
dispatch({
|
|
575
|
+
type: ActionType.ToggleColVisCusModalVisibility,
|
|
576
|
+
});
|
|
577
|
+
}}
|
|
578
|
+
>
|
|
579
|
+
Show/Hide Cols
|
|
580
|
+
{numHiddenCols > 0 && (
|
|
581
|
+
<>
|
|
582
|
+
{' '}
|
|
583
|
+
(
|
|
584
|
+
{numHiddenCols}
|
|
585
|
+
{' '}
|
|
586
|
+
hidden)
|
|
587
|
+
</>
|
|
588
|
+
)}
|
|
589
|
+
</button>
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
592
|
+
{/* Table */}
|
|
593
|
+
<div
|
|
594
|
+
className={`IntelliTable-table-${id}`}
|
|
595
|
+
style={{
|
|
596
|
+
overflowX: 'auto',
|
|
597
|
+
}}
|
|
598
|
+
>
|
|
599
|
+
{table}
|
|
600
|
+
</div>
|
|
601
|
+
</div>
|
|
602
|
+
);
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
/*------------------------------------------------------------------------*/
|
|
606
|
+
/* Wrap Up */
|
|
607
|
+
/*------------------------------------------------------------------------*/
|
|
608
|
+
|
|
609
|
+
// Export component
|
|
610
|
+
export default IntelliTable;
|
|
@@ -31,6 +31,8 @@ type Props = {
|
|
|
31
31
|
* values updated
|
|
32
32
|
*/
|
|
33
33
|
onChanged: (updatedItems: PickableItem[]) => void,
|
|
34
|
+
// If true, don't add margin to bottom of item picker
|
|
35
|
+
noBottomMargin?: boolean,
|
|
34
36
|
};
|
|
35
37
|
|
|
36
38
|
/*------------------------------------------------------------------------*/
|
|
@@ -49,6 +51,7 @@ const ItemPicker: React.FC<Props> = (props) => {
|
|
|
49
51
|
title,
|
|
50
52
|
items,
|
|
51
53
|
onChanged,
|
|
54
|
+
noBottomMargin,
|
|
52
55
|
} = props;
|
|
53
56
|
|
|
54
57
|
/*------------------------------------------------------------------------*/
|
|
@@ -66,6 +69,7 @@ const ItemPicker: React.FC<Props> = (props) => {
|
|
|
66
69
|
return (
|
|
67
70
|
<TabBox
|
|
68
71
|
title={title}
|
|
72
|
+
noBottomMargin={noBottomMargin}
|
|
69
73
|
>
|
|
70
74
|
<div style={{ overflowX: 'auto' }}>
|
|
71
75
|
<NestableItemList
|