From e5a47d54f5aa3eafa64d630802aeaca0429f72a2 Mon Sep 17 00:00:00 2001 From: tikaiz Date: Tue, 10 Mar 2026 16:41:43 +0100 Subject: [PATCH] Add Tournament Creation page --- .../lib/pages/create_tournament_page.dart | 169 ++++++++++++++++++ .../lib/pages/home_page.dart | 18 +- .../lib/pages/tournament_detail_page.dart | 41 +++-- .../lib/providers/tournament_provider.dart | 33 +++- frontend_splatournament_manager/pubspec.yaml | 1 + 5 files changed, 244 insertions(+), 18 deletions(-) create mode 100644 frontend_splatournament_manager/lib/pages/create_tournament_page.dart diff --git a/frontend_splatournament_manager/lib/pages/create_tournament_page.dart b/frontend_splatournament_manager/lib/pages/create_tournament_page.dart new file mode 100644 index 0000000..18fc771 --- /dev/null +++ b/frontend_splatournament_manager/lib/pages/create_tournament_page.dart @@ -0,0 +1,169 @@ +import 'package:flutter/material.dart'; +import 'package:frontend_splatournament_manager/providers/tournament_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:intl/intl.dart'; + +class CreateTournamentPage extends StatefulWidget { + const CreateTournamentPage({super.key}); + + @override + State createState() => _CreateTournamentPageState(); +} + +class _CreateTournamentPageState extends State { + final _formKey = GlobalKey(); + final _nameController = TextEditingController(); + final _descriptionController = TextEditingController(); + final _maxTeamAmountController = TextEditingController(); + + DateTime? _startDate; + DateTime? _endDate; + + bool _isLoading = false; + + final DateFormat _dateFormat = DateFormat('yyyy-MM-dd'); + + Future _selectDate(BuildContext context, bool isStart) async { + final initialDate = DateTime.now(); + final picked = await showDatePicker( + context: context, + initialDate: initialDate, + firstDate: initialDate, + lastDate: DateTime(2101), + ); + if (picked != null) { + setState(() { + if (isStart) { + _startDate = picked; + } else { + _endDate = picked; + } + }); + } + } + + void _submitForm() async { + if (!_formKey.currentState!.validate()) return; + if (_startDate == null || _endDate == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('Please select both start and end dates.')), + ); + return; + } + + if (_endDate!.isBefore(_startDate!)) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text('End date cannot be before start date.')), + ); + return; + } + + setState(() => _isLoading = true); + + try { + final provider = Provider.of(context, listen: false); + await provider.createTournament( + _nameController.text, + _descriptionController.text, + int.parse(_maxTeamAmountController.text), + _startDate!, + _endDate!, + ); + if (!context.mounted) return; + Navigator.pop(context); + } catch (e) { + if (!context.mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text('Error: $e'))); + } finally { + if (context.mounted) setState(() => _isLoading = false); + } + } + + @override + void dispose() { + _nameController.dispose(); + _descriptionController.dispose(); + _maxTeamAmountController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('Create Tournament')), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Form( + key: _formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + TextFormField( + controller: _nameController, + decoration: const InputDecoration(labelText: 'Name'), + validator: (value) => + value == null || value.isEmpty ? 'Required' : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: _descriptionController, + decoration: const InputDecoration(labelText: 'Description'), + maxLines: 3, + validator: (value) => + value == null || value.isEmpty ? 'Required' : null, + ), + const SizedBox(height: 16), + TextFormField( + controller: _maxTeamAmountController, + decoration: const InputDecoration(labelText: 'Max Teams'), + keyboardType: TextInputType.number, + validator: (value) { + if (value == null || value.isEmpty) return 'Required'; + if (int.tryParse(value) == null || int.parse(value) <= 0) { + return 'Must be a positive integer'; + } + return null; + }, + ), + const SizedBox(height: 24), + Row( + children: [ + Expanded( + child: OutlinedButton( + onPressed: () => _selectDate(context, true), + child: Text( + _startDate == null + ? 'Select Start Date' + : 'Start: ${_dateFormat.format(_startDate!)}', + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: OutlinedButton( + onPressed: () => _selectDate(context, false), + child: Text( + _endDate == null + ? 'Select End Date' + : 'End: ${_dateFormat.format(_endDate!)}', + ), + ), + ), + ], + ), + const SizedBox(height: 32), + ElevatedButton( + onPressed: _isLoading ? null : _submitForm, + child: _isLoading + ? const CircularProgressIndicator() + : const Text('Create Tournament'), + ), + ], + ), + ), + ), + ); + } +} diff --git a/frontend_splatournament_manager/lib/pages/home_page.dart b/frontend_splatournament_manager/lib/pages/home_page.dart index b0f7113..2bee6b2 100644 --- a/frontend_splatournament_manager/lib/pages/home_page.dart +++ b/frontend_splatournament_manager/lib/pages/home_page.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:frontend_splatournament_manager/providers/tournament_provider.dart'; import 'package:frontend_splatournament_manager/widgets/available_tournament_list.dart'; +import 'package:frontend_splatournament_manager/pages/create_tournament_page.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; @@ -15,8 +16,10 @@ class HomePage extends StatelessWidget { actions: [ IconButton( onPressed: () async { - final tournamentProvider = - Provider.of(context, listen: false); + final tournamentProvider = Provider.of( + context, + listen: false, + ); try { await tournamentProvider.refreshAvailableTournaments(); } catch (_) { @@ -43,6 +46,17 @@ class HomePage extends StatelessWidget { padding: EdgeInsets.fromLTRB(0, 12, 0, 24), child: Column(children: [Spacer(), AvailableTournamentList()]), ), + floatingActionButton: FloatingActionButton( + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const CreateTournamentPage(), + ), + ); + }, + child: const Icon(Icons.add), + ), ); } } diff --git a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart index bae228f..310a5db 100644 --- a/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart +++ b/frontend_splatournament_manager/lib/pages/tournament_detail_page.dart @@ -62,9 +62,9 @@ class _TournamentDetailPageState extends State { onPressed: () { //TODO: Backend Call ScaffoldMessenger.of(context).clearSnackBars(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text("tournament entered")), - ); + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text("tournament entered"))); }, ), ), @@ -90,8 +90,10 @@ class _TournamentTeamsWidgetState extends State { @override void initState() { super.initState(); - _teamsFuture = Provider.of(context, listen: false) - .getTeamsByTournament(widget.tournament.id); + _teamsFuture = Provider.of( + context, + listen: false, + ).getTeamsByTournament(widget.tournament.id); } @override @@ -132,20 +134,31 @@ class _TournamentTeamsWidgetState extends State { ? Text(team.description) : null, trailing: IconButton( - icon: Icon(Icons.remove_circle_outline, color: Colors.red), + icon: Icon( + Icons.remove_circle_outline, + color: Colors.red, + ), onPressed: () async { try { - await Provider.of(context, listen: false) - .removeTeamFromTournament(widget.tournament.id, team.id); + await Provider.of( + context, + listen: false, + ).removeTeamFromTournament( + widget.tournament.id, + team.id, + ); setState(() { - _teamsFuture = - Provider.of(context, listen: false) - .getTeamsByTournament(widget.tournament.id); + _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')), + SnackBar( + content: Text('Failed to remove team: $e'), + ), ); } }, @@ -160,7 +173,6 @@ class _TournamentTeamsWidgetState extends State { ), ); } - } class TournamentContentWidget extends StatelessWidget { @@ -205,8 +217,7 @@ class TournamentContentWidget extends StatelessWidget { fontSize: 17, ), ), - //TODO: Should show the format instead - Text(tournament.description), + Text("Single Elimination"), Spacer(), SizedBox( width: double.infinity, diff --git a/frontend_splatournament_manager/lib/providers/tournament_provider.dart b/frontend_splatournament_manager/lib/providers/tournament_provider.dart index 0041121..14d4cbc 100644 --- a/frontend_splatournament_manager/lib/providers/tournament_provider.dart +++ b/frontend_splatournament_manager/lib/providers/tournament_provider.dart @@ -40,4 +40,35 @@ class TournamentProvider extends ChangeNotifier { _initialLoadFuture = fetchAvailableTournaments(); return _initialLoadFuture!; } -} \ No newline at end of file + + Future createTournament( + String name, + String description, + int maxTeamAmount, + DateTime registrationStartDate, + DateTime registrationEndDate, + ) async { + final response = await http.post( + Uri.parse('$baseUrl/tournaments'), + headers: {'Content-Type': 'application/json'}, + body: jsonEncode({ + 'name': name, + 'description': description, + 'maxTeamAmount': maxTeamAmount, + //weird date formatting + 'registrationStartDate': registrationStartDate.toIso8601String().split( + 'T', + )[0], + 'registrationEndDate': registrationEndDate.toIso8601String().split( + 'T', + )[0], + }), + ); + + if (response.statusCode != HttpStatus.created) { + throw Exception('Failed to create tournament (${response.statusCode})'); + } + + await refreshAvailableTournaments(); + } +} diff --git a/frontend_splatournament_manager/pubspec.yaml b/frontend_splatournament_manager/pubspec.yaml index aef18ab..b1d6df1 100644 --- a/frontend_splatournament_manager/pubspec.yaml +++ b/frontend_splatournament_manager/pubspec.yaml @@ -37,6 +37,7 @@ dependencies: http: ^1.6.0 provider: ^6.1.5+1 go_router: ^17.1.0 + intl: ^0.20.2 dev_dependencies: flutter_test: