Add service and provider for Teams

This commit is contained in:
2026-03-10 15:59:03 +01:00
parent ea45d74c0f
commit 3b8209225e
7 changed files with 295 additions and 20 deletions

View File

@@ -17,7 +17,6 @@ export class TournamentService {
name TEXT, name TEXT,
description TEXT, description TEXT,
maxTeamAmount INTEGER, maxTeamAmount INTEGER,
currentTeamAmount INTEGER,
registrationStartDate TEXT, registrationStartDate TEXT,
registrationEndDate TEXT registrationEndDate TEXT
)`); )`);
@@ -43,7 +42,7 @@ export class TournamentService {
name: row.name, name: row.name,
description: row.description, description: row.description,
maxTeamAmount: row.maxTeamAmount, maxTeamAmount: row.maxTeamAmount,
currentTeamAmount: row.currentTeamAmount, currentTeamAmount: 0,
registrationStartDate: row.registrationStartDate, registrationStartDate: row.registrationStartDate,
registrationEndDate: row.registrationEndDate, registrationEndDate: row.registrationEndDate,
teams: [], teams: [],
@@ -57,6 +56,7 @@ export class TournamentService {
description: row.teamDescription, description: row.teamDescription,
createdAt: row.teamCreatedAt, createdAt: row.teamCreatedAt,
} as Team); } as Team);
tournamentsMap.get(row.id)!.currentTeamAmount++;
} }
} }
resolve(Array.from(tournamentsMap.values())); resolve(Array.from(tournamentsMap.values()));
@@ -98,6 +98,7 @@ export class TournamentService {
description: row.teamDescription, description: row.teamDescription,
createdAt: row.teamCreatedAt, createdAt: row.teamCreatedAt,
} as Team); } as Team);
tournament.currentTeamAmount++;
} }
} }
resolve(tournament); resolve(tournament);
@@ -108,8 +109,8 @@ export class TournamentService {
addTournament(tournament: Tournament): Promise<void> { addTournament(tournament: Tournament): Promise<void> {
return new Promise<void>((resolve, reject) => { return new Promise<void>((resolve, reject) => {
const statement = this.db.prepare('Insert Into Tournaments (name, description, maxTeamAmount, currentTeamAmount, registrationStartDate, registrationEndDate) VALUES (?, ?, ?, ?, ?, ?)') const statement = this.db.prepare('Insert Into Tournaments (name, description, maxTeamAmount, registrationStartDate, registrationEndDate) VALUES (?, ?, ?, ?, ?)')
statement.run(tournament.name, tournament.description, tournament.maxTeamAmount, tournament.currentTeamAmount, tournament.registrationStartDate, tournament.registrationEndDate); statement.run(tournament.name, tournament.description, tournament.maxTeamAmount, tournament.registrationStartDate, tournament.registrationEndDate);
resolve(); resolve();
}) })
} }
@@ -120,7 +121,6 @@ export class TournamentService {
Set name = $name, Set name = $name,
description = $description, description = $description,
maxTeamAmount = $maxTeamAmount, maxTeamAmount = $maxTeamAmount,
currentTeamAmount = $currentTeamAmount,
registrationStartDate = $registrationStartDate, registrationStartDate = $registrationStartDate,
registrationEndDate = $registrationEndDate registrationEndDate = $registrationEndDate
where id = $id`, { where id = $id`, {
@@ -128,7 +128,6 @@ export class TournamentService {
$name: updatedTournament.name, $name: updatedTournament.name,
$description: updatedTournament.description, $description: updatedTournament.description,
$maxTeamAmount: updatedTournament.maxTeamAmount, $maxTeamAmount: updatedTournament.maxTeamAmount,
$currentTeamAmount: updatedTournament.currentTeamAmount,
$registrationStartDate: updatedTournament.registrationStartDate, $registrationStartDate: updatedTournament.registrationStartDate,
$registrationEndDate: updatedTournament.registrationEndDate, $registrationEndDate: updatedTournament.registrationEndDate,
}); });

View File

@@ -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/login_page.dart';
import 'package:frontend_splatournament_manager/pages/settings_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/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/theme_provider.dart';
import 'package:frontend_splatournament_manager/providers/tournament_provider.dart'; import 'package:frontend_splatournament_manager/providers/tournament_provider.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -15,6 +16,7 @@ void main() {
ChangeNotifierProvider(create: (_) => ThemeProvider()), ChangeNotifierProvider(create: (_) => ThemeProvider()),
ChangeNotifierProvider(create: (_) => TournamentProvider()), ChangeNotifierProvider(create: (_) => TournamentProvider()),
ChangeNotifierProvider(create: (_) => AuthProvider()), ChangeNotifierProvider(create: (_) => AuthProvider()),
ChangeNotifierProvider(create: (_) => TeamProvider()),
], ],
child: const SplatournamentApp(), child: const SplatournamentApp(),
), ),
@@ -22,6 +24,7 @@ void main() {
} }
class SplatournamentApp extends StatelessWidget { class SplatournamentApp extends StatelessWidget {
static const String baseUrl = "http://10.0.2.2:3000";
const SplatournamentApp({super.key}); const SplatournamentApp({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -41,12 +44,17 @@ class SplatournamentApp extends StatelessWidget {
); );
} }
} }
var routes = GoRouter( var routes = GoRouter(
initialLocation: '/login', initialLocation: '/login',
routes: [ routes: [
GoRoute(path: "/login", builder: (context, state) => const LoginPage()), GoRoute(path: "/login", builder: (context, state) => const LoginPage()),
GoRoute(path: "/", builder: (context, state) => HomePage(),routes: [ GoRoute(
GoRoute(path: "settings", builder: (context, state) => SettingsPage(),) path: "/",
]) builder: (context, state) => HomePage(),
] routes: [
GoRoute(path: "settings", builder: (context, state) => SettingsPage()),
],
),
],
); );

