Files
strava-mcp/client/src/app/dashboard/dashboard.component.ts
Bastian Wagner 7423920f34 details
2026-06-17 15:41:49 +02:00

130 lines
3.6 KiB
TypeScript

import {
Component,
OnInit,
computed,
inject,
signal,
} from '@angular/core';
import { RouterLink } from '@angular/router';
import { resolveApiBaseUrl } from '../shared/api-base-url';
import { AnalyticsDashboard } from './dashboard.types';
import { DashboardService } from './dashboard.service';
@Component({
selector: 'app-dashboard',
standalone: true,
imports: [RouterLink],
templateUrl: './dashboard.component.html',
styleUrl: './dashboard.component.scss',
})
export class DashboardComponent implements OnInit {
private readonly dashboardService = inject(DashboardService);
private readonly apiBaseUrl = resolveApiBaseUrl();
protected readonly dashboard = signal<AnalyticsDashboard | null>(null);
protected readonly dashboardLoading = signal(false);
protected readonly dashboardError = signal<string | null>(null);
protected readonly selectedSportType = signal<string | null>(null);
protected readonly maxWeeklyDistance = computed(() =>
Math.max(
1,
...(this.dashboard()?.weekly.map((week) => week.distanceMeters) ?? [0]),
),
);
protected readonly maxSportDistance = computed(() =>
Math.max(
1,
...(this.dashboard()?.sports.map((sport) => sport.distanceMeters) ?? [0]),
),
);
ngOnInit(): void {
this.loadDashboard();
}
protected loadDashboard(): void {
this.dashboardLoading.set(true);
this.dashboardError.set(null);
this.dashboardService
.getDashboard(this.apiBaseUrl, 12, this.selectedSportType())
.subscribe({
next: (dashboard) => {
this.dashboard.set(dashboard);
this.dashboardLoading.set(false);
},
error: () => {
this.dashboardError.set('Dashboard konnte nicht geladen werden.');
this.dashboardLoading.set(false);
},
});
}
protected selectSportType(value: string): void {
this.selectedSportType.set(value === 'all' ? null : value);
this.loadDashboard();
}
protected distanceKm(meters: number | null | undefined): string {
return `${((meters ?? 0) / 1000).toLocaleString('de-DE', {
maximumFractionDigits: 1,
})} km`;
}
protected duration(seconds: number | null | undefined): string {
const totalSeconds = seconds ?? 0;
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.round((totalSeconds % 3600) / 60);
return hours > 0 ? `${hours} h ${minutes} min` : `${minutes} min`;
}
protected elevation(meters: number | null | undefined): string {
return `${Math.round(meters ?? 0).toLocaleString('de-DE')} m`;
}
protected pace(secondsPerKm: number | null): string {
if (!secondsPerKm) {
return '-';
}
const minutes = Math.floor(secondsPerKm / 60);
const seconds = Math.round(secondsPerKm % 60)
.toString()
.padStart(2, '0');
return `${minutes}:${seconds} /km`;
}
protected speed(metersPerSecond: number | null): string {
if (!metersPerSecond) {
return '-';
}
return `${(metersPerSecond * 3.6).toLocaleString('de-DE', {
maximumFractionDigits: 1,
})} km/h`;
}
protected number(value: number | null | undefined, suffix = ''): string {
if (value === null || value === undefined) {
return '-';
}
return `${Math.round(value).toLocaleString('de-DE')}${suffix}`;
}
protected percent(value: number, max: number): number {
return Math.max(3, Math.round((value / max) * 100));
}
protected shortDate(value: string | null): string {
if (!value) {
return '-';
}
return new Intl.DateTimeFormat('de-DE', {
day: '2-digit',
month: '2-digit',
}).format(new Date(value));
}
}