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/cjs/index.js CHANGED
@@ -6,6 +6,7 @@ var React = require('react');
6
6
  var freeSolidSvgIcons = require('@fortawesome/free-solid-svg-icons');
7
7
  var reactFontawesome = require('@fortawesome/react-fontawesome');
8
8
  var freeRegularSvgIcons = require('@fortawesome/free-regular-svg-icons');
9
+ var clone = require('nanoclone');
9
10
 
10
11
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
11
12
 
@@ -29,6 +30,7 @@ function _interopNamespace(e) {
29
30
 
30
31
  var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
31
32
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
33
+ var clone__default = /*#__PURE__*/_interopDefaultLegacy(clone);
32
34
 
33
35
  /*! *****************************************************************************
34
36
  Copyright (c) Microsoft Corporation.
@@ -2656,56 +2658,20 @@ const getTimeInfoInET = (dateOrTimestamp) => {
2656
2658
  * @author Gardenia Liu
2657
2659
  */
2658
2660
  /*------------------------------------------------------------------------*/
2659
- /* -------------------------------- State ------------------------------- */
2660
- /*------------------------------------------------------------------------*/
2661
- /* -------------- Views ------------- */
2662
- var View;
2663
- (function (View) {
2664
- // Date chooser
2665
- View["DateChooser"] = "DateChooser";
2666
- // Invalid date
2667
- View["InvalidDate"] = "InvalidDate";
2668
- })(View || (View = {}));
2669
- /* ------------- Actions ------------ */
2670
- // Types of actions
2671
- var ActionType$a;
2672
- (function (ActionType) {
2673
- // Fix invalid date so it is now in range
2674
- ActionType["FixInvalidDate"] = "FixInvalidDate";
2675
- })(ActionType$a || (ActionType$a = {}));
2676
- /**
2677
- * Reducer that executes actions
2678
- * @author Gardenia Liu
2679
- * @param state current state
2680
- * @param action action to execute
2681
- * @returns updated state
2682
- */
2683
- const reducer$b = (state, action) => {
2684
- switch (action.type) {
2685
- case ActionType$a.FixInvalidDate: {
2686
- return Object.assign(Object.assign({}, state), { view: View.DateChooser });
2687
- }
2688
- default: {
2689
- return state;
2690
- }
2691
- }
2692
- };
2693
- /*------------------------------------------------------------------------*/
2694
- /* --------------------------- Static Helpers --------------------------- */
2661
+ /* ------------------------------ Component ----------------------------- */
2695
2662
  /*------------------------------------------------------------------------*/
2696
- /**
2697
- * Get the list of choices in the date chooser given props
2698
- * @author Gardenia Liu
2699
- * @author Gabe Abrams
2700
- * @param opts object containing all arguments
2701
- * @param opts.numMonthsToShow number of months to show
2702
- * @param opts.dontAllowPast if true, the user isn't allowed to select dates in the past
2703
- * @param opts.dontAllowFuture if true, the user isn't allowed to select dates in the future
2704
- * @returns choices
2705
- */
2706
- const getChoices = (opts) => {
2707
- // Destructure props
2708
- const { dontAllowFuture, dontAllowPast, numMonthsToShow = 6, } = opts;
2663
+ const SimpleDateChooser = (props) => {
2664
+ /*------------------------------------------------------------------------*/
2665
+ /* -------------------------------- Setup ------------------------------- */
2666
+ /*------------------------------------------------------------------------*/
2667
+ /* -------------- Props ------------- */
2668
+ const { ariaLabel, name, onChange, numMonthsToShow = 6, dontAllowFuture, dontAllowPast, } = props;
2669
+ /*------------------------------------------------------------------------*/
2670
+ /* ------------------------------- Render ------------------------------- */
2671
+ /*------------------------------------------------------------------------*/
2672
+ /*----------------------------------------*/
2673
+ /* --------------- Main UI -------------- */
2674
+ /*----------------------------------------*/
2709
2675
  // Determine the set of choices allowed
2710
2676
  const today = getTimeInfoInET();
2711
2677
  const choices = [];
@@ -2780,143 +2746,36 @@ const getChoices = (opts) => {
2780
2746
  days,
2781
2747
  });
2782
2748
  }
2783
- // Return choices
2784
- return choices;
2785
- };
2786
- /**
2787
- * Checks whether a given date is outside the valid range of allowed choices
2788
- * @author Gardenia Liu
2789
- * @param opts object containing all arguments
2790
- * @param opts.month 1-indexed month
2791
- * @param opts.day day of the month
2792
- * @param opts.year full year
2793
- * @param opts.choices valid date choices
2794
- * @returns true if date is out of range
2795
- */
2796
- const isDateOutOfRange = (opts) => {
2797
- const { month, day, year, choices, } = opts;
2798
- return !choices.some((choice) => {
2799
- return (choice.month === month
2800
- && choice.year === year
2801
- && choice.days.includes(day));
2802
- });
2803
- };
2804
- /*------------------------------------------------------------------------*/
2805
- /* ------------------------------ Component ----------------------------- */
2806
- /*------------------------------------------------------------------------*/
2807
- const SimpleDateChooser = (props) => {
2808
- /*------------------------------------------------------------------------*/
2809
- /* -------------------------------- Setup ------------------------------- */
2810
- /*------------------------------------------------------------------------*/
2811
- /* -------------- Props ------------- */
2812
- const { ariaLabel, name, dontAllowPast, dontAllowFuture, numMonthsToShow, onChange, month, day, year, } = props;
2813
- // Get choices
2814
- const choices = getChoices({
2815
- numMonthsToShow,
2816
- dontAllowPast,
2817
- dontAllowFuture,
2818
- });
2819
- /* -------------- State ------------- */
2820
- // Check if the currently selected date is out of range
2821
- const currentSelectedDateOutOfRange = isDateOutOfRange({
2822
- month,
2823
- day,
2824
- year,
2825
- choices,
2826
- });
2827
- // Initial state
2828
- const initialState = {
2829
- view: (currentSelectedDateOutOfRange
2830
- ? View.InvalidDate
2831
- : View.DateChooser),
2832
- };
2833
- // Initialize state
2834
- const [state, dispatch] = React.useReducer(reducer$b, initialState);
2835
- // Destructure common state
2836
- const { view, } = state;
2837
- /*------------------------------------------------------------------------*/
2838
- /* ------------------------- Component Functions ------------------------ */
2839
- /*------------------------------------------------------------------------*/
2840
- /**
2841
- * Ask the user if they want to edit an invalid date
2842
- * @author Gardenia Liu
2843
- * @author Gabe Abrams
2844
- */
2845
- const askToEditInvalidDate = () => __awaiter(void 0, void 0, void 0, function* () {
2846
- // Ask the user if they want to edit the date
2847
- 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.', {
2848
- confirmButtonText: 'Edit Date',
2849
- });
2850
- // Check if user confirmed
2851
- if (confirmed) {
2852
- // Update the date to today
2853
- const today = getTimeInfoInET();
2854
- onChange(today.month, today.day, today.year);
2855
- // Update state
2856
- dispatch({
2857
- type: ActionType$a.FixInvalidDate,
2749
+ // Create choice options
2750
+ const { month, day, year, } = props;
2751
+ const monthOptions = [];
2752
+ const dayOptions = [];
2753
+ choices.forEach((choice) => {
2754
+ // Create month option
2755
+ monthOptions.push(React__default["default"].createElement("option", { key: `${choice.year}-${choice.month}`, value: `${choice.month}-${choice.year}`, "aria-label": `choose ${choice.choiceName}`, onSelect: () => {
2756
+ onChange(choice.month, choice.days[0], choice.year);
2757
+ } }, choice.choiceName));
2758
+ // This is the currently selected month
2759
+ if (month === choice.month) {
2760
+ // Create day options
2761
+ choice.days.forEach((dayChoice) => {
2762
+ const ordinal = getOrdinal(dayChoice);
2763
+ dayOptions.push(React__default["default"].createElement("option", { key: `${choice.year}-${choice.month}-${dayChoice}`, value: dayChoice, "aria-label": `choose date ${dayChoice}` },
2764
+ dayChoice,
2765
+ ordinal));
2858
2766
  });
2859
2767
  }
2860
2768
  });
2861
- /*------------------------------------------------------------------------*/
2862
- /* ------------------------------- Render ------------------------------- */
2863
- /*------------------------------------------------------------------------*/
2864
- /*----------------------------------------*/
2865
- /* ---------------- Views --------------- */
2866
- /*----------------------------------------*/
2867
- // Body that will be filled with the current view
2868
- let body;
2869
- /* ---------- DateChooser ---------- */
2870
- if (view === View.DateChooser) {
2871
- // Create lists of options
2872
- const monthOptions = [];
2873
- const dayOptions = [];
2874
- // Render each option and add it to the list
2875
- choices.forEach((choice) => {
2876
- // Create month option
2877
- monthOptions.push(React__default["default"].createElement("option", { key: `${choice.year}-${choice.month}`, value: `${choice.month}-${choice.year}`, "aria-label": `choose ${choice.choiceName}`, onSelect: () => {
2878
- onChange(choice.month, choice.days[0], choice.year);
2879
- } }, choice.choiceName));
2880
- // This is the currently selected month
2881
- if (month === choice.month) {
2882
- // Create day options
2883
- choice.days.forEach((dayChoice) => {
2884
- const ordinal = getOrdinal(dayChoice);
2885
- dayOptions.push(React__default["default"].createElement("option", { key: `${choice.year}-${choice.month}-${dayChoice}`, value: dayChoice, "aria-label": `choose date ${dayChoice}` },
2886
- dayChoice,
2887
- ordinal));
2888
- });
2889
- }
2890
- });
2891
- // Create body
2892
- body = (React__default["default"].createElement("div", { className: "SimpleDateChooser-inner-container d-inline-block", "aria-label": `date chooser with selected date: ${month}/${day}/${year}` },
2893
- React__default["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) => {
2894
- const choice = choices[e.target.selectedIndex];
2895
- // Change day, month, and year
2896
- onChange(choice.month, choice.days[0], choice.year);
2897
- } }, monthOptions),
2898
- React__default["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) => {
2899
- // Only change the day
2900
- onChange(month, Number.parseInt(e.target.value, 10), year);
2901
- } }, dayOptions)));
2902
- }
2903
- /* --------- DateOutOfRange --------- */
2904
- if (view === View.InvalidDate) {
2905
- body = (React__default["default"].createElement("div", { className: "SimpleDateChooser-inner-container d-inline-block" },
2906
- React__default["default"].createElement("button", { type: "button", className: "btn btn-light", onClick: askToEditInvalidDate, "aria-label": `edit date for ${ariaLabel}` },
2907
- getMonthName(month).full,
2908
- ' ',
2909
- day,
2910
- getOrdinal(day),
2911
- ",",
2912
- ' ',
2913
- year),
2914
- React__default["default"].createElement("button", { type: "button", className: "btn btn-secondary", onClick: askToEditInvalidDate, "aria-label": `edit date for ${ariaLabel}` }, "Edit")));
2915
- }
2916
- /*----------------------------------------*/
2917
- /* --------------- Main UI -------------- */
2918
- /*----------------------------------------*/
2919
- return (React__default["default"].createElement("span", { className: "SimpleDateChooser-outer-container" }, body));
2769
+ return (React__default["default"].createElement("div", { className: "SimpleDateChooser d-inline-block", "aria-label": `date chooser with selected date: ${month}/${day}/${year}` },
2770
+ React__default["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) => {
2771
+ const choice = choices[e.target.selectedIndex];
2772
+ // Change day, month, and year
2773
+ onChange(choice.month, choice.days[0], choice.year);
2774
+ } }, monthOptions),
2775
+ React__default["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) => {
2776
+ // Only change the day
2777
+ onChange(month, Number.parseInt(e.target.value, 10), year);
2778
+ } }, dayOptions)));
2920
2779
  };
