dce-reactkit 4.0.2-beta-simple-date-chooser.1 → 4.1.0

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 (38) hide show
  1. package/dist/cjs/index.js +702 -954
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/components/Pagination.d.ts +13 -0
  4. package/dist/cjs/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
  5. package/dist/cjs/types/helpers/cloneDeep.d.ts +8 -0
  6. package/dist/cjs/types/index.d.ts +4 -1
  7. package/dist/cjs/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +17 -0
  8. package/dist/cjs/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +21 -0
  9. package/dist/cjs/types/types/LogReviewerFilterState/ContextFilterState.d.ts +10 -0
  10. package/dist/cjs/types/types/LogReviewerFilterState/DateFilterState.d.ts +17 -0
  11. package/dist/cjs/types/types/LogReviewerFilterState/TagFilterState.d.ts +8 -0
  12. package/dist/cjs/types/types/LogReviewerFilterState/index.d.ts +17 -0
  13. package/dist/esm/index.js +701 -956
  14. package/dist/esm/index.js.map +1 -1
  15. package/dist/esm/types/components/Pagination.d.ts +13 -0
  16. package/dist/esm/types/constants/LOG_REVIEW_GET_LOGS_ROUTE.d.ts +6 -0
  17. package/dist/esm/types/helpers/cloneDeep.d.ts +8 -0
  18. package/dist/esm/types/index.d.ts +4 -1
  19. package/dist/esm/types/types/LogReviewerFilterState/ActionErrorFilterState.d.ts +17 -0
  20. package/dist/esm/types/types/LogReviewerFilterState/AdvancedFilterState.d.ts +21 -0
  21. package/dist/esm/types/types/LogReviewerFilterState/ContextFilterState.d.ts +10 -0
  22. package/dist/esm/types/types/LogReviewerFilterState/DateFilterState.d.ts +17 -0
  23. package/dist/esm/types/types/LogReviewerFilterState/TagFilterState.d.ts +8 -0
  24. package/dist/esm/types/types/LogReviewerFilterState/index.d.ts +17 -0
  25. package/dist/index.d.ts +98 -1
  26. package/package.json +2 -1
  27. package/src/components/LogReviewer.tsx +1133 -1307
  28. package/src/components/Pagination.tsx +172 -0
  29. package/src/components/SimpleDateChooser.tsx +95 -337
  30. package/src/constants/LOG_REVIEW_GET_LOGS_ROUTE.ts +9 -0
  31. package/src/helpers/cloneDeep.ts +11 -0
  32. package/src/index.ts +6 -0
  33. package/src/types/LogReviewerFilterState/ActionErrorFilterState.ts +24 -0
  34. package/src/types/LogReviewerFilterState/AdvancedFilterState.ts +36 -0
  35. package/src/types/LogReviewerFilterState/ContextFilterState.ts +16 -0
  36. package/src/types/LogReviewerFilterState/DateFilterState.ts +26 -0
  37. package/src/types/LogReviewerFilterState/TagFilterState.ts +9 -0
  38. package/src/types/LogReviewerFilterState/index.ts +24 -0
