| 1 | efrain | 1 | // This file is part of Moodle - http://moodle.org/
 | 
        
           |  |  | 2 | //
 | 
        
           |  |  | 3 | // Moodle is free software: you can redistribute it and/or modify
 | 
        
           |  |  | 4 | // it under the terms of the GNU General Public License as published by
 | 
        
           |  |  | 5 | // the Free Software Foundation, either version 3 of the License, or
 | 
        
           |  |  | 6 | // (at your option) any later version.
 | 
        
           |  |  | 7 | //
 | 
        
           |  |  | 8 | // Moodle is distributed in the hope that it will be useful,
 | 
        
           |  |  | 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
        
           |  |  | 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
        
           |  |  | 11 | // GNU General Public License for more details.
 | 
        
           |  |  | 12 | //
 | 
        
           |  |  | 13 | // You should have received a copy of the GNU General Public License
 | 
        
           |  |  | 14 | // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
 | 
        
           |  |  | 15 |   | 
        
           |  |  | 16 | /**
 | 
        
           |  |  | 17 |  * Module responsible for handling forum summary report filters.
 | 
        
           |  |  | 18 |  *
 | 
        
           |  |  | 19 |  * @module     forumreport_summary/filters
 | 
        
           |  |  | 20 |  * @copyright  2019 Michael Hawkins <michaelh@moodle.com>
 | 
        
           |  |  | 21 |  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
 | 
        
           |  |  | 22 |  */
 | 
        
           |  |  | 23 |   | 
        
           |  |  | 24 | import $ from 'jquery';
 | 
        
           |  |  | 25 | import {createPopper} from 'core/popper2';
 | 
        
           |  |  | 26 | import CustomEvents from 'core/custom_interaction_events';
 | 
        
           |  |  | 27 | import Selectors from 'forumreport_summary/selectors';
 | 
        
           |  |  | 28 | import Ajax from 'core/ajax';
 | 
        
           |  |  | 29 | import KeyCodes from 'core/key_codes';
 | 
        
           |  |  | 30 | import * as FormChangeChecker from 'core_form/changechecker';
 | 
        
           |  |  | 31 |   | 
        
           |  |  | 32 | export const init = (root) => {
 | 
        
           |  |  | 33 |     let jqRoot = $(root);
 | 
        
           |  |  | 34 |   | 
        
           |  |  | 35 |     // Hide loading spinner and show report once page is ready.
 | 
        
           |  |  | 36 |     // This ensures filters can be applied when sorting by columns.
 | 
        
           |  |  | 37 |     $(document).ready(function() {
 | 
        
           |  |  | 38 |         $('.loading-icon').hide();
 | 
        
           |  |  | 39 |         $('#summaryreport').removeClass('hidden');
 | 
        
           |  |  | 40 |     });
 | 
        
           |  |  | 41 |   | 
        
           |  |  | 42 |     // Generic filter handlers.
 | 
        
           |  |  | 43 |   | 
        
           |  |  | 44 |     // Called to override click event to trigger a proper generate request with filtering.
 | 
        
           |  |  | 45 |     const generateWithFilters = (event, getparams) => {
 | 
        
           |  |  | 46 |         let currentLink = document.forms.filtersform.action,
 | 
        
           |  |  | 47 |             newLink;
 | 
        
           |  |  | 48 |   | 
        
           |  |  | 49 |         if (event) {
 | 
        
           |  |  | 50 |             event.preventDefault();
 | 
        
           |  |  | 51 |   | 
        
           |  |  | 52 |            let currentSplit = currentLink.split('?'),
 | 
        
           |  |  | 53 |                currentstring = currentSplit[1],
 | 
        
           |  |  | 54 |                newparamsarray = getparams.split('&'),
 | 
        
           |  |  | 55 |                paramsstring = '',
 | 
        
           |  |  | 56 |                paramkeys = [],
 | 
        
           |  |  | 57 |                paramvalues = [];
 | 
        
           |  |  | 58 |   | 
        
           |  |  | 59 |             // Separate out the existing action GET param string.
 | 
        
           |  |  | 60 |             currentstring.split('&').forEach(function(param) {
 | 
        
           |  |  | 61 |                 let splitparam = param.split('=');
 | 
        
           |  |  | 62 |                 paramkeys.push(splitparam[0]);
 | 
        
           |  |  | 63 |                 paramvalues.push(splitparam[1]);
 | 
        
           |  |  | 64 |             });
 | 
        
           |  |  | 65 |   | 
        
           |  |  | 66 |             newparamsarray.forEach(function(paramstring) {
 | 
        
           |  |  | 67 |                 let newparam = paramstring.split('='),
 | 
        
           |  |  | 68 |                     existingkey = paramkeys.indexOf(newparam[0]);
 | 
        
           |  |  | 69 |   | 
        
           |  |  | 70 |                 // Overwrite value if existing, otherwise add new param.
 | 
        
           |  |  | 71 |                 if (existingkey > -1) {
 | 
        
           |  |  | 72 |                     paramvalues[existingkey] = newparam[1];
 | 
        
           |  |  | 73 |                 } else {
 | 
        
           |  |  | 74 |                     paramkeys.push(newparam[0]);
 | 
        
           |  |  | 75 |                     paramvalues.push(newparam[1]);
 | 
        
           |  |  | 76 |                 }
 | 
        
           |  |  | 77 |             });
 | 
        
           |  |  | 78 |   | 
        
           |  |  | 79 |             // Build URL.
 | 
        
           |  |  | 80 |             paramkeys.forEach(function(name, key) {
 | 
        
           |  |  | 81 |                 paramsstring += `&${name}=${paramvalues[key]}`;
 | 
        
           |  |  | 82 |             });
 | 
        
           |  |  | 83 |   | 
        
           |  |  | 84 |             newLink = currentSplit[0] + '?' + paramsstring.substr(1);
 | 
        
           |  |  | 85 |         } else {
 | 
        
           |  |  | 86 |             newLink = currentLink;
 | 
        
           |  |  | 87 |         }
 | 
        
           |  |  | 88 |   | 
        
           |  |  | 89 |         document.forms.filtersform.action = newLink;
 | 
        
           |  |  | 90 |         document.forms.filtersform.submit();
 | 
        
           |  |  | 91 |     };
 | 
        
           |  |  | 92 |   | 
        
           |  |  | 93 |     // Override 'reset table preferences' so it generates with filters.
 | 
        
           |  |  | 94 |     $('.resettable').on("click", "a", function(event) {
 | 
        
           |  |  | 95 |         generateWithFilters(event, event.target.search.substr(1));
 | 
        
           |  |  | 96 |     });
 | 
        
           |  |  | 97 |   | 
        
           |  |  | 98 |     // Override table heading sort links so they generate with filters.
 | 
        
           |  |  | 99 |     $('thead').on("click", "a", function(event) {
 | 
        
           |  |  | 100 |         generateWithFilters(event, event.target.search.substr(1));
 | 
        
           |  |  | 101 |     });
 | 
        
           |  |  | 102 |   | 
        
           |  |  | 103 |     // Override pagination page links so they generate with filters.
 | 
        
           |  |  | 104 |     $('.pagination').on("click", "a", function(event) {
 | 
        
           |  |  | 105 |         generateWithFilters(event, event.target.search.substr(1));
 | 
        
           |  |  | 106 |     });
 | 
        
           |  |  | 107 |   | 
        
           |  |  | 108 |     // Override rows per page submission so it generates with filters.
 | 
        
           |  |  | 109 |     if (document.forms.selectperpage) {
 | 
        
           |  |  | 110 |         document.forms.selectperpage.onsubmit = (event) => {
 | 
        
           |  |  | 111 |             let getparam = 'perpage=' + document.forms.selectperpage.elements.perpage.value;
 | 
        
           |  |  | 112 |             generateWithFilters(event, getparam);
 | 
        
           |  |  | 113 |         };
 | 
        
           |  |  | 114 |     }
 | 
        
           |  |  | 115 |   | 
        
           |  |  | 116 |     // Override download link so the file is generated with filters.
 | 
        
           |  |  | 117 |     const downloadForm = document.getElementById('summaryreport').querySelector('form.dataformatselector');
 | 
        
           |  |  | 118 |     if (downloadForm) {
 | 
        
           |  |  | 119 |         downloadForm.onsubmit = (event) => {
 | 
        
           |  |  | 120 |             const downloadType = downloadForm.querySelector('#downloadtype_download').value;
 | 
        
           |  |  | 121 |             const getParams = `download=${downloadType}`;
 | 
        
           |  |  | 122 |             const prevAction = document.forms.filtersform.action;
 | 
        
           |  |  | 123 |   | 
        
           |  |  | 124 |             generateWithFilters(event, getParams);
 | 
        
           |  |  | 125 |   | 
        
           |  |  | 126 |             // Revert action, so re-submitting the form via filter does not trigger a further download.
 | 
        
           |  |  | 127 |             document.forms.filtersform.action = prevAction;
 | 
        
           |  |  | 128 |         };
 | 
        
           |  |  | 129 |     }
 | 
        
           |  |  | 130 |   | 
        
           |  |  | 131 |     // Submit report via filter
 | 
        
           |  |  | 132 |     const submitWithFilter = (containerelement) => {
 | 
        
           |  |  | 133 |         // Disable the dates filter mform checker to prevent any changes triggering a warning to the user.
 | 
        
           | 1441 | ariadna | 134 |         FormChangeChecker.unWatchForm(document.querySelector(containerelement).querySelector('form'));
 | 
        
           | 1 | efrain | 135 |         // Close the container (eg popover).
 | 
        
           |  |  | 136 |         $(containerelement).addClass('hidden');
 | 
        
           |  |  | 137 |   | 
        
           |  |  | 138 |         // Submit the filter values and re-generate report.
 | 
        
           |  |  | 139 |         generateWithFilters(false);
 | 
        
           |  |  | 140 |     };
 | 
        
           |  |  | 141 |   | 
        
           |  |  | 142 |     // Use popper to override date mform calendar position.
 | 
        
           |  |  | 143 |     const updateCalendarPosition = (referenceid) => {
 | 
        
           |  |  | 144 |         let referenceElement = document.querySelector(referenceid),
 | 
        
           |  |  | 145 |             popperContent = document.querySelector(Selectors.filters.date.calendar);
 | 
        
           |  |  | 146 |   | 
        
           |  |  | 147 |         popperContent.style.removeProperty("z-index");
 | 
        
           |  |  | 148 |         createPopper(referenceElement, popperContent, {placement: 'bottom-end'});
 | 
        
           |  |  | 149 |     };
 | 
        
           |  |  | 150 |   | 
        
           |  |  | 151 |     // Close the relevant filter.
 | 
        
           |  |  | 152 |     const closeOpenFilters = (openFilterButton, openFilter) => {
 | 
        
           |  |  | 153 |         openFilter.classList.add('hidden');
 | 
        
           |  |  | 154 |         openFilter.setAttribute('data-openfilter', 'false');
 | 
        
           |  |  | 155 |   | 
        
           |  |  | 156 |         openFilterButton.classList.add('btn-primary');
 | 
        
           |  |  | 157 |         openFilterButton.classList.remove('btn-outline-primary');
 | 
        
           |  |  | 158 |         openFilterButton.setAttribute('aria-expanded', false);
 | 
        
           |  |  | 159 |     };
 | 
        
           |  |  | 160 |   | 
        
           |  |  | 161 |     // Groups filter specific handlers.
 | 
        
           |  |  | 162 |   | 
        
           |  |  | 163 |     // Event handler for clicking select all groups.
 | 
        
           |  |  | 164 |     jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.selectall, function() {
 | 
        
           |  |  | 165 |         let deselected = root.querySelectorAll(Selectors.filters.group.checkbox + ':not(:checked)');
 | 
        
           |  |  | 166 |         deselected.forEach(function(checkbox) {
 | 
        
           |  |  | 167 |             checkbox.checked = true;
 | 
        
           |  |  | 168 |         });
 | 
        
           |  |  | 169 |     });
 | 
        
           |  |  | 170 |   | 
        
           |  |  | 171 |     // Event handler for clearing filter by clicking option.
 | 
        
           |  |  | 172 |     jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.clear, function() {
 | 
        
           |  |  | 173 |         // Clear checkboxes.
 | 
        
           |  |  | 174 |         let selected = root.querySelectorAll(Selectors.filters.group.checkbox + ':checked');
 | 
        
           |  |  | 175 |         selected.forEach(function(checkbox) {
 | 
        
           |  |  | 176 |             checkbox.checked = false;
 | 
        
           |  |  | 177 |         });
 | 
        
           |  |  | 178 |     });
 | 
        
           |  |  | 179 |   | 
        
           |  |  | 180 |     // Event handler for showing groups filter popover.
 | 
        
           |  |  | 181 |     jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.trigger, function() {
 | 
        
           |  |  | 182 |         // Create popover.
 | 
        
           |  |  | 183 |         let referenceElement = root.querySelector(Selectors.filters.group.trigger),
 | 
        
           |  |  | 184 |             popperContent = root.querySelector(Selectors.filters.group.popover);
 | 
        
           |  |  | 185 |   | 
        
           |  |  | 186 |         createPopper(referenceElement, popperContent, {placement: 'bottom-end'});
 | 
        
           |  |  | 187 |   | 
        
           |  |  | 188 |         // Show popover.
 | 
        
           |  |  | 189 |         popperContent.classList.remove('hidden');
 | 
        
           |  |  | 190 |         popperContent.setAttribute('data-openfilter', 'true');
 | 
        
           |  |  | 191 |   | 
        
           |  |  | 192 |         // Change to outlined button.
 | 
        
           |  |  | 193 |         referenceElement.classList.add('btn-outline-primary');
 | 
        
           |  |  | 194 |         referenceElement.classList.remove('btn-primary');
 | 
        
           |  |  | 195 |   | 
        
           |  |  | 196 |         // Let screen readers know that it's now expanded.
 | 
        
           |  |  | 197 |         referenceElement.setAttribute('aria-expanded', true);
 | 
        
           |  |  | 198 |   | 
        
           |  |  | 199 |         // Add listeners to handle closing filter.
 | 
        
           |  |  | 200 |         const closeListener = e => {
 | 
        
           |  |  | 201 |             if (e.target.id !== referenceElement.id && popperContent !== e.target.closest('[data-openfilter="true"]') &&
 | 
        
           |  |  | 202 |                     (typeof e.keyCode === 'undefined' || e.keyCode === KeyCodes.enter || e.keyCode === KeyCodes.space)) {
 | 
        
           |  |  | 203 |                 closeOpenFilters(referenceElement, popperContent);
 | 
        
           |  |  | 204 |                 document.removeEventListener('click', closeListener);
 | 
        
           |  |  | 205 |                 document.removeEventListener('keyup', closeListener);
 | 
        
           |  |  | 206 |                 document.removeEventListener('keyup', escCloseListener);
 | 
        
           |  |  | 207 |             }
 | 
        
           |  |  | 208 |         };
 | 
        
           |  |  | 209 |   | 
        
           |  |  | 210 |         document.addEventListener('click', closeListener);
 | 
        
           |  |  | 211 |         document.addEventListener('keyup', closeListener);
 | 
        
           |  |  | 212 |   | 
        
           |  |  | 213 |         const escCloseListener = e => {
 | 
        
           |  |  | 214 |             if (e.keyCode === KeyCodes.escape) {
 | 
        
           |  |  | 215 |                 closeOpenFilters(referenceElement, popperContent);
 | 
        
           |  |  | 216 |                 document.removeEventListener('keyup', escCloseListener);
 | 
        
           |  |  | 217 |                 document.removeEventListener('click', closeListener);
 | 
        
           |  |  | 218 |             }
 | 
        
           |  |  | 219 |         };
 | 
        
           |  |  | 220 |   | 
        
           |  |  | 221 |         document.addEventListener('keyup', escCloseListener);
 | 
        
           |  |  | 222 |     });
 | 
        
           |  |  | 223 |   | 
        
           |  |  | 224 |     // Event handler to click save groups filter.
 | 
        
           |  |  | 225 |     jqRoot.on(CustomEvents.events.activate, Selectors.filters.group.save, function() {
 | 
        
           |  |  | 226 |         // Copy the saved values into the form before submitting.
 | 
        
           |  |  | 227 |         let popcheckboxes = root.querySelectorAll(Selectors.filters.group.checkbox);
 | 
        
           |  |  | 228 |   | 
        
           |  |  | 229 |         popcheckboxes.forEach(function(popcheckbox) {
 | 
        
           |  |  | 230 |             let filtersform = document.forms.filtersform,
 | 
        
           |  |  | 231 |                 saveid = popcheckbox.getAttribute('data-saveid');
 | 
        
           |  |  | 232 |   | 
        
           |  |  | 233 |             filtersform.querySelector(`#${saveid}`).checked = popcheckbox.checked;
 | 
        
           |  |  | 234 |         });
 | 
        
           |  |  | 235 |   | 
        
           |  |  | 236 |         submitWithFilter('#filter-groups-popover');
 | 
        
           |  |  | 237 |     });
 | 
        
           |  |  | 238 |   | 
        
           |  |  | 239 |     // Listeners for export buttons.
 | 
        
           |  |  | 240 |     // These allow fetching of the relevant export URL, before submitting the request with
 | 
        
           |  |  | 241 |     // any POST data that is common to all of the export links. This allows filters to be
 | 
        
           |  |  | 242 |     // applied that contain potentially a lot of data (eg discussion IDs for groups filtering).
 | 
        
           |  |  | 243 |     document.querySelectorAll(Selectors.filters.exportlink.link).forEach(function(exportbutton) {
 | 
        
           |  |  | 244 |         exportbutton.addEventListener('click', function(event) {
 | 
        
           |  |  | 245 |             document.forms.exportlinkform.action = event.target.dataset.url;
 | 
        
           |  |  | 246 |             document.forms.exportlinkform.submit();
 | 
        
           |  |  | 247 |         });
 | 
        
           |  |  | 248 |     });
 | 
        
           |  |  | 249 |   | 
        
           |  |  | 250 |     // Dates filter specific handlers.
 | 
        
           |  |  | 251 |   | 
        
           |  |  | 252 |    // Event handler for showing dates filter popover.
 | 
        
           |  |  | 253 |     jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.trigger, function() {
 | 
        
           |  |  | 254 |   | 
        
           |  |  | 255 |         // Create popover.
 | 
        
           |  |  | 256 |         let referenceElement = root.querySelector(Selectors.filters.date.trigger),
 | 
        
           |  |  | 257 |             popperContent = root.querySelector(Selectors.filters.date.popover);
 | 
        
           |  |  | 258 |   | 
        
           |  |  | 259 |         createPopper(referenceElement, popperContent, {placement: 'bottom-end'});
 | 
        
           |  |  | 260 |   | 
        
           |  |  | 261 |         // Show popover and move focus.
 | 
        
           |  |  | 262 |         popperContent.classList.remove('hidden');
 | 
        
           |  |  | 263 |         popperContent.setAttribute('data-openfilter', 'true');
 | 
        
           |  |  | 264 |         popperContent.querySelector('[name="filterdatefrompopover[enabled]"]').focus();
 | 
        
           |  |  | 265 |   | 
        
           |  |  | 266 |         // Change to outlined button.
 | 
        
           |  |  | 267 |         referenceElement.classList.add('btn-outline-primary');
 | 
        
           |  |  | 268 |         referenceElement.classList.remove('btn-primary');
 | 
        
           |  |  | 269 |   | 
        
           |  |  | 270 |         // Let screen readers know that it's now expanded.
 | 
        
           |  |  | 271 |         referenceElement.setAttribute('aria-expanded', true);
 | 
        
           |  |  | 272 |   | 
        
           |  |  | 273 |         // Add listener to handle closing filter.
 | 
        
           |  |  | 274 |         const closeListener = e => {
 | 
        
           |  |  | 275 |             if (e.target.id !== referenceElement.id && popperContent !== e.target.closest('[data-openfilter="true"]') &&
 | 
        
           |  |  | 276 |                     (typeof e.keyCode === 'undefined' || e.keyCode === KeyCodes.enter || e.keyCode === KeyCodes.space)) {
 | 
        
           |  |  | 277 |                 closeOpenFilters(referenceElement, popperContent);
 | 
        
           |  |  | 278 |                 document.removeEventListener('click', closeListener);
 | 
        
           |  |  | 279 |                 document.removeEventListener('keyup', closeListener);
 | 
        
           |  |  | 280 |                 document.removeEventListener('keyup', escCloseListener);
 | 
        
           |  |  | 281 |             }
 | 
        
           |  |  | 282 |         };
 | 
        
           |  |  | 283 |   | 
        
           |  |  | 284 |         document.addEventListener('click', closeListener);
 | 
        
           |  |  | 285 |         document.addEventListener('keyup', closeListener);
 | 
        
           |  |  | 286 |   | 
        
           |  |  | 287 |         const escCloseListener = e => {
 | 
        
           |  |  | 288 |             if (e.keyCode === KeyCodes.escape) {
 | 
        
           |  |  | 289 |                 closeOpenFilters(referenceElement, popperContent);
 | 
        
           |  |  | 290 |                 document.removeEventListener('keyup', escCloseListener);
 | 
        
           |  |  | 291 |                 document.removeEventListener('click', closeListener);
 | 
        
           |  |  | 292 |             }
 | 
        
           |  |  | 293 |         };
 | 
        
           |  |  | 294 |   | 
        
           |  |  | 295 |         document.addEventListener('keyup', escCloseListener);
 | 
        
           |  |  | 296 |     });
 | 
        
           |  |  | 297 |   | 
        
           |  |  | 298 |     // Event handler to save dates filter.
 | 
        
           |  |  | 299 |     jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.save, function() {
 | 
        
           |  |  | 300 |         // Populate the hidden form inputs to submit the data.
 | 
        
           |  |  | 301 |         let filtersForm = document.forms.filtersform;
 | 
        
           |  |  | 302 |         const datesPopover = root.querySelector(Selectors.filters.date.popover);
 | 
        
           |  |  | 303 |         const fromEnabled = datesPopover.querySelector('[name="filterdatefrompopover[enabled]"]').checked ? 1 : 0;
 | 
        
           |  |  | 304 |         const toEnabled = datesPopover.querySelector('[name="filterdatetopopover[enabled]"]').checked ? 1 : 0;
 | 
        
           |  |  | 305 |   | 
        
           |  |  | 306 |         if (!fromEnabled && !toEnabled) {
 | 
        
           |  |  | 307 |             // Update the elements in the filter form.
 | 
        
           |  |  | 308 |             filtersForm.elements['datefrom[timestamp]'].value = 0;
 | 
        
           |  |  | 309 |             filtersForm.elements['datefrom[enabled]'].value = fromEnabled;
 | 
        
           |  |  | 310 |             filtersForm.elements['dateto[timestamp]'].value = 0;
 | 
        
           |  |  | 311 |             filtersForm.elements['dateto[enabled]'].value = toEnabled;
 | 
        
           |  |  | 312 |   | 
        
           |  |  | 313 |             // Submit the filter values and re-generate report.
 | 
        
           |  |  | 314 |             submitWithFilter('#filter-dates-popover');
 | 
        
           |  |  | 315 |         } else {
 | 
        
           |  |  | 316 |             let args = {data: []};
 | 
        
           |  |  | 317 |   | 
        
           |  |  | 318 |             if (fromEnabled) {
 | 
        
           |  |  | 319 |                 args.data.push({
 | 
        
           |  |  | 320 |                     'key': 'from',
 | 
        
           |  |  | 321 |                     'year': datesPopover.querySelector('[name="filterdatefrompopover[year]"]').value,
 | 
        
           |  |  | 322 |                     'month': datesPopover.querySelector('[name="filterdatefrompopover[month]"]').value,
 | 
        
           |  |  | 323 |                     'day': datesPopover.querySelector('[name="filterdatefrompopover[day]"]').value,
 | 
        
           |  |  | 324 |                     'hour': 0,
 | 
        
           |  |  | 325 |                     'minute': 0
 | 
        
           |  |  | 326 |                 });
 | 
        
           |  |  | 327 |             }
 | 
        
           |  |  | 328 |   | 
        
           |  |  | 329 |             if (toEnabled) {
 | 
        
           |  |  | 330 |                 args.data.push({
 | 
        
           |  |  | 331 |                     'key': 'to',
 | 
        
           |  |  | 332 |                     'year': datesPopover.querySelector('[name="filterdatetopopover[year]"]').value,
 | 
        
           |  |  | 333 |                     'month': datesPopover.querySelector('[name="filterdatetopopover[month]"]').value,
 | 
        
           |  |  | 334 |                     'day': datesPopover.querySelector('[name="filterdatetopopover[day]"]').value,
 | 
        
           |  |  | 335 |                     'hour': 23,
 | 
        
           |  |  | 336 |                     'minute': 59
 | 
        
           |  |  | 337 |                 });
 | 
        
           |  |  | 338 |             }
 | 
        
           |  |  | 339 |   | 
        
           |  |  | 340 |             const request = {
 | 
        
           |  |  | 341 |                 methodname: 'core_calendar_get_timestamps',
 | 
        
           |  |  | 342 |                 args: args
 | 
        
           |  |  | 343 |             };
 | 
        
           |  |  | 344 |   | 
        
           |  |  | 345 |             Ajax.call([request])[0].done(function(result) {
 | 
        
           |  |  | 346 |                 let fromTimestamp = 0,
 | 
        
           |  |  | 347 |                     toTimestamp = 0;
 | 
        
           |  |  | 348 |   | 
        
           |  |  | 349 |                 result.timestamps.forEach(function(data) {
 | 
        
           |  |  | 350 |                     if (data.key === 'from') {
 | 
        
           |  |  | 351 |                         fromTimestamp = data.timestamp;
 | 
        
           |  |  | 352 |                     } else if (data.key === 'to') {
 | 
        
           |  |  | 353 |                         toTimestamp = data.timestamp;
 | 
        
           |  |  | 354 |                     }
 | 
        
           |  |  | 355 |                 });
 | 
        
           |  |  | 356 |   | 
        
           |  |  | 357 |                 // Display an error if the from date is later than the do date.
 | 
        
           |  |  | 358 |                 if (toTimestamp > 0 && fromTimestamp > toTimestamp) {
 | 
        
           |  |  | 359 |                     const warningdiv = document.getElementById('dates-filter-warning');
 | 
        
           |  |  | 360 |                     warningdiv.classList.remove('hidden');
 | 
        
           |  |  | 361 |                     warningdiv.classList.add('d-block');
 | 
        
           |  |  | 362 |                 } else {
 | 
        
           |  |  | 363 |                     filtersForm.elements['datefrom[timestamp]'].value = fromTimestamp;
 | 
        
           |  |  | 364 |                     filtersForm.elements['datefrom[enabled]'].value = fromEnabled;
 | 
        
           |  |  | 365 |                     filtersForm.elements['dateto[timestamp]'].value = toTimestamp;
 | 
        
           |  |  | 366 |                     filtersForm.elements['dateto[enabled]'].value = toEnabled;
 | 
        
           |  |  | 367 |   | 
        
           |  |  | 368 |                     // Submit the filter values and re-generate report.
 | 
        
           |  |  | 369 |                     submitWithFilter('#filter-dates-popover');
 | 
        
           |  |  | 370 |                 }
 | 
        
           |  |  | 371 |             });
 | 
        
           |  |  | 372 |         }
 | 
        
           |  |  | 373 |     });
 | 
        
           |  |  | 374 |   | 
        
           |  |  | 375 |     jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.calendariconfrom, function() {
 | 
        
           |  |  | 376 |         updateCalendarPosition(Selectors.filters.date.calendariconfrom);
 | 
        
           |  |  | 377 |     });
 | 
        
           |  |  | 378 |   | 
        
           |  |  | 379 |     jqRoot.on(CustomEvents.events.activate, Selectors.filters.date.calendariconto, function() {
 | 
        
           |  |  | 380 |         updateCalendarPosition(Selectors.filters.date.calendariconto);
 | 
        
           |  |  | 381 |     });
 | 
        
           |  |  | 382 | };
 |