raain-app 1.6.21 → 1.6.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -1
- package/cumulative-selector/cumulative-selector.component.d.ts +45 -0
- package/esm2020/cumulative-selector/cumulative-selector.component.mjs +199 -0
- package/esm2020/index.mjs +2 -1
- package/esm2020/profile.service.mjs +55 -1
- package/esm2020/raain-compare-stack/raain-compare-stack.component.mjs +3 -3
- package/esm2020/raain-details/raain-details.component.mjs +73 -7
- package/esm2020/shared.module.mjs +8 -3
- package/esm2020/tools/CompareManager.mjs +31 -8
- package/esm2020/tools/RefreshManager.mjs +2 -2
- package/fesm2015/raain-app.mjs +1622 -1267
- package/fesm2015/raain-app.mjs.map +1 -1
- package/fesm2020/raain-app.mjs +1515 -1175
- package/fesm2020/raain-app.mjs.map +1 -1
- package/index.d.ts +1 -0
- package/package.json +2 -2
- package/profile.service.d.ts +14 -1
- package/raain-details/raain-details.component.d.ts +7 -1
- package/shared.module.d.ts +6 -5
- package/tools/CompareManager.d.ts +8 -2
package/fesm2020/raain-app.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Injectable, EventEmitter, Component, Input, Output, ViewChild, HostListener, ChangeDetectionStrategy, Pipe, Directive, NgModule, Optional, SkipSelf } from '@angular/core';
|
|
3
|
-
import { CartesianTools, GaugeNode, SpeedMatrixContainer, RadarNode, CartesianMeasureValue,
|
|
3
|
+
import { CartesianTools, GaugeNode, SpeedMatrixContainer, RadarNode, CartesianMeasureValue, Link, EventNode, BuildQueryString, TeamNode, RainNode, RainComputationMap, RainComputationQuality, GaugeMeasure } from 'raain-model';
|
|
4
4
|
import { RaainDivIcon, ElementsFactory, MapLatLng, ChartScaleColors, ScaleElementInput, CompareElementInput, ConfigurationElementInput, Tools, DateRange, DateStatusElementInput, DynamicDateStatusElementInput, SpeedMatrixElementInput, EarthMapElementInput, TimeframeContainers, CartesianMapValue, PolarMapValue } from 'raain-ui';
|
|
5
5
|
import * as i2$1 from 'fidj-angular';
|
|
6
6
|
import { LocalStorage, LoggerLevelEnum } from 'fidj-angular';
|
|
@@ -1303,10 +1303,10 @@ class RaainCompareStackComponent {
|
|
|
1303
1303
|
}
|
|
1304
1304
|
}
|
|
1305
1305
|
RaainCompareStackComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RaainCompareStackComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
|
|
1306
|
-
RaainCompareStackComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: RaainCompareStackComponent, selector: "raain-compare-stack", inputs: { compareManager: "compareManager", currentHeight: "currentHeight", cumulative: "cumulative" }, outputs: { selectedPoint: "selectedPoint" }, ngImport: i0, template: "<div *ngIf=\"cumulative\">\n <ion-col size-md=\"6\" size-xs=\"12\">\n <ion-card class=\"card-compare\">\n <ion-card-header>Cumulative\n [{{ compareManager.compareDates[0] | date:'yyyy-MM-dd HH:mm' }}\n - {{ compareManager.compareDates[compareManager.compareDates.length - 1] | date:'yyyy-MM-dd HH:mm' }}
|
|
1306
|
+
RaainCompareStackComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: RaainCompareStackComponent, selector: "raain-compare-stack", inputs: { compareManager: "compareManager", currentHeight: "currentHeight", cumulative: "cumulative" }, outputs: { selectedPoint: "selectedPoint" }, ngImport: i0, template: "<div *ngIf=\"cumulative\">\n <ion-col size-md=\"6\" size-xs=\"12\">\n <ion-card class=\"card-compare\">\n <ion-card-header>Cumulative\n [{{ compareManager.compareDates[0] | date:'yyyy-MM-dd HH:mm' }}\n - {{ compareManager.compareDates[compareManager.compareDates.length - 1] | date:'yyyy-MM-dd HH:mm' }}[\n </ion-card-header>\n <ion-card-content *ngIf=\"compareManager.globalComparePoints?.length\">\n <raain-compare\n (selectedPoint)=\"onClick($event)\"\n [compareIndex]=\"0\"\n [currentHeight]=\"500\"\n [pointMax]=\"compareManager.globalComparePointsMax\"\n [points]=\"compareManager.globalComparePoints\"></raain-compare>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-button (click)=\"exportCumulativeToCsv()\" *ngIf=\"compareManager.globalComparePoints?.length\" fill=\"outline\"\n size=\"small\">\n <ion-icon name=\"download-outline\" slot=\"start\"></ion-icon>\n Export CSV\n </ion-button>\n</div>\n\n<div *ngIf=\"!cumulative\">\n <ion-col *ngFor=\"let compare of compareManager?.uiCompares; index as compareIndex\" size-md=\"6\" size-xs=\"12\">\n <ion-card class=\"card-compare\">\n <ion-card-header>Gauges filled {{ compare.name }} {{ compare.remarks }}\n </ion-card-header>\n <ion-card-content *ngIf=\"compare.comparePoints?.length\">\n <raain-compare\n (selectedPoint)=\"onClick($event)\"\n [compareIndex]=\"compareIndex\"\n [currentHeight]=\"500\"\n [pointMax]=\"compare.comparePointsMax\"\n [points]=\"compare.comparePoints\"></raain-compare>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-button (click)=\"exportGranularToCsv()\" *ngIf=\"compareManager.uiCompares?.length\" fill=\"outline\" size=\"small\">\n <ion-icon name=\"download-outline\" slot=\"start\"></ion-icon>\n Export CSV\n </ion-button>\n</div>\n", styles: ["#card-matrix{width:170px}.card-compare{min-width:300px}ion-card-header{padding:0 0 0 10px}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i4.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i4.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i4.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i4.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i4.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i4.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: RaainCompareComponent, selector: "raain-compare", inputs: ["points", "pointMax", "remarks", "compareIndex", "currentHeight", "currentWidth"], outputs: ["selectedPoint"] }, { kind: "pipe", type: i2.DatePipe, name: "date" }] });
|
|
1307
1307
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RaainCompareStackComponent, decorators: [{
|
|
1308
1308
|
type: Component,
|
|
1309
|
-
args: [{ selector: 'raain-compare-stack', template: "<div *ngIf=\"cumulative\">\n <ion-col size-md=\"6\" size-xs=\"12\">\n <ion-card class=\"card-compare\">\n <ion-card-header>Cumulative\n [{{ compareManager.compareDates[0] | date:'yyyy-MM-dd HH:mm' }}\n - {{ compareManager.compareDates[compareManager.compareDates.length - 1] | date:'yyyy-MM-dd HH:mm' }}
|
|
1309
|
+
args: [{ selector: 'raain-compare-stack', template: "<div *ngIf=\"cumulative\">\n <ion-col size-md=\"6\" size-xs=\"12\">\n <ion-card class=\"card-compare\">\n <ion-card-header>Cumulative\n [{{ compareManager.compareDates[0] | date:'yyyy-MM-dd HH:mm' }}\n - {{ compareManager.compareDates[compareManager.compareDates.length - 1] | date:'yyyy-MM-dd HH:mm' }}[\n </ion-card-header>\n <ion-card-content *ngIf=\"compareManager.globalComparePoints?.length\">\n <raain-compare\n (selectedPoint)=\"onClick($event)\"\n [compareIndex]=\"0\"\n [currentHeight]=\"500\"\n [pointMax]=\"compareManager.globalComparePointsMax\"\n [points]=\"compareManager.globalComparePoints\"></raain-compare>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-button (click)=\"exportCumulativeToCsv()\" *ngIf=\"compareManager.globalComparePoints?.length\" fill=\"outline\"\n size=\"small\">\n <ion-icon name=\"download-outline\" slot=\"start\"></ion-icon>\n Export CSV\n </ion-button>\n</div>\n\n<div *ngIf=\"!cumulative\">\n <ion-col *ngFor=\"let compare of compareManager?.uiCompares; index as compareIndex\" size-md=\"6\" size-xs=\"12\">\n <ion-card class=\"card-compare\">\n <ion-card-header>Gauges filled {{ compare.name }} {{ compare.remarks }}\n </ion-card-header>\n <ion-card-content *ngIf=\"compare.comparePoints?.length\">\n <raain-compare\n (selectedPoint)=\"onClick($event)\"\n [compareIndex]=\"compareIndex\"\n [currentHeight]=\"500\"\n [pointMax]=\"compare.comparePointsMax\"\n [points]=\"compare.comparePoints\"></raain-compare>\n </ion-card-content>\n </ion-card>\n </ion-col>\n\n <ion-button (click)=\"exportGranularToCsv()\" *ngIf=\"compareManager.uiCompares?.length\" fill=\"outline\" size=\"small\">\n <ion-icon name=\"download-outline\" slot=\"start\"></ion-icon>\n Export CSV\n </ion-button>\n</div>\n", styles: ["#card-matrix{width:170px}.card-compare{min-width:300px}ion-card-header{padding:0 0 0 10px}\n"] }]
|
|
1310
1310
|
}], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { compareManager: [{
|
|
1311
1311
|
type: Input
|
|
1312
1312
|
}], currentHeight: [{
|
|
@@ -1460,10 +1460,10 @@ class CompareManager {
|
|
|
1460
1460
|
this.uiCompares = uiCompares;
|
|
1461
1461
|
this.onChanges();
|
|
1462
1462
|
}
|
|
1463
|
-
async refreshGlobalCompareQuality(targetsOrdered, withCompareDuplicate, cumulativeHours = 0) {
|
|
1463
|
+
async refreshGlobalCompareQuality(targetsOrdered, period, withCompareDuplicate, cumulativeHours = 0) {
|
|
1464
1464
|
try {
|
|
1465
1465
|
await this.fetchRainComputationQualities(targetsOrdered, cumulativeHours);
|
|
1466
|
-
await this.buildComparesTimeline(targetsOrdered, withCompareDuplicate);
|
|
1466
|
+
await this.buildComparesTimeline(targetsOrdered, period, withCompareDuplicate);
|
|
1467
1467
|
if (!this.buildCompares.compareCumulative) {
|
|
1468
1468
|
// throw Error('needs cumulative compare');
|
|
1469
1469
|
return;
|
|
@@ -1478,6 +1478,9 @@ class CompareManager {
|
|
|
1478
1478
|
}
|
|
1479
1479
|
}
|
|
1480
1480
|
async setGaugesInMap() {
|
|
1481
|
+
// Get all gauge IDs linked to this rainNode
|
|
1482
|
+
const rainNodeGaugeIds = this.rainNode.getLinks(GaugeNode.TYPE).map((l) => l.getId());
|
|
1483
|
+
// Fetch gauges from API (may be filtered/limited)
|
|
1481
1484
|
let gaugesToFilter = await this.profileService.getGauges(this.rainNode?.id, this.rainNode.getCenter());
|
|
1482
1485
|
gaugesToFilter = gaugesToFilter
|
|
1483
1486
|
.sort((a, b) => {
|
|
@@ -1485,9 +1488,29 @@ class CompareManager {
|
|
|
1485
1488
|
b.approxDistanceFrom(this.rainNode.getCenter()));
|
|
1486
1489
|
})
|
|
1487
1490
|
.filter((v, index) => index < 200);
|
|
1488
|
-
|
|
1489
|
-
const
|
|
1490
|
-
|
|
1491
|
+
// Build a map of gauges from API response
|
|
1492
|
+
const gaugesFromApi = new Map();
|
|
1493
|
+
for (const gauge of gaugesToFilter) {
|
|
1494
|
+
gaugesFromApi.set(gauge.id, gauge);
|
|
1495
|
+
}
|
|
1496
|
+
// Fetch missing gauges individually (those linked but not in API response)
|
|
1497
|
+
const missingGaugeIds = rainNodeGaugeIds.filter((id) => !gaugesFromApi.has(id));
|
|
1498
|
+
for (const gaugeId of missingGaugeIds) {
|
|
1499
|
+
try {
|
|
1500
|
+
const gauge = await this.profileService.getGauge(gaugeId);
|
|
1501
|
+
if (gauge) {
|
|
1502
|
+
gaugesFromApi.set(gauge.id, gauge);
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
catch (e) {
|
|
1506
|
+
console.warn(`Failed to fetch gauge ${gaugeId}:`, e);
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
// Filter to only gauges linked to this rainNode
|
|
1510
|
+
const visibleGauges = rainNodeGaugeIds
|
|
1511
|
+
.map((id) => gaugesFromApi.get(id))
|
|
1512
|
+
.filter((g) => !!g);
|
|
1513
|
+
console.log('visibleGauges:', visibleGauges.length, '/', rainNodeGaugeIds.length);
|
|
1491
1514
|
const gaugesLatLng = [];
|
|
1492
1515
|
for (const gauge of visibleGauges) {
|
|
1493
1516
|
gaugesLatLng.push(new MapLatLng(gauge.latitude, gauge.longitude, undefined, gauge.id, gauge.name));
|
|
@@ -1595,13 +1618,13 @@ class CompareManager {
|
|
|
1595
1618
|
}
|
|
1596
1619
|
return result.sort((a, b) => a.date.getTime() - b.date.getTime());
|
|
1597
1620
|
}
|
|
1598
|
-
async buildComparesTimeline(targetsOrdered, withCompareDuplicate) {
|
|
1621
|
+
async buildComparesTimeline(targetsOrdered, period, withCompareDuplicate) {
|
|
1599
1622
|
const compareDates = targetsOrdered.map((t) => t.date);
|
|
1600
1623
|
const qualities = compareDates
|
|
1601
1624
|
.filter((d) => !!d)
|
|
1602
1625
|
.map((d) => this.getRainComputationQuality(d))
|
|
1603
1626
|
.filter((rcq) => !!rcq);
|
|
1604
|
-
this.compareDates = compareDates; // compareDates.slice(1, -1);
|
|
1627
|
+
this.compareDates = compareDates?.length > 1 ? compareDates : [period.begin, period.end]; // compareDates.slice(1, -1);
|
|
1605
1628
|
this.buildCompares = SpeedMatrixContainer.BuildCompares(qualities, !withCompareDuplicate);
|
|
1606
1629
|
return this.buildCompares;
|
|
1607
1630
|
}
|
|
@@ -1967,7 +1990,7 @@ class RefreshManager {
|
|
|
1967
1990
|
const cumulativeHours = this.cumulative
|
|
1968
1991
|
? (this.period.end.getTime() - this.period.begin.getTime()) / (60 * 60 * 1000)
|
|
1969
1992
|
: 0;
|
|
1970
|
-
await this.compareManager.refreshGlobalCompareQuality(targets, !this.removeDuplicate, cumulativeHours);
|
|
1993
|
+
await this.compareManager.refreshGlobalCompareQuality(targets, this.period, !this.removeDuplicate, cumulativeHours);
|
|
1971
1994
|
}
|
|
1972
1995
|
catch (e) {
|
|
1973
1996
|
console.error('refreshGlobalCompareReport', e);
|
|
@@ -2065,1377 +2088,1690 @@ function mapDateRangeToString(range) {
|
|
|
2065
2088
|
}
|
|
2066
2089
|
}
|
|
2067
2090
|
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2091
|
+
class FidjStorageNode {
|
|
2092
|
+
constructor() {
|
|
2093
|
+
this.radars = [];
|
|
2094
|
+
this.rains = [];
|
|
2095
|
+
this.gauges = [];
|
|
2096
|
+
this.events = [];
|
|
2097
|
+
this.infos = {};
|
|
2098
|
+
}
|
|
2099
|
+
static getEmptyNode() {
|
|
2100
|
+
return new FidjStorageNode();
|
|
2101
|
+
}
|
|
2102
|
+
static getDemoNode() {
|
|
2103
|
+
const demoNode = new FidjStorageNode();
|
|
2104
|
+
const link = new Link('rain', 'https://demo/api/rains/2');
|
|
2105
|
+
/*
|
|
2106
|
+
demoNode.radars = [
|
|
2107
|
+
new RadarNode({
|
|
2108
|
+
id: '5efd04569cb1f4161bd69dc7',
|
|
2109
|
+
name: 'demo radar A',
|
|
2110
|
+
links: [link],
|
|
2111
|
+
latitude: 48.774569,
|
|
2112
|
+
longitude: 2.008407
|
|
2113
|
+
}),
|
|
2114
|
+
new RadarNode({
|
|
2115
|
+
id: 'dr2',
|
|
2116
|
+
name: 'demo radar B',
|
|
2117
|
+
links: [link],
|
|
2118
|
+
latitude: 0.11,
|
|
2119
|
+
longitude: -0.753
|
|
2120
|
+
}),
|
|
2121
|
+
new RadarNode({
|
|
2122
|
+
id: 'dr3',
|
|
2123
|
+
name: 'demo radar C',
|
|
2124
|
+
latitude: 0.13,
|
|
2125
|
+
longitude: -0.753,
|
|
2126
|
+
links: []
|
|
2127
|
+
}),
|
|
2128
|
+
new RadarNode({
|
|
2129
|
+
id: 'dr4',
|
|
2130
|
+
name: 'demo radar D',
|
|
2131
|
+
latitude: 0.14,
|
|
2132
|
+
longitude: -0.74,
|
|
2133
|
+
links: []
|
|
2134
|
+
})];
|
|
2135
|
+
demoNode.rains = [
|
|
2136
|
+
new RainNode({
|
|
2137
|
+
id: '5efd04569cb1f4161bd69dc8',
|
|
2138
|
+
name: 'Demo rain zone A',
|
|
2139
|
+
links: [new Link('radar', 'https://demo/api/radars/5efcfe619cb1f4161bd69dc3')],
|
|
2140
|
+
status: 0,
|
|
2141
|
+
quality: 75,
|
|
2142
|
+
latitude: 48.774569,
|
|
2143
|
+
longitude: 2.008407
|
|
2144
|
+
}),
|
|
2145
|
+
new RainNode({
|
|
2146
|
+
id: 'dz2',
|
|
2147
|
+
name: 'Demo rain zone B',
|
|
2148
|
+
radars: [demoNode.radars[0], demoNode.radars[1]],
|
|
2149
|
+
status: 1,
|
|
2150
|
+
quality: 50,
|
|
2151
|
+
latitude: 48.774569,
|
|
2152
|
+
longitude: 2.008407
|
|
2153
|
+
}),
|
|
2154
|
+
new RainNode({
|
|
2155
|
+
id: 'dz3',
|
|
2156
|
+
name: 'Demo rain zone C',
|
|
2157
|
+
radars: [demoNode.radars[0], demoNode.radars[1]],
|
|
2158
|
+
status: 2,
|
|
2159
|
+
quality: 75,
|
|
2160
|
+
latitude: 48.774569,
|
|
2161
|
+
longitude: 2.008407
|
|
2162
|
+
}),
|
|
2163
|
+
new RainNode({
|
|
2164
|
+
id: 'dz4',
|
|
2165
|
+
name: 'Demo rain zone D',
|
|
2166
|
+
radars: [demoNode.radars[0], demoNode.radars[1]],
|
|
2167
|
+
status: 3,
|
|
2168
|
+
quality: 95,
|
|
2169
|
+
latitude: 48.774569,
|
|
2170
|
+
longitude: 2.008407
|
|
2171
|
+
})];
|
|
2172
|
+
|
|
2173
|
+
demoNode.gauges = [
|
|
2174
|
+
new GaugeNode({
|
|
2175
|
+
id: 'g1',
|
|
2176
|
+
name: 'Gauge A',
|
|
2177
|
+
latitude: 48.7748,
|
|
2178
|
+
longitude: 2.28407,
|
|
2179
|
+
}),
|
|
2180
|
+
new GaugeNode({
|
|
2181
|
+
id: 'g2',
|
|
2182
|
+
name: 'Gauge B',
|
|
2183
|
+
latitude: 48.874569,
|
|
2184
|
+
longitude: 2.108407,
|
|
2185
|
+
})];
|
|
2186
|
+
demoNode.events = [{
|
|
2187
|
+
id: 'e2',
|
|
2188
|
+
title: 'Need support ?',
|
|
2189
|
+
status: 0,
|
|
2190
|
+
red: false,
|
|
2191
|
+
description: 'This area is dedicated to support you and your team. Support is made on Radar or Rain quality, ' +
|
|
2192
|
+
'or any feedback we can have about your production system. Our goal : improving your data.',
|
|
2193
|
+
created: new Date(),
|
|
2194
|
+
modified: new Date()
|
|
2195
|
+
}];
|
|
2196
|
+
demoNode.team = {
|
|
2197
|
+
id: 'p1',
|
|
2198
|
+
email: 'demo@demo.com',
|
|
2199
|
+
name: 'demo guy',
|
|
2200
|
+
description: 'the demo guy'
|
|
2201
|
+
};
|
|
2202
|
+
|
|
2203
|
+
*/
|
|
2204
|
+
return demoNode;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
class FidjStorageResult {
|
|
2208
|
+
}
|
|
2209
|
+
class FidjStorage {
|
|
2210
|
+
constructor(storage) {
|
|
2071
2211
|
this.storage = storage;
|
|
2072
|
-
this.
|
|
2073
|
-
this.
|
|
2074
|
-
this.availableTimeSteps = [];
|
|
2075
|
-
this.showPixelMarkers = false;
|
|
2076
|
-
this.qualityIndicators = [];
|
|
2077
|
-
this.qualityIndicatorsLoading = false;
|
|
2078
|
-
// Cached computed values (to avoid method calls in templates)
|
|
2079
|
-
this.percentOfComputations = 0;
|
|
2080
|
-
this.percentOfImages = 0;
|
|
2081
|
-
this.truncatedError = '';
|
|
2082
|
-
this.cumulativeDurationInMinutes = 10;
|
|
2083
|
-
this.DateRange = DateRange;
|
|
2084
|
-
// Wrapper function that preserves the async nature of fetchData
|
|
2085
|
-
this.fetchDataWrapper = async (focusDate, focusRange) => {
|
|
2086
|
-
return await this.fetchData(focusDate, focusRange);
|
|
2087
|
-
};
|
|
2212
|
+
this.node = FidjStorageNode.getEmptyNode();
|
|
2213
|
+
this.fidjMetaResult = { data: new FidjStorageNode() };
|
|
2088
2214
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2215
|
+
async storeData(fidjService, data) {
|
|
2216
|
+
this.node = JSON.parse(JSON.stringify(data));
|
|
2217
|
+
this.fidjMetaResult.data = this.node;
|
|
2218
|
+
if (this.isDemoMode) {
|
|
2219
|
+
this.storage.set('fidjMetaResult', JSON.stringify(this.fidjMetaResult));
|
|
2220
|
+
return;
|
|
2095
2221
|
}
|
|
2096
|
-
|
|
2097
|
-
return d.toISOString().substring(0, exampleFormattedDate.length);
|
|
2222
|
+
await fidjService.put(this.fidjMetaResult);
|
|
2098
2223
|
}
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2224
|
+
async getRefreshedNodeCopy(fidjService) {
|
|
2225
|
+
if (this.isDemoMode) {
|
|
2226
|
+
const fidjMetaResult = this.storage.get('fidjMetaResult');
|
|
2227
|
+
if (fidjMetaResult) {
|
|
2228
|
+
this.fidjMetaResult = JSON.parse(fidjMetaResult);
|
|
2229
|
+
this.node = this.fidjMetaResult.data;
|
|
2230
|
+
}
|
|
2231
|
+
return JSON.parse(JSON.stringify(this.node));
|
|
2105
2232
|
}
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
return {
|
|
2110
|
-
date: RaainDetailsComponent.DateUTC(c.name),
|
|
2111
|
-
value: Math.min(100, c.x),
|
|
2233
|
+
const firstDemoData = async () => {
|
|
2234
|
+
this.node = FidjStorageNode.getDemoNode();
|
|
2235
|
+
await this.storeData(fidjService, this.node);
|
|
2112
2236
|
};
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
const result = await this.profileService.getIndicators(this.rainNode.id);
|
|
2121
|
-
this.qualityIndicators = result.indicators;
|
|
2237
|
+
await fidjService.sync(firstDemoData);
|
|
2238
|
+
const fidjFindAllResults = await fidjService.findAll();
|
|
2239
|
+
if (fidjFindAllResults && fidjFindAllResults.length > 0) {
|
|
2240
|
+
if (fidjFindAllResults[0].data) {
|
|
2241
|
+
this.fidjMetaResult = fidjFindAllResults[0];
|
|
2242
|
+
this.node = this.fidjMetaResult.data;
|
|
2243
|
+
}
|
|
2122
2244
|
}
|
|
2123
|
-
this.
|
|
2124
|
-
this.cdr.markForCheck();
|
|
2245
|
+
return JSON.parse(JSON.stringify(this.node));
|
|
2125
2246
|
}
|
|
2126
|
-
|
|
2127
|
-
this.
|
|
2247
|
+
setDemoMode(isDemo) {
|
|
2248
|
+
this.isDemoMode = isDemo;
|
|
2128
2249
|
}
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2250
|
+
}
|
|
2251
|
+
|
|
2252
|
+
class ProfileService {
|
|
2253
|
+
constructor(storage, fidjService, httpClient, router) {
|
|
2254
|
+
this.storage = storage;
|
|
2255
|
+
this.fidjService = fidjService;
|
|
2256
|
+
this.httpClient = httpClient;
|
|
2257
|
+
this.router = router;
|
|
2258
|
+
this.email = this.storage.get('raain-email');
|
|
2259
|
+
this.asTeamId = this.storage.get('raain-asTeamId');
|
|
2260
|
+
this.readyForSync = new BehaviorSubject(false);
|
|
2261
|
+
this.roles = [];
|
|
2262
|
+
this.fidjStorage = new FidjStorage(storage);
|
|
2263
|
+
this.isDemoMode = false;
|
|
2134
2264
|
}
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2265
|
+
get isDemoMode() {
|
|
2266
|
+
return this.isDemo;
|
|
2267
|
+
}
|
|
2268
|
+
set isDemoMode(mode) {
|
|
2269
|
+
this.isDemo = mode ? mode : true;
|
|
2270
|
+
this.fidjStorage.setDemoMode(this.isDemo);
|
|
2271
|
+
}
|
|
2272
|
+
get defaultUrlForAPI() {
|
|
2273
|
+
return this.storage.get('raain-urlForAPI');
|
|
2274
|
+
}
|
|
2275
|
+
set defaultUrlForAPI(url) {
|
|
2276
|
+
this.storage.set('raain-urlForAPI', url);
|
|
2277
|
+
}
|
|
2278
|
+
async refreshProfile() {
|
|
2279
|
+
try {
|
|
2280
|
+
this.nodeData = await this.fidjStorage.getRefreshedNodeCopy(this.fidjService);
|
|
2281
|
+
this.setRoles(await this.fidjService.getRoles());
|
|
2282
|
+
return this.nodeData;
|
|
2139
2283
|
}
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
label: '% Rainy',
|
|
2143
|
-
style: 'bar',
|
|
2144
|
-
values,
|
|
2145
|
-
},
|
|
2146
|
-
{
|
|
2147
|
-
label: '% Images',
|
|
2148
|
-
style: 'bar',
|
|
2149
|
-
values,
|
|
2150
|
-
},
|
|
2151
|
-
// {
|
|
2152
|
-
// label: '% Quality',
|
|
2153
|
-
// style: 'line',
|
|
2154
|
-
// values,
|
|
2155
|
-
// },
|
|
2156
|
-
];
|
|
2157
|
-
const range = mapDateRangeToString(focusRange);
|
|
2158
|
-
let data = fakeData;
|
|
2159
|
-
if (!this.rainNode) {
|
|
2160
|
-
return data;
|
|
2284
|
+
catch (e) {
|
|
2285
|
+
await this.checkError(e);
|
|
2161
2286
|
}
|
|
2162
|
-
|
|
2163
|
-
|
|
2287
|
+
}
|
|
2288
|
+
async isConnected() {
|
|
2289
|
+
return this.fidjService.isConnected();
|
|
2290
|
+
}
|
|
2291
|
+
getEmail() {
|
|
2292
|
+
return this.email ?? this.storage.get('raain-email', this.email);
|
|
2293
|
+
}
|
|
2294
|
+
setEmail(email) {
|
|
2295
|
+
this.email = email;
|
|
2296
|
+
this.storage.set('raain-email', this.email);
|
|
2297
|
+
}
|
|
2298
|
+
async logout(fidjKey, fidjProd) {
|
|
2299
|
+
// this.storage.remove('raain-email');
|
|
2300
|
+
if (!fidjKey) {
|
|
2301
|
+
try {
|
|
2302
|
+
await this.fidjService.loginInDemoMode();
|
|
2303
|
+
this.readyForSync.next(true);
|
|
2304
|
+
}
|
|
2305
|
+
catch (err) {
|
|
2306
|
+
console.error('initFidj catch pb: ', err);
|
|
2307
|
+
}
|
|
2308
|
+
return;
|
|
2164
2309
|
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2310
|
+
await this.fidjService.logout(true);
|
|
2311
|
+
try {
|
|
2312
|
+
await this.fidjService.init(fidjKey, {
|
|
2313
|
+
logLevel: LoggerLevelEnum.WARN,
|
|
2314
|
+
crypto: false,
|
|
2315
|
+
prod: fidjProd,
|
|
2316
|
+
useDB: false,
|
|
2168
2317
|
});
|
|
2169
|
-
|
|
2170
|
-
{
|
|
2171
|
-
label: 'Rainy Count',
|
|
2172
|
-
style: 'line',
|
|
2173
|
-
values: hourCounts.rainyCount.map(RaainDetailsComponent.MapCountToDateValue),
|
|
2174
|
-
},
|
|
2175
|
-
{
|
|
2176
|
-
label: '% Images',
|
|
2177
|
-
style: 'bar',
|
|
2178
|
-
values: hourCounts.percentImages.map(RaainDetailsComponent.MapCountToDateValue),
|
|
2179
|
-
},
|
|
2180
|
-
{
|
|
2181
|
-
label: 'Rainy Sum',
|
|
2182
|
-
style: 'line',
|
|
2183
|
-
values: hourCounts.rainySum.map(RaainDetailsComponent.MapCountToDateValue),
|
|
2184
|
-
},
|
|
2185
|
-
];
|
|
2318
|
+
this.readyForSync.next(true);
|
|
2186
2319
|
}
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
range,
|
|
2190
|
-
periodBegin: focusDate,
|
|
2191
|
-
});
|
|
2192
|
-
data = [
|
|
2193
|
-
{
|
|
2194
|
-
label: '% Rainy',
|
|
2195
|
-
style: 'bar',
|
|
2196
|
-
values: counts.percentRainy.map(RaainDetailsComponent.MapCountToDateValue),
|
|
2197
|
-
},
|
|
2198
|
-
{
|
|
2199
|
-
label: '% Images',
|
|
2200
|
-
style: 'bar',
|
|
2201
|
-
values: counts.percentImages.map(RaainDetailsComponent.MapCountToDateValue),
|
|
2202
|
-
},
|
|
2203
|
-
];
|
|
2320
|
+
catch (err) {
|
|
2321
|
+
console.error('initFidj catch pb: ', err);
|
|
2204
2322
|
}
|
|
2205
|
-
// console.log(`fetchData DONE ${range}`, data);
|
|
2206
|
-
return data;
|
|
2207
|
-
}
|
|
2208
|
-
ngOnChanges(changes) {
|
|
2209
|
-
this.change(changes).then((ignored) => { });
|
|
2210
|
-
}
|
|
2211
|
-
ngOnDestroy() {
|
|
2212
|
-
this.cleanAll();
|
|
2213
2323
|
}
|
|
2214
|
-
async
|
|
2215
|
-
if (
|
|
2216
|
-
|
|
2324
|
+
async checkError(error) {
|
|
2325
|
+
if (error.code === 401) {
|
|
2326
|
+
console.warn('Pb on auth');
|
|
2327
|
+
if (this.router.url.indexOf('login') < 0) {
|
|
2328
|
+
try {
|
|
2329
|
+
await this.fidjService.logout();
|
|
2330
|
+
}
|
|
2331
|
+
catch (ignored) {
|
|
2332
|
+
// Ignore logout errors as we're redirecting to logout page anyway
|
|
2333
|
+
}
|
|
2334
|
+
return this.gotoLout();
|
|
2335
|
+
}
|
|
2217
2336
|
}
|
|
2218
2337
|
}
|
|
2219
|
-
async
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
this.periodEnd = new Date(newValue);
|
|
2229
|
-
this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
|
|
2230
|
-
this.storage.set('raain-periodEnd', this.periodEnd);
|
|
2231
|
-
this.periodBegin = new Date(this.periodEnd.getTime() - this.getDurationInHours() * RaainDetailsComponent.HOUR_MS);
|
|
2232
|
-
this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
|
|
2233
|
-
this.storage.set('raain-periodBegin', this.periodBegin);
|
|
2234
|
-
this.updateRefreshManagerPeriod();
|
|
2235
|
-
}
|
|
2236
|
-
async onPeriodDurationChange(_event) {
|
|
2237
|
-
const durationInHours = this.getDurationInHours();
|
|
2238
|
-
this.periodEnd = new Date(this.periodBegin.getTime() + durationInHours * RaainDetailsComponent.HOUR_MS);
|
|
2239
|
-
this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
|
|
2240
|
-
this.storage.set('raain-periodEnd', this.periodEnd);
|
|
2241
|
-
this.storage.set('raain-periodDurationInHours', durationInHours);
|
|
2242
|
-
this.updateRefreshManagerPeriod();
|
|
2243
|
-
this.updateCumulativeDurationInMinutes();
|
|
2244
|
-
}
|
|
2245
|
-
async onDateChangeInCount(dateChanged) {
|
|
2246
|
-
const dateString = dateChanged.toISOString().substring(0, 11) +
|
|
2247
|
-
dateChanged.toLocaleTimeString().substring(0, 5);
|
|
2248
|
-
this.periodDurationAsString = '1';
|
|
2249
|
-
if (this.toggleCumulative) {
|
|
2250
|
-
// Cumulative: select periodEnd
|
|
2251
|
-
this.periodEndAsString = dateString;
|
|
2252
|
-
await this.onPeriodEndChange(null);
|
|
2338
|
+
async gotoLout() {
|
|
2339
|
+
try {
|
|
2340
|
+
if (this.router.url.indexOf('login') > -1) {
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2343
|
+
await this.router.navigateByUrl('/logout', {
|
|
2344
|
+
skipLocationChange: true,
|
|
2345
|
+
replaceUrl: true,
|
|
2346
|
+
});
|
|
2253
2347
|
}
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
this.periodBeginAsString = dateString;
|
|
2257
|
-
await this.onPeriodBeginChange(null);
|
|
2348
|
+
catch (e) {
|
|
2349
|
+
console.error('gotoLout error: ', e);
|
|
2258
2350
|
}
|
|
2259
|
-
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
2260
|
-
}
|
|
2261
|
-
async onDateChangeInMap(dateShown) {
|
|
2262
|
-
this.dateShown = dateShown;
|
|
2263
|
-
await this.fetchAndUpdateMap();
|
|
2264
|
-
await this.refreshManager.setReportPeriod(this.dateShown);
|
|
2265
|
-
}
|
|
2266
|
-
async onSumChangeInMap(sum) {
|
|
2267
|
-
this.sumDetails = sum;
|
|
2268
2351
|
}
|
|
2269
|
-
async
|
|
2270
|
-
|
|
2271
|
-
if (gaugesFiltered.length !== 1) {
|
|
2352
|
+
async gotoLogin() {
|
|
2353
|
+
if (this.router.url.indexOf('login') > -1) {
|
|
2272
2354
|
return;
|
|
2273
2355
|
}
|
|
2274
|
-
|
|
2275
|
-
await this.
|
|
2276
|
-
await this.compareManager.selectGauge(gaugeSelected.id, 0);
|
|
2356
|
+
// await this.router.navigateByUrl('/', {skipLocationChange: true});
|
|
2357
|
+
await this.router.navigate(['/login']);
|
|
2277
2358
|
}
|
|
2278
|
-
|
|
2279
|
-
const
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
gaugeValueShowEnd.setHours(23, 59);
|
|
2283
|
-
const gaugeMeasures = await this.profileService.getGaugeMeasures(gaugeSelected.id, gaugeValueShowBegin, gaugeValueShowEnd);
|
|
2284
|
-
const gaugeValues = gaugeMeasures.map((gm) => {
|
|
2285
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2286
|
-
const cartesianMeasureValue = new CartesianMeasureValue(gm.values[0]);
|
|
2287
|
-
return {
|
|
2288
|
-
date: gm.date,
|
|
2289
|
-
value: cartesianMeasureValue.getCartesianValues()[0].value,
|
|
2290
|
-
};
|
|
2291
|
-
});
|
|
2292
|
-
this.gaugeSelectedPoints = [
|
|
2293
|
-
{
|
|
2294
|
-
label: gaugeSelected.name,
|
|
2295
|
-
style: 'bar',
|
|
2296
|
-
values: gaugeValues,
|
|
2297
|
-
},
|
|
2298
|
-
];
|
|
2299
|
-
this.cdr.markForCheck();
|
|
2359
|
+
isLoggedIn() {
|
|
2360
|
+
const loggedIn = this.fidjService.isLoggedIn();
|
|
2361
|
+
console.log('isLoggedIn: ', loggedIn);
|
|
2362
|
+
return loggedIn;
|
|
2300
2363
|
}
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
await this.compareManager.selectGauge(e.point.id, e.compareIndex);
|
|
2364
|
+
needsConnectionRefresh() {
|
|
2365
|
+
return this.fidjService.needsRefresh();
|
|
2304
2366
|
}
|
|
2305
|
-
async
|
|
2306
|
-
|
|
2307
|
-
// await this.refreshMap();
|
|
2308
|
-
// }
|
|
2367
|
+
async connectionRefresh() {
|
|
2368
|
+
await this.fidjService.refresh();
|
|
2309
2369
|
}
|
|
2310
|
-
|
|
2311
|
-
|
|
2370
|
+
async storeAll() {
|
|
2371
|
+
return this.fidjStorage.storeData(this.fidjService, this.nodeData);
|
|
2312
2372
|
}
|
|
2313
|
-
|
|
2314
|
-
this.
|
|
2315
|
-
this.dateShown = this.getDateBasedOnCumulativeMode(this.timeframeDates);
|
|
2316
|
-
this.updateCumulativeDurationInMinutes();
|
|
2317
|
-
if (this.toggleMap) {
|
|
2318
|
-
this.updateRefreshManagerPeriod(); // Update cumulative flag before refresh
|
|
2319
|
-
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
2320
|
-
}
|
|
2373
|
+
isAdmin() {
|
|
2374
|
+
return this.roles.indexOf('admin') > -1;
|
|
2321
2375
|
}
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2376
|
+
// === Notifications ===
|
|
2377
|
+
async createNotification(rainId, message) {
|
|
2378
|
+
const data = {
|
|
2379
|
+
rain: rainId,
|
|
2380
|
+
message,
|
|
2381
|
+
};
|
|
2382
|
+
try {
|
|
2383
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2384
|
+
key: 'notifications',
|
|
2385
|
+
verb: 'POST',
|
|
2386
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/notifications',
|
|
2387
|
+
data,
|
|
2388
|
+
});
|
|
2389
|
+
return new EventNode(resp.data);
|
|
2326
2390
|
}
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
this.cumulativeDurationInMinutes = this.selectedTimeStep || 10;
|
|
2391
|
+
catch (e) {
|
|
2392
|
+
await this.checkError(e);
|
|
2330
2393
|
}
|
|
2394
|
+
return null;
|
|
2331
2395
|
}
|
|
2332
|
-
async
|
|
2333
|
-
this.selectedProvider = $event?.detail?.value;
|
|
2334
|
-
this.storage.set('raain-selectedProvider', this.selectedProvider);
|
|
2335
|
-
await this.applyRefreshManagerSettings();
|
|
2336
|
-
}
|
|
2337
|
-
async onTimeStepChanged($event) {
|
|
2338
|
-
this.selectedTimeStep = $event?.detail?.value;
|
|
2339
|
-
this.storage.set('raain-selectedTimeStep', this.selectedTimeStep);
|
|
2340
|
-
this.updateCumulativeDurationInMinutes();
|
|
2341
|
-
await this.applyRefreshManagerSettings();
|
|
2342
|
-
}
|
|
2343
|
-
async loadProviders() {
|
|
2344
|
-
if (!this.rainNode?.id) {
|
|
2345
|
-
return;
|
|
2346
|
-
}
|
|
2396
|
+
async getNotifications(rainId) {
|
|
2347
2397
|
try {
|
|
2348
|
-
const
|
|
2349
|
-
|
|
2350
|
-
|
|
2351
|
-
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
(this.availableTimeSteps.length > 0 ? this.availableTimeSteps[0] : 10);
|
|
2398
|
+
const params = { rain: rainId };
|
|
2399
|
+
const queryString = BuildQueryString(params);
|
|
2400
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2401
|
+
key: 'notifications',
|
|
2402
|
+
verb: 'GET',
|
|
2403
|
+
relativePath: queryString ? '?' + queryString : '',
|
|
2404
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/notifications',
|
|
2405
|
+
});
|
|
2406
|
+
return resp.data.notifications.map((n) => new EventNode(n));
|
|
2358
2407
|
}
|
|
2359
2408
|
catch (e) {
|
|
2360
|
-
|
|
2361
|
-
this.availableProviders = ['Raain'];
|
|
2362
|
-
this.availableTimeSteps = [5, 10, 15, 30, 60];
|
|
2363
|
-
this.selectedProvider = 'Raain';
|
|
2364
|
-
this.selectedTimeStep = 10;
|
|
2365
|
-
}
|
|
2366
|
-
// Set provider and timeStep on refreshManager
|
|
2367
|
-
if (this.refreshManager) {
|
|
2368
|
-
this.refreshManager.provider = this.selectedProvider;
|
|
2369
|
-
this.refreshManager.timeStepInMinutes = this.selectedTimeStep;
|
|
2409
|
+
await this.checkError(e);
|
|
2370
2410
|
}
|
|
2371
|
-
|
|
2372
|
-
}
|
|
2373
|
-
onChangeDetectionTest(rainNode) {
|
|
2374
|
-
console.log(TEST_DETECTION++, 'onChangeDetectionTest');
|
|
2375
|
-
return '';
|
|
2411
|
+
return null;
|
|
2376
2412
|
}
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2413
|
+
async getAllNotifications() {
|
|
2414
|
+
try {
|
|
2415
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2416
|
+
key: 'notifications',
|
|
2417
|
+
verb: 'GET',
|
|
2418
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/notifications',
|
|
2419
|
+
});
|
|
2420
|
+
return resp.data.notifications.map((n) => new EventNode(n));
|
|
2382
2421
|
}
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
this.updatePercentOfComputations();
|
|
2388
|
-
this.updateTruncatedError();
|
|
2389
|
-
this.updateCumulativeDurationInMinutes();
|
|
2390
|
-
}
|
|
2391
|
-
async refreshMap() {
|
|
2392
|
-
this.gaugeSelectedPoints = [];
|
|
2393
|
-
this.dateShown = this.getDateBasedOnCumulativeMode();
|
|
2394
|
-
this.borders = [];
|
|
2395
|
-
this.compareManager.cleanAll();
|
|
2396
|
-
await this.compareManager.setGaugesInMap();
|
|
2397
|
-
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
2398
|
-
this.cdr.markForCheck();
|
|
2399
|
-
}
|
|
2400
|
-
async setPeriodOfNow() {
|
|
2401
|
-
const last30mn = new Date();
|
|
2402
|
-
last30mn.setMinutes(last30mn.getMinutes() - 30);
|
|
2403
|
-
this.periodBeginAsString =
|
|
2404
|
-
last30mn.toISOString().substring(0, 11) + last30mn.toLocaleTimeString().substring(0, 5);
|
|
2405
|
-
this.periodDurationAsString = '1';
|
|
2406
|
-
await this.onPeriodBeginChange(null);
|
|
2407
|
-
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
2422
|
+
catch (e) {
|
|
2423
|
+
await this.checkError(e);
|
|
2424
|
+
}
|
|
2425
|
+
return [];
|
|
2408
2426
|
}
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2427
|
+
// === Teams ===
|
|
2428
|
+
async getTeams() {
|
|
2429
|
+
const teams = [];
|
|
2430
|
+
const params = {};
|
|
2431
|
+
const queryString = BuildQueryString(params);
|
|
2432
|
+
try {
|
|
2433
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2434
|
+
key: 'teams',
|
|
2435
|
+
verb: 'GET',
|
|
2436
|
+
relativePath: queryString ? '?' + queryString : '',
|
|
2437
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/teams',
|
|
2438
|
+
});
|
|
2439
|
+
for (const team of resp.data.teams) {
|
|
2440
|
+
teams.push(new TeamNode(team));
|
|
2441
|
+
}
|
|
2413
2442
|
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2443
|
+
catch (e) {
|
|
2444
|
+
await this.checkError(e);
|
|
2445
|
+
}
|
|
2446
|
+
return teams;
|
|
2418
2447
|
}
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2448
|
+
async getTeam(teamId) {
|
|
2449
|
+
try {
|
|
2450
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2451
|
+
key: 'teams',
|
|
2452
|
+
verb: 'GET',
|
|
2453
|
+
relativePath: teamId,
|
|
2454
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/teams',
|
|
2455
|
+
});
|
|
2456
|
+
return new TeamNode(resp.data);
|
|
2424
2457
|
}
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2458
|
+
catch (e) {
|
|
2459
|
+
await this.checkError(e);
|
|
2460
|
+
}
|
|
2461
|
+
return null;
|
|
2428
2462
|
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2463
|
+
// === Radars ===
|
|
2464
|
+
async getRadars(name) {
|
|
2465
|
+
const radars = [];
|
|
2466
|
+
const params = {};
|
|
2467
|
+
if (name) {
|
|
2468
|
+
params.name = name;
|
|
2469
|
+
}
|
|
2470
|
+
const queryString = BuildQueryString(params);
|
|
2471
|
+
try {
|
|
2472
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2473
|
+
key: 'radars',
|
|
2474
|
+
verb: 'GET',
|
|
2475
|
+
relativePath: queryString ? '?' + queryString : '',
|
|
2476
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/radars',
|
|
2477
|
+
});
|
|
2478
|
+
for (const r of resp.data.radars) {
|
|
2479
|
+
const radar = new RadarNode(r);
|
|
2480
|
+
radars.push(radar);
|
|
2436
2481
|
}
|
|
2437
2482
|
}
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
return
|
|
2483
|
+
catch (e) {
|
|
2484
|
+
await this.checkError(e);
|
|
2485
|
+
}
|
|
2486
|
+
return radars;
|
|
2442
2487
|
}
|
|
2443
|
-
|
|
2444
|
-
|
|
2488
|
+
async getRadar(id) {
|
|
2489
|
+
try {
|
|
2490
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2491
|
+
key: 'radars',
|
|
2492
|
+
verb: 'GET',
|
|
2493
|
+
relativePath: id,
|
|
2494
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/radars',
|
|
2495
|
+
});
|
|
2496
|
+
return new RadarNode(resp.data);
|
|
2497
|
+
}
|
|
2498
|
+
catch (e) {
|
|
2499
|
+
await this.checkError(e);
|
|
2500
|
+
}
|
|
2501
|
+
return null;
|
|
2445
2502
|
}
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
const alignTo5minFloor = (date) => {
|
|
2450
|
-
const minutes = date.getMinutes();
|
|
2451
|
-
const alignedMinutes = Math.floor(minutes / 5) * 5;
|
|
2452
|
-
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), alignedMinutes, 0, 0);
|
|
2453
|
-
};
|
|
2454
|
-
this.refreshManager.period = {
|
|
2455
|
-
begin: alignTo5minFloor(this.periodBegin),
|
|
2456
|
-
end: alignTo5minFloor(this.periodEnd),
|
|
2503
|
+
async putRadar(radarNode) {
|
|
2504
|
+
const data = {
|
|
2505
|
+
name: radarNode.name,
|
|
2457
2506
|
};
|
|
2507
|
+
try {
|
|
2508
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2509
|
+
key: 'radars',
|
|
2510
|
+
relativePath: radarNode.id,
|
|
2511
|
+
verb: 'PUT',
|
|
2512
|
+
data,
|
|
2513
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/radars/',
|
|
2514
|
+
});
|
|
2515
|
+
return new RadarNode(resp.data);
|
|
2516
|
+
}
|
|
2517
|
+
catch (e) {
|
|
2518
|
+
await this.checkError(e);
|
|
2519
|
+
}
|
|
2458
2520
|
}
|
|
2459
|
-
async
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2521
|
+
async getLonelyRadars(rains) {
|
|
2522
|
+
try {
|
|
2523
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2524
|
+
key: 'radars',
|
|
2525
|
+
verb: 'GET',
|
|
2526
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/radars',
|
|
2527
|
+
});
|
|
2528
|
+
const lonelyRadars = [];
|
|
2529
|
+
const radars = resp.data.radars;
|
|
2530
|
+
radars.forEach((radar) => {
|
|
2531
|
+
let found = false;
|
|
2532
|
+
rains.forEach((rain) => {
|
|
2533
|
+
const rdId = rain.getLink(RadarNode.TYPE).getId();
|
|
2534
|
+
if (rdId === radar.id) {
|
|
2535
|
+
found = true;
|
|
2536
|
+
}
|
|
2537
|
+
});
|
|
2538
|
+
if (!found) {
|
|
2539
|
+
lonelyRadars.push(new RadarNode(radar));
|
|
2540
|
+
}
|
|
2541
|
+
});
|
|
2542
|
+
return lonelyRadars;
|
|
2467
2543
|
}
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2544
|
+
catch (e) {
|
|
2545
|
+
await this.checkError(e);
|
|
2546
|
+
}
|
|
2547
|
+
return [];
|
|
2471
2548
|
}
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
this.showFullError = false;
|
|
2496
|
-
this.showQualityModal = false;
|
|
2497
|
-
this.qualityIndicators = [];
|
|
2498
|
-
this.qualityIndicatorsLoading = false;
|
|
2499
|
-
this.compareManager?.cleanAll();
|
|
2500
|
-
this.refreshManager?.cleanAll();
|
|
2501
|
-
}
|
|
2502
|
-
async init() {
|
|
2503
|
-
this.cleanAll();
|
|
2504
|
-
this.updateCachedValues();
|
|
2505
|
-
await this.initRain();
|
|
2506
|
-
this.cdr.markForCheck();
|
|
2507
|
-
}
|
|
2508
|
-
async initRain() {
|
|
2509
|
-
if (!this.rainNode) {
|
|
2510
|
-
return;
|
|
2549
|
+
async getRainTimeframe(rainId, begin, end, forced = false, provider = 'Raain', timeStepInMinutes = 10, windowInMinutes = 0) {
|
|
2550
|
+
try {
|
|
2551
|
+
const params = {
|
|
2552
|
+
format: 'timeframeCumulative',
|
|
2553
|
+
provider,
|
|
2554
|
+
timeStepInMinutes: String(timeStepInMinutes),
|
|
2555
|
+
begin: begin?.toISOString(),
|
|
2556
|
+
end: end?.toISOString(),
|
|
2557
|
+
};
|
|
2558
|
+
if (forced) {
|
|
2559
|
+
params.forced = 'true';
|
|
2560
|
+
}
|
|
2561
|
+
params.windowInMinutes = String(windowInMinutes);
|
|
2562
|
+
const queryString = BuildQueryString(params);
|
|
2563
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2564
|
+
key: 'rains',
|
|
2565
|
+
verb: 'GET',
|
|
2566
|
+
relativePath: rainId + (queryString ? '?' + queryString : ''),
|
|
2567
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
2568
|
+
});
|
|
2569
|
+
const rainNode = new RainNode(resp.data.timeframeCumulative);
|
|
2570
|
+
rainNode.name += '.radar.timeframeCumulative';
|
|
2571
|
+
return rainNode;
|
|
2511
2572
|
}
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
this.compareManager.rainNode = this.rainNode;
|
|
2515
|
-
// Load providers and set on refreshManager
|
|
2516
|
-
await this.loadProviders();
|
|
2517
|
-
this.refreshManager.setMethods(this.onRefreshInProgress.bind(this), this.onRefreshDone.bind(this), this.onFetchDone.bind(this));
|
|
2518
|
-
const center = this.rainNode.getCenter();
|
|
2519
|
-
this.coordinates = new MapLatLng(center.lat, center.lng);
|
|
2520
|
-
this.teamNode = await this.profileService.getTeam(this.rainNode.getLink(TeamNode.TYPE).getId());
|
|
2521
|
-
if (this.periodBegin && this.periodEnd) {
|
|
2522
|
-
this.updateRefreshManagerPeriod();
|
|
2523
|
-
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
2573
|
+
catch (e) {
|
|
2574
|
+
await this.checkError(e);
|
|
2524
2575
|
}
|
|
2576
|
+
return null;
|
|
2525
2577
|
}
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2578
|
+
// === Rains ===
|
|
2579
|
+
async getRains(name) {
|
|
2580
|
+
const rains = [];
|
|
2581
|
+
const params = {};
|
|
2582
|
+
if (name) {
|
|
2583
|
+
params.name = name;
|
|
2584
|
+
}
|
|
2585
|
+
const queryString = BuildQueryString(params);
|
|
2586
|
+
try {
|
|
2587
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2588
|
+
key: 'rains',
|
|
2589
|
+
verb: 'GET',
|
|
2590
|
+
relativePath: queryString ? '?' + queryString : '',
|
|
2591
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
2592
|
+
});
|
|
2593
|
+
for (const rain of resp.data.rains) {
|
|
2594
|
+
rains.push(new RainNode(rain));
|
|
2542
2595
|
}
|
|
2543
2596
|
}
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
if (timeframeContainers) {
|
|
2547
|
-
this.timeframeContainers = timeframeContainers;
|
|
2597
|
+
catch (e) {
|
|
2598
|
+
await this.checkError(e);
|
|
2548
2599
|
}
|
|
2549
|
-
|
|
2600
|
+
return rains;
|
|
2550
2601
|
}
|
|
2551
|
-
async
|
|
2552
|
-
|
|
2602
|
+
async getRain(id) {
|
|
2603
|
+
try {
|
|
2604
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2605
|
+
key: 'rains',
|
|
2606
|
+
relativePath: id,
|
|
2607
|
+
verb: 'GET',
|
|
2608
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains/',
|
|
2609
|
+
});
|
|
2610
|
+
return new RainNode(resp.data);
|
|
2611
|
+
}
|
|
2612
|
+
catch (e) {
|
|
2613
|
+
await this.checkError(e);
|
|
2614
|
+
}
|
|
2615
|
+
return null;
|
|
2553
2616
|
}
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
RaainDetailsComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: RaainDetailsComponent, selector: "raain-details", inputs: { toggleAdmin: "toggleAdmin", rainNode: "rainNode", compareManager: "compareManager", refreshManager: "refreshManager", profileService: "profileService", radarService: "radarService" }, usesOnChanges: true, ngImport: i0, template: "<!-- Main content container -->\n<div *ngIf=\"rainNode\" class=\"raain-details-container\">\n\n <!-- Period selection section -->\n <ion-card class=\"period-card\">\n <ion-card-content>\n <div class=\"period-controls\">\n <div class=\"period-row\">\n\n <ion-button (click)=\"toggleHistory = !toggleHistory; onEnableCountHistoryTab(rainNode)\"\n fill=\"outline\">\n <ion-icon name=\"calendar-clear-outline\" slot=\"start\"></ion-icon>\n <ion-icon [name]=\"toggleHistory ? 'chevron-down' : 'chevron-forward'\" slot=\"end\"></ion-icon>\n </ion-button>\n\n <div class=\"period-start ion-hide-md-down\">\n <input (change)=\"onPeriodBeginChange($event)\"\n [disabled]=\"toggleCumulative\"\n [value]=\"periodBeginAsString\"\n class=\"datetime-input\"\n type=\"datetime-local\">\n </div>\n\n <div class=\"period-duration ion-hide-md-down\">\n <ion-select (ionDismiss)=\"onPeriodDurationChange($event)\"\n [(ngModel)]=\"periodDurationAsString\"\n class=\"duration-select\"\n id=\"periodDuration\"\n interface=\"popover\">\n <ion-select-option value=\"0.25\">15 minutes</ion-select-option>\n <ion-select-option value=\"0.5\">30 minutes</ion-select-option>\n <ion-select-option value=\"1\">1 hour</ion-select-option>\n <ion-select-option value=\"2\">2 hours</ion-select-option>\n <ion-select-option value=\"3\">3 hours</ion-select-option>\n <ion-select-option value=\"4\">4 hours</ion-select-option>\n <ion-select-option value=\"5\">5 hours</ion-select-option>\n <ion-select-option value=\"6\">6 hours</ion-select-option>\n <ion-select-option value=\"8\">8 hours</ion-select-option>\n <ion-select-option value=\"10\">10 hours</ion-select-option>\n <ion-select-option value=\"12\">12 hours</ion-select-option>\n <ion-select-option *ngIf=\"isAdmin\" value=\"24\">24 hours</ion-select-option>\n </ion-select>\n </div>\n\n <div class=\"period-start ion-hide-md-down\">\n <input (change)=\"onPeriodEndChange($event)\"\n [disabled]=\"!toggleCumulative\"\n [value]=\"periodEndAsString\"\n class=\"datetime-input\"\n type=\"datetime-local\">\n </div>\n\n <div class=\"toggle-cumulative\">\n <ion-label [class.text-primary]=\"toggleCumulative\">\n {{ toggleCumulative ? 'Cumulative' : 'Granular' }}\n </ion-label>\n <ion-toggle (ionChange)=\"toggleCumulativeChanged($event)\"\n [(ngModel)]=\"toggleCumulative\"\n [checked]=\"toggleCumulative\">\n </ion-toggle>\n </div>\n </div>\n\n <!-- Hidden label for change detection (uncomment to debug)\n <div class=\"hidden-label\">{{ onChangeDetectionTest(rainNode) }}</div>\n -->\n </div>\n\n <!-- Historical map section -->\n <div *ngIf=\"toggleHistory\" class=\"period-controls\">\n <raain-date-dynamic (changedDate)=\"onDateChangeInCount($event)\"\n [currentHeight]=\"300\"\n [fetchData]=\"fetchDataWrapper\"\n [points]=\"countPoints\">\n </raain-date-dynamic>\n </div>\n </ion-card-content>\n </ion-card>\n\n <!-- Map performance -->\n <ion-grid class=\"map-performance\">\n <ion-row id=\"progressAndRefresh\">\n <ion-col class=\"provider-selection\" size=\"12\" size-md=\"6\">\n <ion-button (click)=\"openQualityModal()\" class=\"quality-info-button\" fill=\"clear\">\n {{ refreshManager.rainComputationMapVersion }}\n <ion-icon name=\"help-circle-outline\" slot=\"end\"></ion-icon>\n </ion-button>\n <div *ngIf=\"availableProviders.length > 0 || availableTimeSteps.length > 0\" class=\"selection-row\">\n <ion-select (ionChange)=\"onProviderChanged($event)\"\n [value]=\"selectedProvider\"\n interface=\"popover\"\n label=\"Provider\"\n placeholder=\"Select Provider\">\n <ion-select-option *ngFor=\"let provider of availableProviders\" [value]=\"provider\">\n {{ provider }}\n </ion-select-option>\n </ion-select>\n\n <ion-select (ionChange)=\"onTimeStepChanged($event)\"\n [value]=\"selectedTimeStep\"\n interface=\"popover\"\n label=\"Time Step\"\n placeholder=\"Select Time Step\">\n <ion-select-option *ngFor=\"let step of availableTimeSteps\" [value]=\"step\">\n {{ step }} min\n </ion-select-option>\n </ion-select>\n </div>\n </ion-col>\n <ion-col class=\"ion-text-right\" size=\"12\" size-md=\"6\">\n <ion-label class=\"ion-margin-end\">\n <span *ngIf=\"percentOfComputations\">\n {{ percentOfComputations }}% Images\n <i *ngIf=\"countsPeriod.progress\" class=\"progress-indicator\">\n In Progress: {{ countsPeriod.progress }}...\n </i>\n </span>\n <span *ngIf=\"!percentOfComputations\">\n No image available\n </span>\n\n </ion-label>\n\n <ion-button (click)=\"refreshMap()\" [disabled]=\"refreshInProgress\" class=\"refresh-button\">\n Refresh Map\n </ion-button>\n </ion-col>\n </ion-row>\n\n <!-- status update row -->\n <ion-row>\n <!-- Progress col -->\n <ion-progress-bar\n [buffer]=\"(countsPeriod.progress / ((timeframeDates?.length || 10) +3))+0.01\"\n [style.visibility]=\"refreshInProgress ? 'visible' : 'hidden'\"\n [value]=\"countsPeriod.progress / ((timeframeDates?.length || 10) +3)\"\n color=\"primary\">\n </ion-progress-bar>\n\n <!-- Error col -->\n <ion-col (click)=\"showFullError = !showFullError\" *ngIf=\"refreshManager.lastError\" class=\"error-row\"\n size=\"12\">\n <div class=\"error-content\">\n <ion-icon class=\"error-icon\" name=\"warning-outline\"></ion-icon>\n <span [class.expanded]=\"showFullError\" class=\"error-text\">\n {{ showFullError ? refreshManager.lastError : truncatedError }}\n </span>\n <ion-icon [name]=\"showFullError ? 'chevron-up' : 'chevron-down'\" class=\"error-caret\"></ion-icon>\n </div>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Map section -->\n <ion-card class=\"map-card\">\n <ion-card-content class=\"map-content\">\n <ion-grid>\n <ion-row *ngIf=\"toggleMap && percentOfImages\">\n <!-- Map component -->\n <ion-col class=\"map-column\" size-lg=\"7\" size-md=\"12\">\n <div class=\"map-container\">\n <raain-map #raainMapRef\n (changedDate)=\"onDateChangeInMap($event)\"\n (changedSum)=\"onSumChangeInMap($event)\"\n (selectedMarker)=\"onGaugeSelectInMap($event)\"\n [coordinates]=\"coordinates\"\n [cumulativeDurationInMinutes]=\"cumulativeDurationInMinutes\"\n [currentHeight]=\"500\"\n [defaultDate]=\"dateShown\"\n [markers]=\"{\n borders,\n gauges: compareManager.gaugesInMap,\n gaugesInCompare: compareManager.gaugesInCompare,\n selectedGauges: compareManager.selectedGauges,\n pixels: compareManager.selectedPixels,\n pixelsSolution: compareManager.pixelsSolutions?.length ? compareManager.pixelsSolutions[0] : [],\n speeds: compareManager.speeds\n }\"\n [showCumulative]=\"toggleCumulative\"\n [showVisiblePixelMarkers]=\"showPixelMarkers\"\n [sumFn]=\"refreshManager.sumFn\"\n [sumValues]=\"refreshManager.sumValues\"\n [timeframeContainers]=\"timeframeContainers\"\n [timeframeDates]=\"timeframeDates\">\n </raain-map>\n </div>\n\n <div class=\"data-column\">\n <!-- Technical details (collapsible for cleaner UI) -->\n <details class=\"technical-details\">\n <summary>Image Details</summary>\n <div class=\"details-content\">\n <div class=\"detail-row\">\n <span class=\"detail-label\">Computed:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapDoneDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Version:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapVersion }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Launched by:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapLaunchedBy }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Time spent:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapTimeSpentInMs }}\n ms</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Date:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapDate?.toISOString() }}\n | {{ refreshManager.rainComputationMapDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Water in the map:</span>\n <span class=\"detail-value\">{{ sumDetails }}</span>\n <ion-toggle\n (ionChange)=\"onTogglePixelMarkers()\"\n [(ngModel)]=\"showPixelMarkers\"\n style=\"margin-left: 8px; transform: scale(0.7);\">\n </ion-toggle>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Source DBZ min:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapOriginalMin }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Source DBZ max:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapOriginalMax }}</span>\n </div>\n </div>\n </details>\n </div>\n </ion-col>\n\n <!-- Data panel -->\n <ion-col *ngIf=\"!!compareManager.compareVersion\" class=\"data-column\" size-lg=\"5\" size-md=\"12\">\n <div class=\"data-panel\">\n <!-- Compare stack component -->\n <div class=\"compare-stack\">\n <raain-compare-stack\n (selectedPoint)=\"onGaugeSelectInCompare($event)\"\n [compareManager]=\"compareManager\"\n [cumulative]=\"toggleCumulative\">\n </raain-compare-stack>\n </div>\n\n <!-- Technical details (collapsible for cleaner UI) -->\n <details class=\"technical-details\">\n <summary>Compare Details</summary>\n <div class=\"details-content\">\n <div [ngClass]=\"{'warning': refreshManager.rainComputationMapDoneDate?.getTime() > compareManager.currentQualityDoneDate?.getTime() + 60000}\"\n class=\"detail-row\">\n <span class=\"detail-label\">Computed:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityDoneDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Version:</span>\n <span class=\"detail-value\">{{ compareManager.compareVersion }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Launched by:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityLaunchedBy }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Time spent:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityTimeSpentInMs }}\n ms</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Gauges:</span>\n <span class=\"detail-value\">{{ compareManager.gaugesInCompare.length }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Points:</span>\n <span class=\"detail-value\">{{ compareManager.globalComparePoints.length }}</span>\n </div>\n </div>\n </details>\n </div>\n </ion-col>\n </ion-row>\n <ion-row>\n <!-- Bottom progress bar -->\n <ion-progress-bar *ngIf=\"refreshInProgress\"\n [buffer]=\"(countsPeriod.progress / ((timeframeDates?.length || 10) +3))+0.01\"\n [value]=\"countsPeriod.progress / ((timeframeDates?.length || 10) +3)\"\n color=\"primary\">\n </ion-progress-bar>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n\n <!-- Gauge values section -->\n <ion-card *ngIf=\"gaugeSelectedPoints.length && gaugeSelectedPoints[0].values.length\" class=\"gauge-card\">\n <ion-card-header>\n <ion-card-title>\n <ion-icon name=\"analytics-outline\"></ion-icon>\n Selected Gauge Data\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <raain-date-focus\n [currentHeight]=\"300\"\n [focusDate]=\"periodBegin\"\n [focusRange]=\"DateRange.DAY\"\n [points]=\"gaugeSelectedPoints\"\n [withoutAll]=\"true\">\n </raain-date-focus>\n </ion-card-content>\n </ion-card>\n\n <!-- Quality Performance Modal -->\n <div (click)=\"closeQualityModal()\" *ngIf=\"showQualityModal\" class=\"quality-modal-overlay\">\n <div (click)=\"$event.stopPropagation()\" class=\"quality-modal-content\">\n <div class=\"quality-modal-header\">\n <h2>Model Quality Performance</h2>\n <ion-button (click)=\"closeQualityModal()\" fill=\"clear\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </div>\n <div class=\"quality-modal-body\">\n <div *ngIf=\"qualityIndicatorsLoading\" class=\"quality-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n <span>Loading indicators...</span>\n </div>\n <div *ngIf=\"!qualityIndicatorsLoading && qualityIndicators.length === 0\" class=\"quality-empty\">\n No quality indicators available for this year.\n </div>\n <table *ngIf=\"!qualityIndicatorsLoading && qualityIndicators.length > 0\" class=\"quality-table\">\n <thead>\n <tr>\n <th>Model</th>\n <th>Compare</th>\n <th>Gauges</th>\n <th>Period</th>\n <th>Avg Quality</th>\n <th>Updated</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let indicator of qualityIndicators\">\n <td>{{ indicator.computingVersion }}</td>\n <td>{{ indicator.qualityVersion }}</td>\n <td>{{ indicator.provider }}<br>{{ indicator.timeStepInMinutes }} min</td>\n <td>{{ formatDate(indicator.startDate) }}<br>{{ formatDate(indicator.endDate) }}</td>\n <td>{{ indicator.averageQuality | number:'1.2-2' }}</td>\n <td>{{ formatDate(indicator.lastUpdatedAt) }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n </div>\n\n</div>\n", styles: [".raain-details-container{max-width:var(--app-max-width);margin:0 auto;padding:0 0 24px}.raain-details-card{width:100%;margin-bottom:20px}.raain-details-card ion-card-header{border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.raain-details-card ion-card-header ion-card-title{display:flex;align-items:center}.raain-details-card ion-card-header ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary)}.node-info-card{background-color:var(--ion-color-light)}.node-info-card .node-header{display:flex;align-items:center}.node-info-card .node-header .node-status{margin-right:16px}.node-info-card .node-header .node-status ion-icon{font-size:24px}.node-info-card .node-header .node-titles{flex:1}.node-info-card .node-header .node-titles ion-card-title{margin:0;font-size:1.4rem;font-weight:600;color:var(--ion-color-dark)}.node-info-card .node-header .node-titles ion-card-subtitle{padding-left:0;margin:4px 0 0;font-size:.9rem;color:var(--ion-color-medium)}.count-map-card,.period-card{background-color:var(--ion-color-light)}.period-card ion-card-title{display:flex;align-items:center}.period-card ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary);font-size:20px}.period-row{display:flex;flex-wrap:wrap;align-items:center;gap:16px;position:relative}.now-button{min-width:100px}#all-dates{display:flex;align-items:center;margin-left:auto}#all-dates .toggle-label{margin-right:8px;white-space:nowrap}.refresh-button ion-icon{margin-right:4px;color:var(--ion-color-light)}.provider-selection{display:flex;align-items:center}.quality-info-button{--padding-start: 8px;--padding-end: 8px;font-size:.85rem;color:var(--ion-color-medium)}.quality-info-button ion-icon{font-size:18px;margin-left:4px}.selection-row{display:flex;flex-direction:row;align-items:center;gap:12px}.selection-row ion-select{flex:0 0 auto;min-width:100px;margin-bottom:0;padding:4px 8px;border:1px solid rgba(var(--ion-color-medium-rgb),.3);border-radius:var(--ion-border-radius);background-color:var(--ion-color-light)}.period-start,.period-duration{display:flex;align-items:center}.toggle-cumulative{display:flex;align-items:center;gap:8px;margin-left:auto}.toggle-cumulative .text-primary{color:var(--ion-color-primary);font-weight:600}.hidden-label{display:none}.datetime-input,#periodDuration,.duration-select{padding:8px 12px;border:1px solid rgba(var(--ion-color-medium-rgb),.3);border-radius:var(--ion-border-radius);background-color:var(--ion-color-light);font-family:var(--ion-font-family)}.datetime-input:focus,#periodDuration:focus,.duration-select:focus{outline:none;border-color:var(--ion-color-primary)}#periodDuration,.duration-select{min-width:150px}.gauge-card{background-color:var(--ion-color-light)}.gauge-card ion-card-title{display:flex;align-items:center}.gauge-card ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary);font-size:20px}.error-row{cursor:pointer;background-color:rgba(var(--ion-color-danger-rgb),.1);border-left:3px solid var(--ion-color-danger);margin-top:8px;border-radius:4px;transition:background-color .2s ease}.error-row:hover{background-color:rgba(var(--ion-color-danger-rgb),.15)}.error-row .error-content{display:flex;align-items:flex-start;padding:8px 12px;gap:8px}.error-row .error-icon{color:var(--ion-color-danger);font-size:18px;flex-shrink:0;margin-top:2px}.error-row .error-text{flex:1;color:var(--ion-color-danger-shade);font-size:.9rem;word-break:break-word}.error-row .error-text.expanded{white-space:pre-wrap}.error-row .error-caret{color:var(--ion-color-danger);font-size:16px;flex-shrink:0;margin-top:2px}raain-compare-stack{width:100%;display:block}@media (max-width: 768px){.period-row{flex-direction:row;justify-content:space-between;align-items:center}#all-dates{margin-left:auto;padding-left:8px}#all-dates .toggle-label{font-size:.9rem}.map-header{flex-direction:row;justify-content:space-between;align-items:center;gap:16px}}.quality-modal-overlay{position:fixed;inset:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:9999}.quality-modal-content{background-color:var(--ion-background-color, #fff);border-radius:12px;width:95%;max-width:900px;max-height:80vh;overflow:auto;box-shadow:0 4px 24px #0003}.quality-modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.quality-modal-header h2{margin:0;font-size:1.25rem;font-weight:600;color:var(--ion-color-dark)}.quality-modal-header ion-button{--padding-start: 8px;--padding-end: 8px}.quality-modal-body{padding:20px}.quality-table{width:100%;border-collapse:collapse;margin-top:16px;table-layout:fixed}.quality-table th,.quality-table td{width:16.66%;padding:12px 16px;text-align:center;vertical-align:top;border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.quality-table th{background-color:rgba(var(--ion-color-primary-rgb),.1);font-weight:600;color:var(--ion-color-dark)}.quality-table td{color:var(--ion-color-dark-tint);font-size:.7rem}.quality-table tbody tr:hover{background-color:rgba(var(--ion-color-primary-rgb),.05)}.quality-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;color:var(--ion-color-medium)}.quality-loading ion-spinner{margin-bottom:12px}.quality-empty{text-align:center;padding:40px 20px;color:var(--ion-color-medium);font-style:italic}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i4.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i4.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i4.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i4.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i4.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i4.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i4.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i4.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i4.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i4.IonProgressBar, selector: "ion-progress-bar", inputs: ["buffer", "color", "mode", "reversed", "type", "value"] }, { kind: "component", type: i4.IonRow, selector: "ion-row" }, { kind: "component", type: i4.IonSelect, selector: "ion-select", inputs: ["cancelText", "compareWith", "disabled", "interface", "interfaceOptions", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "value"] }, { kind: "component", type: i4.IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: i4.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: i4.IonToggle, selector: "ion-toggle", inputs: ["checked", "color", "disabled", "mode", "name", "value"] }, { kind: "directive", type: i4.BooleanValueAccessor, selector: "ion-checkbox,ion-toggle" }, { kind: "directive", type: i4.SelectValueAccessor, selector: "ion-range, ion-select, ion-radio-group, ion-segment, ion-datetime" }, { kind: "component", type: RaainMapComponent, selector: "raain-map", inputs: ["coordinates", "markers", "timeframeContainers", "autoplay", "showMarkers", "showSpeedMarkers", "showVisiblePixelMarkers", "showCumulative", "cumulativeDurationInMinutes", "currentHeight", "timeframeDates", "defaultDate", "sumValues", "sumFn"], outputs: ["selectedMarker", "changedDate", "changedSum"] }, { kind: "component", type: RaainCompareStackComponent, selector: "raain-compare-stack", inputs: ["compareManager", "currentHeight", "cumulative"], outputs: ["selectedPoint"] }, { kind: "component", type: RaainDateFocusComponent, selector: "raain-date-focus", inputs: ["points", "focusDate", "focusRange", "withoutAll", "currentHeight"] }, { kind: "component", type: RaainDateDynamicComponent, selector: "raain-date-dynamic", inputs: ["points", "focusDate", "focusRange", "withoutAll", "currentHeight", "fetchData"], outputs: ["changedDate"] }, { kind: "pipe", type: i2.DecimalPipe, name: "number" }, { kind: "pipe", type: i2.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
args: [{ selector: 'raain-details', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Main content container -->\n<div *ngIf=\"rainNode\" class=\"raain-details-container\">\n\n <!-- Period selection section -->\n <ion-card class=\"period-card\">\n <ion-card-content>\n <div class=\"period-controls\">\n <div class=\"period-row\">\n\n <ion-button (click)=\"toggleHistory = !toggleHistory; onEnableCountHistoryTab(rainNode)\"\n fill=\"outline\">\n <ion-icon name=\"calendar-clear-outline\" slot=\"start\"></ion-icon>\n <ion-icon [name]=\"toggleHistory ? 'chevron-down' : 'chevron-forward'\" slot=\"end\"></ion-icon>\n </ion-button>\n\n <div class=\"period-start ion-hide-md-down\">\n <input (change)=\"onPeriodBeginChange($event)\"\n [disabled]=\"toggleCumulative\"\n [value]=\"periodBeginAsString\"\n class=\"datetime-input\"\n type=\"datetime-local\">\n </div>\n\n <div class=\"period-duration ion-hide-md-down\">\n <ion-select (ionDismiss)=\"onPeriodDurationChange($event)\"\n [(ngModel)]=\"periodDurationAsString\"\n class=\"duration-select\"\n id=\"periodDuration\"\n interface=\"popover\">\n <ion-select-option value=\"0.25\">15 minutes</ion-select-option>\n <ion-select-option value=\"0.5\">30 minutes</ion-select-option>\n <ion-select-option value=\"1\">1 hour</ion-select-option>\n <ion-select-option value=\"2\">2 hours</ion-select-option>\n <ion-select-option value=\"3\">3 hours</ion-select-option>\n <ion-select-option value=\"4\">4 hours</ion-select-option>\n <ion-select-option value=\"5\">5 hours</ion-select-option>\n <ion-select-option value=\"6\">6 hours</ion-select-option>\n <ion-select-option value=\"8\">8 hours</ion-select-option>\n <ion-select-option value=\"10\">10 hours</ion-select-option>\n <ion-select-option value=\"12\">12 hours</ion-select-option>\n <ion-select-option *ngIf=\"isAdmin\" value=\"24\">24 hours</ion-select-option>\n </ion-select>\n </div>\n\n <div class=\"period-start ion-hide-md-down\">\n <input (change)=\"onPeriodEndChange($event)\"\n [disabled]=\"!toggleCumulative\"\n [value]=\"periodEndAsString\"\n class=\"datetime-input\"\n type=\"datetime-local\">\n </div>\n\n <div class=\"toggle-cumulative\">\n <ion-label [class.text-primary]=\"toggleCumulative\">\n {{ toggleCumulative ? 'Cumulative' : 'Granular' }}\n </ion-label>\n <ion-toggle (ionChange)=\"toggleCumulativeChanged($event)\"\n [(ngModel)]=\"toggleCumulative\"\n [checked]=\"toggleCumulative\">\n </ion-toggle>\n </div>\n </div>\n\n <!-- Hidden label for change detection (uncomment to debug)\n <div class=\"hidden-label\">{{ onChangeDetectionTest(rainNode) }}</div>\n -->\n </div>\n\n <!-- Historical map section -->\n <div *ngIf=\"toggleHistory\" class=\"period-controls\">\n <raain-date-dynamic (changedDate)=\"onDateChangeInCount($event)\"\n [currentHeight]=\"300\"\n [fetchData]=\"fetchDataWrapper\"\n [points]=\"countPoints\">\n </raain-date-dynamic>\n </div>\n </ion-card-content>\n </ion-card>\n\n <!-- Map performance -->\n <ion-grid class=\"map-performance\">\n <ion-row id=\"progressAndRefresh\">\n <ion-col class=\"provider-selection\" size=\"12\" size-md=\"6\">\n <ion-button (click)=\"openQualityModal()\" class=\"quality-info-button\" fill=\"clear\">\n {{ refreshManager.rainComputationMapVersion }}\n <ion-icon name=\"help-circle-outline\" slot=\"end\"></ion-icon>\n </ion-button>\n <div *ngIf=\"availableProviders.length > 0 || availableTimeSteps.length > 0\" class=\"selection-row\">\n <ion-select (ionChange)=\"onProviderChanged($event)\"\n [value]=\"selectedProvider\"\n interface=\"popover\"\n label=\"Provider\"\n placeholder=\"Select Provider\">\n <ion-select-option *ngFor=\"let provider of availableProviders\" [value]=\"provider\">\n {{ provider }}\n </ion-select-option>\n </ion-select>\n\n <ion-select (ionChange)=\"onTimeStepChanged($event)\"\n [value]=\"selectedTimeStep\"\n interface=\"popover\"\n label=\"Time Step\"\n placeholder=\"Select Time Step\">\n <ion-select-option *ngFor=\"let step of availableTimeSteps\" [value]=\"step\">\n {{ step }} min\n </ion-select-option>\n </ion-select>\n </div>\n </ion-col>\n <ion-col class=\"ion-text-right\" size=\"12\" size-md=\"6\">\n <ion-label class=\"ion-margin-end\">\n <span *ngIf=\"percentOfComputations\">\n {{ percentOfComputations }}% Images\n <i *ngIf=\"countsPeriod.progress\" class=\"progress-indicator\">\n In Progress: {{ countsPeriod.progress }}...\n </i>\n </span>\n <span *ngIf=\"!percentOfComputations\">\n No image available\n </span>\n\n </ion-label>\n\n <ion-button (click)=\"refreshMap()\" [disabled]=\"refreshInProgress\" class=\"refresh-button\">\n Refresh Map\n </ion-button>\n </ion-col>\n </ion-row>\n\n <!-- status update row -->\n <ion-row>\n <!-- Progress col -->\n <ion-progress-bar\n [buffer]=\"(countsPeriod.progress / ((timeframeDates?.length || 10) +3))+0.01\"\n [style.visibility]=\"refreshInProgress ? 'visible' : 'hidden'\"\n [value]=\"countsPeriod.progress / ((timeframeDates?.length || 10) +3)\"\n color=\"primary\">\n </ion-progress-bar>\n\n <!-- Error col -->\n <ion-col (click)=\"showFullError = !showFullError\" *ngIf=\"refreshManager.lastError\" class=\"error-row\"\n size=\"12\">\n <div class=\"error-content\">\n <ion-icon class=\"error-icon\" name=\"warning-outline\"></ion-icon>\n <span [class.expanded]=\"showFullError\" class=\"error-text\">\n {{ showFullError ? refreshManager.lastError : truncatedError }}\n </span>\n <ion-icon [name]=\"showFullError ? 'chevron-up' : 'chevron-down'\" class=\"error-caret\"></ion-icon>\n </div>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Map section -->\n <ion-card class=\"map-card\">\n <ion-card-content class=\"map-content\">\n <ion-grid>\n <ion-row *ngIf=\"toggleMap && percentOfImages\">\n <!-- Map component -->\n <ion-col class=\"map-column\" size-lg=\"7\" size-md=\"12\">\n <div class=\"map-container\">\n <raain-map #raainMapRef\n (changedDate)=\"onDateChangeInMap($event)\"\n (changedSum)=\"onSumChangeInMap($event)\"\n (selectedMarker)=\"onGaugeSelectInMap($event)\"\n [coordinates]=\"coordinates\"\n [cumulativeDurationInMinutes]=\"cumulativeDurationInMinutes\"\n [currentHeight]=\"500\"\n [defaultDate]=\"dateShown\"\n [markers]=\"{\n borders,\n gauges: compareManager.gaugesInMap,\n gaugesInCompare: compareManager.gaugesInCompare,\n selectedGauges: compareManager.selectedGauges,\n pixels: compareManager.selectedPixels,\n pixelsSolution: compareManager.pixelsSolutions?.length ? compareManager.pixelsSolutions[0] : [],\n speeds: compareManager.speeds\n }\"\n [showCumulative]=\"toggleCumulative\"\n [showVisiblePixelMarkers]=\"showPixelMarkers\"\n [sumFn]=\"refreshManager.sumFn\"\n [sumValues]=\"refreshManager.sumValues\"\n [timeframeContainers]=\"timeframeContainers\"\n [timeframeDates]=\"timeframeDates\">\n </raain-map>\n </div>\n\n <div class=\"data-column\">\n <!-- Technical details (collapsible for cleaner UI) -->\n <details class=\"technical-details\">\n <summary>Image Details</summary>\n <div class=\"details-content\">\n <div class=\"detail-row\">\n <span class=\"detail-label\">Computed:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapDoneDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Version:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapVersion }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Launched by:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapLaunchedBy }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Time spent:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapTimeSpentInMs }}\n ms</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Date:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapDate?.toISOString() }}\n | {{ refreshManager.rainComputationMapDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Water in the map:</span>\n <span class=\"detail-value\">{{ sumDetails }}</span>\n <ion-toggle\n (ionChange)=\"onTogglePixelMarkers()\"\n [(ngModel)]=\"showPixelMarkers\"\n style=\"margin-left: 8px; transform: scale(0.7);\">\n </ion-toggle>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Source DBZ min:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapOriginalMin }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Source DBZ max:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapOriginalMax }}</span>\n </div>\n </div>\n </details>\n </div>\n </ion-col>\n\n <!-- Data panel -->\n <ion-col *ngIf=\"!!compareManager.compareVersion\" class=\"data-column\" size-lg=\"5\" size-md=\"12\">\n <div class=\"data-panel\">\n <!-- Compare stack component -->\n <div class=\"compare-stack\">\n <raain-compare-stack\n (selectedPoint)=\"onGaugeSelectInCompare($event)\"\n [compareManager]=\"compareManager\"\n [cumulative]=\"toggleCumulative\">\n </raain-compare-stack>\n </div>\n\n <!-- Technical details (collapsible for cleaner UI) -->\n <details class=\"technical-details\">\n <summary>Compare Details</summary>\n <div class=\"details-content\">\n <div [ngClass]=\"{'warning': refreshManager.rainComputationMapDoneDate?.getTime() > compareManager.currentQualityDoneDate?.getTime() + 60000}\"\n class=\"detail-row\">\n <span class=\"detail-label\">Computed:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityDoneDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Version:</span>\n <span class=\"detail-value\">{{ compareManager.compareVersion }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Launched by:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityLaunchedBy }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Time spent:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityTimeSpentInMs }}\n ms</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Gauges:</span>\n <span class=\"detail-value\">{{ compareManager.gaugesInCompare.length }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Points:</span>\n <span class=\"detail-value\">{{ compareManager.globalComparePoints.length }}</span>\n </div>\n </div>\n </details>\n </div>\n </ion-col>\n </ion-row>\n <ion-row>\n <!-- Bottom progress bar -->\n <ion-progress-bar *ngIf=\"refreshInProgress\"\n [buffer]=\"(countsPeriod.progress / ((timeframeDates?.length || 10) +3))+0.01\"\n [value]=\"countsPeriod.progress / ((timeframeDates?.length || 10) +3)\"\n color=\"primary\">\n </ion-progress-bar>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n\n <!-- Gauge values section -->\n <ion-card *ngIf=\"gaugeSelectedPoints.length && gaugeSelectedPoints[0].values.length\" class=\"gauge-card\">\n <ion-card-header>\n <ion-card-title>\n <ion-icon name=\"analytics-outline\"></ion-icon>\n Selected Gauge Data\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <raain-date-focus\n [currentHeight]=\"300\"\n [focusDate]=\"periodBegin\"\n [focusRange]=\"DateRange.DAY\"\n [points]=\"gaugeSelectedPoints\"\n [withoutAll]=\"true\">\n </raain-date-focus>\n </ion-card-content>\n </ion-card>\n\n <!-- Quality Performance Modal -->\n <div (click)=\"closeQualityModal()\" *ngIf=\"showQualityModal\" class=\"quality-modal-overlay\">\n <div (click)=\"$event.stopPropagation()\" class=\"quality-modal-content\">\n <div class=\"quality-modal-header\">\n <h2>Model Quality Performance</h2>\n <ion-button (click)=\"closeQualityModal()\" fill=\"clear\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </div>\n <div class=\"quality-modal-body\">\n <div *ngIf=\"qualityIndicatorsLoading\" class=\"quality-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n <span>Loading indicators...</span>\n </div>\n <div *ngIf=\"!qualityIndicatorsLoading && qualityIndicators.length === 0\" class=\"quality-empty\">\n No quality indicators available for this year.\n </div>\n <table *ngIf=\"!qualityIndicatorsLoading && qualityIndicators.length > 0\" class=\"quality-table\">\n <thead>\n <tr>\n <th>Model</th>\n <th>Compare</th>\n <th>Gauges</th>\n <th>Period</th>\n <th>Avg Quality</th>\n <th>Updated</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let indicator of qualityIndicators\">\n <td>{{ indicator.computingVersion }}</td>\n <td>{{ indicator.qualityVersion }}</td>\n <td>{{ indicator.provider }}<br>{{ indicator.timeStepInMinutes }} min</td>\n <td>{{ formatDate(indicator.startDate) }}<br>{{ formatDate(indicator.endDate) }}</td>\n <td>{{ indicator.averageQuality | number:'1.2-2' }}</td>\n <td>{{ formatDate(indicator.lastUpdatedAt) }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n </div>\n\n</div>\n", styles: [".raain-details-container{max-width:var(--app-max-width);margin:0 auto;padding:0 0 24px}.raain-details-card{width:100%;margin-bottom:20px}.raain-details-card ion-card-header{border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.raain-details-card ion-card-header ion-card-title{display:flex;align-items:center}.raain-details-card ion-card-header ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary)}.node-info-card{background-color:var(--ion-color-light)}.node-info-card .node-header{display:flex;align-items:center}.node-info-card .node-header .node-status{margin-right:16px}.node-info-card .node-header .node-status ion-icon{font-size:24px}.node-info-card .node-header .node-titles{flex:1}.node-info-card .node-header .node-titles ion-card-title{margin:0;font-size:1.4rem;font-weight:600;color:var(--ion-color-dark)}.node-info-card .node-header .node-titles ion-card-subtitle{padding-left:0;margin:4px 0 0;font-size:.9rem;color:var(--ion-color-medium)}.count-map-card,.period-card{background-color:var(--ion-color-light)}.period-card ion-card-title{display:flex;align-items:center}.period-card ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary);font-size:20px}.period-row{display:flex;flex-wrap:wrap;align-items:center;gap:16px;position:relative}.now-button{min-width:100px}#all-dates{display:flex;align-items:center;margin-left:auto}#all-dates .toggle-label{margin-right:8px;white-space:nowrap}.refresh-button ion-icon{margin-right:4px;color:var(--ion-color-light)}.provider-selection{display:flex;align-items:center}.quality-info-button{--padding-start: 8px;--padding-end: 8px;font-size:.85rem;color:var(--ion-color-medium)}.quality-info-button ion-icon{font-size:18px;margin-left:4px}.selection-row{display:flex;flex-direction:row;align-items:center;gap:12px}.selection-row ion-select{flex:0 0 auto;min-width:100px;margin-bottom:0;padding:4px 8px;border:1px solid rgba(var(--ion-color-medium-rgb),.3);border-radius:var(--ion-border-radius);background-color:var(--ion-color-light)}.period-start,.period-duration{display:flex;align-items:center}.toggle-cumulative{display:flex;align-items:center;gap:8px;margin-left:auto}.toggle-cumulative .text-primary{color:var(--ion-color-primary);font-weight:600}.hidden-label{display:none}.datetime-input,#periodDuration,.duration-select{padding:8px 12px;border:1px solid rgba(var(--ion-color-medium-rgb),.3);border-radius:var(--ion-border-radius);background-color:var(--ion-color-light);font-family:var(--ion-font-family)}.datetime-input:focus,#periodDuration:focus,.duration-select:focus{outline:none;border-color:var(--ion-color-primary)}#periodDuration,.duration-select{min-width:150px}.gauge-card{background-color:var(--ion-color-light)}.gauge-card ion-card-title{display:flex;align-items:center}.gauge-card ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary);font-size:20px}.error-row{cursor:pointer;background-color:rgba(var(--ion-color-danger-rgb),.1);border-left:3px solid var(--ion-color-danger);margin-top:8px;border-radius:4px;transition:background-color .2s ease}.error-row:hover{background-color:rgba(var(--ion-color-danger-rgb),.15)}.error-row .error-content{display:flex;align-items:flex-start;padding:8px 12px;gap:8px}.error-row .error-icon{color:var(--ion-color-danger);font-size:18px;flex-shrink:0;margin-top:2px}.error-row .error-text{flex:1;color:var(--ion-color-danger-shade);font-size:.9rem;word-break:break-word}.error-row .error-text.expanded{white-space:pre-wrap}.error-row .error-caret{color:var(--ion-color-danger);font-size:16px;flex-shrink:0;margin-top:2px}raain-compare-stack{width:100%;display:block}@media (max-width: 768px){.period-row{flex-direction:row;justify-content:space-between;align-items:center}#all-dates{margin-left:auto;padding-left:8px}#all-dates .toggle-label{font-size:.9rem}.map-header{flex-direction:row;justify-content:space-between;align-items:center;gap:16px}}.quality-modal-overlay{position:fixed;inset:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:9999}.quality-modal-content{background-color:var(--ion-background-color, #fff);border-radius:12px;width:95%;max-width:900px;max-height:80vh;overflow:auto;box-shadow:0 4px 24px #0003}.quality-modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.quality-modal-header h2{margin:0;font-size:1.25rem;font-weight:600;color:var(--ion-color-dark)}.quality-modal-header ion-button{--padding-start: 8px;--padding-end: 8px}.quality-modal-body{padding:20px}.quality-table{width:100%;border-collapse:collapse;margin-top:16px;table-layout:fixed}.quality-table th,.quality-table td{width:16.66%;padding:12px 16px;text-align:center;vertical-align:top;border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.quality-table th{background-color:rgba(var(--ion-color-primary-rgb),.1);font-weight:600;color:var(--ion-color-dark)}.quality-table td{color:var(--ion-color-dark-tint);font-size:.7rem}.quality-table tbody tr:hover{background-color:rgba(var(--ion-color-primary-rgb),.05)}.quality-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;color:var(--ion-color-medium)}.quality-loading ion-spinner{margin-bottom:12px}.quality-empty{text-align:center;padding:40px 20px;color:var(--ion-color-medium);font-style:italic}\n"] }]
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2617
|
+
// === Count
|
|
2618
|
+
async getCounts(rainId, options) {
|
|
2619
|
+
try {
|
|
2620
|
+
const params = {
|
|
2621
|
+
range: options.range,
|
|
2622
|
+
begin: options.periodBegin.toISOString(),
|
|
2623
|
+
};
|
|
2624
|
+
const queryString = BuildQueryString(params);
|
|
2625
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2626
|
+
key: 'rains',
|
|
2627
|
+
relativePath: rainId + '/counts' + (queryString ? '?' + queryString : ''),
|
|
2628
|
+
verb: 'GET',
|
|
2629
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains/',
|
|
2630
|
+
});
|
|
2631
|
+
const counts = resp.data.counts.result;
|
|
2632
|
+
const percentImages = [], percentRainy = [], percentQ = [];
|
|
2633
|
+
counts.forEach((c) => {
|
|
2634
|
+
const label = this.setDateComponents(options.periodBegin, c);
|
|
2635
|
+
percentImages.push(new XYType(c.percentImages ?? 0, NaN, NaN, label));
|
|
2636
|
+
percentRainy.push(new XYType(c.percentRainy ?? 0, NaN, NaN, label));
|
|
2637
|
+
percentQ.push(new XYType(c.percentQ ?? 0, NaN, NaN, label));
|
|
2638
|
+
});
|
|
2639
|
+
return {
|
|
2640
|
+
percentImages,
|
|
2641
|
+
percentRainy,
|
|
2642
|
+
percentQ,
|
|
2643
|
+
queueRunning: resp.data.queueRunning,
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
catch (e) {
|
|
2647
|
+
await this.checkError(e);
|
|
2648
|
+
}
|
|
2649
|
+
return null;
|
|
2579
2650
|
}
|
|
2580
|
-
async
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2651
|
+
async getCountsHour(rainId, options) {
|
|
2652
|
+
try {
|
|
2653
|
+
const params = {
|
|
2654
|
+
range: 'hour',
|
|
2655
|
+
begin: options.periodBegin.toISOString(),
|
|
2656
|
+
};
|
|
2657
|
+
const queryString = BuildQueryString(params);
|
|
2658
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2659
|
+
key: 'rains',
|
|
2660
|
+
relativePath: rainId + '/counts' + (queryString ? '?' + queryString : ''),
|
|
2661
|
+
verb: 'GET',
|
|
2662
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains/',
|
|
2663
|
+
});
|
|
2664
|
+
const counts = resp.data.counts.result;
|
|
2665
|
+
const percentImages = [], rainySum = [], rainyCount = [];
|
|
2666
|
+
counts.forEach((c) => {
|
|
2667
|
+
const label = this.setDateComponents(options.periodBegin, c);
|
|
2668
|
+
percentImages.push(new XYType(c.percentImages ?? 0, NaN, NaN, label));
|
|
2669
|
+
rainyCount.push(new XYType(c.rainyCount ?? 0, NaN, NaN, label));
|
|
2670
|
+
rainySum.push(new XYType(c.rainySum ?? 0, NaN, NaN, label));
|
|
2671
|
+
});
|
|
2672
|
+
return {
|
|
2673
|
+
percentImages,
|
|
2674
|
+
rainyCount,
|
|
2675
|
+
rainySum,
|
|
2676
|
+
queueRunning: resp.data.queueRunning,
|
|
2677
|
+
};
|
|
2584
2678
|
}
|
|
2585
|
-
|
|
2586
|
-
|
|
2679
|
+
catch (e) {
|
|
2680
|
+
await this.checkError(e);
|
|
2587
2681
|
}
|
|
2588
|
-
return
|
|
2682
|
+
return null;
|
|
2589
2683
|
}
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
const
|
|
2593
|
-
|
|
2594
|
-
|
|
2684
|
+
// === Computing ===
|
|
2685
|
+
async getRainComputationCumulativeCartesianMapById(rainId, rainComputationCumulativeId) {
|
|
2686
|
+
const params = { format: 'cartesian-map' };
|
|
2687
|
+
const queryString = BuildQueryString(params);
|
|
2688
|
+
try {
|
|
2689
|
+
const response = await this.fidjService.sendOnEndpoint({
|
|
2690
|
+
key: 'rains',
|
|
2691
|
+
verb: 'GET',
|
|
2692
|
+
relativePath: `${rainId}/cumulatives/${rainComputationCumulativeId}?${queryString}`,
|
|
2693
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
2694
|
+
});
|
|
2695
|
+
if (!response.data['cartesian-map']) {
|
|
2696
|
+
return null;
|
|
2697
|
+
}
|
|
2698
|
+
const rainComputationMap = new RainComputationMap(response.data['cartesian-map']);
|
|
2699
|
+
rainComputationMap.name = rainId + '.rain.cartesian-map';
|
|
2700
|
+
return rainComputationMap;
|
|
2701
|
+
}
|
|
2702
|
+
catch (e) {
|
|
2703
|
+
await this.checkError(e);
|
|
2595
2704
|
}
|
|
2705
|
+
return null;
|
|
2596
2706
|
}
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
Cache.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: Cache, providedIn: 'root' });
|
|
2600
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: Cache, decorators: [{
|
|
2601
|
-
type: Injectable,
|
|
2602
|
-
args: [{
|
|
2603
|
-
providedIn: 'root',
|
|
2604
|
-
}]
|
|
2605
|
-
}], ctorParameters: function () { return []; } });
|
|
2606
|
-
|
|
2607
|
-
class FidjStorageNode {
|
|
2608
|
-
constructor() {
|
|
2609
|
-
this.radars = [];
|
|
2610
|
-
this.rains = [];
|
|
2611
|
-
this.gauges = [];
|
|
2612
|
-
this.events = [];
|
|
2613
|
-
this.infos = {};
|
|
2614
|
-
}
|
|
2615
|
-
static getEmptyNode() {
|
|
2616
|
-
return new FidjStorageNode();
|
|
2617
|
-
}
|
|
2618
|
-
static getDemoNode() {
|
|
2619
|
-
const demoNode = new FidjStorageNode();
|
|
2620
|
-
const link = new Link('rain', 'https://demo/api/rains/2');
|
|
2621
|
-
/*
|
|
2622
|
-
demoNode.radars = [
|
|
2623
|
-
new RadarNode({
|
|
2624
|
-
id: '5efd04569cb1f4161bd69dc7',
|
|
2625
|
-
name: 'demo radar A',
|
|
2626
|
-
links: [link],
|
|
2627
|
-
latitude: 48.774569,
|
|
2628
|
-
longitude: 2.008407
|
|
2629
|
-
}),
|
|
2630
|
-
new RadarNode({
|
|
2631
|
-
id: 'dr2',
|
|
2632
|
-
name: 'demo radar B',
|
|
2633
|
-
links: [link],
|
|
2634
|
-
latitude: 0.11,
|
|
2635
|
-
longitude: -0.753
|
|
2636
|
-
}),
|
|
2637
|
-
new RadarNode({
|
|
2638
|
-
id: 'dr3',
|
|
2639
|
-
name: 'demo radar C',
|
|
2640
|
-
latitude: 0.13,
|
|
2641
|
-
longitude: -0.753,
|
|
2642
|
-
links: []
|
|
2643
|
-
}),
|
|
2644
|
-
new RadarNode({
|
|
2645
|
-
id: 'dr4',
|
|
2646
|
-
name: 'demo radar D',
|
|
2647
|
-
latitude: 0.14,
|
|
2648
|
-
longitude: -0.74,
|
|
2649
|
-
links: []
|
|
2650
|
-
})];
|
|
2651
|
-
demoNode.rains = [
|
|
2652
|
-
new RainNode({
|
|
2653
|
-
id: '5efd04569cb1f4161bd69dc8',
|
|
2654
|
-
name: 'Demo rain zone A',
|
|
2655
|
-
links: [new Link('radar', 'https://demo/api/radars/5efcfe619cb1f4161bd69dc3')],
|
|
2656
|
-
status: 0,
|
|
2657
|
-
quality: 75,
|
|
2658
|
-
latitude: 48.774569,
|
|
2659
|
-
longitude: 2.008407
|
|
2660
|
-
}),
|
|
2661
|
-
new RainNode({
|
|
2662
|
-
id: 'dz2',
|
|
2663
|
-
name: 'Demo rain zone B',
|
|
2664
|
-
radars: [demoNode.radars[0], demoNode.radars[1]],
|
|
2665
|
-
status: 1,
|
|
2666
|
-
quality: 50,
|
|
2667
|
-
latitude: 48.774569,
|
|
2668
|
-
longitude: 2.008407
|
|
2669
|
-
}),
|
|
2670
|
-
new RainNode({
|
|
2671
|
-
id: 'dz3',
|
|
2672
|
-
name: 'Demo rain zone C',
|
|
2673
|
-
radars: [demoNode.radars[0], demoNode.radars[1]],
|
|
2674
|
-
status: 2,
|
|
2675
|
-
quality: 75,
|
|
2676
|
-
latitude: 48.774569,
|
|
2677
|
-
longitude: 2.008407
|
|
2678
|
-
}),
|
|
2679
|
-
new RainNode({
|
|
2680
|
-
id: 'dz4',
|
|
2681
|
-
name: 'Demo rain zone D',
|
|
2682
|
-
radars: [demoNode.radars[0], demoNode.radars[1]],
|
|
2683
|
-
status: 3,
|
|
2684
|
-
quality: 95,
|
|
2685
|
-
latitude: 48.774569,
|
|
2686
|
-
longitude: 2.008407
|
|
2687
|
-
})];
|
|
2688
|
-
|
|
2689
|
-
demoNode.gauges = [
|
|
2690
|
-
new GaugeNode({
|
|
2691
|
-
id: 'g1',
|
|
2692
|
-
name: 'Gauge A',
|
|
2693
|
-
latitude: 48.7748,
|
|
2694
|
-
longitude: 2.28407,
|
|
2695
|
-
}),
|
|
2696
|
-
new GaugeNode({
|
|
2697
|
-
id: 'g2',
|
|
2698
|
-
name: 'Gauge B',
|
|
2699
|
-
latitude: 48.874569,
|
|
2700
|
-
longitude: 2.108407,
|
|
2701
|
-
})];
|
|
2702
|
-
demoNode.events = [{
|
|
2703
|
-
id: 'e2',
|
|
2704
|
-
title: 'Need support ?',
|
|
2705
|
-
status: 0,
|
|
2706
|
-
red: false,
|
|
2707
|
-
description: 'This area is dedicated to support you and your team. Support is made on Radar or Rain quality, ' +
|
|
2708
|
-
'or any feedback we can have about your production system. Our goal : improving your data.',
|
|
2709
|
-
created: new Date(),
|
|
2710
|
-
modified: new Date()
|
|
2711
|
-
}];
|
|
2712
|
-
demoNode.team = {
|
|
2713
|
-
id: 'p1',
|
|
2714
|
-
email: 'demo@demo.com',
|
|
2715
|
-
name: 'demo guy',
|
|
2716
|
-
description: 'the demo guy'
|
|
2717
|
-
};
|
|
2718
|
-
|
|
2719
|
-
*/
|
|
2720
|
-
return demoNode;
|
|
2721
|
-
}
|
|
2722
|
-
}
|
|
2723
|
-
class FidjStorageResult {
|
|
2724
|
-
}
|
|
2725
|
-
class FidjStorage {
|
|
2726
|
-
constructor(storage) {
|
|
2727
|
-
this.storage = storage;
|
|
2728
|
-
this.node = FidjStorageNode.getEmptyNode();
|
|
2729
|
-
this.fidjMetaResult = { data: new FidjStorageNode() };
|
|
2730
|
-
}
|
|
2731
|
-
async storeData(fidjService, data) {
|
|
2732
|
-
this.node = JSON.parse(JSON.stringify(data));
|
|
2733
|
-
this.fidjMetaResult.data = this.node;
|
|
2734
|
-
if (this.isDemoMode) {
|
|
2735
|
-
this.storage.set('fidjMetaResult', JSON.stringify(this.fidjMetaResult));
|
|
2736
|
-
return;
|
|
2737
|
-
}
|
|
2738
|
-
await fidjService.put(this.fidjMetaResult);
|
|
2739
|
-
}
|
|
2740
|
-
async getRefreshedNodeCopy(fidjService) {
|
|
2741
|
-
if (this.isDemoMode) {
|
|
2742
|
-
const fidjMetaResult = this.storage.get('fidjMetaResult');
|
|
2743
|
-
if (fidjMetaResult) {
|
|
2744
|
-
this.fidjMetaResult = JSON.parse(fidjMetaResult);
|
|
2745
|
-
this.node = this.fidjMetaResult.data;
|
|
2746
|
-
}
|
|
2747
|
-
return JSON.parse(JSON.stringify(this.node));
|
|
2748
|
-
}
|
|
2749
|
-
const firstDemoData = async () => {
|
|
2750
|
-
this.node = FidjStorageNode.getDemoNode();
|
|
2751
|
-
await this.storeData(fidjService, this.node);
|
|
2752
|
-
};
|
|
2753
|
-
await fidjService.sync(firstDemoData);
|
|
2754
|
-
const fidjFindAllResults = await fidjService.findAll();
|
|
2755
|
-
if (fidjFindAllResults && fidjFindAllResults.length > 0) {
|
|
2756
|
-
if (fidjFindAllResults[0].data) {
|
|
2757
|
-
this.fidjMetaResult = fidjFindAllResults[0];
|
|
2758
|
-
this.node = this.fidjMetaResult.data;
|
|
2759
|
-
}
|
|
2760
|
-
}
|
|
2761
|
-
return JSON.parse(JSON.stringify(this.node));
|
|
2762
|
-
}
|
|
2763
|
-
setDemoMode(isDemo) {
|
|
2764
|
-
this.isDemoMode = isDemo;
|
|
2765
|
-
}
|
|
2766
|
-
}
|
|
2767
|
-
|
|
2768
|
-
class ProfileService {
|
|
2769
|
-
constructor(storage, fidjService, httpClient, router) {
|
|
2770
|
-
this.storage = storage;
|
|
2771
|
-
this.fidjService = fidjService;
|
|
2772
|
-
this.httpClient = httpClient;
|
|
2773
|
-
this.router = router;
|
|
2774
|
-
this.email = this.storage.get('raain-email');
|
|
2775
|
-
this.asTeamId = this.storage.get('raain-asTeamId');
|
|
2776
|
-
this.readyForSync = new BehaviorSubject(false);
|
|
2777
|
-
this.roles = [];
|
|
2778
|
-
this.fidjStorage = new FidjStorage(storage);
|
|
2779
|
-
this.isDemoMode = false;
|
|
2780
|
-
}
|
|
2781
|
-
get isDemoMode() {
|
|
2782
|
-
return this.isDemo;
|
|
2783
|
-
}
|
|
2784
|
-
set isDemoMode(mode) {
|
|
2785
|
-
this.isDemo = mode ? mode : true;
|
|
2786
|
-
this.fidjStorage.setDemoMode(this.isDemo);
|
|
2787
|
-
}
|
|
2788
|
-
get defaultUrlForAPI() {
|
|
2789
|
-
return this.storage.get('raain-urlForAPI');
|
|
2790
|
-
}
|
|
2791
|
-
set defaultUrlForAPI(url) {
|
|
2792
|
-
this.storage.set('raain-urlForAPI', url);
|
|
2793
|
-
}
|
|
2794
|
-
async refreshProfile() {
|
|
2707
|
+
async getRainComputationCumulativeCumulativesMapById(rainId, rainComputationCumulativeId, cumulativeHours) {
|
|
2708
|
+
const queryPath = `${rainId}/cumulatives/${rainComputationCumulativeId}/cumulative/${cumulativeHours}`;
|
|
2795
2709
|
try {
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
|
|
2710
|
+
const response = await this.fidjService.sendOnEndpoint({
|
|
2711
|
+
key: 'rains',
|
|
2712
|
+
verb: 'GET',
|
|
2713
|
+
relativePath: queryPath,
|
|
2714
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
2715
|
+
});
|
|
2716
|
+
if (!response.data['cumulative']) {
|
|
2717
|
+
return null;
|
|
2718
|
+
}
|
|
2719
|
+
const rainComputationMap = new RainComputationMap(response.data['cumulative']);
|
|
2720
|
+
rainComputationMap.name = rainId + '.rain.cumulative-cumulative';
|
|
2721
|
+
return rainComputationMap;
|
|
2799
2722
|
}
|
|
2800
2723
|
catch (e) {
|
|
2801
2724
|
await this.checkError(e);
|
|
2802
2725
|
}
|
|
2726
|
+
return null;
|
|
2803
2727
|
}
|
|
2804
|
-
async
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
getEmail() {
|
|
2808
|
-
return this.email ?? this.storage.get('raain-email', this.email);
|
|
2809
|
-
}
|
|
2810
|
-
setEmail(email) {
|
|
2811
|
-
this.email = email;
|
|
2812
|
-
this.storage.set('raain-email', this.email);
|
|
2813
|
-
}
|
|
2814
|
-
async logout(fidjKey, fidjProd) {
|
|
2815
|
-
// this.storage.remove('raain-email');
|
|
2816
|
-
if (!fidjKey) {
|
|
2817
|
-
try {
|
|
2818
|
-
await this.fidjService.loginInDemoMode();
|
|
2819
|
-
this.readyForSync.next(true);
|
|
2820
|
-
}
|
|
2821
|
-
catch (err) {
|
|
2822
|
-
console.error('initFidj catch pb: ', err);
|
|
2823
|
-
}
|
|
2824
|
-
return;
|
|
2825
|
-
}
|
|
2826
|
-
await this.fidjService.logout(true);
|
|
2728
|
+
async getRainCumulativeCompareByDate(rainNode, rainComputationCumulativeId, date) {
|
|
2729
|
+
const params = { date: date.toISOString() };
|
|
2730
|
+
const queryString = BuildQueryString(params);
|
|
2827
2731
|
try {
|
|
2828
|
-
await this.fidjService.
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2732
|
+
const response = await this.fidjService.sendOnEndpoint({
|
|
2733
|
+
key: 'rains',
|
|
2734
|
+
verb: 'GET',
|
|
2735
|
+
relativePath: `${rainNode.id}/cumulatives/${rainComputationCumulativeId}/compares?${queryString}`,
|
|
2736
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
2833
2737
|
});
|
|
2834
|
-
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2738
|
+
const qualityJson = response.data.qualities[0];
|
|
2739
|
+
const rainComputationQuality = new RainComputationQuality(qualityJson);
|
|
2740
|
+
rainComputationQuality.qualitySpeedMatrixContainer =
|
|
2741
|
+
SpeedMatrixContainer.CreateFromJson(rainComputationQuality.qualitySpeedMatrixContainer);
|
|
2742
|
+
return rainComputationQuality;
|
|
2838
2743
|
}
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
if (error.code === 401) {
|
|
2842
|
-
console.warn('Pb on auth');
|
|
2843
|
-
if (this.router.url.indexOf('login') < 0) {
|
|
2844
|
-
try {
|
|
2845
|
-
await this.fidjService.logout();
|
|
2846
|
-
}
|
|
2847
|
-
catch (ignored) {
|
|
2848
|
-
// Ignore logout errors as we're redirecting to logout page anyway
|
|
2849
|
-
}
|
|
2850
|
-
return this.gotoLout();
|
|
2851
|
-
}
|
|
2744
|
+
catch (e) {
|
|
2745
|
+
await this.checkError(e);
|
|
2852
2746
|
}
|
|
2747
|
+
return null;
|
|
2853
2748
|
}
|
|
2854
|
-
async
|
|
2749
|
+
async getRainCumulativeCumulativesCompareByDate(rainNode, rainComputationCumulativeId, date, cumulativeHours) {
|
|
2750
|
+
const params = {
|
|
2751
|
+
date: date.toISOString(),
|
|
2752
|
+
cumulativeHours,
|
|
2753
|
+
};
|
|
2754
|
+
const queryString = BuildQueryString(params);
|
|
2855
2755
|
try {
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
replaceUrl: true,
|
|
2756
|
+
const response = await this.fidjService.sendOnEndpoint({
|
|
2757
|
+
key: 'rains',
|
|
2758
|
+
verb: 'GET',
|
|
2759
|
+
relativePath: `${rainNode.id}/cumulatives/${rainComputationCumulativeId}/compares?${queryString}`,
|
|
2760
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
2862
2761
|
});
|
|
2762
|
+
const qualityJson = response.data.qualities[0];
|
|
2763
|
+
const rainComputationQuality = new RainComputationQuality(qualityJson);
|
|
2764
|
+
rainComputationQuality.qualitySpeedMatrixContainer =
|
|
2765
|
+
SpeedMatrixContainer.CreateFromJson(rainComputationQuality.qualitySpeedMatrixContainer);
|
|
2766
|
+
return rainComputationQuality;
|
|
2863
2767
|
}
|
|
2864
2768
|
catch (e) {
|
|
2865
|
-
|
|
2866
|
-
}
|
|
2867
|
-
}
|
|
2868
|
-
async gotoLogin() {
|
|
2869
|
-
if (this.router.url.indexOf('login') > -1) {
|
|
2870
|
-
return;
|
|
2769
|
+
await this.checkError(e);
|
|
2871
2770
|
}
|
|
2872
|
-
|
|
2873
|
-
await this.router.navigate(['/login']);
|
|
2874
|
-
}
|
|
2875
|
-
isLoggedIn() {
|
|
2876
|
-
const loggedIn = this.fidjService.isLoggedIn();
|
|
2877
|
-
console.log('isLoggedIn: ', loggedIn);
|
|
2878
|
-
return loggedIn;
|
|
2879
|
-
}
|
|
2880
|
-
needsConnectionRefresh() {
|
|
2881
|
-
return this.fidjService.needsRefresh();
|
|
2882
|
-
}
|
|
2883
|
-
async connectionRefresh() {
|
|
2884
|
-
await this.fidjService.refresh();
|
|
2885
|
-
}
|
|
2886
|
-
async storeAll() {
|
|
2887
|
-
return this.fidjStorage.storeData(this.fidjService, this.nodeData);
|
|
2888
|
-
}
|
|
2889
|
-
isAdmin() {
|
|
2890
|
-
return this.roles.indexOf('admin') > -1;
|
|
2771
|
+
return null;
|
|
2891
2772
|
}
|
|
2892
|
-
|
|
2893
|
-
async createNotification(rainId, message) {
|
|
2894
|
-
const data = {
|
|
2895
|
-
rain: rainId,
|
|
2896
|
-
message,
|
|
2897
|
-
};
|
|
2773
|
+
async getRainProgress(rainId) {
|
|
2898
2774
|
try {
|
|
2899
|
-
const
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2775
|
+
const queryPath = '' + rainId + '/progress';
|
|
2776
|
+
const response = await this.fidjService.sendOnEndpoint({
|
|
2777
|
+
key: 'rains',
|
|
2778
|
+
verb: 'GET',
|
|
2779
|
+
relativePath: queryPath,
|
|
2780
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
2904
2781
|
});
|
|
2905
|
-
return
|
|
2782
|
+
// return response.data.inProgress;
|
|
2783
|
+
return response.data.inQueue;
|
|
2906
2784
|
}
|
|
2907
2785
|
catch (e) {
|
|
2908
2786
|
await this.checkError(e);
|
|
2909
2787
|
}
|
|
2910
|
-
return
|
|
2788
|
+
return 0;
|
|
2911
2789
|
}
|
|
2912
|
-
async
|
|
2790
|
+
async getIndicators(rainId) {
|
|
2913
2791
|
try {
|
|
2914
|
-
const params = {
|
|
2792
|
+
const params = {
|
|
2793
|
+
cumulative: 'true',
|
|
2794
|
+
};
|
|
2915
2795
|
const queryString = BuildQueryString(params);
|
|
2916
|
-
const
|
|
2917
|
-
key: '
|
|
2796
|
+
const response = await this.fidjService.sendOnEndpoint({
|
|
2797
|
+
key: 'rains',
|
|
2918
2798
|
verb: 'GET',
|
|
2919
|
-
relativePath: queryString ? '?' + queryString : '',
|
|
2920
|
-
defaultKeyUrl: this.defaultUrlForAPI + '/
|
|
2799
|
+
relativePath: rainId + '/indicators' + (queryString ? '?' + queryString : ''),
|
|
2800
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
2921
2801
|
});
|
|
2922
|
-
return
|
|
2802
|
+
return response.data;
|
|
2923
2803
|
}
|
|
2924
2804
|
catch (e) {
|
|
2925
2805
|
await this.checkError(e);
|
|
2926
2806
|
}
|
|
2927
|
-
return
|
|
2807
|
+
return { indicators: [] };
|
|
2928
2808
|
}
|
|
2929
|
-
|
|
2809
|
+
// GET /rains/:rainId/cumulatives - List available cumulative periods
|
|
2810
|
+
async getCumulativePeriods(rainId, filters) {
|
|
2930
2811
|
try {
|
|
2931
|
-
const
|
|
2932
|
-
|
|
2812
|
+
const params = {};
|
|
2813
|
+
if (filters?.windowInMinutes !== undefined) {
|
|
2814
|
+
params.windowInMinutes = filters.windowInMinutes;
|
|
2815
|
+
}
|
|
2816
|
+
if (filters?.provider) {
|
|
2817
|
+
params.provider = filters.provider;
|
|
2818
|
+
}
|
|
2819
|
+
if (filters?.isReady !== undefined) {
|
|
2820
|
+
params.isReady = filters.isReady;
|
|
2821
|
+
}
|
|
2822
|
+
if (filters?.forced !== undefined) {
|
|
2823
|
+
params.forced = filters.forced;
|
|
2824
|
+
}
|
|
2825
|
+
const queryString = BuildQueryString(params);
|
|
2826
|
+
const response = await this.fidjService.sendOnEndpoint({
|
|
2827
|
+
key: 'rains',
|
|
2933
2828
|
verb: 'GET',
|
|
2934
|
-
|
|
2829
|
+
relativePath: rainId + '/cumulatives' + (queryString ? '?' + queryString : ''),
|
|
2830
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
2935
2831
|
});
|
|
2936
|
-
return
|
|
2832
|
+
return response.data;
|
|
2937
2833
|
}
|
|
2938
2834
|
catch (e) {
|
|
2939
2835
|
await this.checkError(e);
|
|
2940
2836
|
}
|
|
2941
|
-
return [];
|
|
2837
|
+
return { periods: [], total: 0 };
|
|
2942
2838
|
}
|
|
2943
|
-
//
|
|
2944
|
-
async
|
|
2945
|
-
const teams = [];
|
|
2946
|
-
const params = {};
|
|
2947
|
-
const queryString = BuildQueryString(params);
|
|
2839
|
+
// POST /rains/:rainId/cumulatives - Trigger cumulative computation
|
|
2840
|
+
async createCumulativePeriod(rainId, params) {
|
|
2948
2841
|
try {
|
|
2949
|
-
const
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2842
|
+
const body = {
|
|
2843
|
+
periodBegin: params.periodBegin.toISOString(),
|
|
2844
|
+
periodEnd: params.periodEnd.toISOString(),
|
|
2845
|
+
provider: params.provider,
|
|
2846
|
+
confName: params.confName || 'admin',
|
|
2847
|
+
timeStepInMinutes: params.timeStepInMinutes || 5,
|
|
2848
|
+
};
|
|
2849
|
+
const response = await this.fidjService.sendOnEndpoint({
|
|
2850
|
+
key: 'rains',
|
|
2851
|
+
verb: 'POST',
|
|
2852
|
+
relativePath: rainId + '/cumulatives',
|
|
2853
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
2854
|
+
data: body,
|
|
2954
2855
|
});
|
|
2955
|
-
|
|
2956
|
-
teams.push(new TeamNode(team));
|
|
2957
|
-
}
|
|
2856
|
+
return response.data;
|
|
2958
2857
|
}
|
|
2959
2858
|
catch (e) {
|
|
2960
2859
|
await this.checkError(e);
|
|
2961
2860
|
}
|
|
2962
|
-
return
|
|
2861
|
+
return null;
|
|
2963
2862
|
}
|
|
2964
|
-
|
|
2863
|
+
// === Gauges ===
|
|
2864
|
+
async getGauge(gaugeId) {
|
|
2965
2865
|
try {
|
|
2966
2866
|
const resp = await this.fidjService.sendOnEndpoint({
|
|
2967
|
-
key: '
|
|
2867
|
+
key: 'gauges',
|
|
2968
2868
|
verb: 'GET',
|
|
2969
|
-
relativePath:
|
|
2970
|
-
defaultKeyUrl: this.defaultUrlForAPI + '/
|
|
2869
|
+
relativePath: gaugeId,
|
|
2870
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/gauges',
|
|
2971
2871
|
});
|
|
2972
|
-
return new
|
|
2872
|
+
return new GaugeNode(resp.data);
|
|
2973
2873
|
}
|
|
2974
2874
|
catch (e) {
|
|
2975
2875
|
await this.checkError(e);
|
|
2976
2876
|
}
|
|
2977
|
-
return null;
|
|
2978
2877
|
}
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2878
|
+
async getGauges(rainId, aroundLatLng, pageCount = 1) {
|
|
2879
|
+
const baseParams = {
|
|
2880
|
+
aroundLatLng: `${aroundLatLng.lat},${aroundLatLng.lng}`,
|
|
2881
|
+
rainId,
|
|
2882
|
+
};
|
|
2883
|
+
if (this.asTeamId) {
|
|
2884
|
+
baseParams.teamId = this.asTeamId;
|
|
2985
2885
|
}
|
|
2986
|
-
const
|
|
2886
|
+
const gauges = [];
|
|
2987
2887
|
try {
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2888
|
+
for (let count = 1; count <= pageCount; count++) {
|
|
2889
|
+
const params = { ...baseParams, page: count };
|
|
2890
|
+
const queryString = BuildQueryString(params);
|
|
2891
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2892
|
+
key: 'gauges',
|
|
2893
|
+
verb: 'GET',
|
|
2894
|
+
relativePath: queryString ? '?' + queryString : '',
|
|
2895
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/gauges',
|
|
2896
|
+
});
|
|
2897
|
+
for (const gauge of resp.data.gauges) {
|
|
2898
|
+
gauges.push(new GaugeNodeFilter(gauge));
|
|
2899
|
+
}
|
|
2997
2900
|
}
|
|
2998
2901
|
}
|
|
2999
2902
|
catch (e) {
|
|
3000
2903
|
await this.checkError(e);
|
|
3001
2904
|
}
|
|
3002
|
-
return
|
|
2905
|
+
return gauges;
|
|
3003
2906
|
}
|
|
3004
|
-
async
|
|
2907
|
+
async getGaugeMeasures(gaugeId, begin, end) {
|
|
2908
|
+
const params = {
|
|
2909
|
+
begin: begin.toISOString(),
|
|
2910
|
+
end: end.toISOString(),
|
|
2911
|
+
};
|
|
2912
|
+
const queryString = BuildQueryString(params);
|
|
2913
|
+
const resp = await this.fidjService.sendOnEndpoint({
|
|
2914
|
+
key: 'gauges',
|
|
2915
|
+
verb: 'GET',
|
|
2916
|
+
relativePath: gaugeId + '/measures' + (queryString ? '?' + queryString : ''),
|
|
2917
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/gauges',
|
|
2918
|
+
});
|
|
2919
|
+
const gaugeMeasures = [];
|
|
2920
|
+
for (const gaugeMeasure of resp.data.gaugeMeasures) {
|
|
2921
|
+
gaugeMeasures.push(new GaugeMeasure(gaugeMeasure));
|
|
2922
|
+
}
|
|
2923
|
+
return gaugeMeasures;
|
|
2924
|
+
}
|
|
2925
|
+
async getProviders(rainId) {
|
|
3005
2926
|
try {
|
|
3006
|
-
const
|
|
3007
|
-
key: 'radars',
|
|
2927
|
+
const response = await this.fidjService.sendOnEndpoint({
|
|
3008
2928
|
verb: 'GET',
|
|
3009
|
-
|
|
3010
|
-
|
|
2929
|
+
key: 'rains',
|
|
2930
|
+
relativePath: rainId + '/providers',
|
|
2931
|
+
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
3011
2932
|
});
|
|
3012
|
-
return
|
|
2933
|
+
return {
|
|
2934
|
+
providers: response.data.providers || [],
|
|
2935
|
+
timeStepInMinutes: response.data.timeStepInMinutes || [5, 10, 15, 30, 60],
|
|
2936
|
+
};
|
|
3013
2937
|
}
|
|
3014
2938
|
catch (e) {
|
|
3015
|
-
|
|
2939
|
+
console.error('getProviders error:', e);
|
|
2940
|
+
return {
|
|
2941
|
+
providers: [],
|
|
2942
|
+
timeStepInMinutes: [5, 10, 15, 30, 60],
|
|
2943
|
+
};
|
|
3016
2944
|
}
|
|
3017
|
-
return null;
|
|
3018
2945
|
}
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
2946
|
+
setRoles(roles) {
|
|
2947
|
+
this.roles = roles;
|
|
2948
|
+
}
|
|
2949
|
+
setDateComponents(date, c) {
|
|
2950
|
+
const dateToShow = new Date(date);
|
|
2951
|
+
if (c.year !== undefined) {
|
|
2952
|
+
dateToShow.setUTCFullYear(c.year);
|
|
2953
|
+
}
|
|
2954
|
+
if (c.month !== undefined) {
|
|
2955
|
+
dateToShow.setUTCMonth(c.month - 1);
|
|
2956
|
+
}
|
|
2957
|
+
if (c.day !== undefined) {
|
|
2958
|
+
dateToShow.setUTCDate(c.day);
|
|
2959
|
+
}
|
|
2960
|
+
if (c.hour !== undefined) {
|
|
2961
|
+
dateToShow.setUTCHours(c.hour);
|
|
2962
|
+
}
|
|
2963
|
+
if (c.minute !== undefined) {
|
|
2964
|
+
dateToShow.setUTCMinutes(c.minute);
|
|
2965
|
+
}
|
|
2966
|
+
return dateToShow.toISOString();
|
|
2967
|
+
}
|
|
2968
|
+
}
|
|
2969
|
+
ProfileService.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: ProfileService, deps: [{ token: Storage }, { token: i2$1.FidjService }, { token: i3$1.HttpClient }, { token: i4$1.Router }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
2970
|
+
ProfileService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: ProfileService, providedIn: 'root' });
|
|
2971
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: ProfileService, decorators: [{
|
|
2972
|
+
type: Injectable,
|
|
2973
|
+
args: [{
|
|
2974
|
+
providedIn: 'root',
|
|
2975
|
+
}]
|
|
2976
|
+
}], ctorParameters: function () { return [{ type: Storage }, { type: i2$1.FidjService }, { type: i3$1.HttpClient }, { type: i4$1.Router }]; } });
|
|
2977
|
+
|
|
2978
|
+
class CumulativeSelectorComponent {
|
|
2979
|
+
constructor(profileService, cdr) {
|
|
2980
|
+
this.profileService = profileService;
|
|
2981
|
+
this.cdr = cdr;
|
|
2982
|
+
this.timeStepInMinutes = 5;
|
|
2983
|
+
this.isAdmin = false;
|
|
2984
|
+
this.periodSelected = new EventEmitter();
|
|
2985
|
+
this.cancelled = new EventEmitter();
|
|
2986
|
+
this.availablePeriods = [];
|
|
2987
|
+
this.baseCumulatives = null;
|
|
2988
|
+
this.loading = true;
|
|
2989
|
+
this.creating = false;
|
|
2990
|
+
this.creationProgress = '';
|
|
2991
|
+
this.errorMessage = '';
|
|
2992
|
+
this.coveragePercent = 0;
|
|
2993
|
+
this.canCreateNew = false;
|
|
2994
|
+
this.currentWindowMinutes = 0;
|
|
2995
|
+
this.POLL_TIMEOUT_MS = 900000; // 900 seconds
|
|
2996
|
+
this.POLL_INTERVAL_MS = 3000;
|
|
2997
|
+
}
|
|
2998
|
+
async ngOnInit() {
|
|
2999
|
+
this.currentWindowMinutes = Math.round((this.currentPeriodEnd.getTime() - this.currentPeriodBegin.getTime()) / 60000);
|
|
3000
|
+
await this.loadAvailablePeriods();
|
|
3001
|
+
}
|
|
3002
|
+
async loadAvailablePeriods() {
|
|
3003
|
+
this.loading = true;
|
|
3004
|
+
this.errorMessage = '';
|
|
3005
|
+
this.cdr.markForCheck();
|
|
3023
3006
|
try {
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
data,
|
|
3029
|
-
defaultKeyUrl: this.defaultUrlForAPI + '/radars/',
|
|
3007
|
+
// Fetch all cumulative periods (admin sees all, non-admin sees only customer-launched)
|
|
3008
|
+
const response = await this.profileService.getCumulativePeriods(this.rainId, {
|
|
3009
|
+
provider: this.provider,
|
|
3010
|
+
forced: this.isAdmin,
|
|
3030
3011
|
});
|
|
3031
|
-
|
|
3012
|
+
// Filter for existing custom cumulatives (window > 0)
|
|
3013
|
+
this.availablePeriods = response.periods.filter((p) => p.windowInMinutes > 0);
|
|
3014
|
+
// Get base cumulatives (window = 0) to check coverage
|
|
3015
|
+
this.baseCumulatives = response.periods.find((p) => p.windowInMinutes === 0);
|
|
3016
|
+
this.calculateCoverage();
|
|
3032
3017
|
}
|
|
3033
3018
|
catch (e) {
|
|
3034
|
-
|
|
3019
|
+
this.errorMessage = 'Failed to load cumulative periods';
|
|
3020
|
+
console.error('Error loading cumulative periods:', e);
|
|
3035
3021
|
}
|
|
3022
|
+
this.loading = false;
|
|
3023
|
+
this.cdr.markForCheck();
|
|
3036
3024
|
}
|
|
3037
|
-
|
|
3025
|
+
calculateCoverage() {
|
|
3026
|
+
if (!this.baseCumulatives) {
|
|
3027
|
+
this.coveragePercent = 0;
|
|
3028
|
+
this.canCreateNew = false;
|
|
3029
|
+
return;
|
|
3030
|
+
}
|
|
3031
|
+
const baseBegin = new Date(this.baseCumulatives.periodBegin);
|
|
3032
|
+
const baseEnd = new Date(this.baseCumulatives.periodEnd);
|
|
3033
|
+
// Check if current period is within base coverage
|
|
3034
|
+
const currentInRange = this.currentPeriodBegin >= baseBegin && this.currentPeriodEnd <= baseEnd;
|
|
3035
|
+
if (!currentInRange) {
|
|
3036
|
+
this.coveragePercent = 0;
|
|
3037
|
+
this.canCreateNew = false;
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
// Calculate expected number of base cumulatives needed
|
|
3041
|
+
const expectedCount = Math.ceil(this.currentWindowMinutes / this.timeStepInMinutes);
|
|
3042
|
+
// Check if enough base cumulatives exist
|
|
3043
|
+
// We assume coverage is complete if count >= expected (simplified check)
|
|
3044
|
+
if (this.baseCumulatives.count >= expectedCount) {
|
|
3045
|
+
this.coveragePercent = 100;
|
|
3046
|
+
this.canCreateNew = true;
|
|
3047
|
+
}
|
|
3048
|
+
else {
|
|
3049
|
+
this.coveragePercent = Math.round((this.baseCumulatives.count / expectedCount) * 100);
|
|
3050
|
+
this.canCreateNew = this.coveragePercent >= 100;
|
|
3051
|
+
}
|
|
3052
|
+
}
|
|
3053
|
+
selectPeriod(period) {
|
|
3054
|
+
this.periodSelected.emit({
|
|
3055
|
+
periodBegin: new Date(period.periodBegin),
|
|
3056
|
+
periodEnd: new Date(period.periodEnd),
|
|
3057
|
+
windowInMinutes: period.windowInMinutes,
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
3060
|
+
async createNewPeriod() {
|
|
3061
|
+
if (!this.canCreateNew || this.creating) {
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
this.creating = true;
|
|
3065
|
+
this.creationProgress = 'Starting cumulative computation...';
|
|
3066
|
+
this.errorMessage = '';
|
|
3067
|
+
this.cdr.markForCheck();
|
|
3038
3068
|
try {
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3069
|
+
// Trigger cumulative creation
|
|
3070
|
+
const result = await this.profileService.createCumulativePeriod(this.rainId, {
|
|
3071
|
+
periodBegin: this.currentPeriodBegin,
|
|
3072
|
+
periodEnd: this.currentPeriodEnd,
|
|
3073
|
+
provider: this.provider,
|
|
3074
|
+
timeStepInMinutes: this.timeStepInMinutes,
|
|
3043
3075
|
});
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3076
|
+
if (!result) {
|
|
3077
|
+
throw new Error('Failed to trigger cumulative computation');
|
|
3078
|
+
}
|
|
3079
|
+
this.creationProgress = `Jobs queued. Polling for completion...`;
|
|
3080
|
+
this.cdr.markForCheck();
|
|
3081
|
+
// Poll for completion
|
|
3082
|
+
const success = await this.pollForCompletion();
|
|
3083
|
+
if (success) {
|
|
3084
|
+
this.periodSelected.emit({
|
|
3085
|
+
periodBegin: this.currentPeriodBegin,
|
|
3086
|
+
periodEnd: this.currentPeriodEnd,
|
|
3087
|
+
windowInMinutes: this.currentWindowMinutes,
|
|
3053
3088
|
});
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
}
|
|
3058
|
-
return lonelyRadars;
|
|
3089
|
+
}
|
|
3090
|
+
else {
|
|
3091
|
+
this.errorMessage = 'Timeout waiting for cumulative computation';
|
|
3092
|
+
}
|
|
3059
3093
|
}
|
|
3060
3094
|
catch (e) {
|
|
3061
|
-
|
|
3095
|
+
this.errorMessage = `Error: ${e.message || 'Unknown error'}`;
|
|
3096
|
+
console.error('Error creating cumulative:', e);
|
|
3062
3097
|
}
|
|
3063
|
-
|
|
3098
|
+
this.creating = false;
|
|
3099
|
+
this.cdr.markForCheck();
|
|
3064
3100
|
}
|
|
3065
|
-
async
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3101
|
+
async pollForCompletion() {
|
|
3102
|
+
const startTime = Date.now();
|
|
3103
|
+
while (Date.now() - startTime < this.POLL_TIMEOUT_MS) {
|
|
3104
|
+
try {
|
|
3105
|
+
const progress = await this.profileService.getRainProgress(this.rainId);
|
|
3106
|
+
if (progress === 0) {
|
|
3107
|
+
// Queue is empty, check if cumulative exists (admin sees all cumulatives)
|
|
3108
|
+
const response = await this.profileService.getCumulativePeriods(this.rainId, {
|
|
3109
|
+
provider: this.provider,
|
|
3110
|
+
windowInMinutes: this.currentWindowMinutes,
|
|
3111
|
+
forced: this.isAdmin,
|
|
3112
|
+
});
|
|
3113
|
+
const found = response.periods.find((p) => p.windowInMinutes === this.currentWindowMinutes);
|
|
3114
|
+
if (found) {
|
|
3115
|
+
return true;
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
const elapsed = Math.round((Date.now() - startTime) / 1000);
|
|
3119
|
+
this.creationProgress = `Computing... (${elapsed}s, queue: ${progress})`;
|
|
3120
|
+
this.cdr.markForCheck();
|
|
3121
|
+
await this.sleep(this.POLL_INTERVAL_MS);
|
|
3122
|
+
}
|
|
3123
|
+
catch (e) {
|
|
3124
|
+
console.warn('Poll error:', e);
|
|
3125
|
+
await this.sleep(this.POLL_INTERVAL_MS);
|
|
3076
3126
|
}
|
|
3077
|
-
params.windowInMinutes = String(windowInMinutes);
|
|
3078
|
-
const queryString = BuildQueryString(params);
|
|
3079
|
-
const resp = await this.fidjService.sendOnEndpoint({
|
|
3080
|
-
key: 'rains',
|
|
3081
|
-
verb: 'GET',
|
|
3082
|
-
relativePath: rainId + (queryString ? '?' + queryString : ''),
|
|
3083
|
-
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
3084
|
-
});
|
|
3085
|
-
const rainNode = new RainNode(resp.data.timeframeCumulative);
|
|
3086
|
-
rainNode.name += '.radar.timeframeCumulative';
|
|
3087
|
-
return rainNode;
|
|
3088
3127
|
}
|
|
3089
|
-
|
|
3090
|
-
|
|
3128
|
+
return false;
|
|
3129
|
+
}
|
|
3130
|
+
sleep(ms) {
|
|
3131
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3132
|
+
}
|
|
3133
|
+
cancel() {
|
|
3134
|
+
this.cancelled.emit();
|
|
3135
|
+
}
|
|
3136
|
+
formatPeriod(period) {
|
|
3137
|
+
const begin = new Date(period.periodBegin);
|
|
3138
|
+
const end = new Date(period.periodEnd);
|
|
3139
|
+
return `${begin.toLocaleString()} → ${end.toLocaleString()}`;
|
|
3140
|
+
}
|
|
3141
|
+
formatWindow(minutes) {
|
|
3142
|
+
if (minutes < 60) {
|
|
3143
|
+
return `${minutes} min`;
|
|
3091
3144
|
}
|
|
3092
|
-
|
|
3145
|
+
const hours = minutes / 60;
|
|
3146
|
+
return hours === 1 ? '1 hour' : `${hours} hours`;
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3149
|
+
CumulativeSelectorComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: CumulativeSelectorComponent, deps: [{ token: ProfileService }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3150
|
+
CumulativeSelectorComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: CumulativeSelectorComponent, selector: "cumulative-selector", inputs: { rainId: "rainId", currentPeriodBegin: "currentPeriodBegin", currentPeriodEnd: "currentPeriodEnd", provider: "provider", timeStepInMinutes: "timeStepInMinutes", isAdmin: "isAdmin" }, outputs: { periodSelected: "periodSelected", cancelled: "cancelled" }, ngImport: i0, template: "<div class=\"cumulative-selector-overlay\">\n <div class=\"cumulative-selector-modal\">\n <div class=\"modal-header\">\n <h2>Select Cumulative Period</h2>\n <ion-button fill=\"clear\" (click)=\"cancel()\">\n <ion-icon name=\"close\"></ion-icon>\n </ion-button>\n </div>\n\n <div class=\"modal-content\">\n <!-- Loading state -->\n <div *ngIf=\"loading\" class=\"loading-state\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n <p>Loading available periods...</p>\n </div>\n\n <!-- Error message -->\n <div *ngIf=\"errorMessage\" class=\"error-message\">\n <ion-icon name=\"warning-outline\"></ion-icon>\n <span>{{ errorMessage }}</span>\n </div>\n\n <!-- Available periods list -->\n <div *ngIf=\"!loading && availablePeriods.length > 0\" class=\"periods-section\">\n <h3>Available Cumulative Periods</h3>\n <ion-list>\n <ion-item *ngFor=\"let period of availablePeriods\"\n button\n (click)=\"selectPeriod(period)\"\n [disabled]=\"creating\">\n <ion-icon name=\"layers-outline\" slot=\"start\"></ion-icon>\n <ion-label>\n <h2>{{ formatWindow(period.windowInMinutes) }}</h2>\n <p>{{ formatPeriod(period) }}</p>\n <p class=\"count-info\">{{ period.count }} cumulative(s)</p>\n </ion-label>\n <ion-icon name=\"chevron-forward\" slot=\"end\"></ion-icon>\n </ion-item>\n </ion-list>\n </div>\n\n <!-- No periods available -->\n <div *ngIf=\"!loading && availablePeriods.length === 0\" class=\"no-periods\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>No cumulative periods available yet.</p>\n </div>\n\n <!-- Create new section (admin only) -->\n <div *ngIf=\"!loading && isAdmin\" class=\"create-section\">\n <h3>Create New Cumulative</h3>\n <div class=\"create-info\">\n <p>\n <strong>Period:</strong>\n {{ currentPeriodBegin?.toLocaleString() }} \u2192 {{ currentPeriodEnd?.toLocaleString() }}\n </p>\n <p>\n <strong>Window:</strong> {{ formatWindow(currentWindowMinutes) }}\n </p>\n <p *ngIf=\"baseCumulatives\" class=\"coverage-info\"\n [class.coverage-ok]=\"coveragePercent >= 100\"\n [class.coverage-warn]=\"coveragePercent > 0 && coveragePercent < 100\"\n [class.coverage-error]=\"coveragePercent === 0\">\n <ion-icon [name]=\"coveragePercent >= 100 ? 'checkmark-circle' : 'alert-circle'\"></ion-icon>\n Base data coverage: {{ coveragePercent }}%\n </p>\n <p *ngIf=\"!baseCumulatives\" class=\"coverage-error\">\n <ion-icon name=\"alert-circle\"></ion-icon>\n No base cumulatives available\n </p>\n </div>\n\n <!-- Creation progress -->\n <div *ngIf=\"creating\" class=\"creation-progress\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n <span>{{ creationProgress }}</span>\n </div>\n\n <ion-button [disabled]=\"!canCreateNew || creating\"\n expand=\"block\"\n (click)=\"createNewPeriod()\">\n <ion-icon name=\"add-circle-outline\" slot=\"start\"></ion-icon>\n Create {{ formatWindow(currentWindowMinutes) }} Cumulative\n </ion-button>\n\n <p *ngIf=\"!canCreateNew && !creating\" class=\"create-hint\">\n Create is disabled because base 5-min cumulatives don't fully cover the selected period.\n </p>\n </div>\n </div>\n\n <div class=\"modal-footer\">\n <ion-button fill=\"outline\" (click)=\"cancel()\" [disabled]=\"creating\">\n Cancel\n </ion-button>\n </div>\n </div>\n</div>\n", styles: [".cumulative-selector-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:1000}.cumulative-selector-modal{background:var(--ion-background-color, #fff);border-radius:12px;max-width:500px;width:90%;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 4px 20px #0000004d}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--ion-border-color, #ddd)}.modal-header h2{margin:0;font-size:1.25rem;font-weight:600}.modal-header ion-button{--padding-start: 8px;--padding-end: 8px}.modal-content{flex:1;overflow-y:auto;padding:16px 20px}.loading-state{display:flex;flex-direction:column;align-items:center;padding:40px 0}.loading-state ion-spinner{margin-bottom:16px}.loading-state p{color:var(--ion-color-medium)}.error-message{display:flex;align-items:center;gap:8px;padding:12px;background:var(--ion-color-danger-tint);color:var(--ion-color-danger);border-radius:8px;margin-bottom:16px}.error-message ion-icon{font-size:1.25rem}.periods-section{margin-bottom:24px}.periods-section h3{font-size:1rem;font-weight:600;margin-bottom:12px;color:var(--ion-color-dark)}.periods-section ion-list{border-radius:8px;overflow:hidden}.periods-section ion-item{--padding-start: 12px;--padding-end: 12px}.periods-section ion-item ion-label h2{font-weight:500}.periods-section ion-item ion-label p{font-size:.85rem;color:var(--ion-color-medium)}.periods-section ion-item ion-label .count-info{font-size:.75rem;color:var(--ion-color-primary)}.no-periods{display:flex;flex-direction:column;align-items:center;padding:24px;text-align:center;color:var(--ion-color-medium)}.no-periods ion-icon{font-size:2rem;margin-bottom:8px}.create-section{border-top:1px solid var(--ion-border-color, #ddd);padding-top:16px}.create-section h3{font-size:1rem;font-weight:600;margin-bottom:12px;color:var(--ion-color-dark)}.create-section .create-info{background:var(--ion-color-light);padding:12px;border-radius:8px;margin-bottom:16px}.create-section .create-info p{margin:4px 0;font-size:.9rem}.create-section .coverage-info{display:flex;align-items:center;gap:6px}.create-section .coverage-info ion-icon{font-size:1.1rem}.create-section .coverage-ok{color:var(--ion-color-success)}.create-section .coverage-warn{color:var(--ion-color-warning)}.create-section .coverage-error{color:var(--ion-color-danger)}.create-section .creation-progress{display:flex;align-items:center;gap:12px;padding:16px;background:var(--ion-color-primary-tint);border-radius:8px;margin-bottom:16px}.create-section .creation-progress ion-spinner{--color: var(--ion-color-primary)}.create-section .creation-progress span{color:var(--ion-color-primary);font-size:.9rem}.create-section .create-hint{font-size:.8rem;color:var(--ion-color-medium);text-align:center;margin-top:8px}.modal-footer{padding:16px 20px;border-top:1px solid var(--ion-border-color, #ddd);display:flex;justify-content:flex-end}\n"], dependencies: [{ kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i4.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i4.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i4.IonItem, selector: "ion-item", inputs: ["button", "color", "counter", "counterFormatter", "detail", "detailIcon", "disabled", "download", "fill", "href", "lines", "mode", "rel", "routerAnimation", "routerDirection", "shape", "target", "type"] }, { kind: "component", type: i4.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i4.IonList, selector: "ion-list", inputs: ["inset", "lines", "mode"] }, { kind: "component", type: i4.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3151
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: CumulativeSelectorComponent, decorators: [{
|
|
3152
|
+
type: Component,
|
|
3153
|
+
args: [{ selector: 'cumulative-selector', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"cumulative-selector-overlay\">\n <div class=\"cumulative-selector-modal\">\n <div class=\"modal-header\">\n <h2>Select Cumulative Period</h2>\n <ion-button fill=\"clear\" (click)=\"cancel()\">\n <ion-icon name=\"close\"></ion-icon>\n </ion-button>\n </div>\n\n <div class=\"modal-content\">\n <!-- Loading state -->\n <div *ngIf=\"loading\" class=\"loading-state\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n <p>Loading available periods...</p>\n </div>\n\n <!-- Error message -->\n <div *ngIf=\"errorMessage\" class=\"error-message\">\n <ion-icon name=\"warning-outline\"></ion-icon>\n <span>{{ errorMessage }}</span>\n </div>\n\n <!-- Available periods list -->\n <div *ngIf=\"!loading && availablePeriods.length > 0\" class=\"periods-section\">\n <h3>Available Cumulative Periods</h3>\n <ion-list>\n <ion-item *ngFor=\"let period of availablePeriods\"\n button\n (click)=\"selectPeriod(period)\"\n [disabled]=\"creating\">\n <ion-icon name=\"layers-outline\" slot=\"start\"></ion-icon>\n <ion-label>\n <h2>{{ formatWindow(period.windowInMinutes) }}</h2>\n <p>{{ formatPeriod(period) }}</p>\n <p class=\"count-info\">{{ period.count }} cumulative(s)</p>\n </ion-label>\n <ion-icon name=\"chevron-forward\" slot=\"end\"></ion-icon>\n </ion-item>\n </ion-list>\n </div>\n\n <!-- No periods available -->\n <div *ngIf=\"!loading && availablePeriods.length === 0\" class=\"no-periods\">\n <ion-icon name=\"information-circle-outline\"></ion-icon>\n <p>No cumulative periods available yet.</p>\n </div>\n\n <!-- Create new section (admin only) -->\n <div *ngIf=\"!loading && isAdmin\" class=\"create-section\">\n <h3>Create New Cumulative</h3>\n <div class=\"create-info\">\n <p>\n <strong>Period:</strong>\n {{ currentPeriodBegin?.toLocaleString() }} \u2192 {{ currentPeriodEnd?.toLocaleString() }}\n </p>\n <p>\n <strong>Window:</strong> {{ formatWindow(currentWindowMinutes) }}\n </p>\n <p *ngIf=\"baseCumulatives\" class=\"coverage-info\"\n [class.coverage-ok]=\"coveragePercent >= 100\"\n [class.coverage-warn]=\"coveragePercent > 0 && coveragePercent < 100\"\n [class.coverage-error]=\"coveragePercent === 0\">\n <ion-icon [name]=\"coveragePercent >= 100 ? 'checkmark-circle' : 'alert-circle'\"></ion-icon>\n Base data coverage: {{ coveragePercent }}%\n </p>\n <p *ngIf=\"!baseCumulatives\" class=\"coverage-error\">\n <ion-icon name=\"alert-circle\"></ion-icon>\n No base cumulatives available\n </p>\n </div>\n\n <!-- Creation progress -->\n <div *ngIf=\"creating\" class=\"creation-progress\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n <span>{{ creationProgress }}</span>\n </div>\n\n <ion-button [disabled]=\"!canCreateNew || creating\"\n expand=\"block\"\n (click)=\"createNewPeriod()\">\n <ion-icon name=\"add-circle-outline\" slot=\"start\"></ion-icon>\n Create {{ formatWindow(currentWindowMinutes) }} Cumulative\n </ion-button>\n\n <p *ngIf=\"!canCreateNew && !creating\" class=\"create-hint\">\n Create is disabled because base 5-min cumulatives don't fully cover the selected period.\n </p>\n </div>\n </div>\n\n <div class=\"modal-footer\">\n <ion-button fill=\"outline\" (click)=\"cancel()\" [disabled]=\"creating\">\n Cancel\n </ion-button>\n </div>\n </div>\n</div>\n", styles: [".cumulative-selector-overlay{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:1000}.cumulative-selector-modal{background:var(--ion-background-color, #fff);border-radius:12px;max-width:500px;width:90%;max-height:80vh;display:flex;flex-direction:column;box-shadow:0 4px 20px #0000004d}.modal-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--ion-border-color, #ddd)}.modal-header h2{margin:0;font-size:1.25rem;font-weight:600}.modal-header ion-button{--padding-start: 8px;--padding-end: 8px}.modal-content{flex:1;overflow-y:auto;padding:16px 20px}.loading-state{display:flex;flex-direction:column;align-items:center;padding:40px 0}.loading-state ion-spinner{margin-bottom:16px}.loading-state p{color:var(--ion-color-medium)}.error-message{display:flex;align-items:center;gap:8px;padding:12px;background:var(--ion-color-danger-tint);color:var(--ion-color-danger);border-radius:8px;margin-bottom:16px}.error-message ion-icon{font-size:1.25rem}.periods-section{margin-bottom:24px}.periods-section h3{font-size:1rem;font-weight:600;margin-bottom:12px;color:var(--ion-color-dark)}.periods-section ion-list{border-radius:8px;overflow:hidden}.periods-section ion-item{--padding-start: 12px;--padding-end: 12px}.periods-section ion-item ion-label h2{font-weight:500}.periods-section ion-item ion-label p{font-size:.85rem;color:var(--ion-color-medium)}.periods-section ion-item ion-label .count-info{font-size:.75rem;color:var(--ion-color-primary)}.no-periods{display:flex;flex-direction:column;align-items:center;padding:24px;text-align:center;color:var(--ion-color-medium)}.no-periods ion-icon{font-size:2rem;margin-bottom:8px}.create-section{border-top:1px solid var(--ion-border-color, #ddd);padding-top:16px}.create-section h3{font-size:1rem;font-weight:600;margin-bottom:12px;color:var(--ion-color-dark)}.create-section .create-info{background:var(--ion-color-light);padding:12px;border-radius:8px;margin-bottom:16px}.create-section .create-info p{margin:4px 0;font-size:.9rem}.create-section .coverage-info{display:flex;align-items:center;gap:6px}.create-section .coverage-info ion-icon{font-size:1.1rem}.create-section .coverage-ok{color:var(--ion-color-success)}.create-section .coverage-warn{color:var(--ion-color-warning)}.create-section .coverage-error{color:var(--ion-color-danger)}.create-section .creation-progress{display:flex;align-items:center;gap:12px;padding:16px;background:var(--ion-color-primary-tint);border-radius:8px;margin-bottom:16px}.create-section .creation-progress ion-spinner{--color: var(--ion-color-primary)}.create-section .creation-progress span{color:var(--ion-color-primary);font-size:.9rem}.create-section .create-hint{font-size:.8rem;color:var(--ion-color-medium);text-align:center;margin-top:8px}.modal-footer{padding:16px 20px;border-top:1px solid var(--ion-border-color, #ddd);display:flex;justify-content:flex-end}\n"] }]
|
|
3154
|
+
}], ctorParameters: function () { return [{ type: ProfileService }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { rainId: [{
|
|
3155
|
+
type: Input
|
|
3156
|
+
}], currentPeriodBegin: [{
|
|
3157
|
+
type: Input
|
|
3158
|
+
}], currentPeriodEnd: [{
|
|
3159
|
+
type: Input
|
|
3160
|
+
}], provider: [{
|
|
3161
|
+
type: Input
|
|
3162
|
+
}], timeStepInMinutes: [{
|
|
3163
|
+
type: Input
|
|
3164
|
+
}], isAdmin: [{
|
|
3165
|
+
type: Input
|
|
3166
|
+
}], periodSelected: [{
|
|
3167
|
+
type: Output
|
|
3168
|
+
}], cancelled: [{
|
|
3169
|
+
type: Output
|
|
3170
|
+
}] } });
|
|
3171
|
+
|
|
3172
|
+
let TEST_DETECTION = 0;
|
|
3173
|
+
class RaainDetailsComponent {
|
|
3174
|
+
constructor(storage, cdr) {
|
|
3175
|
+
this.storage = storage;
|
|
3176
|
+
this.cdr = cdr;
|
|
3177
|
+
this.availableProviders = [];
|
|
3178
|
+
this.availableTimeSteps = [];
|
|
3179
|
+
this.showPixelMarkers = false;
|
|
3180
|
+
this.qualityIndicators = [];
|
|
3181
|
+
this.qualityIndicatorsLoading = false;
|
|
3182
|
+
this.showCumulativeSelector = false;
|
|
3183
|
+
// Cached computed values (to avoid method calls in templates)
|
|
3184
|
+
this.percentOfComputations = 0;
|
|
3185
|
+
this.percentOfImages = 0;
|
|
3186
|
+
this.truncatedError = '';
|
|
3187
|
+
this.cumulativeDurationInMinutes = 10;
|
|
3188
|
+
this.DateRange = DateRange;
|
|
3189
|
+
// Wrapper function that preserves the async nature of fetchData
|
|
3190
|
+
this.fetchDataWrapper = async (focusDate, focusRange) => {
|
|
3191
|
+
return await this.fetchData(focusDate, focusRange);
|
|
3192
|
+
};
|
|
3093
3193
|
}
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
}
|
|
3101
|
-
const queryString = BuildQueryString(params);
|
|
3102
|
-
try {
|
|
3103
|
-
const resp = await this.fidjService.sendOnEndpoint({
|
|
3104
|
-
key: 'rains',
|
|
3105
|
-
verb: 'GET',
|
|
3106
|
-
relativePath: queryString ? '?' + queryString : '',
|
|
3107
|
-
defaultKeyUrl: this.defaultUrlForAPI + '/rains',
|
|
3108
|
-
});
|
|
3109
|
-
for (const rain of resp.data.rains) {
|
|
3110
|
-
rains.push(new RainNode(rain));
|
|
3111
|
-
}
|
|
3112
|
-
}
|
|
3113
|
-
catch (e) {
|
|
3114
|
-
await this.checkError(e);
|
|
3194
|
+
static PeriodDisplay(date) {
|
|
3195
|
+
let d = new Date();
|
|
3196
|
+
if (date) {
|
|
3197
|
+
d = new Date(date);
|
|
3198
|
+
const userTimezoneOffset = d.getTimezoneOffset() * 60000;
|
|
3199
|
+
d = new Date(d.getTime() - userTimezoneOffset);
|
|
3115
3200
|
}
|
|
3116
|
-
|
|
3201
|
+
const exampleFormattedDate = '2017-06-01T08:30';
|
|
3202
|
+
return d.toISOString().substring(0, exampleFormattedDate.length);
|
|
3117
3203
|
}
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
defaultKeyUrl: this.defaultUrlForAPI + '/rains/',
|
|
3125
|
-
});
|
|
3126
|
-
return new RainNode(resp.data);
|
|
3204
|
+
static DateUTC(date) {
|
|
3205
|
+
const hasISOFormat = date.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
|
|
3206
|
+
let d = new Date(date);
|
|
3207
|
+
if (!hasISOFormat) {
|
|
3208
|
+
const userTimezoneOffset = d.getTimezoneOffset() * 60000;
|
|
3209
|
+
d = new Date(d.getTime() - userTimezoneOffset);
|
|
3127
3210
|
}
|
|
3128
|
-
|
|
3129
|
-
|
|
3211
|
+
return d;
|
|
3212
|
+
}
|
|
3213
|
+
static MapCountToDateValue(c) {
|
|
3214
|
+
return {
|
|
3215
|
+
date: RaainDetailsComponent.DateUTC(c.name),
|
|
3216
|
+
value: Math.min(100, c.x),
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
async openQualityModal() {
|
|
3220
|
+
this.showQualityModal = true;
|
|
3221
|
+
this.qualityIndicatorsLoading = true;
|
|
3222
|
+
this.qualityIndicators = [];
|
|
3223
|
+
this.cdr.markForCheck();
|
|
3224
|
+
if (this.rainNode?.id) {
|
|
3225
|
+
const result = await this.profileService.getIndicators(this.rainNode.id);
|
|
3226
|
+
this.qualityIndicators = result.indicators;
|
|
3130
3227
|
}
|
|
3131
|
-
|
|
3228
|
+
this.qualityIndicatorsLoading = false;
|
|
3229
|
+
this.cdr.markForCheck();
|
|
3132
3230
|
}
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
});
|
|
3147
|
-
const counts = resp.data.counts.result;
|
|
3148
|
-
const percentImages = [], percentRainy = [], percentQ = [];
|
|
3149
|
-
counts.forEach((c) => {
|
|
3150
|
-
const label = this.setDateComponents(options.periodBegin, c);
|
|
3151
|
-
percentImages.push(new XYType(c.percentImages ?? 0, NaN, NaN, label));
|
|
3152
|
-
percentRainy.push(new XYType(c.percentRainy ?? 0, NaN, NaN, label));
|
|
3153
|
-
percentQ.push(new XYType(c.percentQ ?? 0, NaN, NaN, label));
|
|
3154
|
-
});
|
|
3155
|
-
return {
|
|
3156
|
-
percentImages,
|
|
3157
|
-
percentRainy,
|
|
3158
|
-
percentQ,
|
|
3159
|
-
queueRunning: resp.data.queueRunning,
|
|
3160
|
-
};
|
|
3231
|
+
closeQualityModal() {
|
|
3232
|
+
this.showQualityModal = false;
|
|
3233
|
+
}
|
|
3234
|
+
formatDate(dateStr) {
|
|
3235
|
+
const date = new Date(dateStr);
|
|
3236
|
+
return (date.toLocaleDateString() +
|
|
3237
|
+
' ' +
|
|
3238
|
+
date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
|
|
3239
|
+
}
|
|
3240
|
+
async fetchData(focusDate, focusRange) {
|
|
3241
|
+
const values = [];
|
|
3242
|
+
for (let i = 0; i < 10; i++) {
|
|
3243
|
+
values.push({ date: new Date(2020 + i, 0), value: 10 });
|
|
3161
3244
|
}
|
|
3162
|
-
|
|
3163
|
-
|
|
3245
|
+
const fakeData = [
|
|
3246
|
+
{
|
|
3247
|
+
label: '% Rainy',
|
|
3248
|
+
style: 'bar',
|
|
3249
|
+
values,
|
|
3250
|
+
},
|
|
3251
|
+
{
|
|
3252
|
+
label: '% Images',
|
|
3253
|
+
style: 'bar',
|
|
3254
|
+
values,
|
|
3255
|
+
},
|
|
3256
|
+
// {
|
|
3257
|
+
// label: '% Quality',
|
|
3258
|
+
// style: 'line',
|
|
3259
|
+
// values,
|
|
3260
|
+
// },
|
|
3261
|
+
];
|
|
3262
|
+
const range = mapDateRangeToString(focusRange);
|
|
3263
|
+
let data = fakeData;
|
|
3264
|
+
if (!this.rainNode) {
|
|
3265
|
+
return data;
|
|
3164
3266
|
}
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
const
|
|
3170
|
-
|
|
3171
|
-
begin: options.periodBegin.toISOString(),
|
|
3172
|
-
};
|
|
3173
|
-
const queryString = BuildQueryString(params);
|
|
3174
|
-
const resp = await this.fidjService.sendOnEndpoint({
|
|
3175
|
-
key: 'rains',
|
|
3176
|
-
relativePath: rainId + '/counts' + (queryString ? '?' + queryString : ''),
|
|
3177
|
-
verb: 'GET',
|
|
3178
|
-
defaultKeyUrl: this.defaultUrlForAPI + '/rains/',
|
|
3267
|
+
if (focusRange === DateRange.CENTURY) {
|
|
3268
|
+
// fake
|
|
3269
|
+
}
|
|
3270
|
+
else if (focusRange === DateRange.HOUR) {
|
|
3271
|
+
const hourCounts = await this.profileService.getCountsHour(this.rainNode.id, {
|
|
3272
|
+
periodBegin: focusDate,
|
|
3179
3273
|
});
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3274
|
+
data = [
|
|
3275
|
+
{
|
|
3276
|
+
label: 'Rainy Count',
|
|
3277
|
+
style: 'line',
|
|
3278
|
+
values: hourCounts.rainyCount.map(RaainDetailsComponent.MapCountToDateValue),
|
|
3279
|
+
},
|
|
3280
|
+
{
|
|
3281
|
+
label: '% Images',
|
|
3282
|
+
style: 'bar',
|
|
3283
|
+
values: hourCounts.percentImages.map(RaainDetailsComponent.MapCountToDateValue),
|
|
3284
|
+
},
|
|
3285
|
+
{
|
|
3286
|
+
label: 'Rainy Sum',
|
|
3287
|
+
style: 'line',
|
|
3288
|
+
values: hourCounts.rainySum.map(RaainDetailsComponent.MapCountToDateValue),
|
|
3289
|
+
},
|
|
3290
|
+
];
|
|
3291
|
+
}
|
|
3292
|
+
else {
|
|
3293
|
+
const counts = await this.profileService.getCounts(this.rainNode.id, {
|
|
3294
|
+
range,
|
|
3295
|
+
periodBegin: focusDate,
|
|
3187
3296
|
});
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3297
|
+
data = [
|
|
3298
|
+
{
|
|
3299
|
+
label: '% Rainy',
|
|
3300
|
+
style: 'bar',
|
|
3301
|
+
values: counts.percentRainy.map(RaainDetailsComponent.MapCountToDateValue),
|
|
3302
|
+
},
|
|
3303
|
+
{
|
|
3304
|
+
label: '% Images',
|
|
3305
|
+
style: 'bar',
|
|
3306
|
+
values: counts.percentImages.map(RaainDetailsComponent.MapCountToDateValue),
|
|
3307
|
+
},
|
|
3308
|
+
];
|
|
3194
3309
|
}
|
|
3195
|
-
|
|
3196
|
-
|
|
3310
|
+
// console.log(`fetchData DONE ${range}`, data);
|
|
3311
|
+
return data;
|
|
3312
|
+
}
|
|
3313
|
+
ngOnChanges(changes) {
|
|
3314
|
+
this.change(changes).then((ignored) => { });
|
|
3315
|
+
}
|
|
3316
|
+
ngOnDestroy() {
|
|
3317
|
+
this.cleanAll();
|
|
3318
|
+
}
|
|
3319
|
+
async onEnableCountHistoryTab(rain) {
|
|
3320
|
+
if (!this.toggleHistory) {
|
|
3321
|
+
this.countPoints = [];
|
|
3197
3322
|
}
|
|
3198
|
-
return null;
|
|
3199
3323
|
}
|
|
3200
|
-
|
|
3201
|
-
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3324
|
+
async onPeriodBeginChange(event) {
|
|
3325
|
+
const newValue = event?.target?.value ?? this.periodBeginAsString;
|
|
3326
|
+
this.periodBegin = new Date(newValue);
|
|
3327
|
+
this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
|
|
3328
|
+
this.storage.set('raain-periodBegin', this.periodBegin);
|
|
3329
|
+
await this.onPeriodDurationChange(event);
|
|
3330
|
+
}
|
|
3331
|
+
async onPeriodEndChange(event) {
|
|
3332
|
+
const newValue = event?.target?.value ?? this.periodEndAsString;
|
|
3333
|
+
this.periodEnd = new Date(newValue);
|
|
3334
|
+
this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
|
|
3335
|
+
this.storage.set('raain-periodEnd', this.periodEnd);
|
|
3336
|
+
this.periodBegin = new Date(this.periodEnd.getTime() - this.getDurationInHours() * RaainDetailsComponent.HOUR_MS);
|
|
3337
|
+
this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
|
|
3338
|
+
this.storage.set('raain-periodBegin', this.periodBegin);
|
|
3339
|
+
this.updateRefreshManagerPeriod();
|
|
3340
|
+
}
|
|
3341
|
+
async onPeriodDurationChange(_event) {
|
|
3342
|
+
const durationInHours = this.getDurationInHours();
|
|
3343
|
+
this.periodEnd = new Date(this.periodBegin.getTime() + durationInHours * RaainDetailsComponent.HOUR_MS);
|
|
3344
|
+
this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
|
|
3345
|
+
this.storage.set('raain-periodEnd', this.periodEnd);
|
|
3346
|
+
this.storage.set('raain-periodDurationInHours', durationInHours);
|
|
3347
|
+
this.updateRefreshManagerPeriod();
|
|
3348
|
+
this.updateCumulativeDurationInMinutes();
|
|
3349
|
+
}
|
|
3350
|
+
async onDateChangeInCount(dateChanged) {
|
|
3351
|
+
const dateString = dateChanged.toISOString().substring(0, 11) +
|
|
3352
|
+
dateChanged.toLocaleTimeString().substring(0, 5);
|
|
3353
|
+
this.periodDurationAsString = '1';
|
|
3354
|
+
if (this.toggleCumulative) {
|
|
3355
|
+
// Cumulative: select periodEnd
|
|
3356
|
+
this.periodEndAsString = dateString;
|
|
3357
|
+
await this.onPeriodEndChange(null);
|
|
3217
3358
|
}
|
|
3218
|
-
|
|
3219
|
-
|
|
3359
|
+
else {
|
|
3360
|
+
// Granular: select periodBegin
|
|
3361
|
+
this.periodBeginAsString = dateString;
|
|
3362
|
+
await this.onPeriodBeginChange(null);
|
|
3220
3363
|
}
|
|
3221
|
-
|
|
3364
|
+
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
3222
3365
|
}
|
|
3223
|
-
async
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
const rainComputationMap = new RainComputationMap(response.data['cumulative']);
|
|
3236
|
-
rainComputationMap.name = rainId + '.rain.cumulative-cumulative';
|
|
3237
|
-
return rainComputationMap;
|
|
3238
|
-
}
|
|
3239
|
-
catch (e) {
|
|
3240
|
-
await this.checkError(e);
|
|
3366
|
+
async onDateChangeInMap(dateShown) {
|
|
3367
|
+
this.dateShown = dateShown;
|
|
3368
|
+
await this.fetchAndUpdateMap();
|
|
3369
|
+
await this.refreshManager.setReportPeriod(this.dateShown);
|
|
3370
|
+
}
|
|
3371
|
+
async onSumChangeInMap(sum) {
|
|
3372
|
+
this.sumDetails = sum;
|
|
3373
|
+
}
|
|
3374
|
+
async onGaugeSelectInMap(mapLatLng) {
|
|
3375
|
+
const gaugesFiltered = this.compareManager.gaugesInMap.filter((g) => g.lat === mapLatLng.lat && g.lng === mapLatLng.lng);
|
|
3376
|
+
if (gaugesFiltered.length !== 1) {
|
|
3377
|
+
return;
|
|
3241
3378
|
}
|
|
3242
|
-
|
|
3379
|
+
const gaugeSelected = gaugesFiltered[0];
|
|
3380
|
+
await this.refreshGaugeValues({ id: gaugeSelected.id, name: gaugeSelected.name });
|
|
3381
|
+
await this.compareManager.selectGauge(gaugeSelected.id, 0);
|
|
3243
3382
|
}
|
|
3244
|
-
async
|
|
3245
|
-
const
|
|
3246
|
-
const
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3383
|
+
async refreshGaugeValues(gaugeSelected) {
|
|
3384
|
+
const gaugeValueShowBegin = new Date(this.periodBegin.getTime() - RaainDetailsComponent.DAY_MS);
|
|
3385
|
+
const gaugeValueShowEnd = new Date(this.periodEnd.getTime() + RaainDetailsComponent.DAY_MS);
|
|
3386
|
+
gaugeValueShowBegin.setHours(0, 0);
|
|
3387
|
+
gaugeValueShowEnd.setHours(23, 59);
|
|
3388
|
+
const gaugeMeasures = await this.profileService.getGaugeMeasures(gaugeSelected.id, gaugeValueShowBegin, gaugeValueShowEnd);
|
|
3389
|
+
const gaugeValues = gaugeMeasures.map((gm) => {
|
|
3390
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
3391
|
+
const cartesianMeasureValue = new CartesianMeasureValue(gm.values[0]);
|
|
3392
|
+
return {
|
|
3393
|
+
date: gm.date,
|
|
3394
|
+
value: cartesianMeasureValue.getCartesianValues()[0].value,
|
|
3395
|
+
};
|
|
3396
|
+
});
|
|
3397
|
+
this.gaugeSelectedPoints = [
|
|
3398
|
+
{
|
|
3399
|
+
label: gaugeSelected.name,
|
|
3400
|
+
style: 'bar',
|
|
3401
|
+
values: gaugeValues,
|
|
3402
|
+
},
|
|
3403
|
+
];
|
|
3404
|
+
this.cdr.markForCheck();
|
|
3405
|
+
}
|
|
3406
|
+
async onGaugeSelectInCompare(e) {
|
|
3407
|
+
await this.refreshGaugeValues({ id: e.point.id, name: e.point.name });
|
|
3408
|
+
await this.compareManager.selectGauge(e.point.id, e.compareIndex);
|
|
3409
|
+
}
|
|
3410
|
+
async onToggleMap($event) {
|
|
3411
|
+
// if (this.toggleMap) {
|
|
3412
|
+
// await this.refreshMap();
|
|
3413
|
+
// }
|
|
3414
|
+
}
|
|
3415
|
+
onTogglePixelMarkers() {
|
|
3416
|
+
// Toggle is bound to showPixelMarkers, raain-map handles marker display
|
|
3417
|
+
}
|
|
3418
|
+
async onCumulativeToggleClick($event) {
|
|
3419
|
+
console.log('[CumulativeToggle] click event', {
|
|
3420
|
+
currentValue: this.toggleCumulative,
|
|
3421
|
+
eventType: $event.type,
|
|
3422
|
+
target: $event.target?.tagName,
|
|
3423
|
+
});
|
|
3424
|
+
$event.preventDefault();
|
|
3425
|
+
$event.stopPropagation();
|
|
3426
|
+
if (!this.toggleCumulative) {
|
|
3427
|
+
console.log('[CumulativeToggle] showing popup, toggle stays OFF');
|
|
3428
|
+
// Currently OFF, user wants to enable - show selector popup
|
|
3429
|
+
this.showCumulativeSelector = true;
|
|
3430
|
+
// Force reset the toggle visual state after ion-toggle's internal handler
|
|
3431
|
+
setTimeout(() => {
|
|
3432
|
+
if (this.cumulativeToggleRef) {
|
|
3433
|
+
this.cumulativeToggleRef.checked = false;
|
|
3434
|
+
console.log('[CumulativeToggle] force reset toggle to OFF');
|
|
3435
|
+
}
|
|
3436
|
+
}, 0);
|
|
3437
|
+
this.cdr.markForCheck();
|
|
3259
3438
|
}
|
|
3260
|
-
|
|
3261
|
-
|
|
3439
|
+
else {
|
|
3440
|
+
console.log('[CumulativeToggle] turning OFF');
|
|
3441
|
+
// Currently ON, user wants to disable - direct toggle off
|
|
3442
|
+
this.toggleCumulative = false;
|
|
3443
|
+
this.storage.set('raain-toggleCumulative', false);
|
|
3444
|
+
this.dateShown = this.getDateBasedOnCumulativeMode(this.timeframeDates);
|
|
3445
|
+
this.updateCumulativeDurationInMinutes();
|
|
3446
|
+
if (this.toggleMap) {
|
|
3447
|
+
this.updateRefreshManagerPeriod();
|
|
3448
|
+
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
3449
|
+
}
|
|
3450
|
+
this.cdr.markForCheck();
|
|
3262
3451
|
}
|
|
3263
|
-
|
|
3452
|
+
console.log('[CumulativeToggle] after handler, toggleCumulative =', this.toggleCumulative);
|
|
3264
3453
|
}
|
|
3265
|
-
async
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3454
|
+
async onCumulativePeriodSelected(selection) {
|
|
3455
|
+
this.showCumulativeSelector = false;
|
|
3456
|
+
// Update period to match selection
|
|
3457
|
+
this.periodBegin = selection.periodBegin;
|
|
3458
|
+
this.periodEnd = selection.periodEnd;
|
|
3459
|
+
this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
|
|
3460
|
+
this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
|
|
3461
|
+
// Calculate duration in hours
|
|
3462
|
+
const durationMs = this.periodEnd.getTime() - this.periodBegin.getTime();
|
|
3463
|
+
const durationHours = durationMs / RaainDetailsComponent.HOUR_MS;
|
|
3464
|
+
this.periodDurationAsString = String(durationHours);
|
|
3465
|
+
// Store cumulative period
|
|
3466
|
+
this.storage.set('raain-periodBegin', this.periodBegin);
|
|
3467
|
+
this.storage.set('raain-periodEnd', this.periodEnd);
|
|
3468
|
+
this.storage.set('raain-periodDurationInHours', durationHours);
|
|
3469
|
+
// Enable cumulative mode
|
|
3470
|
+
this.toggleCumulative = true;
|
|
3471
|
+
this.storage.set('raain-toggleCumulative', true);
|
|
3472
|
+
this.dateShown = this.getDateBasedOnCumulativeMode(this.timeframeDates);
|
|
3473
|
+
this.updateCumulativeDurationInMinutes();
|
|
3474
|
+
// Refresh map
|
|
3475
|
+
if (this.toggleMap) {
|
|
3476
|
+
this.updateRefreshManagerPeriod();
|
|
3477
|
+
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
3286
3478
|
}
|
|
3287
|
-
|
|
3479
|
+
this.cdr.markForCheck();
|
|
3288
3480
|
}
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
// return response.data.inProgress;
|
|
3299
|
-
return response.data.inQueue;
|
|
3481
|
+
onCumulativeSelectorCancelled() {
|
|
3482
|
+
console.log('[CumulativeToggle] cancelled, toggleCumulative =', this.toggleCumulative);
|
|
3483
|
+
this.showCumulativeSelector = false;
|
|
3484
|
+
this.cdr.markForCheck();
|
|
3485
|
+
}
|
|
3486
|
+
updateCumulativeDurationInMinutes() {
|
|
3487
|
+
if (this.toggleCumulative) {
|
|
3488
|
+
// Cumulative mode: use period duration
|
|
3489
|
+
this.cumulativeDurationInMinutes = parseFloat(this.periodDurationAsString) * 60;
|
|
3300
3490
|
}
|
|
3301
|
-
|
|
3302
|
-
|
|
3491
|
+
else {
|
|
3492
|
+
// Granular mode: use selectedTimeStep
|
|
3493
|
+
this.cumulativeDurationInMinutes = this.selectedTimeStep || 10;
|
|
3303
3494
|
}
|
|
3304
|
-
return 0;
|
|
3305
3495
|
}
|
|
3306
|
-
async
|
|
3496
|
+
async onProviderChanged($event) {
|
|
3497
|
+
this.selectedProvider = $event?.detail?.value;
|
|
3498
|
+
this.storage.set('raain-selectedProvider', this.selectedProvider);
|
|
3499
|
+
await this.applyRefreshManagerSettings();
|
|
3500
|
+
}
|
|
3501
|
+
async onTimeStepChanged($event) {
|
|
3502
|
+
this.selectedTimeStep = $event?.detail?.value;
|
|
3503
|
+
this.storage.set('raain-selectedTimeStep', this.selectedTimeStep);
|
|
3504
|
+
this.updateCumulativeDurationInMinutes();
|
|
3505
|
+
await this.applyRefreshManagerSettings();
|
|
3506
|
+
}
|
|
3507
|
+
async loadProviders() {
|
|
3508
|
+
if (!this.rainNode?.id) {
|
|
3509
|
+
return;
|
|
3510
|
+
}
|
|
3307
3511
|
try {
|
|
3308
|
-
const
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
return response.data;
|
|
3512
|
+
const result = await this.profileService.getProviders(this.rainNode.id);
|
|
3513
|
+
this.availableProviders = result.providers;
|
|
3514
|
+
this.availableTimeSteps = result.timeStepInMinutes;
|
|
3515
|
+
// Load saved selections or use defaults
|
|
3516
|
+
this.selectedProvider =
|
|
3517
|
+
this.storage.get('raain-selectedProvider') ||
|
|
3518
|
+
(this.availableProviders.length > 0 ? this.availableProviders[0] : 'Raain');
|
|
3519
|
+
this.selectedTimeStep =
|
|
3520
|
+
this.storage.get('raain-selectedTimeStep') ||
|
|
3521
|
+
(this.availableTimeSteps.length > 0 ? this.availableTimeSteps[0] : 10);
|
|
3319
3522
|
}
|
|
3320
3523
|
catch (e) {
|
|
3321
|
-
|
|
3524
|
+
// Set fallback values
|
|
3525
|
+
this.availableProviders = ['Raain'];
|
|
3526
|
+
this.availableTimeSteps = [5, 10, 15, 30, 60];
|
|
3527
|
+
this.selectedProvider = 'Raain';
|
|
3528
|
+
this.selectedTimeStep = 10;
|
|
3322
3529
|
}
|
|
3323
|
-
|
|
3530
|
+
// Set provider and timeStep on refreshManager
|
|
3531
|
+
if (this.refreshManager) {
|
|
3532
|
+
this.refreshManager.provider = this.selectedProvider;
|
|
3533
|
+
this.refreshManager.timeStepInMinutes = this.selectedTimeStep;
|
|
3534
|
+
}
|
|
3535
|
+
this.cdr.markForCheck();
|
|
3324
3536
|
}
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
return new GaugeNode(resp.data);
|
|
3537
|
+
onChangeDetectionTest(rainNode) {
|
|
3538
|
+
console.log(TEST_DETECTION++, 'onChangeDetectionTest');
|
|
3539
|
+
return '';
|
|
3540
|
+
}
|
|
3541
|
+
updateTruncatedError() {
|
|
3542
|
+
const error = this.refreshManager?.lastError || '';
|
|
3543
|
+
if (error.length <= 80) {
|
|
3544
|
+
this.truncatedError = error;
|
|
3545
|
+
return;
|
|
3335
3546
|
}
|
|
3336
|
-
|
|
3337
|
-
|
|
3547
|
+
this.truncatedError = error.substring(0, 80) + '...';
|
|
3548
|
+
}
|
|
3549
|
+
updateCachedValues() {
|
|
3550
|
+
this.updatePercentOfImages();
|
|
3551
|
+
this.updatePercentOfComputations();
|
|
3552
|
+
this.updateTruncatedError();
|
|
3553
|
+
this.updateCumulativeDurationInMinutes();
|
|
3554
|
+
}
|
|
3555
|
+
async refreshMap() {
|
|
3556
|
+
this.gaugeSelectedPoints = [];
|
|
3557
|
+
this.dateShown = this.getDateBasedOnCumulativeMode();
|
|
3558
|
+
this.borders = [];
|
|
3559
|
+
this.compareManager.cleanAll();
|
|
3560
|
+
await this.compareManager.setGaugesInMap();
|
|
3561
|
+
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
3562
|
+
this.cdr.markForCheck();
|
|
3563
|
+
}
|
|
3564
|
+
async setPeriodOfNow() {
|
|
3565
|
+
const last30mn = new Date();
|
|
3566
|
+
last30mn.setMinutes(last30mn.getMinutes() - 30);
|
|
3567
|
+
this.periodBeginAsString =
|
|
3568
|
+
last30mn.toISOString().substring(0, 11) + last30mn.toLocaleTimeString().substring(0, 5);
|
|
3569
|
+
this.periodDurationAsString = '1';
|
|
3570
|
+
await this.onPeriodBeginChange(null);
|
|
3571
|
+
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
3572
|
+
}
|
|
3573
|
+
updatePercentOfImages() {
|
|
3574
|
+
if (!this.countsPeriod?.percentImages?.length) {
|
|
3575
|
+
this.percentOfImages = 0;
|
|
3576
|
+
return;
|
|
3338
3577
|
}
|
|
3578
|
+
const duringPeriod = this.countsPeriod.percentImages.filter((a) => this.periodBegin.getTime() <= new Date(a.name).getTime() &&
|
|
3579
|
+
new Date(a.name).getTime() <= this.periodEnd.getTime());
|
|
3580
|
+
const summed = duringPeriod.reduce((a, b) => a + (b.x ?? 0), 0);
|
|
3581
|
+
this.percentOfImages = Math.round(summed / duringPeriod.length);
|
|
3339
3582
|
}
|
|
3340
|
-
|
|
3341
|
-
const
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
if (this.asTeamId) {
|
|
3346
|
-
baseParams.teamId = this.asTeamId;
|
|
3583
|
+
updatePercentOfComputations() {
|
|
3584
|
+
const timeline = this.refreshManager?.getTimelineFrameSet();
|
|
3585
|
+
if (!timeline?.length) {
|
|
3586
|
+
this.percentOfComputations = 0;
|
|
3587
|
+
return;
|
|
3347
3588
|
}
|
|
3348
|
-
const
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
for (const gauge of resp.data.gauges) {
|
|
3360
|
-
gauges.push(new GaugeNodeFilter(gauge));
|
|
3361
|
-
}
|
|
3589
|
+
const timelineWithComputation = timeline.filter((a) => !!a.rainComputationCumulativeId || !!a.rainComputationId);
|
|
3590
|
+
const ratio = timelineWithComputation.length / timeline.length;
|
|
3591
|
+
this.percentOfComputations = Math.round(ratio * 100);
|
|
3592
|
+
}
|
|
3593
|
+
getDateBasedOnCumulativeMode(fallbackDates) {
|
|
3594
|
+
if (fallbackDates?.length > 0) {
|
|
3595
|
+
const dateExists = fallbackDates.some((d) => d.getTime() === this.dateShown?.getTime());
|
|
3596
|
+
if (!dateExists) {
|
|
3597
|
+
return this.toggleCumulative
|
|
3598
|
+
? fallbackDates[fallbackDates.length - 1]
|
|
3599
|
+
: fallbackDates[0];
|
|
3362
3600
|
}
|
|
3363
3601
|
}
|
|
3364
|
-
|
|
3365
|
-
await this.checkError(e);
|
|
3366
|
-
}
|
|
3367
|
-
return gauges;
|
|
3602
|
+
return this.toggleCumulative ? this.periodEnd : this.periodBegin;
|
|
3368
3603
|
}
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3604
|
+
getCumulativeHours() {
|
|
3605
|
+
return this.toggleCumulative ? parseFloat(this.periodDurationAsString) : 0;
|
|
3606
|
+
}
|
|
3607
|
+
getDurationInHours() {
|
|
3608
|
+
return parseFloat(this.periodDurationAsString);
|
|
3609
|
+
}
|
|
3610
|
+
updateRefreshManagerPeriod() {
|
|
3611
|
+
this.refreshManager.cumulative = this.toggleCumulative;
|
|
3612
|
+
// Align dates to 5-minute boundaries (floor) for consistency with raain-ground
|
|
3613
|
+
const alignTo5minFloor = (date) => {
|
|
3614
|
+
const minutes = date.getMinutes();
|
|
3615
|
+
const alignedMinutes = Math.floor(minutes / 5) * 5;
|
|
3616
|
+
return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), alignedMinutes, 0, 0);
|
|
3373
3617
|
};
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3377
|
-
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3618
|
+
this.refreshManager.period = {
|
|
3619
|
+
begin: alignTo5minFloor(this.periodBegin),
|
|
3620
|
+
end: alignTo5minFloor(this.periodEnd),
|
|
3621
|
+
};
|
|
3622
|
+
}
|
|
3623
|
+
async fetchAndUpdateMap() {
|
|
3624
|
+
await this.refreshManager.fetch(this.dateShown, this.toggleGaugeMeasures, this.getCumulativeHours());
|
|
3625
|
+
this.currentTimeframeTarget = this.refreshManager.getTimelineSelectedFrameSet();
|
|
3626
|
+
this.cdr.markForCheck();
|
|
3627
|
+
}
|
|
3628
|
+
async applyRefreshManagerSettings() {
|
|
3629
|
+
if (!this.refreshManager) {
|
|
3630
|
+
return;
|
|
3384
3631
|
}
|
|
3385
|
-
|
|
3632
|
+
this.refreshManager.provider = this.selectedProvider;
|
|
3633
|
+
this.refreshManager.timeStepInMinutes = this.selectedTimeStep;
|
|
3634
|
+
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
3386
3635
|
}
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3636
|
+
cleanAll() {
|
|
3637
|
+
this.borders = [];
|
|
3638
|
+
this.isAdmin = false;
|
|
3639
|
+
this.timeframeContainers = new TimeframeContainers([]);
|
|
3640
|
+
this.currentTimeframeTarget = null;
|
|
3641
|
+
this.timeframeDates = [];
|
|
3642
|
+
this.countPoints = [];
|
|
3643
|
+
this.countsPeriod = { progress: 0, queueRunning: 0, percentImages: [] };
|
|
3644
|
+
this.gaugeSelectedPoints = [];
|
|
3645
|
+
this.toggleHistory = false;
|
|
3646
|
+
this.toggleMap = true;
|
|
3647
|
+
this.toggleCompare = false;
|
|
3648
|
+
this.toggleGaugeMeasures = false;
|
|
3649
|
+
this.toggleRemoveCompareDuplicate = true;
|
|
3650
|
+
this.toggleCumulative = this.storage.get('raain-toggleCumulative');
|
|
3651
|
+
this.periodBegin = new Date(this.storage.get('raain-periodBegin'));
|
|
3652
|
+
this.periodEnd = new Date(this.storage.get('raain-periodEnd'));
|
|
3653
|
+
this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
|
|
3654
|
+
this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
|
|
3655
|
+
const durationMs = this.periodEnd.getTime() - this.periodBegin.getTime();
|
|
3656
|
+
this.periodDurationAsString = '' + durationMs / RaainDetailsComponent.HOUR_MS;
|
|
3657
|
+
this.dateShown = this.getDateBasedOnCumulativeMode();
|
|
3658
|
+
this.refreshInProgress = false;
|
|
3659
|
+
this.showFullError = false;
|
|
3660
|
+
this.showQualityModal = false;
|
|
3661
|
+
this.qualityIndicators = [];
|
|
3662
|
+
this.qualityIndicatorsLoading = false;
|
|
3663
|
+
this.compareManager?.cleanAll();
|
|
3664
|
+
this.refreshManager?.cleanAll();
|
|
3665
|
+
}
|
|
3666
|
+
async init() {
|
|
3667
|
+
this.cleanAll();
|
|
3668
|
+
this.updateCachedValues();
|
|
3669
|
+
await this.initRain();
|
|
3670
|
+
this.cdr.markForCheck();
|
|
3671
|
+
}
|
|
3672
|
+
async initRain() {
|
|
3673
|
+
if (!this.rainNode) {
|
|
3674
|
+
return;
|
|
3399
3675
|
}
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3676
|
+
this.isAdmin = this.profileService.isAdmin();
|
|
3677
|
+
this.refreshManager.rainNode = this.rainNode;
|
|
3678
|
+
this.compareManager.rainNode = this.rainNode;
|
|
3679
|
+
// Load providers and set on refreshManager
|
|
3680
|
+
await this.loadProviders();
|
|
3681
|
+
this.refreshManager.setMethods(this.onRefreshInProgress.bind(this), this.onRefreshDone.bind(this), this.onFetchDone.bind(this));
|
|
3682
|
+
const center = this.rainNode.getCenter();
|
|
3683
|
+
this.coordinates = new MapLatLng(center.lat, center.lng);
|
|
3684
|
+
this.teamNode = await this.profileService.getTeam(this.rainNode.getLink(TeamNode.TYPE).getId());
|
|
3685
|
+
// Load all gauges linked to the rainNode on map
|
|
3686
|
+
await this.compareManager.setGaugesInMap();
|
|
3687
|
+
if (this.periodBegin && this.periodEnd) {
|
|
3688
|
+
this.updateRefreshManagerPeriod();
|
|
3689
|
+
await this.refreshManager.refresh(false, this.toggleAdmin);
|
|
3406
3690
|
}
|
|
3407
3691
|
}
|
|
3408
|
-
|
|
3409
|
-
this.
|
|
3692
|
+
async onRefreshInProgress(countPeriods, timeframeDates) {
|
|
3693
|
+
this.refreshInProgress = true;
|
|
3694
|
+
this.countsPeriod = countPeriods;
|
|
3695
|
+
this.timeframeDates = timeframeDates;
|
|
3696
|
+
this.updateCachedValues();
|
|
3697
|
+
this.cdr.markForCheck();
|
|
3410
3698
|
}
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3699
|
+
async onRefreshDone(timeframeDates) {
|
|
3700
|
+
this.timeframeDates = timeframeDates;
|
|
3701
|
+
this.refreshInProgress = false;
|
|
3702
|
+
this.updateCachedValues();
|
|
3703
|
+
this.cdr.markForCheck();
|
|
3704
|
+
if (this.toggleMap && timeframeDates.length > 0) {
|
|
3705
|
+
this.dateShown = this.getDateBasedOnCumulativeMode(timeframeDates);
|
|
3706
|
+
if (this.dateShown) {
|
|
3707
|
+
await this.fetchAndUpdateMap();
|
|
3708
|
+
}
|
|
3415
3709
|
}
|
|
3416
|
-
|
|
3417
|
-
|
|
3710
|
+
}
|
|
3711
|
+
async onFetchDone(timeframeContainers) {
|
|
3712
|
+
if (timeframeContainers) {
|
|
3713
|
+
this.timeframeContainers = timeframeContainers;
|
|
3418
3714
|
}
|
|
3419
|
-
|
|
3420
|
-
|
|
3715
|
+
this.cdr.markForCheck();
|
|
3716
|
+
}
|
|
3717
|
+
async change(_changes) {
|
|
3718
|
+
await this.init();
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
RaainDetailsComponent.HOUR_MS = 60 * 60000;
|
|
3722
|
+
RaainDetailsComponent.DAY_MS = 24 * 60 * 60 * 1000;
|
|
3723
|
+
RaainDetailsComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RaainDetailsComponent, deps: [{ token: Storage }, { token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
3724
|
+
RaainDetailsComponent.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "15.2.10", type: RaainDetailsComponent, selector: "raain-details", inputs: { toggleAdmin: "toggleAdmin", rainNode: "rainNode", compareManager: "compareManager", refreshManager: "refreshManager", profileService: "profileService", radarService: "radarService" }, viewQueries: [{ propertyName: "cumulativeToggleRef", first: true, predicate: ["cumulativeToggle"], descendants: true }], usesOnChanges: true, ngImport: i0, template: "<!-- Main content container -->\n<div *ngIf=\"rainNode\" class=\"raain-details-container\">\n\n <!-- Period selection section -->\n <ion-card class=\"period-card\">\n <ion-card-content>\n <div class=\"period-controls\">\n <div class=\"period-row\">\n\n <ion-button (click)=\"toggleHistory = !toggleHistory; onEnableCountHistoryTab(rainNode)\"\n fill=\"outline\">\n <ion-icon name=\"calendar-clear-outline\" slot=\"start\"></ion-icon>\n <ion-icon [name]=\"toggleHistory ? 'chevron-down' : 'chevron-forward'\" slot=\"end\"></ion-icon>\n </ion-button>\n\n <div class=\"period-start ion-hide-md-down\">\n <input (change)=\"onPeriodBeginChange($event)\"\n [disabled]=\"toggleCumulative\"\n [value]=\"periodBeginAsString\"\n class=\"datetime-input\"\n type=\"datetime-local\">\n </div>\n\n <div class=\"period-duration ion-hide-md-down\">\n <ion-select (ionDismiss)=\"onPeriodDurationChange($event)\"\n [(ngModel)]=\"periodDurationAsString\"\n [disabled]=\"toggleCumulative\"\n class=\"duration-select\"\n id=\"periodDuration\"\n interface=\"popover\">\n <ion-select-option value=\"0.25\">15 minutes</ion-select-option>\n <ion-select-option value=\"0.5\">30 minutes</ion-select-option>\n <ion-select-option value=\"1\">1 hour</ion-select-option>\n <ion-select-option value=\"2\">2 hours</ion-select-option>\n <ion-select-option value=\"3\">3 hours</ion-select-option>\n <ion-select-option value=\"4\">4 hours</ion-select-option>\n <ion-select-option value=\"5\">5 hours</ion-select-option>\n <ion-select-option value=\"6\">6 hours</ion-select-option>\n <ion-select-option value=\"8\">8 hours</ion-select-option>\n <ion-select-option value=\"10\">10 hours</ion-select-option>\n <ion-select-option value=\"12\">12 hours</ion-select-option>\n <ion-select-option *ngIf=\"isAdmin\" value=\"24\">24 hours</ion-select-option>\n </ion-select>\n </div>\n\n <div class=\"period-start ion-hide-md-down\">\n <input (change)=\"onPeriodEndChange($event)\"\n [disabled]=\"toggleCumulative\"\n [value]=\"periodEndAsString\"\n class=\"datetime-input\"\n type=\"datetime-local\">\n </div>\n\n <div class=\"toggle-cumulative\" (click)=\"onCumulativeToggleClick($event)\">\n <ion-label [class.text-primary]=\"toggleCumulative\">\n {{ toggleCumulative ? 'Cumulative' : 'Granular' }}\n </ion-label>\n <ion-toggle #cumulativeToggle [checked]=\"toggleCumulative\">\n </ion-toggle>\n </div>\n </div>\n\n <!-- Hidden label for change detection (uncomment to debug)\n <div class=\"hidden-label\">{{ onChangeDetectionTest(rainNode) }}</div>\n -->\n </div>\n\n <!-- Historical map section -->\n <div *ngIf=\"toggleHistory\" class=\"period-controls\">\n <raain-date-dynamic (changedDate)=\"onDateChangeInCount($event)\"\n [currentHeight]=\"300\"\n [fetchData]=\"fetchDataWrapper\"\n [points]=\"countPoints\">\n </raain-date-dynamic>\n </div>\n </ion-card-content>\n </ion-card>\n\n <!-- Map performance -->\n <ion-grid class=\"map-performance\">\n <ion-row id=\"progressAndRefresh\">\n <ion-col class=\"provider-selection\" size=\"12\" size-md=\"6\">\n <ion-button (click)=\"openQualityModal()\" class=\"quality-info-button\" fill=\"clear\">\n {{ refreshManager.rainComputationMapVersion }}\n <ion-icon name=\"help-circle-outline\" slot=\"end\"></ion-icon>\n </ion-button>\n <div *ngIf=\"availableProviders.length > 0 || availableTimeSteps.length > 0\" class=\"selection-row\">\n <ion-select (ionChange)=\"onProviderChanged($event)\"\n [value]=\"selectedProvider\"\n interface=\"popover\"\n label=\"Provider\"\n placeholder=\"Select Provider\">\n <ion-select-option *ngFor=\"let provider of availableProviders\" [value]=\"provider\">\n {{ provider }}\n </ion-select-option>\n </ion-select>\n\n <ion-select (ionChange)=\"onTimeStepChanged($event)\"\n [value]=\"selectedTimeStep\"\n interface=\"popover\"\n label=\"Time Step\"\n placeholder=\"Select Time Step\">\n <ion-select-option *ngFor=\"let step of availableTimeSteps\" [value]=\"step\">\n {{ step }} min\n </ion-select-option>\n </ion-select>\n </div>\n </ion-col>\n <ion-col class=\"ion-text-right\" size=\"12\" size-md=\"6\">\n <ion-label class=\"ion-margin-end\">\n <span *ngIf=\"percentOfComputations\">\n {{ percentOfComputations }}% Images\n <i *ngIf=\"countsPeriod.progress\" class=\"progress-indicator\">\n In Progress: {{ countsPeriod.progress }}...\n </i>\n </span>\n <span *ngIf=\"!percentOfComputations\">\n No image available\n </span>\n\n </ion-label>\n\n <ion-button (click)=\"refreshMap()\" [disabled]=\"refreshInProgress\" class=\"refresh-button\">\n Refresh Map\n </ion-button>\n </ion-col>\n </ion-row>\n\n <!-- status update row -->\n <ion-row>\n <!-- Progress col -->\n <ion-progress-bar\n [buffer]=\"(countsPeriod.progress / ((timeframeDates?.length || 10) +3))+0.01\"\n [style.visibility]=\"refreshInProgress ? 'visible' : 'hidden'\"\n [value]=\"countsPeriod.progress / ((timeframeDates?.length || 10) +3)\"\n color=\"primary\">\n </ion-progress-bar>\n\n <!-- Error col -->\n <ion-col (click)=\"showFullError = !showFullError\" *ngIf=\"refreshManager.lastError\" class=\"error-row\"\n size=\"12\">\n <div class=\"error-content\">\n <ion-icon class=\"error-icon\" name=\"warning-outline\"></ion-icon>\n <span [class.expanded]=\"showFullError\" class=\"error-text\">\n {{ showFullError ? refreshManager.lastError : truncatedError }}\n </span>\n <ion-icon [name]=\"showFullError ? 'chevron-up' : 'chevron-down'\" class=\"error-caret\"></ion-icon>\n </div>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Map section -->\n <ion-card class=\"map-card\">\n <ion-card-content class=\"map-content\">\n <ion-grid>\n <ion-row *ngIf=\"toggleMap && percentOfImages\">\n <!-- Map component -->\n <ion-col class=\"map-column\" size-lg=\"7\" size-md=\"12\">\n <div class=\"map-container\">\n <raain-map #raainMapRef\n (changedDate)=\"onDateChangeInMap($event)\"\n (changedSum)=\"onSumChangeInMap($event)\"\n (selectedMarker)=\"onGaugeSelectInMap($event)\"\n [coordinates]=\"coordinates\"\n [cumulativeDurationInMinutes]=\"cumulativeDurationInMinutes\"\n [currentHeight]=\"500\"\n [defaultDate]=\"dateShown\"\n [markers]=\"{\n borders,\n gauges: compareManager.gaugesInMap,\n gaugesInCompare: compareManager.gaugesInCompare,\n selectedGauges: compareManager.selectedGauges,\n pixels: compareManager.selectedPixels,\n pixelsSolution: compareManager.pixelsSolutions?.length ? compareManager.pixelsSolutions[0] : [],\n speeds: compareManager.speeds\n }\"\n [showCumulative]=\"toggleCumulative\"\n [showVisiblePixelMarkers]=\"showPixelMarkers\"\n [sumFn]=\"refreshManager.sumFn\"\n [sumValues]=\"refreshManager.sumValues\"\n [timeframeContainers]=\"timeframeContainers\"\n [timeframeDates]=\"timeframeDates\">\n </raain-map>\n </div>\n\n <div class=\"data-column\">\n <!-- Technical details (collapsible for cleaner UI) -->\n <details class=\"technical-details\">\n <summary>Image Details</summary>\n <div class=\"details-content\">\n <div class=\"detail-row\">\n <span class=\"detail-label\">Computed:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapDoneDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Version:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapVersion }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Launched by:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapLaunchedBy }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Time spent:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapTimeSpentInMs }}\n ms</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Date:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapDate?.toISOString() }}\n | {{ refreshManager.rainComputationMapDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Water in the map:</span>\n <span class=\"detail-value\">{{ sumDetails }}</span>\n <ion-toggle\n (ionChange)=\"onTogglePixelMarkers()\"\n [(ngModel)]=\"showPixelMarkers\"\n style=\"margin-left: 8px; transform: scale(0.7);\">\n </ion-toggle>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Source DBZ min:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapOriginalMin }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Source DBZ max:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapOriginalMax }}</span>\n </div>\n </div>\n </details>\n </div>\n </ion-col>\n\n <!-- Data panel -->\n <ion-col *ngIf=\"!!compareManager.compareVersion\" class=\"data-column\" size-lg=\"5\" size-md=\"12\">\n <div class=\"data-panel\">\n <!-- Compare stack component -->\n <div class=\"compare-stack\">\n <raain-compare-stack\n (selectedPoint)=\"onGaugeSelectInCompare($event)\"\n [compareManager]=\"compareManager\"\n [cumulative]=\"toggleCumulative\">\n </raain-compare-stack>\n </div>\n\n <!-- Technical details (collapsible for cleaner UI) -->\n <details class=\"technical-details\">\n <summary>Compare Details</summary>\n <div class=\"details-content\">\n <div [ngClass]=\"{'warning': refreshManager.rainComputationMapDoneDate?.getTime() > compareManager.currentQualityDoneDate?.getTime() + 60000}\"\n class=\"detail-row\">\n <span class=\"detail-label\">Computed:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityDoneDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Version:</span>\n <span class=\"detail-value\">{{ compareManager.compareVersion }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Launched by:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityLaunchedBy }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Time spent:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityTimeSpentInMs }}\n ms</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Gauges:</span>\n <span class=\"detail-value\">{{ compareManager.gaugesInCompare.length }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Points:</span>\n <span class=\"detail-value\">{{ compareManager.globalComparePoints.length }}</span>\n </div>\n </div>\n </details>\n </div>\n </ion-col>\n </ion-row>\n <ion-row>\n <!-- Bottom progress bar -->\n <ion-progress-bar *ngIf=\"refreshInProgress\"\n [buffer]=\"(countsPeriod.progress / ((timeframeDates?.length || 10) +3))+0.01\"\n [value]=\"countsPeriod.progress / ((timeframeDates?.length || 10) +3)\"\n color=\"primary\">\n </ion-progress-bar>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n\n <!-- Gauge values section -->\n <ion-card *ngIf=\"gaugeSelectedPoints.length && gaugeSelectedPoints[0].values.length\" class=\"gauge-card\">\n <ion-card-header>\n <ion-card-title>\n <ion-icon name=\"analytics-outline\"></ion-icon>\n Selected Gauge Data\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <raain-date-focus\n [currentHeight]=\"300\"\n [focusDate]=\"periodBegin\"\n [focusRange]=\"DateRange.DAY\"\n [points]=\"gaugeSelectedPoints\"\n [withoutAll]=\"true\">\n </raain-date-focus>\n </ion-card-content>\n </ion-card>\n\n <!-- Quality Performance Modal -->\n <div (click)=\"closeQualityModal()\" *ngIf=\"showQualityModal\" class=\"quality-modal-overlay\">\n <div (click)=\"$event.stopPropagation()\" class=\"quality-modal-content\">\n <div class=\"quality-modal-header\">\n <h2>Model Quality Performance</h2>\n <ion-button (click)=\"closeQualityModal()\" fill=\"clear\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </div>\n <div class=\"quality-modal-body\">\n <div *ngIf=\"qualityIndicatorsLoading\" class=\"quality-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n <span>Loading indicators...</span>\n </div>\n <div *ngIf=\"!qualityIndicatorsLoading && qualityIndicators.length === 0\" class=\"quality-empty\">\n No quality indicators available for this year.\n </div>\n <table *ngIf=\"!qualityIndicatorsLoading && qualityIndicators.length > 0\" class=\"quality-table\">\n <thead>\n <tr>\n <th>Model</th>\n <th>Compare</th>\n <th>Gauges</th>\n <th>Period</th>\n <th>Avg Quality</th>\n <th>Updated</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let indicator of qualityIndicators\">\n <td>{{ indicator.computingVersion }}</td>\n <td>{{ indicator.qualityVersion }}</td>\n <td>{{ indicator.provider }}<br>{{ indicator.timeStepInMinutes }} min</td>\n <td>{{ formatDate(indicator.startDate) }}<br>{{ formatDate(indicator.endDate) }}</td>\n <td>{{ indicator.averageQuality | number:'1.2-2' }}</td>\n <td>{{ formatDate(indicator.lastUpdatedAt) }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n </div>\n\n <!-- Cumulative Selector Modal -->\n <cumulative-selector *ngIf=\"showCumulativeSelector\"\n [rainId]=\"rainNode?.id\"\n [currentPeriodBegin]=\"periodBegin\"\n [currentPeriodEnd]=\"periodEnd\"\n [provider]=\"selectedProvider\"\n [timeStepInMinutes]=\"selectedTimeStep\"\n [isAdmin]=\"toggleAdmin\"\n (periodSelected)=\"onCumulativePeriodSelected($event)\"\n (cancelled)=\"onCumulativeSelectorCancelled()\">\n </cumulative-selector>\n\n</div>\n", styles: [".raain-details-container{max-width:var(--app-max-width);margin:0 auto;padding:0 0 24px}.raain-details-card{width:100%;margin-bottom:20px}.raain-details-card ion-card-header{border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.raain-details-card ion-card-header ion-card-title{display:flex;align-items:center}.raain-details-card ion-card-header ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary)}.node-info-card{background-color:var(--ion-color-light)}.node-info-card .node-header{display:flex;align-items:center}.node-info-card .node-header .node-status{margin-right:16px}.node-info-card .node-header .node-status ion-icon{font-size:24px}.node-info-card .node-header .node-titles{flex:1}.node-info-card .node-header .node-titles ion-card-title{margin:0;font-size:1.4rem;font-weight:600;color:var(--ion-color-dark)}.node-info-card .node-header .node-titles ion-card-subtitle{padding-left:0;margin:4px 0 0;font-size:.9rem;color:var(--ion-color-medium)}.count-map-card,.period-card{background-color:var(--ion-color-light)}.period-card ion-card-title{display:flex;align-items:center}.period-card ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary);font-size:20px}.period-row{display:flex;flex-wrap:wrap;align-items:center;gap:16px;position:relative}.now-button{min-width:100px}#all-dates{display:flex;align-items:center;margin-left:auto}#all-dates .toggle-label{margin-right:8px;white-space:nowrap}.refresh-button ion-icon{margin-right:4px;color:var(--ion-color-light)}.provider-selection{display:flex;align-items:center}.quality-info-button{--padding-start: 8px;--padding-end: 8px;font-size:.85rem;color:var(--ion-color-medium)}.quality-info-button ion-icon{font-size:18px;margin-left:4px}.selection-row{display:flex;flex-direction:row;align-items:center;gap:12px}.selection-row ion-select{flex:0 0 auto;min-width:100px;margin-bottom:0;padding:4px 8px;border:1px solid rgba(var(--ion-color-medium-rgb),.3);border-radius:var(--ion-border-radius);background-color:var(--ion-color-light)}.period-start,.period-duration{display:flex;align-items:center}.toggle-cumulative{display:flex;align-items:center;gap:8px;margin-left:auto}.toggle-cumulative .text-primary{color:var(--ion-color-primary);font-weight:600}.hidden-label{display:none}.datetime-input,#periodDuration,.duration-select{padding:8px 12px;border:1px solid rgba(var(--ion-color-medium-rgb),.3);border-radius:var(--ion-border-radius);background-color:var(--ion-color-light);font-family:var(--ion-font-family)}.datetime-input:focus,#periodDuration:focus,.duration-select:focus{outline:none;border-color:var(--ion-color-primary)}#periodDuration,.duration-select{min-width:150px}.gauge-card{background-color:var(--ion-color-light)}.gauge-card ion-card-title{display:flex;align-items:center}.gauge-card ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary);font-size:20px}.error-row{cursor:pointer;background-color:rgba(var(--ion-color-danger-rgb),.1);border-left:3px solid var(--ion-color-danger);margin-top:8px;border-radius:4px;transition:background-color .2s ease}.error-row:hover{background-color:rgba(var(--ion-color-danger-rgb),.15)}.error-row .error-content{display:flex;align-items:flex-start;padding:8px 12px;gap:8px}.error-row .error-icon{color:var(--ion-color-danger);font-size:18px;flex-shrink:0;margin-top:2px}.error-row .error-text{flex:1;color:var(--ion-color-danger-shade);font-size:.9rem;word-break:break-word}.error-row .error-text.expanded{white-space:pre-wrap}.error-row .error-caret{color:var(--ion-color-danger);font-size:16px;flex-shrink:0;margin-top:2px}raain-compare-stack{width:100%;display:block}@media (max-width: 768px){.period-row{flex-direction:row;justify-content:space-between;align-items:center}#all-dates{margin-left:auto;padding-left:8px}#all-dates .toggle-label{font-size:.9rem}.map-header{flex-direction:row;justify-content:space-between;align-items:center;gap:16px}}.quality-modal-overlay{position:fixed;inset:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:9999}.quality-modal-content{background-color:var(--ion-background-color, #fff);border-radius:12px;width:95%;max-width:900px;max-height:80vh;overflow:auto;box-shadow:0 4px 24px #0003}.quality-modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.quality-modal-header h2{margin:0;font-size:1.25rem;font-weight:600;color:var(--ion-color-dark)}.quality-modal-header ion-button{--padding-start: 8px;--padding-end: 8px}.quality-modal-body{padding:20px}.quality-table{width:100%;border-collapse:collapse;margin-top:16px;table-layout:fixed}.quality-table th,.quality-table td{width:16.66%;padding:12px 16px;text-align:center;vertical-align:top;border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.quality-table th{background-color:rgba(var(--ion-color-primary-rgb),.1);font-weight:600;color:var(--ion-color-dark)}.quality-table td{color:var(--ion-color-dark-tint);font-size:.7rem}.quality-table tbody tr:hover{background-color:rgba(var(--ion-color-primary-rgb),.05)}.quality-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;color:var(--ion-color-medium)}.quality-loading ion-spinner{margin-bottom:12px}.quality-empty{text-align:center;padding:40px 20px;color:var(--ion-color-medium);font-style:italic}\n"], dependencies: [{ kind: "directive", type: i2.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i2.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i4.IonButton, selector: "ion-button", inputs: ["buttonType", "color", "disabled", "download", "expand", "fill", "href", "mode", "rel", "routerAnimation", "routerDirection", "shape", "size", "strong", "target", "type"] }, { kind: "component", type: i4.IonCard, selector: "ion-card", inputs: ["button", "color", "disabled", "download", "href", "mode", "rel", "routerAnimation", "routerDirection", "target", "type"] }, { kind: "component", type: i4.IonCardContent, selector: "ion-card-content", inputs: ["mode"] }, { kind: "component", type: i4.IonCardHeader, selector: "ion-card-header", inputs: ["color", "mode", "translucent"] }, { kind: "component", type: i4.IonCardTitle, selector: "ion-card-title", inputs: ["color", "mode"] }, { kind: "component", type: i4.IonCol, selector: "ion-col", inputs: ["offset", "offsetLg", "offsetMd", "offsetSm", "offsetXl", "offsetXs", "pull", "pullLg", "pullMd", "pullSm", "pullXl", "pullXs", "push", "pushLg", "pushMd", "pushSm", "pushXl", "pushXs", "size", "sizeLg", "sizeMd", "sizeSm", "sizeXl", "sizeXs"] }, { kind: "component", type: i4.IonGrid, selector: "ion-grid", inputs: ["fixed"] }, { kind: "component", type: i4.IonIcon, selector: "ion-icon", inputs: ["color", "flipRtl", "icon", "ios", "lazy", "md", "mode", "name", "sanitize", "size", "src"] }, { kind: "component", type: i4.IonLabel, selector: "ion-label", inputs: ["color", "mode", "position"] }, { kind: "component", type: i4.IonProgressBar, selector: "ion-progress-bar", inputs: ["buffer", "color", "mode", "reversed", "type", "value"] }, { kind: "component", type: i4.IonRow, selector: "ion-row" }, { kind: "component", type: i4.IonSelect, selector: "ion-select", inputs: ["cancelText", "compareWith", "disabled", "interface", "interfaceOptions", "mode", "multiple", "name", "okText", "placeholder", "selectedText", "value"] }, { kind: "component", type: i4.IonSelectOption, selector: "ion-select-option", inputs: ["disabled", "value"] }, { kind: "component", type: i4.IonSpinner, selector: "ion-spinner", inputs: ["color", "duration", "name", "paused"] }, { kind: "component", type: i4.IonToggle, selector: "ion-toggle", inputs: ["checked", "color", "disabled", "mode", "name", "value"] }, { kind: "directive", type: i4.BooleanValueAccessor, selector: "ion-checkbox,ion-toggle" }, { kind: "directive", type: i4.SelectValueAccessor, selector: "ion-range, ion-select, ion-radio-group, ion-segment, ion-datetime" }, { kind: "component", type: RaainMapComponent, selector: "raain-map", inputs: ["coordinates", "markers", "timeframeContainers", "autoplay", "showMarkers", "showSpeedMarkers", "showVisiblePixelMarkers", "showCumulative", "cumulativeDurationInMinutes", "currentHeight", "timeframeDates", "defaultDate", "sumValues", "sumFn"], outputs: ["selectedMarker", "changedDate", "changedSum"] }, { kind: "component", type: RaainCompareStackComponent, selector: "raain-compare-stack", inputs: ["compareManager", "currentHeight", "cumulative"], outputs: ["selectedPoint"] }, { kind: "component", type: RaainDateFocusComponent, selector: "raain-date-focus", inputs: ["points", "focusDate", "focusRange", "withoutAll", "currentHeight"] }, { kind: "component", type: RaainDateDynamicComponent, selector: "raain-date-dynamic", inputs: ["points", "focusDate", "focusRange", "withoutAll", "currentHeight", "fetchData"], outputs: ["changedDate"] }, { kind: "component", type: CumulativeSelectorComponent, selector: "cumulative-selector", inputs: ["rainId", "currentPeriodBegin", "currentPeriodEnd", "provider", "timeStepInMinutes", "isAdmin"], outputs: ["periodSelected", "cancelled"] }, { kind: "pipe", type: i2.DecimalPipe, name: "number" }, { kind: "pipe", type: i2.DatePipe, name: "date" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3725
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RaainDetailsComponent, decorators: [{
|
|
3726
|
+
type: Component,
|
|
3727
|
+
args: [{ selector: 'raain-details', changeDetection: ChangeDetectionStrategy.OnPush, template: "<!-- Main content container -->\n<div *ngIf=\"rainNode\" class=\"raain-details-container\">\n\n <!-- Period selection section -->\n <ion-card class=\"period-card\">\n <ion-card-content>\n <div class=\"period-controls\">\n <div class=\"period-row\">\n\n <ion-button (click)=\"toggleHistory = !toggleHistory; onEnableCountHistoryTab(rainNode)\"\n fill=\"outline\">\n <ion-icon name=\"calendar-clear-outline\" slot=\"start\"></ion-icon>\n <ion-icon [name]=\"toggleHistory ? 'chevron-down' : 'chevron-forward'\" slot=\"end\"></ion-icon>\n </ion-button>\n\n <div class=\"period-start ion-hide-md-down\">\n <input (change)=\"onPeriodBeginChange($event)\"\n [disabled]=\"toggleCumulative\"\n [value]=\"periodBeginAsString\"\n class=\"datetime-input\"\n type=\"datetime-local\">\n </div>\n\n <div class=\"period-duration ion-hide-md-down\">\n <ion-select (ionDismiss)=\"onPeriodDurationChange($event)\"\n [(ngModel)]=\"periodDurationAsString\"\n [disabled]=\"toggleCumulative\"\n class=\"duration-select\"\n id=\"periodDuration\"\n interface=\"popover\">\n <ion-select-option value=\"0.25\">15 minutes</ion-select-option>\n <ion-select-option value=\"0.5\">30 minutes</ion-select-option>\n <ion-select-option value=\"1\">1 hour</ion-select-option>\n <ion-select-option value=\"2\">2 hours</ion-select-option>\n <ion-select-option value=\"3\">3 hours</ion-select-option>\n <ion-select-option value=\"4\">4 hours</ion-select-option>\n <ion-select-option value=\"5\">5 hours</ion-select-option>\n <ion-select-option value=\"6\">6 hours</ion-select-option>\n <ion-select-option value=\"8\">8 hours</ion-select-option>\n <ion-select-option value=\"10\">10 hours</ion-select-option>\n <ion-select-option value=\"12\">12 hours</ion-select-option>\n <ion-select-option *ngIf=\"isAdmin\" value=\"24\">24 hours</ion-select-option>\n </ion-select>\n </div>\n\n <div class=\"period-start ion-hide-md-down\">\n <input (change)=\"onPeriodEndChange($event)\"\n [disabled]=\"toggleCumulative\"\n [value]=\"periodEndAsString\"\n class=\"datetime-input\"\n type=\"datetime-local\">\n </div>\n\n <div class=\"toggle-cumulative\" (click)=\"onCumulativeToggleClick($event)\">\n <ion-label [class.text-primary]=\"toggleCumulative\">\n {{ toggleCumulative ? 'Cumulative' : 'Granular' }}\n </ion-label>\n <ion-toggle #cumulativeToggle [checked]=\"toggleCumulative\">\n </ion-toggle>\n </div>\n </div>\n\n <!-- Hidden label for change detection (uncomment to debug)\n <div class=\"hidden-label\">{{ onChangeDetectionTest(rainNode) }}</div>\n -->\n </div>\n\n <!-- Historical map section -->\n <div *ngIf=\"toggleHistory\" class=\"period-controls\">\n <raain-date-dynamic (changedDate)=\"onDateChangeInCount($event)\"\n [currentHeight]=\"300\"\n [fetchData]=\"fetchDataWrapper\"\n [points]=\"countPoints\">\n </raain-date-dynamic>\n </div>\n </ion-card-content>\n </ion-card>\n\n <!-- Map performance -->\n <ion-grid class=\"map-performance\">\n <ion-row id=\"progressAndRefresh\">\n <ion-col class=\"provider-selection\" size=\"12\" size-md=\"6\">\n <ion-button (click)=\"openQualityModal()\" class=\"quality-info-button\" fill=\"clear\">\n {{ refreshManager.rainComputationMapVersion }}\n <ion-icon name=\"help-circle-outline\" slot=\"end\"></ion-icon>\n </ion-button>\n <div *ngIf=\"availableProviders.length > 0 || availableTimeSteps.length > 0\" class=\"selection-row\">\n <ion-select (ionChange)=\"onProviderChanged($event)\"\n [value]=\"selectedProvider\"\n interface=\"popover\"\n label=\"Provider\"\n placeholder=\"Select Provider\">\n <ion-select-option *ngFor=\"let provider of availableProviders\" [value]=\"provider\">\n {{ provider }}\n </ion-select-option>\n </ion-select>\n\n <ion-select (ionChange)=\"onTimeStepChanged($event)\"\n [value]=\"selectedTimeStep\"\n interface=\"popover\"\n label=\"Time Step\"\n placeholder=\"Select Time Step\">\n <ion-select-option *ngFor=\"let step of availableTimeSteps\" [value]=\"step\">\n {{ step }} min\n </ion-select-option>\n </ion-select>\n </div>\n </ion-col>\n <ion-col class=\"ion-text-right\" size=\"12\" size-md=\"6\">\n <ion-label class=\"ion-margin-end\">\n <span *ngIf=\"percentOfComputations\">\n {{ percentOfComputations }}% Images\n <i *ngIf=\"countsPeriod.progress\" class=\"progress-indicator\">\n In Progress: {{ countsPeriod.progress }}...\n </i>\n </span>\n <span *ngIf=\"!percentOfComputations\">\n No image available\n </span>\n\n </ion-label>\n\n <ion-button (click)=\"refreshMap()\" [disabled]=\"refreshInProgress\" class=\"refresh-button\">\n Refresh Map\n </ion-button>\n </ion-col>\n </ion-row>\n\n <!-- status update row -->\n <ion-row>\n <!-- Progress col -->\n <ion-progress-bar\n [buffer]=\"(countsPeriod.progress / ((timeframeDates?.length || 10) +3))+0.01\"\n [style.visibility]=\"refreshInProgress ? 'visible' : 'hidden'\"\n [value]=\"countsPeriod.progress / ((timeframeDates?.length || 10) +3)\"\n color=\"primary\">\n </ion-progress-bar>\n\n <!-- Error col -->\n <ion-col (click)=\"showFullError = !showFullError\" *ngIf=\"refreshManager.lastError\" class=\"error-row\"\n size=\"12\">\n <div class=\"error-content\">\n <ion-icon class=\"error-icon\" name=\"warning-outline\"></ion-icon>\n <span [class.expanded]=\"showFullError\" class=\"error-text\">\n {{ showFullError ? refreshManager.lastError : truncatedError }}\n </span>\n <ion-icon [name]=\"showFullError ? 'chevron-up' : 'chevron-down'\" class=\"error-caret\"></ion-icon>\n </div>\n </ion-col>\n </ion-row>\n </ion-grid>\n\n <!-- Map section -->\n <ion-card class=\"map-card\">\n <ion-card-content class=\"map-content\">\n <ion-grid>\n <ion-row *ngIf=\"toggleMap && percentOfImages\">\n <!-- Map component -->\n <ion-col class=\"map-column\" size-lg=\"7\" size-md=\"12\">\n <div class=\"map-container\">\n <raain-map #raainMapRef\n (changedDate)=\"onDateChangeInMap($event)\"\n (changedSum)=\"onSumChangeInMap($event)\"\n (selectedMarker)=\"onGaugeSelectInMap($event)\"\n [coordinates]=\"coordinates\"\n [cumulativeDurationInMinutes]=\"cumulativeDurationInMinutes\"\n [currentHeight]=\"500\"\n [defaultDate]=\"dateShown\"\n [markers]=\"{\n borders,\n gauges: compareManager.gaugesInMap,\n gaugesInCompare: compareManager.gaugesInCompare,\n selectedGauges: compareManager.selectedGauges,\n pixels: compareManager.selectedPixels,\n pixelsSolution: compareManager.pixelsSolutions?.length ? compareManager.pixelsSolutions[0] : [],\n speeds: compareManager.speeds\n }\"\n [showCumulative]=\"toggleCumulative\"\n [showVisiblePixelMarkers]=\"showPixelMarkers\"\n [sumFn]=\"refreshManager.sumFn\"\n [sumValues]=\"refreshManager.sumValues\"\n [timeframeContainers]=\"timeframeContainers\"\n [timeframeDates]=\"timeframeDates\">\n </raain-map>\n </div>\n\n <div class=\"data-column\">\n <!-- Technical details (collapsible for cleaner UI) -->\n <details class=\"technical-details\">\n <summary>Image Details</summary>\n <div class=\"details-content\">\n <div class=\"detail-row\">\n <span class=\"detail-label\">Computed:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapDoneDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Version:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapVersion }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Launched by:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapLaunchedBy }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Time spent:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapTimeSpentInMs }}\n ms</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Date:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapDate?.toISOString() }}\n | {{ refreshManager.rainComputationMapDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Water in the map:</span>\n <span class=\"detail-value\">{{ sumDetails }}</span>\n <ion-toggle\n (ionChange)=\"onTogglePixelMarkers()\"\n [(ngModel)]=\"showPixelMarkers\"\n style=\"margin-left: 8px; transform: scale(0.7);\">\n </ion-toggle>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Source DBZ min:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapOriginalMin }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Source DBZ max:</span>\n <span class=\"detail-value\">{{ refreshManager.rainComputationMapOriginalMax }}</span>\n </div>\n </div>\n </details>\n </div>\n </ion-col>\n\n <!-- Data panel -->\n <ion-col *ngIf=\"!!compareManager.compareVersion\" class=\"data-column\" size-lg=\"5\" size-md=\"12\">\n <div class=\"data-panel\">\n <!-- Compare stack component -->\n <div class=\"compare-stack\">\n <raain-compare-stack\n (selectedPoint)=\"onGaugeSelectInCompare($event)\"\n [compareManager]=\"compareManager\"\n [cumulative]=\"toggleCumulative\">\n </raain-compare-stack>\n </div>\n\n <!-- Technical details (collapsible for cleaner UI) -->\n <details class=\"technical-details\">\n <summary>Compare Details</summary>\n <div class=\"details-content\">\n <div [ngClass]=\"{'warning': refreshManager.rainComputationMapDoneDate?.getTime() > compareManager.currentQualityDoneDate?.getTime() + 60000}\"\n class=\"detail-row\">\n <span class=\"detail-label\">Computed:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityDoneDate | date:'yyyy-MM-dd HH:mm' }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Version:</span>\n <span class=\"detail-value\">{{ compareManager.compareVersion }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Launched by:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityLaunchedBy }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Time spent:</span>\n <span class=\"detail-value\">{{ compareManager.currentQualityTimeSpentInMs }}\n ms</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Gauges:</span>\n <span class=\"detail-value\">{{ compareManager.gaugesInCompare.length }}</span>\n </div>\n <div class=\"detail-row\">\n <span class=\"detail-label\">Points:</span>\n <span class=\"detail-value\">{{ compareManager.globalComparePoints.length }}</span>\n </div>\n </div>\n </details>\n </div>\n </ion-col>\n </ion-row>\n <ion-row>\n <!-- Bottom progress bar -->\n <ion-progress-bar *ngIf=\"refreshInProgress\"\n [buffer]=\"(countsPeriod.progress / ((timeframeDates?.length || 10) +3))+0.01\"\n [value]=\"countsPeriod.progress / ((timeframeDates?.length || 10) +3)\"\n color=\"primary\">\n </ion-progress-bar>\n </ion-row>\n </ion-grid>\n </ion-card-content>\n </ion-card>\n\n <!-- Gauge values section -->\n <ion-card *ngIf=\"gaugeSelectedPoints.length && gaugeSelectedPoints[0].values.length\" class=\"gauge-card\">\n <ion-card-header>\n <ion-card-title>\n <ion-icon name=\"analytics-outline\"></ion-icon>\n Selected Gauge Data\n </ion-card-title>\n </ion-card-header>\n <ion-card-content>\n <raain-date-focus\n [currentHeight]=\"300\"\n [focusDate]=\"periodBegin\"\n [focusRange]=\"DateRange.DAY\"\n [points]=\"gaugeSelectedPoints\"\n [withoutAll]=\"true\">\n </raain-date-focus>\n </ion-card-content>\n </ion-card>\n\n <!-- Quality Performance Modal -->\n <div (click)=\"closeQualityModal()\" *ngIf=\"showQualityModal\" class=\"quality-modal-overlay\">\n <div (click)=\"$event.stopPropagation()\" class=\"quality-modal-content\">\n <div class=\"quality-modal-header\">\n <h2>Model Quality Performance</h2>\n <ion-button (click)=\"closeQualityModal()\" fill=\"clear\">\n <ion-icon name=\"close-outline\"></ion-icon>\n </ion-button>\n </div>\n <div class=\"quality-modal-body\">\n <div *ngIf=\"qualityIndicatorsLoading\" class=\"quality-loading\">\n <ion-spinner name=\"crescent\"></ion-spinner>\n <span>Loading indicators...</span>\n </div>\n <div *ngIf=\"!qualityIndicatorsLoading && qualityIndicators.length === 0\" class=\"quality-empty\">\n No quality indicators available for this year.\n </div>\n <table *ngIf=\"!qualityIndicatorsLoading && qualityIndicators.length > 0\" class=\"quality-table\">\n <thead>\n <tr>\n <th>Model</th>\n <th>Compare</th>\n <th>Gauges</th>\n <th>Period</th>\n <th>Avg Quality</th>\n <th>Updated</th>\n </tr>\n </thead>\n <tbody>\n <tr *ngFor=\"let indicator of qualityIndicators\">\n <td>{{ indicator.computingVersion }}</td>\n <td>{{ indicator.qualityVersion }}</td>\n <td>{{ indicator.provider }}<br>{{ indicator.timeStepInMinutes }} min</td>\n <td>{{ formatDate(indicator.startDate) }}<br>{{ formatDate(indicator.endDate) }}</td>\n <td>{{ indicator.averageQuality | number:'1.2-2' }}</td>\n <td>{{ formatDate(indicator.lastUpdatedAt) }}</td>\n </tr>\n </tbody>\n </table>\n </div>\n </div>\n </div>\n\n <!-- Cumulative Selector Modal -->\n <cumulative-selector *ngIf=\"showCumulativeSelector\"\n [rainId]=\"rainNode?.id\"\n [currentPeriodBegin]=\"periodBegin\"\n [currentPeriodEnd]=\"periodEnd\"\n [provider]=\"selectedProvider\"\n [timeStepInMinutes]=\"selectedTimeStep\"\n [isAdmin]=\"toggleAdmin\"\n (periodSelected)=\"onCumulativePeriodSelected($event)\"\n (cancelled)=\"onCumulativeSelectorCancelled()\">\n </cumulative-selector>\n\n</div>\n", styles: [".raain-details-container{max-width:var(--app-max-width);margin:0 auto;padding:0 0 24px}.raain-details-card{width:100%;margin-bottom:20px}.raain-details-card ion-card-header{border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.raain-details-card ion-card-header ion-card-title{display:flex;align-items:center}.raain-details-card ion-card-header ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary)}.node-info-card{background-color:var(--ion-color-light)}.node-info-card .node-header{display:flex;align-items:center}.node-info-card .node-header .node-status{margin-right:16px}.node-info-card .node-header .node-status ion-icon{font-size:24px}.node-info-card .node-header .node-titles{flex:1}.node-info-card .node-header .node-titles ion-card-title{margin:0;font-size:1.4rem;font-weight:600;color:var(--ion-color-dark)}.node-info-card .node-header .node-titles ion-card-subtitle{padding-left:0;margin:4px 0 0;font-size:.9rem;color:var(--ion-color-medium)}.count-map-card,.period-card{background-color:var(--ion-color-light)}.period-card ion-card-title{display:flex;align-items:center}.period-card ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary);font-size:20px}.period-row{display:flex;flex-wrap:wrap;align-items:center;gap:16px;position:relative}.now-button{min-width:100px}#all-dates{display:flex;align-items:center;margin-left:auto}#all-dates .toggle-label{margin-right:8px;white-space:nowrap}.refresh-button ion-icon{margin-right:4px;color:var(--ion-color-light)}.provider-selection{display:flex;align-items:center}.quality-info-button{--padding-start: 8px;--padding-end: 8px;font-size:.85rem;color:var(--ion-color-medium)}.quality-info-button ion-icon{font-size:18px;margin-left:4px}.selection-row{display:flex;flex-direction:row;align-items:center;gap:12px}.selection-row ion-select{flex:0 0 auto;min-width:100px;margin-bottom:0;padding:4px 8px;border:1px solid rgba(var(--ion-color-medium-rgb),.3);border-radius:var(--ion-border-radius);background-color:var(--ion-color-light)}.period-start,.period-duration{display:flex;align-items:center}.toggle-cumulative{display:flex;align-items:center;gap:8px;margin-left:auto}.toggle-cumulative .text-primary{color:var(--ion-color-primary);font-weight:600}.hidden-label{display:none}.datetime-input,#periodDuration,.duration-select{padding:8px 12px;border:1px solid rgba(var(--ion-color-medium-rgb),.3);border-radius:var(--ion-border-radius);background-color:var(--ion-color-light);font-family:var(--ion-font-family)}.datetime-input:focus,#periodDuration:focus,.duration-select:focus{outline:none;border-color:var(--ion-color-primary)}#periodDuration,.duration-select{min-width:150px}.gauge-card{background-color:var(--ion-color-light)}.gauge-card ion-card-title{display:flex;align-items:center}.gauge-card ion-card-title ion-icon{margin-right:8px;color:var(--ion-color-primary);font-size:20px}.error-row{cursor:pointer;background-color:rgba(var(--ion-color-danger-rgb),.1);border-left:3px solid var(--ion-color-danger);margin-top:8px;border-radius:4px;transition:background-color .2s ease}.error-row:hover{background-color:rgba(var(--ion-color-danger-rgb),.15)}.error-row .error-content{display:flex;align-items:flex-start;padding:8px 12px;gap:8px}.error-row .error-icon{color:var(--ion-color-danger);font-size:18px;flex-shrink:0;margin-top:2px}.error-row .error-text{flex:1;color:var(--ion-color-danger-shade);font-size:.9rem;word-break:break-word}.error-row .error-text.expanded{white-space:pre-wrap}.error-row .error-caret{color:var(--ion-color-danger);font-size:16px;flex-shrink:0;margin-top:2px}raain-compare-stack{width:100%;display:block}@media (max-width: 768px){.period-row{flex-direction:row;justify-content:space-between;align-items:center}#all-dates{margin-left:auto;padding-left:8px}#all-dates .toggle-label{font-size:.9rem}.map-header{flex-direction:row;justify-content:space-between;align-items:center;gap:16px}}.quality-modal-overlay{position:fixed;inset:0;background-color:#00000080;display:flex;align-items:center;justify-content:center;z-index:9999}.quality-modal-content{background-color:var(--ion-background-color, #fff);border-radius:12px;width:95%;max-width:900px;max-height:80vh;overflow:auto;box-shadow:0 4px 24px #0003}.quality-modal-header{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.quality-modal-header h2{margin:0;font-size:1.25rem;font-weight:600;color:var(--ion-color-dark)}.quality-modal-header ion-button{--padding-start: 8px;--padding-end: 8px}.quality-modal-body{padding:20px}.quality-table{width:100%;border-collapse:collapse;margin-top:16px;table-layout:fixed}.quality-table th,.quality-table td{width:16.66%;padding:12px 16px;text-align:center;vertical-align:top;border-bottom:1px solid rgba(var(--ion-color-medium-rgb),.2)}.quality-table th{background-color:rgba(var(--ion-color-primary-rgb),.1);font-weight:600;color:var(--ion-color-dark)}.quality-table td{color:var(--ion-color-dark-tint);font-size:.7rem}.quality-table tbody tr:hover{background-color:rgba(var(--ion-color-primary-rgb),.05)}.quality-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;color:var(--ion-color-medium)}.quality-loading ion-spinner{margin-bottom:12px}.quality-empty{text-align:center;padding:40px 20px;color:var(--ion-color-medium);font-style:italic}\n"] }]
|
|
3728
|
+
}], ctorParameters: function () { return [{ type: Storage }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { toggleAdmin: [{
|
|
3729
|
+
type: Input
|
|
3730
|
+
}], rainNode: [{
|
|
3731
|
+
type: Input
|
|
3732
|
+
}], compareManager: [{
|
|
3733
|
+
type: Input
|
|
3734
|
+
}], refreshManager: [{
|
|
3735
|
+
type: Input
|
|
3736
|
+
}], profileService: [{
|
|
3737
|
+
type: Input
|
|
3738
|
+
}], radarService: [{
|
|
3739
|
+
type: Input
|
|
3740
|
+
}], cumulativeToggleRef: [{
|
|
3741
|
+
type: ViewChild,
|
|
3742
|
+
args: ['cumulativeToggle']
|
|
3743
|
+
}] } });
|
|
3744
|
+
|
|
3745
|
+
class Cache {
|
|
3746
|
+
constructor() {
|
|
3747
|
+
this._cache = {};
|
|
3748
|
+
}
|
|
3749
|
+
async getValue(key, asyncGetter) {
|
|
3750
|
+
if (!Object.prototype.hasOwnProperty.call(this._cache, key)) {
|
|
3751
|
+
console.log('cache not done: ', key);
|
|
3752
|
+
this.putValue(key, await asyncGetter());
|
|
3421
3753
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3754
|
+
else {
|
|
3755
|
+
console.log('cache done: ', key);
|
|
3424
3756
|
}
|
|
3425
|
-
|
|
3426
|
-
|
|
3757
|
+
return this._cache[key];
|
|
3758
|
+
}
|
|
3759
|
+
putValue(key, value) {
|
|
3760
|
+
this._cache[key] = value;
|
|
3761
|
+
const length = Object.getOwnPropertyNames(this._cache).length;
|
|
3762
|
+
if (length > 30) {
|
|
3763
|
+
console.warn('Pb on cache size exceed ? do a restart ?', length);
|
|
3427
3764
|
}
|
|
3428
|
-
return dateToShow.toISOString();
|
|
3429
3765
|
}
|
|
3430
3766
|
}
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type:
|
|
3767
|
+
Cache.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: Cache, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
3768
|
+
Cache.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: Cache, providedIn: 'root' });
|
|
3769
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: Cache, decorators: [{
|
|
3434
3770
|
type: Injectable,
|
|
3435
3771
|
args: [{
|
|
3436
3772
|
providedIn: 'root',
|
|
3437
3773
|
}]
|
|
3438
|
-
}], ctorParameters: function () { return [
|
|
3774
|
+
}], ctorParameters: function () { return []; } });
|
|
3439
3775
|
|
|
3440
3776
|
class RadarService {
|
|
3441
3777
|
constructor(profileService) {
|
|
@@ -3863,7 +4199,8 @@ SharedModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "
|
|
|
3863
4199
|
RaainSpeedComponent,
|
|
3864
4200
|
RaainGlobeComponent,
|
|
3865
4201
|
ProfileIconDirective,
|
|
3866
|
-
RaainDetailsComponent
|
|
4202
|
+
RaainDetailsComponent,
|
|
4203
|
+
CumulativeSelectorComponent], imports: [CommonModule, FormsModule, IonicModule, NgOptimizedImage, PipesModule], exports: [CommonModule,
|
|
3867
4204
|
NgStyle,
|
|
3868
4205
|
PipesModule,
|
|
3869
4206
|
RaainMapComponent,
|
|
@@ -3875,7 +4212,8 @@ SharedModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "
|
|
|
3875
4212
|
RaainSpeedComponent,
|
|
3876
4213
|
RaainGlobeComponent,
|
|
3877
4214
|
ProfileIconDirective,
|
|
3878
|
-
RaainDetailsComponent
|
|
4215
|
+
RaainDetailsComponent,
|
|
4216
|
+
CumulativeSelectorComponent] });
|
|
3879
4217
|
SharedModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SharedModule, providers: [Storage, RadarService, ProfileService, Cache, IonRange], imports: [CommonModule, FormsModule, IonicModule, PipesModule, CommonModule,
|
|
3880
4218
|
PipesModule] });
|
|
3881
4219
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SharedModule, decorators: [{
|
|
@@ -3892,6 +4230,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
3892
4230
|
RaainGlobeComponent,
|
|
3893
4231
|
ProfileIconDirective,
|
|
3894
4232
|
RaainDetailsComponent,
|
|
4233
|
+
CumulativeSelectorComponent,
|
|
3895
4234
|
],
|
|
3896
4235
|
imports: [CommonModule, FormsModule, IonicModule, NgOptimizedImage, PipesModule],
|
|
3897
4236
|
providers: [Storage, RadarService, ProfileService, Cache, IonRange],
|
|
@@ -3909,6 +4248,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
3909
4248
|
RaainGlobeComponent,
|
|
3910
4249
|
ProfileIconDirective,
|
|
3911
4250
|
RaainDetailsComponent,
|
|
4251
|
+
CumulativeSelectorComponent,
|
|
3912
4252
|
],
|
|
3913
4253
|
}]
|
|
3914
4254
|
}], ctorParameters: function () { return [{ type: SharedModule, decorators: [{
|
|
@@ -3921,5 +4261,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
|
|
|
3921
4261
|
* Generated bundle index. Do not edit.
|
|
3922
4262
|
*/
|
|
3923
4263
|
|
|
3924
|
-
export { AreInProgressPipe, AreReady, AreStopped, CONSTANTS, Cache, CompareManager, FidjStorage, FidjStorageNode, FrameSet, GaugeNodeFilter, HasGoodQuality, HasNotGoodQuality, HaveNotBeenRed, IsNotReady, IsReady, PipesModule, ProfileIconDirective, ProfileService, ProgressBuffer, ProgressValue, RaainCompareComponent, RaainCompareStackComponent, RaainConfigurationComponent, RaainDateDynamicComponent, RaainDateFocusComponent, RaainDetailsComponent, RaainGlobeComponent, RaainMapComponent, RaainSpeedComponent, RadarService, RefreshManager, SharedModule, Storage, WaitForValidation, XYType, mapDateRangeToString };
|
|
4264
|
+
export { AreInProgressPipe, AreReady, AreStopped, CONSTANTS, Cache, CompareManager, CumulativeSelectorComponent, FidjStorage, FidjStorageNode, FrameSet, GaugeNodeFilter, HasGoodQuality, HasNotGoodQuality, HaveNotBeenRed, IsNotReady, IsReady, PipesModule, ProfileIconDirective, ProfileService, ProgressBuffer, ProgressValue, RaainCompareComponent, RaainCompareStackComponent, RaainConfigurationComponent, RaainDateDynamicComponent, RaainDateFocusComponent, RaainDetailsComponent, RaainGlobeComponent, RaainMapComponent, RaainSpeedComponent, RadarService, RefreshManager, SharedModule, Storage, WaitForValidation, XYType, mapDateRangeToString };
|
|
3925
4265
|
//# sourceMappingURL=raain-app.mjs.map
|