diff --git a/Stoolball.Data.SqlServer.IntegrationTests/Statistics/SqlServerPlayerDataSourceTests.cs b/Stoolball.Data.SqlServer.IntegrationTests/Statistics/SqlServerPlayerDataSourceTests.cs index 4cf84cb7..ffe8199d 100644 --- a/Stoolball.Data.SqlServer.IntegrationTests/Statistics/SqlServerPlayerDataSourceTests.cs +++ b/Stoolball.Data.SqlServer.IntegrationTests/Statistics/SqlServerPlayerDataSourceTests.cs @@ -600,6 +600,7 @@ public async Task ReadPlayerIdentityByRoute_returns_identity_player_team_and_clu Assert.Equal(identity.PlayerIdentityName, result.PlayerIdentityName); Assert.Equal(identity.RouteSegment, result.RouteSegment); Assert.Equal(identity.Player?.PlayerId, result.Player?.PlayerId); + Assert.Equal(identity.Player?.PlayerRoute, result.Player?.PlayerRoute); Assert.Equal(identity.Team.TeamId, result.Team!.TeamId); Assert.Equal(identity.Team.TeamName, result.Team!.TeamName); Assert.Equal(identity.Team.TeamRoute, result.Team!.TeamRoute); diff --git a/Stoolball.Data.SqlServer/SqlServerPlayerDataSource.cs b/Stoolball.Data.SqlServer/SqlServerPlayerDataSource.cs index 2485eb8c..ae3f28ab 100644 --- a/Stoolball.Data.SqlServer/SqlServerPlayerDataSource.cs +++ b/Stoolball.Data.SqlServer/SqlServerPlayerDataSource.cs @@ -278,7 +278,7 @@ WHERE LOWER(PlayerRoute) = @Route { var results = await connection.QueryAsync( $@"SELECT pi.PlayerIdentityId, pi.PlayerIdentityName, pi.RouteSegment, - pi.PlayerId, + pi.PlayerId, pi.PlayerRoute, t.TeamId, tv.TeamName, t.TeamRoute, c.ClubId, cv.ClubName, c.ClubRoute FROM {Views.PlayerIdentity} pi INNER JOIN {Tables.Team} t ON pi.TeamId = t.TeamId diff --git a/Stoolball.Web.UnitTests/Routing/StoolballRouteParserTests.cs b/Stoolball.Web.UnitTests/Routing/StoolballRouteParserTests.cs index f6d032ce..f018acaa 100644 --- a/Stoolball.Web.UnitTests/Routing/StoolballRouteParserTests.cs +++ b/Stoolball.Web.UnitTests/Routing/StoolballRouteParserTests.cs @@ -16,6 +16,7 @@ public class StoolballRouteParserTests [InlineData("https://example.org/teams/example123/edit/players", StoolballRouteType.EditPlayersForTeam)] [InlineData("https://example.org/teams/example123/edit/players/some-player", StoolballRouteType.PlayerIdentityActions)] [InlineData("https://example.org/teams/example123/edit/players/some-player/rename", StoolballRouteType.RenamePlayerIdentity)] + [InlineData("https://example.org/teams/example123/edit/players/some-player/statistics", StoolballRouteType.LinkedPlayersForIdentity)] [InlineData("https://example.org/teams/example123/delete", StoolballRouteType.DeleteTeam)] [InlineData("https://example.org/teams/example123/players", StoolballRouteType.PlayersForTeam)] [InlineData("https://example.org/teams/example123/matches", StoolballRouteType.MatchesForTeam)] diff --git a/Stoolball.Web.UnitTests/Statistics/LinkedPlayersForMemberControllerTests.cs b/Stoolball.Web.UnitTests/Statistics/LinkedPlayersForMemberControllerTests.cs index 214cfa78..58ec5690 100644 --- a/Stoolball.Web.UnitTests/Statistics/LinkedPlayersForMemberControllerTests.cs +++ b/Stoolball.Web.UnitTests/Statistics/LinkedPlayersForMemberControllerTests.cs @@ -38,7 +38,7 @@ public async Task Unauthenticated_returns_view_model_without_player() { var result = await controller.Index(); - Assert.Null(((LinkedPlayersForMemberViewModel)((ViewResult)result).Model).Player); + Assert.Null(((LinkedPlayersViewModel)((ViewResult)result).Model).Player); } } @@ -54,7 +54,7 @@ public async Task Member_without_linked_player_returns_view_model_without_player _playerDataSource.Verify(x => x.ReadPlayerByMemberKey(memberKey), Times.Once); _playerDataSource.Verify(x => x.ReadPlayerByRoute(It.IsAny(), null), Times.Never); - Assert.Null(((LinkedPlayersForMemberViewModel)((ViewResult)result).Model).Player); + Assert.Null(((LinkedPlayersViewModel)((ViewResult)result).Model).Player); } } @@ -72,7 +72,7 @@ public async Task Member_with_linked_player_sets_player_in_view_model() { var result = await controller.Index(); - Assert.Equal(player2, ((LinkedPlayersForMemberViewModel)((ViewResult)result).Model).Player); + Assert.Equal(player2, ((LinkedPlayersViewModel)((ViewResult)result).Model).Player); } } @@ -83,7 +83,7 @@ public async Task Sets_breadcrumbs() { var result = await controller.Index(); - var breadcrumbs = ((LinkedPlayersForMemberViewModel)((ViewResult)result).Model).Breadcrumbs; + var breadcrumbs = ((LinkedPlayersViewModel)((ViewResult)result).Model).Breadcrumbs; Assert.Equal(2, breadcrumbs.Count); Assert.Equal("Home", breadcrumbs[0].Name); Assert.Equal("My account", breadcrumbs[1].Name); @@ -97,7 +97,7 @@ public async Task Sets_page_title() { var result = await controller.Index(); - Assert.False(string.IsNullOrWhiteSpace(((LinkedPlayersForMemberViewModel)((ViewResult)result).Model).Metadata.PageTitle)); + Assert.False(string.IsNullOrWhiteSpace(((LinkedPlayersViewModel)((ViewResult)result).Model).Metadata.PageTitle)); } } @@ -110,7 +110,7 @@ public async Task Sets_PreferredNextRoute_from_referer_if_removing_domain() { var result = await controller.Index(); - Assert.Equal("/from-referer", ((LinkedPlayersForMemberViewModel)((ViewResult)result).Model).PreferredNextRoute); + Assert.Equal("/from-referer", ((LinkedPlayersViewModel)((ViewResult)result).Model).PreferredNextRoute); } } @@ -121,7 +121,7 @@ public async Task Sets_PreferredNextRoute_to_account_if_no_referer() { var result = await controller.Index(); - Assert.Equal(Constants.Pages.AccountUrl, ((LinkedPlayersForMemberViewModel)((ViewResult)result).Model).PreferredNextRoute); + Assert.Equal(Constants.Pages.AccountUrl, ((LinkedPlayersViewModel)((ViewResult)result).Model).PreferredNextRoute); } } } diff --git a/Stoolball.Web.UnitTests/Teams/LinkedPlayersForIdentityControllerTests.cs b/Stoolball.Web.UnitTests/Teams/LinkedPlayersForIdentityControllerTests.cs new file mode 100644 index 00000000..d3875735 --- /dev/null +++ b/Stoolball.Web.UnitTests/Teams/LinkedPlayersForIdentityControllerTests.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Moq; +using Stoolball.Data.Abstractions; +using Stoolball.Security; +using Stoolball.Statistics; +using Stoolball.Teams; +using Stoolball.Web.Navigation; +using Stoolball.Web.Statistics.Models; +using Stoolball.Web.Teams; +using Xunit; + +namespace Stoolball.Web.UnitTests.Teams +{ + public class LinkedPlayersForIdentityControllerTests : UmbracoBaseTest + { + private readonly Mock _playerDataSource = new(); + private readonly Mock> _authorizationPolicy = new(); + private readonly Mock _breadcrumbBuilder = new(); + + private LinkedPlayersForIdentityController CreateController() + { + return new LinkedPlayersForIdentityController( + Mock.Of>(), + CompositeViewEngine.Object, + UmbracoContextAccessor.Object, + _authorizationPolicy.Object, + _playerDataSource.Object, + _breadcrumbBuilder.Object) + { + ControllerContext = ControllerContext + }; + } + + private PlayerIdentity SetupMocks() + { + var identity = new PlayerIdentity + { + PlayerIdentityId = Guid.NewGuid(), + PlayerIdentityName = "John Smith", + Team = new Team(), + Player = new Player { PlayerId = Guid.NewGuid(), PlayerRoute = "/players/example-player" } + }; + _playerDataSource.Setup(x => x.ReadPlayerIdentityByRoute(Request.Object.Path)).ReturnsAsync(identity); + _playerDataSource.Setup(x => x.ReadPlayerByRoute(identity.Player.PlayerRoute, null)).ReturnsAsync(identity.Player); + _authorizationPolicy.Setup(x => x.IsAuthorized(identity.Team)).Returns(Task.FromResult(new Dictionary { { AuthorizedAction.EditTeam, true } })); + return identity; + } + + [Fact] + public async Task Route_not_matching_identity_returns_404() + { + _playerDataSource.Setup(x => x.ReadPlayerIdentityByRoute(Request.Object.Path)).Returns(Task.FromResult(null)); + + using (var controller = CreateController()) + { + var result = await controller.Index(); + + Assert.IsType(result); + } + } + + [Fact] + public async Task Route_matching_identity_sets_authorization() + { + _ = SetupMocks(); + + using (var controller = CreateController()) + { + var result = await controller.Index(); + + var model = (LinkedPlayersViewModel)((ViewResult)result).Model; + + Assert.True(model.Authorization.CurrentMemberIsAuthorized[AuthorizedAction.EditTeam]); + } + } + + [Fact] + public async Task Route_matching_identity_returns_player_and_identity() + { + var identity = SetupMocks(); + + using (var controller = CreateController()) + { + var result = await controller.Index(); + + var model = (LinkedPlayersViewModel)((ViewResult)result).Model; + + Assert.Equal(identity, model.ContextIdentity); + Assert.Equal(identity.Player, model.Player); + } + } + + [Fact] + public async Task Route_matching_identity_sets_page_title() + { + var identity = SetupMocks(); + + using (var controller = CreateController()) + { + var result = await controller.Index(); + + var model = (LinkedPlayersViewModel)((ViewResult)result).Model; + + Assert.Equal($"Players linked to the statistics for {identity.PlayerIdentityName}", model.Metadata.PageTitle); + } + } + + [Fact] + public async Task Route_matching_identity_sets_breadcrumbs() + { + var identity = SetupMocks(); + + using (var controller = CreateController()) + { + var result = await controller.Index(); + + var model = (LinkedPlayersViewModel)((ViewResult)result).Model; + + _breadcrumbBuilder.Verify(x => x.BuildBreadcrumbsForEditPlayers(model.Breadcrumbs, identity.Team!), Times.Once()); + } + } + } +} diff --git a/Stoolball.Web/Routing/StoolballRouteParser.cs b/Stoolball.Web/Routing/StoolballRouteParser.cs index ccba8f4d..3ffbbbc9 100644 --- a/Stoolball.Web/Routing/StoolballRouteParser.cs +++ b/Stoolball.Web/Routing/StoolballRouteParser.cs @@ -162,6 +162,7 @@ public class StoolballRouteParser : IStoolballRouteParser { $"teams{SLASH}{ANY_VALID_ROUTE}{SLASH}edit{SLASH}players{OPTIONAL_SLASH}", StoolballRouteType.EditPlayersForTeam }, { $"teams{SLASH}{ANY_VALID_ROUTE}{SLASH}edit{SLASH}players{SLASH}{ANY_VALID_ROUTE}{OPTIONAL_SLASH}", StoolballRouteType.PlayerIdentityActions }, { $"teams{SLASH}{ANY_VALID_ROUTE}{SLASH}edit{SLASH}players{SLASH}{ANY_VALID_ROUTE}{SLASH}rename{OPTIONAL_SLASH}", StoolballRouteType.RenamePlayerIdentity }, + { $"teams{SLASH}{ANY_VALID_ROUTE}{SLASH}edit{SLASH}players{SLASH}{ANY_VALID_ROUTE}{SLASH}statistics{OPTIONAL_SLASH}", StoolballRouteType.LinkedPlayersForIdentity }, { $"teams{SLASH}{ANY_VALID_ROUTE}{SLASH}delete{OPTIONAL_SLASH}", StoolballRouteType.DeleteTeam }, { $"locations{SLASH}{ANY_VALID_ROUTE}{SLASH}matches{OPTIONAL_SLASH}", StoolballRouteType.MatchesForMatchLocation }, { $"locations{SLASH}{ANY_VALID_ROUTE}{SLASH}matches{SLASH}ics{OPTIONAL_SLASH}", StoolballRouteType.MatchesCalendar }, diff --git a/Stoolball.Web/Routing/StoolballRouteType.cs b/Stoolball.Web/Routing/StoolballRouteType.cs index f822207b..3bee3585 100644 --- a/Stoolball.Web/Routing/StoolballRouteType.cs +++ b/Stoolball.Web/Routing/StoolballRouteType.cs @@ -80,6 +80,7 @@ public enum StoolballRouteType PlayerBowling, PlayerFielding, LinkPlayerToMember, + LinkedPlayersForIdentity, LinkedPlayersForMember, RenamePlayerIdentity, PlayersForTeam, diff --git a/Stoolball.Web/Routing/StoolballRouteTypeMapper.cs b/Stoolball.Web/Routing/StoolballRouteTypeMapper.cs index 3d62dde8..991d248a 100644 --- a/Stoolball.Web/Routing/StoolballRouteTypeMapper.cs +++ b/Stoolball.Web/Routing/StoolballRouteTypeMapper.cs @@ -90,6 +90,7 @@ public class StoolballRouteTypeMapper : IStoolballRouteTypeMapper { StoolballRouteType.PlayerBowling, typeof(PlayerBowlingController) }, { StoolballRouteType.PlayerFielding, typeof(PlayerFieldingController) }, { StoolballRouteType.LinkPlayerToMember, typeof(LinkPlayerToMemberController) }, + { StoolballRouteType.LinkedPlayersForIdentity, typeof(LinkedPlayersForIdentityController) }, { StoolballRouteType.LinkedPlayersForMember, typeof(LinkedPlayersForMemberController) }, { StoolballRouteType.RenamePlayerIdentity, typeof(RenamePlayerIdentityController) }, { StoolballRouteType.PlayerIdentityActions, typeof(PlayerIdentityActionsController) }, diff --git a/Stoolball.Web/Startup.cs b/Stoolball.Web/Startup.cs index d8f66fe4..0a078bbd 100644 --- a/Stoolball.Web/Startup.cs +++ b/Stoolball.Web/Startup.cs @@ -309,6 +309,7 @@ public void ConfigureServices(IServiceCollection services) services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); services.AddTransient(); diff --git a/Stoolball.Web/Statistics/LinkedPlayersForMemberController.cs b/Stoolball.Web/Statistics/LinkedPlayersForMemberController.cs index 53474c66..63cbc098 100644 --- a/Stoolball.Web/Statistics/LinkedPlayersForMemberController.cs +++ b/Stoolball.Web/Statistics/LinkedPlayersForMemberController.cs @@ -35,7 +35,7 @@ public LinkedPlayersForMemberController(ILogger Index() { - var model = new LinkedPlayersForMemberViewModel(CurrentPage); + var model = new LinkedPlayersViewModel(CurrentPage); var member = await _memberManager.GetCurrentMemberAsync(); if (member != null) diff --git a/Stoolball.Web/Statistics/Models/LinkedPlayersForMemberViewModel.cs b/Stoolball.Web/Statistics/Models/LinkedPlayersForMemberViewModel.cs deleted file mode 100644 index 1fd728e8..00000000 --- a/Stoolball.Web/Statistics/Models/LinkedPlayersForMemberViewModel.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Stoolball.Statistics; -using Stoolball.Web.Models; -using Umbraco.Cms.Core.Models.PublishedContent; -using Umbraco.Cms.Core.Services; - -namespace Stoolball.Web.Statistics.Models -{ - public class LinkedPlayersForMemberViewModel : BaseViewModel - { - public LinkedPlayersForMemberViewModel(IPublishedContent? contentModel = null, IUserService? userService = null) : base(contentModel, userService) - { - } - - public Player? Player { get; set; } - - public string PreferredNextRoute { get; set; } = Constants.Pages.AccountUrl; - } -} diff --git a/Stoolball.Web/Statistics/Models/LinkedPlayersViewModel.cs b/Stoolball.Web/Statistics/Models/LinkedPlayersViewModel.cs new file mode 100644 index 00000000..7c13ef7a --- /dev/null +++ b/Stoolball.Web/Statistics/Models/LinkedPlayersViewModel.cs @@ -0,0 +1,27 @@ +using Stoolball.Statistics; +using Stoolball.Web.Models; +using Umbraco.Cms.Core.Models.PublishedContent; +using Umbraco.Cms.Core.Services; + +namespace Stoolball.Web.Statistics.Models +{ + public class LinkedPlayersViewModel : BaseViewModel + { + public LinkedPlayersViewModel(IPublishedContent? contentModel = null, IUserService? userService = null) : base(contentModel, userService) + { + } + + public Player? Player { get; set; } + + public PlayerIdentity? ContextIdentity { get; set; } + + public string PreferredNextRoute { get; set; } = Constants.Pages.AccountUrl; + + public string LinkedByHeading { get; set; } = "Linked by"; + public string LinkedByMemberLabel { get; set; } = PlayerIdentityLinkedBy.Member.ToString(); + public string LinkedByClubOrTeamLabel { get; set; } = PlayerIdentityLinkedBy.ClubOrTeam.ToString(); + public string LinkedByStoolballEnglandLabel { get; set; } = PlayerIdentityLinkedBy.StoolballEngland.ToString(); + + public bool CanUnlinkIdentitiesLinkedByMember { get; set; } + } +} diff --git a/Stoolball.Web/Teams/LinkedPlayersForIdentityController.cs b/Stoolball.Web/Teams/LinkedPlayersForIdentityController.cs new file mode 100644 index 00000000..289aa63f --- /dev/null +++ b/Stoolball.Web/Teams/LinkedPlayersForIdentityController.cs @@ -0,0 +1,64 @@ +using System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.Extensions.Logging; +using Stoolball.Data.Abstractions; +using Stoolball.Security; +using Stoolball.Teams; +using Stoolball.Web.Navigation; +using Stoolball.Web.Routing; +using Stoolball.Web.Security; +using Stoolball.Web.Statistics.Models; +using Umbraco.Cms.Core.Web; +using Umbraco.Cms.Web.Common.Controllers; + +namespace Stoolball.Web.Teams +{ + public class LinkedPlayersForIdentityController : RenderController, IRenderControllerAsync + { + private readonly IAuthorizationPolicy _authorizationPolicy; + private readonly IPlayerDataSource _playerDataSource; + private readonly ITeamBreadcrumbBuilder _breadcrumbBuilder; + + public LinkedPlayersForIdentityController(ILogger logger, + ICompositeViewEngine compositeViewEngine, + IUmbracoContextAccessor umbracoContextAccessor, + IAuthorizationPolicy authorizationPolicy, + IPlayerDataSource playerDataSource, + ITeamBreadcrumbBuilder breadcrumbBuilder) + : base(logger, compositeViewEngine, umbracoContextAccessor) + { + _authorizationPolicy = authorizationPolicy ?? throw new ArgumentNullException(nameof(authorizationPolicy)); + _playerDataSource = playerDataSource ?? throw new ArgumentNullException(nameof(playerDataSource)); + _breadcrumbBuilder = breadcrumbBuilder ?? throw new ArgumentNullException(nameof(breadcrumbBuilder)); + } + + [HttpGet] + [ContentSecurityPolicy] + public async new Task Index() + { + var model = new LinkedPlayersViewModel(CurrentPage) + { + ContextIdentity = await _playerDataSource.ReadPlayerIdentityByRoute(Request.Path) + }; + + if (model.ContextIdentity?.Team == null || model.ContextIdentity?.Player == null) + { + return NotFound(); + } + else + { + model.Player = await _playerDataSource.ReadPlayerByRoute(model.ContextIdentity.Player!.PlayerRoute!); + + model.Authorization.CurrentMemberIsAuthorized = await _authorizationPolicy.IsAuthorized(model.ContextIdentity.Team); + + model.Metadata.PageTitle = "Players linked to the statistics for " + model.ContextIdentity.PlayerIdentityName; + + _breadcrumbBuilder.BuildBreadcrumbsForEditPlayers(model.Breadcrumbs, model.ContextIdentity.Team); + + return CurrentTemplate(model); + } + } + } +} \ No newline at end of file diff --git a/Stoolball.Web/Teams/PlayerIdentityActionsController.cs b/Stoolball.Web/Teams/PlayerIdentityActionsController.cs index 28ac50f8..09f99d51 100644 --- a/Stoolball.Web/Teams/PlayerIdentityActionsController.cs +++ b/Stoolball.Web/Teams/PlayerIdentityActionsController.cs @@ -49,7 +49,6 @@ public PlayerIdentityActionsController(ILogger } else { - model.Authorization.CurrentMemberIsAuthorized = await _authorizationPolicy.IsAuthorized(model.PlayerIdentity.Team); model.Metadata.PageTitle = "Edit " + model.PlayerIdentity.PlayerIdentityName; diff --git a/Stoolball.Web/Views/LinkedPlayersForIdentity.cshtml b/Stoolball.Web/Views/LinkedPlayersForIdentity.cshtml new file mode 100644 index 00000000..9ea623a2 --- /dev/null +++ b/Stoolball.Web/Views/LinkedPlayersForIdentity.cshtml @@ -0,0 +1,31 @@ +@inherits UmbracoViewPage +@using Stoolball.Security +@using Stoolball.Web.Statistics.Models; +@using Stoolball.Web.Teams +@using Stoolball.Web.Teams.Models +@using Umbraco.Cms.Web.Website.Controllers +@section head { + +} +@{ + Model.LinkedByMemberLabel = "Player"; + Model.LinkedByClubOrTeamLabel = "Team or club"; + Model.LinkedByStoolballEnglandLabel = "Stoolball England"; +} +@await Html.PartialAsync("_RelatedItems") +
+

Players linked to the statistics for @Model.ContextIdentity?.PlayerIdentityName

+ + @if (Model.Authorization.CurrentMemberIsAuthorized[AuthorizedAction.EditTeam]) + { +

If a player changed their name or played for multiple teams in your club (for example, your mixed and ladies teams), + their statistics may be spread over multiple players.

+ +

You should combine all the player pages into one, to tidy up the list of players in your team and to see their true statistics.

+ + } + else + { + @await Html.PartialAsync("_Login") + } +
\ No newline at end of file diff --git a/Stoolball.Web/Views/LinkedPlayersForMember.cshtml b/Stoolball.Web/Views/LinkedPlayersForMember.cshtml index d4c7b232..40d1f8ec 100644 --- a/Stoolball.Web/Views/LinkedPlayersForMember.cshtml +++ b/Stoolball.Web/Views/LinkedPlayersForMember.cshtml @@ -1,27 +1,17 @@ @using Stoolball.Statistics; @using Stoolball.Web.Statistics @using Stoolball.Web.Statistics.Models -@inherits UmbracoViewPage +@inherits UmbracoViewPage @section head { } @{ SmidgeHelper.RequiresCss(new CssFile("~/css/account.min.css") { Order = 100 }); -} -@functions { - string DisplayLinkedBy(PlayerIdentityLinkedBy linkedBy) - { - switch (linkedBy) - { - case PlayerIdentityLinkedBy.Member: - return "You"; - case PlayerIdentityLinkedBy.ClubOrTeam: - return "Your team"; - case PlayerIdentityLinkedBy.StoolballEngland: - return "Admins"; - } - return "Not linked"; - } + Model.LinkedByHeading = "Added by"; + Model.LinkedByMemberLabel = "You"; + Model.LinkedByClubOrTeamLabel = "Your team"; + Model.LinkedByStoolballEnglandLabel = "Admins"; + Model.CanUnlinkIdentitiesLinkedByMember = true; } @await Html.PartialAsync("_RelatedItems")
@@ -45,47 +35,7 @@ using (Html.BeginUmbracoForm(nameof(LinkedPlayersForMemberSurfaceController.UpdateLinkedPlayers))) { @Html.HiddenFor(m => Model.PreferredNextRoute) - - - - - - - - - - - - @for (var i = 0; i < Model.Player.PlayerIdentities.Count; i++) - { - var firstMatch = Model.Player.PlayerIdentities[i].FirstPlayed; - var lastMatch = Model.Player.PlayerIdentities[i].LastPlayed; - var matchYears = firstMatch != null && lastMatch != null ? (firstMatch.Value.Year == lastMatch.Value.Year) ? $"in {firstMatch.Value.Year}" : $"from {firstMatch.Value.Year} to {lastMatch.Value.Year}" : string.Empty; - var when = $"{Model.Player.PlayerIdentities[i].TotalMatches} {(Model.Player.PlayerIdentities[i].TotalMatches == 1 ? "match" : "matches")} {matchYears}"; - - - - - - - - - } - - - - - - - @await Html.PartialAsync("_StatisticsCacheWarning") +

Cancel

diff --git a/Stoolball.Web/Views/Partials/_LinkedPlayers.cshtml b/Stoolball.Web/Views/Partials/_LinkedPlayers.cshtml new file mode 100644 index 00000000..14317c46 --- /dev/null +++ b/Stoolball.Web/Views/Partials/_LinkedPlayers.cshtml @@ -0,0 +1,62 @@ +@model LinkedPlayersViewModel +@using Stoolball.Statistics; +@using Stoolball.Web.Statistics.Models; +@functions { + string DisplayLinkedBy(PlayerIdentityLinkedBy linkedBy) + { + switch (linkedBy) + { + case PlayerIdentityLinkedBy.Member: + return Model.LinkedByMemberLabel; + case PlayerIdentityLinkedBy.ClubOrTeam: + return Model.LinkedByClubOrTeamLabel; + case PlayerIdentityLinkedBy.StoolballEngland: + return Model.LinkedByStoolballEnglandLabel; + } + return "Default"; + } +} +@{ + if (Model.Player is null) { return; } +} + + + + + + + + + + + + @for (var i = 0; i < Model.Player.PlayerIdentities.Count; i++) + { + var firstMatch = Model.Player.PlayerIdentities[i].FirstPlayed; + var lastMatch = Model.Player.PlayerIdentities[i].LastPlayed; + var matchYears = firstMatch != null && lastMatch != null ? (firstMatch.Value.Year == lastMatch.Value.Year) ? $"in {firstMatch.Value.Year}" : $"from {firstMatch.Value.Year} to {lastMatch.Value.Year}" : string.Empty; + var when = $"{Model.Player.PlayerIdentities[i].TotalMatches} {(Model.Player.PlayerIdentities[i].TotalMatches == 1 ? "match" : "matches")} {matchYears}"; + + + + + + + + + } + + + + + + +@await Html.PartialAsync("_StatisticsCacheWarning") \ No newline at end of file diff --git a/Stoolball.Web/Views/PlayerIdentityActions.cshtml b/Stoolball.Web/Views/PlayerIdentityActions.cshtml index d9271870..027d5a50 100644 --- a/Stoolball.Web/Views/PlayerIdentityActions.cshtml +++ b/Stoolball.Web/Views/PlayerIdentityActions.cshtml @@ -14,6 +14,8 @@
Rename @Model.PlayerIdentity?.PlayerIdentityName
Rename @Model.PlayerIdentity?.PlayerIdentityName if the name is incomplete or incorrect and the correct name is not also listed.
+
Players linked to the statistics for @Model.PlayerIdentity?.PlayerIdentityName
+
If the same player is listed multiple times, combine the player pages to see their true statistics.
} else diff --git a/Stoolball.Web/Views/RenamePlayerIdentity.cshtml b/Stoolball.Web/Views/RenamePlayerIdentity.cshtml index 0f51f3dc..caee1a09 100644 --- a/Stoolball.Web/Views/RenamePlayerIdentity.cshtml +++ b/Stoolball.Web/Views/RenamePlayerIdentity.cshtml @@ -21,7 +21,7 @@
  • the name is incomplete or incorrect
  • the correct name is not also listed
  • -

    If the correct name is already listed ask the player to create an account on this website and add both names to their 'My statistics' page.

    +

    If the correct name is already listed you should link it to the statistics for @Model.PlayerIdentity?.PlayerIdentityName.

    @await Html.PartialAsync("_StatisticsCacheWarning") @using (Html.BeginUmbracoForm(nameof(RenamePlayerIdentitySurfaceController.RenamePlayerIdentity), null, new { id = "calling-it-search-disables-contact-managers" })) diff --git a/Stoolball.Web/umbraco/Deploy/Revision/document-type__b406c97934284717ade92aa16d4cb679.uda b/Stoolball.Web/umbraco/Deploy/Revision/document-type__b406c97934284717ade92aa16d4cb679.uda index 954d5a83..bf6abbed 100644 --- a/Stoolball.Web/umbraco/Deploy/Revision/document-type__b406c97934284717ade92aa16d4cb679.uda +++ b/Stoolball.Web/umbraco/Deploy/Revision/document-type__b406c97934284717ade92aa16d4cb679.uda @@ -58,6 +58,7 @@ "umb://template/7e20e79a091e4213924cdc4784b1152b", "umb://template/7e2209f722e043f78946bd091b0f3ece", "umb://template/842af0b889ea48c38e429b41b86eef3a", + "umb://template/88ad298b8eea4cca9f74308d5379de69", "umb://template/8b6f9fcb2b1a4486a5e8cd1e8c9b93f3", "umb://template/8cfc82c459294e15b572081a5a782caf", "umb://template/8d35edd4fbd0495c98d0483c9d755dd5", @@ -414,6 +415,11 @@ "Ordering": true, "Mode": 0 }, + { + "Udi": "umb://template/88ad298b8eea4cca9f74308d5379de69", + "Ordering": true, + "Mode": 0 + }, { "Udi": "umb://template/8b6f9fcb2b1a4486a5e8cd1e8c9b93f3", "Ordering": true, diff --git a/Stoolball.Web/umbraco/Deploy/Revision/template__88ad298b8eea4cca9f74308d5379de69.uda b/Stoolball.Web/umbraco/Deploy/Revision/template__88ad298b8eea4cca9f74308d5379de69.uda new file mode 100644 index 00000000..5baf0a18 --- /dev/null +++ b/Stoolball.Web/umbraco/Deploy/Revision/template__88ad298b8eea4cca9f74308d5379de69.uda @@ -0,0 +1,16 @@ +{ + "Name": "Linked Players For Identity", + "Alias": "LinkedPlayersForIdentity", + "File": "umb://template-file/LinkedPlayersForIdentity.cshtml", + "Master": null, + "Udi": "umb://template/88ad298b8eea4cca9f74308d5379de69", + "Dependencies": [ + { + "Udi": "umb://template-file/LinkedPlayersForIdentity.cshtml", + "Ordering": true, + "Mode": 0 + } + ], + "__type": "Umbraco.Deploy.Infrastructure,Umbraco.Deploy.Infrastructure.Artifacts.TemplateArtifact", + "__version": "9.4.2" +} \ No newline at end of file