diff --git a/backend_splatournament_manager/src/services/team-service.ts b/backend_splatournament_manager/src/services/team-service.ts index e093bd3..f94b811 100644 --- a/backend_splatournament_manager/src/services/team-service.ts +++ b/backend_splatournament_manager/src/services/team-service.ts @@ -1,4 +1,5 @@ import { Team, TournamentTeam, TeamMember } from '../models/team'; +import { Tournament } from '../models/tournament'; import { Database, RunResult } from 'sqlite3'; import fs from 'fs'; import path from 'path'; @@ -125,12 +126,16 @@ export class TeamService { }); } - getTournamentsByTeamId(teamId: number): Promise { - return new Promise((resolve, reject) => { + getTournamentsByTeamId(teamId: number): Promise { + return new Promise((resolve, reject) => { this.db.all( - `SELECT * FROM TournamentTeams WHERE teamId = ?`, + `SELECT t.*, + (SELECT COUNT(*) FROM TournamentTeams WHERE tournamentId = t.id) as currentTeamAmount + FROM Tournaments t + INNER JOIN TournamentTeams tt ON t.id = tt.tournamentId + WHERE tt.teamId = ?`, [teamId], - (err: Error | null, rows: TournamentTeam[]) => { + (err: Error | null, rows: Tournament[]) => { if (err) return reject(err); resolve(rows); } diff --git a/docs/prompt.md b/docs/prompt.md index 8ec14d9..1ec5225 100644 --- a/docs/prompt.md +++ b/docs/prompt.md @@ -53,3 +53,17 @@ Folgende Dateien wurden in diesem Prompt verändert: - Implement auth-aware router to keep users logged in after app restart.

Folgende Dateien wurden in diesem Prompt verändert: - frontend_splatournament_manager/lib/main.dart + +- Create a carousel on the homepage that shows all the tournaments that one of your teams is participating in.

+Folgende Dateien wurden in diesem Prompt verändert: + - frontend_splatournament_manager/lib/providers/team_provider.dart + - frontend_splatournament_manager/lib/widgets/my_tournaments_carousel.dart (neu erstellt) + - frontend_splatournament_manager/lib/pages/home_page.dart + +- Fix getTournamentsByTeam endpoint to return full Tournament objects instead of TournamentTeam objects.

+Folgende Dateien wurden in diesem Prompt verändert: + - backend_splatournament_manager/src/services/team-service.ts (changed return type from any[] to Tournament[]) + +- Add navigation to tournament details in the carousel.

+Folgende Dateien wurden in diesem Prompt verändert: + - frontend_splatournament_manager/lib/widgets/my_tournaments_carousel.dart diff --git a/frontend_splatournament_manager/lib/pages/home_page.dart b/frontend_splatournament_manager/lib/pages/home_page.dart index f1a9c0a..4436914 100644 --- a/frontend_splatournament_manager/lib/pages/home_page.dart +++ b/frontend_splatournament_manager/lib/pages/home_page.dart @@ -4,6 +4,7 @@ import 'package:frontend_splatournament_manager/providers/team_provider.dart'; import 'package:frontend_splatournament_manager/widgets/available_tournament_list.dart'; import 'package:frontend_splatournament_manager/widgets/teams_list_widget.dart'; import 'package:frontend_splatournament_manager/widgets/my_teams_widget.dart'; +import 'package:frontend_splatournament_manager/widgets/my_tournaments_carousel.dart'; import 'package:frontend_splatournament_manager/pages/create_tournament_page.dart'; import 'package:frontend_splatournament_manager/pages/create_team_page.dart'; import 'package:go_router/go_router.dart'; @@ -91,9 +92,11 @@ class _HomePageState extends State with SingleTickerProviderStateMixin index: _selectedIndex, children: [ // Tournaments View - Container( - padding: const EdgeInsets.fromLTRB(0, 12, 0, 36), - child: Column(children: [const Spacer(), const AvailableTournamentList()]), + Column( + children: [ + const MyTournamentsCarousel(), + const Expanded(child: AvailableTournamentList()), + ], ), // Teams View with tabs TabBarView( diff --git a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart index a8daad1..0476543 100644 --- a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart +++ b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart @@ -104,7 +104,7 @@ class _TournamentDetailPageState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: Text("Tournament"), + title: Text(widget.tournament.name, style: TextStyle(overflow: TextOverflow.ellipsis)), backgroundColor: Theme.of(context).colorScheme.surface.withAlpha(180), elevation: 3, actions: [ diff --git a/frontend_splatournament_manager/lib/providers/team_provider.dart b/frontend_splatournament_manager/lib/providers/team_provider.dart index defc55a..a60e691 100644 --- a/frontend_splatournament_manager/lib/providers/team_provider.dart +++ b/frontend_splatournament_manager/lib/providers/team_provider.dart @@ -88,5 +88,21 @@ class TeamProvider extends ChangeNotifier { Future>> getTeamMembers(int teamId) { return _teamService.getTeamMembers(teamId); } + + Future>> getMyTeamsTournaments() async { + final userTeams = await getUserTeams(); + final Set> tournamentsSet = {}; + + for (final team in userTeams) { + try { + final tournaments = await _teamService.getTournamentsByTeam(team.id); + tournamentsSet.addAll(tournaments); + } catch (e) { + // If a team has no tournaments, continue with others + } + } + + return tournamentsSet.toList(); + } } diff --git a/frontend_splatournament_manager/lib/widgets/my_tournaments_carousel.dart b/frontend_splatournament_manager/lib/widgets/my_tournaments_carousel.dart new file mode 100644 index 0000000..f300ea3 --- /dev/null +++ b/frontend_splatournament_manager/lib/widgets/my_tournaments_carousel.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:frontend_splatournament_manager/providers/team_provider.dart'; +import 'package:frontend_splatournament_manager/models/tournament.dart'; +import 'package:frontend_splatournament_manager/pages/tournament_detail_page.dart'; +import 'package:provider/provider.dart'; + +class MyTournamentsCarousel extends StatelessWidget { + const MyTournamentsCarousel({super.key}); + + @override + Widget build(BuildContext context) { + return FutureBuilder>>( + future: Provider.of(context, listen: false).getMyTeamsTournaments(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const SizedBox( + height: 180, + child: Center(child: CircularProgressIndicator()), + ); + } + + if (snapshot.hasError) { + return const SizedBox.shrink(); + } + + final tournaments = snapshot.data ?? []; + + if (tournaments.isEmpty) { + return const SizedBox.shrink(); + } + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Padding( + padding: EdgeInsets.fromLTRB(16, 16, 16, 8), + child: Text( + 'My Tournaments', + style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold), + ), + ), + SizedBox( + height: 150, + child: PageView.builder( + controller: PageController(viewportFraction: 0.9), + itemCount: tournaments.length, + itemBuilder: (context, index) { + final tournament = Tournament.fromJson(tournaments[index]); + return _TournamentCard(tournament: tournament); + }, + ), + ), + const SizedBox(height: 16), + ], + ); + }, + ); + } +} + +class _TournamentCard extends StatelessWidget { + final Tournament tournament; + + const _TournamentCard({required this.tournament}); + + @override + Widget build(BuildContext context) { + return Card( + margin: const EdgeInsets.symmetric(horizontal: 8), + elevation: 4, + child: InkWell( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => TournamentDetailPage(tournament: tournament), + ), + ); + }, + child: Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Row( + children: [ + const Icon(Icons.emoji_events, size: 32), + const SizedBox(width: 12), + Expanded( + child: Text( + tournament.name, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 8), + Text( + tournament.description, + style: const TextStyle(fontSize: 16), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + const SizedBox(height: 8), + Row( + children: [ + Icon( + tournament.isRegistrationOpen + ? Icons.check_circle + : Icons.cancel, + size: 18, + ), + const SizedBox(width: 4), + Text( + tournament.isRegistrationOpen + ? 'Registration Open' + : 'Registration Closed', + style: const TextStyle(fontSize: 14), + ), + const Spacer(), + Text( + '${tournament.currentTeamAmount}/${tournament.maxTeamAmount} teams', + style: const TextStyle(fontSize: 14), + ), + ], + ), + ], + ), + ), + ), + ); + } +}