From 3b8209225ede33d65f65b2b730acbddfdb14d8d9 Mon Sep 17 00:00:00 2001 From: tikaiz Date: Tue, 10 Mar 2026 15:59:03 +0100 Subject: [PATCH] Add service and provider for Teams --- .../src/services/tournament-service.ts | 11 +- frontend_splatournament_manager/lib/main.dart | 18 ++- .../lib/pages/tournament_detail_page.dart | 89 +++++++++++- .../lib/providers/team_provider.dart | 62 +++++++++ .../lib/providers/tournament_provider.dart | 4 +- .../lib/services/auth_service.dart | 4 +- .../lib/services/team_service.dart | 127 ++++++++++++++++++ 7 files changed, 295 insertions(+), 20 deletions(-) create mode 100644 frontend_splatournament_manager/lib/providers/team_provider.dart create mode 100644 frontend_splatournament_manager/lib/services/team_service.dart diff --git a/backend_splatournament_manager/src/services/tournament-service.ts b/backend_splatournament_manager/src/services/tournament-service.ts index 100094a..5dce704 100644 --- a/backend_splatournament_manager/src/services/tournament-service.ts +++ b/backend_splatournament_manager/src/services/tournament-service.ts @@ -17,7 +17,6 @@ export class TournamentService { name TEXT, description TEXT, maxTeamAmount INTEGER, - currentTeamAmount INTEGER, registrationStartDate TEXT, registrationEndDate TEXT )`); @@ -43,7 +42,7 @@ export class TournamentService { name: row.name, description: row.description, maxTeamAmount: row.maxTeamAmount, - currentTeamAmount: row.currentTeamAmount, + currentTeamAmount: 0, registrationStartDate: row.registrationStartDate, registrationEndDate: row.registrationEndDate, teams: [], @@ -57,6 +56,7 @@ export class TournamentService { description: row.teamDescription, createdAt: row.teamCreatedAt, } as Team); + tournamentsMap.get(row.id)!.currentTeamAmount++; } } resolve(Array.from(tournamentsMap.values())); @@ -98,6 +98,7 @@ export class TournamentService { description: row.teamDescription, createdAt: row.teamCreatedAt, } as Team); + tournament.currentTeamAmount++; } } resolve(tournament); @@ -108,8 +109,8 @@ export class TournamentService { addTournament(tournament: Tournament): Promise { return new Promise((resolve, reject) => { - const statement = this.db.prepare('Insert Into Tournaments (name, description, maxTeamAmount, currentTeamAmount, registrationStartDate, registrationEndDate) VALUES (?, ?, ?, ?, ?, ?)') - statement.run(tournament.name, tournament.description, tournament.maxTeamAmount, tournament.currentTeamAmount, tournament.registrationStartDate, tournament.registrationEndDate); + const statement = this.db.prepare('Insert Into Tournaments (name, description, maxTeamAmount, registrationStartDate, registrationEndDate) VALUES (?, ?, ?, ?, ?)') + statement.run(tournament.name, tournament.description, tournament.maxTeamAmount, tournament.registrationStartDate, tournament.registrationEndDate); resolve(); }) } @@ -120,7 +121,6 @@ export class TournamentService { Set name = $name, description = $description, maxTeamAmount = $maxTeamAmount, - currentTeamAmount = $currentTeamAmount, registrationStartDate = $registrationStartDate, registrationEndDate = $registrationEndDate where id = $id`, { @@ -128,7 +128,6 @@ export class TournamentService { $name: updatedTournament.name, $description: updatedTournament.description, $maxTeamAmount: updatedTournament.maxTeamAmount, - $currentTeamAmount: updatedTournament.currentTeamAmount, $registrationStartDate: updatedTournament.registrationStartDate, $registrationEndDate: updatedTournament.registrationEndDate, }); diff --git a/frontend_splatournament_manager/lib/main.dart b/frontend_splatournament_manager/lib/main.dart index d9d96c7..5eeb7dd 100644 --- a/frontend_splatournament_manager/lib/main.dart +++ b/frontend_splatournament_manager/lib/main.dart @@ -3,6 +3,7 @@ import 'package:frontend_splatournament_manager/pages/home_page.dart'; import 'package:frontend_splatournament_manager/pages/login_page.dart'; import 'package:frontend_splatournament_manager/pages/settings_page.dart'; import 'package:frontend_splatournament_manager/providers/auth_provider.dart'; +import 'package:frontend_splatournament_manager/providers/team_provider.dart'; import 'package:frontend_splatournament_manager/providers/theme_provider.dart'; import 'package:frontend_splatournament_manager/providers/tournament_provider.dart'; import 'package:go_router/go_router.dart'; @@ -15,6 +16,7 @@ void main() { ChangeNotifierProvider(create: (_) => ThemeProvider()), ChangeNotifierProvider(create: (_) => TournamentProvider()), ChangeNotifierProvider(create: (_) => AuthProvider()), + ChangeNotifierProvider(create: (_) => TeamProvider()), ], child: const SplatournamentApp(), ), @@ -22,6 +24,7 @@ void main() { } class SplatournamentApp extends StatelessWidget { + static const String baseUrl = "http://10.0.2.2:3000"; const SplatournamentApp({super.key}); @override Widget build(BuildContext context) { @@ -41,12 +44,17 @@ class SplatournamentApp extends StatelessWidget { ); } } + var routes = GoRouter( initialLocation: '/login', routes: [ GoRoute(path: "/login", builder: (context, state) => const LoginPage()), - GoRoute(path: "/", builder: (context, state) => HomePage(),routes: [ - GoRoute(path: "settings", builder: (context, state) => SettingsPage(),) - ]) - ] -); \ No newline at end of file + GoRoute( + path: "/", + builder: (context, state) => HomePage(), + routes: [ + GoRoute(path: "settings", builder: (context, state) => SettingsPage()), + ], + ), + ], +); diff --git a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart index d381d68..bae228f 100644 --- a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart +++ b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart @@ -1,5 +1,8 @@ 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 TournamentDetailPage extends StatefulWidget { final Tournament tournament; @@ -72,15 +75,89 @@ class _TournamentDetailPageState extends State { } } -class TournamentTeamsWidget extends StatelessWidget{ - const TournamentTeamsWidget({super.key, required Tournament tournament}); +class TournamentTeamsWidget extends StatefulWidget { + final Tournament tournament; + + const TournamentTeamsWidget({super.key, required this.tournament}); + + @override + State createState() => _TournamentTeamsWidgetState(); +} + +class _TournamentTeamsWidgetState extends State { + late Future> _teamsFuture; + + @override + void initState() { + super.initState(); + _teamsFuture = Provider.of(context, listen: false) + .getTeamsByTournament(widget.tournament.id); + } + @override Widget build(BuildContext context) { return Expanded( - child: Column( children: [ - //TODO: Show participating Teams - Text("Teams"), - ],), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(16, 12, 16, 4), + child: Text( + 'Registered Teams', + style: TextStyle(fontWeight: FontWeight.w600, fontSize: 17), + ), + ), + Expanded( + child: FutureBuilder>( + future: _teamsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return Center(child: CircularProgressIndicator()); + } + if (snapshot.hasError) { + return Center(child: Text('Error: ${snapshot.error}')); + } + final teams = snapshot.data ?? []; + if (teams.isEmpty) { + return Center(child: Text('No teams registered yet.')); + } + return ListView.builder( + itemCount: teams.length, + itemBuilder: (context, index) { + final team = teams[index]; + return ListTile( + leading: CircleAvatar(child: Text(team.tag)), + title: Text(team.name), + subtitle: team.description.isNotEmpty + ? Text(team.description) + : null, + trailing: IconButton( + icon: Icon(Icons.remove_circle_outline, color: Colors.red), + onPressed: () async { + try { + await Provider.of(context, listen: false) + .removeTeamFromTournament(widget.tournament.id, team.id); + setState(() { + _teamsFuture = + Provider.of(context, listen: false) + .getTeamsByTournament(widget.tournament.id); + }); + } catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Failed to remove team: $e')), + ); + } + }, + ), + ); + }, + ); + }, + ), + ), + ], + ), ); } diff --git a/frontend_splatournament_manager/lib/providers/team_provider.dart b/frontend_splatournament_manager/lib/providers/team_provider.dart new file mode 100644 index 0000000..e0f5a4b --- /dev/null +++ b/frontend_splatournament_manager/lib/providers/team_provider.dart @@ -0,0 +1,62 @@ +import 'package:flutter/material.dart'; +import 'package:frontend_splatournament_manager/models/team.dart'; +import 'package:frontend_splatournament_manager/services/team_service.dart'; + +class TeamProvider extends ChangeNotifier { + final TeamService _teamService = TeamService(); + + List _teams = []; + List get teams => _teams; + + Future> fetchAllTeams() async { + _teams = await _teamService.getAllTeams(); + notifyListeners(); + return _teams; + } + + Future createTeam({ + required String name, + required String tag, + String description = '', + }) async { + final team = await _teamService.createTeam( + name: name, + tag: tag, + description: description, + ); + _teams = [..._teams, team]; + notifyListeners(); + return team; + } + + Future updateTeam( + int id, { + String? name, + String? tag, + String? description, + }) async { + await _teamService.updateTeam(id, name: name, tag: tag, description: description); + await fetchAllTeams(); + } + + Future deleteTeam(int id) async { + await _teamService.deleteTeam(id); + _teams = _teams.where((t) => t.id != id).toList(); + notifyListeners(); + } + + Future> getTeamsByTournament(int tournamentId) async { + return _teamService.getTeamsByTournament(tournamentId); + } + + Future registerTeamForTournament(int tournamentId, int teamId) async { + await _teamService.registerTeamForTournament(tournamentId, teamId); + notifyListeners(); + } + + Future removeTeamFromTournament(int tournamentId, int teamId) async { + await _teamService.removeTeamFromTournament(tournamentId, teamId); + notifyListeners(); + } +} + diff --git a/frontend_splatournament_manager/lib/providers/tournament_provider.dart b/frontend_splatournament_manager/lib/providers/tournament_provider.dart index d18040e..0041121 100644 --- a/frontend_splatournament_manager/lib/providers/tournament_provider.dart +++ b/frontend_splatournament_manager/lib/providers/tournament_provider.dart @@ -5,8 +5,10 @@ import 'package:flutter/material.dart'; import 'package:frontend_splatournament_manager/models/tournament.dart'; import 'package:http/http.dart' as http; +import '../main.dart'; + class TournamentProvider extends ChangeNotifier { - static const String baseUrl = "http://10.0.2.2:3000"; + final String baseUrl = SplatournamentApp.baseUrl; List _availableTournaments = []; Future>? _initialLoadFuture; diff --git a/frontend_splatournament_manager/lib/services/auth_service.dart b/frontend_splatournament_manager/lib/services/auth_service.dart index 8671870..9822cb8 100644 --- a/frontend_splatournament_manager/lib/services/auth_service.dart +++ b/frontend_splatournament_manager/lib/services/auth_service.dart @@ -1,9 +1,9 @@ import 'dart:convert'; +import 'package:frontend_splatournament_manager/main.dart'; import 'package:http/http.dart' as http; class AuthService { - static const String baseUrl = "http://10.0.2.2:3000"; - + final String baseUrl = SplatournamentApp.baseUrl; Future> register(String username, String password) async { final response = await http.post( Uri.parse('$baseUrl/register'), diff --git a/frontend_splatournament_manager/lib/services/team_service.dart b/frontend_splatournament_manager/lib/services/team_service.dart new file mode 100644 index 0000000..566dd3e --- /dev/null +++ b/frontend_splatournament_manager/lib/services/team_service.dart @@ -0,0 +1,127 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:frontend_splatournament_manager/main.dart'; +import 'package:frontend_splatournament_manager/models/team.dart'; +import 'package:http/http.dart' as http; + +class TeamService { + final String baseUrl = SplatournamentApp.baseUrl; + + Future> getAllTeams() async { + final response = await http.get(Uri.parse('$baseUrl/teams')); + if (response.statusCode != HttpStatus.ok) { + throw Exception('Failed to load teams (${response.statusCode})'); + } + final List list = json.decode(response.body); + return list.map((j) => Team.fromJson(j as Map)).toList(); + } + + Future getTeamById(int id) async { + final response = await http.get(Uri.parse('$baseUrl/teams/$id')); + if (response.statusCode == HttpStatus.notFound) { + throw Exception('Team not found'); + } + if (response.statusCode != HttpStatus.ok) { + throw Exception('Failed to load team (${response.statusCode})'); + } + return Team.fromJson(json.decode(response.body) as Map); + } + + Future createTeam({ + required String name, + required String tag, + String description = '', + }) async { + final response = await http.post( + Uri.parse('$baseUrl/teams'), + headers: {'Content-Type': 'application/json'}, + body: json.encode({'name': name, 'tag': tag, 'description': description}), + ); + if (response.statusCode != HttpStatus.created) { + final body = json.decode(response.body); + throw Exception(body['error'] ?? 'Failed to create team'); + } + return Team.fromJson(json.decode(response.body) as Map); + } + + Future updateTeam( + int id, { + String? name, + String? tag, + String? description, + }) async { + final response = await http.put( + Uri.parse('$baseUrl/teams/$id'), + headers: {'Content-Type': 'application/json'}, + body: json.encode({ + 'name': ?name, + 'tag': ?tag, + 'description': ?description, + }), + ); + if (response.statusCode != HttpStatus.ok) { + final body = json.decode(response.body); + throw Exception(body['error'] ?? 'Failed to update team'); + } + } + + Future deleteTeam(int id) async { + final response = await http.delete(Uri.parse('$baseUrl/teams/$id')); + if (response.statusCode != HttpStatus.ok) { + final body = json.decode(response.body); + throw Exception(body['error'] ?? 'Failed to delete team'); + } + } + + Future> getTeamsByTournament(int tournamentId) async { + final response = await http.get( + Uri.parse('$baseUrl/tournaments/$tournamentId/teams'), + ); + if (response.statusCode != HttpStatus.ok) { + throw Exception( + 'Failed to load teams for tournament (${response.statusCode})', + ); + } + final List list = json.decode(response.body); + return list.map((j) => Team.fromJson(j as Map)).toList(); + } + + Future registerTeamForTournament(int tournamentId, int teamId) async { + final response = await http.post( + Uri.parse('$baseUrl/tournaments/$tournamentId/teams'), + headers: {'Content-Type': 'application/json'}, + body: json.encode({'teamId': teamId}), + ); + if (response.statusCode == 409) { + throw Exception('Team is already registered for this tournament'); + } + if (response.statusCode != HttpStatus.created) { + final body = json.decode(response.body); + throw Exception(body['error'] ?? 'Failed to register team'); + } + } + + Future removeTeamFromTournament(int tournamentId, int teamId) async { + final response = await http.delete( + Uri.parse('$baseUrl/tournaments/$tournamentId/teams/$teamId'), + ); + if (response.statusCode != HttpStatus.ok) { + final body = json.decode(response.body); + throw Exception(body['error'] ?? 'Failed to remove team from tournament'); + } + } + + Future>> getTournamentsByTeam(int teamId) async { + final response = await http.get( + Uri.parse('$baseUrl/teams/$teamId/tournaments'), + ); + if (response.statusCode != HttpStatus.ok) { + throw Exception( + 'Failed to load tournaments for team (${response.statusCode})', + ); + } + final List list = json.decode(response.body); + return list.cast>(); + } +}