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
package/dist/esm/index.js CHANGED
@@ -1,8 +1,9 @@
1
1
  import * as React from 'react';
2
2
  import React__default, { useState, useRef, useEffect, useReducer, forwardRef, useContext, useLayoutEffect, createContext, useMemo, useCallback, Component, Fragment } from 'react';
3
- import { faExclamationTriangle, faCircle, faHourglassEnd, faDotCircle, faCheckSquare, faHourglass, faClipboard, faChevronDown, faChevronRight, faCloudDownloadAlt, faMinus, faCheckCircle, faXmarkCircle, faSort, faSortDown, faSortUp, faCalendar, faTag, faHammer, faList, faTimes, faSave, faTrash, faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
3
+ import { faExclamationTriangle, faCircle, faHourglassEnd, faDotCircle, faCheckSquare, faHourglass, faClipboard, faChevronDown, faChevronRight, faCloudDownloadAlt, faMinus, faCheckCircle, faXmarkCircle, faSort, faSortDown, faSortUp, faArrowLeft, faArrowRight, faCalendar, faTag, faHammer, faList, faTimes, faSearch, faSave, faTrash, faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
4
4
  import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
5
5
  import { faCircle as faCircle$1, faSquareMinus, faSquare } from '@fortawesome/free-regular-svg-icons';
6
+ import clone from 'nanoclone';
6
7
 
7
8
  /*! *****************************************************************************
8
9
  Copyright (c) Microsoft Corporation.
@@ -2630,56 +2631,20 @@ const getTimeInfoInET = (dateOrTimestamp) => {
2630
2631
  * @author Gardenia Liu
2631
2632
  */
2632
2633
  /*------------------------------------------------------------------------*/
2633
- /* -------------------------------- State ------------------------------- */
2634
- /*------------------------------------------------------------------------*/
2635
- /* -------------- Views ------------- */
2636
- var View;
2637
- (function (View) {
2638
- // Date chooser
2639
- View["DateChooser"] = "DateChooser";
2640
- // Invalid date
2641
- View["InvalidDate"] = "InvalidDate";
2642
- })(View || (View = {}));
2643
- /* ------------- Actions ------------ */
2644
- // Types of actions
2645
- var ActionType$a;
2646
- (function (ActionType) {
2647
- // Fix invalid date so it is now in range
2648
- ActionType["FixInvalidDate"] = "FixInvalidDate";
2649
- })(ActionType$a || (ActionType$a = {}));
2650
- /**
2651
- * Reducer that executes actions
2652
- * @author Gardenia Liu
2653
- * @param state current state
2654
- * @param action action to execute
2655
- * @returns updated state
2656
- */
2657
- const reducer$b = (state, action) => {
2658
- switch (action.type) {
2659
- case ActionType$a.FixInvalidDate: {
2660
- return Object.assign(Object.assign({}, state), { view: View.DateChooser });
2661
- }
2662
- default: {
2663
- return state;
2664
- }
2665
- }
2666
- };
2667
- /*------------------------------------------------------------------------*/
2668
- /* --------------------------- Static Helpers --------------------------- */
2634
+ /* ------------------------------ Component ----------------------------- */
2669
2635
  /*------------------------------------------------------------------------*/
2670
- /**
2671
- * Get the list of choices in the date chooser given props
2672
- * @author Gardenia Liu
2673
- * @author Gabe Abrams
2674
- * @param opts object containing all arguments
2675
- * @param opts.numMonthsToShow number of months to show
2676
- * @param opts.dontAllowPast if true, the user isn't allowed to select dates in the past
2677
- * @param opts.dontAllowFuture if true, the user isn't allowed to select dates in the future
2678
- * @returns choices
2679
- */
2680
- const getChoices = (opts) => {
2681
- // Destructure props
2682
- const { dontAllowFuture, dontAllowPast, numMonthsToShow = 6, } = opts;
2636
+ const SimpleDateChooser = (props) => {
2637
+ /*------------------------------------------------------------------------*/
2638
+ /* -------------------------------- Setup ------------------------------- */
2639
+ /*------------------------------------------------------------------------*/
2640
+ /* -------------- Props ------------- */
2641
+ const { ariaLabel, name, onChange, numMonthsToShow = 6, dontAllowFuture, dontAllowPast, } = props;
2642
+ /*------------------------------------------------------------------------*/
2643
+ /* ------------------------------- Render ------------------------------- */
2644
+ /*------------------------------------------------------------------------*/
2645
+ /*----------------------------------------*/
2646
+ /* --------------- Main UI -------------- */
2647
+ /*----------------------------------------*/
2683
2648
  // Determine the set of choices allowed
2684
2649
  const today = getTimeInfoInET();
2685
2650
  const choices = [];
@@ -2754,143 +2719,36 @@ const getChoices = (opts) => {
2754
2719
  days,
2755
2720
  });
2756
2721
  }
2757
- // Return choices
2758
- return choices;
2759
- };
2760
- /**
2761
- * Checks whether a given date is outside the valid range of allowed choices
2762
- * @author Gardenia Liu
2763
- * @param opts object containing all arguments
2764
- * @param opts.month 1-indexed month
2765
- * @param opts.day day of the month
2766
- * @param opts.year full year
2767
- * @param opts.choices valid date choices
2768
- * @returns true if date is out of range
2769
- */
2770
- const isDateOutOfRange = (opts) => {
2771
- const { month, day, year, choices, } = opts;
2772
- return !choices.some((choice) => {
2773
- return (choice.month === month
2774
- && choice.year === year
2775
- && choice.days.includes(day));
2776
- });
2777
- };
2778
- /*------------------------------------------------------------------------*/
2779
- /* ------------------------------ Component ----------------------------- */
2780
- /*------------------------------------------------------------------------*/
2781
- const SimpleDateChooser = (props) => {
2782
- /*------------------------------------------------------------------------*/
2783
- /* -------------------------------- Setup ------------------------------- */
2784
- /*------------------------------------------------------------------------*/
2785
- /* -------------- Props ------------- */
2786
- const { ariaLabel, name, dontAllowPast, dontAllowFuture, numMonthsToShow, onChange, month, day, year, } = props;
2787
- // Get choices
2788
- const choices = getChoices({
2789
- numMonthsToShow,
2790
- dontAllowPast,
2791
- dontAllowFuture,
2792
- });
2793
- /* -------------- State ------------- */
2794
- // Check if the currently selected date is out of range
2795
- const currentSelectedDateOutOfRange = isDateOutOfRange({
2796
- month,
2797
- day,
2798
- year,
2799
- choices,
2800
- });
2801
- // Initial state
2802
- const initialState = {
2803
- view: (currentSelectedDateOutOfRange
2804
- ? View.InvalidDate
2805
- : View.DateChooser),
2806
- };
2807
- // Initialize state
2808
- const [state, dispatch] = useReducer(reducer$b, initialState);
2809
- // Destructure common state
2810
- const { view, } = state;
2811
- /*------------------------------------------------------------------------*/
2812
- /* ------------------------- Component Functions ------------------------ */
2813
- /*------------------------------------------------------------------------*/
2814
- /**
2815
- * Ask the user if they want to edit an invalid date
2816
- * @author Gardenia Liu
2817
- * @author Gabe Abrams
2818
- */
2819
- const askToEditInvalidDate = () => __awaiter(void 0, void 0, void 0, function* () {
2820
- // Ask the user if they want to edit the date
2821
- const confirmed = yield confirm('Are you sure?', 'The current date is outside the normal range. If you edit it, you\'ll need to choose a new date in the normal range.', {
2822
- confirmButtonText: 'Edit Date',
2823
- });
2824
- // Check if user confirmed
2825
- if (confirmed) {
2826
- // Update the date to today
2827
- const today = getTimeInfoInET();
2828
- onChange(today.month, today.day, today.year);
2829
- // Update state
2830
- dispatch({
2831
- type: ActionType$a.FixInvalidDate,
2722
+ // Create choice options
2723
+ const { month, day, year, } = props;
2724
+ const monthOptions = [];
2725
+ const dayOptions = [];
2726
+ choices.forEach((choice) => {
2727
+ // Create month option
2728
+ monthOptions.push(React__default.createElement("option", { key: `${choice.year}-${choice.month}`, value: `${choice.month}-${choice.year}`, "aria-label": `choose ${choice.choiceName}`, onSelect: () => {
2729
+ onChange(choice.month, choice.days[0], choice.year);
2730
+ } }, choice.choiceName));
2731
+ // This is the currently selected month
2732
+ if (month === choice.month) {
2733
+ // Create day options
2734
+ choice.days.forEach((dayChoice) => {
2735
+ const ordinal = getOrdinal(dayChoice);
2736
+ dayOptions.push(React__default.createElement("option", { key: `${choice.year}-${choice.month}-${dayChoice}`, value: dayChoice, "aria-label": `choose date ${dayChoice}` },
2737
+ dayChoice,
2738
+ ordinal));
2832
2739
  });
2833
2740
  }
2834
2741
  });
2835
- /*------------------------------------------------------------------------*/
2836
- /* ------------------------------- Render ------------------------------- */
2837
- /*------------------------------------------------------------------------*/
2838
- /*----------------------------------------*/
2839
- /* ---------------- Views --------------- */
2840
- /*----------------------------------------*/
2841
- // Body that will be filled with the current view
2842
- let body;
2843
- /* ---------- DateChooser ---------- */
2844
- if (view === View.DateChooser) {
2845
- // Create lists of options
2846
- const monthOptions = [];
2847
- const dayOptions = [];
2848
- // Render each option and add it to the list
2849
- choices.forEach((choice) => {
2850
- // Create month option
2851
- monthOptions.push(React__default.createElement("option", { key: `${choice.year}-${choice.month}`, value: `${choice.month}-${choice.year}`, "aria-label": `choose ${choice.choiceName}`, onSelect: () => {
2852
- onChange(choice.month, choice.days[0], choice.year);
2853
- } }, choice.choiceName));
2854
- // This is the currently selected month
2855
- if (month === choice.month) {
2856
- // Create day options
2857
- choice.days.forEach((dayChoice) => {
2858
- const ordinal = getOrdinal(dayChoice);
2859
- dayOptions.push(React__default.createElement("option", { key: `${choice.year}-${choice.month}-${dayChoice}`, value: dayChoice, "aria-label": `choose date ${dayChoice}` },
2860
- dayChoice,
2861
- ordinal));
2862
- });
2863
- }
2864
- });
2865
- // Create body
2866
- body = (React__default.createElement("div", { className: "SimpleDateChooser-inner-container d-inline-block", "aria-label": `date chooser with selected date: ${month}/${day}/${year}` },
2867
- React__default.createElement("select", { "aria-label": `month for ${ariaLabel}`, className: "custom-select d-inline-block mr-1", style: { width: 'auto' }, id: `SimpleDateChooser-${name}-month`, value: `${month}-${year}`, onChange: (e) => {
2868
- const choice = choices[e.target.selectedIndex];
2869
- // Change day, month, and year
2870
- onChange(choice.month, choice.days[0], choice.year);
2871
- } }, monthOptions),
2872
- React__default.createElement("select", { "aria-label": `day for ${ariaLabel}`, className: "custom-select d-inline-block", style: { width: 'auto' }, id: `SimpleDateChooser-${name}-day`, value: day, onChange: (e) => {
2873
- // Only change the day
2874
- onChange(month, Number.parseInt(e.target.value, 10), year);
2875
- } }, dayOptions)));
2876
- }
2877
- /* --------- DateOutOfRange --------- */
2878
- if (view === View.InvalidDate) {
2879
- body = (React__default.createElement("div", { className: "SimpleDateChooser-inner-container d-inline-block" },
2880
- React__default.createElement("button", { type: "button", className: "btn btn-light", onClick: askToEditInvalidDate, "aria-label": `edit date for ${ariaLabel}` },
2881
- getMonthName(month).full,
2882
- ' ',
2883
- day,
2884
- getOrdinal(day),
2885
- ",",
2886
- ' ',
2887
- year),
2888
- React__default.createElement("button", { type: "button", className: "btn btn-secondary", onClick: askToEditInvalidDate, "aria-label": `edit date for ${ariaLabel}` }, "Edit")));
2889
- }
2890
- /*----------------------------------------*/
2891
- /* --------------- Main UI -------------- */
2892
- /*----------------------------------------*/
2893
- return (React__default.createElement("span", { className: "SimpleDateChooser-outer-container" }, body));
2742
+ return (React__default.createElement("div", { className: "SimpleDateChooser d-inline-block", "aria-label": `date chooser with selected date: ${month}/${day}/${year}` },
2743
+ React__default.createElement("select", { "aria-label": `month for ${ariaLabel}`, className: "custom-select d-inline-block mr-1", style: { width: 'auto' }, id: `SimpleDateChooser-${name}-month`, value: `${month}-${year}`, onChange: (e) => {
2744
+ const choice = choices[e.target.selectedIndex];
2745
+ // Change day, month, and year
2746
+ onChange(choice.month, choice.days[0], choice.year);
2747
+ } }, monthOptions),
2748
+ React__default.createElement("select", { "aria-label": `day for ${ariaLabel}`, className: "custom-select d-inline-block", style: { width: 'auto' }, id: `SimpleDateChooser-${name}-day`, value: day, onChange: (e) => {
2749
+ // Only change the day
2750
+ onChange(month, Number.parseInt(e.target.value, 10), year);
2751
+ } }, dayOptions)));
2894
2752
  };
2895
2753
 
2896
2754
  /**
@@ -3767,10 +3625,10 @@ const getHumanReadableDate = (dateOrTimestamp) => {
3767
3625
  };
3768
3626
 
3769
3627
  /**
3770
- * Path of the route for storing client-side logs
3628
+ * Path of the route for getting logs for log review
3771
3629
  * @author Gabe Abrams
3772
3630
  */
3773
- const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
3631
+ const LOG_REVIEW_GET_LOGS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs`;
3774
3632
 
3775
3633
  /**
3776
3634
  * Source of a log event
@@ -4375,6 +4233,71 @@ const IntelliTable = (props) => {
4375
4233
  } }, table)));
4376
4234
  };
4377
4235
 
4236
+ // Import React
4237
+ /*------------------------------------------------------------------------*/
4238
+ /* ------------------------------ Component ----------------------------- */
4239
+ /*------------------------------------------------------------------------*/
4240
+ const Pagination = (props) => {
4241
+ /*------------------------------------------------------------------------*/
4242
+ /* -------------------------------- Setup ------------------------------- */
4243
+ /*------------------------------------------------------------------------*/
4244
+ /* -------------- Props ------------- */
4245
+ // Destructure props
4246
+ const { currentPage, numPages, loading = false, onPageChanged, } = props;
4247
+ /*------------------------------------------------------------------------*/
4248
+ /* ------------------------------- Render ------------------------------- */
4249
+ /*------------------------------------------------------------------------*/
4250
+ // Compute pages to display
4251
+ const pages = [];
4252
+ const delta = 2; // how many pages to show on either side of current
4253
+ let start = Math.max(1, currentPage - delta);
4254
+ let end = Math.min(numPages, currentPage + delta);
4255
+ // If we are too close to the beginning or end, shift the window.
4256
+ if (currentPage - delta < 1) {
4257
+ end = Math.min(numPages, end + (1 - (currentPage - delta)));
4258
+ }
4259
+ if (currentPage + delta > numPages) {
4260
+ start = Math.max(1, start - ((currentPage + delta) - numPages));
4261
+ }
4262
+ for (let i = start; i <= end; i++) {
4263
+ pages.push(i);
4264
+ }
4265
+ // Render
4266
+ return (React__default.createElement("nav", { "aria-label": "Page navigation for logs", className: "mt-3" },
4267
+ React__default.createElement("ul", { className: "pagination justify-content-center" },
4268
+ React__default.createElement("li", { className: `page-item ${(currentPage <= 1 || loading) ? 'disabled' : ''}` },
4269
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(currentPage - 1); }, disabled: currentPage <= 1 || loading, "aria-label": "Go to previous page" },
4270
+ React__default.createElement(FontAwesomeIcon, { icon: faArrowLeft }),
4271
+ ' ',
4272
+ "Prev")),
4273
+ (currentPage > 3 && pages[0] !== 1) && (React__default.createElement("li", { className: "page-item" },
4274
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(1); }, disabled: loading, "aria-label": "Go to page 1" }, "1"))),
4275
+ currentPage > 4 && (React__default.createElement("li", { className: "page-item disabled" },
4276
+ React__default.createElement("span", { className: "page-link" }, "..."))),
4277
+ pages.map((pageNum) => {
4278
+ return (React__default.createElement("li", { key: pageNum, className: `page-item ${pageNum === currentPage ? 'active' : ''}` },
4279
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(pageNum); }, disabled: loading, "aria-label": `Go to page ${pageNum}` }, pageNum)));
4280
+ }),
4281
+ currentPage < numPages - 3 && (React__default.createElement("li", { className: "page-item disabled" },
4282
+ React__default.createElement("span", { className: "page-link" }, "..."))),
4283
+ (currentPage < numPages - 2
4284
+ && pages[pages.length - 1] !== numPages) && (React__default.createElement("li", { className: "page-item" },
4285
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(numPages); }, disabled: loading, "aria-label": "Go to last page" }, numPages))),
4286
+ React__default.createElement("li", { className: `page-item ${(currentPage >= numPages || loading) ? 'disabled' : ''}` },
4287
+ React__default.createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(currentPage + 1); }, disabled: currentPage >= numPages || loading, "aria-label": "Go to next page" },
4288
+ "Next",
4289
+ ' ',
4290
+ React__default.createElement(FontAwesomeIcon, { icon: faArrowRight }))))));
4291
+ };
4292
+
4293
+ /**
4294
+ * Deeply clones an object
4295
+ * @author Yuen Ler Chow
4296
+ * @param obj the object to clone
4297
+ * @returns a deep clone of the object
4298
+ */
4299
+ const cloneDeep = clone;
4300
+
4378
4301
  /**
4379
4302
  * Log reviewer panel that allows users (must be approved admins) to
4380
4303
  * review logs written by dce-reactkit
@@ -4730,7 +4653,7 @@ var ActionType$6;
4730
4653
  (function (ActionType) {
4731
4654
  // Show the loading bar
4732
4655
  ActionType["StartLoading"] = "start-loading";
4733
- // Finish loading one or more months of logs
4656
+ // Finish loading logs
4734
4657
  ActionType["FinishLoading"] = "finish-loading";
4735
4658
  // Reset filters to initial values
4736
4659
  ActionType["ResetFilters"] = "reset-filters";
@@ -4748,6 +4671,14 @@ var ActionType$6;
4748
4671
  ActionType["UpdateActionErrorFilterState"] = "update-action-error-filter-state";
4749
4672
  // Update the advanced filter state
4750
4673
  ActionType["UpdateAdvancedFilterState"] = "update-advanced-filter-state";
4674
+ // Set has another page
4675
+ ActionType["SetHasAnotherPage"] = "set-has-another-page";
4676
+ // Set the number of pages
4677
+ ActionType["SetNumPages"] = "set-num-pages";
4678
+ // Set page number
4679
+ ActionType["SetPageNumber"] = "set-page-number";
4680
+ // Reset user made filter change indicator
4681
+ ActionType["ResetUserMadeFilterChange"] = "reset-user-made-filter-change";
4751
4682
  })(ActionType$6 || (ActionType$6 = {}));
4752
4683
  /**
4753
4684
  * Reducer that executes actions
@@ -4761,7 +4692,7 @@ const reducer$7 = (state, action) => {
4761
4692
  return Object.assign(Object.assign({}, state), { loading: true });
4762
4693
  }
4763
4694
  case ActionType$6.FinishLoading: {
4764
- return Object.assign(Object.assign({}, state), { loading: false, logMap: action.logMap });
4695
+ return Object.assign(Object.assign({}, state), { loading: false, logs: action.logs });
4765
4696
  }
4766
4697
  case ActionType$6.ToggleFilterDrawer: {
4767
4698
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: (state.expandedFilterDrawer === action.filterDrawer
@@ -4772,22 +4703,34 @@ const reducer$7 = (state, action) => {
4772
4703
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: undefined });
4773
4704
  }
4774
4705
  case ActionType$6.ResetFilters: {
4775
- return Object.assign(Object.assign({}, state), { dateFilterState: action.initDateFilterState, contextFilterState: action.initContextFilterState, tagFilterState: action.initTagFilterState, actionErrorFilterState: action.initActionErrorFilterState, advancedFilterState: action.initAdvancedFilterState });
4706
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.initDateFilterState, pendingContextFilterState: action.initContextFilterState, pendingTagFilterState: action.initTagFilterState, pendingActionErrorFilterState: action.initActionErrorFilterState, pendingAdvancedFilterState: action.initAdvancedFilterState, pageNumber: 1 });
4776
4707
  }
4777
4708
  case ActionType$6.UpdateDateFilterState: {
4778
- return Object.assign(Object.assign({}, state), { dateFilterState: action.dateFilterState });
4709
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.dateFilterState, userMadeFilterChange: true });
4779
4710
  }
4780
4711
  case ActionType$6.UpdateContextFilterState: {
4781
- return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
4712
+ return Object.assign(Object.assign({}, state), { pendingContextFilterState: action.contextFilterState, userMadeFilterChange: true });
4782
4713
  }
4783
4714
  case ActionType$6.UpdateTagFilterState: {
4784
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
4715
+ return Object.assign(Object.assign({}, state), { pendingTagFilterState: action.tagFilterState, userMadeFilterChange: true });
4785
4716
  }
4786
4717
  case ActionType$6.UpdateActionErrorFilterState: {
4787
- return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
4718
+ return Object.assign(Object.assign({}, state), { pendingActionErrorFilterState: action.actionErrorFilterState, userMadeFilterChange: true });
4788
4719
  }
4789
4720
  case ActionType$6.UpdateAdvancedFilterState: {
4790
- return Object.assign(Object.assign({}, state), { advancedFilterState: action.advancedFilterState });
4721
+ return Object.assign(Object.assign({}, state), { pendingAdvancedFilterState: action.advancedFilterState, userMadeFilterChange: true });
4722
+ }
4723
+ case ActionType$6.SetHasAnotherPage: {
4724
+ return Object.assign(Object.assign({}, state), { hasAnotherPage: action.hasAnotherPage });
4725
+ }
4726
+ case ActionType$6.SetNumPages: {
4727
+ return Object.assign(Object.assign({}, state), { numPages: action.numPages });
4728
+ }
4729
+ case ActionType$6.SetPageNumber: {
4730
+ return Object.assign(Object.assign({}, state), { pageNumber: action.pageNumber });
4731
+ }
4732
+ case ActionType$6.ResetUserMadeFilterChange: {
4733
+ return Object.assign(Object.assign({}, state), { userMadeFilterChange: false });
4791
4734
  }
4792
4735
  default: {
4793
4736
  return state;
@@ -4858,7 +4801,7 @@ const LogReviewer = (props) => {
4858
4801
  else {
4859
4802
  // Case: subcontexts exist
4860
4803
  initContextFilterState[context] = {};
4861
- Object.values(contextMap[context]).forEach((subcontext) => {
4804
+ Object.keys(contextMap[context]).forEach((subcontext) => {
4862
4805
  // Skip self ("_")
4863
4806
  if (subcontext === '_') {
4864
4807
  return;
@@ -4906,125 +4849,105 @@ const LogReviewer = (props) => {
4906
4849
  // Initial state
4907
4850
  const initialState = {
4908
4851
  loading: true,
4909
- logMap: {},
4852
+ logs: [],
4910
4853
  expandedFilterDrawer: undefined,
4911
- dateFilterState: initDateFilterState,
4912
- contextFilterState: initContextFilterState,
4913
- tagFilterState: initTagFilterState,
4914
- actionErrorFilterState: initActionErrorFilterState,
4915
- advancedFilterState: initAdvancedFilterState,
4854
+ pendingDateFilterState: initDateFilterState,
4855
+ pendingContextFilterState: initContextFilterState,
4856
+ pendingTagFilterState: initTagFilterState,
4857
+ pendingActionErrorFilterState: initActionErrorFilterState,
4858
+ pendingAdvancedFilterState: initAdvancedFilterState,
4859
+ pageNumber: 1,
4860
+ hasAnotherPage: false,
4861
+ numPages: 1,
4862
+ userMadeFilterChange: false,
4916
4863
  };
4917
4864
  // Initialize state
4918
4865
  const [state, dispatch] = useReducer(reducer$7, initialState);
4919
4866
  // Destructure common state
4920
- const { loading, logMap, expandedFilterDrawer, dateFilterState, contextFilterState, tagFilterState, actionErrorFilterState, advancedFilterState, } = state;
4867
+ const { loading, logs, expandedFilterDrawer, pendingDateFilterState, pendingContextFilterState, pendingTagFilterState, pendingActionErrorFilterState, pendingAdvancedFilterState, pageNumber, numPages, userMadeFilterChange, } = state;
4868
+ /* -------------- Refs -------------- */
4869
+ // Initialize refs
4870
+ const activeFiltersRef = useRef({
4871
+ dateFilterState: JSON.parse(JSON.stringify(pendingDateFilterState)),
4872
+ contextFilterState: JSON.parse(JSON.stringify(pendingContextFilterState)),
4873
+ tagFilterState: JSON.parse(JSON.stringify(pendingTagFilterState)),
4874
+ actionErrorFilterState: JSON.parse(JSON.stringify(pendingActionErrorFilterState)),
4875
+ advancedFilterState: JSON.parse(JSON.stringify(pendingAdvancedFilterState)),
4876
+ });
4921
4877
  /*------------------------------------------------------------------------*/
4922
4878
  /* ------------------------- Component Functions ------------------------ */
4923
4879
  /*------------------------------------------------------------------------*/
4924
4880
  /**
4925
- * Get the list of year/month combos that need to be loaded given a new
4926
- * start or end date and the existing logMap
4927
- * @author Gabe Abrams
4928
- * @param newDateFilterState the new date filter state
4929
- * @returns list of year/month combos that need to be loaded
4930
- */
4931
- const listMonthsToLoad = (newDateFilterState) => {
4932
- // List of year/month combos that need to be loaded
4933
- const toLoad = [];
4934
- // Loop through dates
4935
- let { year, month } = newDateFilterState.startDate;
4936
- while (
4937
- // Earlier year
4938
- (year < newDateFilterState.endDate.year)
4939
- // Current year but included month
4940
- || (year === newDateFilterState.endDate.year
4941
- && month <= newDateFilterState.endDate.month)) {
4942
- // Add to list if not already loaded
4943
- if (!logMap[year]
4944
- || !logMap[year][month]) {
4945
- toLoad.push({
4946
- year,
4947
- month,
4948
- });
4949
- }
4950
- // Increment
4951
- month += 1;
4952
- if (month > 12) {
4953
- month -= 12;
4954
- year += 1;
4955
- }
4956
- }
4957
- // Return
4958
- return toLoad;
4959
- };
4960
- /**
4961
- * Handle updated start/end dates (updates state, loads if necessary)
4962
- * @author Gabe Abrams
4963
- * @param newDateFilterState the new date filter state
4881
+ * Fetch logs from the server based on current filters
4882
+ * @author Yuen Ler Chow
4883
+ * @param opts object containing all arguments
4884
+ * @param opts.filters the filters to apply
4885
+ * @param opts.pageNum the page number to fetch
4886
+ * @param opts.filtersChanged if true, the filters have changed
4964
4887
  */
4965
- const handleDateRangeUpdated = (newDateFilterState) => __awaiter(void 0, void 0, void 0, function* () {
4966
- // Update state
4967
- dispatch({
4968
- type: ActionType$6.UpdateDateFilterState,
4969
- dateFilterState: newDateFilterState,
4970
- });
4971
- // Check which year/month combos we need to load
4972
- const toLoad = listMonthsToLoad(newDateFilterState);
4973
- // If nothing to load, finished
4974
- if (toLoad.length === 0) {
4975
- return;
4976
- }
4977
- // Start loading
4888
+ const fetchLogs = (opts) => __awaiter(void 0, void 0, void 0, function* () {
4889
+ const { filters, pageNum, filtersChanged, } = opts;
4978
4890
  dispatch({
4979
4891
  type: ActionType$6.StartLoading,
4980
4892
  });
4981
- // Load required months
4982
4893
  try {
4983
- for (let i = 0; i < toLoad.length; i++) {
4984
- // Destructure
4985
- const { year, month } = toLoad[i];
4986
- // Load
4987
- let logs = [];
4988
- let pageNumber = 1;
4989
- let hasAnotherPage = true;
4990
- while (hasAnotherPage) {
4991
- const response = yield visitServerEndpoint({
4992
- path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/${year}/months/${month}`,
4993
- method: 'GET',
4994
- params: {
4995
- pageNumber,
4996
- },
4997
- });
4998
- logs = logs.concat(response.items);
4999
- hasAnotherPage = response.hasAnotherPage;
5000
- pageNumber += 1;
5001
- }
5002
- // Add to map
5003
- if (!logMap[year]) {
5004
- logMap[year] = {};
5005
- }
5006
- logMap[year][month] = logs;
4894
+ // Send filters to the server
4895
+ let fetchedLogs = [];
4896
+ // Get logs from server
4897
+ const response = yield visitServerEndpoint({
4898
+ path: LOG_REVIEW_GET_LOGS_ROUTE,
4899
+ method: 'GET',
4900
+ params: {
4901
+ pageNumber: pageNum,
4902
+ filters,
4903
+ countDocuments: filtersChanged,
4904
+ },
4905
+ });
4906
+ fetchedLogs = fetchedLogs.concat(response.items);
4907
+ dispatch({
4908
+ type: ActionType$6.SetHasAnotherPage,
4909
+ hasAnotherPage: response.hasAnotherPage,
4910
+ });
4911
+ if (filtersChanged && response.numPages !== undefined) {
4912
+ dispatch({
4913
+ type: ActionType$6.SetNumPages,
4914
+ numPages: response.numPages,
4915
+ });
5007
4916
  }
4917
+ // Update logs in state
4918
+ dispatch({
4919
+ type: ActionType$6.FinishLoading,
4920
+ logs: fetchedLogs,
4921
+ });
4922
+ // Update page number
4923
+ dispatch({
4924
+ type: ActionType$6.SetPageNumber,
4925
+ pageNumber: pageNum,
4926
+ });
5008
4927
  }
5009
4928
  catch (err) {
5010
4929
  return showFatalError(err);
5011
4930
  }
5012
- // Finish loading
5013
- dispatch({
5014
- type: ActionType$6.FinishLoading,
5015
- logMap,
5016
- });
5017
4931
  });
