dce-reactkit 3.2.2-beta.9 → 3.2.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.
Files changed (50) hide show
  1. package/dist/cjs/index.js +742 -435
  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/dynamicConstants/DynamicWord.d.ts +13 -0
  7. package/dist/cjs/types/helpers/isMobileOrTablet.d.ts +7 -0
  8. package/dist/cjs/types/index.d.ts +3 -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 +742 -437
  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/dynamicConstants/DynamicWord.d.ts +13 -0
  21. package/dist/esm/types/helpers/isMobileOrTablet.d.ts +7 -0
  22. package/dist/esm/types/index.d.ts +3 -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 +70 -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/LogReviewer.tsx +584 -424
  37. package/src/components/SimpleDateChooser.tsx +30 -25
  38. package/src/dynamicConstants/DynamicWord.tsx +44 -0
  39. package/src/helpers/genCSV.ts +22 -4
  40. package/src/helpers/genRouteHandler.ts +2 -2
  41. package/src/helpers/isMobileOrTablet.tsx +26 -0
  42. package/src/helpers/logClientEvent.tsx +1 -1
  43. package/src/index.ts +7 -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
@@ -62,11 +62,7 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
62
62
  onChange,
63
63
  chooseFromPast,
64
64
  } = props;
65
- let numMonthsToShow = forceNumIntoBounds(
66
- (props.numMonthsToShow ?? 6),
67
- 1,
68
- 12,
69
- );
65
+ const numMonthsToShow = (props.numMonthsToShow ?? 6);
70
66
 
71
67
  /*------------------------------------------------------------------------*/
72
68
  /* Render */
@@ -88,7 +84,7 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
88
84
  let startMonth = today.month;
