feat(overview): add KPI charts to team overview page

Adds three chart.js-backed KPI cards (Kassenstand-Verlauf, Einnahmen &
Ausgaben, Top-10 offene Beitraege) to the existing Uebersicht page,
consuming the new GET teams/:id/overview/stats endpoint via a new
TeamStatsApi service. Introduces a small reusable ChartCanvas shared
component that wraps the Chart.js instance lifecycle via @Input()/
ngOnChanges, following this codebase's existing input-decorator
convention rather than effect().

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Bastian Wagner
2026-08-01 20:21:50 +02:00
parent ecb6fd4394
commit 435b8d5c53
13 changed files with 674 additions and 28 deletions

View File

@@ -18,6 +18,89 @@
></mat-card
>
</div>
<section class="chart-block chart-block--balance">
<div class="section-heading">
<div>
<p class="eyebrow">Kennzahlen</p>
<h2>Kassenstand-Verlauf</h2>
</div>
</div>
<mat-card class="chart-card">
<mat-card-content>
@if (loadingStats()) {
<div class="state"><mat-spinner diameter="32" /></div>
} @else if (balanceHistory().length === 0) {
<div class="state">
<mat-icon>show_chart</mat-icon><strong>Noch keine Kassenstand-Historie</strong
><span>Sobald Buchungen vorliegen, siehst du den Verlauf hier.</span>
</div>
} @else {
<div class="chart-canvas-box">
<app-chart-canvas type="line" [data]="balanceChartData()" [options]="balanceChartOptions" />
</div>
}
</mat-card-content>
</mat-card>
</section>
<section class="chart-block chart-block--flow">
<div class="section-heading">
<div>
<p class="eyebrow">Kennzahlen</p>
<h2>Einnahmen &amp; Ausgaben</h2>
</div>
</div>
<mat-card class="chart-card">
<mat-card-content>
@if (loadingStats()) {
<div class="state"><mat-spinner diameter="32" /></div>
} @else if (monthlyFlow().length === 0) {
<div class="state">
<mat-icon>bar_chart</mat-icon><strong>Noch keine Bewegungen</strong
><span>Einnahmen und Ausgaben erscheinen hier pro Monat.</span>
</div>
} @else {
<div class="chart-canvas-box">
<app-chart-canvas type="bar" [data]="flowChartData()" [options]="flowChartOptions" />
</div>
}
</mat-card-content>
</mat-card>
</section>
<section class="chart-block chart-block--outstanding">
<div class="section-heading">
<div>
<p class="eyebrow">Kennzahlen</p>
<h2>Offene Beiträge (Top 10)</h2>
</div>
</div>
<mat-card class="chart-card">
<mat-card-content>
@if (loadingStats()) {
<div class="state"><mat-spinner diameter="32" /></div>
} @else if (topOutstanding().length === 0) {
<div class="state">
<mat-icon>emoji_events</mat-icon><strong>Keine offenen Beiträge</strong
><span>Alle aktiven Mitglieder sind ausgeglichen.</span>
</div>
} @else {
<div class="chart-canvas-box">
<app-chart-canvas
type="bar"
[data]="topOutstandingChartData()"
[options]="topOutstandingChartOptions"
/>
</div>
}
<a mat-button class="chart-card__link" routerLink="../members"
><mat-icon>group</mat-icon>Alle Spieler ansehen</a
>
</mat-card-content>
</mat-card>
</section>
<div class="section-heading">
<div>
<p class="eyebrow">Zuletzt passiert</p>