5018
4932
  /*------------------------------------------------------------------------*/
5019
4933
  /* ------------------------- Lifecycle Functions ------------------------ */
5020
4934
  /*------------------------------------------------------------------------*/
5021
4935
  /**
5022
- * Mount
5023
- * @author Gabe Abrams
4936
+ * Fetch logs on mount
4937
+ * @author Yuen Ler Chow
5024
4938
  */
5025
4939
  useEffect(() => {
5026
- // Perform initial load
5027
- handleDateRangeUpdated(dateFilterState);
4940
+ fetchLogs({
4941
+ filters: {
4942
+ dateFilterState: pendingDateFilterState,
4943
+ contextFilterState: pendingContextFilterState,
4944
+ tagFilterState: pendingTagFilterState,
4945
+ actionErrorFilterState: pendingActionErrorFilterState,
4946
+ advancedFilterState: pendingAdvancedFilterState,
4947
+ },
4948
+ pageNum: 1,
4949
+ filtersChanged: true,
4950
+ });
5028
4951
  }, []);
5029
4952
  /*------------------------------------------------------------------------*/
5030
4953
  /* ------------------------------- Render ------------------------------- */
@@ -5032,718 +4955,534 @@ const LogReviewer = (props) => {
5032
4955
  /*----------------------------------------*/
5033
4956
  /* --------------- Main UI -------------- */
5034
4957
  /*----------------------------------------*/
5035
- // Body that will be filled with the contents of the panel
5036
- let body;
5037
- /* ------------- Loading ------------ */
5038
- if (loading) {
5039
- body = (React__default.createElement("div", { className: "text-center p-5" },
5040
- React__default.createElement(LoadingSpinner, null)));
5041
- }
5042
- /* ------------ Review UI ----------- */
5043
- if (!loading) {
5044
- /*----------------------------------------*/
5045
- /* --------------- Filters -------------- */
5046
- /*----------------------------------------*/
5047
- // Filter toggle
5048
- const filterToggles = (React__default.createElement("div", { className: "LogReviewer-filter-toggles" },
5049
- React__default.createElement("h3", { className: "m-0" }, "Filters:"),
5050
- React__default.createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
5051
- React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-date-filter-drawer", className: `btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle date filter drawer", onClick: () => {
5052
- dispatch({
5053
- type: ActionType$6.ToggleFilterDrawer,
5054
- filterDrawer: FilterDrawer.Date,
5055
- });
5056
- } },
5057
- React__default.createElement(FontAwesomeIcon, { icon: faCalendar, className: "me-2" }),
5058
- "Date"),
5059
- React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-context-filter-drawer", className: `btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle context filter drawer", onClick: () => {
5060
- dispatch({
5061
- type: ActionType$6.ToggleFilterDrawer,
5062
- filterDrawer: FilterDrawer.Context,
5063
- });
5064
- } },
5065
- React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "me-2" }),
5066
- "Context"),
5067
- (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-tag-filter-drawer", className: `btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle tag filter drawer", onClick: () => {
5068
- dispatch({
5069
- type: ActionType$6.ToggleFilterDrawer,
5070
- filterDrawer: FilterDrawer.Tag,
5071
- });
5072
- } },
5073
- React__default.createElement(FontAwesomeIcon, { icon: faTag, className: "me-2" }),
5074
- "Tag")),
5075
- React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-action-filter-drawer", className: `btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle action and error filter drawer", onClick: () => {
5076
- dispatch({
5077
- type: ActionType$6.ToggleFilterDrawer,
5078
- filterDrawer: FilterDrawer.Action,
5079
- });
5080
- } },
5081
- React__default.createElement(FontAwesomeIcon, { icon: faHammer, className: "me-2" }),
5082
- "Action"),
5083
- React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle advanced filter drawer", onClick: () => {
4958
+ /*----------------------------------------*/
4959
+ /* ------------ Pagination -------------- */
4960
+ /*----------------------------------------*/
4961
+ const paginationControls = logs.length > 0 && (React__default.createElement(Pagination, { currentPage: pageNumber, numPages: numPages, loading: loading, onPageChanged: (targetPage) => {
4962
+ const { current: activeFilters } = activeFiltersRef;
4963
+ fetchLogs({
4964
+ filters: {
4965
+ dateFilterState: activeFilters.dateFilterState,
4966
+ contextFilterState: activeFilters.contextFilterState,
4967
+ tagFilterState: activeFilters.tagFilterState,
4968
+ actionErrorFilterState: activeFilters.actionErrorFilterState,
4969
+ advancedFilterState: activeFilters.advancedFilterState,
4970
+ },
4971
+ pageNum: targetPage,
4972
+ filtersChanged: false,
4973
+ });
4974
+ } }));
4975
+ /*----------------------------------------*/
4976
+ /* --------------- Filters -------------- */
4977
+ /*----------------------------------------*/
4978
+ // Filter toggle
4979
+ const filterToggles = (React__default.createElement("div", { className: "LogReviewer-filter-toggles" },
4980
+ React__default.createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
4981
+ React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-date-filter-drawer", className: `btn btn-${FilterDrawer.Date === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle date filter drawer", onClick: () => {
4982
+ dispatch({
4983
+ type: ActionType$6.ToggleFilterDrawer,
4984
+ filterDrawer: FilterDrawer.Date,
4985
+ });
4986
+ } },
4987
+ React__default.createElement(FontAwesomeIcon, { icon: faCalendar, className: "me-2" }),
4988
+ "Date"),
4989
+ React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-context-filter-drawer", className: `btn btn-${FilterDrawer.Context === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle context filter drawer", onClick: () => {
4990
+ dispatch({
4991
+ type: ActionType$6.ToggleFilterDrawer,
4992
+ filterDrawer: FilterDrawer.Context,
4993
+ });
4994
+ } },
4995
+ React__default.createElement(FontAwesomeIcon, { icon: faCircle, className: "me-2" }),
4996
+ "Context"),
4997
+ (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-tag-filter-drawer", className: `btn btn-${FilterDrawer.Tag === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle tag filter drawer", onClick: () => {
4998
+ dispatch({
4999
+ type: ActionType$6.ToggleFilterDrawer,
5000
+ filterDrawer: FilterDrawer.Tag,
5001
+ });
5002
+ } },
5003
+ React__default.createElement(FontAwesomeIcon, { icon: faTag, className: "me-2" }),
5004
+ "Tag")),
5005
+ React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-action-filter-drawer", className: `btn btn-${FilterDrawer.Action === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle action and error filter drawer", onClick: () => {
5006
+ dispatch({
5007
+ type: ActionType$6.ToggleFilterDrawer,
5008
+ filterDrawer: FilterDrawer.Action,
5009
+ });
5010
+ } },
5011
+ React__default.createElement(FontAwesomeIcon, { icon: faHammer, className: "me-2" }),
5012
+ "Action"),
5013
+ React__default.createElement("button", { type: "button", id: "LogReviewer-toggle-advanced-filter-drawer", className: `btn btn-${FilterDrawer.Advanced === expandedFilterDrawer ? 'warning' : 'light'} me-2`, "aria-label": "toggle advanced filter drawer", onClick: () => {
5014
+ dispatch({
5015
+ type: ActionType$6.ToggleFilterDrawer,
5016
+ filterDrawer: FilterDrawer.Advanced,
5017
+ });
5018
+ } },
5019
+ React__default.createElement(FontAwesomeIcon, { icon: faList, className: "me-2" }),
5020
+ "Advanced"),
5021
+ React__default.createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
5022
+ // Save active filters
5023
+ // Deep clone the initial filter states to avoid any reference issues
5024
+ // if the filter states are modified later
5025
+ activeFiltersRef.current = cloneDeep({
5026
+ dateFilterState: initDateFilterState,
5027
+ contextFilterState: initContextFilterState,
5028
+ tagFilterState: initTagFilterState,
5029
+ actionErrorFilterState: initActionErrorFilterState,
5030
+ advancedFilterState: initAdvancedFilterState,
5031
+ });
5032
+ fetchLogs({
5033
+ filters: {
5034
+ dateFilterState: initDateFilterState,
5035
+ contextFilterState: initContextFilterState,
5036
+ tagFilterState: initTagFilterState,
5037
+ actionErrorFilterState: initActionErrorFilterState,
5038
+ advancedFilterState: initAdvancedFilterState,
5039
+ },
5040
+ pageNum: 1,
5041
+ filtersChanged: true,
5042
+ });
5043
+ dispatch({
5044
+ type: ActionType$6.ResetFilters,
5045
+ initActionErrorFilterState,
5046
+ initAdvancedFilterState,
5047
+ initContextFilterState,
5048
+ initDateFilterState,
5049
+ initTagFilterState,
5050
+ });
5051
+ dispatch({
5052
+ type: ActionType$6.HideFilterDrawer,
5053
+ });
5054
+ } },
5055
+ React__default.createElement(FontAwesomeIcon, { icon: faTimes }),
5056
+ ' ',
5057
+ "Reset"),
5058
+ userMadeFilterChange && (React__default.createElement("button", { type: "button", id: "LogReviewer-submit-filters-button", className: "btn btn-primary ms-2", "aria-label": "submit filters", onClick: () => {
5059
+ dispatch({
5060
+ type: ActionType$6.HideFilterDrawer,
5061
+ });
5062
+ // Reset user made filter change indicator
5063
+ dispatch({
5064
+ type: ActionType$6.ResetUserMadeFilterChange,
5065
+ });
5066
+ // Save active filters
5067
+ // Deep clone the pending filter states to avoid any reference issues
5068
+ // if the filter states are modified later
5069
+ activeFiltersRef.current = cloneDeep({
5070
+ dateFilterState: pendingDateFilterState,
5071
+ contextFilterState: pendingContextFilterState,
5072
+ tagFilterState: pendingTagFilterState,
5073
+ actionErrorFilterState: pendingActionErrorFilterState,
5074
+ advancedFilterState: pendingAdvancedFilterState,
5075
+ });
5076
+ fetchLogs({
5077
+ filters: {
5078
+ dateFilterState: pendingDateFilterState,
5079
+ contextFilterState: pendingContextFilterState,
5080
+ tagFilterState: pendingTagFilterState,
5081
+ actionErrorFilterState: pendingActionErrorFilterState,
5082
+ advancedFilterState: pendingAdvancedFilterState,
5083
+ },
5084
+ pageNum: 1,
5085
+ filtersChanged: true,
5086
+ });
5087
+ } },
5088
+ React__default.createElement(FontAwesomeIcon, { icon: faSearch }),
5089
+ ' ',
5090
+ "Apply Filters")))));
5091
+ // Filter drawer
5092
+ let filterDrawer;
5093
+ if (expandedFilterDrawer) {
5094
+ if (expandedFilterDrawer === FilterDrawer.Date) {
5095
+ filterDrawer = (React__default.createElement(TabBox, { title: "Date" },
5096
+ React__default.createElement(SimpleDateChooser, { ariaLabel: "filter start date", name: "filter-start-date", year: pendingDateFilterState.startDate.year, month: pendingDateFilterState.startDate.month, day: pendingDateFilterState.startDate.day, dontAllowFuture: true, numMonthsToShow: 36, onChange: (month, day, year) => {
5097
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { startDate: { month, day, year } });
5084
5098
  dispatch({
5085
- type: ActionType$6.ToggleFilterDrawer,
5086
- filterDrawer: FilterDrawer.Advanced,
5099
+ type: ActionType$6.UpdateDateFilterState,
5100
+ dateFilterState: newDateFilterState,
5087
5101
  });
5088
- } },
5089
- React__default.createElement(FontAwesomeIcon, { icon: faList, className: "me-2" }),
5090
- "Advanced"),
5091
- React__default.createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
5102
+ } }),
5103
+ ' ',
5104
+ "to",
5105
+ ' ',
5106
+ React__default.createElement(SimpleDateChooser, { ariaLabel: "filter end date", name: "filter-end-date", year: pendingDateFilterState.endDate.year, month: pendingDateFilterState.endDate.month, day: pendingDateFilterState.endDate.day, dontAllowFuture: true, numMonthsToShow: 12, onChange: (month, day, year) => {
5107
+ if (year < pendingDateFilterState.startDate.year
5108
+ || (year === pendingDateFilterState.startDate.year
5109
+ && month < pendingDateFilterState.startDate.month)
5110
+ || (year === pendingDateFilterState.startDate.year
5111
+ && month === pendingDateFilterState.startDate.month
5112
+ && day < pendingDateFilterState.startDate.day)) {
5113
+ return alert('Invalid End Date', 'The end date cannot be before the start date.');
5114
+ }
5115
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { endDate: { month, day, year } });
5092
5116
  dispatch({
5093
- type: ActionType$6.ResetFilters,
5094
- initActionErrorFilterState,
5095
- initAdvancedFilterState,
5096
- initContextFilterState,
5097
- initDateFilterState,
5098
- initTagFilterState,
5117
+ type: ActionType$6.UpdateDateFilterState,
5118
+ dateFilterState: newDateFilterState,
5099
5119
  });
5100
- } },
5101
- React__default.createElement(FontAwesomeIcon, { icon: faTimes }),
5102
- ' ',
5103
- "Reset"))));
5104
- // Filter drawer
5105
- let filterDrawer;
5106
- if (expandedFilterDrawer) {
5107
- if (expandedFilterDrawer === FilterDrawer.Date) {
5108
- filterDrawer = (React__default.createElement(TabBox, { title: "Date" },
5109
- React__default.createElement(SimpleDateChooser, { ariaLabel: "filter start date", name: "filter-start-date", year: dateFilterState.startDate.year, month: dateFilterState.startDate.month, day: dateFilterState.startDate.day, dontAllowFuture: true, numMonthsToShow: 36, onChange: (month, day, year) => {
5110
- dateFilterState.startDate = { month, day, year };
5111
- handleDateRangeUpdated(dateFilterState);
5112
- } }),
5113
- ' ',
5114
- "to",
5115
- ' ',
5116
- React__default.createElement(SimpleDateChooser, { ariaLabel: "filter end date", name: "filter-end-date", year: dateFilterState.endDate.year, month: dateFilterState.endDate.month, day: dateFilterState.endDate.day, dontAllowFuture: true, numMonthsToShow: 12, onChange: (month, day, year) => {
5117
- if (year < dateFilterState.startDate.year
5118
- || (year === dateFilterState.startDate.year
5119
- && month < dateFilterState.startDate.month)
5120
- || (year === dateFilterState.startDate.year
5121
- && month === dateFilterState.startDate.month
5122
- && day < dateFilterState.startDate.day)) {
5123
- return alert('Invalid Start Date', 'The start date cannot be before the end date.');
5124
- }
5125
- dateFilterState.endDate = { month, day, year };
5126
- handleDateRangeUpdated(dateFilterState);
5127
- } })));
5128
- }
5129
- else if (expandedFilterDrawer === FilterDrawer.Context) {
5130
- // Create item picker items
5131
- const builtInPickableItem = {
5132
- id: 'built-in-contexts',
5133
- name: 'Auto-logged',
5134
- isGroup: true,
5135
- children: [],
5136
- };
5137
- const pickableItems = [];
5138
- Object.keys(contextMap)
5139
- .forEach((context) => {
5140
- const value = contextMap[context];
5141
- if (typeof value === 'string') {
5142
- // No subcategories
5143
- const item = {
5144
- id: context,
5145
- name: genHumanReadableName(context),
5146
- isGroup: false,
5147
- checked: !!contextFilterState[context],
5148
- };
5149
- // Add built-in items to its own folder
5150
- const isBuiltIn = context in LogBuiltInMetadata.Context;
5151
- if (isBuiltIn) {
5152
- // Add to built-in pickable item
5153
- builtInPickableItem.children.push(item);
5154
- }
5155
- else {
5156
- // Add to pickable items list
5157
- pickableItems.push(item);
5158
- }
5159
- return;
5160
- }
5161
- // Has subcategories
5162
- const children = (Object.keys(value)
5163
- // Remove parent name
5164
- .filter((subcontext) => {
5165
- return subcontext !== '_';
5166
- })
5167
- // Create child pickable items
5168
- .map((subcontext) => {
5169
- return {
5170
- id: subcontext,
5171
- name: genHumanReadableName(subcontext),
5172
- isGroup: false,
5173
- checked: contextFilterState[context][subcontext],
5174
- };
5175
- }));
5120
+ } })));
5121
+ }
5122
+ else if (expandedFilterDrawer === FilterDrawer.Context) {
5123
+ // Create item picker items
5124
+ const builtInPickableItem = {
5125
+ id: 'built-in-contexts',
5126
+ name: 'Auto-logged',
5127
+ isGroup: true,
5128
+ children: [],
5129
+ };
5130
+ const pickableItems = [];
5131
+ Object.keys(contextMap)
5132
+ .forEach((context) => {
5133
+ const value = contextMap[context];
5134
+ if (typeof value === 'string') {
5135
+ // No subcategories
5176
5136
  const item = {
5177
5137
  id: context,
5178
5138
  name: genHumanReadableName(context),
5179
- isGroup: true,
5180
- children,
5139
+ isGroup: false,
5140
+ checked: !!pendingContextFilterState[context],
5181
5141
  };
5182
- pickableItems.push(item);
5183
- });
5184
- // Add built-in contexts to end ofl ist
5185
- pickableItems.push(builtInPickableItem);
5186
- // Create filter UI
5187
- filterDrawer = (React__default.createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5188
- // Update our state
5189
- updatedItems.forEach((pickableItem) => {
5190
- if (pickableItem.isGroup) {
5191
- // Has subcontexts
5192
- if (pickableItem.id === 'built-in-contexts') {
5193
- // Built-in
5194
- // Treat as if these were top-level contexts
5195
- pickableItem.children.forEach((subcontextItem) => {
5196
- contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5197
- && subcontextItem.checked);
5198
- });
5199
- }
5200
- else {
5201
- // Not built-in
5202
- pickableItem.children.forEach((subcontextItem) => {
5203
- if (!subcontextItem.isGroup) {
5204
- contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5205
- }
5206
- });
5207
- }
5142
+ // Add built-in items to its own folder
5143
+ const isBuiltIn = context in LogBuiltInMetadata.Context;
5144
+ if (isBuiltIn) {
5145
+ // Add to built-in pickable item
5146
+ builtInPickableItem.children.push(item);
5147
+ }
5148
+ else {
5149
+ // Add to pickable items list
5150
+ pickableItems.push(item);
5151
+ }
5152
+ return;
5153
+ }
5154
+ // Has subcategories
5155
+ const children = (Object.keys(value)
5156
+ // Remove parent name
5157
+ .filter((subcontext) => {
5158
+ return (subcontext !== '_');
5159
+ })
5160
+ // Create child pickable items
5161
+ .map((subcontext) => {
5162
+ return {
5163
+ id: subcontext,
5164
+ name: genHumanReadableName(subcontext),
5165
+ isGroup: false,
5166
+ checked: pendingContextFilterState[context][subcontext],
5167
+ };
5168
+ }));
5169
+ const item = {
5170
+ id: context,
5171
+ name: genHumanReadableName(context),
5172
+ isGroup: true,
5173
+ children,
5174
+ };
5175
+ pickableItems.push(item);
5176
+ });
5177
+ // Add built-in contexts to end of list
5178
+ pickableItems.push(builtInPickableItem);
5179
+ // Create filter UI
5180
+ filterDrawer = (React__default.createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5181
+ // Update our state
5182
+ const newContextFilterState = Object.assign({}, pendingContextFilterState);
5183
+ updatedItems.forEach((pickableItem) => {
5184
+ if (pickableItem.isGroup) {
5185
+ // Has subcontexts
5186
+ if (pickableItem.id === 'built-in-contexts') {
5187
+ // Built-in
5188
+ // Treat as if these were top-level contexts
5189
+ pickableItem.children.forEach((subcontextItem) => {
5190
+ newContextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5191
+ && subcontextItem.checked);
5192
+ });
5208
5193
  }
5209
5194
  else {
5210
- // No subcontexts
5211
- contextFilterState[pickableItem.id] = (pickableItem.checked);
5195
+ // Not built-in
5196
+ pickableItem.children.forEach((subcontextItem) => {
5197
+ if (!subcontextItem.isGroup) {
5198
+ newContextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5199
+ }
5200
+ });
5212
5201
  }
5213
- });
5214
- dispatch({
5215
- type: ActionType$6.UpdateContextFilterState,
5216
- contextFilterState,
5217
- });
5218
- } }));
5219
- }
5220
- else if (expandedFilterDrawer === FilterDrawer.Tag) {
5221
- // Create filter UI
5222
- filterDrawer = (React__default.createElement(TabBox, { title: "Tags" },
5223
- React__default.createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
5224
- React__default.createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5225
- .map((tag) => {
5226
- const description = genHumanReadableName(tag);
5227
- return (React__default.createElement(CheckboxButton, { key: tag, id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `require that logs be tagged with "${description}" or any other selected tag`, checked: tagFilterState[tag], onChanged: (checked) => {
5228
- tagFilterState[tag] = checked;
5202
+ }
5203
+ else {
5204
+ // No subcontexts
5205
+ newContextFilterState[pickableItem.id] = (pickableItem.checked);
5206
+ }
5207
+ });
5208
+ // Update state
5209
+ dispatch({
5210
+ type: ActionType$6.UpdateContextFilterState,
5211
+ contextFilterState: newContextFilterState,
5212
+ });
5213
+ } }));
5214
+ }
5215
+ else if (expandedFilterDrawer === FilterDrawer.Tag) {
5216
+ // Create filter UI
5217
+ filterDrawer = (React__default.createElement(TabBox, { title: "Tags" },
5218
+ React__default.createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
5219
+ React__default.createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5220
+ .map((tag) => {
5221
+ const description = genHumanReadableName(tag);
5222
+ return (React__default.createElement(CheckboxButton, { key: tag, id: `LogReviewer-tag-${tag}-checkbox`, text: description, ariaLabel: `require that logs be tagged with "${description}" or any other selected tag`, checked: pendingTagFilterState[tag], onChanged: (checked) => {
5223
+ const newTagFilterState = Object.assign(Object.assign({}, pendingTagFilterState), { [tag]: checked });
5224
+ dispatch({
5225
+ type: ActionType$6.UpdateTagFilterState,
5226
+ tagFilterState: newTagFilterState,
5227
+ });
5228
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5229
+ }))));
5230
+ }
5231
+ else if (expandedFilterDrawer === FilterDrawer.Action) {
5232
+ // Create filter UI
5233
+ filterDrawer = (React__default.createElement(React__default.Fragment, null,
5234
+ React__default.createElement(TabBox, { title: "Log Type" },
5235
+ React__default.createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5236
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: undefined });
5237
+ dispatch({
5238
+ type: ActionType$6.UpdateActionErrorFilterState,
5239
+ actionErrorFilterState: newActionErrorFilterState,
5240
+ });
5241
+ }, ariaLabel: "show logs of all types", selected: pendingActionErrorFilterState.type === undefined, unselectedVariant: Variant$1.Light }),
5242
+ React__default.createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5243
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Action });
5244
+ dispatch({
5245
+ type: ActionType$6.UpdateActionErrorFilterState,
5246
+ actionErrorFilterState: newActionErrorFilterState,
5247
+ });
5248
+ }, ariaLabel: "only show action logs", selected: pendingActionErrorFilterState.type === LogType$1.Action, unselectedVariant: Variant$1.Light }),
5249
+ React__default.createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5250
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Error });
5251
+ dispatch({
5252
+ type: ActionType$6.UpdateActionErrorFilterState,
5253
+ actionErrorFilterState: newActionErrorFilterState,
5254
+ });
5255
+ }, ariaLabel: "only show error logs", selected: pendingActionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5256
+ (pendingActionErrorFilterState.type === undefined
5257
+ || pendingActionErrorFilterState.type === LogType$1.Action) && (React__default.createElement(TabBox, { title: "Action Log Details" },
5258
+ React__default.createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5259
+ .map((action) => {
5260
+ const description = genHumanReadableName(action);
5261
+ return (React__default.createElement(CheckboxButton, { key: action, id: `LogReviewer-action-${action}-checkbox`, text: description, ariaLabel: `include logs with action type "${description}" in results`, noMarginOnRight: true, checked: pendingActionErrorFilterState.action[action], onChanged: (checked) => {
5262
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { action: Object.assign(Object.assign({}, pendingActionErrorFilterState.action), { [action]: checked }) });
5229
5263
  dispatch({
5230
- type: ActionType$6.UpdateTagFilterState,
5231
- tagFilterState,
5264
+ type: ActionType$6.UpdateActionErrorFilterState,
5265
+ actionErrorFilterState: newActionErrorFilterState,
5232
5266
  });
5233
- } }));
5234
- }))));
5235
- }
5236
- else if (expandedFilterDrawer === FilterDrawer.Action) {
5237
- // Create filter UI
5238
- filterDrawer = (React__default.createElement(React__default.Fragment, null,
5239
- React__default.createElement(TabBox, { title: "Log Type" },
5240
- React__default.createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5241
- actionErrorFilterState.type = undefined;
5267
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5268
+ })),
5269
+ React__default.createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5270
+ .map((target) => {
5271
+ const description = genHumanReadableName(target);
5272
+ return (React__default.createElement(CheckboxButton, { key: target, id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, checked: pendingActionErrorFilterState.target[target], noMarginOnRight: true, onChanged: (checked) => {
5273
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { target: Object.assign(Object.assign({}, pendingActionErrorFilterState.target), { [target]: checked }) });
5242
5274
  dispatch({
5243
5275
  type: ActionType$6.UpdateActionErrorFilterState,
5244
- actionErrorFilterState,
5276
+ actionErrorFilterState: newActionErrorFilterState,
5245
5277
  });
5246
- }, ariaLabel: "show logs of all types", selected: actionErrorFilterState.type === undefined }),
5247
- React__default.createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5248
- actionErrorFilterState.type = LogType$1.Action;
5278
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5279
+ })))),
5280
+ (pendingActionErrorFilterState.type === undefined
5281
+ || pendingActionErrorFilterState.type === LogType$1.Error) && (React__default.createElement(TabBox, { title: "Error Log Details" },
5282
+ React__default.createElement("div", { className: "input-group mb-2" },
5283
+ React__default.createElement("span", { className: "input-group-text" }, "Error Message"),
5284
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error message", value: pendingActionErrorFilterState.errorMessage, placeholder: "e.g. undefined is not a function", onChange: (e) => {
5285
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorMessage: e.target.value });
5249
5286
  dispatch({
5250
5287
  type: ActionType$6.UpdateActionErrorFilterState,
5251
- actionErrorFilterState,
5288
+ actionErrorFilterState: newActionErrorFilterState,
5252
5289
  });
5253
- }, ariaLabel: "only show action logs", selected: actionErrorFilterState.type === LogType$1.Action }),
5254
- React__default.createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5255
- actionErrorFilterState.type = LogType$1.Error;
5290
+ } })),
5291
+ React__default.createElement("div", { className: "input-group mb-2" },
5292
+ React__default.createElement("span", { className: "input-group-text" }, "Error Code"),
5293
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: pendingActionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
5294
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorCode: ((e.target.value)
5295
+ .trim()
5296
+ .toUpperCase()) });
5256
5297
  dispatch({
5257
5298
  type: ActionType$6.UpdateActionErrorFilterState,
5258
- actionErrorFilterState,
5299
+ actionErrorFilterState: newActionErrorFilterState,
5259
5300
  });
