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.
@@ -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, TeamNode, Link, EventNode, BuildQueryString, RainNode, RainComputationMap, RainComputationQuality, GaugeMeasure } from 'raain-model';
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';
@@ -1347,10 +1347,10 @@ class RaainCompareStackComponent {
1347
1347
  }
1348
1348
  }
1349
1349
  RaainCompareStackComponent.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RaainCompareStackComponent, deps: [{ token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Component });
1350
- 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\" 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" }] });
1350
+ 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" }] });
1351
1351
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RaainCompareStackComponent, decorators: [{
1352
1352
  type: Component,
1353
- 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\" 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"] }]
1353
+ 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"] }]
1354
1354
  }], ctorParameters: function () { return [{ type: i0.NgZone }]; }, propDecorators: { compareManager: [{
1355
1355
  type: Input
1356
1356
  }], currentHeight: [{
@@ -1512,11 +1512,11 @@ class CompareManager {
1512
1512
  this.onChanges();
1513
1513
  });
1514
1514
  }
1515
- refreshGlobalCompareQuality(targetsOrdered, withCompareDuplicate, cumulativeHours = 0) {
1515
+ refreshGlobalCompareQuality(targetsOrdered, period, withCompareDuplicate, cumulativeHours = 0) {
1516
1516
  return __awaiter(this, void 0, void 0, function* () {
1517
1517
  try {
1518
1518
  yield this.fetchRainComputationQualities(targetsOrdered, cumulativeHours);
1519
- yield this.buildComparesTimeline(targetsOrdered, withCompareDuplicate);
1519
+ yield this.buildComparesTimeline(targetsOrdered, period, withCompareDuplicate);
1520
1520
  if (!this.buildCompares.compareCumulative) {
1521
1521
  // throw Error('needs cumulative compare');
1522
1522
  return;
@@ -1534,6 +1534,9 @@ class CompareManager {
1534
1534
  setGaugesInMap() {
1535
1535
  var _a;
1536
1536
  return __awaiter(this, void 0, void 0, function* () {
1537
+ // Get all gauge IDs linked to this rainNode
1538
+ const rainNodeGaugeIds = this.rainNode.getLinks(GaugeNode.TYPE).map((l) => l.getId());
1539
+ // Fetch gauges from API (may be filtered/limited)
1537
1540
  let gaugesToFilter = yield this.profileService.getGauges((_a = this.rainNode) === null || _a === void 0 ? void 0 : _a.id, this.rainNode.getCenter());
1538
1541
  gaugesToFilter = gaugesToFilter
1539
1542
  .sort((a, b) => {
@@ -1541,9 +1544,29 @@ class CompareManager {
1541
1544
  b.approxDistanceFrom(this.rainNode.getCenter()));
1542
1545
  })
1543
1546
  .filter((v, index) => index < 200);
1544
- const rainNodeGaugeIds = this.rainNode.getLinks(GaugeNode.TYPE).map((l) => l.getId());
1545
- const visibleGauges = gaugesToFilter.filter((g) => rainNodeGaugeIds.indexOf(g.id) > -1);
1546
- console.log('visibleGauges:', visibleGauges);
1547
+ // Build a map of gauges from API response
1548
+ const gaugesFromApi = new Map();
1549
+ for (const gauge of gaugesToFilter) {
1550
+ gaugesFromApi.set(gauge.id, gauge);
1551
+ }
1552
+ // Fetch missing gauges individually (those linked but not in API response)
1553
+ const missingGaugeIds = rainNodeGaugeIds.filter((id) => !gaugesFromApi.has(id));
1554
+ for (const gaugeId of missingGaugeIds) {
1555
+ try {
1556
+ const gauge = yield this.profileService.getGauge(gaugeId);
1557
+ if (gauge) {
1558
+ gaugesFromApi.set(gauge.id, gauge);
1559
+ }
1560
+ }
1561
+ catch (e) {
1562
+ console.warn(`Failed to fetch gauge ${gaugeId}:`, e);
1563
+ }
1564
+ }
1565
+ // Filter to only gauges linked to this rainNode
1566
+ const visibleGauges = rainNodeGaugeIds
1567
+ .map((id) => gaugesFromApi.get(id))
1568
+ .filter((g) => !!g);
1569
+ console.log('visibleGauges:', visibleGauges.length, '/', rainNodeGaugeIds.length);
1547
1570
  const gaugesLatLng = [];
1548
1571
  for (const gauge of visibleGauges) {
1549
1572
  gaugesLatLng.push(new MapLatLng(gauge.latitude, gauge.longitude, undefined, gauge.id, gauge.name));
@@ -1657,14 +1680,14 @@ class CompareManager {
1657
1680
  }
1658
1681
  return result.sort((a, b) => a.date.getTime() - b.date.getTime());
1659
1682
  }
1660
- buildComparesTimeline(targetsOrdered, withCompareDuplicate) {
1683
+ buildComparesTimeline(targetsOrdered, period, withCompareDuplicate) {
1661
1684
  return __awaiter(this, void 0, void 0, function* () {
1662
1685
  const compareDates = targetsOrdered.map((t) => t.date);
1663
1686
  const qualities = compareDates
1664
1687
  .filter((d) => !!d)
1665
1688
  .map((d) => this.getRainComputationQuality(d))
1666
1689
  .filter((rcq) => !!rcq);
1667
- this.compareDates = compareDates; // compareDates.slice(1, -1);
1690
+ this.compareDates = (compareDates === null || compareDates === void 0 ? void 0 : compareDates.length) > 1 ? compareDates : [period.begin, period.end]; // compareDates.slice(1, -1);
1668
1691
  this.buildCompares = SpeedMatrixContainer.BuildCompares(qualities, !withCompareDuplicate);
1669
1692
  return this.buildCompares;
1670
1693
  });
@@ -2044,7 +2067,7 @@ class RefreshManager {
2044
2067
  const cumulativeHours = this.cumulative
2045
2068
  ? (this.period.end.getTime() - this.period.begin.getTime()) / (60 * 60 * 1000)
2046
2069
  : 0;
2047
- yield this.compareManager.refreshGlobalCompareQuality(targets, !this.removeDuplicate, cumulativeHours);
2070
+ yield this.compareManager.refreshGlobalCompareQuality(targets, this.period, !this.removeDuplicate, cumulativeHours);
2048
2071
  }
2049
2072
  catch (e) {
2050
2073
  console.error('refreshGlobalCompareReport', e);
@@ -2149,1514 +2172,1842 @@ function mapDateRangeToString(range) {
2149
2172
  }
2150
2173
  }
2151
2174
 
2152
- let TEST_DETECTION = 0;
2153
- class RaainDetailsComponent {
2154
- constructor(storage, cdr) {
2155
- this.storage = storage;
2156
- this.cdr = cdr;
2157
- this.availableProviders = [];
2158
- this.availableTimeSteps = [];
2159
- this.showPixelMarkers = false;
2160
- this.qualityIndicators = [];
2161
- this.qualityIndicatorsLoading = false;
2162
- // Cached computed values (to avoid method calls in templates)
2163
- this.percentOfComputations = 0;
2164
- this.percentOfImages = 0;
2165
- this.truncatedError = '';
2166
- this.cumulativeDurationInMinutes = 10;
2167
- this.DateRange = DateRange;
2168
- // Wrapper function that preserves the async nature of fetchData
2169
- this.fetchDataWrapper = (focusDate, focusRange) => __awaiter(this, void 0, void 0, function* () {
2170
- return yield this.fetchData(focusDate, focusRange);
2171
- });
2175
+ class FidjStorageNode {
2176
+ constructor() {
2177
+ this.radars = [];
2178
+ this.rains = [];
2179
+ this.gauges = [];
2180
+ this.events = [];
2181
+ this.infos = {};
2172
2182
  }
2173
- static PeriodDisplay(date) {
2174
- let d = new Date();
2175
- if (date) {
2176
- d = new Date(date);
2177
- const userTimezoneOffset = d.getTimezoneOffset() * 60000;
2178
- d = new Date(d.getTime() - userTimezoneOffset);
2179
- }
2180
- const exampleFormattedDate = '2017-06-01T08:30';
2181
- return d.toISOString().substring(0, exampleFormattedDate.length);
2183
+ static getEmptyNode() {
2184
+ return new FidjStorageNode();
2182
2185
  }
2183
- static DateUTC(date) {
2184
- const hasISOFormat = date.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
2185
- let d = new Date(date);
2186
- if (!hasISOFormat) {
2187
- const userTimezoneOffset = d.getTimezoneOffset() * 60000;
2188
- d = new Date(d.getTime() - userTimezoneOffset);
2189
- }
2190
- return d;
2186
+ static getDemoNode() {
2187
+ const demoNode = new FidjStorageNode();
2188
+ const link = new Link('rain', 'https://demo/api/rains/2');
2189
+ /*
2190
+ demoNode.radars = [
2191
+ new RadarNode({
2192
+ id: '5efd04569cb1f4161bd69dc7',
2193
+ name: 'demo radar A',
2194
+ links: [link],
2195
+ latitude: 48.774569,
2196
+ longitude: 2.008407
2197
+ }),
2198
+ new RadarNode({
2199
+ id: 'dr2',
2200
+ name: 'demo radar B',
2201
+ links: [link],
2202
+ latitude: 0.11,
2203
+ longitude: -0.753
2204
+ }),
2205
+ new RadarNode({
2206
+ id: 'dr3',
2207
+ name: 'demo radar C',
2208
+ latitude: 0.13,
2209
+ longitude: -0.753,
2210
+ links: []
2211
+ }),
2212
+ new RadarNode({
2213
+ id: 'dr4',
2214
+ name: 'demo radar D',
2215
+ latitude: 0.14,
2216
+ longitude: -0.74,
2217
+ links: []
2218
+ })];
2219
+ demoNode.rains = [
2220
+ new RainNode({
2221
+ id: '5efd04569cb1f4161bd69dc8',
2222
+ name: 'Demo rain zone A',
2223
+ links: [new Link('radar', 'https://demo/api/radars/5efcfe619cb1f4161bd69dc3')],
2224
+ status: 0,
2225
+ quality: 75,
2226
+ latitude: 48.774569,
2227
+ longitude: 2.008407
2228
+ }),
2229
+ new RainNode({
2230
+ id: 'dz2',
2231
+ name: 'Demo rain zone B',
2232
+ radars: [demoNode.radars[0], demoNode.radars[1]],
2233
+ status: 1,
2234
+ quality: 50,
2235
+ latitude: 48.774569,
2236
+ longitude: 2.008407
2237
+ }),
2238
+ new RainNode({
2239
+ id: 'dz3',
2240
+ name: 'Demo rain zone C',
2241
+ radars: [demoNode.radars[0], demoNode.radars[1]],
2242
+ status: 2,
2243
+ quality: 75,
2244
+ latitude: 48.774569,
2245
+ longitude: 2.008407
2246
+ }),
2247
+ new RainNode({
2248
+ id: 'dz4',
2249
+ name: 'Demo rain zone D',
2250
+ radars: [demoNode.radars[0], demoNode.radars[1]],
2251
+ status: 3,
2252
+ quality: 95,
2253
+ latitude: 48.774569,
2254
+ longitude: 2.008407
2255
+ })];
2256
+
2257
+ demoNode.gauges = [
2258
+ new GaugeNode({
2259
+ id: 'g1',
2260
+ name: 'Gauge A',
2261
+ latitude: 48.7748,
2262
+ longitude: 2.28407,
2263
+ }),
2264
+ new GaugeNode({
2265
+ id: 'g2',
2266
+ name: 'Gauge B',
2267
+ latitude: 48.874569,
2268
+ longitude: 2.108407,
2269
+ })];
2270
+ demoNode.events = [{
2271
+ id: 'e2',
2272
+ title: 'Need support ?',
2273
+ status: 0,
2274
+ red: false,
2275
+ description: 'This area is dedicated to support you and your team. Support is made on Radar or Rain quality, ' +
2276
+ 'or any feedback we can have about your production system. Our goal : improving your data.',
2277
+ created: new Date(),
2278
+ modified: new Date()
2279
+ }];
2280
+ demoNode.team = {
2281
+ id: 'p1',
2282
+ email: 'demo@demo.com',
2283
+ name: 'demo guy',
2284
+ description: 'the demo guy'
2285
+ };
2286
+
2287
+ */
2288
+ return demoNode;
2191
2289
  }
2192
- static MapCountToDateValue(c) {
2193
- return {
2194
- date: RaainDetailsComponent.DateUTC(c.name),
2195
- value: Math.min(100, c.x),
2196
- };
2290
+ }
2291
+ class FidjStorageResult {
2292
+ }
2293
+ class FidjStorage {
2294
+ constructor(storage) {
2295
+ this.storage = storage;
2296
+ this.node = FidjStorageNode.getEmptyNode();
2297
+ this.fidjMetaResult = { data: new FidjStorageNode() };
2197
2298
  }
2198
- openQualityModal() {
2199
- var _a;
2299
+ storeData(fidjService, data) {
2200
2300
  return __awaiter(this, void 0, void 0, function* () {
2201
- this.showQualityModal = true;
2202
- this.qualityIndicatorsLoading = true;
2203
- this.qualityIndicators = [];
2204
- this.cdr.markForCheck();
2205
- if ((_a = this.rainNode) === null || _a === void 0 ? void 0 : _a.id) {
2206
- const result = yield this.profileService.getIndicators(this.rainNode.id);
2207
- this.qualityIndicators = result.indicators;
2301
+ this.node = JSON.parse(JSON.stringify(data));
2302
+ this.fidjMetaResult.data = this.node;
2303
+ if (this.isDemoMode) {
2304
+ this.storage.set('fidjMetaResult', JSON.stringify(this.fidjMetaResult));
2305
+ return;
2208
2306
  }
2209
- this.qualityIndicatorsLoading = false;
2210
- this.cdr.markForCheck();
2307
+ yield fidjService.put(this.fidjMetaResult);
2211
2308
  });
2212
2309
  }
2213
- closeQualityModal() {
2214
- this.showQualityModal = false;
2215
- }
2216
- formatDate(dateStr) {
2217
- const date = new Date(dateStr);
2218
- return (date.toLocaleDateString() +
2219
- ' ' +
2220
- date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
2221
- }
2222
- fetchData(focusDate, focusRange) {
2310
+ getRefreshedNodeCopy(fidjService) {
2223
2311
  return __awaiter(this, void 0, void 0, function* () {
2224
- const values = [];
2225
- for (let i = 0; i < 10; i++) {
2226
- values.push({ date: new Date(2020 + i, 0), value: 10 });
2227
- }
2228
- const fakeData = [
2229
- {
2230
- label: '% Rainy',
2231
- style: 'bar',
2232
- values,
2233
- },
2234
- {
2235
- label: '% Images',
2236
- style: 'bar',
2237
- values,
2238
- },
2239
- // {
2240
- // label: '% Quality',
2241
- // style: 'line',
2242
- // values,
2243
- // },
2244
- ];
2245
- const range = mapDateRangeToString(focusRange);
2246
- let data = fakeData;
2247
- if (!this.rainNode) {
2248
- return data;
2312
+ if (this.isDemoMode) {
2313
+ const fidjMetaResult = this.storage.get('fidjMetaResult');
2314
+ if (fidjMetaResult) {
2315
+ this.fidjMetaResult = JSON.parse(fidjMetaResult);
2316
+ this.node = this.fidjMetaResult.data;
2317
+ }
2318
+ return JSON.parse(JSON.stringify(this.node));
2249
2319
  }
2250
- if (focusRange === DateRange.CENTURY) {
2251
- // fake
2320
+ const firstDemoData = () => __awaiter(this, void 0, void 0, function* () {
2321
+ this.node = FidjStorageNode.getDemoNode();
2322
+ yield this.storeData(fidjService, this.node);
2323
+ });
2324
+ yield fidjService.sync(firstDemoData);
2325
+ const fidjFindAllResults = yield fidjService.findAll();
2326
+ if (fidjFindAllResults && fidjFindAllResults.length > 0) {
2327
+ if (fidjFindAllResults[0].data) {
2328
+ this.fidjMetaResult = fidjFindAllResults[0];
2329
+ this.node = this.fidjMetaResult.data;
2330
+ }
2252
2331
  }
2253
- else if (focusRange === DateRange.HOUR) {
2254
- const hourCounts = yield this.profileService.getCountsHour(this.rainNode.id, {
2255
- periodBegin: focusDate,
2256
- });
2257
- data = [
2258
- {
2259
- label: 'Rainy Count',
2260
- style: 'line',
2261
- values: hourCounts.rainyCount.map(RaainDetailsComponent.MapCountToDateValue),
2262
- },
2263
- {
2264
- label: '% Images',
2265
- style: 'bar',
2266
- values: hourCounts.percentImages.map(RaainDetailsComponent.MapCountToDateValue),
2267
- },
2268
- {
2269
- label: 'Rainy Sum',
2270
- style: 'line',
2271
- values: hourCounts.rainySum.map(RaainDetailsComponent.MapCountToDateValue),
2272
- },
2273
- ];
2274
- }
2275
- else {
2276
- const counts = yield this.profileService.getCounts(this.rainNode.id, {
2277
- range,
2278
- periodBegin: focusDate,
2279
- });
2280
- data = [
2281
- {
2282
- label: '% Rainy',
2283
- style: 'bar',
2284
- values: counts.percentRainy.map(RaainDetailsComponent.MapCountToDateValue),
2285
- },
2286
- {
2287
- label: '% Images',
2288
- style: 'bar',
2289
- values: counts.percentImages.map(RaainDetailsComponent.MapCountToDateValue),
2290
- },
2291
- ];
2292
- }
2293
- // console.log(`fetchData DONE ${range}`, data);
2294
- return data;
2332
+ return JSON.parse(JSON.stringify(this.node));
2295
2333
  });
2296
2334
  }
2297
- ngOnChanges(changes) {
2298
- this.change(changes).then((ignored) => { });
2335
+ setDemoMode(isDemo) {
2336
+ this.isDemoMode = isDemo;
2299
2337
  }
2300
- ngOnDestroy() {
2301
- this.cleanAll();
2338
+ }
2339
+
2340
+ class ProfileService {
2341
+ constructor(storage, fidjService, httpClient, router) {
2342
+ this.storage = storage;
2343
+ this.fidjService = fidjService;
2344
+ this.httpClient = httpClient;
2345
+ this.router = router;
2346
+ this.email = this.storage.get('raain-email');
2347
+ this.asTeamId = this.storage.get('raain-asTeamId');
2348
+ this.readyForSync = new BehaviorSubject(false);
2349
+ this.roles = [];
2350
+ this.fidjStorage = new FidjStorage(storage);
2351
+ this.isDemoMode = false;
2302
2352
  }
2303
- onEnableCountHistoryTab(rain) {
2353
+ get isDemoMode() {
2354
+ return this.isDemo;
2355
+ }
2356
+ set isDemoMode(mode) {
2357
+ this.isDemo = mode ? mode : true;
2358
+ this.fidjStorage.setDemoMode(this.isDemo);
2359
+ }
2360
+ get defaultUrlForAPI() {
2361
+ return this.storage.get('raain-urlForAPI');
2362
+ }
2363
+ set defaultUrlForAPI(url) {
2364
+ this.storage.set('raain-urlForAPI', url);
2365
+ }
2366
+ refreshProfile() {
2304
2367
  return __awaiter(this, void 0, void 0, function* () {
2305
- if (!this.toggleHistory) {
2306
- this.countPoints = [];
2368
+ try {
2369
+ this.nodeData = yield this.fidjStorage.getRefreshedNodeCopy(this.fidjService);
2370
+ this.setRoles(yield this.fidjService.getRoles());
2371
+ return this.nodeData;
2372
+ }
2373
+ catch (e) {
2374
+ yield this.checkError(e);
2307
2375
  }
2308
2376
  });
2309
2377
  }
2310
- onPeriodBeginChange(event) {
2311
- var _a, _b;
2378
+ isConnected() {
2312
2379
  return __awaiter(this, void 0, void 0, function* () {
2313
- const newValue = (_b = (_a = event === null || event === void 0 ? void 0 : event.target) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : this.periodBeginAsString;
2314
- this.periodBegin = new Date(newValue);
2315
- this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
2316
- this.storage.set('raain-periodBegin', this.periodBegin);
2317
- yield this.onPeriodDurationChange(event);
2380
+ return this.fidjService.isConnected();
2318
2381
  });
2319
2382
  }
2320
- onPeriodEndChange(event) {
2321
- var _a, _b;
2322
- return __awaiter(this, void 0, void 0, function* () {
2323
- const newValue = (_b = (_a = event === null || event === void 0 ? void 0 : event.target) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : this.periodEndAsString;
2324
- this.periodEnd = new Date(newValue);
2325
- this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
2326
- this.storage.set('raain-periodEnd', this.periodEnd);
2327
- this.periodBegin = new Date(this.periodEnd.getTime() - this.getDurationInHours() * RaainDetailsComponent.HOUR_MS);
2328
- this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
2329
- this.storage.set('raain-periodBegin', this.periodBegin);
2330
- this.updateRefreshManagerPeriod();
2331
- });
2383
+ getEmail() {
2384
+ var _a;
2385
+ return (_a = this.email) !== null && _a !== void 0 ? _a : this.storage.get('raain-email', this.email);
2332
2386
  }
2333
- onPeriodDurationChange(_event) {
2334
- return __awaiter(this, void 0, void 0, function* () {
2335
- const durationInHours = this.getDurationInHours();
2336
- this.periodEnd = new Date(this.periodBegin.getTime() + durationInHours * RaainDetailsComponent.HOUR_MS);
2337
- this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
2338
- this.storage.set('raain-periodEnd', this.periodEnd);
2339
- this.storage.set('raain-periodDurationInHours', durationInHours);
2340
- this.updateRefreshManagerPeriod();
2341
- this.updateCumulativeDurationInMinutes();
2342
- });
2387
+ setEmail(email) {
2388
+ this.email = email;
2389
+ this.storage.set('raain-email', this.email);
2343
2390
  }
2344
- onDateChangeInCount(dateChanged) {
2391
+ logout(fidjKey, fidjProd) {
2345
2392
  return __awaiter(this, void 0, void 0, function* () {
2346
- const dateString = dateChanged.toISOString().substring(0, 11) +
2347
- dateChanged.toLocaleTimeString().substring(0, 5);
2348
- this.periodDurationAsString = '1';
2349
- if (this.toggleCumulative) {
2350
- // Cumulative: select periodEnd
2351
- this.periodEndAsString = dateString;
2352
- yield this.onPeriodEndChange(null);
2393
+ // this.storage.remove('raain-email');
2394
+ if (!fidjKey) {
2395
+ try {
2396
+ yield this.fidjService.loginInDemoMode();
2397
+ this.readyForSync.next(true);
2398
+ }
2399
+ catch (err) {
2400
+ console.error('initFidj catch pb: ', err);
2401
+ }
2402
+ return;
2353
2403
  }
2354
- else {
2355
- // Granular: select periodBegin
2356
- this.periodBeginAsString = dateString;
2357
- yield this.onPeriodBeginChange(null);
2404
+ yield this.fidjService.logout(true);
2405
+ try {
2406
+ yield this.fidjService.init(fidjKey, {
2407
+ logLevel: LoggerLevelEnum.WARN,
2408
+ crypto: false,
2409
+ prod: fidjProd,
2410
+ useDB: false,
2411
+ });
2412
+ this.readyForSync.next(true);
2413
+ }
2414
+ catch (err) {
2415
+ console.error('initFidj catch pb: ', err);
2358
2416
  }
2359
- yield this.refreshManager.refresh(false, this.toggleAdmin);
2360
2417
  });
2361
2418
  }
2362
- onDateChangeInMap(dateShown) {
2419
+ checkError(error) {
2363
2420
  return __awaiter(this, void 0, void 0, function* () {
2364
- this.dateShown = dateShown;
2365
- yield this.fetchAndUpdateMap();
2366
- yield this.refreshManager.setReportPeriod(this.dateShown);
2421
+ if (error.code === 401) {
2422
+ console.warn('Pb on auth');
2423
+ if (this.router.url.indexOf('login') < 0) {
2424
+ try {
2425
+ yield this.fidjService.logout();
2426
+ }
2427
+ catch (ignored) {
2428
+ // Ignore logout errors as we're redirecting to logout page anyway
2429
+ }
2430
+ return this.gotoLout();
2431
+ }
2432
+ }
2367
2433
  });
2368
2434
  }
2369
- onSumChangeInMap(sum) {
2435
+ gotoLout() {
2370
2436
  return __awaiter(this, void 0, void 0, function* () {
2371
- this.sumDetails = sum;
2437
+ try {
2438
+ if (this.router.url.indexOf('login') > -1) {
2439
+ return;
2440
+ }
2441
+ yield this.router.navigateByUrl('/logout', {
2442
+ skipLocationChange: true,
2443
+ replaceUrl: true,
2444
+ });
2445
+ }
2446
+ catch (e) {
2447
+ console.error('gotoLout error: ', e);
2448
+ }
2372
2449
  });
2373
2450
  }
2374
- onGaugeSelectInMap(mapLatLng) {
2451
+ gotoLogin() {
2375
2452
  return __awaiter(this, void 0, void 0, function* () {
2376
- const gaugesFiltered = this.compareManager.gaugesInMap.filter((g) => g.lat === mapLatLng.lat && g.lng === mapLatLng.lng);
2377
- if (gaugesFiltered.length !== 1) {
2453
+ if (this.router.url.indexOf('login') > -1) {
2378
2454
  return;
2379
2455
  }
2380
- const gaugeSelected = gaugesFiltered[0];
2381
- yield this.refreshGaugeValues({ id: gaugeSelected.id, name: gaugeSelected.name });
2382
- yield this.compareManager.selectGauge(gaugeSelected.id, 0);
2456
+ // await this.router.navigateByUrl('/', {skipLocationChange: true});
2457
+ yield this.router.navigate(['/login']);
2383
2458
  });
2384
2459
  }
2385
- refreshGaugeValues(gaugeSelected) {
2386
- return __awaiter(this, void 0, void 0, function* () {
2387
- const gaugeValueShowBegin = new Date(this.periodBegin.getTime() - RaainDetailsComponent.DAY_MS);
2388
- const gaugeValueShowEnd = new Date(this.periodEnd.getTime() + RaainDetailsComponent.DAY_MS);
2389
- gaugeValueShowBegin.setHours(0, 0);
2390
- gaugeValueShowEnd.setHours(23, 59);
2391
- const gaugeMeasures = yield this.profileService.getGaugeMeasures(gaugeSelected.id, gaugeValueShowBegin, gaugeValueShowEnd);
2392
- const gaugeValues = gaugeMeasures.map((gm) => {
2393
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2394
- const cartesianMeasureValue = new CartesianMeasureValue(gm.values[0]);
2395
- return {
2396
- date: gm.date,
2397
- value: cartesianMeasureValue.getCartesianValues()[0].value,
2398
- };
2399
- });
2400
- this.gaugeSelectedPoints = [
2401
- {
2402
- label: gaugeSelected.name,
2403
- style: 'bar',
2404
- values: gaugeValues,
2405
- },
2406
- ];
2407
- this.cdr.markForCheck();
2408
- });
2460
+ isLoggedIn() {
2461
+ const loggedIn = this.fidjService.isLoggedIn();
2462
+ console.log('isLoggedIn: ', loggedIn);
2463
+ return loggedIn;
2409
2464
  }
2410
- onGaugeSelectInCompare(e) {
2411
- return __awaiter(this, void 0, void 0, function* () {
2412
- yield this.refreshGaugeValues({ id: e.point.id, name: e.point.name });
2413
- yield this.compareManager.selectGauge(e.point.id, e.compareIndex);
2414
- });
2465
+ needsConnectionRefresh() {
2466
+ return this.fidjService.needsRefresh();
2415
2467
  }
2416
- onToggleMap($event) {
2468
+ connectionRefresh() {
2417
2469
  return __awaiter(this, void 0, void 0, function* () {
2418
- // if (this.toggleMap) {
2419
- // await this.refreshMap();
2420
- // }
2470
+ yield this.fidjService.refresh();
2421
2471
  });
2422
2472
  }
2423
- onTogglePixelMarkers() {
2424
- // Toggle is bound to showPixelMarkers, raain-map handles marker display
2425
- }
2426
- toggleCumulativeChanged(_$event) {
2473
+ storeAll() {
2427
2474
  return __awaiter(this, void 0, void 0, function* () {
2428
- this.storage.set('raain-toggleCumulative', this.toggleCumulative);
2429
- this.dateShown = this.getDateBasedOnCumulativeMode(this.timeframeDates);
2430
- this.updateCumulativeDurationInMinutes();
2431
- if (this.toggleMap) {
2432
- this.updateRefreshManagerPeriod(); // Update cumulative flag before refresh
2433
- yield this.refreshManager.refresh(false, this.toggleAdmin);
2434
- }
2475
+ return this.fidjStorage.storeData(this.fidjService, this.nodeData);
2435
2476
  });
2436
2477
  }
2437
- updateCumulativeDurationInMinutes() {
2438
- if (this.toggleCumulative) {
2439
- // Cumulative mode: use period duration
2440
- this.cumulativeDurationInMinutes = parseFloat(this.periodDurationAsString) * 60;
2441
- }
2442
- else {
2443
- // Granular mode: use selectedTimeStep
2444
- this.cumulativeDurationInMinutes = this.selectedTimeStep || 10;
2445
- }
2478
+ isAdmin() {
2479
+ return this.roles.indexOf('admin') > -1;
2446
2480
  }
2447
- onProviderChanged($event) {
2448
- var _a;
2481
+ // === Notifications ===
2482
+ createNotification(rainId, message) {
2449
2483
  return __awaiter(this, void 0, void 0, function* () {
2450
- this.selectedProvider = (_a = $event === null || $event === void 0 ? void 0 : $event.detail) === null || _a === void 0 ? void 0 : _a.value;
2451
- this.storage.set('raain-selectedProvider', this.selectedProvider);
2452
- yield this.applyRefreshManagerSettings();
2484
+ const data = {
2485
+ rain: rainId,
2486
+ message,
2487
+ };
2488
+ try {
2489
+ const resp = yield this.fidjService.sendOnEndpoint({
2490
+ key: 'notifications',
2491
+ verb: 'POST',
2492
+ defaultKeyUrl: this.defaultUrlForAPI + '/notifications',
2493
+ data,
2494
+ });
2495
+ return new EventNode(resp.data);
2496
+ }
2497
+ catch (e) {
2498
+ yield this.checkError(e);
2499
+ }
2500
+ return null;
2453
2501
  });
2454
2502
  }
2455
- onTimeStepChanged($event) {
2456
- var _a;
2503
+ getNotifications(rainId) {
2457
2504
  return __awaiter(this, void 0, void 0, function* () {
2458
- this.selectedTimeStep = (_a = $event === null || $event === void 0 ? void 0 : $event.detail) === null || _a === void 0 ? void 0 : _a.value;
2459
- this.storage.set('raain-selectedTimeStep', this.selectedTimeStep);
2460
- this.updateCumulativeDurationInMinutes();
2461
- yield this.applyRefreshManagerSettings();
2505
+ try {
2506
+ const params = { rain: rainId };
2507
+ const queryString = BuildQueryString(params);
2508
+ const resp = yield this.fidjService.sendOnEndpoint({
2509
+ key: 'notifications',
2510
+ verb: 'GET',
2511
+ relativePath: queryString ? '?' + queryString : '',
2512
+ defaultKeyUrl: this.defaultUrlForAPI + '/notifications',
2513
+ });
2514
+ return resp.data.notifications.map((n) => new EventNode(n));
2515
+ }
2516
+ catch (e) {
2517
+ yield this.checkError(e);
2518
+ }
2519
+ return null;
2462
2520
  });
2463
2521
  }
2464
- loadProviders() {
2465
- var _a;
2522
+ getAllNotifications() {
2466
2523
  return __awaiter(this, void 0, void 0, function* () {
2467
- if (!((_a = this.rainNode) === null || _a === void 0 ? void 0 : _a.id)) {
2468
- return;
2469
- }
2470
2524
  try {
2471
- const result = yield this.profileService.getProviders(this.rainNode.id);
2472
- this.availableProviders = result.providers;
2473
- this.availableTimeSteps = result.timeStepInMinutes;
2474
- // Load saved selections or use defaults
2475
- this.selectedProvider =
2476
- this.storage.get('raain-selectedProvider') ||
2477
- (this.availableProviders.length > 0 ? this.availableProviders[0] : 'Raain');
2478
- this.selectedTimeStep =
2479
- this.storage.get('raain-selectedTimeStep') ||
2480
- (this.availableTimeSteps.length > 0 ? this.availableTimeSteps[0] : 10);
2525
+ const resp = yield this.fidjService.sendOnEndpoint({
2526
+ key: 'notifications',
2527
+ verb: 'GET',
2528
+ defaultKeyUrl: this.defaultUrlForAPI + '/notifications',
2529
+ });
2530
+ return resp.data.notifications.map((n) => new EventNode(n));
2481
2531
  }
2482
2532
  catch (e) {
2483
- // Set fallback values
2484
- this.availableProviders = ['Raain'];
2485
- this.availableTimeSteps = [5, 10, 15, 30, 60];
2486
- this.selectedProvider = 'Raain';
2487
- this.selectedTimeStep = 10;
2488
- }
2489
- // Set provider and timeStep on refreshManager
2490
- if (this.refreshManager) {
2491
- this.refreshManager.provider = this.selectedProvider;
2492
- this.refreshManager.timeStepInMinutes = this.selectedTimeStep;
2533
+ yield this.checkError(e);
2493
2534
  }
2494
- this.cdr.markForCheck();
2535
+ return [];
2495
2536
  });
2496
2537
  }
2497
- onChangeDetectionTest(rainNode) {
2498
- console.log(TEST_DETECTION++, 'onChangeDetectionTest');
2499
- return '';
2500
- }
2501
- updateTruncatedError() {
2502
- var _a;
2503
- const error = ((_a = this.refreshManager) === null || _a === void 0 ? void 0 : _a.lastError) || '';
2504
- if (error.length <= 80) {
2505
- this.truncatedError = error;
2506
- return;
2507
- }
2508
- this.truncatedError = error.substring(0, 80) + '...';
2509
- }
2510
- updateCachedValues() {
2511
- this.updatePercentOfImages();
2512
- this.updatePercentOfComputations();
2513
- this.updateTruncatedError();
2514
- this.updateCumulativeDurationInMinutes();
2515
- }
2516
- refreshMap() {
2538
+ // === Teams ===
2539
+ getTeams() {
2517
2540
  return __awaiter(this, void 0, void 0, function* () {
2518
- this.gaugeSelectedPoints = [];
2519
- this.dateShown = this.getDateBasedOnCumulativeMode();
2520
- this.borders = [];
2521
- this.compareManager.cleanAll();
2522
- yield this.compareManager.setGaugesInMap();
2523
- yield this.refreshManager.refresh(false, this.toggleAdmin);
2524
- this.cdr.markForCheck();
2541
+ const teams = [];
2542
+ const params = {};
2543
+ const queryString = BuildQueryString(params);
2544
+ try {
2545
+ const resp = yield this.fidjService.sendOnEndpoint({
2546
+ key: 'teams',
2547
+ verb: 'GET',
2548
+ relativePath: queryString ? '?' + queryString : '',
2549
+ defaultKeyUrl: this.defaultUrlForAPI + '/teams',
2550
+ });
2551
+ for (const team of resp.data.teams) {
2552
+ teams.push(new TeamNode(team));
2553
+ }
2554
+ }
2555
+ catch (e) {
2556
+ yield this.checkError(e);
2557
+ }
2558
+ return teams;
2525
2559
  });
2526
2560
  }
2527
- setPeriodOfNow() {
2561
+ getTeam(teamId) {
2528
2562
  return __awaiter(this, void 0, void 0, function* () {
2529
- const last30mn = new Date();
2530
- last30mn.setMinutes(last30mn.getMinutes() - 30);
2531
- this.periodBeginAsString =
2532
- last30mn.toISOString().substring(0, 11) + last30mn.toLocaleTimeString().substring(0, 5);
2533
- this.periodDurationAsString = '1';
2534
- yield this.onPeriodBeginChange(null);
2535
- yield this.refreshManager.refresh(false, this.toggleAdmin);
2536
- });
2537
- }
2538
- updatePercentOfImages() {
2539
- var _a, _b;
2540
- if (!((_b = (_a = this.countsPeriod) === null || _a === void 0 ? void 0 : _a.percentImages) === null || _b === void 0 ? void 0 : _b.length)) {
2541
- this.percentOfImages = 0;
2542
- return;
2543
- }
2544
- const duringPeriod = this.countsPeriod.percentImages.filter((a) => this.periodBegin.getTime() <= new Date(a.name).getTime() &&
2545
- new Date(a.name).getTime() <= this.periodEnd.getTime());
2546
- const summed = duringPeriod.reduce((a, b) => { var _a; return a + ((_a = b.x) !== null && _a !== void 0 ? _a : 0); }, 0);
2547
- this.percentOfImages = Math.round(summed / duringPeriod.length);
2548
- }
2549
- updatePercentOfComputations() {
2550
- var _a;
2551
- const timeline = (_a = this.refreshManager) === null || _a === void 0 ? void 0 : _a.getTimelineFrameSet();
2552
- if (!(timeline === null || timeline === void 0 ? void 0 : timeline.length)) {
2553
- this.percentOfComputations = 0;
2554
- return;
2555
- }
2556
- const timelineWithComputation = timeline.filter((a) => !!a.rainComputationCumulativeId || !!a.rainComputationId);
2557
- const ratio = timelineWithComputation.length / timeline.length;
2558
- this.percentOfComputations = Math.round(ratio * 100);
2559
- }
2560
- getDateBasedOnCumulativeMode(fallbackDates) {
2561
- if ((fallbackDates === null || fallbackDates === void 0 ? void 0 : fallbackDates.length) > 0) {
2562
- const dateExists = fallbackDates.some((d) => { var _a; return d.getTime() === ((_a = this.dateShown) === null || _a === void 0 ? void 0 : _a.getTime()); });
2563
- if (!dateExists) {
2564
- return this.toggleCumulative
2565
- ? fallbackDates[fallbackDates.length - 1]
2566
- : fallbackDates[0];
2563
+ try {
2564
+ const resp = yield this.fidjService.sendOnEndpoint({
2565
+ key: 'teams',
2566
+ verb: 'GET',
2567
+ relativePath: teamId,
2568
+ defaultKeyUrl: this.defaultUrlForAPI + '/teams',
2569
+ });
2570
+ return new TeamNode(resp.data);
2567
2571
  }
2568
- }
2569
- return this.toggleCumulative ? this.periodEnd : this.periodBegin;
2570
- }
2571
- getCumulativeHours() {
2572
- return this.toggleCumulative ? parseFloat(this.periodDurationAsString) : 0;
2573
- }
2574
- getDurationInHours() {
2575
- return parseFloat(this.periodDurationAsString);
2576
- }
2577
- updateRefreshManagerPeriod() {
2578
- this.refreshManager.cumulative = this.toggleCumulative;
2579
- // Align dates to 5-minute boundaries (floor) for consistency with raain-ground
2580
- const alignTo5minFloor = (date) => {
2581
- const minutes = date.getMinutes();
2582
- const alignedMinutes = Math.floor(minutes / 5) * 5;
2583
- return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), alignedMinutes, 0, 0);
2584
- };
2585
- this.refreshManager.period = {
2586
- begin: alignTo5minFloor(this.periodBegin),
2587
- end: alignTo5minFloor(this.periodEnd),
2588
- };
2572
+ catch (e) {
2573
+ yield this.checkError(e);
2574
+ }
2575
+ return null;
2576
+ });
2589
2577
  }
2590
- fetchAndUpdateMap() {
2578
+ // === Radars ===
2579
+ getRadars(name) {
2591
2580
  return __awaiter(this, void 0, void 0, function* () {
2592
- yield this.refreshManager.fetch(this.dateShown, this.toggleGaugeMeasures, this.getCumulativeHours());
2593
- this.currentTimeframeTarget = this.refreshManager.getTimelineSelectedFrameSet();
2594
- this.cdr.markForCheck();
2581
+ const radars = [];
2582
+ const params = {};
2583
+ if (name) {
2584
+ params.name = name;
2585
+ }
2586
+ const queryString = BuildQueryString(params);
2587
+ try {
2588
+ const resp = yield this.fidjService.sendOnEndpoint({
2589
+ key: 'radars',
2590
+ verb: 'GET',
2591
+ relativePath: queryString ? '?' + queryString : '',
2592
+ defaultKeyUrl: this.defaultUrlForAPI + '/radars',
2593
+ });
2594
+ for (const r of resp.data.radars) {
2595
+ const radar = new RadarNode(r);
2596
+ radars.push(radar);
2597
+ }
2598
+ }
2599
+ catch (e) {
2600
+ yield this.checkError(e);
2601
+ }
2602
+ return radars;
2595
2603
  });
2596
2604
  }
2597
- applyRefreshManagerSettings() {
2605
+ getRadar(id) {
2598
2606
  return __awaiter(this, void 0, void 0, function* () {
2599
- if (!this.refreshManager) {
2600
- return;
2607
+ try {
2608
+ const resp = yield this.fidjService.sendOnEndpoint({
2609
+ key: 'radars',
2610
+ verb: 'GET',
2611
+ relativePath: id,
2612
+ defaultKeyUrl: this.defaultUrlForAPI + '/radars',
2613
+ });
2614
+ return new RadarNode(resp.data);
2601
2615
  }
2602
- this.refreshManager.provider = this.selectedProvider;
2603
- this.refreshManager.timeStepInMinutes = this.selectedTimeStep;
2604
- yield this.refreshManager.refresh(false, this.toggleAdmin);
2616
+ catch (e) {
2617
+ yield this.checkError(e);
2618
+ }
2619
+ return null;
2605
2620
  });
2606
2621
  }
2607
- cleanAll() {
2608
- var _a, _b;
2609
- this.borders = [];
2610
- this.isAdmin = false;
2611
- this.timeframeContainers = new TimeframeContainers([]);
2612
- this.currentTimeframeTarget = null;
2613
- this.timeframeDates = [];
2614
- this.countPoints = [];
2615
- this.countsPeriod = { progress: 0, queueRunning: 0, percentImages: [] };
2616
- this.gaugeSelectedPoints = [];
2617
- this.toggleHistory = false;
2618
- this.toggleMap = true;
2619
- this.toggleCompare = false;
2620
- this.toggleGaugeMeasures = false;
2621
- this.toggleRemoveCompareDuplicate = true;
2622
- this.toggleCumulative = this.storage.get('raain-toggleCumulative');
2623
- this.periodBegin = new Date(this.storage.get('raain-periodBegin'));
2624
- this.periodEnd = new Date(this.storage.get('raain-periodEnd'));
2625
- this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
2626
- this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
2627
- const durationMs = this.periodEnd.getTime() - this.periodBegin.getTime();
2628
- this.periodDurationAsString = '' + durationMs / RaainDetailsComponent.HOUR_MS;
2629
- this.dateShown = this.getDateBasedOnCumulativeMode();
2630
- this.refreshInProgress = false;
2631
- this.showFullError = false;
2632
- this.showQualityModal = false;
2633
- this.qualityIndicators = [];
2634
- this.qualityIndicatorsLoading = false;
2635
- (_a = this.compareManager) === null || _a === void 0 ? void 0 : _a.cleanAll();
2636
- (_b = this.refreshManager) === null || _b === void 0 ? void 0 : _b.cleanAll();
2637
- }
2638
- init() {
2639
- return __awaiter(this, void 0, void 0, function* () {
2640
- this.cleanAll();
2641
- this.updateCachedValues();
2642
- yield this.initRain();
2643
- this.cdr.markForCheck();
2644
- });
2645
- }
2646
- initRain() {
2622
+ putRadar(radarNode) {
2647
2623
  return __awaiter(this, void 0, void 0, function* () {
2648
- if (!this.rainNode) {
2649
- return;
2624
+ const data = {
2625
+ name: radarNode.name,
2626
+ };
2627
+ try {
2628
+ const resp = yield this.fidjService.sendOnEndpoint({
2629
+ key: 'radars',
2630
+ relativePath: radarNode.id,
2631
+ verb: 'PUT',
2632
+ data,
2633
+ defaultKeyUrl: this.defaultUrlForAPI + '/radars/',
2634
+ });
2635
+ return new RadarNode(resp.data);
2650
2636
  }
2651
- this.isAdmin = this.profileService.isAdmin();
2652
- this.refreshManager.rainNode = this.rainNode;
2653
- this.compareManager.rainNode = this.rainNode;
2654
- // Load providers and set on refreshManager
2655
- yield this.loadProviders();
2656
- this.refreshManager.setMethods(this.onRefreshInProgress.bind(this), this.onRefreshDone.bind(this), this.onFetchDone.bind(this));
2657
- const center = this.rainNode.getCenter();
2658
- this.coordinates = new MapLatLng(center.lat, center.lng);
2659
- this.teamNode = yield this.profileService.getTeam(this.rainNode.getLink(TeamNode.TYPE).getId());
2660
- if (this.periodBegin && this.periodEnd) {
2661
- this.updateRefreshManagerPeriod();
2662
- yield this.refreshManager.refresh(false, this.toggleAdmin);
2637
+ catch (e) {
2638
+ yield this.checkError(e);
2663
2639
  }
2664
2640
  });
2665
2641
  }
2666
- onRefreshInProgress(countPeriods, timeframeDates) {
2642
+ getLonelyRadars(rains) {
2667
2643
  return __awaiter(this, void 0, void 0, function* () {
2668
- this.refreshInProgress = true;
2669
- this.countsPeriod = countPeriods;
2670
- this.timeframeDates = timeframeDates;
2671
- this.updateCachedValues();
2672
- this.cdr.markForCheck();
2644
+ try {
2645
+ const resp = yield this.fidjService.sendOnEndpoint({
2646
+ key: 'radars',
2647
+ verb: 'GET',
2648
+ defaultKeyUrl: this.defaultUrlForAPI + '/radars',
2649
+ });
2650
+ const lonelyRadars = [];
2651
+ const radars = resp.data.radars;
2652
+ radars.forEach((radar) => {
2653
+ let found = false;
2654
+ rains.forEach((rain) => {
2655
+ const rdId = rain.getLink(RadarNode.TYPE).getId();
2656
+ if (rdId === radar.id) {
2657
+ found = true;
2658
+ }
2659
+ });
2660
+ if (!found) {
2661
+ lonelyRadars.push(new RadarNode(radar));
2662
+ }
2663
+ });
2664
+ return lonelyRadars;
2665
+ }
2666
+ catch (e) {
2667
+ yield this.checkError(e);
2668
+ }
2669
+ return [];
2673
2670
  });
2674
2671
  }
2675
- onRefreshDone(timeframeDates) {
2672
+ getRainTimeframe(rainId, begin, end, forced = false, provider = 'Raain', timeStepInMinutes = 10, windowInMinutes = 0) {
2676
2673
  return __awaiter(this, void 0, void 0, function* () {
2677
- this.timeframeDates = timeframeDates;
2678
- this.refreshInProgress = false;
2679
- this.updateCachedValues();
2680
- this.cdr.markForCheck();
2681
- if (this.toggleMap && timeframeDates.length > 0) {
2682
- this.dateShown = this.getDateBasedOnCumulativeMode(timeframeDates);
2683
- if (this.dateShown) {
2684
- yield this.fetchAndUpdateMap();
2674
+ try {
2675
+ const params = {
2676
+ format: 'timeframeCumulative',
2677
+ provider,
2678
+ timeStepInMinutes: String(timeStepInMinutes),
2679
+ begin: begin === null || begin === void 0 ? void 0 : begin.toISOString(),
2680
+ end: end === null || end === void 0 ? void 0 : end.toISOString(),
2681
+ };
2682
+ if (forced) {
2683
+ params.forced = 'true';
2685
2684
  }
2685
+ params.windowInMinutes = String(windowInMinutes);
2686
+ const queryString = BuildQueryString(params);
2687
+ const resp = yield this.fidjService.sendOnEndpoint({
2688
+ key: 'rains',
2689
+ verb: 'GET',
2690
+ relativePath: rainId + (queryString ? '?' + queryString : ''),
2691
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains',
2692
+ });
2693
+ const rainNode = new RainNode(resp.data.timeframeCumulative);
2694
+ rainNode.name += '.radar.timeframeCumulative';
2695
+ return rainNode;
2696
+ }
2697
+ catch (e) {
2698
+ yield this.checkError(e);
2686
2699
  }
2700
+ return null;
2687
2701
  });
2688
2702
  }
2689
- onFetchDone(timeframeContainers) {
2703
+ // === Rains ===
2704
+ getRains(name) {
2690
2705
  return __awaiter(this, void 0, void 0, function* () {
2691
- if (timeframeContainers) {
2692
- this.timeframeContainers = timeframeContainers;
2706
+ const rains = [];
2707
+ const params = {};
2708
+ if (name) {
2709
+ params.name = name;
2693
2710
  }
2694
- this.cdr.markForCheck();
2711
+ const queryString = BuildQueryString(params);
2712
+ try {
2713
+ const resp = yield this.fidjService.sendOnEndpoint({
2714
+ key: 'rains',
2715
+ verb: 'GET',
2716
+ relativePath: queryString ? '?' + queryString : '',
2717
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains',
2718
+ });
2719
+ for (const rain of resp.data.rains) {
2720
+ rains.push(new RainNode(rain));
2721
+ }
2722
+ }
2723
+ catch (e) {
2724
+ yield this.checkError(e);
2725
+ }
2726
+ return rains;
2695
2727
  });
2696
2728
  }
2697
- change(_changes) {
2729
+ getRain(id) {
2698
2730
  return __awaiter(this, void 0, void 0, function* () {
2699
- yield this.init();
2731
+ try {
2732
+ const resp = yield this.fidjService.sendOnEndpoint({
2733
+ key: 'rains',
2734
+ relativePath: id,
2735
+ verb: 'GET',
2736
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains/',
2737
+ });
2738
+ return new RainNode(resp.data);
2739
+ }
2740
+ catch (e) {
2741
+ yield this.checkError(e);
2742
+ }
2743
+ return null;
2700
2744
  });
2701
2745
  }
2702
- }
2703
- RaainDetailsComponent.HOUR_MS = 60 * 60000;
2704
- RaainDetailsComponent.DAY_MS = 24 * 60 * 60 * 1000;
2705
- 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 });
2706
- 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 });
2707
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RaainDetailsComponent, decorators: [{
2708
- type: Component,
2709
- 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"] }]
2710
- }], ctorParameters: function () { return [{ type: Storage }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { toggleAdmin: [{
2711
- type: Input
2712
- }], rainNode: [{
2713
- type: Input
2714
- }], compareManager: [{
2715
- type: Input
2716
- }], refreshManager: [{
2717
- type: Input
2718
- }], profileService: [{
2719
- type: Input
2720
- }], radarService: [{
2721
- type: Input
2722
- }] } });
2723
-
2724
- class Cache {
2725
- constructor() {
2726
- this._cache = {};
2727
- }
2728
- getValue(key, asyncGetter) {
2746
+ // === Count
2747
+ getCounts(rainId, options) {
2729
2748
  return __awaiter(this, void 0, void 0, function* () {
2730
- if (!Object.prototype.hasOwnProperty.call(this._cache, key)) {
2731
- console.log('cache not done: ', key);
2732
- this.putValue(key, yield asyncGetter());
2749
+ try {
2750
+ const params = {
2751
+ range: options.range,
2752
+ begin: options.periodBegin.toISOString(),
2753
+ };
2754
+ const queryString = BuildQueryString(params);
2755
+ const resp = yield this.fidjService.sendOnEndpoint({
2756
+ key: 'rains',
2757
+ relativePath: rainId + '/counts' + (queryString ? '?' + queryString : ''),
2758
+ verb: 'GET',
2759
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains/',
2760
+ });
2761
+ const counts = resp.data.counts.result;
2762
+ const percentImages = [], percentRainy = [], percentQ = [];
2763
+ counts.forEach((c) => {
2764
+ var _a, _b, _c;
2765
+ const label = this.setDateComponents(options.periodBegin, c);
2766
+ percentImages.push(new XYType((_a = c.percentImages) !== null && _a !== void 0 ? _a : 0, NaN, NaN, label));
2767
+ percentRainy.push(new XYType((_b = c.percentRainy) !== null && _b !== void 0 ? _b : 0, NaN, NaN, label));
2768
+ percentQ.push(new XYType((_c = c.percentQ) !== null && _c !== void 0 ? _c : 0, NaN, NaN, label));
2769
+ });
2770
+ return {
2771
+ percentImages,
2772
+ percentRainy,
2773
+ percentQ,
2774
+ queueRunning: resp.data.queueRunning,
2775
+ };
2733
2776
  }
2734
- else {
2735
- console.log('cache done: ', key);
2777
+ catch (e) {
2778
+ yield this.checkError(e);
2736
2779
  }
2737
- return this._cache[key];
2780
+ return null;
2738
2781
  });
2739
2782
  }
2740
- putValue(key, value) {
2741
- this._cache[key] = value;
2742
- const length = Object.getOwnPropertyNames(this._cache).length;
2743
- if (length > 30) {
2744
- console.warn('Pb on cache size exceed ? do a restart ?', length);
2745
- }
2746
- }
2747
- }
2748
- Cache.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: Cache, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2749
- Cache.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: Cache, providedIn: 'root' });
2750
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: Cache, decorators: [{
2751
- type: Injectable,
2752
- args: [{
2753
- providedIn: 'root',
2754
- }]
2755
- }], ctorParameters: function () { return []; } });
2756
-
2757
- class FidjStorageNode {
2758
- constructor() {
2759
- this.radars = [];
2760
- this.rains = [];
2761
- this.gauges = [];
2762
- this.events = [];
2763
- this.infos = {};
2764
- }
2765
- static getEmptyNode() {
2766
- return new FidjStorageNode();
2783
+ getCountsHour(rainId, options) {
2784
+ return __awaiter(this, void 0, void 0, function* () {
2785
+ try {
2786
+ const params = {
2787
+ range: 'hour',
2788
+ begin: options.periodBegin.toISOString(),
2789
+ };
2790
+ const queryString = BuildQueryString(params);
2791
+ const resp = yield this.fidjService.sendOnEndpoint({
2792
+ key: 'rains',
2793
+ relativePath: rainId + '/counts' + (queryString ? '?' + queryString : ''),
2794
+ verb: 'GET',
2795
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains/',
2796
+ });
2797
+ const counts = resp.data.counts.result;
2798
+ const percentImages = [], rainySum = [], rainyCount = [];
2799
+ counts.forEach((c) => {
2800
+ var _a, _b, _c;
2801
+ const label = this.setDateComponents(options.periodBegin, c);
2802
+ percentImages.push(new XYType((_a = c.percentImages) !== null && _a !== void 0 ? _a : 0, NaN, NaN, label));
2803
+ rainyCount.push(new XYType((_b = c.rainyCount) !== null && _b !== void 0 ? _b : 0, NaN, NaN, label));
2804
+ rainySum.push(new XYType((_c = c.rainySum) !== null && _c !== void 0 ? _c : 0, NaN, NaN, label));
2805
+ });
2806
+ return {
2807
+ percentImages,
2808
+ rainyCount,
2809
+ rainySum,
2810
+ queueRunning: resp.data.queueRunning,
2811
+ };
2812
+ }
2813
+ catch (e) {
2814
+ yield this.checkError(e);
2815
+ }
2816
+ return null;
2817
+ });
2767
2818
  }
2768
- static getDemoNode() {
2769
- const demoNode = new FidjStorageNode();
2770
- const link = new Link('rain', 'https://demo/api/rains/2');
2771
- /*
2772
- demoNode.radars = [
2773
- new RadarNode({
2774
- id: '5efd04569cb1f4161bd69dc7',
2775
- name: 'demo radar A',
2776
- links: [link],
2777
- latitude: 48.774569,
2778
- longitude: 2.008407
2779
- }),
2780
- new RadarNode({
2781
- id: 'dr2',
2782
- name: 'demo radar B',
2783
- links: [link],
2784
- latitude: 0.11,
2785
- longitude: -0.753
2786
- }),
2787
- new RadarNode({
2788
- id: 'dr3',
2789
- name: 'demo radar C',
2790
- latitude: 0.13,
2791
- longitude: -0.753,
2792
- links: []
2793
- }),
2794
- new RadarNode({
2795
- id: 'dr4',
2796
- name: 'demo radar D',
2797
- latitude: 0.14,
2798
- longitude: -0.74,
2799
- links: []
2800
- })];
2801
- demoNode.rains = [
2802
- new RainNode({
2803
- id: '5efd04569cb1f4161bd69dc8',
2804
- name: 'Demo rain zone A',
2805
- links: [new Link('radar', 'https://demo/api/radars/5efcfe619cb1f4161bd69dc3')],
2806
- status: 0,
2807
- quality: 75,
2808
- latitude: 48.774569,
2809
- longitude: 2.008407
2810
- }),
2811
- new RainNode({
2812
- id: 'dz2',
2813
- name: 'Demo rain zone B',
2814
- radars: [demoNode.radars[0], demoNode.radars[1]],
2815
- status: 1,
2816
- quality: 50,
2817
- latitude: 48.774569,
2818
- longitude: 2.008407
2819
- }),
2820
- new RainNode({
2821
- id: 'dz3',
2822
- name: 'Demo rain zone C',
2823
- radars: [demoNode.radars[0], demoNode.radars[1]],
2824
- status: 2,
2825
- quality: 75,
2826
- latitude: 48.774569,
2827
- longitude: 2.008407
2828
- }),
2829
- new RainNode({
2830
- id: 'dz4',
2831
- name: 'Demo rain zone D',
2832
- radars: [demoNode.radars[0], demoNode.radars[1]],
2833
- status: 3,
2834
- quality: 95,
2835
- latitude: 48.774569,
2836
- longitude: 2.008407
2837
- })];
2838
-
2839
- demoNode.gauges = [
2840
- new GaugeNode({
2841
- id: 'g1',
2842
- name: 'Gauge A',
2843
- latitude: 48.7748,
2844
- longitude: 2.28407,
2845
- }),
2846
- new GaugeNode({
2847
- id: 'g2',
2848
- name: 'Gauge B',
2849
- latitude: 48.874569,
2850
- longitude: 2.108407,
2851
- })];
2852
- demoNode.events = [{
2853
- id: 'e2',
2854
- title: 'Need support ?',
2855
- status: 0,
2856
- red: false,
2857
- description: 'This area is dedicated to support you and your team. Support is made on Radar or Rain quality, ' +
2858
- 'or any feedback we can have about your production system. Our goal : improving your data.',
2859
- created: new Date(),
2860
- modified: new Date()
2861
- }];
2862
- demoNode.team = {
2863
- id: 'p1',
2864
- email: 'demo@demo.com',
2865
- name: 'demo guy',
2866
- description: 'the demo guy'
2867
- };
2868
-
2869
- */
2870
- return demoNode;
2819
+ // === Computing ===
2820
+ getRainComputationCumulativeCartesianMapById(rainId, rainComputationCumulativeId) {
2821
+ return __awaiter(this, void 0, void 0, function* () {
2822
+ const params = { format: 'cartesian-map' };
2823
+ const queryString = BuildQueryString(params);
2824
+ try {
2825
+ const response = yield this.fidjService.sendOnEndpoint({
2826
+ key: 'rains',
2827
+ verb: 'GET',
2828
+ relativePath: `${rainId}/cumulatives/${rainComputationCumulativeId}?${queryString}`,
2829
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains',
2830
+ });
2831
+ if (!response.data['cartesian-map']) {
2832
+ return null;
2833
+ }
2834
+ const rainComputationMap = new RainComputationMap(response.data['cartesian-map']);
2835
+ rainComputationMap.name = rainId + '.rain.cartesian-map';
2836
+ return rainComputationMap;
2837
+ }
2838
+ catch (e) {
2839
+ yield this.checkError(e);
2840
+ }
2841
+ return null;
2842
+ });
2871
2843
  }
2872
- }
2873
- class FidjStorageResult {
2874
- }
2875
- class FidjStorage {
2876
- constructor(storage) {
2877
- this.storage = storage;
2878
- this.node = FidjStorageNode.getEmptyNode();
2879
- this.fidjMetaResult = { data: new FidjStorageNode() };
2844
+ getRainComputationCumulativeCumulativesMapById(rainId, rainComputationCumulativeId, cumulativeHours) {
2845
+ return __awaiter(this, void 0, void 0, function* () {
2846
+ const queryPath = `${rainId}/cumulatives/${rainComputationCumulativeId}/cumulative/${cumulativeHours}`;
2847
+ try {
2848
+ const response = yield this.fidjService.sendOnEndpoint({
2849
+ key: 'rains',
2850
+ verb: 'GET',
2851
+ relativePath: queryPath,
2852
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains',
2853
+ });
2854
+ if (!response.data['cumulative']) {
2855
+ return null;
2856
+ }
2857
+ const rainComputationMap = new RainComputationMap(response.data['cumulative']);
2858
+ rainComputationMap.name = rainId + '.rain.cumulative-cumulative';
2859
+ return rainComputationMap;
2860
+ }
2861
+ catch (e) {
2862
+ yield this.checkError(e);
2863
+ }
2864
+ return null;
2865
+ });
2880
2866
  }
2881
- storeData(fidjService, data) {
2867
+ getRainCumulativeCompareByDate(rainNode, rainComputationCumulativeId, date) {
2882
2868
  return __awaiter(this, void 0, void 0, function* () {
2883
- this.node = JSON.parse(JSON.stringify(data));
2884
- this.fidjMetaResult.data = this.node;
2885
- if (this.isDemoMode) {
2886
- this.storage.set('fidjMetaResult', JSON.stringify(this.fidjMetaResult));
2887
- return;
2869
+ const params = { date: date.toISOString() };
2870
+ const queryString = BuildQueryString(params);
2871
+ try {
2872
+ const response = yield this.fidjService.sendOnEndpoint({
2873
+ key: 'rains',
2874
+ verb: 'GET',
2875
+ relativePath: `${rainNode.id}/cumulatives/${rainComputationCumulativeId}/compares?${queryString}`,
2876
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains',
2877
+ });
2878
+ const qualityJson = response.data.qualities[0];
2879
+ const rainComputationQuality = new RainComputationQuality(qualityJson);
2880
+ rainComputationQuality.qualitySpeedMatrixContainer =
2881
+ SpeedMatrixContainer.CreateFromJson(rainComputationQuality.qualitySpeedMatrixContainer);
2882
+ return rainComputationQuality;
2883
+ }
2884
+ catch (e) {
2885
+ yield this.checkError(e);
2886
+ }
2887
+ return null;
2888
+ });
2889
+ }
2890
+ getRainCumulativeCumulativesCompareByDate(rainNode, rainComputationCumulativeId, date, cumulativeHours) {
2891
+ return __awaiter(this, void 0, void 0, function* () {
2892
+ const params = {
2893
+ date: date.toISOString(),
2894
+ cumulativeHours,
2895
+ };
2896
+ const queryString = BuildQueryString(params);
2897
+ try {
2898
+ const response = yield this.fidjService.sendOnEndpoint({
2899
+ key: 'rains',
2900
+ verb: 'GET',
2901
+ relativePath: `${rainNode.id}/cumulatives/${rainComputationCumulativeId}/compares?${queryString}`,
2902
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains',
2903
+ });
2904
+ const qualityJson = response.data.qualities[0];
2905
+ const rainComputationQuality = new RainComputationQuality(qualityJson);
2906
+ rainComputationQuality.qualitySpeedMatrixContainer =
2907
+ SpeedMatrixContainer.CreateFromJson(rainComputationQuality.qualitySpeedMatrixContainer);
2908
+ return rainComputationQuality;
2909
+ }
2910
+ catch (e) {
2911
+ yield this.checkError(e);
2912
+ }
2913
+ return null;
2914
+ });
2915
+ }
2916
+ getRainProgress(rainId) {
2917
+ return __awaiter(this, void 0, void 0, function* () {
2918
+ try {
2919
+ const queryPath = '' + rainId + '/progress';
2920
+ const response = yield this.fidjService.sendOnEndpoint({
2921
+ key: 'rains',
2922
+ verb: 'GET',
2923
+ relativePath: queryPath,
2924
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains',
2925
+ });
2926
+ // return response.data.inProgress;
2927
+ return response.data.inQueue;
2928
+ }
2929
+ catch (e) {
2930
+ yield this.checkError(e);
2931
+ }
2932
+ return 0;
2933
+ });
2934
+ }
2935
+ getIndicators(rainId) {
2936
+ return __awaiter(this, void 0, void 0, function* () {
2937
+ try {
2938
+ const params = {
2939
+ cumulative: 'true',
2940
+ };
2941
+ const queryString = BuildQueryString(params);
2942
+ const response = yield this.fidjService.sendOnEndpoint({
2943
+ key: 'rains',
2944
+ verb: 'GET',
2945
+ relativePath: rainId + '/indicators' + (queryString ? '?' + queryString : ''),
2946
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains',
2947
+ });
2948
+ return response.data;
2949
+ }
2950
+ catch (e) {
2951
+ yield this.checkError(e);
2952
+ }
2953
+ return { indicators: [] };
2954
+ });
2955
+ }
2956
+ // GET /rains/:rainId/cumulatives - List available cumulative periods
2957
+ getCumulativePeriods(rainId, filters) {
2958
+ return __awaiter(this, void 0, void 0, function* () {
2959
+ try {
2960
+ const params = {};
2961
+ if ((filters === null || filters === void 0 ? void 0 : filters.windowInMinutes) !== undefined) {
2962
+ params.windowInMinutes = filters.windowInMinutes;
2963
+ }
2964
+ if (filters === null || filters === void 0 ? void 0 : filters.provider) {
2965
+ params.provider = filters.provider;
2966
+ }
2967
+ if ((filters === null || filters === void 0 ? void 0 : filters.isReady) !== undefined) {
2968
+ params.isReady = filters.isReady;
2969
+ }
2970
+ if ((filters === null || filters === void 0 ? void 0 : filters.forced) !== undefined) {
2971
+ params.forced = filters.forced;
2972
+ }
2973
+ const queryString = BuildQueryString(params);
2974
+ const response = yield this.fidjService.sendOnEndpoint({
2975
+ key: 'rains',
2976
+ verb: 'GET',
2977
+ relativePath: rainId + '/cumulatives' + (queryString ? '?' + queryString : ''),
2978
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains',
2979
+ });
2980
+ return response.data;
2981
+ }
2982
+ catch (e) {
2983
+ yield this.checkError(e);
2984
+ }
2985
+ return { periods: [], total: 0 };
2986
+ });
2987
+ }
2988
+ // POST /rains/:rainId/cumulatives - Trigger cumulative computation
2989
+ createCumulativePeriod(rainId, params) {
2990
+ return __awaiter(this, void 0, void 0, function* () {
2991
+ try {
2992
+ const body = {
2993
+ periodBegin: params.periodBegin.toISOString(),
2994
+ periodEnd: params.periodEnd.toISOString(),
2995
+ provider: params.provider,
2996
+ confName: params.confName || 'admin',
2997
+ timeStepInMinutes: params.timeStepInMinutes || 5,
2998
+ };
2999
+ const response = yield this.fidjService.sendOnEndpoint({
3000
+ key: 'rains',
3001
+ verb: 'POST',
3002
+ relativePath: rainId + '/cumulatives',
3003
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains',
3004
+ data: body,
3005
+ });
3006
+ return response.data;
3007
+ }
3008
+ catch (e) {
3009
+ yield this.checkError(e);
3010
+ }
3011
+ return null;
3012
+ });
3013
+ }
3014
+ // === Gauges ===
3015
+ getGauge(gaugeId) {
3016
+ return __awaiter(this, void 0, void 0, function* () {
3017
+ try {
3018
+ const resp = yield this.fidjService.sendOnEndpoint({
3019
+ key: 'gauges',
3020
+ verb: 'GET',
3021
+ relativePath: gaugeId,
3022
+ defaultKeyUrl: this.defaultUrlForAPI + '/gauges',
3023
+ });
3024
+ return new GaugeNode(resp.data);
3025
+ }
3026
+ catch (e) {
3027
+ yield this.checkError(e);
2888
3028
  }
2889
- yield fidjService.put(this.fidjMetaResult);
2890
3029
  });
2891
3030
  }
2892
- getRefreshedNodeCopy(fidjService) {
3031
+ getGauges(rainId, aroundLatLng, pageCount = 1) {
2893
3032
  return __awaiter(this, void 0, void 0, function* () {
2894
- if (this.isDemoMode) {
2895
- const fidjMetaResult = this.storage.get('fidjMetaResult');
2896
- if (fidjMetaResult) {
2897
- this.fidjMetaResult = JSON.parse(fidjMetaResult);
2898
- this.node = this.fidjMetaResult.data;
2899
- }
2900
- return JSON.parse(JSON.stringify(this.node));
3033
+ const baseParams = {
3034
+ aroundLatLng: `${aroundLatLng.lat},${aroundLatLng.lng}`,
3035
+ rainId,
3036
+ };
3037
+ if (this.asTeamId) {
3038
+ baseParams.teamId = this.asTeamId;
2901
3039
  }
2902
- const firstDemoData = () => __awaiter(this, void 0, void 0, function* () {
2903
- this.node = FidjStorageNode.getDemoNode();
2904
- yield this.storeData(fidjService, this.node);
2905
- });
2906
- yield fidjService.sync(firstDemoData);
2907
- const fidjFindAllResults = yield fidjService.findAll();
2908
- if (fidjFindAllResults && fidjFindAllResults.length > 0) {
2909
- if (fidjFindAllResults[0].data) {
2910
- this.fidjMetaResult = fidjFindAllResults[0];
2911
- this.node = this.fidjMetaResult.data;
3040
+ const gauges = [];
3041
+ try {
3042
+ for (let count = 1; count <= pageCount; count++) {
3043
+ const params = Object.assign(Object.assign({}, baseParams), { page: count });
3044
+ const queryString = BuildQueryString(params);
3045
+ const resp = yield this.fidjService.sendOnEndpoint({
3046
+ key: 'gauges',
3047
+ verb: 'GET',
3048
+ relativePath: queryString ? '?' + queryString : '',
3049
+ defaultKeyUrl: this.defaultUrlForAPI + '/gauges',
3050
+ });
3051
+ for (const gauge of resp.data.gauges) {
3052
+ gauges.push(new GaugeNodeFilter(gauge));
3053
+ }
2912
3054
  }
2913
3055
  }
2914
- return JSON.parse(JSON.stringify(this.node));
3056
+ catch (e) {
3057
+ yield this.checkError(e);
3058
+ }
3059
+ return gauges;
2915
3060
  });
2916
3061
  }
2917
- setDemoMode(isDemo) {
2918
- this.isDemoMode = isDemo;
3062
+ getGaugeMeasures(gaugeId, begin, end) {
3063
+ return __awaiter(this, void 0, void 0, function* () {
3064
+ const params = {
3065
+ begin: begin.toISOString(),
3066
+ end: end.toISOString(),
3067
+ };
3068
+ const queryString = BuildQueryString(params);
3069
+ const resp = yield this.fidjService.sendOnEndpoint({
3070
+ key: 'gauges',
3071
+ verb: 'GET',
3072
+ relativePath: gaugeId + '/measures' + (queryString ? '?' + queryString : ''),
3073
+ defaultKeyUrl: this.defaultUrlForAPI + '/gauges',
3074
+ });
3075
+ const gaugeMeasures = [];
3076
+ for (const gaugeMeasure of resp.data.gaugeMeasures) {
3077
+ gaugeMeasures.push(new GaugeMeasure(gaugeMeasure));
3078
+ }
3079
+ return gaugeMeasures;
3080
+ });
2919
3081
  }
2920
- }
2921
-
2922
- class ProfileService {
2923
- constructor(storage, fidjService, httpClient, router) {
2924
- this.storage = storage;
2925
- this.fidjService = fidjService;
2926
- this.httpClient = httpClient;
2927
- this.router = router;
2928
- this.email = this.storage.get('raain-email');
2929
- this.asTeamId = this.storage.get('raain-asTeamId');
2930
- this.readyForSync = new BehaviorSubject(false);
2931
- this.roles = [];
2932
- this.fidjStorage = new FidjStorage(storage);
2933
- this.isDemoMode = false;
3082
+ getProviders(rainId) {
3083
+ return __awaiter(this, void 0, void 0, function* () {
3084
+ try {
3085
+ const response = yield this.fidjService.sendOnEndpoint({
3086
+ verb: 'GET',
3087
+ key: 'rains',
3088
+ relativePath: rainId + '/providers',
3089
+ defaultKeyUrl: this.defaultUrlForAPI + '/rains',
3090
+ });
3091
+ return {
3092
+ providers: response.data.providers || [],
3093
+ timeStepInMinutes: response.data.timeStepInMinutes || [5, 10, 15, 30, 60],
3094
+ };
3095
+ }
3096
+ catch (e) {
3097
+ console.error('getProviders error:', e);
3098
+ return {
3099
+ providers: [],
3100
+ timeStepInMinutes: [5, 10, 15, 30, 60],
3101
+ };
3102
+ }
3103
+ });
2934
3104
  }
2935
- get isDemoMode() {
2936
- return this.isDemo;
3105
+ setRoles(roles) {
3106
+ this.roles = roles;
2937
3107
  }
2938
- set isDemoMode(mode) {
2939
- this.isDemo = mode ? mode : true;
2940
- this.fidjStorage.setDemoMode(this.isDemo);
3108
+ setDateComponents(date, c) {
3109
+ const dateToShow = new Date(date);
3110
+ if (c.year !== undefined) {
3111
+ dateToShow.setUTCFullYear(c.year);
3112
+ }
3113
+ if (c.month !== undefined) {
3114
+ dateToShow.setUTCMonth(c.month - 1);
3115
+ }
3116
+ if (c.day !== undefined) {
3117
+ dateToShow.setUTCDate(c.day);
3118
+ }
3119
+ if (c.hour !== undefined) {
3120
+ dateToShow.setUTCHours(c.hour);
3121
+ }
3122
+ if (c.minute !== undefined) {
3123
+ dateToShow.setUTCMinutes(c.minute);
3124
+ }
3125
+ return dateToShow.toISOString();
2941
3126
  }
2942
- get defaultUrlForAPI() {
2943
- return this.storage.get('raain-urlForAPI');
3127
+ }
3128
+ 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 });
3129
+ ProfileService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: ProfileService, providedIn: 'root' });
3130
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: ProfileService, decorators: [{
3131
+ type: Injectable,
3132
+ args: [{
3133
+ providedIn: 'root',
3134
+ }]
3135
+ }], ctorParameters: function () { return [{ type: Storage }, { type: i2$1.FidjService }, { type: i3$1.HttpClient }, { type: i4$1.Router }]; } });
3136
+
3137
+ class CumulativeSelectorComponent {
3138
+ constructor(profileService, cdr) {
3139
+ this.profileService = profileService;
3140
+ this.cdr = cdr;
3141
+ this.timeStepInMinutes = 5;
3142
+ this.isAdmin = false;
3143
+ this.periodSelected = new EventEmitter();
3144
+ this.cancelled = new EventEmitter();
3145
+ this.availablePeriods = [];
3146
+ this.baseCumulatives = null;
3147
+ this.loading = true;
3148
+ this.creating = false;
3149
+ this.creationProgress = '';
3150
+ this.errorMessage = '';
3151
+ this.coveragePercent = 0;
3152
+ this.canCreateNew = false;
3153
+ this.currentWindowMinutes = 0;
3154
+ this.POLL_TIMEOUT_MS = 900000; // 900 seconds
3155
+ this.POLL_INTERVAL_MS = 3000;
2944
3156
  }
2945
- set defaultUrlForAPI(url) {
2946
- this.storage.set('raain-urlForAPI', url);
3157
+ ngOnInit() {
3158
+ return __awaiter(this, void 0, void 0, function* () {
3159
+ this.currentWindowMinutes = Math.round((this.currentPeriodEnd.getTime() - this.currentPeriodBegin.getTime()) / 60000);
3160
+ yield this.loadAvailablePeriods();
3161
+ });
2947
3162
  }
2948
- refreshProfile() {
3163
+ loadAvailablePeriods() {
2949
3164
  return __awaiter(this, void 0, void 0, function* () {
3165
+ this.loading = true;
3166
+ this.errorMessage = '';
3167
+ this.cdr.markForCheck();
2950
3168
  try {
2951
- this.nodeData = yield this.fidjStorage.getRefreshedNodeCopy(this.fidjService);
2952
- this.setRoles(yield this.fidjService.getRoles());
2953
- return this.nodeData;
3169
+ // Fetch all cumulative periods (admin sees all, non-admin sees only customer-launched)
3170
+ const response = yield this.profileService.getCumulativePeriods(this.rainId, {
3171
+ provider: this.provider,
3172
+ forced: this.isAdmin,
3173
+ });
3174
+ // Filter for existing custom cumulatives (window > 0)
3175
+ this.availablePeriods = response.periods.filter((p) => p.windowInMinutes > 0);
3176
+ // Get base cumulatives (window = 0) to check coverage
3177
+ this.baseCumulatives = response.periods.find((p) => p.windowInMinutes === 0);
3178
+ this.calculateCoverage();
2954
3179
  }
2955
3180
  catch (e) {
2956
- yield this.checkError(e);
3181
+ this.errorMessage = 'Failed to load cumulative periods';
3182
+ console.error('Error loading cumulative periods:', e);
2957
3183
  }
3184
+ this.loading = false;
3185
+ this.cdr.markForCheck();
2958
3186
  });
2959
3187
  }
2960
- isConnected() {
2961
- return __awaiter(this, void 0, void 0, function* () {
2962
- return this.fidjService.isConnected();
2963
- });
2964
- }
2965
- getEmail() {
2966
- var _a;
2967
- return (_a = this.email) !== null && _a !== void 0 ? _a : this.storage.get('raain-email', this.email);
3188
+ calculateCoverage() {
3189
+ if (!this.baseCumulatives) {
3190
+ this.coveragePercent = 0;
3191
+ this.canCreateNew = false;
3192
+ return;
3193
+ }
3194
+ const baseBegin = new Date(this.baseCumulatives.periodBegin);
3195
+ const baseEnd = new Date(this.baseCumulatives.periodEnd);
3196
+ // Check if current period is within base coverage
3197
+ const currentInRange = this.currentPeriodBegin >= baseBegin && this.currentPeriodEnd <= baseEnd;
3198
+ if (!currentInRange) {
3199
+ this.coveragePercent = 0;
3200
+ this.canCreateNew = false;
3201
+ return;
3202
+ }
3203
+ // Calculate expected number of base cumulatives needed
3204
+ const expectedCount = Math.ceil(this.currentWindowMinutes / this.timeStepInMinutes);
3205
+ // Check if enough base cumulatives exist
3206
+ // We assume coverage is complete if count >= expected (simplified check)
3207
+ if (this.baseCumulatives.count >= expectedCount) {
3208
+ this.coveragePercent = 100;
3209
+ this.canCreateNew = true;
3210
+ }
3211
+ else {
3212
+ this.coveragePercent = Math.round((this.baseCumulatives.count / expectedCount) * 100);
3213
+ this.canCreateNew = this.coveragePercent >= 100;
3214
+ }
2968
3215
  }
2969
- setEmail(email) {
2970
- this.email = email;
2971
- this.storage.set('raain-email', this.email);
3216
+ selectPeriod(period) {
3217
+ this.periodSelected.emit({
3218
+ periodBegin: new Date(period.periodBegin),
3219
+ periodEnd: new Date(period.periodEnd),
3220
+ windowInMinutes: period.windowInMinutes,
3221
+ });
2972
3222
  }
2973
- logout(fidjKey, fidjProd) {
3223
+ createNewPeriod() {
2974
3224
  return __awaiter(this, void 0, void 0, function* () {
2975
- // this.storage.remove('raain-email');
2976
- if (!fidjKey) {
2977
- try {
2978
- yield this.fidjService.loginInDemoMode();
2979
- this.readyForSync.next(true);
2980
- }
2981
- catch (err) {
2982
- console.error('initFidj catch pb: ', err);
2983
- }
3225
+ if (!this.canCreateNew || this.creating) {
2984
3226
  return;
2985
3227
  }
2986
- yield this.fidjService.logout(true);
3228
+ this.creating = true;
3229
+ this.creationProgress = 'Starting cumulative computation...';
3230
+ this.errorMessage = '';
3231
+ this.cdr.markForCheck();
2987
3232
  try {
2988
- yield this.fidjService.init(fidjKey, {
2989
- logLevel: LoggerLevelEnum.WARN,
2990
- crypto: false,
2991
- prod: fidjProd,
2992
- useDB: false,
3233
+ // Trigger cumulative creation
3234
+ const result = yield this.profileService.createCumulativePeriod(this.rainId, {
3235
+ periodBegin: this.currentPeriodBegin,
3236
+ periodEnd: this.currentPeriodEnd,
3237
+ provider: this.provider,
3238
+ timeStepInMinutes: this.timeStepInMinutes,
2993
3239
  });
2994
- this.readyForSync.next(true);
3240
+ if (!result) {
3241
+ throw new Error('Failed to trigger cumulative computation');
3242
+ }
3243
+ this.creationProgress = `Jobs queued. Polling for completion...`;
3244
+ this.cdr.markForCheck();
3245
+ // Poll for completion
3246
+ const success = yield this.pollForCompletion();
3247
+ if (success) {
3248
+ this.periodSelected.emit({
3249
+ periodBegin: this.currentPeriodBegin,
3250
+ periodEnd: this.currentPeriodEnd,
3251
+ windowInMinutes: this.currentWindowMinutes,
3252
+ });
3253
+ }
3254
+ else {
3255
+ this.errorMessage = 'Timeout waiting for cumulative computation';
3256
+ }
2995
3257
  }
2996
- catch (err) {
2997
- console.error('initFidj catch pb: ', err);
3258
+ catch (e) {
3259
+ this.errorMessage = `Error: ${e.message || 'Unknown error'}`;
3260
+ console.error('Error creating cumulative:', e);
2998
3261
  }
3262
+ this.creating = false;
3263
+ this.cdr.markForCheck();
2999
3264
  });
3000
3265
  }
3001
- checkError(error) {
3266
+ pollForCompletion() {
3002
3267
  return __awaiter(this, void 0, void 0, function* () {
3003
- if (error.code === 401) {
3004
- console.warn('Pb on auth');
3005
- if (this.router.url.indexOf('login') < 0) {
3006
- try {
3007
- yield this.fidjService.logout();
3008
- }
3009
- catch (ignored) {
3010
- // Ignore logout errors as we're redirecting to logout page anyway
3268
+ const startTime = Date.now();
3269
+ while (Date.now() - startTime < this.POLL_TIMEOUT_MS) {
3270
+ try {
3271
+ const progress = yield this.profileService.getRainProgress(this.rainId);
3272
+ if (progress === 0) {
3273
+ // Queue is empty, check if cumulative exists (admin sees all cumulatives)
3274
+ const response = yield this.profileService.getCumulativePeriods(this.rainId, {
3275
+ provider: this.provider,
3276
+ windowInMinutes: this.currentWindowMinutes,
3277
+ forced: this.isAdmin,
3278
+ });
3279
+ const found = response.periods.find((p) => p.windowInMinutes === this.currentWindowMinutes);
3280
+ if (found) {
3281
+ return true;
3282
+ }
3011
3283
  }
3012
- return this.gotoLout();
3284
+ const elapsed = Math.round((Date.now() - startTime) / 1000);
3285
+ this.creationProgress = `Computing... (${elapsed}s, queue: ${progress})`;
3286
+ this.cdr.markForCheck();
3287
+ yield this.sleep(this.POLL_INTERVAL_MS);
3288
+ }
3289
+ catch (e) {
3290
+ console.warn('Poll error:', e);
3291
+ yield this.sleep(this.POLL_INTERVAL_MS);
3013
3292
  }
3014
3293
  }
3294
+ return false;
3015
3295
  });
3016
3296
  }
3017
- gotoLout() {
3018
- return __awaiter(this, void 0, void 0, function* () {
3019
- try {
3020
- if (this.router.url.indexOf('login') > -1) {
3021
- return;
3022
- }
3023
- yield this.router.navigateByUrl('/logout', {
3024
- skipLocationChange: true,
3025
- replaceUrl: true,
3026
- });
3027
- }
3028
- catch (e) {
3029
- console.error('gotoLout error: ', e);
3030
- }
3297
+ sleep(ms) {
3298
+ return new Promise((resolve) => setTimeout(resolve, ms));
3299
+ }
3300
+ cancel() {
3301
+ this.cancelled.emit();
3302
+ }
3303
+ formatPeriod(period) {
3304
+ const begin = new Date(period.periodBegin);
3305
+ const end = new Date(period.periodEnd);
3306
+ return `${begin.toLocaleString()} → ${end.toLocaleString()}`;
3307
+ }
3308
+ formatWindow(minutes) {
3309
+ if (minutes < 60) {
3310
+ return `${minutes} min`;
3311
+ }
3312
+ const hours = minutes / 60;
3313
+ return hours === 1 ? '1 hour' : `${hours} hours`;
3314
+ }
3315
+ }
3316
+ 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 });
3317
+ 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 });
3318
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: CumulativeSelectorComponent, decorators: [{
3319
+ type: Component,
3320
+ 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"] }]
3321
+ }], ctorParameters: function () { return [{ type: ProfileService }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { rainId: [{
3322
+ type: Input
3323
+ }], currentPeriodBegin: [{
3324
+ type: Input
3325
+ }], currentPeriodEnd: [{
3326
+ type: Input
3327
+ }], provider: [{
3328
+ type: Input
3329
+ }], timeStepInMinutes: [{
3330
+ type: Input
3331
+ }], isAdmin: [{
3332
+ type: Input
3333
+ }], periodSelected: [{
3334
+ type: Output
3335
+ }], cancelled: [{
3336
+ type: Output
3337
+ }] } });
3338
+
3339
+ let TEST_DETECTION = 0;
3340
+ class RaainDetailsComponent {
3341
+ constructor(storage, cdr) {
3342
+ this.storage = storage;
3343
+ this.cdr = cdr;
3344
+ this.availableProviders = [];
3345
+ this.availableTimeSteps = [];
3346
+ this.showPixelMarkers = false;
3347
+ this.qualityIndicators = [];
3348
+ this.qualityIndicatorsLoading = false;
3349
+ this.showCumulativeSelector = false;
3350
+ // Cached computed values (to avoid method calls in templates)
3351
+ this.percentOfComputations = 0;
3352
+ this.percentOfImages = 0;
3353
+ this.truncatedError = '';
3354
+ this.cumulativeDurationInMinutes = 10;
3355
+ this.DateRange = DateRange;
3356
+ // Wrapper function that preserves the async nature of fetchData
3357
+ this.fetchDataWrapper = (focusDate, focusRange) => __awaiter(this, void 0, void 0, function* () {
3358
+ return yield this.fetchData(focusDate, focusRange);
3031
3359
  });
3032
3360
  }
3033
- gotoLogin() {
3034
- return __awaiter(this, void 0, void 0, function* () {
3035
- if (this.router.url.indexOf('login') > -1) {
3036
- return;
3037
- }
3038
- // await this.router.navigateByUrl('/', {skipLocationChange: true});
3039
- yield this.router.navigate(['/login']);
3040
- });
3361
+ static PeriodDisplay(date) {
3362
+ let d = new Date();
3363
+ if (date) {
3364
+ d = new Date(date);
3365
+ const userTimezoneOffset = d.getTimezoneOffset() * 60000;
3366
+ d = new Date(d.getTime() - userTimezoneOffset);
3367
+ }
3368
+ const exampleFormattedDate = '2017-06-01T08:30';
3369
+ return d.toISOString().substring(0, exampleFormattedDate.length);
3041
3370
  }
3042
- isLoggedIn() {
3043
- const loggedIn = this.fidjService.isLoggedIn();
3044
- console.log('isLoggedIn: ', loggedIn);
3045
- return loggedIn;
3371
+ static DateUTC(date) {
3372
+ const hasISOFormat = date.match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/);
3373
+ let d = new Date(date);
3374
+ if (!hasISOFormat) {
3375
+ const userTimezoneOffset = d.getTimezoneOffset() * 60000;
3376
+ d = new Date(d.getTime() - userTimezoneOffset);
3377
+ }
3378
+ return d;
3046
3379
  }
3047
- needsConnectionRefresh() {
3048
- return this.fidjService.needsRefresh();
3380
+ static MapCountToDateValue(c) {
3381
+ return {
3382
+ date: RaainDetailsComponent.DateUTC(c.name),
3383
+ value: Math.min(100, c.x),
3384
+ };
3049
3385
  }
3050
- connectionRefresh() {
3386
+ openQualityModal() {
3387
+ var _a;
3051
3388
  return __awaiter(this, void 0, void 0, function* () {
3052
- yield this.fidjService.refresh();
3389
+ this.showQualityModal = true;
3390
+ this.qualityIndicatorsLoading = true;
3391
+ this.qualityIndicators = [];
3392
+ this.cdr.markForCheck();
3393
+ if ((_a = this.rainNode) === null || _a === void 0 ? void 0 : _a.id) {
3394
+ const result = yield this.profileService.getIndicators(this.rainNode.id);
3395
+ this.qualityIndicators = result.indicators;
3396
+ }
3397
+ this.qualityIndicatorsLoading = false;
3398
+ this.cdr.markForCheck();
3053
3399
  });
3054
3400
  }
3055
- storeAll() {
3056
- return __awaiter(this, void 0, void 0, function* () {
3057
- return this.fidjStorage.storeData(this.fidjService, this.nodeData);
3058
- });
3401
+ closeQualityModal() {
3402
+ this.showQualityModal = false;
3059
3403
  }
3060
- isAdmin() {
3061
- return this.roles.indexOf('admin') > -1;
3404
+ formatDate(dateStr) {
3405
+ const date = new Date(dateStr);
3406
+ return (date.toLocaleDateString() +
3407
+ ' ' +
3408
+ date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }));
3062
3409
  }
3063
- // === Notifications ===
3064
- createNotification(rainId, message) {
3410
+ fetchData(focusDate, focusRange) {
3065
3411
  return __awaiter(this, void 0, void 0, function* () {
3066
- const data = {
3067
- rain: rainId,
3068
- message,
3069
- };
3070
- try {
3071
- const resp = yield this.fidjService.sendOnEndpoint({
3072
- key: 'notifications',
3073
- verb: 'POST',
3074
- defaultKeyUrl: this.defaultUrlForAPI + '/notifications',
3075
- data,
3076
- });
3077
- return new EventNode(resp.data);
3412
+ const values = [];
3413
+ for (let i = 0; i < 10; i++) {
3414
+ values.push({ date: new Date(2020 + i, 0), value: 10 });
3078
3415
  }
3079
- catch (e) {
3080
- yield this.checkError(e);
3416
+ const fakeData = [
3417
+ {
3418
+ label: '% Rainy',
3419
+ style: 'bar',
3420
+ values,
3421
+ },
3422
+ {
3423
+ label: '% Images',
3424
+ style: 'bar',
3425
+ values,
3426
+ },
3427
+ // {
3428
+ // label: '% Quality',
3429
+ // style: 'line',
3430
+ // values,
3431
+ // },
3432
+ ];
3433
+ const range = mapDateRangeToString(focusRange);
3434
+ let data = fakeData;
3435
+ if (!this.rainNode) {
3436
+ return data;
3081
3437
  }
3082
- return null;
3083
- });
3084
- }
3085
- getNotifications(rainId) {
3086
- return __awaiter(this, void 0, void 0, function* () {
3087
- try {
3088
- const params = { rain: rainId };
3089
- const queryString = BuildQueryString(params);
3090
- const resp = yield this.fidjService.sendOnEndpoint({
3091
- key: 'notifications',
3092
- verb: 'GET',
3093
- relativePath: queryString ? '?' + queryString : '',
3094
- defaultKeyUrl: this.defaultUrlForAPI + '/notifications',
3438
+ if (focusRange === DateRange.CENTURY) {
3439
+ // fake
3440
+ }
3441
+ else if (focusRange === DateRange.HOUR) {
3442
+ const hourCounts = yield this.profileService.getCountsHour(this.rainNode.id, {
3443
+ periodBegin: focusDate,
3095
3444
  });
3096
- return resp.data.notifications.map((n) => new EventNode(n));
3445
+ data = [
3446
+ {
3447
+ label: 'Rainy Count',
3448
+ style: 'line',
3449
+ values: hourCounts.rainyCount.map(RaainDetailsComponent.MapCountToDateValue),
3450
+ },
3451
+ {
3452
+ label: '% Images',
3453
+ style: 'bar',
3454
+ values: hourCounts.percentImages.map(RaainDetailsComponent.MapCountToDateValue),
3455
+ },
3456
+ {
3457
+ label: 'Rainy Sum',
3458
+ style: 'line',
3459
+ values: hourCounts.rainySum.map(RaainDetailsComponent.MapCountToDateValue),
3460
+ },
3461
+ ];
3097
3462
  }
3098
- catch (e) {
3099
- yield this.checkError(e);
3463
+ else {
3464
+ const counts = yield this.profileService.getCounts(this.rainNode.id, {
3465
+ range,
3466
+ periodBegin: focusDate,
3467
+ });
3468
+ data = [
3469
+ {
3470
+ label: '% Rainy',
3471
+ style: 'bar',
3472
+ values: counts.percentRainy.map(RaainDetailsComponent.MapCountToDateValue),
3473
+ },
3474
+ {
3475
+ label: '% Images',
3476
+ style: 'bar',
3477
+ values: counts.percentImages.map(RaainDetailsComponent.MapCountToDateValue),
3478
+ },
3479
+ ];
3100
3480
  }
3101
- return null;
3481
+ // console.log(`fetchData DONE ${range}`, data);
3482
+ return data;
3102
3483
  });
3103
3484
  }
3104
- getAllNotifications() {
3485
+ ngOnChanges(changes) {
3486
+ this.change(changes).then((ignored) => { });
3487
+ }
3488
+ ngOnDestroy() {
3489
+ this.cleanAll();
3490
+ }
3491
+ onEnableCountHistoryTab(rain) {
3105
3492
  return __awaiter(this, void 0, void 0, function* () {
3106
- try {
3107
- const resp = yield this.fidjService.sendOnEndpoint({
3108
- key: 'notifications',
3109
- verb: 'GET',
3110
- defaultKeyUrl: this.defaultUrlForAPI + '/notifications',
3111
- });
3112
- return resp.data.notifications.map((n) => new EventNode(n));
3113
- }
3114
- catch (e) {
3115
- yield this.checkError(e);
3493
+ if (!this.toggleHistory) {
3494
+ this.countPoints = [];
3116
3495
  }
3117
- return [];
3118
3496
  });
3119
3497
  }
3120
- // === Teams ===
3121
- getTeams() {
3498
+ onPeriodBeginChange(event) {
3499
+ var _a, _b;
3122
3500
  return __awaiter(this, void 0, void 0, function* () {
3123
- const teams = [];
3124
- const params = {};
3125
- const queryString = BuildQueryString(params);
3126
- try {
3127
- const resp = yield this.fidjService.sendOnEndpoint({
3128
- key: 'teams',
3129
- verb: 'GET',
3130
- relativePath: queryString ? '?' + queryString : '',
3131
- defaultKeyUrl: this.defaultUrlForAPI + '/teams',
3132
- });
3133
- for (const team of resp.data.teams) {
3134
- teams.push(new TeamNode(team));
3135
- }
3136
- }
3137
- catch (e) {
3138
- yield this.checkError(e);
3139
- }
3140
- return teams;
3501
+ const newValue = (_b = (_a = event === null || event === void 0 ? void 0 : event.target) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : this.periodBeginAsString;
3502
+ this.periodBegin = new Date(newValue);
3503
+ this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
3504
+ this.storage.set('raain-periodBegin', this.periodBegin);
3505
+ yield this.onPeriodDurationChange(event);
3141
3506
  });
3142
3507
  }
3143
- getTeam(teamId) {
3508
+ onPeriodEndChange(event) {
3509
+ var _a, _b;
3144
3510
  return __awaiter(this, void 0, void 0, function* () {
3145
- try {
3146
- const resp = yield this.fidjService.sendOnEndpoint({
3147
- key: 'teams',
3148
- verb: 'GET',
3149
- relativePath: teamId,
3150
- defaultKeyUrl: this.defaultUrlForAPI + '/teams',
3151
- });
3152
- return new TeamNode(resp.data);
3153
- }
3154
- catch (e) {
3155
- yield this.checkError(e);
3156
- }
3157
- return null;
3511
+ const newValue = (_b = (_a = event === null || event === void 0 ? void 0 : event.target) === null || _a === void 0 ? void 0 : _a.value) !== null && _b !== void 0 ? _b : this.periodEndAsString;
3512
+ this.periodEnd = new Date(newValue);
3513
+ this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
3514
+ this.storage.set('raain-periodEnd', this.periodEnd);
3515
+ this.periodBegin = new Date(this.periodEnd.getTime() - this.getDurationInHours() * RaainDetailsComponent.HOUR_MS);
3516
+ this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
3517
+ this.storage.set('raain-periodBegin', this.periodBegin);
3518
+ this.updateRefreshManagerPeriod();
3519
+ });
3520
+ }
3521
+ onPeriodDurationChange(_event) {
3522
+ return __awaiter(this, void 0, void 0, function* () {
3523
+ const durationInHours = this.getDurationInHours();
3524
+ this.periodEnd = new Date(this.periodBegin.getTime() + durationInHours * RaainDetailsComponent.HOUR_MS);
3525
+ this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
3526
+ this.storage.set('raain-periodEnd', this.periodEnd);
3527
+ this.storage.set('raain-periodDurationInHours', durationInHours);
3528
+ this.updateRefreshManagerPeriod();
3529
+ this.updateCumulativeDurationInMinutes();
3158
3530
  });
3159
3531
  }
3160
- // === Radars ===
3161
- getRadars(name) {
3532
+ onDateChangeInCount(dateChanged) {
3162
3533
  return __awaiter(this, void 0, void 0, function* () {
3163
- const radars = [];
3164
- const params = {};
3165
- if (name) {
3166
- params.name = name;
3167
- }
3168
- const queryString = BuildQueryString(params);
3169
- try {
3170
- const resp = yield this.fidjService.sendOnEndpoint({
3171
- key: 'radars',
3172
- verb: 'GET',
3173
- relativePath: queryString ? '?' + queryString : '',
3174
- defaultKeyUrl: this.defaultUrlForAPI + '/radars',
3175
- });
3176
- for (const r of resp.data.radars) {
3177
- const radar = new RadarNode(r);
3178
- radars.push(radar);
3179
- }
3534
+ const dateString = dateChanged.toISOString().substring(0, 11) +
3535
+ dateChanged.toLocaleTimeString().substring(0, 5);
3536
+ this.periodDurationAsString = '1';
3537
+ if (this.toggleCumulative) {
3538
+ // Cumulative: select periodEnd
3539
+ this.periodEndAsString = dateString;
3540
+ yield this.onPeriodEndChange(null);
3180
3541
  }
3181
- catch (e) {
3182
- yield this.checkError(e);
3542
+ else {
3543
+ // Granular: select periodBegin
3544
+ this.periodBeginAsString = dateString;
3545
+ yield this.onPeriodBeginChange(null);
3183
3546
  }
3184
- return radars;
3547
+ yield this.refreshManager.refresh(false, this.toggleAdmin);
3185
3548
  });
3186
3549
  }
3187
- getRadar(id) {
3550
+ onDateChangeInMap(dateShown) {
3188
3551
  return __awaiter(this, void 0, void 0, function* () {
3189
- try {
3190
- const resp = yield this.fidjService.sendOnEndpoint({
3191
- key: 'radars',
3192
- verb: 'GET',
3193
- relativePath: id,
3194
- defaultKeyUrl: this.defaultUrlForAPI + '/radars',
3195
- });
3196
- return new RadarNode(resp.data);
3197
- }
3198
- catch (e) {
3199
- yield this.checkError(e);
3200
- }
3201
- return null;
3552
+ this.dateShown = dateShown;
3553
+ yield this.fetchAndUpdateMap();
3554
+ yield this.refreshManager.setReportPeriod(this.dateShown);
3202
3555
  });
3203
3556
  }
3204
- putRadar(radarNode) {
3557
+ onSumChangeInMap(sum) {
3205
3558
  return __awaiter(this, void 0, void 0, function* () {
3206
- const data = {
3207
- name: radarNode.name,
3208
- };
3209
- try {
3210
- const resp = yield this.fidjService.sendOnEndpoint({
3211
- key: 'radars',
3212
- relativePath: radarNode.id,
3213
- verb: 'PUT',
3214
- data,
3215
- defaultKeyUrl: this.defaultUrlForAPI + '/radars/',
3216
- });
3217
- return new RadarNode(resp.data);
3218
- }
3219
- catch (e) {
3220
- yield this.checkError(e);
3221
- }
3559
+ this.sumDetails = sum;
3222
3560
  });
3223
3561
  }
3224
- getLonelyRadars(rains) {
3562
+ onGaugeSelectInMap(mapLatLng) {
3225
3563
  return __awaiter(this, void 0, void 0, function* () {
3226
- try {
3227
- const resp = yield this.fidjService.sendOnEndpoint({
3228
- key: 'radars',
3229
- verb: 'GET',
3230
- defaultKeyUrl: this.defaultUrlForAPI + '/radars',
3231
- });
3232
- const lonelyRadars = [];
3233
- const radars = resp.data.radars;
3234
- radars.forEach((radar) => {
3235
- let found = false;
3236
- rains.forEach((rain) => {
3237
- const rdId = rain.getLink(RadarNode.TYPE).getId();
3238
- if (rdId === radar.id) {
3239
- found = true;
3240
- }
3241
- });
3242
- if (!found) {
3243
- lonelyRadars.push(new RadarNode(radar));
3244
- }
3245
- });
3246
- return lonelyRadars;
3247
- }
3248
- catch (e) {
3249
- yield this.checkError(e);
3564
+ const gaugesFiltered = this.compareManager.gaugesInMap.filter((g) => g.lat === mapLatLng.lat && g.lng === mapLatLng.lng);
3565
+ if (gaugesFiltered.length !== 1) {
3566
+ return;
3250
3567
  }
3251
- return [];
3568
+ const gaugeSelected = gaugesFiltered[0];
3569
+ yield this.refreshGaugeValues({ id: gaugeSelected.id, name: gaugeSelected.name });
3570
+ yield this.compareManager.selectGauge(gaugeSelected.id, 0);
3252
3571
  });
3253
3572
  }
3254
- getRainTimeframe(rainId, begin, end, forced = false, provider = 'Raain', timeStepInMinutes = 10, windowInMinutes = 0) {
3573
+ refreshGaugeValues(gaugeSelected) {
3255
3574
  return __awaiter(this, void 0, void 0, function* () {
3256
- try {
3257
- const params = {
3258
- format: 'timeframeCumulative',
3259
- provider,
3260
- timeStepInMinutes: String(timeStepInMinutes),
3261
- begin: begin === null || begin === void 0 ? void 0 : begin.toISOString(),
3262
- end: end === null || end === void 0 ? void 0 : end.toISOString(),
3575
+ const gaugeValueShowBegin = new Date(this.periodBegin.getTime() - RaainDetailsComponent.DAY_MS);
3576
+ const gaugeValueShowEnd = new Date(this.periodEnd.getTime() + RaainDetailsComponent.DAY_MS);
3577
+ gaugeValueShowBegin.setHours(0, 0);
3578
+ gaugeValueShowEnd.setHours(23, 59);
3579
+ const gaugeMeasures = yield this.profileService.getGaugeMeasures(gaugeSelected.id, gaugeValueShowBegin, gaugeValueShowEnd);
3580
+ const gaugeValues = gaugeMeasures.map((gm) => {
3581
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
3582
+ const cartesianMeasureValue = new CartesianMeasureValue(gm.values[0]);
3583
+ return {
3584
+ date: gm.date,
3585
+ value: cartesianMeasureValue.getCartesianValues()[0].value,
3263
3586
  };
3264
- if (forced) {
3265
- params.forced = 'true';
3266
- }
3267
- params.windowInMinutes = String(windowInMinutes);
3268
- const queryString = BuildQueryString(params);
3269
- const resp = yield this.fidjService.sendOnEndpoint({
3270
- key: 'rains',
3271
- verb: 'GET',
3272
- relativePath: rainId + (queryString ? '?' + queryString : ''),
3273
- defaultKeyUrl: this.defaultUrlForAPI + '/rains',
3274
- });
3275
- const rainNode = new RainNode(resp.data.timeframeCumulative);
3276
- rainNode.name += '.radar.timeframeCumulative';
3277
- return rainNode;
3278
- }
3279
- catch (e) {
3280
- yield this.checkError(e);
3281
- }
3282
- return null;
3587
+ });
3588
+ this.gaugeSelectedPoints = [
3589
+ {
3590
+ label: gaugeSelected.name,
3591
+ style: 'bar',
3592
+ values: gaugeValues,
3593
+ },
3594
+ ];
3595
+ this.cdr.markForCheck();
3283
3596
  });
3284
3597
  }
3285
- // === Rains ===
3286
- getRains(name) {
3598
+ onGaugeSelectInCompare(e) {
3287
3599
  return __awaiter(this, void 0, void 0, function* () {
3288
- const rains = [];
3289
- const params = {};
3290
- if (name) {
3291
- params.name = name;
3292
- }
3293
- const queryString = BuildQueryString(params);
3294
- try {
3295
- const resp = yield this.fidjService.sendOnEndpoint({
3296
- key: 'rains',
3297
- verb: 'GET',
3298
- relativePath: queryString ? '?' + queryString : '',
3299
- defaultKeyUrl: this.defaultUrlForAPI + '/rains',
3300
- });
3301
- for (const rain of resp.data.rains) {
3302
- rains.push(new RainNode(rain));
3303
- }
3304
- }
3305
- catch (e) {
3306
- yield this.checkError(e);
3307
- }
3308
- return rains;
3600
+ yield this.refreshGaugeValues({ id: e.point.id, name: e.point.name });
3601
+ yield this.compareManager.selectGauge(e.point.id, e.compareIndex);
3309
3602
  });
3310
3603
  }
3311
- getRain(id) {
3604
+ onToggleMap($event) {
3312
3605
  return __awaiter(this, void 0, void 0, function* () {
3313
- try {
3314
- const resp = yield this.fidjService.sendOnEndpoint({
3315
- key: 'rains',
3316
- relativePath: id,
3317
- verb: 'GET',
3318
- defaultKeyUrl: this.defaultUrlForAPI + '/rains/',
3319
- });
3320
- return new RainNode(resp.data);
3606
+ // if (this.toggleMap) {
3607
+ // await this.refreshMap();
3608
+ // }
3609
+ });
3610
+ }
3611
+ onTogglePixelMarkers() {
3612
+ // Toggle is bound to showPixelMarkers, raain-map handles marker display
3613
+ }
3614
+ onCumulativeToggleClick($event) {
3615
+ var _a;
3616
+ return __awaiter(this, void 0, void 0, function* () {
3617
+ console.log('[CumulativeToggle] click event', {
3618
+ currentValue: this.toggleCumulative,
3619
+ eventType: $event.type,
3620
+ target: (_a = $event.target) === null || _a === void 0 ? void 0 : _a.tagName,
3621
+ });
3622
+ $event.preventDefault();
3623
+ $event.stopPropagation();
3624
+ if (!this.toggleCumulative) {
3625
+ console.log('[CumulativeToggle] showing popup, toggle stays OFF');
3626
+ // Currently OFF, user wants to enable - show selector popup
3627
+ this.showCumulativeSelector = true;
3628
+ // Force reset the toggle visual state after ion-toggle's internal handler
3629
+ setTimeout(() => {
3630
+ if (this.cumulativeToggleRef) {
3631
+ this.cumulativeToggleRef.checked = false;
3632
+ console.log('[CumulativeToggle] force reset toggle to OFF');
3633
+ }
3634
+ }, 0);
3635
+ this.cdr.markForCheck();
3321
3636
  }
3322
- catch (e) {
3323
- yield this.checkError(e);
3637
+ else {
3638
+ console.log('[CumulativeToggle] turning OFF');
3639
+ // Currently ON, user wants to disable - direct toggle off
3640
+ this.toggleCumulative = false;
3641
+ this.storage.set('raain-toggleCumulative', false);
3642
+ this.dateShown = this.getDateBasedOnCumulativeMode(this.timeframeDates);
3643
+ this.updateCumulativeDurationInMinutes();
3644
+ if (this.toggleMap) {
3645
+ this.updateRefreshManagerPeriod();
3646
+ yield this.refreshManager.refresh(false, this.toggleAdmin);
3647
+ }
3648
+ this.cdr.markForCheck();
3324
3649
  }
3325
- return null;
3650
+ console.log('[CumulativeToggle] after handler, toggleCumulative =', this.toggleCumulative);
3326
3651
  });
3327
3652
  }
3328
- // === Count
3329
- getCounts(rainId, options) {
3653
+ onCumulativePeriodSelected(selection) {
3330
3654
  return __awaiter(this, void 0, void 0, function* () {
3331
- try {
3332
- const params = {
3333
- range: options.range,
3334
- begin: options.periodBegin.toISOString(),
3335
- };
3336
- const queryString = BuildQueryString(params);
3337
- const resp = yield this.fidjService.sendOnEndpoint({
3338
- key: 'rains',
3339
- relativePath: rainId + '/counts' + (queryString ? '?' + queryString : ''),
3340
- verb: 'GET',
3341
- defaultKeyUrl: this.defaultUrlForAPI + '/rains/',
3342
- });
3343
- const counts = resp.data.counts.result;
3344
- const percentImages = [], percentRainy = [], percentQ = [];
3345
- counts.forEach((c) => {
3346
- var _a, _b, _c;
3347
- const label = this.setDateComponents(options.periodBegin, c);
3348
- percentImages.push(new XYType((_a = c.percentImages) !== null && _a !== void 0 ? _a : 0, NaN, NaN, label));
3349
- percentRainy.push(new XYType((_b = c.percentRainy) !== null && _b !== void 0 ? _b : 0, NaN, NaN, label));
3350
- percentQ.push(new XYType((_c = c.percentQ) !== null && _c !== void 0 ? _c : 0, NaN, NaN, label));
3351
- });
3352
- return {
3353
- percentImages,
3354
- percentRainy,
3355
- percentQ,
3356
- queueRunning: resp.data.queueRunning,
3357
- };
3358
- }
3359
- catch (e) {
3360
- yield this.checkError(e);
3655
+ this.showCumulativeSelector = false;
3656
+ // Update period to match selection
3657
+ this.periodBegin = selection.periodBegin;
3658
+ this.periodEnd = selection.periodEnd;
3659
+ this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
3660
+ this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
3661
+ // Calculate duration in hours
3662
+ const durationMs = this.periodEnd.getTime() - this.periodBegin.getTime();
3663
+ const durationHours = durationMs / RaainDetailsComponent.HOUR_MS;
3664
+ this.periodDurationAsString = String(durationHours);
3665
+ // Store cumulative period
3666
+ this.storage.set('raain-periodBegin', this.periodBegin);
3667
+ this.storage.set('raain-periodEnd', this.periodEnd);
3668
+ this.storage.set('raain-periodDurationInHours', durationHours);
3669
+ // Enable cumulative mode
3670
+ this.toggleCumulative = true;
3671
+ this.storage.set('raain-toggleCumulative', true);
3672
+ this.dateShown = this.getDateBasedOnCumulativeMode(this.timeframeDates);
3673
+ this.updateCumulativeDurationInMinutes();
3674
+ // Refresh map
3675
+ if (this.toggleMap) {
3676
+ this.updateRefreshManagerPeriod();
3677
+ yield this.refreshManager.refresh(false, this.toggleAdmin);
3361
3678
  }
3362
- return null;
3679
+ this.cdr.markForCheck();
3363
3680
  });
3364
3681
  }
3365
- getCountsHour(rainId, options) {
3682
+ onCumulativeSelectorCancelled() {
3683
+ console.log('[CumulativeToggle] cancelled, toggleCumulative =', this.toggleCumulative);
3684
+ this.showCumulativeSelector = false;
3685
+ this.cdr.markForCheck();
3686
+ }
3687
+ updateCumulativeDurationInMinutes() {
3688
+ if (this.toggleCumulative) {
3689
+ // Cumulative mode: use period duration
3690
+ this.cumulativeDurationInMinutes = parseFloat(this.periodDurationAsString) * 60;
3691
+ }
3692
+ else {
3693
+ // Granular mode: use selectedTimeStep
3694
+ this.cumulativeDurationInMinutes = this.selectedTimeStep || 10;
3695
+ }
3696
+ }
3697
+ onProviderChanged($event) {
3698
+ var _a;
3366
3699
  return __awaiter(this, void 0, void 0, function* () {
3367
- try {
3368
- const params = {
3369
- range: 'hour',
3370
- begin: options.periodBegin.toISOString(),
3371
- };
3372
- const queryString = BuildQueryString(params);
3373
- const resp = yield this.fidjService.sendOnEndpoint({
3374
- key: 'rains',
3375
- relativePath: rainId + '/counts' + (queryString ? '?' + queryString : ''),
3376
- verb: 'GET',
3377
- defaultKeyUrl: this.defaultUrlForAPI + '/rains/',
3378
- });
3379
- const counts = resp.data.counts.result;
3380
- const percentImages = [], rainySum = [], rainyCount = [];
3381
- counts.forEach((c) => {
3382
- var _a, _b, _c;
3383
- const label = this.setDateComponents(options.periodBegin, c);
3384
- percentImages.push(new XYType((_a = c.percentImages) !== null && _a !== void 0 ? _a : 0, NaN, NaN, label));
3385
- rainyCount.push(new XYType((_b = c.rainyCount) !== null && _b !== void 0 ? _b : 0, NaN, NaN, label));
3386
- rainySum.push(new XYType((_c = c.rainySum) !== null && _c !== void 0 ? _c : 0, NaN, NaN, label));
3387
- });
3388
- return {
3389
- percentImages,
3390
- rainyCount,
3391
- rainySum,
3392
- queueRunning: resp.data.queueRunning,
3393
- };
3394
- }
3395
- catch (e) {
3396
- yield this.checkError(e);
3397
- }
3398
- return null;
3700
+ this.selectedProvider = (_a = $event === null || $event === void 0 ? void 0 : $event.detail) === null || _a === void 0 ? void 0 : _a.value;
3701
+ this.storage.set('raain-selectedProvider', this.selectedProvider);
3702
+ yield this.applyRefreshManagerSettings();
3399
3703
  });
3400
3704
  }
3401
- // === Computing ===
3402
- getRainComputationCumulativeCartesianMapById(rainId, rainComputationCumulativeId) {
3705
+ onTimeStepChanged($event) {
3706
+ var _a;
3403
3707
  return __awaiter(this, void 0, void 0, function* () {
3404
- const params = { format: 'cartesian-map' };
3405
- const queryString = BuildQueryString(params);
3406
- try {
3407
- const response = yield this.fidjService.sendOnEndpoint({
3408
- key: 'rains',
3409
- verb: 'GET',
3410
- relativePath: `${rainId}/cumulatives/${rainComputationCumulativeId}?${queryString}`,
3411
- defaultKeyUrl: this.defaultUrlForAPI + '/rains',
3412
- });
3413
- if (!response.data['cartesian-map']) {
3414
- return null;
3415
- }
3416
- const rainComputationMap = new RainComputationMap(response.data['cartesian-map']);
3417
- rainComputationMap.name = rainId + '.rain.cartesian-map';
3418
- return rainComputationMap;
3419
- }
3420
- catch (e) {
3421
- yield this.checkError(e);
3422
- }
3423
- return null;
3708
+ this.selectedTimeStep = (_a = $event === null || $event === void 0 ? void 0 : $event.detail) === null || _a === void 0 ? void 0 : _a.value;
3709
+ this.storage.set('raain-selectedTimeStep', this.selectedTimeStep);
3710
+ this.updateCumulativeDurationInMinutes();
3711
+ yield this.applyRefreshManagerSettings();
3424
3712
  });
3425
3713
  }
3426
- getRainComputationCumulativeCumulativesMapById(rainId, rainComputationCumulativeId, cumulativeHours) {
3714
+ loadProviders() {
3715
+ var _a;
3427
3716
  return __awaiter(this, void 0, void 0, function* () {
3428
- const queryPath = `${rainId}/cumulatives/${rainComputationCumulativeId}/cumulative/${cumulativeHours}`;
3717
+ if (!((_a = this.rainNode) === null || _a === void 0 ? void 0 : _a.id)) {
3718
+ return;
3719
+ }
3429
3720
  try {
3430
- const response = yield this.fidjService.sendOnEndpoint({
3431
- key: 'rains',
3432
- verb: 'GET',
3433
- relativePath: queryPath,
3434
- defaultKeyUrl: this.defaultUrlForAPI + '/rains',
3435
- });
3436
- if (!response.data['cumulative']) {
3437
- return null;
3438
- }
3439
- const rainComputationMap = new RainComputationMap(response.data['cumulative']);
3440
- rainComputationMap.name = rainId + '.rain.cumulative-cumulative';
3441
- return rainComputationMap;
3721
+ const result = yield this.profileService.getProviders(this.rainNode.id);
3722
+ this.availableProviders = result.providers;
3723
+ this.availableTimeSteps = result.timeStepInMinutes;
3724
+ // Load saved selections or use defaults
3725
+ this.selectedProvider =
3726
+ this.storage.get('raain-selectedProvider') ||
3727
+ (this.availableProviders.length > 0 ? this.availableProviders[0] : 'Raain');
3728
+ this.selectedTimeStep =
3729
+ this.storage.get('raain-selectedTimeStep') ||
3730
+ (this.availableTimeSteps.length > 0 ? this.availableTimeSteps[0] : 10);
3442
3731
  }
3443
3732
  catch (e) {
3444
- yield this.checkError(e);
3733
+ // Set fallback values
3734
+ this.availableProviders = ['Raain'];
3735
+ this.availableTimeSteps = [5, 10, 15, 30, 60];
3736
+ this.selectedProvider = 'Raain';
3737
+ this.selectedTimeStep = 10;
3445
3738
  }
3446
- return null;
3739
+ // Set provider and timeStep on refreshManager
3740
+ if (this.refreshManager) {
3741
+ this.refreshManager.provider = this.selectedProvider;
3742
+ this.refreshManager.timeStepInMinutes = this.selectedTimeStep;
3743
+ }
3744
+ this.cdr.markForCheck();
3447
3745
  });
3448
3746
  }
3449
- getRainCumulativeCompareByDate(rainNode, rainComputationCumulativeId, date) {
3747
+ onChangeDetectionTest(rainNode) {
3748
+ console.log(TEST_DETECTION++, 'onChangeDetectionTest');
3749
+ return '';
3750
+ }
3751
+ updateTruncatedError() {
3752
+ var _a;
3753
+ const error = ((_a = this.refreshManager) === null || _a === void 0 ? void 0 : _a.lastError) || '';
3754
+ if (error.length <= 80) {
3755
+ this.truncatedError = error;
3756
+ return;
3757
+ }
3758
+ this.truncatedError = error.substring(0, 80) + '...';
3759
+ }
3760
+ updateCachedValues() {
3761
+ this.updatePercentOfImages();
3762
+ this.updatePercentOfComputations();
3763
+ this.updateTruncatedError();
3764
+ this.updateCumulativeDurationInMinutes();
3765
+ }
3766
+ refreshMap() {
3450
3767
  return __awaiter(this, void 0, void 0, function* () {
3451
- const params = { date: date.toISOString() };
3452
- const queryString = BuildQueryString(params);
3453
- try {
3454
- const response = yield this.fidjService.sendOnEndpoint({
3455
- key: 'rains',
3456
- verb: 'GET',
3457
- relativePath: `${rainNode.id}/cumulatives/${rainComputationCumulativeId}/compares?${queryString}`,
3458
- defaultKeyUrl: this.defaultUrlForAPI + '/rains',
3459
- });
3460
- const qualityJson = response.data.qualities[0];
3461
- const rainComputationQuality = new RainComputationQuality(qualityJson);
3462
- rainComputationQuality.qualitySpeedMatrixContainer =
3463
- SpeedMatrixContainer.CreateFromJson(rainComputationQuality.qualitySpeedMatrixContainer);
3464
- return rainComputationQuality;
3465
- }
3466
- catch (e) {
3467
- yield this.checkError(e);
3468
- }
3469
- return null;
3768
+ this.gaugeSelectedPoints = [];
3769
+ this.dateShown = this.getDateBasedOnCumulativeMode();
3770
+ this.borders = [];
3771
+ this.compareManager.cleanAll();
3772
+ yield this.compareManager.setGaugesInMap();
3773
+ yield this.refreshManager.refresh(false, this.toggleAdmin);
3774
+ this.cdr.markForCheck();
3470
3775
  });
3471
3776
  }
3472
- getRainCumulativeCumulativesCompareByDate(rainNode, rainComputationCumulativeId, date, cumulativeHours) {
3777
+ setPeriodOfNow() {
3473
3778
  return __awaiter(this, void 0, void 0, function* () {
3474
- const params = {
3475
- date: date.toISOString(),
3476
- cumulativeHours,
3477
- };
3478
- const queryString = BuildQueryString(params);
3479
- try {
3480
- const response = yield this.fidjService.sendOnEndpoint({
3481
- key: 'rains',
3482
- verb: 'GET',
3483
- relativePath: `${rainNode.id}/cumulatives/${rainComputationCumulativeId}/compares?${queryString}`,
3484
- defaultKeyUrl: this.defaultUrlForAPI + '/rains',
3485
- });
3486
- const qualityJson = response.data.qualities[0];
3487
- const rainComputationQuality = new RainComputationQuality(qualityJson);
3488
- rainComputationQuality.qualitySpeedMatrixContainer =
3489
- SpeedMatrixContainer.CreateFromJson(rainComputationQuality.qualitySpeedMatrixContainer);
3490
- return rainComputationQuality;
3491
- }
3492
- catch (e) {
3493
- yield this.checkError(e);
3779
+ const last30mn = new Date();
3780
+ last30mn.setMinutes(last30mn.getMinutes() - 30);
3781
+ this.periodBeginAsString =
3782
+ last30mn.toISOString().substring(0, 11) + last30mn.toLocaleTimeString().substring(0, 5);
3783
+ this.periodDurationAsString = '1';
3784
+ yield this.onPeriodBeginChange(null);
3785
+ yield this.refreshManager.refresh(false, this.toggleAdmin);
3786
+ });
3787
+ }
3788
+ updatePercentOfImages() {
3789
+ var _a, _b;
3790
+ if (!((_b = (_a = this.countsPeriod) === null || _a === void 0 ? void 0 : _a.percentImages) === null || _b === void 0 ? void 0 : _b.length)) {
3791
+ this.percentOfImages = 0;
3792
+ return;
3793
+ }
3794
+ const duringPeriod = this.countsPeriod.percentImages.filter((a) => this.periodBegin.getTime() <= new Date(a.name).getTime() &&
3795
+ new Date(a.name).getTime() <= this.periodEnd.getTime());
3796
+ const summed = duringPeriod.reduce((a, b) => { var _a; return a + ((_a = b.x) !== null && _a !== void 0 ? _a : 0); }, 0);
3797
+ this.percentOfImages = Math.round(summed / duringPeriod.length);
3798
+ }
3799
+ updatePercentOfComputations() {
3800
+ var _a;
3801
+ const timeline = (_a = this.refreshManager) === null || _a === void 0 ? void 0 : _a.getTimelineFrameSet();
3802
+ if (!(timeline === null || timeline === void 0 ? void 0 : timeline.length)) {
3803
+ this.percentOfComputations = 0;
3804
+ return;
3805
+ }
3806
+ const timelineWithComputation = timeline.filter((a) => !!a.rainComputationCumulativeId || !!a.rainComputationId);
3807
+ const ratio = timelineWithComputation.length / timeline.length;
3808
+ this.percentOfComputations = Math.round(ratio * 100);
3809
+ }
3810
+ getDateBasedOnCumulativeMode(fallbackDates) {
3811
+ if ((fallbackDates === null || fallbackDates === void 0 ? void 0 : fallbackDates.length) > 0) {
3812
+ const dateExists = fallbackDates.some((d) => { var _a; return d.getTime() === ((_a = this.dateShown) === null || _a === void 0 ? void 0 : _a.getTime()); });
3813
+ if (!dateExists) {
3814
+ return this.toggleCumulative
3815
+ ? fallbackDates[fallbackDates.length - 1]
3816
+ : fallbackDates[0];
3494
3817
  }
3495
- return null;
3818
+ }
3819
+ return this.toggleCumulative ? this.periodEnd : this.periodBegin;
3820
+ }
3821
+ getCumulativeHours() {
3822
+ return this.toggleCumulative ? parseFloat(this.periodDurationAsString) : 0;
3823
+ }
3824
+ getDurationInHours() {
3825
+ return parseFloat(this.periodDurationAsString);
3826
+ }
3827
+ updateRefreshManagerPeriod() {
3828
+ this.refreshManager.cumulative = this.toggleCumulative;
3829
+ // Align dates to 5-minute boundaries (floor) for consistency with raain-ground
3830
+ const alignTo5minFloor = (date) => {
3831
+ const minutes = date.getMinutes();
3832
+ const alignedMinutes = Math.floor(minutes / 5) * 5;
3833
+ return new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), alignedMinutes, 0, 0);
3834
+ };
3835
+ this.refreshManager.period = {
3836
+ begin: alignTo5minFloor(this.periodBegin),
3837
+ end: alignTo5minFloor(this.periodEnd),
3838
+ };
3839
+ }
3840
+ fetchAndUpdateMap() {
3841
+ return __awaiter(this, void 0, void 0, function* () {
3842
+ yield this.refreshManager.fetch(this.dateShown, this.toggleGaugeMeasures, this.getCumulativeHours());
3843
+ this.currentTimeframeTarget = this.refreshManager.getTimelineSelectedFrameSet();
3844
+ this.cdr.markForCheck();
3496
3845
  });
3497
3846
  }
3498
- getRainProgress(rainId) {
3847
+ applyRefreshManagerSettings() {
3499
3848
  return __awaiter(this, void 0, void 0, function* () {
3500
- try {
3501
- const queryPath = '' + rainId + '/progress';
3502
- const response = yield this.fidjService.sendOnEndpoint({
3503
- key: 'rains',
3504
- verb: 'GET',
3505
- relativePath: queryPath,
3506
- defaultKeyUrl: this.defaultUrlForAPI + '/rains',
3507
- });
3508
- // return response.data.inProgress;
3509
- return response.data.inQueue;
3510
- }
3511
- catch (e) {
3512
- yield this.checkError(e);
3849
+ if (!this.refreshManager) {
3850
+ return;
3513
3851
  }
3514
- return 0;
3852
+ this.refreshManager.provider = this.selectedProvider;
3853
+ this.refreshManager.timeStepInMinutes = this.selectedTimeStep;
3854
+ yield this.refreshManager.refresh(false, this.toggleAdmin);
3515
3855
  });
3516
3856
  }
3517
- getIndicators(rainId) {
3857
+ cleanAll() {
3858
+ var _a, _b;
3859
+ this.borders = [];
3860
+ this.isAdmin = false;
3861
+ this.timeframeContainers = new TimeframeContainers([]);
3862
+ this.currentTimeframeTarget = null;
3863
+ this.timeframeDates = [];
3864
+ this.countPoints = [];
3865
+ this.countsPeriod = { progress: 0, queueRunning: 0, percentImages: [] };
3866
+ this.gaugeSelectedPoints = [];
3867
+ this.toggleHistory = false;
3868
+ this.toggleMap = true;
3869
+ this.toggleCompare = false;
3870
+ this.toggleGaugeMeasures = false;
3871
+ this.toggleRemoveCompareDuplicate = true;
3872
+ this.toggleCumulative = this.storage.get('raain-toggleCumulative');
3873
+ this.periodBegin = new Date(this.storage.get('raain-periodBegin'));
3874
+ this.periodEnd = new Date(this.storage.get('raain-periodEnd'));
3875
+ this.periodBeginAsString = RaainDetailsComponent.PeriodDisplay(this.periodBegin);
3876
+ this.periodEndAsString = RaainDetailsComponent.PeriodDisplay(this.periodEnd);
3877
+ const durationMs = this.periodEnd.getTime() - this.periodBegin.getTime();
3878
+ this.periodDurationAsString = '' + durationMs / RaainDetailsComponent.HOUR_MS;
3879
+ this.dateShown = this.getDateBasedOnCumulativeMode();
3880
+ this.refreshInProgress = false;
3881
+ this.showFullError = false;
3882
+ this.showQualityModal = false;
3883
+ this.qualityIndicators = [];
3884
+ this.qualityIndicatorsLoading = false;
3885
+ (_a = this.compareManager) === null || _a === void 0 ? void 0 : _a.cleanAll();
3886
+ (_b = this.refreshManager) === null || _b === void 0 ? void 0 : _b.cleanAll();
3887
+ }
3888
+ init() {
3518
3889
  return __awaiter(this, void 0, void 0, function* () {
3519
- try {
3520
- const params = {
3521
- cumulative: 'true',
3522
- };
3523
- const queryString = BuildQueryString(params);
3524
- const response = yield this.fidjService.sendOnEndpoint({
3525
- key: 'rains',
3526
- verb: 'GET',
3527
- relativePath: rainId + '/indicators' + (queryString ? '?' + queryString : ''),
3528
- defaultKeyUrl: this.defaultUrlForAPI + '/rains',
3529
- });
3530
- return response.data;
3531
- }
3532
- catch (e) {
3533
- yield this.checkError(e);
3534
- }
3535
- return { indicators: [] };
3890
+ this.cleanAll();
3891
+ this.updateCachedValues();
3892
+ yield this.initRain();
3893
+ this.cdr.markForCheck();
3536
3894
  });
3537
3895
  }
3538
- // === Gauges ===
3539
- getGauge(gaugeId) {
3896
+ initRain() {
3540
3897
  return __awaiter(this, void 0, void 0, function* () {
3541
- try {
3542
- const resp = yield this.fidjService.sendOnEndpoint({
3543
- key: 'gauges',
3544
- verb: 'GET',
3545
- relativePath: gaugeId,
3546
- defaultKeyUrl: this.defaultUrlForAPI + '/gauges',
3547
- });
3548
- return new GaugeNode(resp.data);
3898
+ if (!this.rainNode) {
3899
+ return;
3549
3900
  }
3550
- catch (e) {
3551
- yield this.checkError(e);
3901
+ this.isAdmin = this.profileService.isAdmin();
3902
+ this.refreshManager.rainNode = this.rainNode;
3903
+ this.compareManager.rainNode = this.rainNode;
3904
+ // Load providers and set on refreshManager
3905
+ yield this.loadProviders();
3906
+ this.refreshManager.setMethods(this.onRefreshInProgress.bind(this), this.onRefreshDone.bind(this), this.onFetchDone.bind(this));
3907
+ const center = this.rainNode.getCenter();
3908
+ this.coordinates = new MapLatLng(center.lat, center.lng);
3909
+ this.teamNode = yield this.profileService.getTeam(this.rainNode.getLink(TeamNode.TYPE).getId());
3910
+ // Load all gauges linked to the rainNode on map
3911
+ yield this.compareManager.setGaugesInMap();
3912
+ if (this.periodBegin && this.periodEnd) {
3913
+ this.updateRefreshManagerPeriod();
3914
+ yield this.refreshManager.refresh(false, this.toggleAdmin);
3552
3915
  }
3553
3916
  });
3554
3917
  }
3555
- getGauges(rainId, aroundLatLng, pageCount = 1) {
3918
+ onRefreshInProgress(countPeriods, timeframeDates) {
3556
3919
  return __awaiter(this, void 0, void 0, function* () {
3557
- const baseParams = {
3558
- aroundLatLng: `${aroundLatLng.lat},${aroundLatLng.lng}`,
3559
- rainId,
3560
- };
3561
- if (this.asTeamId) {
3562
- baseParams.teamId = this.asTeamId;
3563
- }
3564
- const gauges = [];
3565
- try {
3566
- for (let count = 1; count <= pageCount; count++) {
3567
- const params = Object.assign(Object.assign({}, baseParams), { page: count });
3568
- const queryString = BuildQueryString(params);
3569
- const resp = yield this.fidjService.sendOnEndpoint({
3570
- key: 'gauges',
3571
- verb: 'GET',
3572
- relativePath: queryString ? '?' + queryString : '',
3573
- defaultKeyUrl: this.defaultUrlForAPI + '/gauges',
3574
- });
3575
- for (const gauge of resp.data.gauges) {
3576
- gauges.push(new GaugeNodeFilter(gauge));
3577
- }
3920
+ this.refreshInProgress = true;
3921
+ this.countsPeriod = countPeriods;
3922
+ this.timeframeDates = timeframeDates;
3923
+ this.updateCachedValues();
3924
+ this.cdr.markForCheck();
3925
+ });
3926
+ }
3927
+ onRefreshDone(timeframeDates) {
3928
+ return __awaiter(this, void 0, void 0, function* () {
3929
+ this.timeframeDates = timeframeDates;
3930
+ this.refreshInProgress = false;
3931
+ this.updateCachedValues();
3932
+ this.cdr.markForCheck();
3933
+ if (this.toggleMap && timeframeDates.length > 0) {
3934
+ this.dateShown = this.getDateBasedOnCumulativeMode(timeframeDates);
3935
+ if (this.dateShown) {
3936
+ yield this.fetchAndUpdateMap();
3578
3937
  }
3579
3938
  }
3580
- catch (e) {
3581
- yield this.checkError(e);
3582
- }
3583
- return gauges;
3584
3939
  });
3585
3940
  }
3586
- getGaugeMeasures(gaugeId, begin, end) {
3941
+ onFetchDone(timeframeContainers) {
3587
3942
  return __awaiter(this, void 0, void 0, function* () {
3588
- const params = {
3589
- begin: begin.toISOString(),
3590
- end: end.toISOString(),
3591
- };
3592
- const queryString = BuildQueryString(params);
3593
- const resp = yield this.fidjService.sendOnEndpoint({
3594
- key: 'gauges',
3595
- verb: 'GET',
3596
- relativePath: gaugeId + '/measures' + (queryString ? '?' + queryString : ''),
3597
- defaultKeyUrl: this.defaultUrlForAPI + '/gauges',
3598
- });
3599
- const gaugeMeasures = [];
3600
- for (const gaugeMeasure of resp.data.gaugeMeasures) {
3601
- gaugeMeasures.push(new GaugeMeasure(gaugeMeasure));
3943
+ if (timeframeContainers) {
3944
+ this.timeframeContainers = timeframeContainers;
3602
3945
  }
3603
- return gaugeMeasures;
3946
+ this.cdr.markForCheck();
3604
3947
  });
3605
3948
  }
3606
- getProviders(rainId) {
3949
+ change(_changes) {
3607
3950
  return __awaiter(this, void 0, void 0, function* () {
3608
- try {
3609
- const response = yield this.fidjService.sendOnEndpoint({
3610
- verb: 'GET',
3611
- key: 'rains',
3612
- relativePath: rainId + '/providers',
3613
- defaultKeyUrl: this.defaultUrlForAPI + '/rains',
3614
- });
3615
- return {
3616
- providers: response.data.providers || [],
3617
- timeStepInMinutes: response.data.timeStepInMinutes || [5, 10, 15, 30, 60],
3618
- };
3951
+ yield this.init();
3952
+ });
3953
+ }
3954
+ }
3955
+ RaainDetailsComponent.HOUR_MS = 60 * 60000;
3956
+ RaainDetailsComponent.DAY_MS = 24 * 60 * 60 * 1000;
3957
+ 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 });
3958
+ 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 });
3959
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: RaainDetailsComponent, decorators: [{
3960
+ type: Component,
3961
+ 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"] }]
3962
+ }], ctorParameters: function () { return [{ type: Storage }, { type: i0.ChangeDetectorRef }]; }, propDecorators: { toggleAdmin: [{
3963
+ type: Input
3964
+ }], rainNode: [{
3965
+ type: Input
3966
+ }], compareManager: [{
3967
+ type: Input
3968
+ }], refreshManager: [{
3969
+ type: Input
3970
+ }], profileService: [{
3971
+ type: Input
3972
+ }], radarService: [{
3973
+ type: Input
3974
+ }], cumulativeToggleRef: [{
3975
+ type: ViewChild,
3976
+ args: ['cumulativeToggle']
3977
+ }] } });
3978
+
3979
+ class Cache {
3980
+ constructor() {
3981
+ this._cache = {};
3982
+ }
3983
+ getValue(key, asyncGetter) {
3984
+ return __awaiter(this, void 0, void 0, function* () {
3985
+ if (!Object.prototype.hasOwnProperty.call(this._cache, key)) {
3986
+ console.log('cache not done: ', key);
3987
+ this.putValue(key, yield asyncGetter());
3619
3988
  }
3620
- catch (e) {
3621
- console.error('getProviders error:', e);
3622
- return {
3623
- providers: [],
3624
- timeStepInMinutes: [5, 10, 15, 30, 60],
3625
- };
3989
+ else {
3990
+ console.log('cache done: ', key);
3626
3991
  }
3992
+ return this._cache[key];
3627
3993
  });
3628
3994
  }
3629
- setRoles(roles) {
3630
- this.roles = roles;
3631
- }
3632
- setDateComponents(date, c) {
3633
- const dateToShow = new Date(date);
3634
- if (c.year !== undefined) {
3635
- dateToShow.setUTCFullYear(c.year);
3636
- }
3637
- if (c.month !== undefined) {
3638
- dateToShow.setUTCMonth(c.month - 1);
3639
- }
3640
- if (c.day !== undefined) {
3641
- dateToShow.setUTCDate(c.day);
3642
- }
3643
- if (c.hour !== undefined) {
3644
- dateToShow.setUTCHours(c.hour);
3645
- }
3646
- if (c.minute !== undefined) {
3647
- dateToShow.setUTCMinutes(c.minute);
3995
+ putValue(key, value) {
3996
+ this._cache[key] = value;
3997
+ const length = Object.getOwnPropertyNames(this._cache).length;
3998
+ if (length > 30) {
3999
+ console.warn('Pb on cache size exceed ? do a restart ?', length);
3648
4000
  }
3649
- return dateToShow.toISOString();
3650
4001
  }
3651
4002
  }
3652
- 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 });
3653
- ProfileService.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: ProfileService, providedIn: 'root' });
3654
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: ProfileService, decorators: [{
4003
+ Cache.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: Cache, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4004
+ Cache.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: Cache, providedIn: 'root' });
4005
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: Cache, decorators: [{
3655
4006
  type: Injectable,
3656
4007
  args: [{
3657
4008
  providedIn: 'root',
3658
4009
  }]
3659
- }], ctorParameters: function () { return [{ type: Storage }, { type: i2$1.FidjService }, { type: i3$1.HttpClient }, { type: i4$1.Router }]; } });
4010
+ }], ctorParameters: function () { return []; } });
3660
4011
 
3661
4012
  class RadarService {
3662
4013
  constructor(profileService) {
@@ -4089,7 +4440,8 @@ SharedModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "
4089
4440
  RaainSpeedComponent,
4090
4441
  RaainGlobeComponent,
4091
4442
  ProfileIconDirective,
4092
- RaainDetailsComponent], imports: [CommonModule, FormsModule, IonicModule, NgOptimizedImage, PipesModule], exports: [CommonModule,
4443
+ RaainDetailsComponent,
4444
+ CumulativeSelectorComponent], imports: [CommonModule, FormsModule, IonicModule, NgOptimizedImage, PipesModule], exports: [CommonModule,
4093
4445
  NgStyle,
4094
4446
  PipesModule,
4095
4447
  RaainMapComponent,
@@ -4101,7 +4453,8 @@ SharedModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "
4101
4453
  RaainSpeedComponent,
4102
4454
  RaainGlobeComponent,
4103
4455
  ProfileIconDirective,
4104
- RaainDetailsComponent] });
4456
+ RaainDetailsComponent,
4457
+ CumulativeSelectorComponent] });
4105
4458
  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,
4106
4459
  PipesModule] });
4107
4460
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImport: i0, type: SharedModule, decorators: [{
@@ -4118,6 +4471,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
4118
4471
  RaainGlobeComponent,
4119
4472
  ProfileIconDirective,
4120
4473
  RaainDetailsComponent,
4474
+ CumulativeSelectorComponent,
4121
4475
  ],
4122
4476
  imports: [CommonModule, FormsModule, IonicModule, NgOptimizedImage, PipesModule],
4123
4477
  providers: [Storage, RadarService, ProfileService, Cache, IonRange],
@@ -4135,6 +4489,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
4135
4489
  RaainGlobeComponent,
4136
4490
  ProfileIconDirective,
4137
4491
  RaainDetailsComponent,
4492
+ CumulativeSelectorComponent,
4138
4493
  ],
4139
4494
  }]
4140
4495
  }], ctorParameters: function () {
@@ -4149,5 +4504,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "15.2.10", ngImpo
4149
4504
  * Generated bundle index. Do not edit.
4150
4505
  */
4151
4506
 
4152
- 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 };
4507
+ 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 };
4153
4508
  //# sourceMappingURL=raain-app.mjs.map