128 lines
3.5 KiB
TypeScript
128 lines
3.5 KiB
TypeScript
import {
|
|
Component,
|
|
OnInit,
|
|
computed,
|
|
inject,
|
|
signal,
|
|
} from '@angular/core';
|
|
import { resolveApiBaseUrl } from '../shared/api-base-url';
|
|
import { AnalyticsDashboard } from './dashboard.types';
|
|
import { DashboardService } from './dashboard.service';
|
|
|
|
@Component({
|
|
selector: 'app-dashboard',
|
|
standalone: true,
|
|
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));
|
|
}
|
|
}
|