5260
- }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
5261
- (actionErrorFilterState.type === undefined
5262
- || actionErrorFilterState.type === LogType$1.Action) && (React__default.createElement(TabBox, { title: "Action Log Details" },
5263
- React__default.createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5264
- .map((action) => {
5265
- const description = genHumanReadableName(action);
5266
- return (React__default.createElement(CheckboxButton, { key: action, id: `LogReviewer-action-${action}-checkbox`, text: description, ariaLabel: `include logs with action type "${description}" in results`, noMarginOnRight: true, checked: actionErrorFilterState.action[action], onChanged: (checked) => {
5267
- actionErrorFilterState.action[action] = checked;
5268
- dispatch({
5269
- type: ActionType$6.UpdateActionErrorFilterState,
5270
- actionErrorFilterState,
5271
- });
5272
- } }));
5273
- })),
5274
- React__default.createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5275
- .map((target) => {
5276
- const description = genHumanReadableName(target);
5277
- return (React__default.createElement(CheckboxButton, { key: target, id: `LogReviewer-target-${target}-checkbox`, text: description, ariaLabel: `include logs with target "${description}" in results`, checked: actionErrorFilterState.target[target], noMarginOnRight: true, onChanged: (checked) => {
5278
- actionErrorFilterState.target[target] = checked;
5279
- dispatch({
5280
- type: ActionType$6.UpdateActionErrorFilterState,
5281
- actionErrorFilterState,
5282
- });
5283
- } }));
5284
- })))),
5285
- (actionErrorFilterState.type === undefined
5286
- || actionErrorFilterState.type === LogType$1.Error) && (React__default.createElement(TabBox, { title: "Error Log Details" },
5287
- React__default.createElement("div", { className: "input-group mb-2" },
5288
- React__default.createElement("span", { className: "input-group-text" }, "Error Message"),
5289
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error message", value: actionErrorFilterState.errorMessage, placeholder: "e.g. undefined is not a function", onChange: (e) => {
5290
- actionErrorFilterState.errorMessage = e.target.value;
5291
- dispatch({
5292
- type: ActionType$6.UpdateActionErrorFilterState,
5293
- actionErrorFilterState,
5294
- });
5295
- } })),
5296
- React__default.createElement("div", { className: "input-group mb-2" },
5297
- React__default.createElement("span", { className: "input-group-text" }, "Error Code"),
5298
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
5299
- actionErrorFilterState.errorCode = ((e.target.value)
5300
- .trim()
5301
- .toUpperCase());
5302
- dispatch({
5303
- type: ActionType$6.UpdateActionErrorFilterState,
5304
- actionErrorFilterState,
5305
- });
5306
- } }))))));
5307
- }
5308
- else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5309
- // Create advanced filter ui
5310
- filterDrawer = (React__default.createElement(React__default.Fragment, null,
5311
- React__default.createElement(TabBox, { title: "User" },
5312
- React__default.createElement("div", { className: "input-group mb-2" },
5313
- React__default.createElement("span", { className: "input-group-text" }, "User First Name"),
5314
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
5315
- advancedFilterState.userFirstName = e.target.value;
5316
- dispatch({
5317
- type: ActionType$6.UpdateAdvancedFilterState,
5318
- advancedFilterState,
5319
- });
5320
- } })),
5321
- React__default.createElement("div", { className: "input-group mb-2" },
5322
- React__default.createElement("span", { className: "input-group-text" }, "User Last Name"),
5323
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
5324
- advancedFilterState.userLastName = e.target.value;
5325
- dispatch({
5326
- type: ActionType$6.UpdateAdvancedFilterState,
5327
- advancedFilterState,
5328
- });
5329
- } })),
5330
- React__default.createElement("div", { className: "input-group mb-2" },
5331
- React__default.createElement("span", { className: "input-group-text" }, "User Email"),
5332
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user email", value: advancedFilterState.userEmail, placeholder: "e.g. calicci@fas.harvard.edu", onChange: (e) => {
5333
- advancedFilterState.userEmail = ((e.target.value)
5334
- .trim());
5335
- dispatch({
5336
- type: ActionType$6.UpdateAdvancedFilterState,
5337
- advancedFilterState,
5338
- });
5339
- } })),
5340
- React__default.createElement("div", { className: "input-group mb-2" },
5341
- React__default.createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5342
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
5343
- const { value } = e.target;
5344
- // Only update if value contains only numbers
5345
- if (/^\d+$/.test(value)) {
5346
- advancedFilterState.userId = ((e.target.value)
5347
- .trim());
5348
- }
5349
- dispatch({
5350
- type: ActionType$6.UpdateAdvancedFilterState,
5351
- advancedFilterState,
5352
- });
5353
- } })),
5354
- React__default.createElement(ButtonInputGroup, { label: "Role" },
5355
- React__default.createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5356
- advancedFilterState.includeLearners = checked;
5357
- dispatch({
5358
- type: ActionType$6.UpdateAdvancedFilterState,
5359
- advancedFilterState,
5360
- });
5361
- }, checked: advancedFilterState.includeLearners, ariaLabel: "show logs from students" }),
5362
- React__default.createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5363
- advancedFilterState.includeTTMs = checked;
5301
+ } }))))));
5302
+ }
5303
+ else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5304
+ // Create advanced filter ui
5305
+ filterDrawer = (React__default.createElement(React__default.Fragment, null,
5306
+ React__default.createElement(TabBox, { title: "User" },
5307
+ React__default.createElement("div", { className: "input-group mb-2" },
5308
+ React__default.createElement("span", { className: "input-group-text" }, "User First Name"),
5309
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: pendingAdvancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
5310
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userFirstName: e.target.value });
5311
+ dispatch({
5312
+ type: ActionType$6.UpdateAdvancedFilterState,
5313
+ advancedFilterState: newAdvancedFilterState,
5314
+ });
5315
+ } })),
5316
+ React__default.createElement("div", { className: "input-group mb-2" },
5317
+ React__default.createElement("span", { className: "input-group-text" }, "User Last Name"),
5318
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: pendingAdvancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
5319
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userLastName: e.target.value });
5320
+ dispatch({
5321
+ type: ActionType$6.UpdateAdvancedFilterState,
5322
+ advancedFilterState: newAdvancedFilterState,
5323
+ });
5324
+ } })),
5325
+ React__default.createElement("div", { className: "input-group mb-2" },
5326
+ React__default.createElement("span", { className: "input-group-text" }, "User Email"),
5327
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user email", value: pendingAdvancedFilterState.userEmail, placeholder: "e.g. calicci@fas.harvard.edu", onChange: (e) => {
5328
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userEmail: ((e.target.value)
5329
+ .trim()) });
5330
+ dispatch({
5331
+ type: ActionType$6.UpdateAdvancedFilterState,
5332
+ advancedFilterState: newAdvancedFilterState,
5333
+ });
5334
+ } })),
5335
+ React__default.createElement("div", { className: "input-group mb-2" },
5336
+ React__default.createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5337
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: pendingAdvancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
5338
+ const { value } = e.target;
5339
+ // Only update if value contains only numbers
5340
+ if (/^\d+$/.test(value) || value === '') {
5341
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userId: ((e.target.value)
5342
+ .trim()) });
5364
5343
  dispatch({
5365
5344
  type: ActionType$6.UpdateAdvancedFilterState,
5366
- advancedFilterState,
5345
+ advancedFilterState: newAdvancedFilterState,
5367
5346
  });