@@ -0,0 +1,172 @@
1
+ // Import React
2
+ import React from 'react';
3
+
4
+ // Import FontAwesome
5
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
6
+ import { faArrowLeft, faArrowRight } from '@fortawesome/free-solid-svg-icons';
7
+
8
+ /*------------------------------------------------------------------------*/
9
+ /* -------------------------------- Types ------------------------------- */
10
+ /*------------------------------------------------------------------------*/
11
+
12
+ // Props
13
+ type Props = {
14
+ // Current page number (1-indexed)
15
+ currentPage: number,
16
+ // Total number of pages
17
+ numPages: number,
18
+ // True if a page change is in progress (to disable buttons)
19
+ loading?: boolean,
20
+ /**
21
+ * Handler for when page is changed
22
+ * @param page - the new page number
23
+ */
24
+ onPageChanged: (page: number) => void,
25
+ };
26
+
27
+ /*------------------------------------------------------------------------*/
28
+ /* ------------------------------ Component ----------------------------- */
29
+ /*------------------------------------------------------------------------*/
30
+
31
+ const Pagination: React.FC<Props> = (props: Props) => {
32
+ /*------------------------------------------------------------------------*/
33
+ /* -------------------------------- Setup ------------------------------- */
34
+ /*------------------------------------------------------------------------*/
35
+
36
+ /* -------------- Props ------------- */
37
+
38
+ // Destructure props
39
+ const {
40
+ currentPage,
41
+ numPages,
42
+ loading = false,
43
+ onPageChanged,
44
+ } = props;
45
+
46
+ /*------------------------------------------------------------------------*/
47
+ /* ------------------------------- Render ------------------------------- */
48
+ /*------------------------------------------------------------------------*/
49
+
50
+ // Compute pages to display
51
+ const pages: number[] = [];
52
+ const delta = 2; // how many pages to show on either side of current
53
+ let start = Math.max(1, currentPage - delta);
54
+ let end = Math.min(numPages, currentPage + delta);
55
+
56
+ // If we are too close to the beginning or end, shift the window.
57
+ if (currentPage - delta < 1) {
58
+ end = Math.min(numPages, end + (1 - (currentPage - delta)));
59
+ }
60
+ if (currentPage + delta > numPages) {
61
+ start = Math.max(1, start - ((currentPage + delta) - numPages));
62
+ }
63
+ for (let i = start; i <= end; i++) {
64
+ pages.push(i);
65
+ }
66
+
67
+ // Render
68
+ return (
69
+ <nav
70
+ aria-label="Page navigation for logs"
71
+ className="mt-3"
72
+ >
73
+ <ul className="pagination justify-content-center">
74
+ {/* Previous Button */}
75
+ <li className={`page-item ${(currentPage <= 1 || loading) ? 'disabled' : ''}`}>
76
+ <button
77
+ type="button"
78
+ className="page-link"
79
+ onClick={() => { return onPageChanged(currentPage - 1); }}
80
+ disabled={currentPage <= 1 || loading}
81
+ aria-label="Go to previous page"
82
+ >
83
+ <FontAwesomeIcon icon={faArrowLeft} />
84
+ {' '}
85
+ Prev
86
+ </button>
87
+ </li>
88
+
89
+ {/* First page (if needed) */}
90
+ {(currentPage > 3 && pages[0] !== 1) && (
91
+ <li className="page-item">
92
+ <button
93
+ type="button"
94
+ className="page-link"
95
+ onClick={() => { return onPageChanged(1); }}
96
+ disabled={loading}
97
+ aria-label="Go to page 1"
98
+ >
99
+ 1
100
+ </button>
101
+ </li>
102
+ )}
103
+
104
+ {/* Ellipsis if gap between first page and start of window */}
105
+ {currentPage > 4 && (
106
+ <li className="page-item disabled">
107
+ <span className="page-link">...</span>
108
+ </li>
109
+ )}
110
+
111
+ {/* Page numbers */}
112
+ {pages.map((pageNum) => {
113
+ return (
114
+ <li key={pageNum} className={`page-item ${pageNum === currentPage ? 'active' : ''}`}>
115
+ <button
116
+ type="button"
117
+ className="page-link"
118
+ onClick={() => { return onPageChanged(pageNum); }}
119
+ disabled={loading}
120
+ aria-label={`Go to page ${pageNum}`}
121
+ >
122
+ {pageNum}
123
+ </button>
124
+ </li>
125
+ );
126
+ })}
127
+
128
+ {/* Ellipsis if gap between end of window and last page */}
129
+ {currentPage < numPages - 3 && (
130
+ <li className="page-item disabled">
131
+ <span className="page-link">...</span>
132
+ </li>
133
+ )}
134
+
135
+ {/* Last page (if needed) */}
136
+ {(
137
+ currentPage < numPages - 2
138
+ && pages[pages.length - 1] !== numPages
139
+ ) && (
140
+ <li className="page-item">
141
+ <button
142
+ type="button"
143
+ className="page-link"
144
+ onClick={() => { return onPageChanged(numPages); }}
145
+ disabled={loading}
146
+ aria-label="Go to last page"
147
+ >
148
+ {numPages}
149
+ </button>
150
+ </li>
151
+ )}
152
+
153
+ {/* Next Button */}
154
+ <li className={`page-item ${(currentPage >= numPages || loading) ? 'disabled' : ''}`}>
155
+ <button
156
+ type="button"
157
+ className="page-link"
158
+ onClick={() => { return onPageChanged(currentPage + 1); }}
159
+ disabled={currentPage >= numPages || loading}
160
+ aria-label="Go to next page"
161
+ >
162
+ Next
163
+ {' '}
164
+ <FontAwesomeIcon icon={faArrowRight} />
165
+ </button>
166
+ </li>
167
+ </ul>
168
+ </nav>
169
+ );
170
+ };
171
+
172
+ export default Pagination;
@@ -5,10 +5,7 @@
5
5
  */
