feat(tournaments): make finished games in previous rounds clickable (#17)

Load all completed rounds in parallel when a tournament is expanded
and display them below the current round. Each finished game row is
clickable and navigates to the existing game review view. A 'Review'
label fades in on hover to signal interactivity.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: LQ63 <lkhermann@web.de>
Reviewed-on: #17
This commit was merged in pull request #17.
This commit is contained in:
2026-06-30 20:06:27 +02:00
parent efb129c323
commit c0a98a90dd
3 changed files with 54 additions and 0 deletions
@@ -340,6 +340,13 @@
}
.pairing-ongoing svg { animation: pulse 1.4s ease-in-out infinite; }
.pairing-review {
font-size: 10px; font-weight: 700; letter-spacing: 0.06em;
text-transform: uppercase; color: var(--nc-neon);
opacity: 0; transition: opacity 0.15s;
}
.pairing-row.is-watchable:hover .pairing-review { opacity: 1; }
/* Official bot join section */
.join-divider {
display: flex; align-items: center; gap: 10px;
@@ -186,6 +186,35 @@
</section>
}
<!-- Previous rounds -->
@if (previousRoundsLoading) {
<div class="state-msg small"><span class="pulse"></span>Loading previous rounds…</div>
}
@for (rp of previousRoundPairings; track rp.round) {
<section class="detail-section">
<h3 class="detail-heading">Round {{ rp.round }} pairings</h3>
<div class="pairings-list">
@for (p of rp.pairings; track pairingKey(p)) {
@let gid = firstGameId(p);
<div class="pairing-row" [class.is-watchable]="!!gid"
(click)="gid && watchGame(gid)">
<span class="pairing-white">{{ p.white?.name ?? 'Bye' }}</span>
<span class="pairing-vs">vs</span>
<span class="pairing-black">{{ p.black.name }}</span>
@if (p.winner) {
<span class="pairing-result" [class]="'result-' + p.winner">
{{ p.winner === 'draw' ? '½–½' : p.winner === 'white' ? '10' : '01' }}
</span>
}
@if (gid) {
<span class="pairing-review">Review</span>
}
</div>
}
</div>
</section>
}
</div>
}
</div>
@@ -44,6 +44,8 @@ export class TournamentsComponent implements OnInit {
selectedTournament: Tournament | null = null;
pairings: RoundPairings | null = null;
pairingsLoading = false;
previousRoundPairings: RoundPairings[] = [];
previousRoundsLoading = false;
showCreateDialog = false;
createForm: FormGroup = this.fb.group({
@@ -116,19 +118,35 @@ export class TournamentsComponent implements OnInit {
this.tab = tab;
this.selectedTournament = null;
this.pairings = null;
this.previousRoundPairings = [];
}
selectTournament(t: Tournament): void {
if (this.selectedTournament?.id === t.id) {
this.selectedTournament = null;
this.pairings = null;
this.previousRoundPairings = [];
return;
}
this.selectedTournament = t;
this.pairings = null;
this.previousRoundPairings = [];
if (t.round > 0) {
this.loadPairings(t.id, t.round);
}
if (t.round > 1) {
this.previousRoundsLoading = true;
const rounds = Array.from({ length: t.round - 1 }, (_, i) => i + 1);
forkJoin(rounds.map(r => this.tournamentService.roundPairings(t.id, r)))
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: results => {
this.previousRoundPairings = [...results].reverse();
this.previousRoundsLoading = false;
},
error: () => { this.previousRoundsLoading = false; }
});
}
}
get activeList(): Tournament[] {