5368
- }, checked: advancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members" }),
5369
- React__default.createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5370
- advancedFilterState.includeAdmins = checked;
5347
+ }
5348
+ } })),
5349
+ React__default.createElement(ButtonInputGroup, { label: "Role" },
5350
+ React__default.createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5351
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeLearners: checked });
5352
+ dispatch({
5353
+ type: ActionType$6.UpdateAdvancedFilterState,
5354
+ advancedFilterState: newAdvancedFilterState,
5355
+ });
5356
+ }, checked: pendingAdvancedFilterState.includeLearners, ariaLabel: "show logs from students", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5357
+ React__default.createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5358
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeTTMs: checked });
5359
+ dispatch({
5360
+ type: ActionType$6.UpdateAdvancedFilterState,
5361
+ advancedFilterState: newAdvancedFilterState,
5362
+ });
5363
+ }, checked: pendingAdvancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5364
+ React__default.createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5365
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeAdmins: checked });
5366
+ dispatch({
5367
+ type: ActionType$6.UpdateAdvancedFilterState,
5368
+ advancedFilterState: newAdvancedFilterState,
5369
+ });
5370
+ }, checked: pendingAdvancedFilterState.includeAdmins, ariaLabel: "show logs from admins", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }))),
5371
+ React__default.createElement(TabBox, { title: "Course" },
5372
+ React__default.createElement("div", { className: "input-group mb-2" },
5373
+ React__default.createElement("span", { className: "input-group-text" }, "Course Name"),
5374
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: pendingAdvancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
5375
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseName: e.target.value });
5376
+ dispatch({
5377
+ type: ActionType$6.UpdateAdvancedFilterState,
5378
+ advancedFilterState: newAdvancedFilterState,
5379
+ });
5380
+ } })),
5381
+ React__default.createElement("div", { className: "input-group mb-2" },
5382
+ React__default.createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5383
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: pendingAdvancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
5384
+ const { value } = e.target;
5385
+ // Only update if value contains only numbers
5386
+ if (/^\d+$/.test(value) || value === '') {
5387
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseId: ((e.target.value)
5388
+ .trim()) });
5371
5389
  dispatch({
5372
5390
  type: ActionType$6.UpdateAdvancedFilterState,
5373
- advancedFilterState,
5391
+ advancedFilterState: newAdvancedFilterState,
5374
5392
  });
