|
@@ -16,7 +16,18 @@ import { OperationsLiveRefreshService } from '../operations-live-refresh.service
|
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
|
})
|
|
})
|
|
|
export class AdminDashboardComponent implements OnInit, OnDestroy {
|
|
export class AdminDashboardComponent implements OnInit, OnDestroy {
|
|
|
- @ViewChild('trendScroller') private trendScroller?: ElementRef<HTMLElement>;
|
|
|
|
|
|
|
+ @ViewChild('trendScroller') set trendScrollerElement(element: ElementRef<HTMLElement> | undefined) {
|
|
|
|
|
+ this.chartResizeObserver?.disconnect();
|
|
|
|
|
+ this.trendScroller = element;
|
|
|
|
|
+ if (!element) return;
|
|
|
|
|
+ this.chartViewportWidth.set(element.nativeElement.clientWidth);
|
|
|
|
|
+ this.chartResizeObserver = new ResizeObserver(([entry]) => {
|
|
|
|
|
+ this.chartViewportWidth.set(Math.round(entry.contentRect.width));
|
|
|
|
|
+ });
|
|
|
|
|
+ this.chartResizeObserver.observe(element.nativeElement);
|
|
|
|
|
+ }
|
|
|
|
|
+ private trendScroller?: ElementRef<HTMLElement>;
|
|
|
|
|
+ private chartResizeObserver?: ResizeObserver;
|
|
|
readonly data = signal<OperationsDashboardData | null>(null);
|
|
readonly data = signal<OperationsDashboardData | null>(null);
|
|
|
readonly loading = signal(true);
|
|
readonly loading = signal(true);
|
|
|
readonly error = signal('');
|
|
readonly error = signal('');
|
|
@@ -26,7 +37,8 @@ export class AdminDashboardComponent implements OnInit, OnDestroy {
|
|
|
readonly trendError = signal('');
|
|
readonly trendError = signal('');
|
|
|
readonly selectedTrendIndex = signal(0);
|
|
readonly selectedTrendIndex = signal(0);
|
|
|
readonly selectedTrendPoint = computed(() => this.trend()?.points[this.selectedTrendIndex()] ?? null);
|
|
readonly selectedTrendPoint = computed(() => this.trend()?.points[this.selectedTrendIndex()] ?? null);
|
|
|
- readonly chartWidth = computed(() => Math.max(720, (this.trend()?.points.length ?? 0) * 58 + 84));
|
|
|
|
|
|
|
+ readonly chartViewportWidth = signal(0);
|
|
|
|
|
+ readonly chartWidth = computed(() => Math.max(720, this.chartViewportWidth(), (this.trend()?.points.length ?? 0) * 58 + 84));
|
|
|
readonly icons = { ArrowRight, CircleAlert, LoaderCircle, RefreshCw };
|
|
readonly icons = { ArrowRight, CircleAlert, LoaderCircle, RefreshCw };
|
|
|
private readonly operations = inject(OperationsService);
|
|
private readonly operations = inject(OperationsService);
|
|
|
private readonly liveRefresh = inject(OperationsLiveRefreshService);
|
|
private readonly liveRefresh = inject(OperationsLiveRefreshService);
|
|
@@ -42,7 +54,7 @@ export class AdminDashboardComponent implements OnInit, OnDestroy {
|
|
|
} else void this.load();
|
|
} else void this.load();
|
|
|
this.stopLiveRefresh = this.liveRefresh.watch(['learning', 'courses', 'appointments', 'review', 'account'], () => { if (this.loading() || this.trendLoading()) return false; void this.load(true); return true; });
|
|
this.stopLiveRefresh = this.liveRefresh.watch(['learning', 'courses', 'appointments', 'review', 'account'], () => { if (this.loading() || this.trendLoading()) return false; void this.load(true); return true; });
|
|
|
}
|
|
}
|
|
|
- ngOnDestroy(): void { this.stopLiveRefresh?.(); }
|
|
|
|
|
|
|
+ ngOnDestroy(): void { this.stopLiveRefresh?.(); this.chartResizeObserver?.disconnect(); }
|
|
|
|
|
|
|
|
onOperationsRouteAttach(): void {
|
|
onOperationsRouteAttach(): void {
|
|
|
const snapshot = this.operations.dashboardSnapshot();
|
|
const snapshot = this.operations.dashboardSnapshot();
|
|
@@ -80,7 +92,7 @@ export class AdminDashboardComponent implements OnInit, OnDestroy {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
chartX(index: number): number { const count = this.trend()?.points.length ?? 0; return 54 + index * ((this.chartWidth() - 90) / Math.max(1, count - 1)); }
|
|
chartX(index: number): number { const count = this.trend()?.points.length ?? 0; return 54 + index * ((this.chartWidth() - 90) / Math.max(1, count - 1)); }
|
|
|
- chartY(value: number): number { return 204 - (value / Math.max(1, this.trend()?.maxValue ?? 1)) * 148; }
|
|
|
|
|
|
|
+ chartY(value: number): number { return 238 - (value / Math.max(1, this.trend()?.maxValue ?? 1)) * 176; }
|
|
|
chartLine(metric: 'courseCount' | 'studentCount'): string { return (this.trend()?.points ?? []).map((point, index) => `${this.chartX(index)},${this.chartY(point[metric])}`).join(' '); }
|
|
chartLine(metric: 'courseCount' | 'studentCount'): string { return (this.trend()?.points ?? []).map((point, index) => `${this.chartX(index)},${this.chartY(point[metric])}`).join(' '); }
|
|
|
gridValues(): number[] { const max = Math.max(1, this.trend()?.maxValue ?? 1); return [max, Math.round(max * .75), Math.round(max * .5), Math.round(max * .25), 0]; }
|
|
gridValues(): number[] { const max = Math.max(1, this.trend()?.maxValue ?? 1); return [max, Math.round(max * .75), Math.round(max * .5), Math.round(max * .25), 0]; }
|
|
|
dashboardMetric(key: string): OperationsDashboardMetric | null { return this.data()?.metrics.find((metric) => metric.key === key) ?? null; }
|
|
dashboardMetric(key: string): OperationsDashboardMetric | null { return this.data()?.metrics.find((metric) => metric.key === key) ?? null; }
|