89
85
  if (chooseFromPast) {
90
86
  startMonth -= Math.max(0, numMonthsToShow - 1);
91
- if (startMonth <= 0) {
87
+ while (startMonth <= 0) {
92
88
  startMonth += 12;
93
89
  startYear -= 1;
94
90
  }
@@ -96,28 +92,37 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
96
92
  for (let i = 0; i < numMonthsToShow; i++) {
97
93
  // Get month and year info
98
94
  const unmoddedMonth = (startMonth + i);
99
- const month = (
100
- unmoddedMonth > 12
101
- ? unmoddedMonth - 12
102
- : unmoddedMonth
103
- );
95
+ let month = unmoddedMonth;
96
+ while (month > 12) {
97
+ month -= 12;
98
+ }
104
99
  const monthName = getMonthName(month).full;
105
- const year = (
106
- unmoddedMonth > 12
107
- ? startYear + 1
108
- : startYear
109
- );
100
+ // Year is start year +1 for each 12 months
101
+ const year = (startYear + (Math.floor(unmoddedMonth / 12)));
110
102
 
111
103
  // Figure out which days are allowed
112
104
  const days = [];
113
105
  const numDaysInMonth = (new Date(year, month, 0)).getDate();
114
- const firstDay = (
115
- month === today.month
116
- ? today.day // Current month: start at current date
117
- : 1 // Future month: start at beginning of month
118
- );
119
- for (let day = firstDay; day <= numDaysInMonth; day++) {
120
- days.push(day);
106
+ if (chooseFromPast) {
107
+ // Past selection
108
+ const numDaysToAdd = (
109
+ (month === today.month)
110
+ ? today.day // Current month, only add up to today
111
+ : numDaysInMonth // Past month, add all days
112
+ );
113
+ for (let day = 1; day <= numDaysToAdd; day++) {
114
+ days.push(day);
115
+ }
116
+ } else {
117
+ // Future selection: add all remaining days of the month
118
+ const firstDay = (
119
+ month === today.month
120
+ ? today.day // Current month: start at current date
121
+ : 1 // Future month: start at beginning of month
122
+ );
123
+ for (let day = firstDay; day <= numDaysInMonth; day++) {
124
+ days.push(day);
125
+ }
121
126
  }
122
127
 
123
128
  choices.push({
@@ -136,7 +141,7 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
136
141
  monthOptions.push(
137
142
  <option
138
143
  key={choice.month}
139
- value={choice.month}
144
+ value={`${choice.month}-${choice.year}`}
140
145
  aria-label={`choose ${choice.choiceName}`}
141
146
  onSelect={() => {
142
147
  onChange(choice.month, choice.days[0], choice.year);
@@ -176,7 +181,7 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
176
181
  className="custom-select d-inline-block mr-1"
177
182
  style={{ width: 'auto' }}
178
183
  id={`SimpleDateChooser-${name}-month`}
179
- value={month}
184
+ value={`${month}-${year}`}
180
185
  onChange={(e) => {
181
186
  const choice = choices[e.target.selectedIndex];
182
187
 
@@ -0,0 +1,44 @@
1
+ // Import shared types
2
+ import isMobileOrTablet from '../helpers/isMobileOrTablet';
3
+
4
+ // Check if mobile
5
+ const isMobile = isMobileOrTablet();
6
+
7
+ /**
8
+ * Dynamic words determined by the user's platform
9
+ * @author Gabe Abrams
10
+ */
11
+ const DynamicWord = {
12
+ Click: (
13
+ isMobile
14
+ ? 'tap'
15
+ : 'click'
16
+ ),
17
+ ClickCapitalized: (
18
+ isMobile
19
+ ? 'Tap'
20
+ : 'Click'
21
+ ),
22
+ App: (
23
+ isMobile
24
+ ? 'app'
25
+ : 'application'
26
+ ),
27
+ AppCapitalized: (
28
+ isMobile
29
+ ? 'App'
30
+ : 'Application'
31
+ ),
32
+ Device: (
33
+ isMobile
34
+ ? 'device'
35
+ : 'computer'
36
+ ),
37
+ DeviceCapitalized: (
38
+ isMobile
39
+ ? 'Device'
40
+ : 'Computer'
41
+ ),
42
+ };
43
+
44
+ export default DynamicWord;
@@ -5,13 +5,13 @@
5
5
  * @returns escaped cell text
6
6
  */
7
7
  const escapeCellText = (text: string): string => {
8
- if (!text.includes(',')) {
8
+ if (!String(text).includes(',')) {
9
9
  // No need to escape
10
- return text;
10
+ return String(text);
11
11
  }
12
12
 
13
13
  // Perform escape
14
- return `"${text.replace(/"/g, '""')}`;
14
+ return `"${String(text).replace(/"/g, '""')}`;
15
15
  };
16
16
 
17
17
  /**
@@ -43,10 +43,28 @@ const genCSV = (
43
43
 
44
44
  // Add each row
45
45
  data.forEach((datum) => {
46
+ csv += '\n';
46
47
  csv += (
47
48
  columns
48
49
  .map((column) => {
49
- return escapeCellText(datum[column.param]);
50
+ let contents: string;
51
+ const cell = datum[column.param];
52
+ if (
53
+ typeof cell === 'string'
54
+ || typeof cell === 'number'
55
+ ) {
56
+ contents = String(cell);
57
+ } else if (
58
+ typeof cell === 'undefined'
59
+ || cell === null
60
+ ) {
61
+ contents = '';
62
+ } else if (typeof cell === 'object') {
63
+ contents = JSON.stringify(cell);
64
+ } else {
65
+ contents = '';
66
+ }
67
+ return escapeCellText(contents);
50
68
  })
51
69
  .join(',')
52
70
  );
@@ -500,7 +500,7 @@ const genRouteHandler = (
500
500
  isAdmin: !!launchInfo.isAdmin,
501
501
  isTTM: !!launchInfo.isTTM,
502
502
  courseId: launchInfo.courseId,
503
- courseName: launchInfo.courseName,
503
+ courseName: launchInfo.contextLabel,
504
504
  browser,
505
505
  device,
506
506
  year,
@@ -539,7 +539,7 @@ const genRouteHandler = (
539
539
  type: LogType.Action,
540
540
  target: (
541
541
  (opts as any).target
542
- ?? LogBuiltInMetadata.Target.NoSpecificTarget
542
+ ?? LogBuiltInMetadata.Target.NoTarget
543
543
  ),
544
544
  action: (
545
545
  (opts as any).action
@@ -0,0 +1,26 @@
1
+ // True if user is on mobile or tablet
2
+ let cachedResult: boolean | undefined = undefined;
3
+
4
+ /**
5
+ * Check if the user is on a mobile device or tablet
6
+ * from https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser
7
+ * @returns true if user is on a mobile device
8
+ */
9
+ const isMobileOrTablet = () => {
10
+ if (cachedResult !== undefined) {
11
+ return cachedResult;
12
+ }
13
+
14
+ try {
15
+ let check = false;
16
+ ((a) => { if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))) check = true; })((navigator ?? {}).userAgent || (navigator ?? {}).vendor || ((window as any) ?? {}).opera);
17
+ cachedResult = check;
18
+ return cachedResult;
19
+ } catch (err) {
20
+ // Probably this code is being executed on a server
21
+ cachedResult = false;
22
+ return cachedResult;
23
+ }
24
+ };
25
+
26
+ export default isMobileOrTablet;
@@ -53,7 +53,7 @@ const logClientEvent: LogFunction = async (opts) => {
53
53
  (opts as any).action
54
54
  ? (
55
55
  (opts as any).target
56
- ?? LogBuiltInMetadata.Target.NoSpecificTarget
56
+ ?? LogBuiltInMetadata.Target.NoTarget
57
57
  )
58
58
  : undefined
59
59
  ),
package/src/index.ts CHANGED
@@ -26,6 +26,9 @@ import MINUTE_IN_MS from './constants/MINUTE_IN_MS';
26
26
  import HOUR_IN_MS from './constants/HOUR_IN_MS';
27
27
  import DAY_IN_MS from './constants/DAY_IN_MS';
28
28
 
29
+ // Import dynamic constants
30
+ import DynamicWord from './dynamicConstants/DynamicWord';
31
+
29
32
  // Import helpers
30
33
  import abbreviate from './helpers/abbreviate';
31
34
  import avg from './helpers/avg';
@@ -56,6 +59,7 @@ import initLogCollection from './server/initLogCollection';
56
59
  import getMonthName from './helpers/getMonthName';
57
60
  import genCSV from './helpers/genCSV';
58
61
  import canReviewLogs from './helpers/canReviewLogs';
62
+ import isMobileOrTablet from './helpers/isMobileOrTablet';
59
63
 
60
64
  // Import types
61
65
  import ModalButtonType from './types/ModalButtonType';
@@ -107,6 +111,8 @@ export {
107
111
  MINUTE_IN_MS,
108
112
  HOUR_IN_MS,
109
113
  DAY_IN_MS,
114
+ // Dynamic Constants
115
+ DynamicWord,
110
116
  // Helpers
111
117
  abbreviate,
112
118
  avg,
@@ -130,6 +136,7 @@ export {
130
136
  getMonthName,
131
137
  genCSV,
132
138
  canReviewLogs,
139
+ isMobileOrTablet,
133
140
  // Client helpers
134
141
  visitServerEndpoint,
135
142
  logClientEvent,
@@ -249,10 +249,14 @@ const initServer = (
249
249
  },
250
250
  handler: async ({ params }) => {
251
251
  // Get user info
252
- const { userId, isAdmin } = params;
252
+ const {
253
+ year,
254
+ month,
255
+ userId,
256
+ isAdmin,
257
+ } = params;
253
258
 
254
259
  // Validate user
255
- // isAdmin is already checked because path starts with '/admin'
256
260
  const canReview = await canReviewLogs(userId, isAdmin);
257
261
  if (!canReview) {
258
262
  throw new ErrorWithCode(
@@ -262,7 +266,10 @@ const initServer = (
262
266
  }
263
267
 
264
268
  // Query for logs
265
- const logs: Log[] = await _logCollection.find({ userId });
269
+ const logs: Log[] = await _logCollection.find({
270
+ year,
271
+ month,
272
+ });
266
273
 
267
274
  // Return logs
268
275
  return logs;
@@ -4,37 +4,37 @@
4
4
  */
5
5
  enum LogAction {
6
6
  // Target was opened by the user (it was not on screen, but now it is)
7
- Open = 'open',
7
+ Open = 'Open',
8
8
  // Target was closed by the user (it was on screen, but now it is not)
9
- Close = 'close',
9
+ Close = 'Close',
10
10
  // Target was cancelled by the user (it was on closed without saving)
11
- Cancel = 'cancel',
11
+ Cancel = 'Cancel',
12
12
  // Target was expanded by the user (it always remains on screen, but size was changed)
13
- Expand = 'expand',
13
+ Expand = 'Expand',
14
14
  // Target was collapsed by the user (it always remains on screen, but size was changed)
15
- Collapse = 'collapse',
15
+ Collapse = 'Collapse',
16
16
  // Target was viewed by the user (only for items that are not opened or closed, those must use Open/Close actions)
17
- View = 'view',
17
+ View = 'View',
18
18
  // Target interrupted the user (popup, dialog, validation message, etc. appeared without user prompting)
19
- Interrupt = 'interrupt',
19
+ Interrupt = 'Interrupt',
20
20
  // Target was created by the user (it did not exist before)
21
- Create = 'create',
21
+ Create = 'Create',
22
22
  // Target was edited by the user (it existed and was changed)
23
- Edit = 'edit',
23
+ Edit = 'Edit',
24
24
  // Target was deleted by the user (it existed and now it doesn't)
25
- Delete = 'delete',
25
+ Delete = 'Delete',
26
26
  // Target was added by the user (it already existed and was added to another place)
27
- Add = 'add',
27
+ Add = 'Add',
28
28
  // Target was removed by the user (it was removed from something but still exists)
29
- Remove = 'remove',
29
+ Remove = 'Remove',
30
30
  // Target was activated by the user (click, check, tap, keypress, etc.)
31
- Activate = 'activate',
31
+ Activate = 'Activate',
32
32
  // Target was deactivated by the user (click away, uncheck, tap outside of, tab away, etc.)
33
- Deactivate = 'deactivate',
33
+ Deactivate = 'Deactivate',
34
34
  // User showed interest in a target (hover, peek, etc.)
35
- Peek = 'peek',
35
+ Peek = 'Peek',
36
36
  // Unknown action
37
- Unknown = 'unknown',
37
+ Unknown = 'Unknown',
38
38
  };
39
39
 
40
40
  export default LogAction;
@@ -5,14 +5,14 @@
5
5
  const LogBuiltInMetadata = {
6
6
  // Contexts
7
7
  Context: {
8
- Uncategorized: 'n/a',
9
- ServerRenderedErrorPage: '_server-rendered-error-page',
10
- ServerEndpointError: '_server-endpoint-error',
11
- ClientFatalError: '_client-fatal-error',
8
+ Uncategorized: 'Uncategorized',
9
+ ServerRenderedErrorPage: 'ServerRenderedErrorPage',
10
+ ServerEndpointError: 'ServerEndpointError',
11
+ ClientFatalError: 'ClientFatalError',
12
12
  },
13
13
  // Targets
14
14
  Target: {
15
- NoSpecificTarget: 'n/a',
15
+ NoTarget: 'NoTarget',
16
16
  },
17
17
  };
18
18
 
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Type of the context map in a LogMetadata file
3
+ * @author Gabe Abrams
4
+ */
5
+ type LogMetadataContextMap = {
6
+ [k: string]: (
7
+ | string
8
+ | {
9
+ _: string,
10
+ [k: string]: string
11
+ }
12
+ )
13
+ };
14
+
15
+ export default LogMetadataContextMap;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Type of the tag map inside a LogMetadata file
3
+ * @author Gabe Abrams
4
+ */
5
+ type LogMetadataTagMap = {
6
+ [k: string]: string
7
+ };
8
+
9
+ export default LogMetadataTagMap;
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Type of the target map inside a LogMetadata file
3
+ * @author Gabe Abrams
4
+ */
5
+ type LogMetadataTargetMap = {
6
+ [k: string]: string
7
+ };
8
+
9
+ export default LogMetadataTargetMap;
@@ -1,23 +1,16 @@
1
+ // Import shared types
2
+ import LogMetadataContextMap from './LogMetadataContextMap';
3
+ import LogMetadataTagMap from './LogMetadataTagMap';
4
+ import LogMetadataTargetMap from './LogMetadataTargetMap';
5
+
1
6
  /**
2
7
  * Type of a LogMetadata file
3
8
  * @author Gabe Abrams
4
9
  */
5
10
  type LogMetadataType = {
6
- Context?: {
7
- [k: string]: (
8
- | string
9
- | {
10
- _: string,
11
- [k: string]: string
12
- }
13
- )
14
- },
15
- Tag?: {
16
- [k: string]: string
17
- },
18
- Target?: {
19
- [k: string]: string
20
- },
11
+ Context?: LogMetadataContextMap,
12
+ Tag?: LogMetadataTagMap,
13
+ Target?: LogMetadataTargetMap,
21
14
  };
22
15
 
23
16
  export default LogMetadataType;