5375
- }, checked: advancedFilterState.includeAdmins, ariaLabel: "show logs from admins" }))),
5376
- React__default.createElement(TabBox, { title: "Course" },
5393
+ }
5394
+ } }))),
5395
+ React__default.createElement(TabBox, { title: "Device" },
5396
+ React__default.createElement(ButtonInputGroup, { label: "Device Type" },
5397
+ React__default.createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: pendingAdvancedFilterState.isMobile === undefined, onSelected: () => {
5398
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: undefined });
5399
+ dispatch({
5400
+ type: ActionType$6.UpdateAdvancedFilterState,
5401
+ advancedFilterState: newAdvancedFilterState,
5402
+ });
5403
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5404
+ React__default.createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: pendingAdvancedFilterState.isMobile === true, onSelected: () => {
5405
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: true });
5406
+ dispatch({
5407
+ type: ActionType$6.UpdateAdvancedFilterState,
5408
+ advancedFilterState: newAdvancedFilterState,
5409
+ });
5410
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5411
+ React__default.createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: pendingAdvancedFilterState.isMobile === false, onSelected: () => {
5412
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: false });
5413
+ dispatch({
5414
+ type: ActionType$6.UpdateAdvancedFilterState,
5415
+ advancedFilterState: newAdvancedFilterState,
5416
+ });
5417
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }))),
5418
+ React__default.createElement(TabBox, { title: "Source" },
5419
+ React__default.createElement(ButtonInputGroup, { label: "Source Type" },
5420
+ React__default.createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: pendingAdvancedFilterState.source === undefined, onSelected: () => {
5421
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: undefined });
5422
+ dispatch({
5423
+ type: ActionType$6.UpdateAdvancedFilterState,
5424
+ advancedFilterState: newAdvancedFilterState,
5425
+ });
5426
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5427
+ React__default.createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: pendingAdvancedFilterState.source === LogSource$1.Client, onSelected: () => {
5428
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Client });
5429
+ dispatch({
5430
+ type: ActionType$6.UpdateAdvancedFilterState,
5431
+ advancedFilterState: newAdvancedFilterState,
5432
+ });
5433
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5434
+ React__default.createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: pendingAdvancedFilterState.source === LogSource$1.Server, onSelected: () => {
5435
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Server });
5436
+ dispatch({
5437
+ type: ActionType$6.UpdateAdvancedFilterState,
5438
+ advancedFilterState: newAdvancedFilterState,
5439
+ });
5440
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5441
+ pendingAdvancedFilterState.source !== LogSource$1.Client && (React__default.createElement("div", { className: "mt-2" },
5377
5442
  React__default.createElement("div", { className: "input-group mb-2" },
5378
- React__default.createElement("span", { className: "input-group-text" }, "Course Name"),
5379
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
5380
- advancedFilterState.courseName = e.target.value;
5443
+ React__default.createElement("span", { className: "input-group-text" }, "Server Route Path"),
5444
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route path", value: pendingAdvancedFilterState.routePath, placeholder: "e.g. /api/ttm/courses/12345", onChange: (e) => {
5445
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routePath: ((e.target.value)
5446
+ .trim()) });
5381
5447
  dispatch({
5382
5448
  type: ActionType$6.UpdateAdvancedFilterState,
5383
- advancedFilterState,
5449
+ advancedFilterState: newAdvancedFilterState,
5384
5450
  });
5385
5451
  } })),