View File

@@ -1,5 +1,8 @@
import 'package:flutter/material.dart'; 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/models/tournament.dart';
import 'package:frontend_splatournament_manager/providers/team_provider.dart';
import 'package:provider/provider.dart';
class TournamentDetailPage extends StatefulWidget { class TournamentDetailPage extends StatefulWidget {
final Tournament tournament; final Tournament tournament;
@@ -72,15 +75,89 @@ class _TournamentDetailPageState extends State<TournamentDetailPage> {
} }
} }
class TournamentTeamsWidget extends StatelessWidget{ class TournamentTeamsWidget extends StatefulWidget {
const TournamentTeamsWidget({super.key, required Tournament tournament}); final Tournament tournament;
const TournamentTeamsWidget({super.key, required this.tournament});
@override
State<TournamentTeamsWidget> createState() => _TournamentTeamsWidgetState();
}
class _TournamentTeamsWidgetState extends State<TournamentTeamsWidget> {
late Future<List<Team>> _teamsFuture;
@override
void initState() {
super.initState();
_teamsFuture = Provider.of<TeamProvider>(context, listen: false)
.getTeamsByTournament(widget.tournament.id);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Expanded( return Expanded(
child: Column( children: [ child: Column(
//TODO: Show participating Teams crossAxisAlignment: CrossAxisAlignment.start,
Text("Teams"), children: [
],), Padding(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 4),
child: Text(
'Registered Teams',
style: TextStyle(fontWeight: FontWeight.w600, fontSize: 17),
),
),
Expanded(
child: FutureBuilder<List<Team>>(
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<TeamProvider>(context, listen: false)
.removeTeamFromTournament(widget.tournament.id, team.id);
setState(() {
_teamsFuture =
Provider.of<TeamProvider>(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')),
);
}
},
),
);
},
);
},
),
),
],
),
); );
} }

View File

@@ -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<Team> _teams = [];
List<Team> get teams => _teams;
Future<List<Team>> fetchAllTeams() async {
_teams = await _teamService.getAllTeams();
notifyListeners();
return _teams;
}
Future<Team> 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<void> updateTeam(
int id, {
String? name,
String? tag,
String? description,
}) async {
await _teamService.updateTeam(id, name: name, tag: tag, description: description);
await fetchAllTeams();
}
Future<void> deleteTeam(int id) async {
await _teamService.deleteTeam(id);
_teams = _teams.where((t) => t.id != id).toList();
notifyListeners();
}
Future<List<Team>> getTeamsByTournament(int tournamentId) async {
return _teamService.getTeamsByTournament(tournamentId);
}
Future<void> registerTeamForTournament(int tournamentId, int teamId) async {
await _teamService.registerTeamForTournament(tournamentId, teamId);
notifyListeners();
}
Future<void> removeTeamFromTournament(int tournamentId, int teamId) async {
await _teamService.removeTeamFromTournament(tournamentId, teamId);
notifyListeners();
}
}

View File

@@ -5,8 +5,10 @@ import 'package:flutter/material.dart';
import 'package:frontend_splatournament_manager/models/tournament.dart'; import 'package:frontend_splatournament_manager/models/tournament.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../main.dart';
class TournamentProvider extends ChangeNotifier { class TournamentProvider extends ChangeNotifier {
static const String baseUrl = "http://10.0.2.2:3000"; final String baseUrl = SplatournamentApp.baseUrl;
List<Tournament> _availableTournaments = []; List<Tournament> _availableTournaments = [];
Future<List<Tournament>>? _initialLoadFuture; Future<List<Tournament>>? _initialLoadFuture;

View File

@@ -1,9 +1,9 @@
import 'dart:convert'; import 'dart:convert';
import 'package:frontend_splatournament_manager/main.dart';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
class AuthService { class AuthService {
static const String baseUrl = "http://10.0.2.2:3000"; final String baseUrl = SplatournamentApp.baseUrl;
Future<Map<String, dynamic>> register(String username, String password) async { Future<Map<String, dynamic>> register(String username, String password) async {
final response = await http.post( final response = await http.post(
Uri.parse('$baseUrl/register'), Uri.parse('$baseUrl/register'),

View File

@@ -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<List<Team>> 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<dynamic> list = json.decode(response.body);
return list.map((j) => Team.fromJson(j as Map<String, dynamic>)).toList();
}
Future<Team> 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<String, dynamic>);
}
Future<Team> 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<String, dynamic>);
}
Future<void> 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<void> 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<List<Team>> 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<dynamic> list = json.decode(response.body);
return list.map((j) => Team.fromJson(j as Map<String, dynamic>)).toList();
}
Future<void> 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<void> 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<List<Map<String, dynamic>>> 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<dynamic> list = json.decode(response.body);
return list.cast<Map<String, dynamic>>();
}
}