diff --git a/TournamentOrganizer/Models/Game.cs b/TournamentOrganizer/Models/Game.cs index d7994d8..6f95d5c 100644 --- a/TournamentOrganizer/Models/Game.cs +++ b/TournamentOrganizer/Models/Game.cs @@ -12,6 +12,27 @@ public enum RuleSet Swiss } +public static class RuleSetExtensions +{ + public static string ToDisplayName(this RuleSet ruleSet) => ruleSet switch + { + RuleSet.DoubleElimination => "Double Elimination", + RuleSet.SingleElimination => "Single Elimination", + RuleSet.RoundRobin => "Round Robin", + RuleSet.Swiss => "Swiss", + _ => ruleSet.ToString() + }; + + public static RuleSet? FromDisplayName(string? displayName) => displayName switch + { + "Double Elimination" => RuleSet.DoubleElimination, + "Single Elimination" => RuleSet.SingleElimination, + "Round Robin" => RuleSet.RoundRobin, + "Swiss" => RuleSet.Swiss, + _ => null + }; +} + public class Game { [Key] diff --git a/TournamentOrganizer/ViewModels/GamesViewModel.cs b/TournamentOrganizer/ViewModels/GamesViewModel.cs new file mode 100644 index 0000000..60a057e --- /dev/null +++ b/TournamentOrganizer/ViewModels/GamesViewModel.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Threading.Tasks; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using Microsoft.EntityFrameworkCore; +using TournamentOrganizer.Models; + +namespace TournamentOrganizer.ViewModels; + +public partial class GamesViewModel : ViewModelBase +{ + private readonly TournamentContext _context; + + [ObservableProperty] + private ObservableCollection _games = []; + + [ObservableProperty] + private GameDisplay? _selectedGame; + + [ObservableProperty] + private string _gameName = string.Empty; + + [ObservableProperty] + private string _gameDescription = string.Empty; + + [ObservableProperty] + private RuleSetOption? _selectedS1RuleSet; + + [ObservableProperty] + private int? _s1Groups; + + [ObservableProperty] + private int? _s1GroupAdvances; + + [ObservableProperty] + private RuleSetOption? _selectedS2RuleSet; + + [ObservableProperty] + private string _filterGameName = string.Empty; + + [ObservableProperty] + private bool _isEditing; + + [ObservableProperty] + private string _statusMessage = string.Empty; + + public ObservableCollection S1RuleSetOptions { get; } = []; + + public ObservableCollection S2RuleSetOptions { get; } = [new RuleSetOption(null, "(None)")]; + + public bool ShowStage1GroupSettings => SelectedS2RuleSet?.Value != null; + + public GamesViewModel() + { + _context = new TournamentContext(); + foreach (var rs in Enum.GetValues()) + { + S1RuleSetOptions.Add(new RuleSetOption(rs, rs.ToDisplayName())); + S2RuleSetOptions.Add(new RuleSetOption(rs, rs.ToDisplayName())); + } + SelectedS1RuleSet = S1RuleSetOptions.First(); + SelectedS2RuleSet = S2RuleSetOptions.First(); + } + + partial void OnSelectedS2RuleSetChanged(RuleSetOption? value) + { + OnPropertyChanged(nameof(ShowStage1GroupSettings)); + if (value?.Value == null) + { + S1Groups = null; + S1GroupAdvances = null; + } + } + + partial void OnFilterGameNameChanged(string value) => ApplyFilters(); + + private async void ApplyFilters() + { + await LoadGames(); + } + + public async Task LoadGames() + { + var allGames = await _context.Games + .Include(g => g.Tournaments) + .ThenInclude(t => t.Event) + .ToListAsync(); + + var filtered = allGames.AsEnumerable(); + + if (!string.IsNullOrWhiteSpace(FilterGameName)) + { + var filter = FilterGameName.ToLower(); + filtered = filtered.Where(g => g.Name.ToLower().Contains(filter)); + } + + Games.Clear(); + foreach (var game in filtered) + { + Games.Add(new GameDisplay(game)); + } + + SelectedGame = null; + IsEditing = false; + } + + [RelayCommand] + private async Task RefreshGames() + { + await LoadGames(); + } + + [RelayCommand] + private void CreateNewGame() + { + GameName = "New Game"; + GameDescription = string.Empty; + SelectedS1RuleSet = S1RuleSetOptions.FirstOrDefault(o => o.Value == RuleSet.SingleElimination); + S1Groups = null; + S1GroupAdvances = null; + SelectedS2RuleSet = S2RuleSetOptions.First(); + IsEditing = true; + SelectedGame = null; + StatusMessage = "Creating new game"; + } + + [RelayCommand] + private async Task SaveGame() + { + if (string.IsNullOrWhiteSpace(GameName)) + { + StatusMessage = "Game name is required"; + return; + } + + Game? game; + if (SelectedGame != null && SelectedGame.Id > 0) + { + game = await _context.Games.FirstOrDefaultAsync(g => g.Id == SelectedGame.Id); + + if (game == null) + { + StatusMessage = "Game not found"; + return; + } + + game.Name = GameName; + game.Description = GameDescription; + game.S1RuleSet = SelectedS1RuleSet!.Value!.Value; + game.S1Groups = ShowStage1GroupSettings ? S1Groups : null; + game.S1GroupAdvances = ShowStage1GroupSettings ? S1GroupAdvances : null; + game.S2RuleSet = SelectedS2RuleSet?.Value; + } + else + { + game = new Game + { + Name = GameName, + Description = GameDescription, + S1RuleSet = SelectedS1RuleSet!.Value!.Value, + S1Groups = ShowStage1GroupSettings ? S1Groups : null, + S1GroupAdvances = ShowStage1GroupSettings ? S1GroupAdvances : null, + S2RuleSet = SelectedS2RuleSet?.Value, + Tournaments = [] + }; + + _context.Games.Add(game); + } + + await _context.SaveChangesAsync(); + StatusMessage = $"Game '{GameName}' saved successfully"; + await LoadGames(); + } + + [RelayCommand] + private async Task DeleteGame() + { + if (SelectedGame == null || SelectedGame.Id == 0) + { + StatusMessage = "Select a game to delete"; + return; + } + + var game = await _context.Games.FirstOrDefaultAsync(g => g.Id == SelectedGame.Id); + + if (game == null) + { + StatusMessage = "Game not found"; + return; + } + + _context.Games.Remove(game); + await _context.SaveChangesAsync(); + StatusMessage = $"Game '{game.Name}' deleted"; + await LoadGames(); + } + + partial void OnSelectedGameChanged(GameDisplay? value) + { + if (value == null) + { + IsEditing = false; + return; + } + + GameName = value.Name; + GameDescription = value.Description; + SelectedS1RuleSet = S1RuleSetOptions.FirstOrDefault(o => o.Value == value.S1RuleSet) ?? S1RuleSetOptions.First(); + S1Groups = value.S1Groups; + S1GroupAdvances = value.S1GroupAdvances; + SelectedS2RuleSet = S2RuleSetOptions.FirstOrDefault(o => o.Value == value.S2RuleSet) ?? S2RuleSetOptions.First(); + IsEditing = true; + StatusMessage = $"Editing game '{value.Name}'"; + } +} + +public class GameDisplay +{ + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + public string Description { get; set; } = string.Empty; + public RuleSet S1RuleSet { get; set; } + public string S1RuleSetName => S1RuleSet.ToDisplayName(); + public int? S1Groups { get; set; } + public int? S1GroupAdvances { get; set; } + public RuleSet? S2RuleSet { get; set; } + public string? S2RuleSetName => S2RuleSet?.ToDisplayName(); + public List AssociatedEvents { get; set; } = []; + + public GameDisplay() { } + + public GameDisplay(Game game) + { + Id = game.Id; + Name = game.Name; + Description = game.Description; + S1RuleSet = game.S1RuleSet; + S1Groups = game.S1Groups; + S1GroupAdvances = game.S1GroupAdvances; + S2RuleSet = game.S2RuleSet; + + var events = new HashSet(); + if (game.Tournaments != null) + { + foreach (var t in game.Tournaments) + { + if (t.Event != null) + { + events.Add(t.Event.Name); + } + } + } + + AssociatedEvents = events.ToList(); + } +} + +public class RuleSetOption +{ + public RuleSet? Value { get; set; } + public string Label { get; set; } = string.Empty; + + public RuleSetOption() { } + + public RuleSetOption(RuleSet? value, string label) + { + Value = value; + Label = label; + } + + public override string ToString() => Label; +} diff --git a/TournamentOrganizer/Views/GamesView.axaml b/TournamentOrganizer/Views/GamesView.axaml new file mode 100644 index 0000000..e176d9f --- /dev/null +++ b/TournamentOrganizer/Views/GamesView.axaml @@ -0,0 +1,137 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +