PageSourceSearch

https://norfolkairquality.net/Scripts/reports/report.js?v=3.272.23.58

js norfolkairquality.net collected 2026-10-06 22:04:11 UTC 80,514 bytes, 2,189 lines download raw bytes

1
2/*****************************/
3/****** Report Manager *******/
4/*****************************/
5function Report() {
6    let this_ = this;
7    let actualThis = null;
8
9    let model = {
10        latestReportView: REPORT_VIEW.TABLE,
11        reportView: INITIAL_REPORT_VIEW, //current report view
12    }
13
14    this_.modelValid = null
15    this_.pageContainer = null
16    this_.reportControlsConfig = null
17    this_.reportConcreteName = null; //pageName
18    this_.reportsValidator = null;
19
20    this.regionCombobox = null;
21    this.targetCombobox = null;
22    this.ownerCombobox = null;
23    this.stationsAndMonitorsControl = null;
24    this.fromDatePicker = null;
25    this.toDatePicker = null;
26    this.fromTimePicker = null;
27    this.toTimePicker = null;
28
29    /**
30     * Overridable factory for the control configured as REPORT_CONTROL.stationsAndMonitors.
31     * Concrete reports receive the configured containers as jQuery objects, in configuration
32     * order, and return the report-specific station/monitor control instance.
33     */
34    this.createStationsAndMonitorsControls = (a, b, c, d = null) => { }
35
36    /**
37     * Stores the API returned by ReportUtils.createAllStationsTreeViewControl for reports that
38     * opt in. Report implementations use it to apply or inspect the All state and to synchronize
39     * the checkbox after TreeView or programmatic station-selection changes.
40     * @type {Object|null}
41     */
42    this.selectAllTreeViewStationsCheckboxControl = null;
43
44    /**
45     * Enables the reusable All Stations TreeView checkbox for a report. The default is false;
46     * supporting reports set it to true before controls are created. Reports that override the
47     * station selector are responsible for creating and assigning the shared control themselves.
48     * @type {boolean}
49     */
50    this.useSelectAllTreeViewStationsCheckbox = false;
51
52    this.selectTimeBase = null;
53    this.selectUseFilter = null;
54    this.selectEditType = null;
55    this.selectDescription = null;
56    this.selectSharedTimebases = null;
57
58    this.selectProvince = null;
59    this.selectDistrict = null;
60    this.selectMunicipality = null;
61    this.selectIncidentType = null;
62    this.selectCalibrationType = null;
63    this.selectMetadataType = null;
64
65    this.textInput = null;
66    this.selectParameter = null;
67
68    const EURegulations = configManager.read(webConfigs.EURegulations);
69    this.stationTypeCombobox = null;
70    this.stationAreaTypeCombobox = null;
71
72    /*****************************/
73    /*Report API data global vars*/
74    let elemetBuilder
75    /*****************************/
76    /*****************************/
77
78    this.fromTimeBaseCombobox = null;
79    this.toTimeBaseCombobox = null;
80
81    this.typeCombobox = null
82    this.reportPresentationBtnGroup = null
83    this.groupIdComboBox = null;
84    this.prevReportPeriod = INITIAL_REPORT_PERIOD;
85    let m_CurReportPeriod = INITIAL_REPORT_PERIOD;
86
87    this.allDefinedTimeBases = null
88    this.inPopup = null
89    this.firstTime;
90
91    let isTimeEnding = configManager.read(webConfigs.TimeEnding)
92
93    let controls = null;
94    let authControls = null;
95
96    this.init = function (concreteManager, config) {
97        this_.firstTime = true;
98        actualThis = concreteManager;
99        this_.allDefinedTimeBases = SiteUtils.getAllDefinedTimeBases();
100        this_.inPopup = config.popup;
101        this_.pageContainer = config.pageContainer;
102        this_.reportConcreteName = config.concreteName;
103        controls = config.controls;
104        actualThis.createControls(config.controls)
105        actualThis.populateControls();
106        this_.setReportsValidator();
107        actualThis.initEvents();
108
109        if (config.hasOwnProperty('authControls')) {
110            authControls = config.authControls;
111        }
112    }
113
114    this.filterAvailableToTimebases = function (timebases, fromTimebase) {
115        return timebases;
116    }
117
118    this.filterAvailableFromTimebases = function (timebases) {
119        return timebases;
120    }
121
122    this.createControls = function (controls) {
123        $.each(controls, function (controlName, controlSelector) {
124
125            createControl(parseInt(controlName), controlSelector)
126        });
127
128        function createControl(controlName, controlSelector) {
129            switch (controlName) {
130                case REPORT_CONTROL.target:
131                    actualThis.createTargetComboBox($(controlSelector));
132                    break;
133                case REPORT_CONTROL.regions:
134                    actualThis.createRegionComboBox($(controlSelector))
135                    break;
136                case REPORT_CONTROL.owner:
137                    actualThis.createOwnerComboBox($(controlSelector))
138                    break;
139                case REPORT_CONTROL.fromDate:
140                    actualThis.createFromDatePicker($(controlSelector))
141                    break;
142                case REPORT_CONTROL.fromTime:
143                    actualThis.createFromTimePicker($(controlSelector))
144                    break;
145                case REPORT_CONTROL.toDate:
146                    actualThis.createToDatePicker($(controlSelector))
147                    break;
148                case REPORT_CONTROL.toTime:
149                    actualThis.createToTimePicker($(controlSelector))
150                    break;
151                case REPORT_CONTROL.fromTimebase:
152                    actualThis.createFromTimeBaseComboBox($(controlSelector))
153                    break;
154                case REPORT_CONTROL.toTimebase:
155                    actualThis.createToTimeBaseComboBox($(controlSelector))
156                    break;
157                case REPORT_CONTROL.type:
158                    actualThis.createTypeCombobox($(controlSelector))
159                    break;
160                case REPORT_CONTROL.period:
161                    actualThis.createReportPeriod($(controlSelector))
162                    break;
163                case REPORT_CONTROL.presentation:
164                    actualThis.createReportPresentation($(controlSelector));
165                    break;
166                case REPORT_CONTROL.reducePresentation:
167                    actualThis.createReducedReportPresentation($(controlSelector))
168                    break;
169                case REPORT_CONTROL.roseType:
170                    actualThis.createRoseType($(controlSelector))
171                    break;
172                case REPORT_CONTROL.roseSectors:
173                    actualThis.createRoseSectors($(controlSelector[0]), $(controlSelector[1]), $(controlSelector[2]), $(controlSelector[3]), $(controlSelector[4]))
174                    break;
175                case REPORT_CONTROL.stations:
176                    this_.stationsAndMonitorsControl = actualThis.createStationsControl($(controlSelector[0]), $(controlSelector[1]))
177                    break;
178                case REPORT_CONTROL.stationsAndMonitors:
179                    this_.stationsAndMonitorsControl =
180                        actualThis.createStationsAndMonitorsControls(...controlSelector.map(control => $(control)));
181                    break;
182                case REPORT_CONTROL.groupID:
183                    actualThis.populateGroupsComboBox($(controlSelector))
184                    //actualThis.createGroupIdComboBox($(controlSelector));
185                    break;
186                case REPORT_CONTROL.stationsAndWindMonitors:
187                    this_.stationsAndMonitorsControl = actualThis.createStationsAndWindMonitorsControls($(controlSelector[0]), $(controlSelector[1]), $(controlSelector[2]), $(controlSelector[3]))
188                    break;
189                case REPORT_CONTROL.timebase:
190                    actualThis.createTimebaseControl();
191                    break;
192                case REPORT_CONTROL.useFilter:
193                    actualThis.createUseFilterControl();
194                    break;
195                case REPORT_CONTROL.editType:
196                    actualThis.createEditTypeControl();
197                    break;
198                case REPORT_CONTROL.description:
199                    actualThis.createDescriptionControl();
200                    break;
201                case REPORT_CONTROL.sharedTimebases:
202                    actualThis.createSharedTimebasesControl();
203                    break;
204                case REPORT_CONTROL.windBlowDirection:
205                    actualThis.creatWindDirectionControl($(controlSelector));
206                    break;
207                case REPORT_CONTROL.province:
208                    actualThis.createProvinceControl($(controlSelector));
209                    break;
210                case REPORT_CONTROL.district:
211                    actualThis.createDistrictControl($(controlSelector));
212                    break;
213                case REPORT_CONTROL.municipality:
214                    actualThis.createMunicipalityControl($(controlSelector));
215                    break;
216                case REPORT_CONTROL.incidentType:
217                    actualThis.createIncidentTypeControl($(controlSelector));
218                    break;
219                case REPORT_CONTROL.textInput:
220                    actualThis.createTextInputControl($(controlSelector));
221                    break;
222                case REPORT_CONTROL.calibrationType:
223                    actualThis.createCalibrationTypeControl();
224                    break;
225                case REPORT_CONTROL.metadataType:
226                    actualThis.createMetadataDropDown($(controlSelector));
227                    break;
228                case REPORT_CONTROL.parameters:
229                    actualThis.createParametersControl();
230                    break;
231                case REPORT_CONTROL.stationType:
232                    actualThis.createStationTypeControl($(controlSelector));
233                    break;
234                case REPORT_CONTROL.stationAreaType:
235                    actualThis.createStationAreaTypeControl($(controlSelector));
236                    break;
237            }
238        }
239    }
240
241    this.populateControls = function () {
242        this_.setFromTimeBaseComboboxDs(this_.allDefinedTimeBases)
243        this_.setToTimeBaseComboboxDs(this_.allDefinedTimeBases)
244
245        this_.populateTargetCombobox()
246        this_.populateRegionComboboxByTarget()
247        this_.populateOwnersComboBox()
248
249        if (EURegulations) {
250            initEUStationTypeWhenReady();
251            initEUStationAreaTypeWhenReady();
252        }
253
254        this_.populateStationsAndMonitorsControl()
255        this_.setDateTimePickersByReportPeriod()
256        this_.firstTime = false;
257    }
258
259    this.initEvents = function () {
260
261        if (this_.inPopup) {
262            $(".closeReportFormBtn").click(function () {
263                PopupManager.closeReportForm()
264            });
265        }
266
267        $(this_.pageContainer).find("#showResultsBtn").click(function () {
268            actionBtnClicked();
269        });
270    }
271
272    //like pure functions
273    this.showResult = function () { }
274    this.exportToExcel = function () { }
275    this.createReportTitle = function () { }
276    this.getStationsFilter = function () { return {} }
277    this.setRawData = function () { return {} }
278
279    this.getReportName = function () {
280        return actualThis.reportName
281    }
282
283    this.getReportConcreteName = function () {
284        return actualThis.reportConcreteName
285    }
286
287    // Controls Creators
288    /**
289     * Creates the target multiselect.
290     * The widget displays translated labels from `displayName`
291     * while keeping the original `name` field intact for filtering logic.
292     */
293    this.createTargetComboBox = function (container) {
294
295        let extensionConfig = {
296            dataTextField: "displayName",
297            dataValueField: "id",
298        }
299
300        extensionConfig.select = function (e) {
301            this_.targetCombobox._prevValues = e.sender.value() || [];
302        };
303
304        let targetCombobox = KendoFactory.createMultiSelect(container, undefined, undefined, onTargetChanged, undefined, extensionConfig, "targetCombobox");
305
306        this_.targetCombobox = $(targetCombobox).data("kendoMultiSelect");
307        this_.targetCombobox._prevValues = [];
308        return targetCombobox;
309    }
310    /**
311     * createGroupIdComboBox
312     * @param {any} groupContainer
313     */
314    this.createGroupIdComboBox = (groupContainer) => {
315        let extensionConfig = {
316            dataTextField: "GRD_Name",
317            dataValueField: "GRD_GroupCode",
318        }
319
320        let params = { isDropDown: true }
321        var combobox = new kCombobox(groupContainer, extensionConfig, params);
322        $(".k-list-filter").find("input").attr("placeholder", SiteUtils.translate("select.."));
323        return combobox
324    }
325
326
327    /**
328     * Creates the region multiselect.
329     * The selected value is still the stable `id`, and the visible text comes from
330     * `displayName` so region names can be translated without affecting comparisons.
331     */
332    this.createRegionComboBox = function (container) {
333        let extensionConfig = {
334            dataTextField: "displayName",
335            dataValueField: "id",
336        }
337
338        extensionConfig.select = function (e) {
339            this_.regionCombobox._prevValues = e.sender.value() || [];
340        };
341
342        let regionCombobox = KendoFactory.createMultiSelect(container, undefined, undefined, onRegionChanged, undefined, extensionConfig);
343        this_.regionCombobox = $(regionCombobox).data("kendoMultiSelect");
344        this_.regionCombobox._prevValues = [];
345    }
346
347    /**
348     * Creates the owner multiselect.
349     * Owner filtering downstream relies on the original `name` from the datasource,
350     * so the UI must render from `displayName` instead of mutating `name`.
351     */
352    this.createOwnerComboBox = function (container, onChange) {
353
354        let extensionConfig = {
355            dataTextField: "displayName",
356            dataValueField: "id",
357        }
358
359        const onOwnerChange = function (ownersAfterChange) {
360            const ownersBeforeChange = this_.ownerCombobox._prevValues;
361            applyStationFiltersAllExclusiveSelection(this_.ownerCombobox, ownersAfterChange, ownersBeforeChange);
362            refreshDependentFilters();
363        };
364
365        extensionConfig.select = function (e) {
366            this_.ownerCombobox._prevValues = e.sender.value() || [];
367        };
368
369        let regionCombobox = KendoFactory.createMultiSelect(container, undefined, undefined, onOwnerChange, undefined, extensionConfig);
370        this_.ownerCombobox = $(regionCombobox).data("kendoMultiSelect");
371        this_.ownerCombobox._prevValues = [];
372    };
373
374    /**
375     * Enforces exclusive behavior for the "All" option (-1)
376     * in Station Filters MultiSelect controls.
377     *
378     * Rules:
379     * - If nothing is selected → selects "All".
380     * - If "All" is selected with other values:
381     *   • If "All" was previously selected → removes "All".
382     *   • Otherwise → keeps only "All".
383     * - Otherwise → keeps default behavior.
384     */
385    function applyStationFiltersAllExclusiveSelection(multiSelect, valuesAfterChange, valuesBeforeChange) {
386        if (!multiSelect) return;
387        const ALL_VALUE = "-1"
388        const before = (valuesBeforeChange ?? []).map(v => String(v));
389        const after = (valuesAfterChange ?? []).map(v => String(v));
390
391        const beforeHadAll = before.includes(ALL_VALUE);
392        const afterHasAll = after.includes(ALL_VALUE);
393
394        if (after.length === 0) {
395            multiSelect.value([ALL_VALUE]);
396            return;
397        }
398
399        if (afterHasAll && after.length > 1) {
400            if (beforeHadAll) {
401                multiSelect.value(after.filter(v => v !== ALL_VALUE));
402                return;
403            }
404            multiSelect.value([ALL_VALUE]);
405            return;
406        }
407    }
408
409    this.createTypeCombobox = function (container, config) {
410        if (config == null) {
411            let ds = SiteUtils.getTranslatedObject(REPORT_TYPE);
412            ds = filterNotForDisplay(ds, webConfigs.NotForDisplayTypes);
413            config = {
414                change: onTypeChanged,
415                value: INITIAL_REPORT_TYPE,
416                dataSource: ds
417            }
418        }
419        config.filter = "none";
420
421        let params = { isDropDown: true }
422
423        this_.typeCombobox = new kCombobox(container, config, params)
424
425        // not set default value for Pollutant/Wind Rose report
426        if (actualThis.reportName !== REPORTS.pollutantRose) {
427            setDefaultValueByConfig(this_.typeCombobox, webConfigs.DefaultType, config.dataSource);
428        }
429    }
430
431    this.createFromTimePicker = function (container) {
432        var isToTime = false;
433        this_.fromTimePicker = KendoFactory.timepicker(container, true, (val) => onChangeTimePicker(val, isToTime), () => onClickTimePicker(isToTime));
434    }
435
436    this.createToTimePicker = function (container) {
437        var isToTime = true;
438        this_.toTimePicker = KendoFactory.timepicker(container, true, (val) => onChangeTimePicker(val, isToTime), () => onClickTimePicker(isToTime));
439    }
440
441    function onClickTimePicker(isToTime) {
442        var display;
443        if (isToTime) {
444            display = $("#toTimeTimePicker_timeview").closest(".k-animation-container").css('display');
445            display == 'none' || JsUtils.isEmpty(display) ? this_.toTimePicker.open() : this_.toTimePicker.close();
446        } else {
447            display = $("#fromTimeTimePicker_timeview").closest(".k-animation-container").css('display');
448            display == 'none' || JsUtils.isEmpty(display) ? this_.fromTimePicker.open() : this_.fromTimePicker.close();
449        }
450    }
451
452    function onChangeTimePicker(val, isToTime) {
453        if (isTimeEnding) {
454            var timeList = isToTime ? this_.toTimePicker.timeView.list : this_.fromTimePicker.timeView.list;
455            if (val == '24:00') {
456                timeList.find("li").removeClass("k-selected");
457                timeList.find("li:last").addClass("k-selected");
458                isToTime ? this_.toTimePicker.value(null) : this_.fromTimePicker.value(null);
459                isToTime ? this_.toTimePicker.element.val(val) : this_.fromTimePicker.element.val(val);
460            } else {
461                timeList.find("li:last").removeClass("k-selected");
462            }
463        }
464    }
465
466    this.createFromDatePicker = function (container, initialDate) {
467        let config = {
468            change: onFromDateChange,
469            value: initialDate,
470            max: new Date(),
471        };
472
473        this_.fromDatePicker = KendoFactory.datepicker(container, config)
474    }
475
476
477    /**
478     * set the report "from" date picker element from an external source
479     * @param {kendoObject} inputElement
480     */
481    this.setFromDatePickerElement = function (inputElement) {
482        this_.fromDatePicker = inputElement
483    }
484
485    this.createToDatePicker = function (container) {
486        let config = {
487            change: onToDateChange,
488        };
489
490        this_.toDatePicker = KendoFactory.datepicker(container, config)
491    }
492
493    /**
494     * set the report "to" date picker element from an external source
495     * @param {kendoObject} inputElement
496     */
497    this.setToDatePickerElement = function (inputElement) {
498        this_.toDatePicker = inputElement
499    }
500
501    this.createReportPeriod = function (container) {
502
503        $(container).kendoButtonGroup({
504            index: 0
505        });
506
507        // register to event handlers
508        $("#selectReportPeriod .reportPeriod").click(function () {
509            m_CurReportPeriod = $(this).attr("period-type")
510            onReportPeriodChanged(m_CurReportPeriod)
511        });
512
513    }
514
515    /**
516     * receive a list of report view types and assemble the html required to build the view selector
517     * @param {number[]} config
518     * @returns {string} a string representation of html
519     */
520    function generatePresentationHtmlByConfig(config) {
521        const reportViewNames = {
522            [REPORT_VIEW.TABLE]: "Table",
523            [REPORT_VIEW.GRAPH]: "Graph",
524            [REPORT_VIEW.EXCEL]: "Excel",
525            [REPORT_VIEW.HORIZONTAL_TABLE]: "Horizontal Table",
526            [REPORT_VIEW.MAP]: "Map",
527            [REPORT_VIEW.PDF]: "PDF"
528        }
529        let innerHtml = "";
530        config.forEach((viewType) => {
531            innerHtml += '<li class=reportPresentation viewType=' + viewType + ' >' +
532                SiteUtils.translate(reportViewNames[viewType]) +
533                '</li>'
534
535        })
536        return innerHtml;
537    }
538
539    this.createReportPresentation = function (container, onChanged, presentationConfig = actualThis.INITIAL_REPORT_PRESENTATION_CONFIG) {
540        let innerHtml = generatePresentationHtmlByConfig(presentationConfig);
541        $(container).append(innerHtml)
542
543        this_.reportPresentationBtnGroup = $(container).kendoButtonGroup({
544            index: presentationConfig.indexOf(this_.getReportView())
545        }).data("kendoButtonGroup");
546
547        $("#selectReportPresentation .reportPresentation").click(function () {
548
549            let curView = parseInt($(this).attr("viewType"))
550            this_.setReportView(curView);
551
552            if (onChanged != null) {
553                onChanged(curView)
554            }
555        });
556    }
557
558    /**
559    * Updates the state (enabled/disabled) of report presentation buttons based on the allowed views,
560    * and ensures that a valid view is selected. If the currently selected view is not in the allowed list,
561    * the first available allowed view will be selected instead.
562    * @function
563    * @param {number[]} allowedViews - An array of allowed view types, each corresponding to a value from the `REPORT_VIEW` enum.
564    *   Example: [REPORT_VIEW.TABLE, REPORT_VIEW.SUMMARY, REPORT_VIEW.PDF]
565    * @example
566    * updateReportPresentationButtons([REPORT_VIEW.TABLE, REPORT_VIEW.SUMMARY]);
567    *
568    * @see REPORT_VIEW for possible view type constants.
569    */
570    this.updateReportPresentationButtons = function (allowedViews, mode = "disable") {
571        const buttonGroup = this_.reportPresentationBtnGroup;
572        const buttons = buttonGroup ?._buttons || [];
573
574        const toggleClassName = mode == "hide" ? "hidden" : "disabled";
575
576        buttons.forEach(function (btn) {
577            const btnElement = $(btn.element);
578            const viewType = parseInt(btnElement.attr("viewtype"));
579            const isEnabled = allowedViews.includes(viewType);
580
581            btnElement.toggleClass(toggleClassName, !isEnabled);
582        });
583
584        const selectedIndex = buttonGroup.selectedIndices ?.[0];
585        const selectedBtn = buttons[selectedIndex];
586        const selectedViewType = selectedBtn ? parseInt($(selectedBtn.element).attr("viewtype")) : null;
587        this_.setReportView(selectedViewType);
588
589        if (!allowedViews.includes(selectedViewType)) {
590            const firstAllowedIndex = buttons.findIndex(btn => allowedViews.includes(parseInt($(btn.element).attr("viewtype"))));
591            if (firstAllowedIndex >= 0) {
592                buttonGroup.select(firstAllowedIndex);
593                this_.setReportView(parseInt($(buttons[firstAllowedIndex].element).attr("viewtype")));
594            }
595        }
596    }
597
598    /**
599     * createReducedReportPresentation
600     * @param {any} container
601     * @param {any} onChanged
602     */
603    this.createReducedReportPresentation = (container, onChanged) => {
604        let innerHtml = '<li class=reportPresentation viewType=' + REPORT_VIEW.TABLE + ' >' +
605            SiteUtils.translate("Table") +
606            '</li>' +
607            '<li class=reportPresentation viewType=' + REPORT_VIEW.EXCEL + ' >' +
608            SiteUtils.translate("Excel") +
609            '</li>';
610        $(container).append(innerHtml)
611        this_.reportPresentationBtnGroup = $(container).kendoButtonGroup({
612            index: this_.getReportView()
613        }).data("kendoButtonGroup");
614
615        $("#selectReportPresentation .reportPresentation").click(function () {
616
617            let curView = parseInt($(this).attr("viewType"))
618            this_.setReportView(curView);
619
620            if (onChanged != null) {
621                onChanged(curView)
622            }
623        });
624
625    }
626
627    this.createFromTimeBaseComboBox = function (container) {
628
629        let extensionConfig = {
630            change: onFromTimeBaseChanged,
631            filter: "none"
632        }
633
634        let params = { isDropDown: true }
635
636        this_.fromTimeBaseCombobox = new kCombobox(container, extensionConfig, params)
637    }
638
639    this.createToTimeBaseComboBox = function (container) {
640
641        let extensionConfig = {
642            change: onToTimeBaseChanged,
643            filter: "none"
644        }
645
646        let params = { isDropDown: true }
647
648        this_.toTimeBaseCombobox = new kCombobox(container, extensionConfig, params)
649    }
650
651
652    /**
653     * Create the time base control.
654     */
655    this.createTimebaseControl = () => {
656        this_.selectTimeBase = new SelectTimeBase();
657        let extensionConfig = {
658            change: setTimePickersByTimebase,
659            filter: "none"
660        }
661
662        this_.selectTimeBase.init(extensionConfig);
663    }
664
665    /**
666     * Create the use filter control.
667     */
668    this.createUseFilterControl = () => {
669        this_.selectUseFilter = new SelectUseFilter();
670
671        this_.selectUseFilter.init();
672    }
673
674    /**
675     * Create the edit type control.
676     */
677    this.createEditTypeControl = () => {
678        this_.selectEditType = new SelectEditType();
679
680        this_.selectEditType.init();
681    }
682
683    /**
684     * Create the description control.
685     */
686    this.createDescriptionControl = () => {
687        this_.selectDescription = new SelectDescription();
688
689        this_.selectDescription.init();
690    }
691
692    /**
693     * Create the shared timebases control
694     */
695    this.createSharedTimebasesControl = () => {
696        this_.selectSharedTimebases = new SelectSharedTimebases();
697
698        this_.selectSharedTimebases.init();
699    }
700
701    /**
702     * Create the province control.
703     */
704    this.createProvinceControl = () => {
705        this_.selectProvince = new SelectProvince();
706
707        let extensionConfig = {
708            change: onProvinceChanged,
709            filter: "contains",
710            dataValueField: "ID",
711            dataTextField: "Name",
712        }
713
714        this_.selectProvince.init(extensionConfig);
715    }
716
717    /**
718     * Create the District control.
719     */
720    this.createDistrictControl = () => {
721        this_.selectDistrict = new SelectDistrict();
722
723        let extensionConfig = {
724            change: onDistrictChanged,
725            filter: "contains",
726            dataValueField: "ID",
727            dataTextField: "Name",
728        }
729
730        this_.selectDistrict.init(extensionConfig);
731    }
732
733    /**
734     * Create the Municipality control.
735     */
736    this.createMunicipalityControl = () => {
737        this_.selectMunicipality = new SelectMunicipality();
738
739        let extensionConfig = {
740            filter: "contains",
741            dataValueField: "ID",
742            dataTextField: "Name",
743        }
744
745        this_.selectMunicipality.init(extensionConfig);
746    }
747
748    /**
749     * Create the IncidentType control.
750     */
751    this.createIncidentTypeControl = () => {
752        this_.selectIncidentType = new SelectIncidentType();
753
754        let extensionConfig = {
755            filter: "contains",
756            dataValueField: "ID",
757            dataTextField: "Name",
758        }
759
760        this_.selectIncidentType.init(extensionConfig);
761    }
762
763    /**
764     * Create the CalibrationType control.
765     */
766    this.createCalibrationTypeControl = () => {
767        this_.selectCalibrationType = new SelectCalibrationType();
768
769        this_.selectCalibrationType.init();
770    }
771
772    /**
773     * Create the Metadata DropDown
774     */
775    this.createMetadataDropDown = function () {
776        this_.selectMetadataType = new SelectMetadataType();
777
778        let extensionConfig = {
779            dataTextField: "name",
780            dataValueField: "id",
781            filter: "none",
782            change: function () {
783                this_.stationsAndMonitorsControl.populateControls()
784            }
785        }
786
787        this_.selectMetadataType.init(extensionConfig);
788    }
789
790
791    /**
792     * Create the Parameters options
793     */
794    this.createParametersControl = function () {
795        this_.selectParameter = new SelectParameter();
796    }
797
798    /**
799     * Create the Text Input control.
800     */
801    this.createTextInputControl = () => {
802        this_.textInput = new TextInput();
803    }
804
805    /**
806     * Creates the Station Type multiselect used in the EU regulations flow.
807     * Stores the previous selection so the "All" option can be handled exclusively on change.
808     * @param {JQuery} container
809     */
810    this.createStationTypeControl = (container) => {
811        let extensionConfig = {
812            dataTextField: "displayName",
813            dataValueField: "id",
814        }
815
816        extensionConfig.select = function (e) {
817            this_.stationTypeCombobox._prevValues = e.sender.value() || [];
818        };
819
820        let stationTypeCombobox = KendoFactory.createMultiSelect(container, undefined, undefined, onStationTypeChanged, undefined, extensionConfig);
821        this_.stationTypeCombobox = $(stationTypeCombobox).data("kendoMultiSelect");
822        this_.stationTypeCombobox._prevValues = [];
823    }
824
825    /**
826     * Creates the Station Area multiselect used in the EU regulations flow.
827     * Stores the previous selection so the "All" option can be handled exclusively on change.
828     * @param {JQuery} container
829     */
830    this.createStationAreaTypeControl = (container) => {
831        let extensionConfig = {
832            dataTextField: "displayName",
833            dataValueField: "id",
834        }
835
836        extensionConfig.select = function (e) {
837            this_.stationAreaTypeCombobox._prevValues = e.sender.value() || [];
838        };
839
840        let stationAreaTypeCombobox = KendoFactory.createMultiSelect(container, undefined, undefined, onStationAreaTypeChanged, undefined, extensionConfig);
841        this_.stationAreaTypeCombobox = $(stationAreaTypeCombobox).data("kendoMultiSelect");
842        this_.stationAreaTypeCombobox._prevValues = [];
843
844    }
845
846    // Controls Populators
847    /**
848     * Loads the target datasource for reports.
849     * Each item gets a translated `displayName`, but the raw `name` is preserved
850     * because later filters compare against the original non-translated value.
851     */
852    this.populateTargetCombobox = function () {
853        let stationsFilter = actualThis.getStationsFilter();
854        let relevantTargets = dataManager.getTargetsBy(stationsFilter);
855        relevantTargets = filterNotForDisplay(relevantTargets, webConfigs.NotForDisplayTargets);
856        relevantTargets = buildDisplayItems(relevantTargets);
857        this_.targetCombobox.setDataSource(relevantTargets);
858
859        setDefaultValueByConfig(this_.targetCombobox, webConfigs.DefaultTarget, relevantTargets);
860    }
861
862    /**
863     * Rebuilds the region datasource after target changes.
864     * Regions follow the same display/value split: translated `displayName` for UI,
865     * original `name` for any business logic that consumes datasource items.
866     */
867    this.populateRegionComboboxByTarget = function () {
868        let stationsFilter = {
869            target: this_.getTarget()
870        };
871
872        $.extend(stationsFilter, actualThis.getStationsFilter())
873        let relevantRegions = dataManager.getRegionsBy(stationsFilter);
874        relevantRegions = filterNotForDisplay(relevantRegions, webConfigs.NotForDisplayRegions);
875        relevantRegions = buildDisplayItems(relevantRegions);
876        this_.regionCombobox.setDataSource(relevantRegions);
877
878        setDefaultValueByConfig(this_.regionCombobox, webConfigs.DefaultRegion, relevantRegions);
879    }
880
881    /**
882     * Rebuilds the owner datasource from the active upstream filters.
883     * The dropdown renders `displayName`, while report filtering continues to use
884     * the raw `name` returned by the datasource.
885     */
886    this.populateOwnersComboBox = function () {
887        let stationsFilter = {
888            target: this_.getTarget(),
889            regionId: this_.getRegion()
890        };
891
892        $.extend(stationsFilter, actualThis.getStationsFilter());
893        let relevantOwners = dataManager.getOwnersBy(stationsFilter);
894        relevantOwners = filterNotForDisplay(relevantOwners, webConfigs.NotForDisplayOwners);
895        relevantOwners = buildDisplayItems(relevantOwners);
896        this_.ownerCombobox.setDataSource(relevantOwners);
897
898        setDefaultValueByConfig(this_.ownerCombobox, webConfigs.DefaultOwner, relevantOwners);
899    }
900
901    this.populateStationsAndMonitorsControl = function () {
902        if (this_.stationsAndMonitorsControl != null) {
903            this_.stationsAndMonitorsControl.populateControls()
904        }
905    }
906
907    /**
908     * Rebuilds the Station Type data source from the stations that match the upstream filters.
909     * This dropdown is downstream of Target, Region and Owner and must not filter itself.
910     */
911    this.populateTypeOfStationCombobox = function () {
912        if (!EURegulations || !this_.stationTypeCombobox || !EUConfigurationManager.isStationTypeLoaded) {
913            return;
914        }
915
916        let stationsFilter = {
917            target: this_.getTarget(),
918            regionId: this_.getRegion(),
919            owner: this_.getOwner(),
920        };
921
922        $.extend(stationsFilter, actualThis.getStationsFilter());
923        let relevantStationTypes = dataManager.getStationTypesBy(stationsFilter);
924        relevantStationTypes = buildDisplayItems(relevantStationTypes);
925        this_.stationTypeCombobox.setDataSource(relevantStationTypes);
926        setDefaultValueByConfig(this_.stationTypeCombobox, '', relevantStationTypes);
927    };
928
929    /**
930     * Rebuilds the Station Area data source from the stations that match the active filters.
931     * Station Area depends on the selected Station Type, so the current station type is included
932     * while the area dropdown itself is excluded inside the data-manager lookup.
933     */
934    this.populateTypeOfStationAreaCombobox = function () {
935        if (!EURegulations || !this_.stationAreaTypeCombobox || !EUConfigurationManager.isStationAreaTypeLoaded) {
936            return;
937        }
938
939        let stationsFilter = {
940            target: this_.getTarget(),
941            regionId: this_.getRegion(),
942            owner: this_.getOwner(),
943            stationType: this_.getStationType()
944        };
945
946        $.extend(stationsFilter, actualThis.getStationsFilter());
947        let relevantStationAreaTypes = dataManager.getStationAreaTypesBy(stationsFilter);
948        relevantStationAreaTypes = buildDisplayItems(relevantStationAreaTypes);
949        this_.stationAreaTypeCombobox.setDataSource(relevantStationAreaTypes);
950        setDefaultValueByConfig(this_.stationAreaTypeCombobox, '', relevantStationAreaTypes);
951    }
952
953    /**
954     * Enables and populates Station Type as soon as its EU master list is ready.
955     * The handler is removed after the first completion to avoid duplicate refreshes.
956     */
957    function initEUStationTypeWhenReady() {
958        if (!this_.stationTypeCombobox) {
959            return;
960        }
961
962        EventBus.off(EventBus.EventNames.STATION_TYPE_DATA_LOADED, onStationTypeDataLoaded);
963        EventBus.off(EventBus.EventNames.STATION_TYPE_DATA_FAILED, onStationTypeDataFailed);
964
965        if (EUConfigurationManager.isStationTypeLoaded) {
966            this_.stationTypeCombobox.enable(true);
967            this_.populateTypeOfStationCombobox();
968            return;
969        }
970
971        if (EUConfigurationManager.isStationTypeLoadFailed) {
972            this_.stationTypeCombobox.enable(false);
973            return;
974        }
975
976        this_.stationTypeCombobox.enable(false);
977        EventBus.on(EventBus.EventNames.STATION_TYPE_DATA_LOADED, onStationTypeDataLoaded);
978        EventBus.on(EventBus.EventNames.STATION_TYPE_DATA_FAILED, onStationTypeDataFailed);
979    }
980
981    /**
982     * Enables and populates Station Area as soon as its EU master list is ready.
983     * This keeps the two EU dropdowns independent while preserving the cascade logic.
984     */
985    function initEUStationAreaTypeWhenReady() {
986        if (!this_.stationAreaTypeCombobox) {
987            return;
988        }
989
990        EventBus.off(EventBus.EventNames.STATION_AREA_TYPE_DATA_LOADED, onStationAreaTypeDataLoaded);
991        EventBus.off(EventBus.EventNames.STATION_AREA_TYPE_DATA_FAILED, onStationAreaTypeDataFailed);
992
993        if (EUConfigurationManager.isStationAreaTypeLoaded) {
994            this_.stationAreaTypeCombobox.enable(true);
995            this_.populateTypeOfStationAreaCombobox();
996            return;
997        }
998
999        if (EUConfigurationManager.isStationAreaTypeLoadFailed) {
1000            this_.stationAreaTypeCombobox.enable(false);
1001            return;
1002        }
1003
1004        this_.stationAreaTypeCombobox.enable(false);
1005        EventBus.on(EventBus.EventNames.STATION_AREA_TYPE_DATA_LOADED, onStationAreaTypeDataLoaded);
1006        EventBus.on(EventBus.EventNames.STATION_AREA_TYPE_DATA_FAILED, onStationAreaTypeDataFailed);
1007    }
1008
1009    /**
1010     * Handles the Station Type lookup-ready event.
1011     * Re-enables the dropdown, removes the one-time listeners,
1012     * and rebuilds the datasource from the current upstream filters.
1013     */
1014    function onStationTypeDataLoaded() {
1015        EventBus.off(EventBus.EventNames.STATION_TYPE_DATA_LOADED, onStationTypeDataLoaded);
1016        EventBus.off(EventBus.EventNames.STATION_TYPE_DATA_FAILED, onStationTypeDataFailed);
1017
1018        if (this_.stationTypeCombobox) {
1019            this_.stationTypeCombobox.enable(true);
1020            this_.populateTypeOfStationCombobox();
1021        }
1022    }
1023
1024    /**
1025     * Handles a Station Type lookup-load failure.
1026     * The dropdown stays disabled because the EU master list is unavailable.
1027     */
1028    function onStationTypeDataFailed() {
1029        EventBus.off(EventBus.EventNames.STATION_TYPE_DATA_LOADED, onStationTypeDataLoaded);
1030        EventBus.off(EventBus.EventNames.STATION_TYPE_DATA_FAILED, onStationTypeDataFailed);
1031
1032        if (this_.stationTypeCombobox) {
1033            this_.stationTypeCombobox.enable(false);
1034        }
1035    }
1036
1037    /**
1038     * Handles the Station Area lookup-ready event.
1039     * Re-enables the dropdown, removes the one-time listeners,
1040     * and rebuilds the datasource from the current active filters.
1041     */
1042    function onStationAreaTypeDataLoaded() {
1043        EventBus.off(EventBus.EventNames.STATION_AREA_TYPE_DATA_LOADED, onStationAreaTypeDataLoaded);
1044        EventBus.off(EventBus.EventNames.STATION_AREA_TYPE_DATA_FAILED, onStationAreaTypeDataFailed);
1045
1046        if (this_.stationAreaTypeCombobox) {
1047            this_.stationAreaTypeCombobox.enable(true);
1048            this_.populateTypeOfStationAreaCombobox();
1049        }
1050    }
1051
1052    /**
1053     * Handles a Station Area lookup-load failure.
1054     * Removes the temporary EventBus listeners for this load cycle and keeps
1055     * the TypeOfArea dropdown disabled because its master list is unavailable.
1056     */
1057    function onStationAreaTypeDataFailed() {
1058        EventBus.off(EventBus.EventNames.STATION_AREA_TYPE_DATA_LOADED, onStationAreaTypeDataLoaded);
1059        EventBus.off(EventBus.EventNames.STATION_AREA_TYPE_DATA_FAILED, onStationAreaTypeDataFailed);
1060
1061        if (this_.stationAreaTypeCombobox) {
1062            this_.stationAreaTypeCombobox.enable(false);
1063        }
1064    }
1065
1066    // Refresh all dependent dropdowns that rely on upstream filters (Target, Region, Owner).
1067    // TypeOfStation depends on the dropdowns above it, so refresh it before rebuilding stations.
1068    function refreshStationsByCurrentFilters() {
1069        this_.populateStationsAndMonitorsControl();
1070    }
1071
1072    // Keeps cascading filters in sync with currently available stations.
1073    function refreshDependentFilters() {
1074        if (EURegulations) {
1075            this_.populateTypeOfStationCombobox();
1076            this_.populateTypeOfStationAreaCombobox();
1077        }
1078
1079        refreshStationsByCurrentFilters();
1080    }
1081
1082    // Event Handlers
1083
1084    /**
1085     * Show result  button clicked handler
1086     */
1087    function actionBtnClicked() {
1088        if (validationChecks()) {
1089            const fromMainShowResultButton = true;
1090            actualThis.showResult(fromMainShowResultButton);
1091        }
1092    }
1093
1094    function onTargetChanged(target) {
1095        const targetBeforeChange = this_.targetCombobox._prevValues;
1096        applyStationFiltersAllExclusiveSelection(this_.targetCombobox, this_.targetCombobox.value(
1096), targetBeforeChange);
1097
1098        this_.populateRegionComboboxByTarget(target)
1099        onRegionChanged()
1100    }
1101
1102    function onRegionChanged() {
1103        const regionBeforeChange = this_.regionCombobox._prevValues;
1104        applyStationFiltersAllExclusiveSelection(this_.regionCombobox, this_.regionCombobox.value(), regionBeforeChange);
1105
1106        this_.populateOwnersComboBox()
1107        onOwnerChanged()
1108    }
1109
1110    function onOwnerChanged() {
1111        this_.ownerCombobox.trigger("change");
1112    }
1113
1114    /**
1115     * Handles Station Type selection changes.
1116     * Refreshes the dependent Station Area dropdown first and then rebuilds the station list.
1117     */
1118    function onStationTypeChanged() {
1119        if (!EURegulations || !this_.stationTypeCombobox) {
1120            return;
1121        }
1122
1123        applyStationFiltersAllExclusiveSelection(
1124            this_.stationTypeCombobox,
1125            this_.stationTypeCombobox.value(),
1126            this_.stationTypeCombobox._prevValues
1127        );
1128
1129        // TypeOfArea depends on the selected TypeOfStation, so refresh it before stations.
1130        this_.populateTypeOfStationAreaCombobox();
1131
1132        // TypeOfStation is a downstream filter: refresh dependent dropdowns and stations only,
1133        // and do not trigger owner flow that rebuilds upstream controls.
1134        this_.populateStationsAndMonitorsControl();
1135    }
1136
1137    /**
1138     * Handles Station Area selection changes.
1139     * This is a downstream filter, so only the station list is refreshed.
1140     */
1141    function onStationAreaTypeChanged() {
1142        if (!EURegulations || !this_.stationAreaTypeCombobox) {
1143            return;
1144        }
1145
1146        applyStationFiltersAllExclusiveSelection(
1147            this_.stationAreaTypeCombobox,
1148            this_.stationAreaTypeCombobox.value(),
1149            this_.stationAreaTypeCombobox._prevValues
1150        );
1151
1152        // StationArea is also a downstream filter: refresh stations only.
1153        this_.populateStationsAndMonitorsControl();
1154    }
1155
1156    function onFromDateChange() {
1157        if (this_.toDatePicker != null && m_CurReportPeriod != PERIOD_TYPE.CUSTOM) {
1158            var fromDatePicker = this_.getFromDate();
1159            var toDatePicker = getToDatePickerValue(fromDatePicker);
1160            actualThis.setNewFromDatePickerValue(new DateTime(fromDatePicker).dformat());
1161            actualThis.setNewToDatePickerValue(toDatePicker);
1162        }
1163    }
1164
1165    function onToDateChange() {
1166
1167    }
1168
1169    function onReportPeriodChanged(reportPeriod) {
1170
1171        this_.setDateTimePickersByReportPeriod(reportPeriod)
1172    }
1173
1174    this.onFromTimeBaseChanged = function (fromTimebase) {
1175        onFromTimeBaseChanged(fromTimebase)
1176    }
1177
1178    function onFromTimeBaseChanged(fromTimebase) {
1179        let relevantTimeBases = getToTimeBases(fromTimebase)
1180        relevantTimeBases = actualThis.filterAvailableToTimebases(relevantTimeBases, fromTimebase)
1181        let relevantTimeBasesDict = ReportUtils.getTimeBaseTextsByTimebases(relevantTimeBases)
1182        this_.toTimeBaseCombobox.setDataSource(relevantTimeBasesDict)
1183        this_.toTimeBaseCombobox.value(Object.keys(relevantTimeBasesDict)[0])
1184        setTimePickersByTimebase(fromTimebase)
1185    }
1186
1187    function onToTimeBaseChanged(newToTimebase) {
1188        setTimePickersByTimebase(newToTimebase)
1189    }
1190
1191    function onTypeChanged(reportType) {
1192        if (ReportUtils.isMinOrMaxReport(reportType)) {
1193            $("#pr-input-checkbox").data("kendoCheckBox").check(false);
1194            $("#pr-input-checkbox").data("kendoCheckBox").enable(false);
1195        }
1196        else {
1197            $("#pr-input-checkbox").data("kendoCheckBox").enable(true);
1198        }
1199        setFromTimePickerValue()
1200    }
1201
1202    function onProvinceChanged(provinceType) {
1203        actualThis.onProvinceChanged(provinceType);
1204    }
1205
1206    function onDistrictChanged(districtType) {
1207        actualThis.onDistrictChanged(districtType);
1208    }
1209
1210    // Setters
1211
1212    this.setReportsValidator = function (reportValidatorNew = null) {
1213        this_.reportsValidator = JsUtils.isNotEmpty(reportValidatorNew) ? reportValidatorNew : reportsValidator;
1214    }
1215
1216    this.setDateTimePickersByReportPeriod = function (reportPeriod) {
1217        var from = getFromDatePickerValue();
1218        var to = getToDatePickerValue(from);
1219        this_.setNewFromDatePickerValue(from);
1220        this_.setNewToDatePickerValue(to);
1221
1222        if (this_.fromTimePicker != null && this_.toTimePicker != null) {
1223            if (reportPeriod == PERIOD_TYPE.CUSTOM) {
1224                this_.fromTimePicker.enable(true)
1225                this_.toTimePicker.enable(true)
1226            }
1227            else {
1228                this_.fromTimePicker.enable(false);
1229                this_.toTimePicker.enable(false);
1230
1231                if (JsUtils.isNotEmpty(this_.fromTimeBaseCombobox) || JsUtils.isNotEmpty(this_.selectTimeBase)) {
1232                    let timebase = this_.fromTimeBaseCombobox != null ? this_.fromTimeBaseCombobox.value(
1232) : this_.selectTimeBase.getValue();
1233                    setToTimePickerByTimebase(timebase);
1234                    setFromTimePickerValue();
1235                }
1236
1237                if (actualThis.getReportName() == "index report") {
1238                    let timebase = IndexUtils.getIndexTimeStep();
1239                    if (JsUtils.isNotEmpty(timebase)) {
1240                        setToTimePickerByTimebase(timebase);
1241                        setFromTimePickerValue();
1242                    }
1243                }
1244            }
1245        }
1246    }
1247
1248    function setTimePickersByTimebase(currentTimebase) {
1249        setToTimePickerByTimebase(currentTimebase)
1250        setFromTimePickerValue()
1251    }
1252
1253    function setToTimePickerByTimebase(timebase) {
1254        if (JsUtils.isNotEmpty(timebase)) {
1255            // Workaround for Kendo bug: setOption re-enables the time picker element
1256            let enable = JsUtils.isEmpty(this_.toTimePicker.element.attr('disabled'));
1257            this_.toTimePicker.setOptions({ interval: timebase });
1258            this_.toTimePicker.enable(enable);
1259
1260            let endTime = new DateTime(ReportUtils.getToTime(timebase));
1261
1262            if (isTimeEnding) {
1263                var newDateMin = new DateTime(endTime).addMinutes(timebase);
1264                this_.toTimePicker.min(new Date(newDateMin.getYear(), newDateMin.getMonth() - 1, newDateMin.getDay(), newDateMin.getHour(), newDateMin.getMinutes(), 0));
1265
1266                setLastTimeListElementByTimeEnding();
1267            }
1268            else {
1269                this_.toTimePicker.value(new DateTime(endTime).tformat());
1270            }
1271        }
1272    }
1273
1274    function setLastTimeListElementByTimeEnding() {
1275        var timeList = this_.toTimePicker.timeView.list;
1276        var lastItem = timeList.find("li:last");
1277        if (lastItem.text() != '00:00') {
1278            lastItem = timeList.find("li:last")[0].cloneNode(true);
1279            timeList.find("ul").append(lastItem);
1280        }
1281        $(lastItem).text("24:00");
1282
1283        this_.toTimePicker.value('00:00'); //this is only the value, not the text, otherwise this_.toTimePicker.value() is null!!
1284        this_.toTimePicker.element.val('24:00');
1285
1286        timeList.find("li").removeClass("k-selected");
1287        timeList.find("li:last").addClass("k-selected");
1288        this_.toTimePicker.trigger("change");
1289    }
1290
1291    this.setFromTimeBaseComboboxDs = function (ds) {
1292        if (this_.fromTimeBaseCombobox != null) {
1293            this_.fromTimeBaseCombobox.setDataSource(ds)
1294        }
1295    }
1296
1297    this.setToTimeBaseComboboxDs = function (ds) {
1298        if (this_.toTimeBaseCombobox != null) {
1299            this_.toTimeBaseCombobox.setDataSource(ds)
1300        }
1301    }
1302
1303    function setFromTimePickerValue() {
1304        let timebase;
1305        let curReportType = JsUtils.isNotEmpty(this_.typeCombobox) ? this_.typeCombobox.value() : null;
1306
1307        if (actualThis.getReportName() == "index report") {
1308            timebase = IndexUtils.getIndexTimeStep();
1309        } else if (JsUtils.isEmpty(curReportType)) {// for EditBlock timebase value
1310            timebase = this_.selectTimeBase.getValue();
1311        } else if (ReportUtils.isRunningReport(curReportType)) {
1312            timebase = this_.fromTimeBaseCombobox.value()
1313        } else {
1314            timebase = this_.toTimeBaseCombobox.value()
1315        }
1316
1317        let startTime = ReportUtils.getFromTime(timebase);
1318
1319        if (JsUtils.isNotEmpty(timebase)) {
1320            // Workaround for Kendo bug: setOption re-enables the time picker element
1321            let enable = JsUtils.isEmpty(this_.fromTimePicker.element.attr('disabled'));
1322            this_.fromTimePicker.setOptions({ interval: timebase });
1323            this_.fromTimePicker.enable(enable);
1324        }
1325
1326        if (isTimeEnding) {
1327            this_.fromTimePicker.min(new Date(startTime.year(), startTime.month() + 1, startTime.date(), startTime.hour(), startTime.minute(), 0));
1328            this_.fromTimePicker.value(new DateTime(startTime).tformat());
1329            var timeList = this_.fromTimePicker.timeView.list;
1330            var lastItemClone = timeList.find("li:last");
1331            if (lastItemClone.text() != '00:00') {
1332                lastItemClone = timeList.find("li:last")[0].cloneNode(true);
1333                this_.fromTimePicker.timeView.list.find("ul").append(lastItemClone);
1334            }
1335            $(lastItemClone).text("24:00");
1336        } else {
1337            this_.fromTimePicker.value("00:00");
1338        }
1339    }
1340
1341    function getFromDatePickerValue() {
1342        if (m_CurReportPeriod != PERIOD_TYPE.CUSTOM) {
1343            return ReportUtils.getFromDateByPeriod(m_CurReportPeriod);
1344        }
1345    }
1346
1347    function getToDatePickerValue(from) {
1348        if (this_.toDatePicker != null) {
1349            if (m_CurReportPeriod != PERIOD_TYPE.CUSTOM) {
1350                this_.toDatePicker.enable(false);
1351                let to = ReportUtils.getToDatePickerValue(m_CurReportPeriod, from);
1352                return to;
1353            }
1354            else {
1355                this_.toDatePicker.enable(true);
1356            }
1357        }
1358    }
1359
1360    /**
1361     * set new From date
1362     * @param {String} from
1363     */
1364    this.setNewFromDatePickerValue = function (from) {
1365        if (JsUtils.isNotEmpty(this_.fromDatePicker) && JsUtils.isNotEmpty(this_.fromDatePicker.value)) {
1366            this_.fromDatePicker.value(from);
1367        }
1368    }
1369
1370    /**
1371    * set new To date
1372    * @param {String} to
1373    */
1374    this.setNewToDatePickerValue = function (to) {
1375        if (JsUtils.isNotEmpty(this_.toDatePicker) && JsUtils.isNotEmpty(this_.toDatePicker.value)) {
1376            this_.toDatePicker.value(to);
1377        }
1378    }
1379
1380    this.setFromTimeBase = function (val) {
1381        this_.fromTimeBaseCombobox.value(val)
1382    }
1383    this.setToTimeBase = function (val) {
1384        this_.toTimeBaseCombobox.value(val)
1385    }
1386
1387    this.updateFromTimebaseCombobox = function (timebases, preferredTimebase) {
1388        let relevantTimebases = ArrayUtils.getArraysIntersection(this_.allDefinedTimeBases, timebases)
1389        let timeBaseTextsByTimebases = ReportUtils.getTimeBaseTextsByTimebases(relevantTimebases)
1390
1391        this_.fromTimeBaseCombobox.setDataSource(timeBaseTextsByTimebases)
1392
1393        let curValue = Object.keys(timeBaseTextsByTimebases).map(Number).includes(preferredTimebase)
1394            ? preferredTimebase
1395            : Object.keys(timeBaseTextsByTimebases)[0];
1396
1397        this_.fromTimeBaseCombobox.value(curValue)
1398        this_.onFromTimeBaseChanged(curValue)
1399    }
1400
1401    // Getters
1402
1403    this.getControls = () => {
1404        return controls;
1405    }
1406
1407    this.getAuthControls = () => {
1408        return authControls;
1409    }
1410
1411    this.getFromDateTime = function () {
1412        let fromDateTime = dateTimeUtils.toDateTime(actualThis.getFromDate(), actualThis.getFromTime());
1413        fromDateTime = validateAndShiftDate(fromDateTime);
1414        return fromDateTime.dtformat();
1415    }
1416
1417    this.getToDateTime = function () {
1418        let toDateTime = dateTimeUtils.toDateTime(actualThis.getToDate(), actualThis.getToTime());
1419        toDateTime = validateAndShiftDate(toDateTime);
1420
1421        return toDateTime.dtformat();
1422    }
1423
1424    function validateAndShiftDate(_date) {
1425        if (isTimeEnding && _date.isTimeAtMidnight()) {
1426            _date = _date.addDay();
1427        }
1428        return _date;
1429    }
1430
1431    this.getPeriod = function () {
1432        return m_CurReportPeriod
1433    }
1434
1435    this.getReportType = function () {
1436        if (this_.typeCombobox != null) {
1437            return this_.typeCombobox.value()
1438        }
1439
1440    }
1441
1442    //Set the default values(target, region and owner) to "All" - in the reports that opens through the station dashboard
1443    //while the chosen station not includes in the data source of the configuration
1444    this.setToAll = function () {
1445        this_.targetCombobox.value("-1");
1446        this_.regionCombobox.value("-1");
1447        this_.ownerCombobox.value("-1");
1448    }
1449
1450    /**
1451     * Returns the selected owner raw names from the datasource.
1452     * Use datasource items instead of the visible text so translated labels
1453     * never leak into filtering comparisons.
1454     */
1455    this.getOwner = function () {
1456
1457        if (this_.ownerCombobox != null) {
1458            return this_.ownerCombobox.dataItems().map(item => item.name);
1459        }
1460        else {
1461            console.log("bug")
1462        }
1463    }
1464
1465    this.getRegion = function () {
1466
1467        if (this_.regionCombobox != null) {
1468            return this_.regionCombobox.value()
1469        }
1470        else {
1471            console.log("bug")
1472        }
1473    }
1474
1475    /**
1476     * Returns the selected target raw names from the datasource.
1477     * The widget shows `displayName`, but the reports layer must keep using
1478     * the original `name` values for stable filtering.
1479     */
1480    this.getTarget = function () {
1481        if (this_.targetCombobox != null) {
1482            return this_.targetCombobox.dataItems().map(item => item.name);
1483        }
1484        else {
1485            console.log("bug")
1486        }
1487    }
1488
1489    /**
1490     * Returns the selected Station Type ids as an array.
1491     * Returns [-1] when EU regulations are disabled or when the "All" option is selected.
1492     * @returns {number[]|null}
1493     */
1494    this.getStationType = function () {
1495        if (!EURegulations) {
1496            return [EUConfigurationManager.getAllEUConfigId()];
1497        }
1498
1499        if (!this_.stationTypeCombobox) {
1500            console.log("bug");
1501            return null;
1502        }
1503
1504        const selectedValues = this_.stationTypeCombobox.value() || [];
1505        const allId = EUConfigurationManager.getAllEUConfigId();
1506
1507        if (!selectedValues.length || selectedValues.includes(allId.toString()) || selectedValues.includes(allId)) {
1508            return [allId];
1509        }
1510
1511        const selectedIds = selectedValues
1512            .map(value => Number(value))
1513            .filter(value => !Number.isNaN(value));
1514
1515        if (!selectedIds.length) {
1516            return [allId];
1517        }
1518
1519        return selectedIds;
1520    }
1521
1522    /**
1523     * Returns the selected Station Area ids as an array.
1524     * Returns [-1] when EU regulations are disabled or when the "All" option is selected.
1525     * @returns {number[]|null}
1526     */
1527    this.getStationAreaType = function () {
1528        if (!EURegulations) {
1529            return [EUConfigurationManager.getAllEUConfigId()];
1530        }
1531
1532        if (!this_.stationAreaTypeCombobox) {
1533            console.log("bug")
1534            return null;
1535        }
1536        const selectedValues = this_.stationAreaTypeCombobox.value() || [];
1537        const allId = EUConfigurationManager.getAllEUConfigId();
1538
1539        if (!selectedValues.length || selectedValues.includes(allId.toString()) || selectedValues.includes(allId)) {
1540            return [allId];
1541        }
1542
1543        const selectedIds = selectedValues
1544            .map(value => Number(value))
1545            .filter(value => !Number.isNaN(value));
1546
1547        if (!selectedIds.length) {
1548            return [allId];
1549        }
1550
1551        return selectedIds;
1552    }
1553
1554    this.getToTimeBase = function () {
1555
1556        if (this_.toTimeBaseCombobox != null) {
1557            return this_.toTimeBaseCombobox.value()
1558        }
1559        else {
1560            console.log("bug")
1561        }
1562
1563    }
1564    /**
1565     *Gets tome base from combo box
1566     * Used by single station initialization
1567     *
1568     */
1569    this.getFromTimeBase = function () {
1570
1571        if (this_.fromTimeBaseCombobox != null) {
1572            return this_.fromTimeBaseCombobox.value()
1573        }
1574        else {
1575            console.log("Cannot get time base from combo box")
1576        }
1577    }
1578
1579    this.getToTimeBaseAsText = function () {
1580
1581        let toTb = actualThis.getToTimeBase()
1582        return ReportUtils.getTimeBaseTextByTimebase(toTb)
1583    }
1584
1585    this.getFromDate = function () {
1586        if (this_.fromDatePicker != null) {
1587            return new DateTime(this_.fromDatePicker.value());
1588        }
1589        else {
1590            console.log("reportManager_bug");
1591        }
1592    }
1593
1594    this.getToDate = function () {
1595        if (this_.toDatePicker != null) {
1596            return this_.toDatePicker.value()
1597        }
1598        else {
1599            console.log("reportManager_bug")
1600        }
1601    }
1602
1603    this.getToTime = function () {
1604        if (this_.toTimePicker != null) {
1605            let time = this_.toTimePicker.value();
1606            return time;
1607        }
1608        else {
1609            console.log("reportManager_bug")
1610        }
1611    }
1612
1613    this.getFromTime = function () {
1614        if (this_.fromTimePicker != null) {
1615            return this_.fromTimePicker.value()
1616        }
1617        else {
1618            console.log("reportManager_bug")
1619        }
1620    }
1621
1622    //Helpers
1623    this.validationChecks = validationChecks;
1624    /**
1625     * Reports validation checks
1626     */
1627    function validationChecks() {
1628        if (!this_.reportsValidator.isValid(actualThis)) {
1629            const reportContext = REPORT_CONTEXT.REPORT_FORM;
1630            // Get the error message from reportsValidator
1631            let errMsg = SiteUtils.translate(this_.reportsValidator.getErrMsg());
1632
1633            this_.showErrorMessageHandler(reportContext, errMsg);
1634
1635            return false;
1636        }
1637        else if (!authValidator.isValid(actualThis)) {
1638            const reportContext = REPORT_CONTEXT.REPORT_FORM;
1639            // Get the error message from authValidator
1640            let errMsg = SiteUtils.translate(authValidator.getErrMsg());
1641
1642            this_.showErrorMessageHandler(reportContext, errMsg);
1643
1644            return false;
1645        }
1646
1647        return true;
1648    }
1649
1650    this.isInPopup = function () {
1651        return this_.inPopup;
1652    }
1653
1654    function getToTimeBases(fromTimebase) {
1655        return this_.allDefinedTimeBases.filter(function (tb) { return parseInt(fromTimebase) <= tb })
1656    }
1657
1658    this.notifyNoReportData = function (pageContainer) {
1659        PopupManager.closeWindowByPageContainer(pageContainer)
1660        this_.showErrorMessage(ERROR_MESSAGES.No_Report_Data)
1661
1662    }
1663
1664    this.notifyModelNotValid = function () {
1665        this_.modelValid = false;
1666        $("#showResultsBtn").addClass('disabled');
1667    }
1668
1669    this.notifyModelValid = function () {
1670        this_.modelValid = true;
1671        $("#showResultsBtn").removeClass('disabled');
1672    }
1673
1674    this.showErrorMessageHandler = function (reportContext, errorMessage) {
1675        AjaxLoader.hide();
1676        WindowManager.closeWindowById("report_result");
1677        if (reportContext == REPORT_CONTEXT.REPORT_RESULT) {
1678            this_.showErrorMessageInPopup(errorMessage)
1679        }
1680        else if (reportContext == REPORT_CONTEXT.REPORT_FORM) {
1681            this_.showErrorMessage(errorMessage)
1682        }
1683    }
1684
1685    this.showErrorMessage = function (msg, persistent) {
1686
1687        $("#errorInputsMessage").css("visibility", "visible")
1688
1689        $("#errorInputsMessage").text(SiteUtils.translate(msg))
1690
1691        if (!persistent) {
1692            if (this.lastTimeout != null) {
1693                clearTimeout(this.lastTimeout)
1694            }
1695
1696            this.lastTimeout = setTimeout(this_.hideErrorMessage, ERROR_MESSAGE_SHOW_MILISEC)
1697        }
1698    }
1699
1700    this.hideErrorMessage = function () {
1701        $("#errorInputsMessage").css("visibility", "hidden")
1702
1703    }
1704
1705    this.showErrorMessageInPopup = function (msg) {
1706        PopupManager.ErrorAlert(msg)
1707    }
1708    /**
1709     * getReportConfig
1710     * This is the base for getting configuration from user
1711     *
1712     */
1713    this.getReportConfig = function () {
1714        let config = {
1715            reportName: actualThis.getReportName(),
1716            reportType: actualThis.getReportType(),
1717            actualReportType: actualThis.getReportType(),
1718            startDate: this_.getFromDateTime(),
1719            endDate: this_.getToDateTime(),
1720            startDateAbsolute: this_.getFromDateTime(),
1721            endDateAbsolute: this_.getToDateTime(),
1722        }
1723
1724        if (actualThis.reportName == REPORTS.editBlock) {
1725            config.fromTb = this_.selectTimeBase.getValue();
1726            config.toTb = this_.selectTimeBase.getValue();
1727        } else {
1728            config.fromTb = parseInt(actualThis.getFromTimeBase());
1729            config.toTb = parseInt(actualThis.getToTimeBase());
1730        }
1731
1732        return config;
1733    }
1734
1735    this.disableTimebasesControls = function () {
1736
1737        if (this_.fromTimeBaseCombobox != null && this_.toTimeBaseCombobox != null) {
1738            this_.fromTimeBaseCombobox.enable(false)
1739            this_.toTimeBaseCombobox.enable(false)
1740        }
1741    }
1742
1743    this.enableTimebasesControls = function () {
1744
1745        if (this_.fromTimeBaseCombobox != null && this_.toTimeBaseCombobox != null) {
1746            this_.fromTimeBaseCombobox.enable(true)
1747            this_.toTimeBaseCombobox.enable(true)
1748        }
1749    }
1750
1751    /**
1752     * Filter the NotForDisplay configuration from the data source
1753     * @param {any} dataSource
1754     * @param {any} config
1755     */
1756    function filterNotForDisplay(dataSource, config) {
1757        if (JsUtils.isEmpty(configManager.read(config))) {
1758            return dataSource;
1759        }
1760
1761        let notForDisplayConfig = configManager.read(config).split(',').map(id => {
1762            return isNaN(id) ? id : parseInt(id);
1763        });
1764        if (JsUtils.isNotEmpty(notForDisplayConfig)) {
1765            if (JsUtils.isArray(dataSource)) {
1766                dataSource = dataSource.filter(data => !notForDisplayConfig.includes(data.id));
1767            } else {
1768                let filteredData = Object.entries(dataSource).filter(data => !notForDisplayConfig.includes(data[0]));
1769
1770                dataSource = Object.fromEntries(filteredData);
1771            }
1772        }
1773
1774        return dataSource;
1775    }
1776
1777    /**
1778     * Sets the default value for the given combobox by the given config.
1779     * If the config is empty, the default value will be the first element from the given data source.
1780     * @param {any} combobox
1781     * @param {any} config
1782     * @param {any} dataSource
1783     */
1784    function setDefaultValueByConfig(combobox, config, dataSource) {
1785        let defaultValue = configManager.read(config);
1786        var dsNotEmpty = JsUtils.isNotEmpty(dataSource);
1787        let value;
1788        if (JsUtils.isNotEmpty(defaultValue) && dsNotEmpty && this_.firstTime) {
1789            if (config == webConfigs.DefaultType) {
1790                value = JsUtils.isNotEmpty(Object.keys(dataSource).includes(defaultValue)) ? defaultValue : Object.keys(dataSource)[0];
1791            } else {
1792                value = JsUtils.isNotEmpty(dataSource.find(ds => ds.id == defaultValue)) ? defaultValue : dataSource[0].id;
1793            }
1794        } else if (dsNotEmpty) {
1795            value = config == webConfigs.DefaultType ? Object.keys(dataSource)[0] : dataSource[0].id;
1796
1797        }
1798        combobox.value(value);
1799    }
1800
1801    /**
1802     * Clones datasource items with a translated `displayName` field for UI rendering.
1803     * This prevents accidental mutation of `name`, which is the raw logical value
1804     * used later by report filters and data-manager lookups.
1805     */
1806    function buildDisplayItems(items) {
1807        return items.map(item => ({
1808            ...item,
1809            displayName: SiteUtils.translate(item.name)
1810        }));
1811    }
1812
1813    //model
1814    this.setReportView = function (reportView) {
1815        model.reportView = reportView;
1816
1817        if (JsUtils.isNotEmpty(this_.reportPresentationBtnGroup)) {
1818            //updateReportViewUI
1819            this_.reportPresentationBtnGroup.select(actualThis.INITIAL_REPORT_PRESENTATION_CONFIG.indexOf(reportView));
1820        }
1821    }
1822
1823    this.getReportView = function (reportView) {
1824        return model.reportView;
1825    }
1826
1827
1828    this.setLatestReportView = function (latestReportView) {
1829        model.latestReportView = latestReportView;
1830    }
1831
1832    this.getLatestReportView = function () {
1833        return model.latestReportView;
1834    }
1835
1836    /**
1837    * compare between current and latest view type.
1838    * if the types are different the result popup will close so that new one can open and update latestReportType.
1839    * @param {number} compareTo - current view type
1840    */
1841    this.compareLatestAndCurrentViewType = function (compareTo, id = "report_result") {
1842        if (this_.getLatestReportView() != compareTo) {
1843            WindowManager.closeWindowById(id);
1844        }
1845        this_.setLatestReportView(compareTo);
1846    }
1847
1848    //filter by pollutant - irregularity and multistation
1849    function createMonitorItem(itemContainer, monitor, callBackChangePollutant) {
1850        let item = $("<div class=monitorItemBox>")
1851            .append(`<input class="monitorCheckboxItem" id="checkbox_${monitor.pollutantId}" />`)
1852
1853        $(itemContainer).append(item)
1854
1855        $(`#checkbox_${monitor.pollutantId}`).kendoCheckBox({
1856            label: monitor.text,
1857            change: function (e) {
1858                if (e.checked == false) {
1859                    var isAdd = false;
1860                    callBackChangePollutant(e.sender.element[0], isAdd);
1861                }
1862                else {
1863                    callBackChangePollutant(e.sender.element[0]);
1864                }
1865            }
1866        });
1867    }
1868
1869    this.onFilterMonitors = function (value) {
1870        var data = $(".monitorItemBox").find("label");
1871        for (let i = 0; i < data.length; i++) {
1872            if (data[i].innerText.toLowerCase().includes(value)) {
1873                $($(".monitorItemBox")[i]).show();
1874            } else {
1875                $($(".monitorItemBox")[i]).hide();
1876            }
1877        }
1878    }
1879    this.onFilterStations = function (value) {
1880        var data = $(".stationItemBox").find("label");
1881        for (let i = 0; i < data.length; i++) {
1882            if (data[i].innerText.toLowerCase().includes(value)) {
1883                $($(".stationItemBox")[i]).show();
1884            } else {
1885                $($(".stationItemBox")[i]).hide();
1886            }
1887        }
1888    }
1889
1890
1891    this.changeCheckboxStation = function (isAdd, data, monitorDic) {
1892        monitorDic.forEach(function (monitor) {
1893            var station = $.grep(data, function (d) {
1894                return d.stationId == monitor.stationId;
1895            });
1896            var pollutant = $.grep(station[0].items, function (d) {
1897                return d.pollutantId == monitor.pollutantId;
1898            });
1899            pollutant[0].set('checked', isAdd);
1900        })
1901    }
1902
1903    this.onFilterCheckboxChangeReport = function (checkboxIsChecked, pollutantContainer, stationContainer, m_MonitorsDictionary, changeCheckboxStation) {
1904        if (checkboxIsChecked) {
1905            if ($(pollutantContainer).children().length == 0) {
1906                this.createPollutantControl(pollutantContainer, m_MonitorsDictionary, changeCheckboxStation);
1907            }
1908            $(pollutantContainer).show();
1909            $(stationContainer).hide();
1910        }
1911        else {
1912            $(pollutantContainer).hide();
1913            $(stationContainer).show();
1914        }
1915    }
1916
1917    this.createPollutantControl = function (pollutantContainer, m_MonitorsDictionary, changeCheckboxStation) {
1918        for (var key in m_MonitorsDictionary) {
1919            createMonitorItem(pollutantContainer, m_MonitorsDictionary[key][0], changeCheckboxStation);
1920        }
1921    }
1922
1923    this.filterByStationName = async function (dataSource, query) {
1924        return filterByStatioChildrennName(dataSource, query);
1925    }
1926    async function itemHide(item, query, hasVisibleChildren) {
1927
1928        var text = item.text.toLowerCase();
1929        var itemVisible =
1930            query === true // parent already matches
1931            || query === "" // query is empty
1932            || text.indexOf(query) >= 0; // item text matches query
1933
1934        var anyVisibleChildren = filterByStatioChildrennName(item.children, itemVisible || query); // pass true if parent matches
1935
1936        hasVisibleChildren = hasVisibleChildren || anyVisibleChildren || itemVisible;
1937
1938        item.hidden = !itemVisible && !anyVisibleChildren;
1939    }
1940
1941
1942    function filterByStatioChildrennName(dataSource, query) {
1943
1944        var hasVisibleChildren = false;
1945        var data = dataSource instanceof kendo.data.HierarchicalDataSource && dataSource.data();
1946
1947        for (var i = 0; i < data.length; i++) {
1948
1949            var item = data[i];
1950            itemHide(item, query, hasVisibleChildren)
1951        }
1952
1953        if (data) {
1954            // Re-apply the filter on the children.
1955            dataSource.filter({ field: "hidden", operator: "neq", value: true });
1956        }
1957
1958        return hasVisibleChildren;
1959    }
1960
1961    /**
1962     * setDsData
1963     * @param {any} data
1964     * @param {any} callback
1965     * The purpose of this conditions are to insure there is only one api call per config
1966     * Check if the configure of the request is different
1967     * If the configures are equal return complete data
1968     * Else make api call
1969     * Gets api data for Table
1970     */
1971    this.getRawDataFromApi = (url, requestType, callback_config, stationId, resolvedCallback) => {
1972        let promiseList = [];
1973        const validationPromise = prValidationManager.getApiValidationData(callback_config, stationId);
1974        const callbackPromise = createRawDataRquest(url, requestType, callback_config, stationId);
1975
1976        promiseList.push(validationPromise);
1977        promiseList.push(callbackPromise);
1978        Promise.all(promiseList).then((data) => {
1979            //if we will need at the feature
1980            resolvedCallback(data);
1981        });
1982
1983    }
1984
1985    function createRawDataRquest(url, requestType, callback_config, stationId) {
1986        let apiData = JsUtils.isNotEmpty(callback_config.m_ReportConfig.apiData) ? callback_config.m_ReportConfig.apiData : null;
1987
1988        let errorCallback = JsUtils.isNotEmpty(callback_config.m_ReportConfig.apiData) ? onRawDataFailed : null;
1989
1990        return new Promise((resolve) => {
1991            API.createRequest(url, requestType, (response) => {
1992                onRawDataSuccess(response, resolve, callback_config, stationId);
1993            }, true, apiData, errorCallback);
1994        });
1995    }
1996
1997    function onRawDataSuccess(res, resolve, callback_config, stationId) {
1998        let completeData = this_.kendoElemetCallback(res, callback_config, stationId);
1999        resolve(completeData);
2000    }
2001
2002    function onRawDataFailed(res) {
2003        let pageContainer = "detailedReportGrid";
2004        PopupManager.closeWindowByPageContainer(pageContainer);
2005        let errorMsg = JsUtils.isEmpty(res) ? ERROR_MESSAGES.Unexpected_Error : ERROR_MESSAGES.Higher_Privileges_Permissions;
2006        this_.showErrorMessage(errorMsg);
2007        AjaxLoader.hide();
2008    }
2009
2010    /**
2011     * getApiData
2012     * @param {any} url
2013     * @param {any} callback_config
2014     * @param {any} report_config
2015     * @param {any} resolve
2016     * @param {any} complete_response
2017     * Gets api data for graph and excel and summary
2018     */
2019    this.getStationApiData = (url, config, resolvedCallback) => {
2020        //check what happens in chart
2021        let promiseList = [];
2022        let stationId = config["stationId"]
2023        const validationPromise = prValidationManager.getApiValidationData(config, stationId);
2024        const callbackPromise = new Promise((resolve) => {
2025            API.createRequest(url, TYPE.GET, (response) => {
2026                let completeData = this.kendoElemetCallback(response, config, stationId);
2027                resolve(completeData);
2028            }, true)
2029        })
2030        promiseList.push(validationPromise);
2031        promiseList.push(callbackPromise);
2032        Promise.all(promiseList).then((data) => {
2033
2034            resolvedCallback(data);
2035
2036        })
2037
2038    }
2039    /**
2040     * kendoElemetCallback
2041     * @param {Object} response
2042     * @param {Object} config
2043     * @param {String} stationId
2044     * Callback for report api call
2045     * checks if response is undefined(status 204)-if so display error message
2046     * if there is a response- check what kind of report view(table/graph/excel)
2047     * 👉🏽 in report view of table- make the data processing right away
2048     * 👉🏽 other report views- save and return the response
2049     */
2050    this.kendoElemetCallback = (response, config, stationId) => {
2051        if (config.m_ReportConfig.reportName == REPORTS.index) {
2052            response = IndexUtils.filterResponseDataByTimeStep(response);
2053        }
2054        response = defaulatSummaryHandler(response, config, stationId);
2055        if (JsUtils.isEmpty(response)) {
2056            return null;
2057        }
2058        let rawData = response
2059        rawData['stationId'] = stationId;
2060        elemetBuilder = new kendoElementDataBuilder(rawData, config)
2061        switch (config.kendoElementType) {
2062            case REPORT_VIEW.TABLE:
2063                return handleTableDataResponse(config, rawData, elemetBuilder, stationId)
2064            case REPORT_VIEW.HORIZONTAL_TABLE:
2065                reportApiManager.apiDataManager.setRawData(rawData, stationId)
2066                return rawData;
2067            case REPORT_VIEW.GRAPH:
2068                return rawData;
2069            case REPORT_VIEW.EXCEL:
2070                reportApiManager.apiDataManager.setRawData(rawData, stationId)
2071                return rawData;
2072            case REPORT_VIEW.SUMMARY:
2073                reportApiManager.apiDataManager.setRawSummaryData(rawData.summary, stationId)
2074                elemetBuilder.buildFormattedData(REPORT_VIEW.SUMMARY);
2075                return elemetBuilder.formmated_summary_data
2076            case REPORT_VIEW.HORIZONTAL_TABLE_SUMMARY:
2077                reportApiManager.apiDataManager.setRawSummaryData(rawData.summary, stationId);
2078                return rawData.summary;
2079            default:
2080                return rawData
2081        }
2082    }
2083
2084    /**
2085     * handleTableDataResponse
2086     * @param {any} config
2087     * @param {any} rawData
2088     * @param {any} elemetBuilder
2089     * @param {any} stationId
2090     */
2091    function handleTableDataResponse(config, rawData, elemetBuilder, stationId) {
2092        if (config.m_ReportManager.reportName == REPORTS.event) {
2093            reportApiManager.apiDataManager.setNumberOfRecords(rawData.data)
2094        }
2095        reportApiManager.apiDataManager.setRawData(rawData, stationId)
2096        if (JsUtils.isEmpty(rawData)) {
2097            return null;
2098        }
2099        elemetBuilder.buildFormattedData(REPORT_VIEW.TABLE);
2100        return elemetBuilder.formatted_data
2101    }
2102
2103
2104
2105    /**
2106     * checkDataAndNotifyIfEmpty
2107     * @param {any} formattedData
2108     */
2109    this.checkDataAndNotifyIfEmpty = (formattedData) => {
2110        elemetBuilder.checkDataAndNotifyIfEmpty(formattedData)
2111    }
2112    /**
2113     * defaulatSummaryHandler
2114     * @param {any} response
2115     * @param {any} stationId
2116     */
2117    function defaulatSummaryHandler(response, config, stationId) {
2118        let res = [];
2119        if (config.kendoElementType == REPORT_VIEW.SUMMARY && JsUtils.isEmpty(response)) {
2120            let monitors = config.m_ReportConfig.monitorChannelsByStationId[stationId];
2121
2122            for (let monitor of monitors) {
2123                //in the exceedance report there is a summary column for each pair of monitor and
2124                //exceedance value so we must create a default item per exceedance.
2125                if (config.m_ReportConfig.reportName === REPORTS.exceedance) {
2126                    config.m_ReportConfig.exceedances.forEach(exceedance => {
2127                        let defaultItem = createNewObject(monitor, true);
2128                        defaultItem.stationId = stationId;//for excel
2129                        defaultItem.exceedanceValue = exceedance;
2130                        res.push(defaultItem);
2131                    })
2132                    continue;
2133                }
2134                let defaultItem = createNewObject(monitor)
2135                defaultItem.stationId = stationId;//for excel
2136                res.push(defaultItem)
2137            }
2138            response = {
2139                data: [],
2140                summary: res,
2141            }
2142        }
2143        else if (config.kendoElementType == REPORT_VIEW.TABLE && JsUtils.isEmpty(response)) {
2144            return [];
2145        }
2146        return response;
2147    }
2148    /**
2149     * createNewObject
2150     * @param {any} monitorId
2151     * @param {boolean} isExceedance indicate whether the summary is for an exceedance report
2152     */
2153    function createNewObject(monitorId, isExceedance = false) {
2154        let newObject = Object.assign({}, isExceedance ? DEFAULT_EXCEEDANCE_SUMMARY_OBJECT : DEFAULT_SUMMARY_OBJECT)
2155        newObject.ChannelId = monitorId;
2156        return newObject
2157    }
2158
2159    /*
2160    hideMobileStation
2161    * hides the mobile-Station container and reveal period and presentation
2162    */
2163    this.hideMobileStation = () => {
2164        $('#reportPresentationBox').show();
2165        $('#reportPeriodBox').show();
2166        $('#reportSelectMobileStaionsContainer').hide();
2167    }
2168
2169    /*
2170     handleMobileStationSelection
2171     *@ param {string} mobileStationId
2172     * hides the mobile-Station container and reveal period and presentation
2173     */
2174    this.handleMobileStationSelection = (mobileStationId) => {
2175        $("#reportSelectMobileStaionsContainer").remove()
2176        $.ajax({
2177            type: "POST",
2178            url: baseUrl + "report/SelectMobileStation",
2179            contentType: "application/json; charset=utf-8",
2180            data: JSON.stringify({ mobileStationId: mobileStationId }),
2181            success: function (partialView) {
2182                $("#reportPresentationAndPeriodSection").append(partialView)
2183            },
2184            error: function (err) {
2185                console.log(err)
2186            },
2187        });
2188    }
2189}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.