5386
5452
  React__default.createElement("div", { className: "input-group mb-2" },
5387
- React__default.createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5388
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
5389
- const { value } = e.target;
5390
- // Only update if value contains only numbers
5391
- if (/^\d+$/.test(value)) {
5392
- advancedFilterState.courseId = ((e.target.value)
5393
- .trim());
5394
- }
5453
+ React__default.createElement("span", { className: "input-group-text" }, "Server Route Template"),
5454
+ React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route template", value: pendingAdvancedFilterState.routeTemplate, placeholder: "e.g. /api/ttm/courses/:courseId", onChange: (e) => {
5455
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routeTemplate: ((e.target.value)
5456
+ .trim()) });
5395
5457
  dispatch({
5396
5458
  type: ActionType$6.UpdateAdvancedFilterState,
5397
- advancedFilterState,
5459
+ advancedFilterState: newAdvancedFilterState,
5398
5460
  });
5399
- } }))),
5400
- React__default.createElement(TabBox, { title: "Device" },
5401
- React__default.createElement(ButtonInputGroup, { label: "Device Type" },
5402
- React__default.createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: advancedFilterState.isMobile === undefined, onSelected: () => {
5403
- advancedFilterState.isMobile = undefined;
5404
- dispatch({
5405
- type: ActionType$6.UpdateAdvancedFilterState,
5406
- advancedFilterState,
5407
- });
5408
- } }),
5409
- React__default.createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: advancedFilterState.isMobile === true, onSelected: () => {
5410
- advancedFilterState.isMobile = true;
5411
- dispatch({
5412
- type: ActionType$6.UpdateAdvancedFilterState,
5413
- advancedFilterState,
5414
- });
5415
- } }),
5416
- React__default.createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: advancedFilterState.isMobile === false, onSelected: () => {
5417
- advancedFilterState.isMobile = false;
5418
- dispatch({
5419
- type: ActionType$6.UpdateAdvancedFilterState,
5420
- advancedFilterState,
5421
- });
5422
- }, noMarginOnRight: true }))),
5423
- React__default.createElement(TabBox, { title: "Source" },
5424
- React__default.createElement(ButtonInputGroup, { label: "Source Type" },
5425
- React__default.createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: advancedFilterState.source === undefined, onSelected: () => {
5426
- advancedFilterState.source = undefined;
5427
- dispatch({
5428
- type: ActionType$6.UpdateAdvancedFilterState,
5429
- advancedFilterState,
5430
- });
5431
- } }),
5432
- React__default.createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: advancedFilterState.source === LogSource$1.Client, onSelected: () => {
5433
- advancedFilterState.source = LogSource$1.Client;
5434
- dispatch({
5435
- type: ActionType$6.UpdateAdvancedFilterState,
5436
- advancedFilterState,
5437
- });
5438
- } }),
5439
- React__default.createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: advancedFilterState.source === LogSource$1.Server, onSelected: () => {
5440
- advancedFilterState.source = LogSource$1.Server;
5441
- dispatch({
5442
- type: ActionType$6.UpdateAdvancedFilterState,
5443
- advancedFilterState,
5444
- });
5445
- }, noMarginOnRight: true })),
5446
- advancedFilterState.source !== LogSource$1.Client && (React__default.createElement("div", { className: "mt-2" },
5447
- React__default.createElement("div", { className: "input-group mb-2" },
5448
- React__default.createElement("span", { className: "input-group-text" }, "Server Route Path"),
5449
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route path", value: advancedFilterState.routePath, placeholder: "e.g. /api/ttm/courses/12345", onChange: (e) => {
5450
- advancedFilterState.courseName = ((e.target.value)
5451
- .trim());
5452
- dispatch({
5453
- type: ActionType$6.UpdateAdvancedFilterState,
5454
- advancedFilterState,
5455
- });
5456
- } })),
5457
- React__default.createElement("div", { className: "input-group mb-2" },
5458
- React__default.createElement("span", { className: "input-group-text" }, "Server Route Template"),
5459
- React__default.createElement("input", { type: "text", className: "form-control", "aria-label": "query for server route template", value: advancedFilterState.routeTemplate, placeholder: "e.g. /api/ttm/courses/:courseId", onChange: (e) => {
5460
- advancedFilterState.courseName = ((e.target.value)
5461
- .trim());
5462
- dispatch({
5463
- type: ActionType$6.UpdateAdvancedFilterState,
5464
- advancedFilterState,
5465
- });
5466
- } })))))));
5467
- }
5461
+ } })))))));
5468
5462
  }
