diff --git a/docs/prompt.md b/docs/prompt.md
index fb83464..d56043f 100644
--- a/docs/prompt.md
+++ b/docs/prompt.md
@@ -95,3 +95,8 @@ Folgende Dateien wurden in diesem Prompt verändert:
- Only allow entering a tournament if the registration period is open currently.
Folgende Dateien wurden in diesem Prompt verändert:
- frontend_splatournament_manager/lib/pages/tournament_detail_page.dart
+
+- Add the ongoing view where teams are displayed in a bracket layout: single elimination, supporting 2/4/8 teams with horizontally scrollable rounds connected by bracket lines.
+Folgende Dateien wurden in diesem Prompt verändert:
+ - frontend_splatournament_manager/lib/pages/tournament_bracket_page.dart (neu erstellt)
+ - frontend_splatournament_manager/lib/pages/tournament_detail_page.dart
diff --git a/frontend_splatournament_manager/lib/pages/tournament_bracket_page.dart b/frontend_splatournament_manager/lib/pages/tournament_bracket_page.dart
new file mode 100644
index 0000000..11af7c4
--- /dev/null
+++ b/frontend_splatournament_manager/lib/pages/tournament_bracket_page.dart
@@ -0,0 +1,245 @@
+import 'package:flutter/material.dart';
+import 'package:frontend_splatournament_manager/models/team.dart';
+import 'package:frontend_splatournament_manager/models/tournament.dart';
+import 'package:frontend_splatournament_manager/providers/team_provider.dart';
+import 'package:provider/provider.dart';
+
+class TournamentBracketPage extends StatelessWidget {
+ final Tournament tournament;
+
+ const TournamentBracketPage({super.key, required this.tournament});
+
+ int _bracketSize(int n) {
+ if (n <= 2) return 2;
+ if (n <= 4) return 4;
+ return 8;
+ }
+
+ int _roundCount(int bracketSize) {
+ if (bracketSize == 2) return 2;
+ if (bracketSize == 4) return 3;
+ return 4;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final teamProvider = Provider.of(context, listen: false);
+
+ return Scaffold(
+ appBar: AppBar(title: Text(tournament.name)),
+ body: FutureBuilder>(
+ future: teamProvider.getTeamsByTournament(tournament.id),
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.waiting) {
+ return const Center(child: CircularProgressIndicator());
+ }
+ if (snapshot.hasError) {
+ return Center(child: Text('Error: ${snapshot.error}'));
+ }
+
+ final teams = snapshot.data ?? [];
+ final bracketSize = _bracketSize(tournament.maxTeamAmount);
+ final roundCount = _roundCount(bracketSize);
+
+ return Scrollbar(
+ thumbVisibility: true,
+ child: SingleChildScrollView(
+ scrollDirection: Axis.horizontal,
+ padding: const EdgeInsets.all(24),
+ child: SingleChildScrollView(
+ scrollDirection: Axis.vertical,
+ child: _BracketBoard(
+ teams: teams,
+ bracketSize: bracketSize,
+ roundCount: roundCount,
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ }
+}
+
+class _BracketBoard extends StatelessWidget {
+ final List teams;
+ final int bracketSize;
+ final int roundCount;
+
+ static const double _cardWidth = 112;
+ static const double _cardHeight = 96;
+ static const double _baseSlotHeight = 128;
+ static const double _connectorWidth = 48;
+ static const double _headerHeight = 28;
+ static const double _lineThickness = 2;
+
+ const _BracketBoard({
+ required this.teams,
+ required this.bracketSize,
+ required this.roundCount,
+ });
+
+ String _roundLabel(int round) {
+ if (round == roundCount - 1) return 'Winner';
+ final teamsInRound = bracketSize ~/ (1 << round);
+ if (teamsInRound == 2) return 'Final';
+ if (teamsInRound == 4) return 'Semi-finals';
+ return 'Quarter-finals';
+ }
+
+ double _cardTop(int round, int index) {
+ final slotHeight = _baseSlotHeight * (1 << round).toDouble();
+ return _headerHeight + index * slotHeight + (slotHeight - _cardHeight) / 2;
+ }
+
+ double _cardCenterY(int round, int index) =>
+ _cardTop(round, index) + _cardHeight / 2;
+
+ @override
+ Widget build(BuildContext context) {
+ final colorScheme = Theme.of(context).colorScheme;
+ final lineColor = colorScheme.outlineVariant;
+
+ final boardWidth =
+ roundCount * _cardWidth + (roundCount - 1) * _connectorWidth;
+ final boardHeight = _headerHeight + bracketSize * _baseSlotHeight;
+
+ final children = [];
+
+ for (int round = 0; round < roundCount; round++) {
+ final cardsInRound = bracketSize ~/ (1 << round);
+ final left = round * (_cardWidth + _connectorWidth);
+
+ children.add(
+ Positioned(
+ left: left,
+ top: 0,
+ width: _cardWidth,
+ height: _headerHeight,
+ child: Center(
+ child: Text(
+ _roundLabel(round),
+ style: TextStyle(
+ fontSize: 11,
+ fontWeight: FontWeight.w700,
+ color: colorScheme.primary,
+ letterSpacing: 0.3,
+ ),
+ ),
+ ),
+ ),
+ );
+
+ for (int i = 0; i < cardsInRound; i++) {
+ final String? label = (round == 0 && i < teams.length)
+ ? teams[i].name
+ : null;
+ children.add(
+ Positioned(
+ left: left,
+ top: _cardTop(round, i),
+ width: _cardWidth,
+ height: _cardHeight,
+ child: _TeamCard(label: label),
+ ),
+ );
+ }
+
+ if (round == roundCount - 1) {
+ continue;
+ }
+
+ final connectorLeft = left + _cardWidth;
+ final matches = cardsInRound ~/ 2;
+ final halfConnector = _connectorWidth / 2;
+
+ for (int i = 0; i < matches; i++) {
+ final yTop = _cardCenterY(round, i * 2);
+ final yBottom = _cardCenterY(round, i * 2 + 1);
+ final yMiddle = (yTop + yBottom) / 2;
+
+ children.add(
+ Positioned(
+ left: connectorLeft,
+ top: yTop - _lineThickness / 2,
+ width: halfConnector,
+ height: _lineThickness,
+ child: DecoratedBox(decoration: BoxDecoration(color: lineColor)),
+ ),
+ );
+
+ children.add(
+ Positioned(
+ left: connectorLeft,
+ top: yBottom - _lineThickness / 2,
+ width: halfConnector,
+ height: _lineThickness,
+ child: DecoratedBox(decoration: BoxDecoration(color: lineColor)),
+ ),
+ );
+
+ children.add(
+ Positioned(
+ left: connectorLeft + halfConnector - _lineThickness / 2,
+ top: yTop,
+ width: _lineThickness,
+ height: yBottom - yTop,
+ child: DecoratedBox(decoration: BoxDecoration(color: lineColor)),
+ ),
+ );
+
+ children.add(
+ Positioned(
+ left: connectorLeft + halfConnector,
+ top: yMiddle - _lineThickness / 2,
+ width: halfConnector,
+ height: _lineThickness,
+ child: DecoratedBox(decoration: BoxDecoration(color: lineColor)),
+ ),
+ );
+ }
+ }
+
+ return SizedBox(
+ width: boardWidth,
+ height: boardHeight,
+ child: Stack(children: children),
+ );
+ }
+}
+
+class _TeamCard extends StatelessWidget {
+ final String? label;
+
+ const _TeamCard({this.label});
+
+ @override
+ Widget build(BuildContext context) {
+ final colorScheme = Theme.of(context).colorScheme;
+ final isTbd = label == null;
+
+ return Card(
+ elevation: isTbd ? 1 : 2,
+ shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
+ child: Center(
+ child: Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 8),
+ child: Text(
+ isTbd ? '?' : label!,
+ textAlign: TextAlign.center,
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ style: TextStyle(
+ fontSize: 13,
+ fontWeight: isTbd ? FontWeight.w400 : FontWeight.w600,
+ color: isTbd
+ ? colorScheme.onSurface.withValues(alpha: 0.38)
+ : colorScheme.onSurface,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart
index d65984a..0511535 100644
--- a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart
+++ b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:frontend_splatournament_manager/models/team.dart';
import 'package:frontend_splatournament_manager/models/tournament.dart';
+import 'package:frontend_splatournament_manager/pages/tournament_bracket_page.dart';
import 'package:frontend_splatournament_manager/providers/team_provider.dart';
import 'package:provider/provider.dart';
@@ -322,10 +323,12 @@ class TournamentContentWidget extends StatelessWidget {
width: double.infinity,
child: ElevatedButton(
onPressed: () {
- //TODO: Redirect to Ongoing View
- ScaffoldMessenger.of(context).clearSnackBars();
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(content: Text("ongoing clicked")),
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) =>
+ TournamentBracketPage(tournament: tournament),
+ ),
);
},
child: Text("View ongoing"),