diff --git a/src/iRLeagueManager.Web/Components/ConfirmModal.razor b/src/iRLeagueManager.Web/Components/ConfirmModal.razor index 4594facb..1ea24b49 100644 --- a/src/iRLeagueManager.Web/Components/ConfirmModal.razor +++ b/src/iRLeagueManager.Web/Components/ConfirmModal.razor @@ -6,23 +6,23 @@ @switch (ButtonTypes) { case ButtonTypes.Ok: -
+
-
+
break; case ButtonTypes.OkCancel: -
+
break; case ButtonTypes.YesNo: -
+
-
+
break; diff --git a/src/iRLeagueManager.Web/Components/EditEventModal.razor b/src/iRLeagueManager.Web/Components/EditEventModal.razor index a1c04bee..a5855fd1 100644 --- a/src/iRLeagueManager.Web/Components/EditEventModal.razor +++ b/src/iRLeagueManager.Web/Components/EditEventModal.razor @@ -9,7 +9,7 @@ @inject TrackListService trackListService - +
@@ -97,20 +97,19 @@
- - @config?.Name + @GetReferencedResultConfigName(config) - @config.Name + @GetReferencedResultConfigName(config) @name not found! @@ -118,6 +117,7 @@
+
@@ -196,9 +196,19 @@ private async Task> SearchResultConfigs(string name) { return await Task.FromResult(ResultConfigs + .Where(x => Event.ResultConfigs.Any(y => y.ResultConfigId == x.ResultConfigId) == false) .Where(x => x.Name.Contains(name, StringComparison.OrdinalIgnoreCase) || x.DisplayName.Contains(name, StringComparison.OrdinalIgnoreCase))); } + private string GetReferencedResultConfigName(ResultConfigInfoModel config) + { + if (string.IsNullOrEmpty(config.ChampionshipName)) + { + return config.Name; + } + return $"{config.ChampionshipName} - {config.Name}"; + } + private async Task Submit() { //var status = await Event.SaveChangesAsync(cts.Token); diff --git a/src/iRLeagueManager.Web/Components/EditModalBase.razor.cs b/src/iRLeagueManager.Web/Components/EditModalBase.razor.cs index 2d6c8c39..93c689cf 100644 --- a/src/iRLeagueManager.Web/Components/EditModalBase.razor.cs +++ b/src/iRLeagueManager.Web/Components/EditModalBase.razor.cs @@ -10,7 +10,7 @@ namespace iRLeagueManager.Web.Components; -public class EditModalBase : MvvmComponentBase where TViewModel : LeagueViewModelBase +public class EditModalBase : MvvmComponentBase where TViewModel : LeagueViewModelBase where TModel : class { [Inject] protected TViewModel Vm { get; set; } = default!; diff --git a/src/iRLeagueManager.Web/Components/Settings/ChampSeasonPreview.razor b/src/iRLeagueManager.Web/Components/Settings/ChampSeasonPreview.razor new file mode 100644 index 00000000..7c0c5c92 --- /dev/null +++ b/src/iRLeagueManager.Web/Components/Settings/ChampSeasonPreview.razor @@ -0,0 +1,38 @@ +@using iRLeagueManager.Web.ViewModels + +
+
+
@ChampSeason.ChampionshipName
+
+
+
ResultConfigs
+
+ @foreach(var config in ChampSeason.ResultConfigs) + { +
+ @config.Name +
+ } +
+
+
+
Standing Config
+
+ @ChampSeason.StandingConfig?.Name +
+
+
+ + +@code { + [Parameter(CaptureUnmatchedValues=true)] + public IDictionary? AdditionalAttributes { get; set; } + + [Parameter, EditorRequired] + public ChampSeasonViewModel ChampSeason { get; set; } = default!; + + protected override void OnParametersSet() + { + _ = ChampSeason ?? throw BlazorParameterNullException.New(this, ChampSeason); + } +} diff --git a/src/iRLeagueManager.Web/Components/Settings/ChampionshipPreview.razor b/src/iRLeagueManager.Web/Components/Settings/ChampionshipPreview.razor new file mode 100644 index 00000000..5c072bee --- /dev/null +++ b/src/iRLeagueManager.Web/Components/Settings/ChampionshipPreview.razor @@ -0,0 +1,52 @@ +@using iRLeagueManager.Web.ViewModels + +
+ @{ + var name = ChampSeason?.ChampionshipName ?? Championship.Name; + var displayName = ChampSeason?.ChampionshipDisplayName ?? Championship.DisplayName; + } +
+
@name
+ @if (string.IsNullOrEmpty(displayName) == false && displayName != name) + { +    => "@displayName" + } +
+ @if (ChampSeason is not null) + { +
+
+
Result Configurations
+
    + @foreach(var config in ChampSeason.ResultConfigs) + { +
  • @config.Name
  • + } +
+
+
+
Standings
+
    +
  • Count @ChampSeason.StandingConfig?.WeeksCounted weeks
  • +
+
+
+ } +
+ + +@code { + [Parameter(CaptureUnmatchedValues=true)] + public IDictionary? AdditionalAttributes { get; set; } + + [Parameter, EditorRequired] + public ChampionshipViewModel Championship { get; set; } = default!; + + [Parameter, EditorRequired] + public ChampSeasonViewModel? ChampSeason { get; set; } + + protected override void OnParametersSet() + { + _ = Championship ?? throw BlazorParameterNullException.New(this, Championship); + } +} diff --git a/src/iRLeagueManager.Web/Components/Settings/EditChampSeasonModal.razor b/src/iRLeagueManager.Web/Components/Settings/EditChampSeasonModal.razor new file mode 100644 index 00000000..08f6af50 --- /dev/null +++ b/src/iRLeagueManager.Web/Components/Settings/EditChampSeasonModal.razor @@ -0,0 +1,126 @@ +@using iRLeagueApiCore.Common.Models; +@inherits EditModalBase + + + +
+
+

+ +

+
+ + + + + + +
+
+
+

+ +

+
+ @foreach(var resultConfig in Vm.ResultConfigViewModels) + { +
OnResultConfigClick(resultConfig))> + +
+ + +
+
+ } +
+ +
+
+
+
+

+ +

+
+ @if (Vm.StandingConfig is not null) + { +
+
+ +
+ +
+ + + + } +
+
+
+ + +
+ +@code { + private IEnumerable ResultConfigs { get; set; } = Array.Empty(); + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + if (firstRender == false) return; + + await Vm.LoadResultConfigs(Cts.Token); + await InvokeAsync(StateHasChanged); + } + + private async Task OnResultConfigClick(ResultConfigViewModel config) + { + var parameters = new ModalParameters() + .Add(x => x.Model, config.CopyModel()) + .Add(x => x.OnSubmit, (configVm, cancellationToken) => configVm.SaveChangesAsync(cancellationToken)); + var result = await ModalService.Show("Edit result config", parameters).Result; + if (result.Confirmed && result.Data is ResultConfigModel model) + { + config.SetModel(model); + } + } + + private async Task OnAddResultConfigClick() + { + var parameters = new ModalParameters() + .Add(x => x.Model, CreateResultConfig()) + .Add(x => x.OnSubmit, (x, c) => Vm.AddResultConfig(x.GetModel(), c)); + await ModalService.Show("Add result config", parameters).Result; + } + + private async Task OnDeleteResultConfigClick(ResultConfigViewModel config) + { + var parameters = new ModalParameters() + .Add(x => x.Text, $"Really delete result config \"{config.Name}\"?") + .Add(x => x.ButtonTypes, ButtonTypes.YesNo); + var result = await ModalService.Show("Delete Result Config", parameters).Result; + if (result.Confirmed) + { + await Vm.DeleteResultConfig(config.GetModel()); + } + } + + private ResultConfigModel CreateResultConfig() + { + return new ResultConfigModel(); + } +} diff --git a/src/iRLeagueManager.Web/Components/Settings/EditResultConfigModal.razor b/src/iRLeagueManager.Web/Components/Settings/EditResultConfigModal.razor index 8eb9a0d6..01163145 100644 --- a/src/iRLeagueManager.Web/Components/Settings/EditResultConfigModal.razor +++ b/src/iRLeagueManager.Web/Components/Settings/EditResultConfigModal.razor @@ -13,13 +13,10 @@ General Settings -
- +
+ - - - @@ -36,7 +33,10 @@ @foreach(var config in @Bind(Vm, x => x.AvailableResultConfigs).Where(CanSelectAsSourceConfig)) { - + } @@ -55,7 +55,7 @@ Filters -
+
-
+
@foreach(var scoring in @Bind(Vm, x => x.Scorings).Where(x => x.IsCombinedResult == false)) {
@@ -113,7 +113,7 @@ Combined Result -
+
@@ -133,37 +133,6 @@ }
- -
-

