dce-reactkit 3.2.2-beta.8 → 3.2.3

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.
Files changed (50) hide show
  1. package/dist/cjs/index.js +743 -440
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/Drawer.d.ts +1 -0
  4. package/dist/cjs/types/components/IntelliTable.d.ts +1 -0
  5. package/dist/cjs/types/components/ItemPicker/NestableItemList.d.ts +1 -0
  6. package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
  7. package/dist/cjs/types/dynamicConstants/DynamicWord.d.ts +13 -0
  8. package/dist/cjs/types/index.d.ts +2 -1
  9. package/dist/cjs/types/types/LogAction.d.ts +16 -16
  10. package/dist/cjs/types/types/LogBuiltInMetadata.d.ts +1 -1
  11. package/dist/cjs/types/types/LogMetadataContextMap.d.ts +11 -0
  12. package/dist/cjs/types/types/LogMetadataTagMap.d.ts +8 -0
  13. package/dist/cjs/types/types/LogMetadataTargetMap.d.ts +8 -0
  14. package/dist/cjs/types/types/LogMetadataType.d.ts +6 -12
  15. package/dist/esm/index.js +744 -442
  16. package/dist/esm/index.js.map +1 -1
  17. package/dist/esm/types/components/Drawer.d.ts +1 -0
  18. package/dist/esm/types/components/IntelliTable.d.ts +1 -0
  19. package/dist/esm/types/components/ItemPicker/NestableItemList.d.ts +1 -0
  20. package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
  21. package/dist/esm/types/dynamicConstants/DynamicWord.d.ts +13 -0
  22. package/dist/esm/types/index.d.ts +2 -1
  23. package/dist/esm/types/types/LogAction.d.ts +16 -16
  24. package/dist/esm/types/types/LogBuiltInMetadata.d.ts +1 -1
  25. package/dist/esm/types/types/LogMetadataContextMap.d.ts +11 -0
  26. package/dist/esm/types/types/LogMetadataTagMap.d.ts +8 -0
  27. package/dist/esm/types/types/LogMetadataTargetMap.d.ts +8 -0
  28. package/dist/esm/types/types/LogMetadataType.d.ts +6 -12
  29. package/dist/index.d.ts +64 -30
  30. package/package.json +1 -1
  31. package/src/components/ButtonInputGroup.tsx +1 -1
  32. package/src/components/CSVDownloadButton.tsx +2 -2
  33. package/src/components/Drawer.tsx +9 -1
  34. package/src/components/IntelliTable.tsx +178 -23
  35. package/src/components/ItemPicker/NestableItemList.tsx +32 -14
  36. package/src/components/ItemPicker/index.tsx +4 -0
  37. package/src/components/LogReviewer.tsx +588 -418
  38. package/src/components/SimpleDateChooser.tsx +30 -25
  39. package/src/dynamicConstants/DynamicWord.tsx +55 -0
  40. package/src/helpers/genCSV.ts +22 -4
  41. package/src/helpers/genRouteHandler.ts +2 -2
  42. package/src/helpers/logClientEvent.tsx +1 -1
  43. package/src/index.ts +5 -0
  44. package/src/server/initServer.ts +10 -3
  45. package/src/types/LogAction.ts +16 -16
  46. package/src/types/LogBuiltInMetadata.ts +5 -5
  47. package/src/types/LogMetadataContextMap.ts +15 -0
  48. package/src/types/LogMetadataTagMap.ts +9 -0
  49. package/src/types/LogMetadataTargetMap.ts +9 -0
  50. 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
- ToggleColumnVisibility = 'toggle-column-visibility',
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.ToggleColumnVisibility,
105
- // Param for the column to toggle
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.ToggleColumnVisibility: {
159
+ case ActionType.UpdateColumnVisibility: {
148
160
  const { columnVisibilityMap } = state;
149
- columnVisibilityMap[action.param] = !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.ToggleColumnVisibility,
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,8 +396,13 @@ 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-center flex-row h-100">
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}
@@ -309,7 +411,8 @@ const IntelliTable: React.FC<Props> = (props) => {
309
411
  <div>
310
412
  <button
311
413
  type="button"
312
- className="btn btn-light btn-sm ms-1"
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 0;
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 ? -1 : 1);
505
+ return (descending ? 1 : -1);
376
506
  }
377
507
  if (aVal > bVal) {
378
- return (descending ? 1 : -1);
508
+ return (descending ? -1 : 1);
379
509
  }
380
- return 0;
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 0;
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={faCheckCircle}
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 = (value.trim().length) === 0;
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-center">
708
+ <div className="d-flex align-items-center justify-content-start">
554
709
  {/* Title */}
555
710
  <h3 className="m-0">
556
711
  {title}
@@ -561,7 +716,7 @@ 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={`${title}.csv`}
719
+ filename={filename}
565
720
  csv={csv}
566
721
  />
567
722
  {/* Show/Hide Columns */}
@@ -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
- // True if children are being shown
47
- isShowingItems: boolean,
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 the children are being shown
55
- ToggleItems = 'toggle-items',
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.ToggleItems,
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 Yuen Ler Chow
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.ToggleItems: {
75
- return { isShowingItems: !state.isShowingItems };
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
- isShowingItems: false,
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
- isShowingItems,
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.ToggleItems,
251
+ type: ActionType.ToggleChild,
252
+ id: item.id,
235
253
  });
236
254
  }}
237
- aria-label={`${isShowingItems ? 'Hide' : 'Show'} items in ${item.name}`}
255
+ aria-label={`${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${item.name}`}
238
256
  >
239
257
  <FontAwesomeIcon
240
- icon={isShowingItems ? faChevronDown : faChevronRight}
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 && isShowingItems && (
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