5469
- // Filters UI
5470
- const filters = (React__default.createElement(React__default.Fragment, null,
5471
- filterToggles,
5472
- filterDrawer && (React__default.createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5473
- // Actually filter the logs
5474
- // > Perform filters
5475
- const logs = [];
5476
- Object.keys(logMap).forEach((year) => {
5477
- Object.keys(logMap[year]).forEach((month) => {
5478
- logMap[year][month].forEach((log) => {
5479
- /* ----------- Date Filter ---------- */
5480
- var _a;
5481
- // Before start date
5482
- if (
5483
- // Previous year
5484
- log.year < dateFilterState.startDate.year
5485
- // Same year, earlier month
5486
- || ((log.year === dateFilterState.startDate.year)
5487
- && (log.month < dateFilterState.startDate.month))
5488
- // Same year, same month, earlier day
5489
- || ((log.year === dateFilterState.startDate.year)
5490
- && (log.month === dateFilterState.startDate.month)
5491
- && (log.day < dateFilterState.startDate.day))) {
5492
- return;
5493
- }
5494
- // After end date
5495
- if (
5496
- // Later year
5497
- log.year > dateFilterState.endDate.year
5498
- // Same year, later month
5499
- || ((log.year === dateFilterState.endDate.year)
5500
- && (log.month > dateFilterState.endDate.month))
5501
- // Same year, same month, later day
5502
- || ((log.year === dateFilterState.endDate.year)
5503
- && (log.month === dateFilterState.endDate.month)
5504
- && (log.day > dateFilterState.endDate.day))) {
5505
- return;
5506
- }
5507
- /* --------- Context Filter --------- */
5508
- // Context doesn't match
5509
- if (
5510
- // Whole context is deselected
5511
- contextFilterState[log.context] === false
5512
- // None of the subcontexts are selected
5513
- || (
5514
- // Has subcontexts
5515
- typeof contextFilterState[log.context] !== 'boolean'
5516
- // None of the subcontexts are selected
5517
- && Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
5518
- .every((isSelected) => {
5519
- return !isSelected;
5520
- }))) {
5521
- return;
5522
- }
5523
- // Subcontext doesn't match
5524
- if (
5525
- // Log context is not "uncategorized" (no point in further filters)
5526
- log.context !== LogBuiltInMetadata.Context.Uncategorized
5527
- // Log has a subcontext
5528
- && log.subcontext
5529
- // Context has subcontexts
5530
- && (contextFilterState[log.context]
5531
- && contextFilterState[log.context] !== false
5532
- && contextFilterState[log.context] !== true)
5533
- // Subcontext is not selected
5534
- && !contextFilterState[log.context][log.subcontext]) {
5535
- return;
5536
- }
5537
- /* -------------- Tags -------------- */
5538
- // No tags match
5539
- if (
5540
- // At least one tag is required
5541
- Object.values(tagFilterState)
5542
- .filter((isSelected) => {
5543
- return isSelected;
5544
- })
5545
- .length > 0
5546
- // No tags match
5547
- && log.tags.every((tag) => {
5548
- return !tagFilterState[tag];
5549
- })) {
5550
- return;
5551
- }
5552
- /* ------- Actions and Errors ------- */
5553
- // Log type doesn't match
5554
- if (
5555
- // Filter won't allow all types
5556
- actionErrorFilterState.type !== undefined
5557
- // Log type doesn't match
5558
- && actionErrorFilterState.type !== log.type) {
5559
- return;
5560
- }
5561
- // Filter errors
5562
- if (log.type === LogType$1.Error) {
5563
- // Message doesn't match
5564
- if (
5565
- // Message exists
5566
- log.errorMessage
5567
- // Message filter exists
5568
- && actionErrorFilterState.errorMessage.trim().length > 0
5569
- // Message doesn't match
5570
- && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
5571
- return;
5572
- }
5573
- // Code doesn't match
5574
- if (
5575
- // Code exists
5576
- log.errorCode
5577
- // Code filter exists
5578
- && actionErrorFilterState.errorCode.trim().length > 0
5579
- // Code doesn't match
5580
- && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
5581
- return;
5582
- }
5583
- }
5584
- // Filter actions
5585
- if (log.type === LogType$1.Action) {
5586
- // Target isn't selected
5587
- if (
5588
- // Target exists
5589
- log.target
5590
- // Target isn't selected
5591
- && !actionErrorFilterState.target[log.target]) {
5592
- return;
5593
- }
5594
- // Action
5595
- if (
5596
- // Action exists
5597
- log.action
5598
- // Action isn't selected
5599
- && !actionErrorFilterState.action[log.action]) {
5600
- return;
5601
- }
5602
- }
5603
- /* --------- Advanced Filter -------- */
5604
- // First name doesn't match
5605
- if (
5606
- // First name exists
5607
- log.userFirstName
5608
- // First name query doesn't match
5609
- && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
5610
- return;
5611
- }
5612
- // Last name doesn't match
5613
- if (
5614
- // Last name exists
5615
- log.userLastName
5616
- // Last name query doesn't match
5617
- && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
5618
- return;
5619
- }
5620
- // Email doesn't match
5621
- if (
5622
- // Email exists
5623
- log.userEmail
5624
- // Email query doesn't match
5625
- && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
5626
- return;
5627
- }
5628
- // User id doesn't match
5629
- if (
5630
- // User id exists
5631
- log.userId
5632
- // User id doesn't match
5633
- && !String(log.userId).includes(advancedFilterState.userId.trim())) {
5634
- return;
5635
- }
5636
- // Learner not allowed
5637
- if (
5638
- // User is a learner
5639
- log.isLearner
5640
- // Learners aren't included
5641
- && !advancedFilterState.includeLearners) {
5642
- return;
5643
- }
5644
- // TTM not allowed
5645
- if (
5646
- // User is a ttm
5647
- log.isTTM
5648
- // TTMs aren't included
5649
- && !advancedFilterState.includeTTMs) {
5650
- return;
5651
- }
5652
- // Admin not allowed
5653
- if (
5654
- // User is an admin
5655
- log.isAdmin
5656
- // Admins aren't included
5657
- && !advancedFilterState.includeAdmins) {
5658
- return;
5659
- }
5660
- // Course Id doesn't match
5661
- if (
5662
- // Course Id exists
5663
- log.courseId
5664
- // Course Id doesn't match
5665
- && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
5666
- return;
5667
- }
5668
- // Course name doesn't match
5669
- if (
5670
- // Course name exists
5671
- log.courseName
5672
- // Course name doesn't match
5673
- && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
5674
- return;
5675
- }
5676
- // Mobile filter doesn't match
5677
- if (
5678
- // Mobile filter exists
5679
- advancedFilterState.isMobile !== undefined
5680
- // Device info exists
5681
- && log.device
5682
- // Mobile filter doesn't match
5683
- && (advancedFilterState.isMobile !== log.device.isMobile)) {
5684
- return;
5685
- }
5686
- // Log source doesn't match
5687
- if (
5688
- // Source filter exists
5689
- advancedFilterState.source !== undefined
5690
- // Source info exists
5691
- && log.source
5692
- // Source filter doesn't match
5693
- && (advancedFilterState.source !== log.source)) {
5694
- return;
5695
- }
5696
- // Route path doesn't match (Only for server source)
5697
- if (
5698
- // Source is server
5699
- (log.source === LogSource$1.Server)
5700
- // Route path is being filtered
5701
- && (advancedFilterState.routePath.trim().length)
5702
- // Route path doesn't match
5703
- && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
5704
- return;
5705
- }
5706
- // Route template doesn't match (Only for server source)
5707
- if (
5708
- // Source is server
5709
- (log.source === LogSource$1.Server)
5710
- // Route template is being filtered
5711
- && (advancedFilterState.routeTemplate.trim().length)
5712
- // Route template doesn't match
5713
- && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
5714
- return;
5715
- }
5716
- /* -------------- Done -------------- */
5717
- // Made it past all filters. Add to the list
5718
- logs.push(log);
5719
- });
5720
- });
5721
- });
5722
- /*----------------------------------------*/
5723
- /* ---------------- Data ---------------- */
5724
- /*----------------------------------------*/
5725
- // Nothing to show notice
5726
- const noLogsNotice = (logs.length === 0
5727
- ? (React__default.createElement("div", { className: "alert alert-warning text-center mt-2" },
5728
- React__default.createElement("h4", { className: "m-1" }, "No Logs to Show"),
5729
- React__default.createElement("div", null, "Either your filters are too strict or no matching logs have been created yet.")))
5730
- : undefined);
5731
- // Create intelliTable
5732
- const dataTable = (React__default.createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }));
5733
- // Main body
5734
- body = (React__default.createElement(React__default.Fragment, null,
5735
- filters,
5736
- React__default.createElement("div", { className: "mt-2" },
5737
- dataTable,
5738
- noLogsNotice)));
5739
5463
  }