- -

-
-
-
- -
- -
- @if (Vm.CalculateStandings && Vm.StandingConfig is not null) - { - @if (Vm.CalculateCombinedResult) - { -
-
- -
- -
- } - - - - } -
-
@@ -214,8 +183,9 @@ private bool CanSelectAsSourceConfig(ResultConfigModel model) { - bool isSelf = model.ResultConfigId == Vm.ResultConfigId; - bool isDepending = model.SourceResultConfig != null && model.SourceResultConfig.ResultConfigId == Vm.ResultConfigId; - return isSelf == false && isDepending == false; + bool isNotSelf = model.ResultConfigId != Vm.ResultConfigId; + bool isNotDepending = model.SourceResultConfig?.ResultConfigId != Vm.ResultConfigId; + bool isNotOnSameChampionship = model.ChampSeasonId != Vm.ChampSeasonId; + return isNotSelf && isNotDepending && isNotOnSameChampionship; } } diff --git a/src/iRLeagueManager.Web/Components/Settings/EditScoring.razor b/src/iRLeagueManager.Web/Components/Settings/EditScoring.razor index f9730840..f5d925f1 100644 --- a/src/iRLeagueManager.Web/Components/Settings/EditScoring.razor +++ b/src/iRLeagueManager.Web/Components/Settings/EditScoring.razor @@ -22,35 +22,7 @@ MaxPoints: @pointRule.MaxPoints DropOff p.Pl: @pointRule.PointDropOff } - @if (@Bind(pointRule, x => x.PointsPerPlace).Count() > 0) - { -
- - - - - @foreach ((var point, var position) in @Bind(pointRule, x => x.PointsPerPlace).Select((x, i) => (x, i + 1))) - { - - } - - - - - - @foreach (var point in @Bind(pointRule, x => x.PointsPerPlace)) - { - - } - - -
Pos.@position.
Pts.@point
-
- } - else - { - Points from source - } + diff --git a/src/iRLeagueManager.Web/Components/Settings/EditVoteCategoryModal.razor b/src/iRLeagueManager.Web/Components/Settings/EditVoteCategoryModal.razor index a287277d..a93fcc11 100644 --- a/src/iRLeagueManager.Web/Components/Settings/EditVoteCategoryModal.razor +++ b/src/iRLeagueManager.Web/Components/Settings/EditVoteCategoryModal.razor @@ -11,11 +11,11 @@ + Vm.DefaultPenalty) />
- Vm.DefaultPenalty) /> diff --git a/src/iRLeagueManager.Web/Components/Settings/PointTable.razor b/src/iRLeagueManager.Web/Components/Settings/PointTable.razor new file mode 100644 index 00000000..b9ca5024 --- /dev/null +++ b/src/iRLeagueManager.Web/Components/Settings/PointTable.razor @@ -0,0 +1,44 @@ +@inherits MvvmComponentBase + +
+ @if (@Bind(PointRule, x => x.PointsPerPlace).Count() > 0) + { + + + + + @foreach ((var point, var position) in @Bind(PointRule, x => x.PointsPerPlace).Select((x, i) => (x, i + 1))) + { + + } + + + + + + @foreach (var point in @Bind(PointRule, x => x.PointsPerPlace)) + { + + } + + +
Pos.@position.
Pts.@point
+ } + else + { + Points from source + } +
+ +@code { + [Parameter(CaptureUnmatchedValues = true)] + public IReadOnlyDictionary? AdditionalAttributes { get; set; } + [Parameter] + public PointRuleViewModel PointRule { get; set; } = default!; + + protected override void OnParametersSet() + { + base.OnParametersSet(); + BlazorParameterNullException.ThrowIfNull(this, PointRule); + } +} diff --git a/src/iRLeagueManager.Web/Components/Settings/ResultConfigPreview.razor b/src/iRLeagueManager.Web/Components/Settings/ResultConfigPreview.razor index 96042337..5e3f7887 100644 --- a/src/iRLeagueManager.Web/Components/Settings/ResultConfigPreview.razor +++ b/src/iRLeagueManager.Web/Components/Settings/ResultConfigPreview.razor @@ -1,15 +1,27 @@ -@using iRLeagueManager.Web.ViewModels +@using iRLeagueApiCore.Common.Models; +@using iRLeagueManager.Web.ViewModels
@ResultConfig.Name
+ @if (ResultConfig.SourceResultConfig is not null) + { + Use results from => @GetReferencedResultConfigName(ResultConfig.SourceResultConfig) + } +
+ +
+ @foreach (var scoring in ResultConfig.Scorings) + { +
+
@scoring.Name
+
+ + Sort results by: @string.Join(" then ", scoring.PointRule.FinalSortOptions) +
+
+ }
-
Display Name
- @ResultConfig.DisplayName - @foreach(var scoring in ResultConfig.Scorings) - { - - }
@@ -20,8 +32,20 @@ [Parameter, EditorRequired] public ResultConfigViewModel ResultConfig { get; set; } = default!; + [Parameter] + public bool Collapsed { get; set; } = false; + protected override void OnParametersSet() { _ = ResultConfig ?? throw BlazorParameterNullException.New(this, ResultConfig); } + + private string GetReferencedResultConfigName(ResultConfigInfoModel config) + { + if (string.IsNullOrEmpty(config.ChampionshipName)) + { + return config.Name; + } + return $"{config.ChampionshipName} - {config.Name}"; + } } diff --git a/src/iRLeagueManager.Web/Components/Settings/ResultSettings.razor b/src/iRLeagueManager.Web/Components/Settings/ResultSettings.razor index 106af646..930a54c3 100644 --- a/src/iRLeagueManager.Web/Components/Settings/ResultSettings.razor +++ b/src/iRLeagueManager.Web/Components/Settings/ResultSettings.razor @@ -2,28 +2,50 @@ @using iRLeagueApiCore.Common.Models @using iRLeagueManager.Web.ViewModels @inherits MvvmComponentBase -@inject ResultConfigSettingsViewModel vm +@inject ResultSettingsViewModel vm @inject LeagueApiService apiService +@inject SharedStateService sharedState @inject ILogger logger
+
+ + +
- Result Configurations + Championships
- @foreach((var config, var index) in @Bind(vm, x => x.ResultsConfigs).Select((x, i) => (x, i))) + @foreach ((var championship, var index) in @Bind(vm, x => x.Championships).OrderByDescending(x => x.IsActive).Select((x, i) => (x, i))) { -
OnResultConfigClick(config))> - - + var champSeason = vm.CurrentChampSeasons.FirstOrDefault(x => x.ChampionshipId == championship.ChampionshipId); + var disabled = champSeason == null; +
{ if (champSeason is not null) await OnChampionshipClick(champSeason); })> + +
+ + +
} +
+ +
-
@code { @@ -33,6 +55,12 @@ [Parameter(CaptureUnmatchedValues = true)] public IReadOnlyDictionary? AdditionalAttributes { get; set; } + private long SeasonSelect + { + get => sharedState.SeasonId; + set => _ = OnSeasonSelectChanged(value); + } + private async Task HandleSubmit() { logger.LogInformation("Handle submit called!"); @@ -61,29 +89,53 @@ } } - private async Task OnDeleteResultConfigClick(ResultConfigViewModel config) + private async Task OnChampionshipClick(ChampSeasonViewModel champSeason) + { + var parameters = new ModalParameters() + .Add(x => x.Model, champSeason.CopyModel()) + .Add(x => x.OnSubmit, new((x, cancellation) => x.SaveChangesAsync(cancellation))); + var result = await ModalService.Show("Edit Championship", parameters).Result; + if (result.Confirmed && result.Data is ChampSeasonModel model) + { + champSeason.SetModel(model); + } + } + + private async Task OnDeleteChampionshipClick(ChampionshipViewModel championship) { var parameters = new ModalParameters() - .Add(x => x.Text, $"Really delete result config \"{config.Name}\"?") + .Add(x => x.Text, $"Really delete championship {championship.Name}? This will also remove all settings from previous seasons but it will still keep the results.") .Add(x => x.ButtonTypes, ButtonTypes.YesNo); - var result = await ModalService.Show("Delete Result Config", parameters).Result; + var result = await ModalService.Show("Delete Championship", parameters).Result; if (result.Confirmed) { - await vm.DeleteConfiguration(config); + await vm.DeleteChampionship(championship.GetModel()); } } - private async Task OnAddConfigurationClick() + private async Task OnAddChampionshipClick() { - var parameters = new ModalParameters() - .Add(x => x.Model, CreateConfigFromTemplate()) - .Add(x => x.OnSubmit, new(async (config, ct) => await vm.AddConfiguration(config.GetModel()))); - var options = new ModalOptions() + var parameters = new ModalParameters() + .Add(x => x.Model, CreateChampionshipFromTemplate()) + .Add(x => x.OnSubmit, (x, c) => vm.AddChampionship(x.GetModel(), c)); + var result = await ModalService.Show("Create Championship", parameters).Result; + if (result.Confirmed) { - DisableBackgroundCancel = true, - Size = ModalSize.Large, - }; - var result = await ModalService.Show("Add Config", parameters, options).Result; + await vm.LoadFromCurrentSeasonAsync(); + } + } + + private async Task ToggleChampionshipActive(ChampionshipViewModel championship) + { + if (championship.IsActive) + { + await championship.DeactivateForCurrentSeasonAsync(); + } + else + { + await championship.ActivateForSeasonAsync(); + } + await vm.LoadFromCurrentSeasonAsync(); } private ResultConfigModel CreateConfigFromTemplate() @@ -104,23 +156,37 @@ } } }, - StandingConfig = new() - { - WeeksCounted = 8, - }, }; } - protected override async Task OnAfterRenderAsync(bool firstRender) + private ChampSeasonModel CreateChampionshipFromTemplate() { - if (firstRender == false) + return new() + { + ChampionshipName = "", + ChampionshipDisplayName = "", + StandingConfig = new(), + }; + } + + private async Task OnSeasonSelectChanged(long value) + { + if (sharedState.LeagueName == null || value == 0) { return; } - if (apiService.CurrentLeague != null) + await apiService.SetCurrentSeasonAsync(sharedState.LeagueName, value); + await vm.LoadFromCurrentSeasonAsync(); + await InvokeAsync(StateHasChanged); + } + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (firstRender == false) { - await vm.LoadFromLeagueAsync(); + return; } + await vm.LoadFromCurrentSeasonAsync(); await InvokeAsync(StateHasChanged); } } diff --git a/src/iRLeagueManager.Web/Components/Settings/ResultSettings.razor.css b/src/iRLeagueManager.Web/Components/Settings/ResultSettings.razor.css deleted file mode 100644 index 709471e6..00000000 --- a/src/iRLeagueManager.Web/Components/Settings/ResultSettings.razor.css +++ /dev/null @@ -1,3 +0,0 @@ -.accordion-button-plain::after { - content: none; -} \ No newline at end of file diff --git a/src/iRLeagueManager.Web/Data/ModelHelper.cs b/src/iRLeagueManager.Web/Data/ModelHelper.cs new file mode 100644 index 00000000..7d620583 --- /dev/null +++ b/src/iRLeagueManager.Web/Data/ModelHelper.cs @@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.Json; + +namespace iRLeagueManager.Web.Data; + +internal static class ModelHelper +{ + public static T? CopyModel(T? model) where T : class + { + if (model is null) + { + return default; + } + + return JsonSerializer.Deserialize(JsonSerializer.Serialize(model)) + ?? throw new InvalidOperationException("Could not copy model"); + } +} diff --git a/src/iRLeagueManager.Web/Pages/Seasons.razor b/src/iRLeagueManager.Web/Pages/Seasons.razor index 82bbe43c..7042bda7 100644 --- a/src/iRLeagueManager.Web/Pages/Seasons.razor +++ b/src/iRLeagueManager.Web/Pages/Seasons.razor @@ -2,7 +2,7 @@ @page "/{LeagueName}/Seasons/{SeasonId:long}" @using iRLeagueManager.Web.Components @using iRLeagueManager.Web.ViewModels -@inherits MvvmComponentBase +@inherits LeagueComponentBase @inject NavigationManager navigationManager @inject LeagueApiService apiService @inject SeasonsViewModel vm @@ -18,40 +18,4 @@
@code { - [Parameter] - public string LeagueName { get; set; } = string.Empty; - - [Parameter] - public long? SeasonId { get; set; } - - protected override async Task OnParametersSetAsync() - { - await apiService.SetCurrentLeagueAsync(LeagueName); - if (SeasonId != null) - { - await apiService.SetCurrentSeasonAsync(LeagueName, SeasonId.Value); - return; - } - - // navigate to first season - if (apiService.CurrentLeague == null) - { - return; - } - var result = await apiService.CurrentLeague.Seasons().Get(); - if (result.Success && result.Content is not null) - { - var seasons = result.Content; - navigationManager.NavigateTo($"./{LeagueName}/Seasons/{seasons.Last().SeasonId}"); - } - } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await vm.OnAfterRenderAsync(firstRender); - if (firstRender == false) - { - return; - } - } } diff --git a/src/iRLeagueManager.Web/Services.cs b/src/iRLeagueManager.Web/Services.cs index e7335a8a..18fce473 100644 --- a/src/iRLeagueManager.Web/Services.cs +++ b/src/iRLeagueManager.Web/Services.cs @@ -29,13 +29,15 @@ public static IServiceCollection AddViewModels(this IServiceCollection services) services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); services.TryAddScoped(); - services.TryAddScoped(); + services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); - services.TryAddScoped(); + services.TryAddScoped(); services.TryAddScoped(); services.TryAddScoped(); return services; diff --git a/src/iRLeagueManager.Web/Shared/InputGroup.razor b/src/iRLeagueManager.Web/Shared/InputGroup.razor index 29e59a12..65d211d8 100644 --- a/src/iRLeagueManager.Web/Shared/InputGroup.razor +++ b/src/iRLeagueManager.Web/Shared/InputGroup.razor @@ -1,7 +1,15 @@ -
+@using System.Linq.Expressions; +
@ChildContent
+@if (ValidationFor is not null) +{ +
+
+ +
+} @code { [Parameter] @@ -10,4 +18,6 @@ public RenderFragment? ChildContent { get; set; } [Parameter] public string Label { get; set; } = string.Empty; + [Parameter] + public Expression>? ValidationFor { get; set; } } diff --git a/src/iRLeagueManager.Web/Shared/LeagueComponentBase.cs b/src/iRLeagueManager.Web/Shared/LeagueComponentBase.cs index 907ffede..ec42edc4 100644 --- a/src/iRLeagueManager.Web/Shared/LeagueComponentBase.cs +++ b/src/iRLeagueManager.Web/Shared/LeagueComponentBase.cs @@ -95,6 +95,16 @@ protected override async Task OnAfterRenderAsync(bool firstRender) await ApiService.SetCurrentSeasonAsync(ApiService.CurrentLeague.Name, SeasonId.Value); } if (ApiService.CurrentSeason == null) + { + var currentSeason = await ApiService.CurrentLeague.Seasons() + .Current() + .Get(); + if (currentSeason.Success && currentSeason.Content is not null) + { + await ApiService.SetCurrentSeasonAsync(ApiService.CurrentLeague.Name, currentSeason.Content.SeasonId); + } + } + if (ApiService.CurrentSeason == null) { var lastSeason = Shared.SeasonList.LastOrDefault(); if (lastSeason != null) diff --git a/src/iRLeagueManager.Web/Shared/NavMenu.razor b/src/iRLeagueManager.Web/Shared/NavMenu.razor index 3eabc2c6..65cc942c 100644 --- a/src/iRLeagueManager.Web/Shared/NavMenu.razor +++ b/src/iRLeagueManager.Web/Shared/NavMenu.razor @@ -138,7 +138,7 @@ private long SeasonSelect { get => SharedState.SeasonId; - set => OnSeasonSelectChanged(value); + set => _ = OnSeasonSelectChanged(value); } protected override void OnInitialized() @@ -152,10 +152,15 @@ collapseNavMenu = !collapseNavMenu; } - private void OnSeasonSelectChanged(long value) + private async Task OnSeasonSelectChanged(long value) { + if (SharedState.LeagueName is null) + { + return; + } + await apiService.SetCurrentSeasonAsync(SharedState.LeagueName, value); navigationManager.NavigateTo($"./{SharedState.LeagueName}/Seasons/{value}"); - InvokeAsync(StateHasChanged); + await InvokeAsync(StateHasChanged); } private string GetCurrentUrl() diff --git a/src/iRLeagueManager.Web/Shared/StatusResultValidator.cs b/src/iRLeagueManager.Web/Shared/StatusResultValidator.cs index cd9875ff..ddfd1b3a 100644 --- a/src/iRLeagueManager.Web/Shared/StatusResultValidator.cs +++ b/src/iRLeagueManager.Web/Shared/StatusResultValidator.cs @@ -1,6 +1,7 @@ using iRLeagueManager.Web.Data; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; +using System.Text.RegularExpressions; namespace iRLeagueManager.Web.Shared; @@ -10,6 +11,13 @@ public sealed class StatusResultValidator : ComponentBase [CascadingParameter] private EditContext CurrentEditContext { get; set; } = default!; + /// + /// Regex string to trim the prefix of field identifiers from validation errors + /// -> Default = "Model." + /// + [Parameter] + public string TrimPrefix { get; set; } = "Model."; + public string ErrorMessage { get; set; } = string.Empty; protected override void OnInitialized() @@ -84,12 +92,10 @@ private void AddBadRequestValidationMessages(StatusResult result) } } - private static string GetModelFieldName(string requestFieldName) + private string GetModelFieldName(string requestFieldName) { - if (requestFieldName.StartsWith("Model.")) - { - return requestFieldName.Substring("Model.".Length); - } + // Trim prefix from field identifier + requestFieldName = Regex.Replace(requestFieldName, TrimPrefix, ""); return requestFieldName; } } diff --git a/src/iRLeagueManager.Web/ViewModels/ChampSeasonViewModel.cs b/src/iRLeagueManager.Web/ViewModels/ChampSeasonViewModel.cs new file mode 100644 index 00000000..3df54376 --- /dev/null +++ b/src/iRLeagueManager.Web/ViewModels/ChampSeasonViewModel.cs @@ -0,0 +1,161 @@ +using iRLeagueApiCore.Common.Models; +using iRLeagueManager.Web.Data; +using iRLeagueManager.Web.Extensions; +using System.Collections.Concurrent; + +namespace iRLeagueManager.Web.ViewModels; + +public sealed class ChampSeasonViewModel : LeagueViewModelBase +{ + public ChampSeasonViewModel(ILoggerFactory loggerFactory, LeagueApiService apiService) : + this(loggerFactory, apiService, new()) + { } + + public ChampSeasonViewModel(ILoggerFactory loggerFactory, LeagueApiService apiService, ChampSeasonModel model) : + base(loggerFactory, apiService, model) + { + resultConfigViewModels = new List(); + } + + public long ChampSeasonId => model.ChampSeasonId; + public long ChampionshipId => model.ChampionshipId; + public long SeasonId => model.SeasonId; + public string ChampionshipName { get => model.ChampionshipName; set => SetP(model.ChampionshipName, value => model.ChampionshipName = value, value); } + public string ChampionshipDisplayName { get => model.ChampionshipDisplayName; set => SetP(model.ChampionshipDisplayName, value => model.ChampionshipDisplayName = value, value); } + public string SeasonName => model.SeasonName; + + private StandingConfigurationViewModel? standingConfig; + public StandingConfigurationViewModel? StandingConfig { get => standingConfig; private set => Set(ref standingConfig, value); } + + public ICollection ResultConfigs => model.ResultConfigs; + + private ICollection resultConfigViewModels; + public ICollection ResultConfigViewModels { get => resultConfigViewModels; set => Set(ref resultConfigViewModels, value); } + + private StandingConfigurationViewModel? standingConfigViewModel; + public StandingConfigurationViewModel? StandingConfigViewModel { get => standingConfigViewModel; set => Set(ref standingConfigViewModel, value); } + + public async Task SaveChangesAsync(CancellationToken cancellationToken) + { + if (ApiService.CurrentLeague is null) + { + return LeagueNullResult(); + } + + try + { + Loading = true; + var result = await ApiService.CurrentLeague + .ChampSeasons() + .WithId(ChampSeasonId) + .Put(model, cancellationToken); + if (result.Success && result.Content is not null) + { + SetModel(result.Content); + } + return result.ToStatusResult(); + } + finally + { + Loading = false; + } + } + + public async Task LoadResultConfigs(CancellationToken cancellationToken = default) + { + if (ApiService.CurrentLeague is null) + { + return LeagueNullResult(); + } + + try + { + Loading = true; + List configModels = new(); + foreach(var configInfo in model.ResultConfigs) + { + var result = await ApiService.CurrentLeague + .ResultConfigs() + .WithId(configInfo.ResultConfigId) + .Get(cancellationToken); + if (result.Success == false || result.Content is null) + { + return result.ToStatusResult(); + } + configModels.Add(result.Content); + } + ResultConfigViewModels = configModels.Select(x => new ResultConfigViewModel(LoggerFactory, ApiService, x)).ToList(); + return StatusResult.SuccessResult(); + } + finally + { + Loading = false; + } + } + + public async Task AddResultConfig(ResultConfigModel config, CancellationToken cancellationToken = default) + { + if (CurrentLeague is null) + { + return LeagueNullResult(); + } + + try + { + Loading = true; + var result = await CurrentLeague.ChampSeasons() + .WithId(model.ChampSeasonId) + .ResultConfigs() + .Post(config, cancellationToken); + if (result.Success == false || result.Content is null) + { + return result.ToStatusResult(); + } + config = result.Content; + model.ResultConfigs.Add(new ResultConfigInfoModel() + { + Name = config.Name, + DisplayName = config.DisplayName, + LeagueId = config.LeagueId, + ResultConfigId = config.ResultConfigId, + }); + ResultConfigViewModels.Add(new(LoggerFactory, ApiService, result.Content)); + return StatusResult.SuccessResult(); + } + finally + { + Loading = false; + } + } + + public async Task DeleteResultConfig(ResultConfigModel config, CancellationToken cancellationToken = default) + { + if (CurrentLeague is null) + { + return StatusResult.SuccessResult(); + } + + try + { + Loading = true; + var result = await CurrentLeague.ResultConfigs() + .WithId(config.ResultConfigId) + .Delete(cancellationToken); + if (result.Success) + { + return await LoadResultConfigs(cancellationToken); + } + return result.ToStatusResult(); + } + finally + { + Loading = false; + } + } + + public override void SetModel(ChampSeasonModel model) + { + base.SetModel(model); + StandingConfig = model.StandingConfig == null ? null : new(LoggerFactory, ApiService, model.StandingConfig); + } +} \ No newline at end of file diff --git a/src/iRLeagueManager.Web/ViewModels/ChampionshipViewModel.cs b/src/iRLeagueManager.Web/ViewModels/ChampionshipViewModel.cs new file mode 100644 index 00000000..1ef2b804 --- /dev/null +++ b/src/iRLeagueManager.Web/ViewModels/ChampionshipViewModel.cs @@ -0,0 +1,153 @@ +using iRLeagueApiCore.Common.Models; +using iRLeagueManager.Web.Data; +using iRLeagueManager.Web.Extensions; +using System; +using System.Diagnostics.Contracts; + +namespace iRLeagueManager.Web.ViewModels; + +public sealed class ChampionshipViewModel : LeagueViewModelBase +{ + public ChampionshipViewModel(ILoggerFactory loggerFactory, LeagueApiService apiService) : + this(loggerFactory, apiService, new()) + { } + + public ChampionshipViewModel(ILoggerFactory loggerFactory, LeagueApiService apiService, ChampionshipModel model) : + base(loggerFactory, apiService, model) + { } + + public long ChampionshipId => model.ChampionshipId; + public string Name { get => model.Name; set => SetP(model.Name, value => model.Name = value, value); } + public string DisplayName { get => model.DisplayName; set => SetP(model.DisplayName, value => model.DisplayName = value, value); } + public IReadOnlyCollection Seasons => model.Seasons; + public bool IsActive { get => isActive; private set => Set(ref isActive, value); } + private bool isActive; + + public async Task SaveChangesAsync(CancellationToken cancellationToken = default) + { + if (ApiService.CurrentLeague is null) + { + return LeagueNullResult(); + } + + try + { + Loading = true; + var request = ApiService.CurrentLeague + .Championships() + .WithId(ChampionshipId) + .Put(model, cancellationToken); + var result = await request; + if (result.Success && result.Content is not null) + { + SetModel(result.Content); + } + return result.ToStatusResult(); + } + finally + { + Loading = false; + } + } + + private bool IsChampionshipActive() + { + return Seasons.Any(x => x.SeasonId == ApiService.CurrentSeason?.Id); + } + + public async Task ActivateForSeasonAsync(long? seasonId = null, CancellationToken cancellationToken = default) + { + if (ApiService.CurrentLeague is null) + { + return LeagueNullResult(); + } + if (ApiService.CurrentSeason is null && seasonId == null) + { + return SeasonNullResult(); + } + if (IsChampionshipActive()) + { + isActive = true; + return StatusResult.SuccessResult("Championship already active for current season"); + } + + try + { + Loading = true; + var season = seasonId == null ? CurrentSeason! : ApiService.CurrentLeague.Seasons() + .WithId(seasonId.Value); + var result = await season + .Championships() + .WithId(ChampionshipId) + .Post(new(), cancellationToken); + if (result.Success == false) + { + return result.ToStatusResult(); + } + var getChampionshipResult = await ApiService.CurrentLeague + .Championships() + .WithId(ChampionshipId) + .Get(cancellationToken); + if (getChampionshipResult.Success && getChampionshipResult.Content is not null) + { + SetModel(getChampionshipResult.Content); + } + return getChampionshipResult.ToStatusResult(); + } + finally + { + Loading = false; + } + } + + public async Task DeactivateForCurrentSeasonAsync(CancellationToken cancellationToken = default) + { + IsActive = false; + + if (CurrentLeague is null) + { + return LeagueNullResult(); + } + if (CurrentSeason is null) + { + return SeasonNullResult(); + } + + try + { + Loading = true; + var champSeason = Seasons.FirstOrDefault(x => x.SeasonId == CurrentSeason.Id); + if (champSeason is null) + { + return StatusResult.SuccessResult(); + } + var result = await CurrentLeague + .ChampSeasons() + .WithId(champSeason.ChampSeasonId) + .Delete(cancellationToken); + if (result.Success == false) + { + return result.ToStatusResult(); + } + var getChampionshipResult = await CurrentLeague + .Championships() + .WithId(ChampionshipId) + .Get(cancellationToken); + if (getChampionshipResult.Success && getChampionshipResult.Content is not null) + { + SetModel(getChampionshipResult.Content); + } + return result.ToStatusResult(); + } + finally + { + Loading = false; + } + } + + public override void SetModel(ChampionshipModel model) + { + base.SetModel(model); + IsActive = IsChampionshipActive(); + } +} \ No newline at end of file diff --git a/src/iRLeagueManager.Web/ViewModels/EventViewModel.cs b/src/iRLeagueManager.Web/ViewModels/EventViewModel.cs index 2d6bce5e..c6122f3b 100644 --- a/src/iRLeagueManager.Web/ViewModels/EventViewModel.cs +++ b/src/iRLeagueManager.Web/ViewModels/EventViewModel.cs @@ -191,15 +191,13 @@ public async Task Load(long eventId, CancellationToken cancellatio public async Task LoadAvailableResultConfigs(CancellationToken cancellationToken = default) { - if (ApiService.CurrentLeague is null) - { - return LeagueNullResult(); - } + if (CurrentLeague is null) return LeagueNullResult(); + if (CurrentSeason is null) return SeasonNullResult(); try { Loading = true; - var request = ApiService.CurrentLeague.ResultConfigs() + var request = CurrentSeason.ResultsConfigs() .Get(cancellationToken); var result = await request; if (result.Success && result.Content is not null) @@ -434,6 +432,8 @@ public override void SetModel(EventModel model) { LeagueId = config.LeagueId, ResultConfigId = config.ResultConfigId, + ChampSeasonId = config.ChampSeasonId, + ChampionshipName = config.ChampionshipName, Name = config.Name, DisplayName = config.DisplayName, }; diff --git a/src/iRLeagueManager.Web/ViewModels/LeagueViewModel.cs b/src/iRLeagueManager.Web/ViewModels/LeagueViewModel.cs index 9e2aa442..c2ef5668 100644 --- a/src/iRLeagueManager.Web/ViewModels/LeagueViewModel.cs +++ b/src/iRLeagueManager.Web/ViewModels/LeagueViewModel.cs @@ -91,6 +91,7 @@ public async Task LoadSeasons(CancellationToken cancellationToken if (result.Success && result.Content is IEnumerable seasonModels) { Seasons = new(seasonModels.Select(x => new SeasonViewModel(LoggerFactory, ApiService, x))); + ApiService.Shared.SeasonList = new(Seasons.Select(x => x.GetModel())); } return result.ToStatusResult(); } @@ -122,6 +123,8 @@ public async Task AddSeason(SeasonModel season, CancellationToken .Schedules() .Post(new() { Name = "Schedule" }, cancellationToken); await scheduleRequest; + var activateSeasons = await ActivateChampSeasons(CurrentSeason?.Id, result.Content.SeasonId, cancellationToken); + if (activateSeasons.IsSuccess == false) return activateSeasons; return await LoadSeasons(cancellationToken); } finally @@ -130,6 +133,46 @@ public async Task AddSeason(SeasonModel season, CancellationToken } } + public async Task ActivateChampSeasons(long? previousSeasonId, long? seasonId = null, CancellationToken cancellationToken = default) + { + if (CurrentLeague is null) return LeagueNullResult(); + seasonId ??= CurrentSeason?.Id; + if (seasonId == null) return SeasonNullResult(); + // if previous season is same as current or previous season is null => success (none action taken) + if (previousSeasonId == null || previousSeasonId == seasonId) return StatusResult.SuccessResult(); + + try + { + Loading = true; + // get active champ seasons from previous season + var champSeasonsResult = await CurrentLeague.Seasons() + .WithId(previousSeasonId.Value) + .ChampSeasons() + .Get(cancellationToken); + if (champSeasonsResult.Success == false || champSeasonsResult.Content is null) + { + return champSeasonsResult.ToStatusResult(); + } + foreach (var champSeason in champSeasonsResult.Content) + { + var activateChampSeasonResult = await CurrentLeague.Seasons() + .WithId(seasonId.Value) + .Championships() + .WithId(champSeason.ChampionshipId) + .Post(new(), cancellationToken); + if (activateChampSeasonResult.Success == false) + { + return activateChampSeasonResult.ToStatusResult(); + } + } + return StatusResult.SuccessResult(); + } + finally + { + Loading = false; + } + } + public async Task DeleteSeason(SeasonModel season, CancellationToken cancellationToken = default) { if (ApiService.CurrentLeague is null) diff --git a/src/iRLeagueManager.Web/ViewModels/LeagueViewModelBase.cs b/src/iRLeagueManager.Web/ViewModels/LeagueViewModelBase.cs index aba7ea1b..ceb3524f 100644 --- a/src/iRLeagueManager.Web/ViewModels/LeagueViewModelBase.cs +++ b/src/iRLeagueManager.Web/ViewModels/LeagueViewModelBase.cs @@ -1,4 +1,6 @@ -using iRLeagueManager.Web.Data; +using iRLeagueApiCore.Client.Endpoints.Leagues; +using iRLeagueApiCore.Client.Endpoints.Seasons; +using iRLeagueManager.Web.Data; using iRLeagueManager.Web.Shared; using MvvmBlazor.ViewModel; using System.Runtime.CompilerServices; @@ -19,6 +21,8 @@ public LeagueViewModelBase(ILoggerFactory loggerFactory, LeagueApiService apiSer protected ILogger Logger { get; } protected LeagueApiService ApiService { get; } protected CancellationTokenSource Cts { get; } = new(); + protected ILeagueByNameEndpoint? CurrentLeague => ApiService.CurrentLeague; + protected ISeasonByIdEndpoint? CurrentSeason => ApiService.CurrentSeason; private bool loading; public bool Loading @@ -90,7 +94,7 @@ protected static StatusResult SeasonNullResult() => StatusResult.FailedResult("Season Null", $"{nameof(LeagueApiService)}.{nameof(LeagueApiService.CurrentSeason)} was null", Array.Empty()); } -public class LeagueViewModelBase : LeagueViewModelBase +public class LeagueViewModelBase : LeagueViewModelBase where TModel : class where TViewModel : class { protected TModel model = default!; @@ -112,7 +116,6 @@ public virtual void SetModel(TModel model) public virtual TModel CopyModel() { - return JsonSerializer.Deserialize(JsonSerializer.Serialize(model)) - ?? throw new InvalidOperationException("Could not copy model"); + return ModelHelper.CopyModel(model)!; } } diff --git a/src/iRLeagueManager.Web/ViewModels/ResultConfigSettingsViewModel.cs b/src/iRLeagueManager.Web/ViewModels/ResultConfigSettingsViewModel.cs deleted file mode 100644 index 4d5e7ce1..00000000 --- a/src/iRLeagueManager.Web/ViewModels/ResultConfigSettingsViewModel.cs +++ /dev/null @@ -1,94 +0,0 @@ -using iRLeagueApiCore.Common.Models; -using iRLeagueManager.Web.Data; -using iRLeagueManager.Web.Extensions; - -namespace iRLeagueManager.Web.ViewModels; - -public sealed class ResultConfigSettingsViewModel : LeagueViewModelBase -{ - public ResultConfigSettingsViewModel(ILoggerFactory loggerFactory, LeagueApiService apiService) : - base(loggerFactory, apiService) - { - resultConfigs = new ObservableCollection(); - } - - private ObservableCollection resultConfigs; - public ObservableCollection ResultsConfigs { get => resultConfigs; set => Set(ref resultConfigs, value); } - - private ResultConfigViewModel? selected; - public ResultConfigViewModel? Selected { get => selected; set => Set(ref selected, value); } - - public async Task LoadFromLeagueAsync() - { - if (ApiService.CurrentLeague == null) - { - return; - } - - var resultConfigsEndpoint = ApiService.CurrentLeague.ResultConfigs(); - var resultConfigsResult = await resultConfigsEndpoint.Get(); - if (resultConfigsResult.Success == false || resultConfigsResult.Content is null) - { - return; - } - - ResultsConfigs = new ObservableCollection( - resultConfigsResult.Content.Select(x => new ResultConfigViewModel(LoggerFactory, ApiService, x))); - } - - public async Task AddConfiguration(ResultConfigModel? newConfig = null) - { - if (ApiService.CurrentLeague is null) - { - return LeagueNullResult(); - } - - try - { - Loading = true; - newConfig ??= new() { Name = "New Config", DisplayName = "New Config" }; - var request = ApiService.CurrentLeague.ResultConfigs() - .Post(newConfig); - var result = await request; - - if (result.Success == true && result.Content is not null) - { - ResultsConfigs.Add(new ResultConfigViewModel(LoggerFactory, ApiService, result.Content)); - } - - return result.ToStatusResult(); - } - finally - { - Loading = false; - } - } - - public async Task DeleteConfiguration(ResultConfigViewModel config) - { - if (ApiService.CurrentLeague is null) - { - return LeagueNullResult(); - } - - try - { - Loading = true; - var request = ApiService.CurrentLeague.ResultConfigs() - .WithId(config.ResultConfigId) - .Delete(); - var result = await request; - - if (result.Success) - { - ResultsConfigs.Remove(config); - } - - return result.ToStatusResult(); - } - finally - { - Loading = false; - } - } -} diff --git a/src/iRLeagueManager.Web/ViewModels/ResultConfigViewModel.cs b/src/iRLeagueManager.Web/ViewModels/ResultConfigViewModel.cs index 7706b7a3..5e2b45cd 100644 --- a/src/iRLeagueManager.Web/ViewModels/ResultConfigViewModel.cs +++ b/src/iRLeagueManager.Web/ViewModels/ResultConfigViewModel.cs @@ -2,6 +2,7 @@ using iRLeagueApiCore.Common.Models; using iRLeagueManager.Web.Data; using iRLeagueManager.Web.Extensions; +using Microsoft.Extensions.Configuration.EnvironmentVariables; namespace iRLeagueManager.Web.ViewModels; @@ -15,14 +16,16 @@ public ResultConfigViewModel(ILoggerFactory loggerFactory, LeagueApiService apiS public ResultConfigViewModel(ILoggerFactory loggerFactory, LeagueApiService apiService, ResultConfigModel model) : base(loggerFactory, apiService, model) { - scorings = new(); - filtersForPoints = new(); - filtersForResult = new(); - availableResultConfigs = new(); + scorings ??= new(); + filtersForPoints ??= new(); + filtersForResult ??= new(); + availableResultConfigs ??= new(); } public long LeagueId => model.LeagueId; public long ResultConfigId => model.ResultConfigId; + public long? ChampSeasonId => model.ChampSeasonId; + public string ChampionshipName => model.ChampionshipName; public string Name { get => model.Name; set => SetP(model.Name, value => model.Name = value, value); } public string DisplayName { get => model.DisplayName; set => SetP(model.DisplayName, value => model.DisplayName = value, value); } public ResultKind ResultKind { get => model.ResultKind; set => SetP(model.ResultKind, value => model.ResultKind = value, value); } @@ -57,25 +60,25 @@ public bool CalculateCombinedResult } } - private StandingConfigurationViewModel? standingConfig; - public StandingConfigurationViewModel? StandingConfig { get => standingConfig; set => Set(ref standingConfig, value); } - public bool CalculateStandings - { - get => StandingConfig is not null; - set - { - if (value && model.StandingConfig is null) - { - model.StandingConfig = new StandingConfigModel(); - StandingConfig = new(LoggerFactory, ApiService, model.StandingConfig); - } - if (value == false && model.StandingConfig is not null) - { - model.StandingConfig = null; - StandingConfig = null; - } - } - } + //private StandingConfigurationViewModel? standingConfig; + //public StandingConfigurationViewModel? StandingConfig { get => standingConfig; set => Set(ref standingConfig, value); } + //public bool CalculateStandings + //{ + // get => StandingConfig is not null; + // set + // { + // if (value && model.StandingConfig is null) + // { + // model.StandingConfig = new StandingConfigModel(); + // StandingConfig = new(LoggerFactory, ApiService, model.StandingConfig); + // } + // if (value == false && model.StandingConfig is not null) + // { + // model.StandingConfig = null; + // StandingConfig = null; + // } + // } + //} private ObservableCollection scorings; public ObservableCollection Scorings { get => scorings; set => SetP(scorings, value => scorings = value, value); } @@ -95,22 +98,26 @@ public override void SetModel(ResultConfigModel model) Scorings = new(model.Scorings.Select(scoringModel => new ScoringViewModel(LoggerFactory, ApiService, scoringModel))); FiltersForPoints = new(model.FiltersForPoints.Select(filter => new ResultFilterViewModel(LoggerFactory, ApiService, filter))); FiltersForResult = new(model.FiltersForResult.Select(filter => new ResultFilterViewModel(LoggerFactory, ApiService, filter))); - StandingConfig = model.StandingConfig is not null ? new StandingConfigurationViewModel(LoggerFactory, ApiService, model.StandingConfig) : null; + //StandingConfig = model.StandingConfig is not null ? new StandingConfigurationViewModel(LoggerFactory, ApiService, model.StandingConfig) : null; } public async Task LoadAvailableResultConfigs(CancellationToken cancellationToken) { - if (ApiService.CurrentLeague is null) + if (CurrentLeague is null) { return LeagueNullResult(); } + if (CurrentSeason is null) + { + return SeasonNullResult(); + } - var request = ApiService.CurrentLeague.ResultConfigs() + var request = CurrentSeason.ResultsConfigs() .Get(cancellationToken); var result = await request; - if (result.Success && result.Content is IEnumerable configs) + if (result.Success && result.Content is not null) { - AvailableResultConfigs = new(configs); + AvailableResultConfigs = new(result.Content); } return result.ToStatusResult(); diff --git a/src/iRLeagueManager.Web/ViewModels/ResultSettingsViewModel.cs b/src/iRLeagueManager.Web/ViewModels/ResultSettingsViewModel.cs new file mode 100644 index 00000000..65673c4a --- /dev/null +++ b/src/iRLeagueManager.Web/ViewModels/ResultSettingsViewModel.cs @@ -0,0 +1,126 @@ +using iRLeagueApiCore.Common.Models; +using iRLeagueManager.Web.Data; +using iRLeagueManager.Web.Extensions; +using System; + +namespace iRLeagueManager.Web.ViewModels; + +public sealed class ResultSettingsViewModel : LeagueViewModelBase +{ + public ResultSettingsViewModel(ILoggerFactory loggerFactory, LeagueApiService apiService) : + base(loggerFactory, apiService) + { + championships = new(); + currentChampSeasons = new(); + resultConfigs = new(); + } + + private ObservableCollection championships; + public ObservableCollection Championships { get => championships; set => Set(ref championships, value); } + + private ObservableCollection currentChampSeasons; + public ObservableCollection CurrentChampSeasons { get => currentChampSeasons; set => Set(ref currentChampSeasons, value); } + + private ObservableCollection resultConfigs; + public ObservableCollection ResultsConfigs { get => resultConfigs; set => Set(ref resultConfigs, value); } + + private ResultConfigViewModel? selected; + public ResultConfigViewModel? Selected { get => selected; set => Set(ref selected, value); } + + public async Task LoadFromCurrentSeasonAsync(CancellationToken cancellationToken = default) + { + if (ApiService.CurrentLeague is null) + { + return LeagueNullResult(); + } + if (ApiService.CurrentSeason is null) + { + return SeasonNullResult(); + } + + try + { + Loading = true; + + var getChampionships = await ApiService.CurrentLeague + .Championships() + .Get(cancellationToken); + if (getChampionships.Success == false || getChampionships.Content is null) + { + return getChampionships.ToStatusResult(); + } + Championships = new(getChampionships.Content.Select(x => new ChampionshipViewModel(LoggerFactory, ApiService, x))); + + var getChampSeasons = await ApiService.CurrentSeason + .ChampSeasons() + .Get(cancellationToken); + if (getChampSeasons.Success == false || getChampSeasons.Content is null) + { + return getChampSeasons.ToStatusResult(); + } + CurrentChampSeasons = new(getChampSeasons.Content.Select(x => new ChampSeasonViewModel(LoggerFactory, ApiService, x))); + + return StatusResult.SuccessResult(); + } + finally + { + Loading = false; + } + } + + public async Task AddChampionship(PutChampSeasonModel champSeason, CancellationToken cancellationToken = default) + { + if (CurrentLeague is null) return LeagueNullResult(); + if (CurrentSeason is null) return SeasonNullResult(); + try + { + Loading = true; + // Post a new championship + var postChampionship = new PostChampionshipModel() { Name = champSeason.ChampionshipName, DisplayName = champSeason.ChampionshipDisplayName }; + var postChampionshipResult = await CurrentLeague.Championships() + .Post(postChampionship, cancellationToken); + if (postChampionshipResult.Success == false || postChampionshipResult.Content is null) + { + return postChampionshipResult.ToStatusResult(); + } + // Post a champ season for the new championship and the current season + var postChampSeasonResult = await CurrentSeason.Championships() + .WithId(postChampionshipResult.Content.ChampionshipId) + .Post(champSeason); + if (postChampSeasonResult.Success == false || postChampSeasonResult.Content is null) + { + return postChampionshipResult.ToStatusResult(); + } + // Update the champseason with data from model + var putChampSeasonResult = await CurrentLeague.ChampSeasons() + .WithId(postChampSeasonResult.Content.ChampSeasonId) + .Put(champSeason); + return putChampSeasonResult.ToStatusResult(); + } + finally + { + Loading = false; + } + } + + public async Task DeleteChampionship(ChampionshipModel championship, CancellationToken cancellationToken = default) + { + if (CurrentLeague is null) return LeagueNullResult(); + try + { + Loading = true; + var result = await CurrentLeague.Championships() + .WithId(championship.ChampionshipId) + .Delete(cancellationToken); + if (result.Success == false) + { + return result.ToStatusResult(); + } + return await LoadFromCurrentSeasonAsync(cancellationToken); + } + finally + { + Loading = false; + } + } +} diff --git a/src/iRLeagueManager.Web/iRLeagueManager.Web.csproj b/src/iRLeagueManager.Web/iRLeagueManager.Web.csproj index 5305f028..693ec648 100644 --- a/src/iRLeagueManager.Web/iRLeagueManager.Web.csproj +++ b/src/iRLeagueManager.Web/iRLeagueManager.Web.csproj @@ -2,7 +2,7 @@ net6.0 - 0.4.2 + 0.5.0 enable enable aspnet-iRLeagueManager.Web-2B05F9DC-55A3-49D1-BD64-31507000EDF3 @@ -119,6 +119,10 @@ + + <_ContentIncludedByDefault Remove="wwwroot\css\toggle.css" /> + + @@ -126,8 +130,8 @@ - - + + diff --git a/src/iRLeagueManager.Web/wwwroot/css/site.css b/src/iRLeagueManager.Web/wwwroot/css/site.css index 76032f7c..499ad32b 100644 --- a/src/iRLeagueManager.Web/wwwroot/css/site.css +++ b/src/iRLeagueManager.Web/wwwroot/css/site.css @@ -1,4 +1,9 @@ @import url('open-iconic/font/css/open-iconic-bootstrap.min.css'); +@import url(toggle.css); + +.root { + --bs-border-radius: .25rem; +} html, body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; @@ -86,6 +91,7 @@ a, .btn-link { .validation-message { color: red; + font-size: 0.85em; } .input-plain::-webkit-outer-spin-button, @@ -159,12 +165,17 @@ a, .btn-link { .input-group-list > .input-group:not(:first-child) > * { border-top-left-radius: 0; border-top-right-radius: 0; - border-top: none; } .input-group-list > .input-group:not(:last-child) > * { border-bottom-left-radius: 0; border-bottom-right-radius: 0; + border-bottom: none; +} + +.input-group-list > .input-group:last-child > * { + border-bottom-left-radius: 0.25rem; + border-bottom-right-radius: 0.25rem; } .input-group-list-flush > .input-group > * { @@ -233,6 +244,13 @@ a, .btn-link { animation-delay: -0.15s; } +.btn-toggle[aria-expanded=true] > .btn-toggle-off { + display: none; +} +.btn-toggle[aria-expanded=false] > .btn-toggle-on { + display: none; +} + @keyframes lds-ring { 0% { transform: rotate(0deg); diff --git a/src/iRLeagueManager.Web/wwwroot/css/toggle.css b/src/iRLeagueManager.Web/wwwroot/css/toggle.css new file mode 100644 index 00000000..efdb1109 --- /dev/null +++ b/src/iRLeagueManager.Web/wwwroot/css/toggle.css @@ -0,0 +1,64 @@ +@import url(bootstrap/bootstrap.min.css); + +/* The switch - the box around the slider */ +.switch { + position: relative; + display: inline-block; + width: 2.5rem; + height: 1.3rem; +} + + /* Hide default HTML checkbox */ + .switch input { + opacity: 0; + width: 0; + height: 0; + } + +/* The slider */ +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .2s; + transition: .2s; +} + + .slider:before { + position: absolute; + content: ""; + height: 1rem; + width: 1rem; + left: .15rem; + bottom: .15rem; + background-color: white; + -webkit-transition: .2s; + transition: .2s; + } + +input:checked + .slider { + background-color: #2196F3; +} + +input:focus + .slider { + box-shadow: 0 0 1px #2196F3; +} + +input:checked + .slider:before { + -webkit-transform: translateX(1.15rem); + -ms-transform: translateX(1.15rem); + transform: translateX(1.15rem); +} + +/* Rounded sliders */ +.slider.round { + border-radius: 34px; +} + + .slider.round:before { + border-radius: 50%; + }