fixes initial loading of stats page
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
import { Component, OnInit, inject, AfterViewChecked, OnDestroy, ViewChild, ElementRef, NgZone } from '@angular/core';
|
import { ChangeDetectorRef, Component, inject, AfterViewChecked, OnDestroy, OnInit, ViewChild, ElementRef, NgZone } from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { Chart, registerables } from 'chart.js';
|
import { Chart, registerables } from 'chart.js';
|
||||||
import { BaseChartDirective } from 'ng2-charts';
|
import { BaseChartDirective } from 'ng2-charts';
|
||||||
import L from 'leaflet';
|
import L from 'leaflet';
|
||||||
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
|
||||||
import { StatsService } from '../services/stats.service';
|
import { StatsService } from '../services/stats.service';
|
||||||
import {
|
import {
|
||||||
@@ -27,6 +28,7 @@ Chart.register(...registerables);
|
|||||||
})
|
})
|
||||||
export class AdminStatsDashboardComponent implements OnInit, AfterViewChecked, OnDestroy {
|
export class AdminStatsDashboardComponent implements OnInit, AfterViewChecked, OnDestroy {
|
||||||
private readonly statsService: StatsService = inject(StatsService);
|
private readonly statsService: StatsService = inject(StatsService);
|
||||||
|
private readonly cd = inject(ChangeDetectorRef);
|
||||||
private readonly ngZone = inject(NgZone);
|
private readonly ngZone = inject(NgZone);
|
||||||
|
|
||||||
@ViewChild('mapContainer', { static: false }) mapContainerRef!: ElementRef;
|
@ViewChild('mapContainer', { static: false }) mapContainerRef!: ElementRef;
|
||||||
@@ -82,10 +84,13 @@ export class AdminStatsDashboardComponent implements OnInit, AfterViewChecked, O
|
|||||||
// doesn't exist yet. ngAfterViewChecked detects when it appears.
|
// doesn't exist yet. ngAfterViewChecked detects when it appears.
|
||||||
if (this.mapInitialized) return;
|
if (this.mapInitialized) return;
|
||||||
if (!this.mapContainerRef) return;
|
if (!this.mapContainerRef) return;
|
||||||
|
// Verify the native element actually exists in the DOM (not just that
|
||||||
|
// the query ref was created — the @if block must have rendered too)
|
||||||
|
if (!this.mapContainerRef.nativeElement?.isConnected) return;
|
||||||
|
|
||||||
this.mapInitialized = true;
|
this.mapInitialized = true;
|
||||||
this.ngZone.runOutsideAngular(() => {
|
this.ngZone.runOutsideAngular(() => {
|
||||||
// Defer one frame so the browser has laid out the container
|
// Defer two frames so the browser has laid out the container
|
||||||
requestAnimationFrame(() => {
|
requestAnimationFrame(() => {
|
||||||
requestAnimationFrame(() => this._initMap());
|
requestAnimationFrame(() => this._initMap());
|
||||||
});
|
});
|
||||||
@@ -187,22 +192,34 @@ export class AdminStatsDashboardComponent implements OnInit, AfterViewChecked, O
|
|||||||
const { start, end } = this.getDates();
|
const { start, end } = this.getDates();
|
||||||
try {
|
try {
|
||||||
const [summary, timeSeries, geoStats] = await Promise.all([
|
const [summary, timeSeries, geoStats] = await Promise.all([
|
||||||
this._firstValue(this.statsService.getSummary(start, end)),
|
firstValueFrom(this.statsService.getSummary(start, end)),
|
||||||
this._firstValue(this.statsService.getTimeSeries(start, end)),
|
firstValueFrom(this.statsService.getTimeSeries(start, end)),
|
||||||
this._firstValue(this.statsService.getGeoStats(start, end)),
|
firstValueFrom(this.statsService.getGeoStats(start, end)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Angular 17+ uses fetch-based HTTP whose promise resolutions run
|
||||||
|
// outside the zone. Re-enter the zone and markForCheck() to force
|
||||||
|
// change detection through the OnPush parent chain.
|
||||||
|
this.ngZone.run(() => {
|
||||||
this.summary = summary;
|
this.summary = summary;
|
||||||
this.timeSeries = timeSeries;
|
this.timeSeries = timeSeries;
|
||||||
this.geoStats = geoStats;
|
this.geoStats = geoStats;
|
||||||
|
|
||||||
this._updateChart();
|
this._updateChart();
|
||||||
this._updateMapMarkers();
|
this._updateMapMarkers();
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to load stats:', err);
|
console.error('Failed to load stats:', err);
|
||||||
|
this.ngZone.run(() => {
|
||||||
this.error = 'Failed to load stats data. Please check your connection and try again.';
|
this.error = 'Failed to load stats data. Please check your connection and try again.';
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
} finally {
|
} finally {
|
||||||
|
this.ngZone.run(() => {
|
||||||
this.loading = false;
|
this.loading = false;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -252,11 +269,6 @@ export class AdminStatsDashboardComponent implements OnInit, AfterViewChecked, O
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private _firstValue<T>(obs: import('rxjs').Observable<T>): Promise<T> {
|
|
||||||
return new Promise<T>((resolve, reject) => {
|
|
||||||
obs.subscribe({ next: (v) => resolve(v), error: reject });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Chart ───────────────────────────────────────────────
|
// ── Chart ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user