add carousel view

This commit is contained in:
2026-03-11 22:17:42 +01:00
parent 14c72b06d4
commit 383795bff6
6 changed files with 185 additions and 8 deletions

View File

@@ -1,4 +1,5 @@
import { Team, TournamentTeam, TeamMember } from '../models/team'; import { Team, TournamentTeam, TeamMember } from '../models/team';
import { Tournament } from '../models/tournament';
import { Database, RunResult } from 'sqlite3'; import { Database, RunResult } from 'sqlite3';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
@@ -125,12 +126,16 @@ export class TeamService {
}); });
} }
getTournamentsByTeamId(teamId: number): Promise<TournamentTeam[]> { getTournamentsByTeamId(teamId: number): Promise<Tournament[]> {
return new Promise<TournamentTeam[]>((resolve, reject) => { return new Promise<Tournament[]>((resolve, reject) => {
this.db.all( 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], [teamId],
(err: Error | null, rows: TournamentTeam[]) => { (err: Error | null, rows: Tournament[]) => {
if (err) return reject(err); if (err) return reject(err);
resolve(rows); resolve(rows);
} }

View File

@@ -53,3 +53,17 @@ Folgende Dateien wurden in diesem Prompt verändert:
- Implement auth-aware router to keep users logged in after app restart.<br><br> - Implement auth-aware router to keep users logged in after app restart.<br><br>
Folgende Dateien wurden in diesem Prompt verändert: Folgende Dateien wurden in diesem Prompt verändert:
- frontend_splatournament_manager/lib/main.dart - 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.<br><br>
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.<br><br>
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.<br><br>
Folgende Dateien wurden in diesem Prompt verändert:
- frontend_splatournament_manager/lib/widgets/my_tournaments_carousel.dart

View File

@@ -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/available_tournament_list.dart';
import 'package:frontend_splatournament_manager/widgets/teams_list_widget.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_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_tournament_page.dart';
import 'package:frontend_splatournament_manager/pages/create_team_page.dart'; import 'package:frontend_splatournament_manager/pages/create_team_page.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -91,9 +92,11 @@ class _HomePageState extends State<HomePage> with SingleTickerProviderStateMixin
index: _selectedIndex, index: _selectedIndex,
children: [ children: [
// Tournaments View // Tournaments View
Container( Column(
padding: const EdgeInsets.fromLTRB(0, 12, 0, 36), children: [
child: Column(children: [const Spacer(), const AvailableTournamentList()]), const MyTournamentsCarousel(),
const Expanded(child: AvailableTournamentList()),
],
), ),
// Teams View with tabs // Teams View with tabs
TabBarView( TabBarView(

View File

@@ -104,7 +104,7 @@ class _TournamentDetailPageState extends State<TournamentDetailPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text("Tournament"), title: Text(widget.tournament.name, style: TextStyle(overflow: TextOverflow.ellipsis)),
backgroundColor: Theme.of(context).colorScheme.surface.withAlpha(180), backgroundColor: Theme.of(context).colorScheme.surface.withAlpha(180),
elevation: 3, elevation: 3,
actions: [ actions: [

View File

@@ -88,5 +88,21 @@ class TeamProvider extends ChangeNotifier {
Future<List<Map<String, dynamic>>> getTeamMembers(int teamId) { Future<List<Map<String, dynamic>>> getTeamMembers(int teamId) {
return _teamService.getTeamMembers(teamId); return _teamService.getTeamMembers(teamId);
} }
Future<List<Map<String, dynamic>>> getMyTeamsTournaments() async {
final userTeams = await getUserTeams();
final Set<Map<String, dynamic>> 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();
}
} }

View File

@@ -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<List<Map<String, dynamic>>>(
future: Provider.of<TeamProvider>(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),
),
],
),
],
),
),
),
);
}
}