5464
+ // Filters UI
5465
+ const filters = (React__default.createElement(React__default.Fragment, null,
5466
+ filterToggles,
5467
+ filterDrawer && (React__default.createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5468
+ // Body that will be filled with the contents of the panel
5469
+ const body = (React__default.createElement(React__default.Fragment, null,
5470
+ loading && (React__default.createElement("div", { className: "text-center p-5" },
5471
+ React__default.createElement(LoadingSpinner, null))),
5472
+ !loading && (React__default.createElement("div", null, filters)),
5473
+ !loading && logs.length === 0 && (React__default.createElement("div", { className: "alert alert-warning text-center mt-2" },
5474
+ React__default.createElement("h4", { className: "m-1" }, "No Logs to Show"),
5475
+ React__default.createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))),
5476
+ React__default.createElement("div", { className: loading || logs.length === 0 ? 'd-none' : undefined },
5477
+ React__default.createElement(IntelliTable, { title: "Matching Logs", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns })),
5478
+ !loading && logs.length > 0 && paginationControls));
5740
5479
  /* ---------- Wrap in Modal --------- */
5741
5480
  return (React__default.createElement("div", { className: "LogReviewer-outer-container" },
5742
5481
  React__default.createElement("style", null, style$2),
5743
5482
  React__default.createElement("div", { className: "LogReviewer-inner-container" },
5744
5483
  React__default.createElement("div", { className: "LogReviewer-header" },
5745
5484
  React__default.createElement("div", { className: "LogReviewer-header-title" },
5746
- React__default.createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
5485
+ React__default.createElement("h3", { className: "text-center" }, "Log Review Dashboard")),
5747
5486
  React__default.createElement("div", { style: { width: 0 } },
5748
5487
  React__default.createElement("button", { type: "button", className: "LogReviewer-header-close-button btn btn-dark btn-lg pe-0", "aria-label": "close log reviewer panel", onClick: onClose },
5749
5488
  React__default.createElement(FontAwesomeIcon, { icon: faTimes })))),
@@ -15419,6 +15158,12 @@ const HOUR_IN_MS = 3600000;
15419
15158
  */
15420
15159
  const DAY_IN_MS = 86400000;
15421
15160
 
15161
+ /**
15162
+ * Path of the route for storing client-side logs
15163
+ * @author Gabe Abrams
15164
+ */
15165
+ const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
15166
+
15422
15167
  /**
15423
15168
  * Route for checking the status of the current user's
15424
15169
  * access to log review
@@ -16463,5 +16208,5 @@ var DayOfWeek;
16463
16208
  })(DayOfWeek || (DayOfWeek = {}));
16464
16209
  var DayOfWeek$1 = DayOfWeek;
16465
16210
 
16466
- export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntryFieldType$1 as DBEntryFieldType, DBEntryManagerPanel, DayOfWeek$1 as DayOfWeek, Drawer, Dropdown, DropdownItemType$1 as DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, ItemPicker, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_REVIEW_STATUS_ROUTE, LOG_ROUTE_PATH, LoadingSpinner, LogAction$1 as LogAction, LogBuiltInMetadata, LogLevel$1 as LogLevel, LogReviewer, LogSource$1 as LogSource, LogType$1 as LogType, MINUTE_IN_MS, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, MultiSwitch, ParamType$1 as ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode$1 as ReactKitErrorCode, SimpleDateChooser, SimpleTimeChooser, TabBox, ToggleSwitch, Tooltip, Variant$1 as Variant, abbreviate, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, getWordCount, idify, initClient, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitServerEndpoint, waitMs };
16211
+ export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DAY_IN_MS, DBEntryFieldType$1 as DBEntryFieldType, DBEntryManagerPanel, DayOfWeek$1 as DayOfWeek, Drawer, Dropdown, DropdownItemType$1 as DropdownItemType, DynamicWord, ErrorBox, ErrorWithCode, HOUR_IN_MS, IntelliTable, ItemPicker, LOG_REVIEW_GET_LOGS_ROUTE, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_REVIEW_STATUS_ROUTE, LOG_ROUTE_PATH, LoadingSpinner, LogAction$1 as LogAction, LogBuiltInMetadata, LogLevel$1 as LogLevel, LogReviewer, LogSource$1 as LogSource, LogType$1 as LogType, MINUTE_IN_MS, Modal, ModalButtonType$1 as ModalButtonType, ModalSize$1 as ModalSize, ModalType$1 as ModalType, MultiSwitch, ParamType$1 as ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode$1 as ReactKitErrorCode, SimpleDateChooser, SimpleTimeChooser, TabBox, ToggleSwitch, Tooltip, Variant$1 as Variant, abbreviate, addFatalErrorHandler, alert, avg, canReviewLogs, capitalize, ceilToNumDecimals, cloneDeep, combineClassNames, compareArraysByProp, confirm, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, getHumanReadableDate, getLocalTimeInfo, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, getWordCount, idify, initClient, isMobileOrTablet, leaveToURL, logClientEvent, makeLinksClickable, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, prompt, roundToNumDecimals, setClientEventMetadataPopulator, showFatalError, shuffleArray, someAsync, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, useForceRender, validateEmail, validatePhoneNumber, validateString, visitServerEndpoint, waitMs };
16467
16212
  //# sourceMappingURL=index.js.map