dce-reactkit 3.2.2-beta.5 → 3.2.2-beta.51
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/dist/cjs/index.js +741 -446
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/cjs/types/components/Drawer.d.ts +1 -0
- package/dist/cjs/types/components/IntelliTable.d.ts +1 -0
- package/dist/cjs/types/components/ItemPicker/NestableItemList.d.ts +1 -0
- package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/cjs/types/types/LogAction.d.ts +16 -16
- package/dist/cjs/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/cjs/types/types/LogMetadataContextMap.d.ts +11 -0
- package/dist/cjs/types/types/LogMetadataTagMap.d.ts +8 -0
- package/dist/cjs/types/types/LogMetadataTargetMap.d.ts +8 -0
- package/dist/cjs/types/types/LogMetadataType.d.ts +6 -12
- package/dist/esm/index.js +742 -447
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ButtonInputGroup.d.ts +1 -0
- package/dist/esm/types/components/Drawer.d.ts +1 -0
- package/dist/esm/types/components/IntelliTable.d.ts +1 -0
- package/dist/esm/types/components/ItemPicker/NestableItemList.d.ts +1 -0
- package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/esm/types/types/LogAction.d.ts +16 -16
- package/dist/esm/types/types/LogBuiltInMetadata.d.ts +1 -1
- package/dist/esm/types/types/LogMetadataContextMap.d.ts +11 -0
- package/dist/esm/types/types/LogMetadataTagMap.d.ts +8 -0
- package/dist/esm/types/types/LogMetadataTargetMap.d.ts +8 -0
- package/dist/esm/types/types/LogMetadataType.d.ts +6 -12
- package/dist/index.d.ts +51 -29
- package/package.json +1 -1
- package/src/components/ButtonInputGroup.tsx +5 -2
- package/src/components/CSVDownloadButton.tsx +2 -2
- package/src/components/Drawer.tsx +9 -1
- package/src/components/IntelliTable.tsx +181 -26
- package/src/components/ItemPicker/NestableItemList.tsx +32 -14
- package/src/components/ItemPicker/index.tsx +4 -0
- package/src/components/LogReviewer.tsx +635 -406
- package/src/components/SimpleDateChooser.tsx +28 -23
- package/src/helpers/genCSV.ts +22 -4
- package/src/helpers/genRouteHandler.ts +2 -2
- package/src/helpers/logClientEvent.tsx +1 -1
- package/src/server/initServer.ts +10 -3
- package/src/types/LogAction.ts +16 -16
- package/src/types/LogBuiltInMetadata.ts +5 -5
- package/src/types/LogMetadataContextMap.ts +15 -0
- package/src/types/LogMetadataTagMap.ts +9 -0
- package/src/types/LogMetadataTargetMap.ts +9 -0
- package/src/types/LogMetadataType.ts +8 -15
|
@@ -30,6 +30,8 @@ import Modal from './Modal';
|
|
|
30
30
|
import ModalType from '../types/ModalType';
|
|
31
31
|
import CheckboxButton from './CheckboxButton';
|
|
32
32
|
import CSVDownloadButton from './CSVDownloadButton';
|
|
33
|
+
import Variant from '../types/Variant';
|
|
34
|
+
import { alert } from './AppWrapper';
|
|
33
35
|
|
|
34
36
|
/*------------------------------------------------------------------------*/
|
|
35
37
|
/* Types */
|
|
@@ -50,6 +52,8 @@ type Props = {
|
|
|
50
52
|
}[],
|
|
51
53
|
// Column information
|
|
52
54
|
columns: IntelliTableColumn[],
|
|
55
|
+
// Name of the CSV file. If excluded, title is used
|
|
56
|
+
csvName?: string,
|
|
53
57
|
};
|
|
54
58
|
|
|
55
59
|
// Sort types
|
|
@@ -86,9 +90,13 @@ enum ActionType {
|
|
|
86
90
|
// Toggle sort column param
|
|
87
91
|
ToggleSortColumn = 'toggle-sort-column',
|
|
88
92
|
// Toggle the visibility of a column
|
|
89
|
-
|
|
93
|
+
UpdateColumnVisibility = 'update-column-visibility',
|
|
90
94
|
// Toggle the column visibility customization modal
|
|
91
95
|
ToggleColVisCusModalVisibility = 'toggle-col-vis-cus-modal-visibility',
|
|
96
|
+
// Show all columns
|
|
97
|
+
ShowAllColumns = 'show-all-columns',
|
|
98
|
+
// Hide all columns
|
|
99
|
+
HideAllColumns = 'hide-all-columns',
|
|
92
100
|
}
|
|
93
101
|
|
|
94
102
|
// Action definitions
|
|
@@ -101,14 +109,18 @@ type Action = (
|
|
|
101
109
|
}
|
|
102
110
|
| {
|
|
103
111
|
// Action type
|
|
104
|
-
type: ActionType.
|
|
105
|
-
// Param for the column to
|
|
112
|
+
type: ActionType.UpdateColumnVisibility,
|
|
113
|
+
// Param for the column to update
|
|
106
114
|
param: string,
|
|
115
|
+
// If true, make the column visible
|
|
116
|
+
visible: boolean,
|
|
107
117
|
}
|
|
108
118
|
| {
|
|
109
119
|
// Action type
|
|
110
120
|
type: (
|
|
111
121
|
| ActionType.ToggleColVisCusModalVisibility
|
|
122
|
+
| ActionType.ShowAllColumns
|
|
123
|
+
| ActionType.HideAllColumns
|
|
112
124
|
),
|
|
113
125
|
}
|
|
114
126
|
);
|
|
@@ -144,9 +156,9 @@ const reducer = (state: State, action: Action): State => {
|
|
|
144
156
|
sortType: SortType.Ascending,
|
|
145
157
|
};
|
|
146
158
|
}
|
|
147
|
-
case ActionType.
|
|
159
|
+
case ActionType.UpdateColumnVisibility: {
|
|
148
160
|
const { columnVisibilityMap } = state;
|
|
149
|
-
columnVisibilityMap[action.param] =
|
|
161
|
+
columnVisibilityMap[action.param] = action.visible;
|
|
150
162
|
return {
|
|
151
163
|
...state,
|
|
152
164
|
columnVisibilityMap,
|
|
@@ -158,6 +170,26 @@ const reducer = (state: State, action: Action): State => {
|
|
|
158
170
|
columnVisibilityCustomizationModalVisible: !state.columnVisibilityCustomizationModalVisible,
|
|
159
171
|
};
|
|
160
172
|
}
|
|
173
|
+
case ActionType.ShowAllColumns: {
|
|
174
|
+
const { columnVisibilityMap } = state;
|
|
175
|
+
Object.keys(columnVisibilityMap).forEach((param) => {
|
|
176
|
+
columnVisibilityMap[param] = true;
|
|
177
|
+
});
|
|
178
|
+
return {
|
|
179
|
+
...state,
|
|
180
|
+
columnVisibilityMap,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
case ActionType.HideAllColumns: {
|
|
184
|
+
const { columnVisibilityMap } = state;
|
|
185
|
+
Object.keys(columnVisibilityMap).forEach((param) => {
|
|
186
|
+
columnVisibilityMap[param] = false;
|
|
187
|
+
});
|
|
188
|
+
return {
|
|
189
|
+
...state,
|
|
190
|
+
columnVisibilityMap,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
161
193
|
default: {
|
|
162
194
|
return state;
|
|
163
195
|
}
|
|
@@ -179,10 +211,26 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
179
211
|
const {
|
|
180
212
|
title,
|
|
181
213
|
id,
|
|
182
|
-
data,
|
|
183
214
|
columns,
|
|
184
215
|
} = props;
|
|
185
216
|
|
|
217
|
+
// Get data, show empty row if none
|
|
218
|
+
const data = (
|
|
219
|
+
(props.data && props.data.length > 0)
|
|
220
|
+
? props.data
|
|
221
|
+
: [{ id: 'empty-row' }]
|
|
222
|
+
);
|
|
223
|
+
|
|
224
|
+
// Get CSV filename
|
|
225
|
+
let filename = `${title}.csv`;
|
|
226
|
+
if (props.csvName) {
|
|
227
|
+
filename = (
|
|
228
|
+
props.csvName.endsWith('.csv')
|
|
229
|
+
? props.csvName
|
|
230
|
+
: `${props.csvName}.csv`
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
186
234
|
/* -------------- State ------------- */
|
|
187
235
|
|
|
188
236
|
// Initial state
|
|
@@ -230,30 +278,77 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
230
278
|
type={ModalType.Okay}
|
|
231
279
|
title="Choose columns to show:"
|
|
232
280
|
onClose={() => {
|
|
281
|
+
const noColumnsSelected = (
|
|
282
|
+
Object.values(columnVisibilityMap)
|
|
283
|
+
.every((isSelected) => {
|
|
284
|
+
return !isSelected;
|
|
285
|
+
})
|
|
286
|
+
);
|
|
287
|
+
if (noColumnsSelected) {
|
|
288
|
+
return alert(
|
|
289
|
+
'Choose at least one column',
|
|
290
|
+
'To continue, you have to choose at least one column to show'
|
|
291
|
+
);
|
|
292
|
+
}
|
|
233
293
|
dispatch({
|
|
234
294
|
type: ActionType.ToggleColVisCusModalVisibility,
|
|
235
295
|
});
|
|
236
296
|
}}
|
|
297
|
+
okayVariant={Variant.Light}
|
|
237
298
|
>
|
|
299
|
+
{/* Columns */}
|
|
238
300
|
{
|
|
239
301
|
columns.map((column) => {
|
|
240
302
|
return (
|
|
241
303
|
<CheckboxButton
|
|
242
304
|
key={column.param}
|
|
243
305
|
id={`IntelliTable-${id}-toggle-visibility-${column.param}`}
|
|
306
|
+
className="mb-2"
|
|
244
307
|
text={column.title}
|
|
245
|
-
onChanged={() => {
|
|
308
|
+
onChanged={(checked) => {
|
|
246
309
|
dispatch({
|
|
247
|
-
type: ActionType.
|
|
310
|
+
type: ActionType.UpdateColumnVisibility,
|
|
248
311
|
param: column.param,
|
|
312
|
+
visible: checked,
|
|
249
313
|
});
|
|
250
314
|
}}
|
|
251
315
|
checked={columnVisibilityMap[column.param]}
|
|
252
|
-
ariaLabel={`show "${column.title}" column`}
|
|
316
|
+
ariaLabel={`show "${column.title}" column in the ${title} table`}
|
|
317
|
+
checkedVariant={Variant.Light}
|
|
318
|
+
uncheckedVariant={Variant.Secondary}
|
|
253
319
|
/>
|
|
254
320
|
);
|
|
255
321
|
})
|
|
256
322
|
}
|
|
323
|
+
<div className="mt-3">
|
|
324
|
+
Or you can:
|
|
325
|
+
</div>
|
|
326
|
+
<button
|
|
327
|
+
type="button"
|
|
328
|
+
id={`IntelliTable-${id}-select-all-columns`}
|
|
329
|
+
className="btn btn-secondary me-2"
|
|
330
|
+
aria-label={`show all columns in the ${title} table`}
|
|
331
|
+
onClick={() => {
|
|
332
|
+
dispatch({
|
|
333
|
+
type: ActionType.ShowAllColumns,
|
|
334
|
+
});
|
|
335
|
+
}}
|
|
336
|
+
>
|
|
337
|
+
Select All
|
|
338
|
+
</button>
|
|
339
|
+
<button
|
|
340
|
+
type="button"
|
|
341
|
+
id={`IntelliTable-${id}-select-none-columns`}
|
|
342
|
+
className="btn btn-secondary"
|
|
343
|
+
aria-label={`hide all columns in the ${title} table`}
|
|
344
|
+
onClick={() => {
|
|
345
|
+
dispatch({
|
|
346
|
+
type: ActionType.HideAllColumns,
|
|
347
|
+
});
|
|
348
|
+
}}
|
|
349
|
+
>
|
|
350
|
+
Deselect All
|
|
351
|
+
</button >
|
|
257
352
|
</Modal>
|
|
258
353
|
);
|
|
259
354
|
}
|
|
@@ -272,12 +367,14 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
272
367
|
// Custom info based on current sort type
|
|
273
368
|
let sortButtonAriaLabel;
|
|
274
369
|
let sortIcon = faSort;
|
|
370
|
+
let sortingByThisColumn = false;
|
|
275
371
|
if (!sortColumnParam) {
|
|
276
372
|
// Not being sorted yet
|
|
277
373
|
sortButtonAriaLabel = `sort ascending by ${column.title}`;
|
|
278
374
|
sortIcon = faSort;
|
|
279
375
|
} else if (column.param === sortColumnParam) {
|
|
280
376
|
// Already sorted by this column
|
|
377
|
+
sortingByThisColumn = true;
|
|
281
378
|
if (sortType === SortType.Ascending) {
|
|
282
379
|
// Sorted ascending
|
|
283
380
|
sortButtonAriaLabel = `sort descending by ${column.title}`;
|
|
@@ -299,17 +396,23 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
299
396
|
key={column.param}
|
|
300
397
|
scope="col"
|
|
301
398
|
id={`IntelliTable-${id}-header-${column.param}`}
|
|
399
|
+
className="text-start"
|
|
400
|
+
style={{
|
|
401
|
+
borderRight: '0.05rem solid #555',
|
|
402
|
+
borderLeft: '0.05rem solid #555',
|
|
403
|
+
}}
|
|
302
404
|
>
|
|
303
|
-
<div className="d-flex align-items-center justify-content-
|
|
405
|
+
<div className="d-flex align-items-center justify-content-start flex-row h-100">
|
|
304
406
|
{/* Title */}
|
|
305
|
-
<
|
|
407
|
+
<span className="text-nowrap">
|
|
306
408
|
{column.title}
|
|
307
|
-
</
|
|
409
|
+
</span>
|
|
308
410
|
{/* Buttons */}
|
|
309
411
|
<div>
|
|
310
412
|
<button
|
|
311
413
|
type="button"
|
|
312
|
-
|
|
414
|
+
id={`IntelliTable-${id}-sort-by-${column.param}-button`}
|
|
415
|
+
className={`btn btn-${sortingByThisColumn ? 'light' : 'secondary'} btn-sm ms-1 ps-1 pe-1 pt-0 pb-0`}
|
|
313
416
|
aria-label={sortButtonAriaLabel}
|
|
314
417
|
onClick={() => {
|
|
315
418
|
dispatch({
|
|
@@ -347,6 +450,33 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
347
450
|
const aVal = a[sortColumnParam];
|
|
348
451
|
const bVal = b[sortColumnParam];
|
|
349
452
|
|
|
453
|
+
// Tiebreaker sort by timestamp, most recent first (used if tied)
|
|
454
|
+
const tiebreaker = (
|
|
455
|
+
(b.timestamp ?? 0)
|
|
456
|
+
- (a.timestamp ?? 0)
|
|
457
|
+
);
|
|
458
|
+
|
|
459
|
+
// Auto-sort undefined and null to end of list
|
|
460
|
+
if (
|
|
461
|
+
(aVal === undefined || aVal === null)
|
|
462
|
+
|| (bVal === undefined || bVal === null)
|
|
463
|
+
) {
|
|
464
|
+
// At least one was undefined
|
|
465
|
+
if (
|
|
466
|
+
(aVal === undefined || aVal === null)
|
|
467
|
+
&& (bVal === undefined || bVal === null)
|
|
468
|
+
) {
|
|
469
|
+
// Both undefined
|
|
470
|
+
return tiebreaker;
|
|
471
|
+
}
|
|
472
|
+
if (aVal === undefined || aVal === null) {
|
|
473
|
+
// First was undefined
|
|
474
|
+
return 1;
|
|
475
|
+
}
|
|
476
|
+
// Second was undefined
|
|
477
|
+
return -1;
|
|
478
|
+
}
|
|
479
|
+
|
|
350
480
|
// Sort differently based on the data type
|
|
351
481
|
// > Boolean
|
|
352
482
|
if (paramType === ParamType.Boolean) {
|
|
@@ -356,7 +486,7 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
356
486
|
if (!aVal && bVal) {
|
|
357
487
|
return (descending ? 1 : -1);
|
|
358
488
|
}
|
|
359
|
-
return
|
|
489
|
+
return tiebreaker;
|
|
360
490
|
}
|
|
361
491
|
// > Number
|
|
362
492
|
if (
|
|
@@ -372,12 +502,12 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
372
502
|
// > String
|
|
373
503
|
if (paramType === ParamType.String) {
|
|
374
504
|
if (aVal < bVal) {
|
|
375
|
-
return (descending ?
|
|
505
|
+
return (descending ? 1 : -1);
|
|
376
506
|
}
|
|
377
507
|
if (aVal > bVal) {
|
|
378
|
-
return (descending ? 1 :
|
|
508
|
+
return (descending ? -1 : 1);
|
|
379
509
|
}
|
|
380
|
-
return
|
|
510
|
+
return tiebreaker;
|
|
381
511
|
}
|
|
382
512
|
// > JSON
|
|
383
513
|
if (paramType === ParamType.JSON) {
|
|
@@ -399,7 +529,7 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
399
529
|
}
|
|
400
530
|
|
|
401
531
|
// No sort
|
|
402
|
-
return
|
|
532
|
+
return tiebreaker;
|
|
403
533
|
});
|
|
404
534
|
}
|
|
405
535
|
|
|
@@ -431,16 +561,19 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
431
561
|
noValue
|
|
432
562
|
? (
|
|
433
563
|
<FontAwesomeIcon
|
|
434
|
-
icon={
|
|
564
|
+
icon={faMinus}
|
|
435
565
|
/>
|
|
436
566
|
)
|
|
437
567
|
: (
|
|
438
568
|
<FontAwesomeIcon
|
|
439
|
-
icon={faXmarkCircle}
|
|
569
|
+
icon={fullValue ? faCheckCircle : faXmarkCircle}
|
|
440
570
|
/>
|
|
441
571
|
)
|
|
442
572
|
);
|
|
443
573
|
title = (fullValue ? 'True' : 'False');
|
|
574
|
+
if (noValue) {
|
|
575
|
+
title = 'Empty Cell';
|
|
576
|
+
}
|
|
444
577
|
} else if (column.type === ParamType.Int) {
|
|
445
578
|
fullValue = Number.parseInt(value, 10);
|
|
446
579
|
const noValue = Number.isNaN(fullValue);
|
|
@@ -454,6 +587,9 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
454
587
|
: fullValue
|
|
455
588
|
);
|
|
456
589
|
title = String(fullValue);
|
|
590
|
+
if (noValue) {
|
|
591
|
+
title = 'Empty Cell';
|
|
592
|
+
}
|
|
457
593
|
} else if (column.type === ParamType.Float) {
|
|
458
594
|
fullValue = Number.parseFloat(value);
|
|
459
595
|
const noValue = Number.isNaN(fullValue);
|
|
@@ -467,9 +603,16 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
467
603
|
: roundToNumDecimals(fullValue as number, 2)
|
|
468
604
|
);
|
|
469
605
|
title = String(fullValue);
|
|
606
|
+
if (noValue) {
|
|
607
|
+
title = 'Empty Cell';
|
|
608
|
+
}
|
|
470
609
|
} else if (column.type === ParamType.String) {
|
|
471
610
|
fullValue = String(value).trim();
|
|
472
|
-
const noValue = (
|
|
611
|
+
const noValue = (
|
|
612
|
+
value === undefined
|
|
613
|
+
|| value === null
|
|
614
|
+
|| String(fullValue).trim().length === 0
|
|
615
|
+
);
|
|
473
616
|
visibleValue = (
|
|
474
617
|
noValue
|
|
475
618
|
? (
|
|
@@ -480,6 +623,9 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
480
623
|
: fullValue
|
|
481
624
|
);
|
|
482
625
|
title = `"${value}"`;
|
|
626
|
+
if (noValue) {
|
|
627
|
+
title = 'Empty Cell';
|
|
628
|
+
}
|
|
483
629
|
} else if (column.type === ParamType.JSON) {
|
|
484
630
|
fullValue = JSON.stringify(value);
|
|
485
631
|
const noValue = (
|
|
@@ -496,6 +642,7 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
496
642
|
)
|
|
497
643
|
: fullValue
|
|
498
644
|
);
|
|
645
|
+
title="JSON Object"
|
|
499
646
|
}
|
|
500
647
|
|
|
501
648
|
// Create UI
|
|
@@ -503,6 +650,10 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
503
650
|
<td
|
|
504
651
|
key={`${datum.id}-${column.param}`}
|
|
505
652
|
title={title}
|
|
653
|
+
style={{
|
|
654
|
+
borderRight: '0.05rem solid #555',
|
|
655
|
+
borderLeft: '0.05rem solid #555',
|
|
656
|
+
}}
|
|
506
657
|
>
|
|
507
658
|
{visibleValue}
|
|
508
659
|
</td>
|
|
@@ -543,14 +694,18 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
543
694
|
// Build CSV
|
|
544
695
|
const csv = genCSV(
|
|
545
696
|
data,
|
|
546
|
-
columns
|
|
697
|
+
columns.filter((column) => {
|
|
698
|
+
return columnVisibilityMap[column.param];
|
|
699
|
+
}),
|
|
547
700
|
);
|
|
548
701
|
|
|
549
702
|
// Build main UI
|
|
550
703
|
return (
|
|
551
704
|
<div className={`IntelliTable-container-${id}`}>
|
|
705
|
+
{/* Modal */}
|
|
706
|
+
{modal}
|
|
552
707
|
{/* Header */}
|
|
553
|
-
<div className="d-flex align-items-center justify-content-
|
|
708
|
+
<div className="d-flex align-items-center justify-content-start">
|
|
554
709
|
{/* Title */}
|
|
555
710
|
<h3 className="m-0">
|
|
556
711
|
{title}
|
|
@@ -561,13 +716,13 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
561
716
|
<CSVDownloadButton
|
|
562
717
|
aria-label={`download data as csv for ${title}`}
|
|
563
718
|
id={`IntelliTable-${id}-download-as-csv`}
|
|
564
|
-
filename={
|
|
719
|
+
filename={filename}
|
|
565
720
|
csv={csv}
|
|
566
721
|
/>
|
|
567
722
|
{/* Show/Hide Columns */}
|
|
568
723
|
<button
|
|
569
724
|
type="button"
|
|
570
|
-
className="btn btn-secondary"
|
|
725
|
+
className="btn btn-secondary ms-2"
|
|
571
726
|
aria-label={`show panel for customizing which columns show in table ${title}`}
|
|
572
727
|
id={`IntelliTable-${id}-show-column-customization-modal`}
|
|
573
728
|
onClick={() => {
|
|
@@ -591,7 +746,7 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
591
746
|
</div>
|
|
592
747
|
{/* Table */}
|
|
593
748
|
<div
|
|
594
|
-
className={`IntelliTable-table-${id}`}
|
|
749
|
+
className={`IntelliTable-table-${id} mt-2`}
|
|
595
750
|
style={{
|
|
596
751
|
overflowX: 'auto',
|
|
597
752
|
}}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Reusable nested item picker
|
|
3
3
|
* @author Yuen Ler Chow
|
|
4
|
+
* @author Gabe Abrams
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
// Import React
|
|
@@ -43,36 +44,46 @@ type Props = {
|
|
|
43
44
|
/* -------- State Definition -------- */
|
|
44
45
|
|
|
45
46
|
type State = {
|
|
46
|
-
//
|
|
47
|
-
|
|
47
|
+
// Map of children that are expanded
|
|
48
|
+
childExpanded: {
|
|
49
|
+
[k: string]: boolean
|
|
50
|
+
},
|
|
48
51
|
};
|
|
49
52
|
|
|
50
53
|
/* ------------- Actions ------------ */
|
|
51
54
|
|
|
52
55
|
// Types of actions
|
|
53
56
|
enum ActionType {
|
|
54
|
-
// Toggle whether
|
|
55
|
-
|
|
57
|
+
// Toggle whether a child are being shown
|
|
58
|
+
ToggleChild = 'toggle-child',
|
|
56
59
|
}
|
|
57
60
|
|
|
58
61
|
// Action definitions
|
|
59
62
|
type Action = (
|
|
60
63
|
| {
|
|
61
64
|
// Action type
|
|
62
|
-
type: ActionType.
|
|
65
|
+
type: ActionType.ToggleChild,
|
|
66
|
+
// Id of the child to show
|
|
67
|
+
id: (string | number),
|
|
63
68
|
}
|
|
64
69
|
);
|
|
65
70
|
|
|
66
71
|
/**
|
|
67
72
|
* Reducer that executes actions
|
|
68
|
-
* @author
|
|
73
|
+
* @author Gabe Abrams
|
|
69
74
|
* @param state current state
|
|
70
75
|
* @param action action to execute
|
|
71
76
|
*/
|
|
72
77
|
const reducer = (state: State, action: Action): State => {
|
|
73
78
|
switch (action.type) {
|
|
74
|
-
case ActionType.
|
|
75
|
-
return {
|
|
79
|
+
case ActionType.ToggleChild: {
|
|
80
|
+
return {
|
|
81
|
+
...state,
|
|
82
|
+
childExpanded: {
|
|
83
|
+
...state.childExpanded,
|
|
84
|
+
[String(action.id)]: !state.childExpanded[String(action.id)],
|
|
85
|
+
},
|
|
86
|
+
};
|
|
76
87
|
}
|
|
77
88
|
default: {
|
|
78
89
|
return state;
|
|
@@ -99,9 +110,15 @@ const NestableItemList: React.FC<Props> = (props) => {
|
|
|
99
110
|
|
|
100
111
|
/* -------------- State ------------- */
|
|
101
112
|
|
|
113
|
+
// Create initial map of child expanded booleans
|
|
114
|
+
const initChildExpanded: { [k: string]: boolean } = {};
|
|
115
|
+
items.forEach((item) => {
|
|
116
|
+
initChildExpanded[String(item.id)] = false;
|
|
117
|
+
});
|
|
118
|
+
|
|
102
119
|
// Initial state
|
|
103
120
|
const initialState: State = {
|
|
104
|
-
|
|
121
|
+
childExpanded: initChildExpanded,
|
|
105
122
|
};
|
|
106
123
|
|
|
107
124
|
// Initialize state
|
|
@@ -109,7 +126,7 @@ const NestableItemList: React.FC<Props> = (props) => {
|
|
|
109
126
|
|
|
110
127
|
// Destructure common state
|
|
111
128
|
const {
|
|
112
|
-
|
|
129
|
+
childExpanded,
|
|
113
130
|
} = state;
|
|
114
131
|
|
|
115
132
|
/*------------------------------------------------------------------------*/
|
|
@@ -231,13 +248,14 @@ const NestableItemList: React.FC<Props> = (props) => {
|
|
|
231
248
|
type="button"
|
|
232
249
|
onClick={() => {
|
|
233
250
|
dispatch({
|
|
234
|
-
type: ActionType.
|
|
251
|
+
type: ActionType.ToggleChild,
|
|
252
|
+
id: item.id,
|
|
235
253
|
});
|
|
236
254
|
}}
|
|
237
|
-
aria-label={`${
|
|
255
|
+
aria-label={`${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${item.name}`}
|
|
238
256
|
>
|
|
239
257
|
<FontAwesomeIcon
|
|
240
|
-
icon={
|
|
258
|
+
icon={childExpanded[item.id] ? faChevronDown : faChevronRight}
|
|
241
259
|
/>
|
|
242
260
|
</button>
|
|
243
261
|
)}
|
|
@@ -257,7 +275,7 @@ const NestableItemList: React.FC<Props> = (props) => {
|
|
|
257
275
|
/>
|
|
258
276
|
|
|
259
277
|
{/* Children */}
|
|
260
|
-
{item.isGroup &&
|
|
278
|
+
{(item.isGroup && childExpanded[item.id]) && (
|
|
261
279
|
<div
|
|
262
280
|
className="NestableItemList-children-container"
|
|
263
281
|
style={{
|
|
@@ -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
|