2921
2780
 
2922
2781
  /**
@@ -3793,10 +3652,10 @@ const getHumanReadableDate = (dateOrTimestamp) => {
3793
3652
  };
3794
3653
 
3795
3654
  /**
3796
- * Path of the route for storing client-side logs
3655
+ * Path of the route for getting logs for log review
3797
3656
  * @author Gabe Abrams
3798
3657
  */
3799
- const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
3658
+ const LOG_REVIEW_GET_LOGS_ROUTE = `/admin${ROUTE_PATH_PREFIX}/logs`;
3800
3659
 
3801
3660
  /**
3802
3661
  * Source of a log event
@@ -4401,6 +4260,71 @@ const IntelliTable = (props) => {
4401
4260
  } }, table)));
4402
4261
  };
4403
4262
 
4263
+ // Import React
4264
+ /*------------------------------------------------------------------------*/
4265
+ /* ------------------------------ Component ----------------------------- */
4266
+ /*------------------------------------------------------------------------*/
4267
+ const Pagination = (props) => {
4268
+ /*------------------------------------------------------------------------*/
4269
+ /* -------------------------------- Setup ------------------------------- */
4270
+ /*------------------------------------------------------------------------*/
4271
+ /* -------------- Props ------------- */
4272
+ // Destructure props
4273
+ const { currentPage, numPages, loading = false, onPageChanged, } = props;
4274
+ /*------------------------------------------------------------------------*/
4275
+ /* ------------------------------- Render ------------------------------- */
4276
+ /*------------------------------------------------------------------------*/
4277
+ // Compute pages to display
4278
+ const pages = [];
4279
+ const delta = 2; // how many pages to show on either side of current
4280
+ let start = Math.max(1, currentPage - delta);
4281
+ let end = Math.min(numPages, currentPage + delta);
4282
+ // If we are too close to the beginning or end, shift the window.
4283
+ if (currentPage - delta < 1) {
4284
+ end = Math.min(numPages, end + (1 - (currentPage - delta)));
4285
+ }
4286
+ if (currentPage + delta > numPages) {
4287
+ start = Math.max(1, start - ((currentPage + delta) - numPages));
4288
+ }
4289
+ for (let i = start; i <= end; i++) {
4290
+ pages.push(i);
4291
+ }
4292
+ // Render
4293
+ return (React__default["default"].createElement("nav", { "aria-label": "Page navigation for logs", className: "mt-3" },
4294
+ React__default["default"].createElement("ul", { className: "pagination justify-content-center" },
4295
+ React__default["default"].createElement("li", { className: `page-item ${(currentPage <= 1 || loading) ? 'disabled' : ''}` },
4296
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(currentPage - 1); }, disabled: currentPage <= 1 || loading, "aria-label": "Go to previous page" },
4297
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faArrowLeft }),
4298
+ ' ',
4299
+ "Prev")),
4300
+ (currentPage > 3 && pages[0] !== 1) && (React__default["default"].createElement("li", { className: "page-item" },
4301
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(1); }, disabled: loading, "aria-label": "Go to page 1" }, "1"))),
4302
+ currentPage > 4 && (React__default["default"].createElement("li", { className: "page-item disabled" },
4303
+ React__default["default"].createElement("span", { className: "page-link" }, "..."))),
4304
+ pages.map((pageNum) => {
4305
+ return (React__default["default"].createElement("li", { key: pageNum, className: `page-item ${pageNum === currentPage ? 'active' : ''}` },
4306
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(pageNum); }, disabled: loading, "aria-label": `Go to page ${pageNum}` }, pageNum)));
4307
+ }),
4308
+ currentPage < numPages - 3 && (React__default["default"].createElement("li", { className: "page-item disabled" },
4309
+ React__default["default"].createElement("span", { className: "page-link" }, "..."))),
4310
+ (currentPage < numPages - 2
4311
+ && pages[pages.length - 1] !== numPages) && (React__default["default"].createElement("li", { className: "page-item" },
4312
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(numPages); }, disabled: loading, "aria-label": "Go to last page" }, numPages))),
4313
+ React__default["default"].createElement("li", { className: `page-item ${(currentPage >= numPages || loading) ? 'disabled' : ''}` },
4314
+ React__default["default"].createElement("button", { type: "button", className: "page-link", onClick: () => { return onPageChanged(currentPage + 1); }, disabled: currentPage >= numPages || loading, "aria-label": "Go to next page" },
4315
+ "Next",
4316
+ ' ',
4317
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faArrowRight }))))));
4318
+ };
4319
+
4320
+ /**
4321
+ * Deeply clones an object
4322
+ * @author Yuen Ler Chow
4323
+ * @param obj the object to clone
4324
+ * @returns a deep clone of the object
4325
+ */
4326
+ const cloneDeep = clone__default["default"];
4327
+
4404
4328
  /**
4405
4329
  * Log reviewer panel that allows users (must be approved admins) to
4406
4330
  * review logs written by dce-reactkit
@@ -4756,7 +4680,7 @@ var ActionType$6;
4756
4680
  (function (ActionType) {
4757
4681
  // Show the loading bar
4758
4682
  ActionType["StartLoading"] = "start-loading";
4759
- // Finish loading one or more months of logs
4683
+ // Finish loading logs
4760
4684
  ActionType["FinishLoading"] = "finish-loading";
4761
4685
  // Reset filters to initial values
4762
4686
  ActionType["ResetFilters"] = "reset-filters";
@@ -4774,6 +4698,14 @@ var ActionType$6;
4774
4698
  ActionType["UpdateActionErrorFilterState"] = "update-action-error-filter-state";
4775
4699
  // Update the advanced filter state
4776
4700
  ActionType["UpdateAdvancedFilterState"] = "update-advanced-filter-state";
4701
+ // Set has another page
4702
+ ActionType["SetHasAnotherPage"] = "set-has-another-page";
4703
+ // Set the number of pages
4704
+ ActionType["SetNumPages"] = "set-num-pages";
4705
+ // Set page number
4706
+ ActionType["SetPageNumber"] = "set-page-number";
4707
+ // Reset user made filter change indicator
4708
+ ActionType["ResetUserMadeFilterChange"] = "reset-user-made-filter-change";
4777
4709
  })(ActionType$6 || (ActionType$6 = {}));
4778
4710
  /**
4779
4711
  * Reducer that executes actions
@@ -4787,7 +4719,7 @@ const reducer$7 = (state, action) => {
4787
4719
  return Object.assign(Object.assign({}, state), { loading: true });
4788
4720
  }
4789
4721
  case ActionType$6.FinishLoading: {
4790
- return Object.assign(Object.assign({}, state), { loading: false, logMap: action.logMap });
4722
+ return Object.assign(Object.assign({}, state), { loading: false, logs: action.logs });
4791
4723
  }
4792
4724
  case ActionType$6.ToggleFilterDrawer: {
4793
4725
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: (state.expandedFilterDrawer === action.filterDrawer
@@ -4798,22 +4730,34 @@ const reducer$7 = (state, action) => {
4798
4730
  return Object.assign(Object.assign({}, state), { expandedFilterDrawer: undefined });
4799
4731
  }
4800
4732
  case ActionType$6.ResetFilters: {
4801
- return Object.assign(Object.assign({}, state), { dateFilterState: action.initDateFilterState, contextFilterState: action.initContextFilterState, tagFilterState: action.initTagFilterState, actionErrorFilterState: action.initActionErrorFilterState, advancedFilterState: action.initAdvancedFilterState });
4733
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.initDateFilterState, pendingContextFilterState: action.initContextFilterState, pendingTagFilterState: action.initTagFilterState, pendingActionErrorFilterState: action.initActionErrorFilterState, pendingAdvancedFilterState: action.initAdvancedFilterState, pageNumber: 1 });
4802
4734
  }
4803
4735
  case ActionType$6.UpdateDateFilterState: {
4804
- return Object.assign(Object.assign({}, state), { dateFilterState: action.dateFilterState });
4736
+ return Object.assign(Object.assign({}, state), { pendingDateFilterState: action.dateFilterState, userMadeFilterChange: true });
4805
4737
  }
4806
4738
  case ActionType$6.UpdateContextFilterState: {
4807
- return Object.assign(Object.assign({}, state), { contextFilterState: action.contextFilterState });
4739
+ return Object.assign(Object.assign({}, state), { pendingContextFilterState: action.contextFilterState, userMadeFilterChange: true });
4808
4740
  }
4809
4741
  case ActionType$6.UpdateTagFilterState: {
4810
- return Object.assign(Object.assign({}, state), { tagFilterState: action.tagFilterState });
4742
+ return Object.assign(Object.assign({}, state), { pendingTagFilterState: action.tagFilterState, userMadeFilterChange: true });
4811
4743
  }
4812
4744
  case ActionType$6.UpdateActionErrorFilterState: {
4813
- return Object.assign(Object.assign({}, state), { actionErrorFilterState: action.actionErrorFilterState });
4745
+ return Object.assign(Object.assign({}, state), { pendingActionErrorFilterState: action.actionErrorFilterState, userMadeFilterChange: true });
4814
4746
  }
4815
4747
  case ActionType$6.UpdateAdvancedFilterState: {
4816
- return Object.assign(Object.assign({}, state), { advancedFilterState: action.advancedFilterState });
4748
+ return Object.assign(Object.assign({}, state), { pendingAdvancedFilterState: action.advancedFilterState, userMadeFilterChange: true });
4749
+ }
4750
+ case ActionType$6.SetHasAnotherPage: {
4751
+ return Object.assign(Object.assign({}, state), { hasAnotherPage: action.hasAnotherPage });
4752
+ }
4753
+ case ActionType$6.SetNumPages: {
4754
+ return Object.assign(Object.assign({}, state), { numPages: action.numPages });
4755
+ }
4756
+ case ActionType$6.SetPageNumber: {
4757
+ return Object.assign(Object.assign({}, state), { pageNumber: action.pageNumber });
4758
+ }
4759
+ case ActionType$6.ResetUserMadeFilterChange: {
4760
+ return Object.assign(Object.assign({}, state), { userMadeFilterChange: false });
4817
4761
  }
4818
4762
  default: {
4819
4763
  return state;
@@ -4884,7 +4828,7 @@ const LogReviewer = (props) => {
4884
4828
  else {
4885
4829
  // Case: subcontexts exist
4886
4830
  initContextFilterState[context] = {};
4887
- Object.values(contextMap[context]).forEach((subcontext) => {
4831
+ Object.keys(contextMap[context]).forEach((subcontext) => {
4888
4832
  // Skip self ("_")
4889
4833
  if (subcontext === '_') {
4890
4834
  return;
@@ -4932,125 +4876,105 @@ const LogReviewer = (props) => {
4932
4876
  // Initial state
4933
4877
  const initialState = {
4934
4878
  loading: true,
4935
- logMap: {},
4879
+ logs: [],
4936
4880
  expandedFilterDrawer: undefined,
4937
- dateFilterState: initDateFilterState,
4938
- contextFilterState: initContextFilterState,
4939
- tagFilterState: initTagFilterState,
4940
- actionErrorFilterState: initActionErrorFilterState,
4941
- advancedFilterState: initAdvancedFilterState,
4881
+ pendingDateFilterState: initDateFilterState,
4882
+ pendingContextFilterState: initContextFilterState,
4883
+ pendingTagFilterState: initTagFilterState,
4884
+ pendingActionErrorFilterState: initActionErrorFilterState,
4885
+ pendingAdvancedFilterState: initAdvancedFilterState,
4886
+ pageNumber: 1,
4887
+ hasAnotherPage: false,
4888
+ numPages: 1,
4889
+ userMadeFilterChange: false,
4942
4890
  };
4943
4891
  // Initialize state
4944
4892
  const [state, dispatch] = React.useReducer(reducer$7, initialState);
4945
4893
  // Destructure common state
4946
- const { loading, logMap, expandedFilterDrawer, dateFilterState, contextFilterState, tagFilterState, actionErrorFilterState, advancedFilterState, } = state;
4894
+ const { loading, logs, expandedFilterDrawer, pendingDateFilterState, pendingContextFilterState, pendingTagFilterState, pendingActionErrorFilterState, pendingAdvancedFilterState, pageNumber, numPages, userMadeFilterChange, } = state;
4895
+ /* -------------- Refs -------------- */
4896
+ // Initialize refs
4897
+ const activeFiltersRef = React.useRef({
4898
+ dateFilterState: JSON.parse(JSON.stringify(pendingDateFilterState)),
4899
+ contextFilterState: JSON.parse(JSON.stringify(pendingContextFilterState)),
4900
+ tagFilterState: JSON.parse(JSON.stringify(pendingTagFilterState)),
4901
+ actionErrorFilterState: JSON.parse(JSON.stringify(pendingActionErrorFilterState)),
4902
+ advancedFilterState: JSON.parse(JSON.stringify(pendingAdvancedFilterState)),
4903
+ });
4947
4904
  /*------------------------------------------------------------------------*/
4948
4905
  /* ------------------------- Component Functions ------------------------ */
4949
4906
  /*------------------------------------------------------------------------*/
4950
4907
  /**
4951
- * Get the list of year/month combos that need to be loaded given a new
4952
- * start or end date and the existing logMap
4953
- * @author Gabe Abrams
4954
- * @param newDateFilterState the new date filter state
4955
- * @returns list of year/month combos that need to be loaded
4956
- */
4957
- const listMonthsToLoad = (newDateFilterState) => {
4958
- // List of year/month combos that need to be loaded
4959
- const toLoad = [];
4960
- // Loop through dates
4961
- let { year, month } = newDateFilterState.startDate;
4962
- while (
4963
- // Earlier year
4964
- (year < newDateFilterState.endDate.year)
4965
- // Current year but included month
4966
- || (year === newDateFilterState.endDate.year
4967
- && month <= newDateFilterState.endDate.month)) {
4968
- // Add to list if not already loaded
4969
- if (!logMap[year]
4970
- || !logMap[year][month]) {
4971
- toLoad.push({
4972
- year,
4973
- month,
4974
- });
4975
- }
4976
- // Increment
4977
- month += 1;
4978
- if (month > 12) {
4979
- month -= 12;
4980
- year += 1;
4981
- }
4982
- }
4983
- // Return
4984
- return toLoad;
4985
- };
4986
- /**
4987
- * Handle updated start/end dates (updates state, loads if necessary)
4988
- * @author Gabe Abrams
4989
- * @param newDateFilterState the new date filter state
4908
+ * Fetch logs from the server based on current filters
4909
+ * @author Yuen Ler Chow
4910
+ * @param opts object containing all arguments
4911
+ * @param opts.filters the filters to apply
4912
+ * @param opts.pageNum the page number to fetch
4913
+ * @param opts.filtersChanged if true, the filters have changed
4990
4914
  */
4991
- const handleDateRangeUpdated = (newDateFilterState) => __awaiter(void 0, void 0, void 0, function* () {
4992
- // Update state
4993
- dispatch({
4994
- type: ActionType$6.UpdateDateFilterState,
4995
- dateFilterState: newDateFilterState,
4996
- });
4997
- // Check which year/month combos we need to load
4998
- const toLoad = listMonthsToLoad(newDateFilterState);
4999
- // If nothing to load, finished
5000
- if (toLoad.length === 0) {
5001
- return;
5002
- }
5003
- // Start loading
4915
+ const fetchLogs = (opts) => __awaiter(void 0, void 0, void 0, function* () {
4916
+ const { filters, pageNum, filtersChanged, } = opts;
5004
4917
  dispatch({
5005
4918
  type: ActionType$6.StartLoading,
5006
4919
  });
5007
- // Load required months
5008
4920
  try {
5009
- for (let i = 0; i < toLoad.length; i++) {
5010
- // Destructure
5011
- const { year, month } = toLoad[i];
5012
- // Load
5013
- let logs = [];
5014
- let pageNumber = 1;
5015
- let hasAnotherPage = true;
5016
- while (hasAnotherPage) {
5017
- const response = yield visitServerEndpoint({
5018
- path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/${year}/months/${month}`,
5019
- method: 'GET',
5020
- params: {
5021
- pageNumber,
5022
- },
5023
- });
5024
- logs = logs.concat(response.items);
5025
- hasAnotherPage = response.hasAnotherPage;
5026
- pageNumber += 1;
5027
- }
5028
- // Add to map
5029
- if (!logMap[year]) {
5030
- logMap[year] = {};
5031
- }
5032
- logMap[year][month] = logs;
4921
+ // Send filters to the server
4922
+ let fetchedLogs = [];
4923
+ // Get logs from server
4924
+ const response = yield visitServerEndpoint({
4925
+ path: LOG_REVIEW_GET_LOGS_ROUTE,
4926
+ method: 'GET',
4927
+ params: {
4928
+ pageNumber: pageNum,
4929
+ filters,
4930
+ countDocuments: filtersChanged,
4931
+ },
4932
+ });
4933
+ fetchedLogs = fetchedLogs.concat(response.items);
4934
+ dispatch({
4935
+ type: ActionType$6.SetHasAnotherPage,
4936
+ hasAnotherPage: response.hasAnotherPage,
4937
+ });
4938
+ if (filtersChanged && response.numPages !== undefined) {
4939
+ dispatch({
4940
+ type: ActionType$6.SetNumPages,
4941
+ numPages: response.numPages,
4942
+ });
5033
4943
  }
4944
+ // Update logs in state
4945
+ dispatch({
4946
+ type: ActionType$6.FinishLoading,
4947
+ logs: fetchedLogs,
4948
+ });
4949
+ // Update page number
4950
+ dispatch({
4951
+ type: ActionType$6.SetPageNumber,
4952
+ pageNumber: pageNum,
4953
+ });
5034
4954
  }
5035
4955
  catch (err) {
5036
4956
  return showFatalError(err);
5037
4957
  }
5038
- // Finish loading
5039
- dispatch({
5040
- type: ActionType$6.FinishLoading,
5041
- logMap,
5042
- });
5043
4958
  });
5044
4959
  /*------------------------------------------------------------------------*/
5045
4960
  /* ------------------------- Lifecycle Functions ------------------------ */
5046
4961
  /*------------------------------------------------------------------------*/
5047
4962
  /**
5048
- * Mount
5049
- * @author Gabe Abrams
4963
+ * Fetch logs on mount
4964
+ * @author Yuen Ler Chow
5050
4965
  */
5051
4966
  React.useEffect(() => {
5052
- // Perform initial load
5053
- handleDateRangeUpdated(dateFilterState);
4967
+ fetchLogs({
4968
+ filters: {
4969
+ dateFilterState: pendingDateFilterState,
4970
+ contextFilterState: pendingContextFilterState,
4971
+ tagFilterState: pendingTagFilterState,
4972
+ actionErrorFilterState: pendingActionErrorFilterState,
4973
+ advancedFilterState: pendingAdvancedFilterState,
4974
+ },
4975
+ pageNum: 1,
4976
+ filtersChanged: true,
4977
+ });
5054
4978
  }, []);
5055
4979
  /*------------------------------------------------------------------------*/
5056
4980
  /* ------------------------------- Render ------------------------------- */
@@ -5058,718 +4982,534 @@ const LogReviewer = (props) => {
5058
4982
  /*----------------------------------------*/
5059
4983
  /* --------------- Main UI -------------- */
5060
4984
  /*----------------------------------------*/
5061
- // Body that will be filled with the contents of the panel
5062
- let body;
5063
- /* ------------- Loading ------------ */
5064
- if (loading) {
5065
- body = (React__default["default"].createElement("div", { className: "text-center p-5" },
5066
- React__default["default"].createElement(LoadingSpinner, null)));
5067
- }
5068
- /* ------------ Review UI ----------- */
5069
- if (!loading) {
5070
- /*----------------------------------------*/
5071
- /* --------------- Filters -------------- */
5072
- /*----------------------------------------*/
5073
- // Filter toggle
5074
- const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
5075
- React__default["default"].createElement("h3", { className: "m-0" }, "Filters:"),
5076
- React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
5077
- React__default["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: () => {
5078
- dispatch({
5079
- type: ActionType$6.ToggleFilterDrawer,
5080
- filterDrawer: FilterDrawer.Date,
5081
- });
5082
- } },
5083
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
5084
- "Date"),
5085
- React__default["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: () => {
5086
- dispatch({
5087
- type: ActionType$6.ToggleFilterDrawer,
5088
- filterDrawer: FilterDrawer.Context,
5089
- });
5090
- } },
5091
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
5092
- "Context"),
5093
- (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__default["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: () => {
5094
- dispatch({
5095
- type: ActionType$6.ToggleFilterDrawer,
5096
- filterDrawer: FilterDrawer.Tag,
5097
- });
5098
- } },
5099
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
5100
- "Tag")),
5101
- React__default["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: () => {
5102
- dispatch({
5103
- type: ActionType$6.ToggleFilterDrawer,
5104
- filterDrawer: FilterDrawer.Action,
5105
- });
5106
- } },
5107
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
5108
- "Action"),
5109
- React__default["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: () => {
4985
+ /*----------------------------------------*/
4986
+ /* ------------ Pagination -------------- */
4987
+ /*----------------------------------------*/
4988
+ const paginationControls = logs.length > 0 && (React__default["default"].createElement(Pagination, { currentPage: pageNumber, numPages: numPages, loading: loading, onPageChanged: (targetPage) => {
4989
+ const { current: activeFilters } = activeFiltersRef;
4990
+ fetchLogs({
4991
+ filters: {
4992
+ dateFilterState: activeFilters.dateFilterState,
4993
+ contextFilterState: activeFilters.contextFilterState,
4994
+ tagFilterState: activeFilters.tagFilterState,
4995
+ actionErrorFilterState: activeFilters.actionErrorFilterState,
4996
+ advancedFilterState: activeFilters.advancedFilterState,
4997
+ },
4998
+ pageNum: targetPage,
4999
+ filtersChanged: false,
5000
+ });
5001
+ } }));
5002
+ /*----------------------------------------*/
5003
+ /* --------------- Filters -------------- */
5004
+ /*----------------------------------------*/
5005
+ // Filter toggle
5006
+ const filterToggles = (React__default["default"].createElement("div", { className: "LogReviewer-filter-toggles" },
5007
+ React__default["default"].createElement("div", { className: "LogReviewer-filter-toggle-buttons alert alert-secondary p-2 m-0" },
5008
+ React__default["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: () => {
5009
+ dispatch({
5010
+ type: ActionType$6.ToggleFilterDrawer,
5011
+ filterDrawer: FilterDrawer.Date,
5012
+ });
5013
+ } },
5014
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCalendar, className: "me-2" }),
5015
+ "Date"),
5016
+ React__default["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: () => {
5017
+ dispatch({
5018
+ type: ActionType$6.ToggleFilterDrawer,
5019
+ filterDrawer: FilterDrawer.Context,
5020
+ });
5021
+ } },
5022
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faCircle, className: "me-2" }),
5023
+ "Context"),
5024
+ (LogMetadata.Tag && Object.keys(LogMetadata.Tag).length > 0) && (React__default["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: () => {
5025
+ dispatch({
5026
+ type: ActionType$6.ToggleFilterDrawer,
5027
+ filterDrawer: FilterDrawer.Tag,
5028
+ });
5029
+ } },
5030
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTag, className: "me-2" }),
5031
+ "Tag")),
5032
+ React__default["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: () => {
5033
+ dispatch({
5034
+ type: ActionType$6.ToggleFilterDrawer,
5035
+ filterDrawer: FilterDrawer.Action,
5036
+ });
5037
+ } },
5038
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faHammer, className: "me-2" }),
5039
+ "Action"),
5040
+ React__default["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: () => {
5041
+ dispatch({
5042
+ type: ActionType$6.ToggleFilterDrawer,
5043
+ filterDrawer: FilterDrawer.Advanced,
5044
+ });
5045
+ } },
5046
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
5047
+ "Advanced"),
5048
+ React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
5049
+ // Save active filters
5050
+ // Deep clone the initial filter states to avoid any reference issues
5051
+ // if the filter states are modified later
5052
+ activeFiltersRef.current = cloneDeep({
5053
+ dateFilterState: initDateFilterState,
5054
+ contextFilterState: initContextFilterState,
5055
+ tagFilterState: initTagFilterState,
5056
+ actionErrorFilterState: initActionErrorFilterState,
5057
+ advancedFilterState: initAdvancedFilterState,
5058
+ });
5059
+ fetchLogs({
5060
+ filters: {
5061
+ dateFilterState: initDateFilterState,
5062
+ contextFilterState: initContextFilterState,
5063
+ tagFilterState: initTagFilterState,
5064
+ actionErrorFilterState: initActionErrorFilterState,
5065
+ advancedFilterState: initAdvancedFilterState,
5066
+ },
5067
+ pageNum: 1,
5068
+ filtersChanged: true,
5069
+ });
5070
+ dispatch({
5071
+ type: ActionType$6.ResetFilters,
5072
+ initActionErrorFilterState,
5073
+ initAdvancedFilterState,
5074
+ initContextFilterState,
5075
+ initDateFilterState,
5076
+ initTagFilterState,
5077
+ });
5078
+ dispatch({
5079
+ type: ActionType$6.HideFilterDrawer,
5080
+ });
5081
+ } },
5082
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
5083
+ ' ',
5084
+ "Reset"),
5085
+ userMadeFilterChange && (React__default["default"].createElement("button", { type: "button", id: "LogReviewer-submit-filters-button", className: "btn btn-primary ms-2", "aria-label": "submit filters", onClick: () => {
5086
+ dispatch({
5087
+ type: ActionType$6.HideFilterDrawer,
5088
+ });
5089
+ // Reset user made filter change indicator
5090
+ dispatch({
5091
+ type: ActionType$6.ResetUserMadeFilterChange,
5092
+ });
5093
+ // Save active filters
5094
+ // Deep clone the pending filter states to avoid any reference issues
5095
+ // if the filter states are modified later
5096
+ activeFiltersRef.current = cloneDeep({
5097
+ dateFilterState: pendingDateFilterState,
5098
+ contextFilterState: pendingContextFilterState,
5099
+ tagFilterState: pendingTagFilterState,
5100
+ actionErrorFilterState: pendingActionErrorFilterState,
5101
+ advancedFilterState: pendingAdvancedFilterState,
5102
+ });
5103
+ fetchLogs({
5104
+ filters: {
5105
+ dateFilterState: pendingDateFilterState,
5106
+ contextFilterState: pendingContextFilterState,
5107
+ tagFilterState: pendingTagFilterState,
5108
+ actionErrorFilterState: pendingActionErrorFilterState,
5109
+ advancedFilterState: pendingAdvancedFilterState,
5110
+ },
5111
+ pageNum: 1,
5112
+ filtersChanged: true,
5113
+ });
5114
+ } },
5115
+ React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faSearch }),
5116
+ ' ',
5117
+ "Apply Filters")))));
5118
+ // Filter drawer
5119
+ let filterDrawer;
5120
+ if (expandedFilterDrawer) {
5121
+ if (expandedFilterDrawer === FilterDrawer.Date) {
5122
+ filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
5123
+ React__default["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) => {
5124
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { startDate: { month, day, year } });
5110
5125
  dispatch({
5111
- type: ActionType$6.ToggleFilterDrawer,
5112
- filterDrawer: FilterDrawer.Advanced,
5126
+ type: ActionType$6.UpdateDateFilterState,
5127
+ dateFilterState: newDateFilterState,
5113
5128
  });
5114
- } },
5115
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faList, className: "me-2" }),
5116
- "Advanced"),
5117
- React__default["default"].createElement("button", { type: "button", id: "LogReviewer-reset-filters-button", className: "btn btn-light", "aria-label": "reset filters", onClick: () => {
5129
+ } }),
5130
+ ' ',
5131
+ "to",
5132
+ ' ',
5133
+ React__default["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) => {
5134
+ if (year < pendingDateFilterState.startDate.year
5135
+ || (year === pendingDateFilterState.startDate.year
5136
+ && month < pendingDateFilterState.startDate.month)
5137
+ || (year === pendingDateFilterState.startDate.year
5138
+ && month === pendingDateFilterState.startDate.month
5139
+ && day < pendingDateFilterState.startDate.day)) {
5140
+ return alert('Invalid End Date', 'The end date cannot be before the start date.');
5141
+ }
5142
+ const newDateFilterState = Object.assign(Object.assign({}, pendingDateFilterState), { endDate: { month, day, year } });
5118
5143
  dispatch({
5119
- type: ActionType$6.ResetFilters,
5120
- initActionErrorFilterState,
5121
- initAdvancedFilterState,
5122
- initContextFilterState,
5123
- initDateFilterState,
5124
- initTagFilterState,
5144
+ type: ActionType$6.UpdateDateFilterState,
5145
+ dateFilterState: newDateFilterState,
5125
5146
  });
5126
- } },
5127
- React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes }),
5128
- ' ',
5129
- "Reset"))));
5130
- // Filter drawer
5131
- let filterDrawer;
5132
- if (expandedFilterDrawer) {
5133
- if (expandedFilterDrawer === FilterDrawer.Date) {
5134
- filterDrawer = (React__default["default"].createElement(TabBox, { title: "Date" },
5135
- React__default["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) => {
5136
- dateFilterState.startDate = { month, day, year };
5137
- handleDateRangeUpdated(dateFilterState);
5138
- } }),
5139
- ' ',
5140
- "to",
5141
- ' ',
5142
- React__default["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) => {
5143
- if (year < dateFilterState.startDate.year
5144
- || (year === dateFilterState.startDate.year
5145
- && month < dateFilterState.startDate.month)
5146
- || (year === dateFilterState.startDate.year
5147
- && month === dateFilterState.startDate.month
5148
- && day < dateFilterState.startDate.day)) {
5149
- return alert('Invalid Start Date', 'The start date cannot be before the end date.');
5150
- }
5151
- dateFilterState.endDate = { month, day, year };
5152
- handleDateRangeUpdated(dateFilterState);
5153
- } })));
5154
- }
5155
- else if (expandedFilterDrawer === FilterDrawer.Context) {
5156
- // Create item picker items
5157
- const builtInPickableItem = {
5158
- id: 'built-in-contexts',
5159
- name: 'Auto-logged',
5160
- isGroup: true,
5161
- children: [],
5162
- };
5163
- const pickableItems = [];
5164
- Object.keys(contextMap)
5165
- .forEach((context) => {
5166
- const value = contextMap[context];
5167
- if (typeof value === 'string') {
5168
- // No subcategories
5169
- const item = {
5170
- id: context,
5171
- name: genHumanReadableName(context),
5172
- isGroup: false,
5173
- checked: !!contextFilterState[context],
5174
- };
5175
- // Add built-in items to its own folder
5176
- const isBuiltIn = context in LogBuiltInMetadata.Context;
5177
- if (isBuiltIn) {
5178
- // Add to built-in pickable item
5179
- builtInPickableItem.children.push(item);
5180
- }
5181
- else {
5182
- // Add to pickable items list
5183
- pickableItems.push(item);
5184
- }
5185
- return;
5186
- }
5187
- // Has subcategories
5188
- const children = (Object.keys(value)
5189
- // Remove parent name
5190
- .filter((subcontext) => {
5191
- return subcontext !== '_';
5192
- })
5193
- // Create child pickable items
5194
- .map((subcontext) => {
5195
- return {
5196
- id: subcontext,
5197
- name: genHumanReadableName(subcontext),
5198
- isGroup: false,
5199
- checked: contextFilterState[context][subcontext],
5200
- };
5201
- }));
5147
+ } })));
5148
+ }
5149
+ else if (expandedFilterDrawer === FilterDrawer.Context) {
5150
+ // Create item picker items
5151
+ const builtInPickableItem = {
5152
+ id: 'built-in-contexts',
5153
+ name: 'Auto-logged',
5154
+ isGroup: true,
5155
+ children: [],
5156
+ };
5157
+ const pickableItems = [];
5158
+ Object.keys(contextMap)
5159
+ .forEach((context) => {
5160
+ const value = contextMap[context];
5161
+ if (typeof value === 'string') {
5162
+ // No subcategories
5202
5163
  const item = {
5203
5164
  id: context,
5204
5165
  name: genHumanReadableName(context),
5205
- isGroup: true,
5206
- children,
5166
+ isGroup: false,
5167
+ checked: !!pendingContextFilterState[context],
5207
5168
  };
5208
- pickableItems.push(item);
5209
- });
5210
- // Add built-in contexts to end ofl ist
5211
- pickableItems.push(builtInPickableItem);
5212
- // Create filter UI
5213
- filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5214
- // Update our state
5215
- updatedItems.forEach((pickableItem) => {
5216
- if (pickableItem.isGroup) {
5217
- // Has subcontexts
5218
- if (pickableItem.id === 'built-in-contexts') {
5219
- // Built-in
5220
- // Treat as if these were top-level contexts
5221
- pickableItem.children.forEach((subcontextItem) => {
5222
- contextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5223
- && subcontextItem.checked);
5224
- });
5225
- }
5226
- else {
5227
- // Not built-in
5228
- pickableItem.children.forEach((subcontextItem) => {
5229
- if (!subcontextItem.isGroup) {
5230
- contextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5231
- }
5232
- });
5233
- }
5169
+ // Add built-in items to its own folder
5170
+ const isBuiltIn = context in LogBuiltInMetadata.Context;
5171
+ if (isBuiltIn) {
5172
+ // Add to built-in pickable item
5173
+ builtInPickableItem.children.push(item);
5174
+ }
5175
+ else {
5176
+ // Add to pickable items list
5177
+ pickableItems.push(item);
5178
+ }
5179
+ return;
5180
+ }
5181
+ // Has subcategories
5182
+ const children = (Object.keys(value)
5183
+ // Remove parent name
5184
+ .filter((subcontext) => {
5185
+ return (subcontext !== '_');
5186
+ })
5187
+ // Create child pickable items
5188
+ .map((subcontext) => {
5189
+ return {
5190
+ id: subcontext,
5191
+ name: genHumanReadableName(subcontext),
5192
+ isGroup: false,
5193
+ checked: pendingContextFilterState[context][subcontext],
5194
+ };
5195
+ }));
5196
+ const item = {
5197
+ id: context,
5198
+ name: genHumanReadableName(context),
5199
+ isGroup: true,
5200
+ children,
5201
+ };
5202
+ pickableItems.push(item);
5203
+ });
5204
+ // Add built-in contexts to end of list
5205
+ pickableItems.push(builtInPickableItem);
5206
+ // Create filter UI
5207
+ filterDrawer = (React__default["default"].createElement(ItemPicker, { title: "Context", items: pickableItems, onChanged: (updatedItems) => {
5208
+ // Update our state
5209
+ const newContextFilterState = Object.assign({}, pendingContextFilterState);
5210
+ updatedItems.forEach((pickableItem) => {
5211
+ if (pickableItem.isGroup) {
5212
+ // Has subcontexts
5213
+ if (pickableItem.id === 'built-in-contexts') {
5214
+ // Built-in
5215
+ // Treat as if these were top-level contexts
5216
+ pickableItem.children.forEach((subcontextItem) => {
5217
+ newContextFilterState[subcontextItem.id] = ('checked' in subcontextItem
5218
+ && subcontextItem.checked);
5219
+ });
5234
5220
  }
5235
5221
  else {
5236
- // No subcontexts
5237
- contextFilterState[pickableItem.id] = (pickableItem.checked);
5222
+ // Not built-in
5223
+ pickableItem.children.forEach((subcontextItem) => {
5224
+ if (!subcontextItem.isGroup) {
5225
+ newContextFilterState[pickableItem.id][subcontextItem.id] = (subcontextItem.checked);
5226
+ }
5227
+ });
5238
5228
  }
5239
- });
5240
- dispatch({
5241
- type: ActionType$6.UpdateContextFilterState,
5242
- contextFilterState,
5243
- });
5244
- } }));
5245
- }
5246
- else if (expandedFilterDrawer === FilterDrawer.Tag) {
5247
- // Create filter UI
5248
- filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
5249
- React__default["default"].createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
5250
- React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5251
- .map((tag) => {
5252
- const description = genHumanReadableName(tag);
5253
- return (React__default["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) => {
5254
- tagFilterState[tag] = checked;
5229
+ }
5230
+ else {
5231
+ // No subcontexts
5232
+ newContextFilterState[pickableItem.id] = (pickableItem.checked);
5233
+ }
5234
+ });
5235
+ // Update state
5236
+ dispatch({
5237
+ type: ActionType$6.UpdateContextFilterState,
5238
+ contextFilterState: newContextFilterState,
5239
+ });
5240
+ } }));
5241
+ }
5242
+ else if (expandedFilterDrawer === FilterDrawer.Tag) {
5243
+ // Create filter UI
5244
+ filterDrawer = (React__default["default"].createElement(TabBox, { title: "Tags" },
5245
+ React__default["default"].createElement("div", null, "If any tags are selected, logs must contain at least one (but not necessarily all) of the selected tags."),
5246
+ React__default["default"].createElement("div", { className: "d-flex gap-1 flex-wrap" }, Object.keys((_d = LogMetadata.Tag) !== null && _d !== void 0 ? _d : {})
5247
+ .map((tag) => {
5248
+ const description = genHumanReadableName(tag);
5249
+ return (React__default["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) => {
5250
+ const newTagFilterState = Object.assign(Object.assign({}, pendingTagFilterState), { [tag]: checked });
5251
+ dispatch({
5252
+ type: ActionType$6.UpdateTagFilterState,
5253
+ tagFilterState: newTagFilterState,
5254
+ });
5255
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5256
+ }))));
5257
+ }
5258
+ else if (expandedFilterDrawer === FilterDrawer.Action) {
5259
+ // Create filter UI
5260
+ filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5261
+ React__default["default"].createElement(TabBox, { title: "Log Type" },
5262
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5263
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: undefined });
5264
+ dispatch({
5265
+ type: ActionType$6.UpdateActionErrorFilterState,
5266
+ actionErrorFilterState: newActionErrorFilterState,
5267
+ });
5268
+ }, ariaLabel: "show logs of all types", selected: pendingActionErrorFilterState.type === undefined, unselectedVariant: Variant$1.Light }),
5269
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5270
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Action });
5271
+ dispatch({
5272
+ type: ActionType$6.UpdateActionErrorFilterState,
5273
+ actionErrorFilterState: newActionErrorFilterState,
5274
+ });
5275
+ }, ariaLabel: "only show action logs", selected: pendingActionErrorFilterState.type === LogType$1.Action, unselectedVariant: Variant$1.Light }),
5276
+ React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5277
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { type: LogType$1.Error });
5278
+ dispatch({
5279
+ type: ActionType$6.UpdateActionErrorFilterState,
5280
+ actionErrorFilterState: newActionErrorFilterState,
5281
+ });
5282
+ }, ariaLabel: "only show error logs", selected: pendingActionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5283
+ (pendingActionErrorFilterState.type === undefined
5284
+ || pendingActionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
5285
+ React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5286
+ .map((action) => {
5287
+ const description = genHumanReadableName(action);
5288
+ return (React__default["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) => {
5289
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { action: Object.assign(Object.assign({}, pendingActionErrorFilterState.action), { [action]: checked }) });
5255
5290
  dispatch({
5256
- type: ActionType$6.UpdateTagFilterState,
5257
- tagFilterState,
5291
+ type: ActionType$6.UpdateActionErrorFilterState,
5292
+ actionErrorFilterState: newActionErrorFilterState,
5258
5293
  });
5259
- } }));
5260
- }))));
5261
- }
5262
- else if (expandedFilterDrawer === FilterDrawer.Action) {
5263
- // Create filter UI
5264
- filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5265
- React__default["default"].createElement(TabBox, { title: "Log Type" },
5266
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-all", text: "All Logs", onSelected: () => {
5267
- actionErrorFilterState.type = undefined;
5294
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5295
+ })),
5296
+ React__default["default"].createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5297
+ .map((target) => {
5298
+ const description = genHumanReadableName(target);
5299
+ return (React__default["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) => {
5300
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { target: Object.assign(Object.assign({}, pendingActionErrorFilterState.target), { [target]: checked }) });
5268
5301
  dispatch({
5269
5302
  type: ActionType$6.UpdateActionErrorFilterState,
5270
- actionErrorFilterState,
5303
+ actionErrorFilterState: newActionErrorFilterState,
5271
5304
  });
5272
- }, ariaLabel: "show logs of all types", selected: actionErrorFilterState.type === undefined }),
5273
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-action-only", text: "Action Logs Only", onSelected: () => {
5274
- actionErrorFilterState.type = LogType$1.Action;
5305
+ }, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
5306
+ })))),
5307
+ (pendingActionErrorFilterState.type === undefined
5308
+ || pendingActionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
5309
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5310
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Error Message"),
5311
+ React__default["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) => {
5312
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorMessage: e.target.value });
5275
5313
  dispatch({
5276
5314
  type: ActionType$6.UpdateActionErrorFilterState,
5277
- actionErrorFilterState,
5315
+ actionErrorFilterState: newActionErrorFilterState,
5278
5316
  });
5279
- }, ariaLabel: "only show action logs", selected: actionErrorFilterState.type === LogType$1.Action }),
5280
- React__default["default"].createElement(RadioButton, { id: "LogReviewer-type-error-only", text: "Action Error Only", onSelected: () => {
5281
- actionErrorFilterState.type = LogType$1.Error;
5317
+ } })),
5318
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5319
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Error Code"),
5320
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: pendingActionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
5321
+ const newActionErrorFilterState = Object.assign(Object.assign({}, pendingActionErrorFilterState), { errorCode: ((e.target.value)
5322
+ .trim()
5323
+ .toUpperCase()) });
5282
5324
  dispatch({
5283
5325
  type: ActionType$6.UpdateActionErrorFilterState,
5284
- actionErrorFilterState,
5326
+ actionErrorFilterState: newActionErrorFilterState,
5285
5327
  });
5286
- }, ariaLabel: "only show error logs", selected: actionErrorFilterState.type === LogType$1.Error, noMarginOnRight: true })),
5287
- (actionErrorFilterState.type === undefined
5288
- || actionErrorFilterState.type === LogType$1.Action) && (React__default["default"].createElement(TabBox, { title: "Action Log Details" },
5289
- React__default["default"].createElement(ButtonInputGroup, { label: "Action", className: "mb-2", wrapButtonsAndAddGaps: true }, Object.keys(LogAction$1)
5290
- .map((action) => {
5291
- const description = genHumanReadableName(action);
5292
- return (React__default["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) => {
5293
- actionErrorFilterState.action[action] = checked;
5294
- dispatch({
5295
- type: ActionType$6.UpdateActionErrorFilterState,
5296
- actionErrorFilterState,
5297
- });
5298
- } }));
5299
- })),
5300
- React__default["default"].createElement(ButtonInputGroup, { label: "Target", wrapButtonsAndAddGaps: true }, Object.keys(targetMap)
5301
- .map((target) => {
5302
- const description = genHumanReadableName(target);
5303
- return (React__default["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) => {
5304
- actionErrorFilterState.target[target] = checked;
5305
- dispatch({
5306
- type: ActionType$6.UpdateActionErrorFilterState,
5307
- actionErrorFilterState,
5308
- });
5309
- } }));
5310
- })))),
5311
- (actionErrorFilterState.type === undefined
5312
- || actionErrorFilterState.type === LogType$1.Error) && (React__default["default"].createElement(TabBox, { title: "Error Log Details" },
5313
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5314
- React__default["default"].createElement("span", { className: "input-group-text" }, "Error Message"),
5315
- React__default["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) => {
5316
- actionErrorFilterState.errorMessage = e.target.value;
5317
- dispatch({
5318
- type: ActionType$6.UpdateActionErrorFilterState,
5319
- actionErrorFilterState,
5320
- });
5321
- } })),
5322
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5323
- React__default["default"].createElement("span", { className: "input-group-text" }, "Error Code"),
5324
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for error code", value: actionErrorFilterState.errorCode, placeholder: "e.g. GC22", onChange: (e) => {
5325
- actionErrorFilterState.errorCode = ((e.target.value)
5326
- .trim()
5327
- .toUpperCase());
5328
- dispatch({
5329
- type: ActionType$6.UpdateActionErrorFilterState,
5330
- actionErrorFilterState,
5331
- });
5332
- } }))))));
5333
- }
5334
- else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5335
- // Create advanced filter ui
5336
- filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5337
- React__default["default"].createElement(TabBox, { title: "User" },
5338
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5339
- React__default["default"].createElement("span", { className: "input-group-text" }, "User First Name"),
5340
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: advancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
5341
- advancedFilterState.userFirstName = e.target.value;
5342
- dispatch({
5343
- type: ActionType$6.UpdateAdvancedFilterState,
5344
- advancedFilterState,
5345
- });
5346
- } })),
5347
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5348
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Last Name"),
5349
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: advancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
5350
- advancedFilterState.userLastName = e.target.value;
5351
- dispatch({
5352
- type: ActionType$6.UpdateAdvancedFilterState,
5353
- advancedFilterState,
5354
- });
5355
- } })),
5356
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5357
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Email"),
5358
- React__default["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) => {
5359
- advancedFilterState.userEmail = ((e.target.value)
5360
- .trim());
5361
- dispatch({
5362
- type: ActionType$6.UpdateAdvancedFilterState,
5363
- advancedFilterState,
5364
- });
5365
- } })),
5366
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5367
- React__default["default"].createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5368
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: advancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
5369
- const { value } = e.target;
5370
- // Only update if value contains only numbers
5371
- if (/^\d+$/.test(value)) {
5372
- advancedFilterState.userId = ((e.target.value)
5373
- .trim());
5374
- }
5375
- dispatch({
5376
- type: ActionType$6.UpdateAdvancedFilterState,
5377
- advancedFilterState,
5378
- });
5379
- } })),
5380
- React__default["default"].createElement(ButtonInputGroup, { label: "Role" },
5381
- React__default["default"].createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5382
- advancedFilterState.includeLearners = checked;
5383
- dispatch({
5384
- type: ActionType$6.UpdateAdvancedFilterState,
5385
- advancedFilterState,
5386
- });
5387
- }, checked: advancedFilterState.includeLearners, ariaLabel: "show logs from students" }),
5388
- React__default["default"].createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5389
- advancedFilterState.includeTTMs = checked;
5328
+ } }))))));
5329
+ }
5330
+ else if (expandedFilterDrawer === FilterDrawer.Advanced) {
5331
+ // Create advanced filter ui
5332
+ filterDrawer = (React__default["default"].createElement(React__default["default"].Fragment, null,
5333
+ React__default["default"].createElement(TabBox, { title: "User" },
5334
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5335
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User First Name"),
5336
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user first name", value: pendingAdvancedFilterState.userFirstName, placeholder: "e.g. Divardo", onChange: (e) => {
5337
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userFirstName: e.target.value });
5338
+ dispatch({
5339
+ type: ActionType$6.UpdateAdvancedFilterState,
5340
+ advancedFilterState: newAdvancedFilterState,
5341
+ });
5342
+ } })),
5343
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5344
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Last Name"),
5345
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user last name", value: pendingAdvancedFilterState.userLastName, placeholder: "e.g. Calicci", onChange: (e) => {
5346
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userLastName: e.target.value });
5347
+ dispatch({
5348
+ type: ActionType$6.UpdateAdvancedFilterState,
5349
+ advancedFilterState: newAdvancedFilterState,
5350
+ });
5351
+ } })),
5352
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5353
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Email"),
5354
+ React__default["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) => {
5355
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userEmail: ((e.target.value)
5356
+ .trim()) });
5357
+ dispatch({
5358
+ type: ActionType$6.UpdateAdvancedFilterState,
5359
+ advancedFilterState: newAdvancedFilterState,
5360
+ });
5361
+ } })),
5362
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5363
+ React__default["default"].createElement("span", { className: "input-group-text" }, "User Canvas Id"),
5364
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for user canvas id", value: pendingAdvancedFilterState.userId, placeholder: "e.g. 104985", onChange: (e) => {
5365
+ const { value } = e.target;
5366
+ // Only update if value contains only numbers
5367
+ if (/^\d+$/.test(value) || value === '') {
5368
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { userId: ((e.target.value)
5369
+ .trim()) });
5390
5370
  dispatch({
5391
5371
  type: ActionType$6.UpdateAdvancedFilterState,
5392
- advancedFilterState,
5372
+ advancedFilterState: newAdvancedFilterState,
5393
5373
  });
5394
- }, checked: advancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members" }),
5395
- React__default["default"].createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5396
- advancedFilterState.includeAdmins = checked;
5374
+ }
5375
+ } })),
5376
+ React__default["default"].createElement(ButtonInputGroup, { label: "Role" },
5377
+ React__default["default"].createElement(CheckboxButton, { text: "Students", onChanged: (checked) => {
5378
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeLearners: checked });
5379
+ dispatch({
5380
+ type: ActionType$6.UpdateAdvancedFilterState,
5381
+ advancedFilterState: newAdvancedFilterState,
5382
+ });
5383
+ }, checked: pendingAdvancedFilterState.includeLearners, ariaLabel: "show logs from students", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5384
+ React__default["default"].createElement(CheckboxButton, { text: "Teaching Team Members", onChanged: (checked) => {
5385
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeTTMs: checked });
5386
+ dispatch({
5387
+ type: ActionType$6.UpdateAdvancedFilterState,
5388
+ advancedFilterState: newAdvancedFilterState,
5389
+ });
5390
+ }, checked: pendingAdvancedFilterState.includeTTMs, ariaLabel: "show logs from teaching team members", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }),
5391
+ React__default["default"].createElement(CheckboxButton, { text: "Admins", onChanged: (checked) => {
5392
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { includeAdmins: checked });
5393
+ dispatch({
5394
+ type: ActionType$6.UpdateAdvancedFilterState,
5395
+ advancedFilterState: newAdvancedFilterState,
5396
+ });
5397
+ }, checked: pendingAdvancedFilterState.includeAdmins, ariaLabel: "show logs from admins", checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }))),
5398
+ React__default["default"].createElement(TabBox, { title: "Course" },
5399
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5400
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Course Name"),
5401
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: pendingAdvancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
5402
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseName: e.target.value });
5403
+ dispatch({
5404
+ type: ActionType$6.UpdateAdvancedFilterState,
5405
+ advancedFilterState: newAdvancedFilterState,
5406
+ });
5407
+ } })),
5408
+ React__default["default"].createElement("div", { className: "input-group mb-2" },
5409
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5410
+ React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: pendingAdvancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
5411
+ const { value } = e.target;
5412
+ // Only update if value contains only numbers
5413
+ if (/^\d+$/.test(value) || value === '') {
5414
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { courseId: ((e.target.value)
5415
+ .trim()) });
5397
5416
  dispatch({
5398
5417
  type: ActionType$6.UpdateAdvancedFilterState,
5399
- advancedFilterState,
5418
+ advancedFilterState: newAdvancedFilterState,
5400
5419
  });
5401
- }, checked: advancedFilterState.includeAdmins, ariaLabel: "show logs from admins" }))),
5402
- React__default["default"].createElement(TabBox, { title: "Course" },
5420
+ }
5421
+ } }))),
5422
+ React__default["default"].createElement(TabBox, { title: "Device" },
5423
+ React__default["default"].createElement(ButtonInputGroup, { label: "Device Type" },
5424
+ React__default["default"].createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: pendingAdvancedFilterState.isMobile === undefined, onSelected: () => {
5425
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: undefined });
5426
+ dispatch({
5427
+ type: ActionType$6.UpdateAdvancedFilterState,
5428
+ advancedFilterState: newAdvancedFilterState,
5429
+ });
5430
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5431
+ React__default["default"].createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: pendingAdvancedFilterState.isMobile === true, onSelected: () => {
5432
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: true });
5433
+ dispatch({
5434
+ type: ActionType$6.UpdateAdvancedFilterState,
5435
+ advancedFilterState: newAdvancedFilterState,
5436
+ });
5437
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5438
+ React__default["default"].createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: pendingAdvancedFilterState.isMobile === false, onSelected: () => {
5439
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { isMobile: false });
5440
+ dispatch({
5441
+ type: ActionType$6.UpdateAdvancedFilterState,
5442
+ advancedFilterState: newAdvancedFilterState,
5443
+ });
5444
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }))),
5445
+ React__default["default"].createElement(TabBox, { title: "Source" },
5446
+ React__default["default"].createElement(ButtonInputGroup, { label: "Source Type" },
5447
+ React__default["default"].createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: pendingAdvancedFilterState.source === undefined, onSelected: () => {
5448
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: undefined });
5449
+ dispatch({
5450
+ type: ActionType$6.UpdateAdvancedFilterState,
5451
+ advancedFilterState: newAdvancedFilterState,
5452
+ });
5453
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5454
+ React__default["default"].createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: pendingAdvancedFilterState.source === LogSource$1.Client, onSelected: () => {
5455
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Client });
5456
+ dispatch({
5457
+ type: ActionType$6.UpdateAdvancedFilterState,
5458
+ advancedFilterState: newAdvancedFilterState,
5459
+ });
5460
+ }, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light }),
5461
+ React__default["default"].createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: pendingAdvancedFilterState.source === LogSource$1.Server, onSelected: () => {
5462
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { source: LogSource$1.Server });
5463
+ dispatch({
5464
+ type: ActionType$6.UpdateAdvancedFilterState,
5465
+ advancedFilterState: newAdvancedFilterState,
5466
+ });
5467
+ }, noMarginOnRight: true, selectedVariant: Variant$1.Light, unselectedVariant: Variant$1.Light })),
5468
+ pendingAdvancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
5403
5469
  React__default["default"].createElement("div", { className: "input-group mb-2" },
5404
- React__default["default"].createElement("span", { className: "input-group-text" }, "Course Name"),
5405
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course name", value: advancedFilterState.courseName, placeholder: "e.g. GLC 200", onChange: (e) => {
5406
- advancedFilterState.courseName = e.target.value;
5470
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
5471
+ React__default["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) => {
5472
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routePath: ((e.target.value)
5473
+ .trim()) });
5407
5474
  dispatch({
5408
5475
  type: ActionType$6.UpdateAdvancedFilterState,
5409
- advancedFilterState,
5476
+ advancedFilterState: newAdvancedFilterState,
5410
5477
  });
5411
5478
  } })),
5412
5479
  React__default["default"].createElement("div", { className: "input-group mb-2" },
5413
- React__default["default"].createElement("span", { className: "input-group-text" }, "Course Canvas Id"),
5414
- React__default["default"].createElement("input", { type: "text", className: "form-control", "aria-label": "query for course canvas id", value: advancedFilterState.courseId, placeholder: "e.g. 15948", onChange: (e) => {
5415
- const { value } = e.target;
5416
- // Only update if value contains only numbers
5417
- if (/^\d+$/.test(value)) {
5418
- advancedFilterState.courseId = ((e.target.value)
5419
- .trim());
5420
- }
5480
+ React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
5481
+ React__default["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) => {
5482
+ const newAdvancedFilterState = Object.assign(Object.assign({}, pendingAdvancedFilterState), { routeTemplate: ((e.target.value)
5483
+ .trim()) });
5421
5484
  dispatch({
5422
5485
  type: ActionType$6.UpdateAdvancedFilterState,
5423
- advancedFilterState,
5486
+ advancedFilterState: newAdvancedFilterState,
5424
5487
  });
5425
- } }))),
5426
- React__default["default"].createElement(TabBox, { title: "Device" },
5427
- React__default["default"].createElement(ButtonInputGroup, { label: "Device Type" },
5428
- React__default["default"].createElement(RadioButton, { text: "All Devices", ariaLabel: "show logs from all devices", selected: advancedFilterState.isMobile === undefined, onSelected: () => {
5429
- advancedFilterState.isMobile = undefined;
5430
- dispatch({
5431
- type: ActionType$6.UpdateAdvancedFilterState,
5432
- advancedFilterState,
5433
- });
5434
- } }),
5435
- React__default["default"].createElement(RadioButton, { text: "Mobile Only", ariaLabel: "show logs from mobile devices", selected: advancedFilterState.isMobile === true, onSelected: () => {
5436
- advancedFilterState.isMobile = true;
5437
- dispatch({
5438
- type: ActionType$6.UpdateAdvancedFilterState,
5439
- advancedFilterState,
5440
- });
5441
- } }),
5442
- React__default["default"].createElement(RadioButton, { text: "Desktop Only", ariaLabel: "show logs from desktop devices", selected: advancedFilterState.isMobile === false, onSelected: () => {
5443
- advancedFilterState.isMobile = false;
5444
- dispatch({
5445
- type: ActionType$6.UpdateAdvancedFilterState,
5446
- advancedFilterState,
5447
- });
5448
- }, noMarginOnRight: true }))),
5449
- React__default["default"].createElement(TabBox, { title: "Source" },
5450
- React__default["default"].createElement(ButtonInputGroup, { label: "Source Type" },
5451
- React__default["default"].createElement(RadioButton, { text: "Both", ariaLabel: "show logs from all sources", selected: advancedFilterState.source === undefined, onSelected: () => {
5452
- advancedFilterState.source = undefined;
5453
- dispatch({
5454
- type: ActionType$6.UpdateAdvancedFilterState,
5455
- advancedFilterState,
5456
- });
5457
- } }),
5458
- React__default["default"].createElement(RadioButton, { text: "Client Only", ariaLabel: "show logs from client source", selected: advancedFilterState.source === LogSource$1.Client, onSelected: () => {
5459
- advancedFilterState.source = LogSource$1.Client;
5460
- dispatch({
5461
- type: ActionType$6.UpdateAdvancedFilterState,
5462
- advancedFilterState,
5463
- });
5464
- } }),
5465
- React__default["default"].createElement(RadioButton, { text: "Server Only", ariaLabel: "show logs from server source", selected: advancedFilterState.source === LogSource$1.Server, onSelected: () => {
5466
- advancedFilterState.source = LogSource$1.Server;
5467
- dispatch({
5468
- type: ActionType$6.UpdateAdvancedFilterState,
5469
- advancedFilterState,
5470
- });
5471
- }, noMarginOnRight: true })),
5472
- advancedFilterState.source !== LogSource$1.Client && (React__default["default"].createElement("div", { className: "mt-2" },
5473
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5474
- React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Path"),
5475
- React__default["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) => {
5476
- advancedFilterState.courseName = ((e.target.value)
5477
- .trim());
5478
- dispatch({
5479
- type: ActionType$6.UpdateAdvancedFilterState,
5480
- advancedFilterState,
5481
- });
5482
- } })),
5483
- React__default["default"].createElement("div", { className: "input-group mb-2" },
5484
- React__default["default"].createElement("span", { className: "input-group-text" }, "Server Route Template"),
5485
- React__default["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) => {
5486
- advancedFilterState.courseName = ((e.target.value)
5487
- .trim());
5488
- dispatch({
5489
- type: ActionType$6.UpdateAdvancedFilterState,
5490
- advancedFilterState,
5491
- });
5492
- } })))))));
5493
- }
5488
+ } })))))));
5494
5489
  }
5495
- // Filters UI
5496
- const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
5497
- filterToggles,
5498
- filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5499
- // Actually filter the logs
5500
- // > Perform filters
5501
- const logs = [];
5502
- Object.keys(logMap).forEach((year) => {
5503
- Object.keys(logMap[year]).forEach((month) => {
5504
- logMap[year][month].forEach((log) => {
5505
- /* ----------- Date Filter ---------- */
5506
- var _a;
5507
- // Before start date
5508
- if (
5509
- // Previous year
5510
- log.year < dateFilterState.startDate.year
5511
- // Same year, earlier month
5512
- || ((log.year === dateFilterState.startDate.year)
5513
- && (log.month < dateFilterState.startDate.month))
5514
- // Same year, same month, earlier day
5515
- || ((log.year === dateFilterState.startDate.year)
5516
- && (log.month === dateFilterState.startDate.month)
5517
- && (log.day < dateFilterState.startDate.day))) {
5518
- return;
5519
- }
5520
- // After end date
5521
- if (
5522
- // Later year
5523
- log.year > dateFilterState.endDate.year
5524
- // Same year, later month
5525
- || ((log.year === dateFilterState.endDate.year)
5526
- && (log.month > dateFilterState.endDate.month))
5527
- // Same year, same month, later day
5528
- || ((log.year === dateFilterState.endDate.year)
5529
- && (log.month === dateFilterState.endDate.month)
5530
- && (log.day > dateFilterState.endDate.day))) {
5531
- return;
5532
- }
5533
- /* --------- Context Filter --------- */
5534
- // Context doesn't match
5535
- if (
5536
- // Whole context is deselected
5537
- contextFilterState[log.context] === false
5538
- // None of the subcontexts are selected
5539
- || (
5540
- // Has subcontexts
5541
- typeof contextFilterState[log.context] !== 'boolean'
5542
- // None of the subcontexts are selected
5543
- && Object.values((_a = contextFilterState[log.context]) !== null && _a !== void 0 ? _a : {})
5544
- .every((isSelected) => {
5545
- return !isSelected;
5546
- }))) {
5547
- return;
5548
- }
5549
- // Subcontext doesn't match
5550
- if (
5551
- // Log context is not "uncategorized" (no point in further filters)
5552
- log.context !== LogBuiltInMetadata.Context.Uncategorized
5553
- // Log has a subcontext
5554
- && log.subcontext
5555
- // Context has subcontexts
5556
- && (contextFilterState[log.context]
5557
- && contextFilterState[log.context] !== false
5558
- && contextFilterState[log.context] !== true)
5559
- // Subcontext is not selected
5560
- && !contextFilterState[log.context][log.subcontext]) {
5561
- return;
5562
- }
5563
- /* -------------- Tags -------------- */
5564
- // No tags match
5565
- if (
5566
- // At least one tag is required
5567
- Object.values(tagFilterState)
5568
- .filter((isSelected) => {
5569
- return isSelected;
5570
- })
5571
- .length > 0
5572
- // No tags match
5573
- && log.tags.every((tag) => {
5574
- return !tagFilterState[tag];
5575
- })) {
5576
- return;
5577
- }
5578
- /* ------- Actions and Errors ------- */
5579
- // Log type doesn't match
5580
- if (
5581
- // Filter won't allow all types
5582
- actionErrorFilterState.type !== undefined
5583
- // Log type doesn't match
5584
- && actionErrorFilterState.type !== log.type) {
5585
- return;
5586
- }
5587
- // Filter errors
5588
- if (log.type === LogType$1.Error) {
5589
- // Message doesn't match
5590
- if (
5591
- // Message exists
5592
- log.errorMessage
5593
- // Message filter exists
5594
- && actionErrorFilterState.errorMessage.trim().length > 0
5595
- // Message doesn't match
5596
- && log.errorMessage.toLowerCase().includes(actionErrorFilterState.errorMessage.trim().toLowerCase())) {
5597
- return;
5598
- }
5599
- // Code doesn't match
5600
- if (
5601
- // Code exists
5602
- log.errorCode
5603
- // Code filter exists
5604
- && actionErrorFilterState.errorCode.trim().length > 0
5605
- // Code doesn't match
5606
- && log.errorCode.toUpperCase().includes(actionErrorFilterState.errorCode.trim().toUpperCase())) {
5607
- return;
5608
- }
5609
- }
5610
- // Filter actions
5611
- if (log.type === LogType$1.Action) {
5612
- // Target isn't selected
5613
- if (
5614
- // Target exists
5615
- log.target
5616
- // Target isn't selected
5617
- && !actionErrorFilterState.target[log.target]) {
5618
- return;
5619
- }
5620
- // Action
5621
- if (
5622
- // Action exists
5623
- log.action
5624
- // Action isn't selected
5625
- && !actionErrorFilterState.action[log.action]) {
5626
- return;
5627
- }
5628
- }
5629
- /* --------- Advanced Filter -------- */
5630
- // First name doesn't match
5631
- if (
5632
- // First name exists
5633
- log.userFirstName
5634
- // First name query doesn't match
5635
- && !log.userFirstName.toLowerCase().includes(advancedFilterState.userFirstName.toLowerCase().trim())) {
5636
- return;
5637
- }
5638
- // Last name doesn't match
5639
- if (
5640
- // Last name exists
5641
- log.userLastName
5642
- // Last name query doesn't match
5643
- && !log.userLastName.toLowerCase().includes(advancedFilterState.userLastName.toLowerCase().trim())) {
5644
- return;
5645
- }
5646
- // Email doesn't match
5647
- if (
5648
- // Email exists
5649
- log.userEmail
5650
- // Email query doesn't match
5651
- && !log.userEmail.toLowerCase().includes(advancedFilterState.userEmail.toLowerCase().trim())) {
5652
- return;
5653
- }
5654
- // User id doesn't match
5655
- if (
5656
- // User id exists
5657
- log.userId
5658
- // User id doesn't match
5659
- && !String(log.userId).includes(advancedFilterState.userId.trim())) {
5660
- return;
5661
- }
5662
- // Learner not allowed
5663
- if (
5664
- // User is a learner
5665
- log.isLearner
5666
- // Learners aren't included
5667
- && !advancedFilterState.includeLearners) {
5668
- return;
5669
- }
5670
- // TTM not allowed
5671
- if (
5672
- // User is a ttm
5673
- log.isTTM
5674
- // TTMs aren't included
5675
- && !advancedFilterState.includeTTMs) {
5676
- return;
5677
- }
5678
- // Admin not allowed
5679
- if (
5680
- // User is an admin
5681
- log.isAdmin
5682
- // Admins aren't included
5683
- && !advancedFilterState.includeAdmins) {
5684
- return;
5685
- }
5686
- // Course Id doesn't match
5687
- if (
5688
- // Course Id exists
5689
- log.courseId
5690
- // Course Id doesn't match
5691
- && !String(log.courseId).includes(advancedFilterState.courseId.trim())) {
5692
- return;
5693
- }
5694
- // Course name doesn't match
5695
- if (
5696
- // Course name exists
5697
- log.courseName
5698
- // Course name doesn't match
5699
- && !String(log.courseName).includes(advancedFilterState.courseName.trim())) {
5700
- return;
5701
- }
5702
- // Mobile filter doesn't match
5703
- if (
5704
- // Mobile filter exists
5705
- advancedFilterState.isMobile !== undefined
5706
- // Device info exists
5707
- && log.device
5708
- // Mobile filter doesn't match
5709
- && (advancedFilterState.isMobile !== log.device.isMobile)) {
5710
- return;
5711
- }
5712
- // Log source doesn't match
5713
- if (
5714
- // Source filter exists
5715
- advancedFilterState.source !== undefined
5716
- // Source info exists
5717
- && log.source
5718
- // Source filter doesn't match
5719
- && (advancedFilterState.source !== log.source)) {
5720
- return;
5721
- }
5722
- // Route path doesn't match (Only for server source)
5723
- if (
5724
- // Source is server
5725
- (log.source === LogSource$1.Server)
5726
- // Route path is being filtered
5727
- && (advancedFilterState.routePath.trim().length)
5728
- // Route path doesn't match
5729
- && !(log.routePath.includes(advancedFilterState.routePath.trim()))) {
5730
- return;
5731
- }
5732
- // Route template doesn't match (Only for server source)
5733
- if (
5734
- // Source is server
5735
- (log.source === LogSource$1.Server)
5736
- // Route template is being filtered
5737
- && (advancedFilterState.routeTemplate.trim().length)
5738
- // Route template doesn't match
5739
- && !(log.routeTemplate.includes(advancedFilterState.routeTemplate.trim()))) {
5740
- return;
5741
- }
5742
- /* -------------- Done -------------- */
5743
- // Made it past all filters. Add to the list
5744
- logs.push(log);
5745
- });
5746
- });
5747
- });
5748
- /*----------------------------------------*/
5749
- /* ---------------- Data ---------------- */
5750
- /*----------------------------------------*/
5751
- // Nothing to show notice
5752
- const noLogsNotice = (logs.length === 0
5753
- ? (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
5754
- React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
5755
- React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet.")))
5756
- : undefined);
5757
- // Create intelliTable
5758
- const dataTable = (React__default["default"].createElement(IntelliTable, { title: "Matching Logs:", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns }));
5759
- // Main body
5760
- body = (React__default["default"].createElement(React__default["default"].Fragment, null,
5761
- filters,
5762
- React__default["default"].createElement("div", { className: "mt-2" },
5763
- dataTable,
5764
- noLogsNotice)));
5765
5490
  }
5491
+ // Filters UI
5492
+ const filters = (React__default["default"].createElement(React__default["default"].Fragment, null,
5493
+ filterToggles,
5494
+ filterDrawer && (React__default["default"].createElement(Drawer, { customBackgroundColor: "#eee" }, filterDrawer))));
5495
+ // Body that will be filled with the contents of the panel
5496
+ const body = (React__default["default"].createElement(React__default["default"].Fragment, null,
5497
+ loading && (React__default["default"].createElement("div", { className: "text-center p-5" },
5498
+ React__default["default"].createElement(LoadingSpinner, null))),
5499
+ !loading && (React__default["default"].createElement("div", null, filters)),
5500
+ !loading && logs.length === 0 && (React__default["default"].createElement("div", { className: "alert alert-warning text-center mt-2" },
5501
+ React__default["default"].createElement("h4", { className: "m-1" }, "No Logs to Show"),
5502
+ React__default["default"].createElement("div", null, "Either your filters are too strict or no matching logs have been created yet."))),
5503
+ React__default["default"].createElement("div", { className: loading || logs.length === 0 ? 'd-none' : undefined },
5504
+ React__default["default"].createElement(IntelliTable, { title: "Matching Logs", csvName: `Logs from ${getHumanReadableDate()}`, id: "logs", data: logs, columns: columns })),
5505
+ !loading && logs.length > 0 && paginationControls));
5766
5506
  /* ---------- Wrap in Modal --------- */
5767
5507
  return (React__default["default"].createElement("div", { className: "LogReviewer-outer-container" },
5768
5508
  React__default["default"].createElement("style", null, style$2),
5769
5509
  React__default["default"].createElement("div", { className: "LogReviewer-inner-container" },
5770
5510
  React__default["default"].createElement("div", { className: "LogReviewer-header" },
5771
5511
  React__default["default"].createElement("div", { className: "LogReviewer-header-title" },
5772
- React__default["default"].createElement("h3", { className: "text-center m-0" }, "Log Review Dashboard")),
5512
+ React__default["default"].createElement("h3", { className: "text-center" }, "Log Review Dashboard")),
5773
5513
  React__default["default"].createElement("div", { style: { width: 0 } },
5774
5514
  React__default["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 },
5775
5515
  React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faTimes })))),
@@ -15445,6 +15185,12 @@ const HOUR_IN_MS = 3600000;
15445
15185
  */
15446
15186
  const DAY_IN_MS = 86400000;
15447
15187
 
15188
+ /**
15189
+ * Path of the route for storing client-side logs
15190
+ * @author Gabe Abrams
15191
+ */
15192
+ const LOG_REVIEW_ROUTE_PATH_PREFIX = `/admin${ROUTE_PATH_PREFIX}/logs`;
15193
+
15448
15194
  /**
15449
15195
  * Route for checking the status of the current user's
15450
15196
  * access to log review
@@ -16508,6 +16254,7 @@ exports.ErrorWithCode = ErrorWithCode;
16508
16254
  exports.HOUR_IN_MS = HOUR_IN_MS;
16509
16255
  exports.IntelliTable = IntelliTable;
16510
16256
  exports.ItemPicker = ItemPicker;
16257
+ exports.LOG_REVIEW_GET_LOGS_ROUTE = LOG_REVIEW_GET_LOGS_ROUTE;
16511
16258
  exports.LOG_REVIEW_ROUTE_PATH_PREFIX = LOG_REVIEW_ROUTE_PATH_PREFIX;
16512
16259
  exports.LOG_REVIEW_STATUS_ROUTE = LOG_REVIEW_STATUS_ROUTE;
16513
16260
  exports.LOG_ROUTE_PATH = LOG_ROUTE_PATH;
@@ -16543,6 +16290,7 @@ exports.avg = avg;
16543
16290
  exports.canReviewLogs = canReviewLogs;
16544
16291
  exports.capitalize = capitalize;
16545
16292
  exports.ceilToNumDecimals = ceilToNumDecimals;
16293
+ exports.cloneDeep = cloneDeep;
16546
16294
  exports.combineClassNames = combineClassNames;
16547
16295
  exports.compareArraysByProp = compareArraysByProp;
16548
16296
  exports.confirm = confirm;