6
6
 
7
7
  // Import React
8
- import React, { useReducer } from 'react';
9
-
10
- // Import AppWrapper helpers
11
- import { confirm } from './AppWrapper';
8
+ import React from 'react';
12
9
 
13
10
  // Import helpers
14
11
  import getOrdinal from '../helpers/getOrdinal';
@@ -54,86 +51,32 @@ type Props = {
54
51
  };
55
52
 
56
53
  /*------------------------------------------------------------------------*/
57
- /* -------------------------------- State ------------------------------- */
54
+ /* ------------------------------ Component ----------------------------- */
58
55
  /*------------------------------------------------------------------------*/
59
56
 
60
- /* -------------- Views ------------- */
61
-
62
- enum View {
63
- // Date chooser
64
- DateChooser = 'DateChooser',
65
- // Invalid date
66
- InvalidDate = 'InvalidDate',
67
- }
68
-
69
- /* -------- State Definition -------- */
70
-
71
- type State = {
72
- view: View,
73
- };
74
-
75
- /* ------------- Actions ------------ */
76
-
77
- // Types of actions
78
- enum ActionType {
79
- // Fix invalid date so it is now in range
80
- FixInvalidDate = 'FixInvalidDate',
81
- }
82
-
83
- // Action definitions
84
- type Action = {
85
- // Action type
86
- type: ActionType.FixInvalidDate,
87
- };
88
-
89
- /**
90
- * Reducer that executes actions
91
- * @author Gardenia Liu
92
- * @param state current state
93
- * @param action action to execute
94
- * @returns updated state
95
- */
96
- const reducer = (state: State, action: Action): State => {
97
- switch (action.type) {
98
- case ActionType.FixInvalidDate: {
99
- return {
100
- ...state,
101
- view: View.DateChooser,
102
- };
103
- }
104
- default: {
105
- return state;
106
- }
107
- }
108
- };
57
+ const SimpleDateChooser: React.FC<Props> = (props) => {
58
+ /*------------------------------------------------------------------------*/
59
+ /* -------------------------------- Setup ------------------------------- */
60
+ /*------------------------------------------------------------------------*/
109
61
 
110
- /*------------------------------------------------------------------------*/
111
- /* --------------------------- Static Helpers --------------------------- */
112
- /*------------------------------------------------------------------------*/
62
+ /* -------------- Props ------------- */
113
63
 
114
- /**
115
- * Get the list of choices in the date chooser given props
116
- * @author Gardenia Liu
117
- * @author Gabe Abrams
118
- * @param opts object containing all arguments
119
- * @param opts.numMonthsToShow number of months to show
120
- * @param opts.dontAllowPast if true, the user isn't allowed to select dates in the past
121
- * @param opts.dontAllowFuture if true, the user isn't allowed to select dates in the future
122
- * @returns choices
123
- */
124
- const getChoices = (
125
- opts: {
126
- numMonthsToShow?: number,
127
- dontAllowPast?: boolean,
128
- dontAllowFuture?: boolean,
129
- },
130
- ) => {
131
- // Destructure props
132
64
  const {
65
+ ariaLabel,
66
+ name,
67
+ onChange,
68
+ numMonthsToShow = 6,
133
69
  dontAllowFuture,
134
70
  dontAllowPast,
135
- numMonthsToShow = 6,
136
- } = opts;
71
+ } = props;
72
+
73
+ /*------------------------------------------------------------------------*/
74
+ /* ------------------------------- Render ------------------------------- */
75
+ /*------------------------------------------------------------------------*/
76
+
77
+ /*----------------------------------------*/
78
+ /* --------------- Main UI -------------- */
79
+ /*----------------------------------------*/
137
80
 
138
81
  // Determine the set of choices allowed
139
82
  const today = getTimeInfoInET();
@@ -228,278 +171,93 @@ const getChoices = (
228
171
  });
229
172
  }
230
173
 
231
- // Return choices
232
- return choices;
233
- };
234
-
235
- /**
236
- * Checks whether a given date is outside the valid range of allowed choices
237
- * @author Gardenia Liu
238
- * @param opts object containing all arguments
239
- * @param opts.month 1-indexed month
240
- * @param opts.day day of the month
241
- * @param opts.year full year
242
- * @param opts.choices valid date choices
243
- * @returns true if date is out of range
244
- */
245
- const isDateOutOfRange = (
246
- opts: {
247
- month: number,
248
- day: number,
249
- year: number,
250
- choices: {
251
- month: number,
252
- year: number,
253
- days: number[]
254
- }[],
255
- },
256
- ): boolean => {
174
+ // Create choice options
257
175
  const {
258
176
  month,
259
177
  day,
260
178
  year,
261
- choices,
262
- } = opts;
263
-
264
- return !choices.some((choice) => {
265
- return (
266
- choice.month === month
267
- && choice.year === year
268
- && choice.days.includes(day)
269
- );
270
- });
271
- };
272
-
273
- /*------------------------------------------------------------------------*/
274
- /* ------------------------------ Component ----------------------------- */
275
- /*------------------------------------------------------------------------*/
276
-
277
- const SimpleDateChooser: React.FC<Props> = (props) => {
278
- /*------------------------------------------------------------------------*/
279
- /* -------------------------------- Setup ------------------------------- */
280
- /*------------------------------------------------------------------------*/
281
-
282
- /* -------------- Props ------------- */
283
-
284
- const {
285
- ariaLabel,
286
- name,
287
- dontAllowPast,
288
- dontAllowFuture,
289
- numMonthsToShow,
290
- onChange,
291
- month,
292
- day,
293
- year,
294
179
  } = props;
295
-
296
- // Get choices
297
- const choices = getChoices({
298
- numMonthsToShow,
299
- dontAllowPast,
300
- dontAllowFuture,
301
- });
302
-
303
- /* -------------- State ------------- */
304
-
305
- // Check if the currently selected date is out of range
306
- const currentSelectedDateOutOfRange = isDateOutOfRange({
307
- month,
308
- day,
309
- year,
310
- choices,
311
- });
312
-
313
- // Initial state
314
- const initialState: State = {
315
- view: (
316
- currentSelectedDateOutOfRange
317
- ? View.InvalidDate
318
- : View.DateChooser
319
- ),
320
- };
321
-
322
- // Initialize state
323
- const [state, dispatch] = useReducer(reducer, initialState);
324
-
325
- // Destructure common state
326
- const {
327
- view,
328
- } = state;
329
-
330
- /*------------------------------------------------------------------------*/
331
- /* ------------------------- Component Functions ------------------------ */
332
- /*------------------------------------------------------------------------*/
333
-
334
- /**
335
- * Ask the user if they want to edit an invalid date
336
- * @author Gardenia Liu
337
- * @author Gabe Abrams
338
- */
339
- const askToEditInvalidDate = async () => {
340
- // Ask the user if they want to edit the date
341
- const confirmed = await confirm(
342
- 'Are you sure?',
343
- 'The current date is outside the normal range. If you edit it, you\'ll need to choose a new date in the normal range.',
344
- {
345
- confirmButtonText: 'Edit Date',
346
- },
180
+ const monthOptions: React.ReactNode[] = [];
181
+ const dayOptions: React.ReactNode[] = [];
182
+ choices.forEach((choice) => {
183
+ // Create month option
184
+ monthOptions.push(
185
+ <option
186
+ key={`${choice.year}-${choice.month}`}
187
+ value={`${choice.month}-${choice.year}`}
188
+ aria-label={`choose ${choice.choiceName}`}
189
+ onSelect={() => {
190
+ onChange(choice.month, choice.days[0], choice.year);
191
+ }}
192
+ >
193
+ {choice.choiceName}
194
+ </option>,
347
195
  );
348
196
 
349
- // Check if user confirmed
350
- if (confirmed) {
351
- // Update the date to today
352
- const today = getTimeInfoInET();
353
- onChange(today.month, today.day, today.year);
354
-
355
- // Update state
356
- dispatch({
357
- type: ActionType.FixInvalidDate,
197
+ // This is the currently selected month
198
+ if (month === choice.month) {
199
+ // Create day options
200
+ choice.days.forEach((dayChoice) => {
201
+ const ordinal = getOrdinal(dayChoice);
202
+ dayOptions.push(
203
+ <option
204
+ key={`${choice.year}-${choice.month}-${dayChoice}`}
205
+ value={dayChoice}
206
+ aria-label={`choose date ${dayChoice}`}
207
+ >
208
+ {dayChoice}
209
+ {ordinal}
210
+ </option>,
211
+ );
358
212
  });
359
213
  }
360
- };
361
-
362
- /*------------------------------------------------------------------------*/
363
- /* ------------------------------- Render ------------------------------- */
364
- /*------------------------------------------------------------------------*/
365
-
366
- /*----------------------------------------*/
367
- /* ---------------- Views --------------- */
368
- /*----------------------------------------*/
369
-
370
- // Body that will be filled with the current view
371
- let body: React.ReactNode;
372
-
373
- /* ---------- DateChooser ---------- */
374
-
375
- if (view === View.DateChooser) {
376
- // Create lists of options
377
- const monthOptions: React.ReactNode[] = [];
378
- const dayOptions: React.ReactNode[] = [];
379
-
380
- // Render each option and add it to the list
381
- choices.forEach((choice) => {
382
- // Create month option
383
- monthOptions.push(
384
- <option
385
- key={`${choice.year}-${choice.month}`}
386
- value={`${choice.month}-${choice.year}`}
387
- aria-label={`choose ${choice.choiceName}`}
388
- onSelect={() => {
389
- onChange(choice.month, choice.days[0], choice.year);
390
- }}
391
- >
392
- {choice.choiceName}
393
- </option>,
394
- );
214
+ });
395
215
 
396
- // This is the currently selected month
397
- if (month === choice.month) {
398
- // Create day options
399
- choice.days.forEach((dayChoice) => {
400
- const ordinal = getOrdinal(dayChoice);
401
- dayOptions.push(
402
- <option
403
- key={`${choice.year}-${choice.month}-${dayChoice}`}
404
- value={dayChoice}
405
- aria-label={`choose date ${dayChoice}`}
406
- >
407
- {dayChoice}
408
- {ordinal}
409
- </option>,
216
+ return (
217
+ <div
218
+ className="SimpleDateChooser d-inline-block"
219
+ aria-label={`date chooser with selected date: ${month}/${day}/${year}`}
220
+ >
221
+ {/* Month Chooser */}
222
+ <select
223
+ aria-label={`month for ${ariaLabel}`}
224
+ className="custom-select d-inline-block mr-1"
225
+ style={{ width: 'auto' }}
226
+ id={`SimpleDateChooser-${name}-month`}
227
+ value={`${month}-${year}`}
228
+ onChange={(e) => {
229
+ const choice = choices[e.target.selectedIndex];
230
+
231
+ // Change day, month, and year
232
+ onChange(
233
+ choice.month,
234
+ choice.days[0],
235
+ choice.year,
410
236
  );
411
- });
412
- }
413
- });
414
-
415
- // Create body
416
- body = (
417
- <div
418
- className="SimpleDateChooser-inner-container d-inline-block"
419
- aria-label={`date chooser with selected date: ${month}/${day}/${year}`}
237
+ }}
420
238
  >
421
- {/* Month Chooser */}
422
- <select
423
- aria-label={`month for ${ariaLabel}`}
424
- className="custom-select d-inline-block mr-1"
425
- style={{ width: 'auto' }}
426
- id={`SimpleDateChooser-${name}-month`}
427
- value={`${month}-${year}`}
428
- onChange={(e) => {
429
- const choice = choices[e.target.selectedIndex];
430
-
431
- // Change day, month, and year
432
- onChange(
433
- choice.month,
434
- choice.days[0],
435
- choice.year,
436
- );
437
- }}
438
- >
439
- {monthOptions}
440
- </select>
441
-
442
- {/* Day Chooser */}
443
- <select
444
- aria-label={`day for ${ariaLabel}`}
445
- className="custom-select d-inline-block"
446
- style={{ width: 'auto' }}
447
- id={`SimpleDateChooser-${name}-day`}
448
- value={day}
449
- onChange={(e) => {
450
- // Only change the day
451
- onChange(
452
- month,
453
- Number.parseInt(e.target.value, 10),
454
- year,
455
- );
456
- }}
457
- >
458
- {dayOptions}
459
- </select>
460
- </div>
461
- );
462
- }
463
-
464
- /* --------- DateOutOfRange --------- */
465
-
466
- if (view === View.InvalidDate) {
467
- body = (
468
- <div className="SimpleDateChooser-inner-container d-inline-block">
469
- <button
470
- type="button"
471
- className="btn btn-light"
472
- onClick={askToEditInvalidDate}
473
- aria-label={`edit date for ${ariaLabel}`}
474
- >
475
- {getMonthName(month).full}
476
- {' '}
477
- {day}
478
- {getOrdinal(day)}
479
- ,
480
- {' '}
481
- {year}
482
- </button>
483
- <button
484
- type="button"
485
- className="btn btn-secondary"
486
- onClick={askToEditInvalidDate}
487
- aria-label={`edit date for ${ariaLabel}`}
488
- >
489
- Edit
490
- </button>
491
- </div>
492
- );
493
- }
494
-
495
- /*----------------------------------------*/
496
- /* --------------- Main UI -------------- */
497
- /*----------------------------------------*/
498
-
499
- return (
500
- <span className="SimpleDateChooser-outer-container">
501
- {body}
502
- </span>
239
+ {monthOptions}
240
+ </select>
241
+
242
+ {/* Day Chooser */}
243
+ <select
244
+ aria-label={`day for ${ariaLabel}`}
245
+ className="custom-select d-inline-block"
246
+ style={{ width: 'auto' }}
247
+ id={`SimpleDateChooser-${name}-day`}
248
+ value={day}
249
+ onChange={(e) => {
250
+ // Only change the day
251
+ onChange(
252
+ month,
253
+ Number.parseInt(e.target.value, 10),
254
+ year,
255
+ );
256
+ }}
257
+ >
258
+ {dayOptions}
259
+ </select>
260
+ </div>
503
261
  );
504
262
  };
505
263
 
@@ -0,0 +1,9 @@
1
+ import ROUTE_PATH_PREFIX from './ROUTE_PATH_PREFIX';
2
+
3
+ /**
4
+ * Path of the route for getting logs for log review
5
+ * @author Gabe Abrams
6
+ */
7
+ const LOG_REVIEW_GET_LOGS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs`;
8
+
9
+ export default LOG_REVIEW_GET_LOGS_ROUTE;
@@ -0,0 +1,11 @@
1
+ import clone from 'nanoclone';
2
+
3
+ /**
4
+ * Deeply clones an object
5
+ * @author Yuen Ler Chow
6
+ * @param obj the object to clone
7
+ * @returns a deep clone of the object
8
+ */
9
+ const cloneDeep: <T>(obj: T) => T = clone;
10
+
11
+ export default cloneDeep;