diff --git a/Api/Controllers/v1/PlayersController.cs b/Api/Controllers/v1/PlayersController.cs index fa5ca23..63da4a2 100644 --- a/Api/Controllers/v1/PlayersController.cs +++ b/Api/Controllers/v1/PlayersController.cs @@ -74,6 +74,25 @@ namespace Api.Controllers.v1 } } + [HttpGet("DismissInformation/{playerId:guid}")] + public async Task> GetDismissPlayerInformation(Guid playerId, CancellationToken cancellationToken) + { + try + { + var playerDismissInformationResult = + await playerRepository.GetDismissPlayerInformationAsync(playerId, cancellationToken); + + return playerDismissInformationResult.IsFailure + ? BadRequest(playerDismissInformationResult.Error) + : Ok(playerDismissInformationResult.Value); + } + catch (Exception e) + { + logger.LogError(e, e.Message); + return StatusCode(StatusCodes.Status500InternalServerError); + } + } + [AllowAnonymous] [HttpGet("[action]/{allianceId:guid}")] public async Task>> GetAllianceMvpPlayers(Guid allianceId, diff --git a/Application/DataTransferObjects/Player/DismissPlayerInformationDto.cs b/Application/DataTransferObjects/Player/DismissPlayerInformationDto.cs new file mode 100644 index 0000000..83c4195 --- /dev/null +++ b/Application/DataTransferObjects/Player/DismissPlayerInformationDto.cs @@ -0,0 +1,31 @@ +using Application.DataTransferObjects.Admonition; +using Application.DataTransferObjects.Note; + +namespace Application.DataTransferObjects.Player; + +public class DismissPlayerInformationDto +{ + public Guid Id { get; set; } + + public required string PlayerName { get; set; } + + public DateTime DismissedAt { get; set; } + + public required string DismissalReason { get; set; } + + public ICollection Notes { get; set; } = []; + + public ICollection Admonitions { get; set; } = []; + + public ICollection DesertStormParticipants { get; set; } = []; + + public ICollection MarshalGuardParticipants { get; set; } = []; + + public ICollection VsDuelParticipants { get; set; } = []; +} + +public record DismissDesertStormParticipant(DateTime EventDate, bool Participated); + +public record DismissMarshalParticipant(DateTime EventDate, bool Participated); + +public record DismissVsDuelParticipant(DateTime EventDate, long WeeklyPoints); \ No newline at end of file diff --git a/Application/Interfaces/IPlayerRepository.cs b/Application/Interfaces/IPlayerRepository.cs index d9d3c80..f4ab013 100644 --- a/Application/Interfaces/IPlayerRepository.cs +++ b/Application/Interfaces/IPlayerRepository.cs @@ -15,6 +15,8 @@ public interface IPlayerRepository Task>> GetAllianceLeadershipMvp(Guid allianceId, CancellationToken cancellationToken); + Task> GetDismissPlayerInformationAsync(Guid playerId, CancellationToken cancellationToken); + Task> CreatePlayerAsync(CreatePlayerDto createPlayerDto, string createdBy, CancellationToken cancellationToken); Task> UpdatePlayerAsync(UpdatePlayerDto updatePlayerDto, string modifiedBy, CancellationToken cancellationToken); diff --git a/Application/Profiles/PlayerProfile.cs b/Application/Profiles/PlayerProfile.cs index ade9c03..34b5420 100644 --- a/Application/Profiles/PlayerProfile.cs +++ b/Application/Profiles/PlayerProfile.cs @@ -20,5 +20,27 @@ public class PlayerProfile : Profile CreateMap() .ForMember(des => des.ModifiedOn, opt => opt.MapFrom(src => DateTime.Now)); + + CreateMap() + .ForMember(des => des.VsDuelParticipants, + opt => opt.MapFrom(src => + src.VsDuelParticipants + .OrderByDescending(x => x.VsDuel.EventDate) + .Take(3) + .Select(x => new DismissVsDuelParticipant(x.VsDuel.EventDate, x.WeeklyPoints)))) + .ForMember(des => des.MarshalGuardParticipants, + opt => opt.MapFrom(src => + src.MarshalGuardParticipants + .OrderByDescending(x => x.MarshalGuard.EventDate) + .Take(3) + .Select(x => new DismissMarshalParticipant(x.MarshalGuard.EventDate, x.Participated)))) + .ForMember(des => des.DesertStormParticipants, + opt => opt.MapFrom(src => + src.DesertStormParticipants + .OrderByDescending(x => x.DesertStorm.EventDate) + .Take(3) + .Select(x => new DismissDesertStormParticipant(x.DesertStorm.EventDate, x.Participated)))) + .ForMember(des => des.Notes, opt => opt.MapFrom(src => src.Notes)) + .ForMember(des => des.Admonitions, opt => opt.MapFrom(src => src.Admonitions)); } } \ No newline at end of file diff --git a/Application/Repositories/PlayerRepository.cs b/Application/Repositories/PlayerRepository.cs index 189a507..fead25e 100644 --- a/Application/Repositories/PlayerRepository.cs +++ b/Application/Repositories/PlayerRepository.cs @@ -146,6 +146,19 @@ public class PlayerRepository(ApplicationContext context, IMapper mapper, ILogge return playerMvps; } + public async Task> GetDismissPlayerInformationAsync(Guid playerId, CancellationToken cancellationToken) + { + var dismissPlayerInformation = await context.Players + .IgnoreQueryFilters() + .ProjectTo(mapper.ConfigurationProvider) + .AsNoTracking() + .FirstOrDefaultAsync(player => player.Id == playerId, cancellationToken); + + return dismissPlayerInformation is null + ? Result.Failure(PlayerErrors.NotFound) + : Result.Success(dismissPlayerInformation); + } + public async Task> CreatePlayerAsync(CreatePlayerDto createPlayerDto, string createdBy, CancellationToken cancellationToken) { var newPlayer = mapper.Map(createPlayerDto); diff --git a/Ui/src/app/app.module.ts b/Ui/src/app/app.module.ts index 5aa3429..f8f3b7a 100644 --- a/Ui/src/app/app.module.ts +++ b/Ui/src/app/app.module.ts @@ -53,6 +53,7 @@ import { ZombieSiegeDetailComponent } from './pages/zombie-siege/zombie-siege-de import { CustomEventParticipantsModelComponent } from './modals/custom-event-participants-model/custom-event-participants-model.component'; import { CustomEventDetailComponent } from './pages/custom-event/custom-event-detail/custom-event-detail.component'; import { DismissPlayerComponent } from './pages/dismiss-player/dismiss-player.component'; +import { PlayerDismissInformationModalComponent } from './modals/player-dismiss-information-modal/player-dismiss-information-modal.component'; @NgModule({ declarations: [ @@ -96,7 +97,8 @@ import { DismissPlayerComponent } from './pages/dismiss-player/dismiss-player.co ZombieSiegeDetailComponent, CustomEventParticipantsModelComponent, CustomEventDetailComponent, - DismissPlayerComponent + DismissPlayerComponent, + PlayerDismissInformationModalComponent ], imports: [ BrowserModule, diff --git a/Ui/src/app/modals/player-dismiss-information-modal/player-dismiss-information-modal.component.css b/Ui/src/app/modals/player-dismiss-information-modal/player-dismiss-information-modal.component.css new file mode 100644 index 0000000..e69de29 diff --git a/Ui/src/app/modals/player-dismiss-information-modal/player-dismiss-information-modal.component.html b/Ui/src/app/modals/player-dismiss-information-modal/player-dismiss-information-modal.component.html new file mode 100644 index 0000000..30c739e --- /dev/null +++ b/Ui/src/app/modals/player-dismiss-information-modal/player-dismiss-information-modal.component.html @@ -0,0 +1,99 @@ +@if (dismissPlayerInformation) { + + + + +} + + diff --git a/Ui/src/app/modals/player-dismiss-information-modal/player-dismiss-information-modal.component.ts b/Ui/src/app/modals/player-dismiss-information-modal/player-dismiss-information-modal.component.ts new file mode 100644 index 0000000..0ea4949 --- /dev/null +++ b/Ui/src/app/modals/player-dismiss-information-modal/player-dismiss-information-modal.component.ts @@ -0,0 +1,39 @@ +import {Component, inject, Input, OnInit} from '@angular/core'; +import { DismissPlayerInformationModel} from "../../models/player.model"; +import {NgbActiveModal} from "@ng-bootstrap/ng-bootstrap"; +import {PlayerService} from "../../services/player.service"; +import {ToastrService} from "ngx-toastr"; + +@Component({ + selector: 'app-player-dismiss-information-modal', + templateUrl: './player-dismiss-information-modal.component.html', + styleUrl: './player-dismiss-information-modal.component.css' +}) +export class PlayerDismissInformationModalComponent implements OnInit { + + private readonly _playerService: PlayerService = inject(PlayerService); + private readonly _toastr: ToastrService = inject(ToastrService); + + public dismissPlayerInformation: DismissPlayerInformationModel | undefined; + public activeModal: NgbActiveModal = inject(NgbActiveModal); + + @Input({required: true}) playerId!: string; + + ngOnInit() { + this.getDismissPlayerInformation(); + } + + getDismissPlayerInformation() { + this._playerService.getDismissPlayerInformation(this.playerId).subscribe({ + next: ((response) => { + if (response) { + this.dismissPlayerInformation = response; + } + }), + error: ((error) => { + console.log(error); + this._toastr.error('Could not get dismissPlayerInformation', 'Get Player Information'); + }) + }); + } +} diff --git a/Ui/src/app/models/player.model.ts b/Ui/src/app/models/player.model.ts index 1419af6..1c8f91b 100644 --- a/Ui/src/app/models/player.model.ts +++ b/Ui/src/app/models/player.model.ts @@ -1,3 +1,9 @@ +import {NoteModel} from "./note.model"; +import {AdmonitionModel} from "./admonition.model"; +import {DesertStormParticipantModel} from "./desertStormParticipant.model"; +import {MarshalGuardParticipantModel} from "./marshalGuardParticipant.model"; +import {VsDuelParticipantModel} from "./vsDuelParticipant.model"; + export interface PlayerModel { id: string; playerName: string; @@ -11,6 +17,9 @@ export interface PlayerModel { modifiedBy?: string; notesCount: number; admonitionsCount: number; + isDismissed: boolean; + dismissedAt?: Date; + dismissalReason?: string; } export interface CreatePlayerModel { @@ -26,3 +35,15 @@ export interface UpdatePlayerModel { rankId: string; level: number; } + +export interface DismissPlayerInformationModel { + id: string; + playerName: string; + dismissedAt: Date; + dismissalReason: string; + notes: NoteModel[], + admonitions: AdmonitionModel[], + desertStormParticipants: {eventDate: Date, participated: boolean}[], + marshalGuardParticipants: {eventDate: Date, participated: boolean}[], + vsDuelParticipants: {eventDate: Date, weeklyPoints: number}[], +} diff --git a/Ui/src/app/pages/dismiss-player/dismiss-player.component.html b/Ui/src/app/pages/dismiss-player/dismiss-player.component.html index 7faca1d..10ea456 100644 --- a/Ui/src/app/pages/dismiss-player/dismiss-player.component.html +++ b/Ui/src/app/pages/dismiss-player/dismiss-player.component.html @@ -1,5 +1,42 @@

Dismissed Players

- + @if (dismissedPlayers.length > 0) { +
+ + + + + + + + + + + + @for (player of dismissedPlayers | paginate: { itemsPerPage: itemsPerPage, currentPage: page, id: 'dismissedTable'}; track player.id) { + + + + + + + + } + +
PlayerDismissed onDismissed byReasonAction
{{player.playerName}}{{player.dismissedAt | date: 'dd.MM.yyyy'}}{{player.modifiedBy}}{{player.dismissalReason}} +
+ + + +
+
+
+ + + } @else { + + }
diff --git a/Ui/src/app/pages/dismiss-player/dismiss-player.component.ts b/Ui/src/app/pages/dismiss-player/dismiss-player.component.ts index 598bfa6..c233558 100644 --- a/Ui/src/app/pages/dismiss-player/dismiss-player.component.ts +++ b/Ui/src/app/pages/dismiss-player/dismiss-player.component.ts @@ -1,10 +1,117 @@ -import { Component } from '@angular/core'; +import {Component, inject, OnInit} from '@angular/core'; +import {PlayerService} from "../../services/player.service"; +import {ToastrService} from "ngx-toastr"; +import {PlayerModel} from "../../models/player.model"; +import {JwtTokenService} from "../../services/jwt-token.service"; +import Swal from "sweetalert2"; +import {Router} from "@angular/router"; +import {NgbModal} from "@ng-bootstrap/ng-bootstrap"; +import { + PlayerDismissInformationModalComponent +} from "../../modals/player-dismiss-information-modal/player-dismiss-information-modal.component"; @Component({ selector: 'app-dismiss-player', templateUrl: './dismiss-player.component.html', styleUrl: './dismiss-player.component.css' }) -export class DismissPlayerComponent { +export class DismissPlayerComponent implements OnInit { + private readonly _playerService: PlayerService = inject(PlayerService); + private readonly _toastr: ToastrService = inject(ToastrService); + private readonly _tokenService: JwtTokenService = inject(JwtTokenService); + private readonly _router: Router = inject(Router); + private readonly _modalService: NgbModal = inject(NgbModal); + + private allianceId: string = this._tokenService.getAllianceId()!; + + public dismissedPlayers: PlayerModel[] = []; + itemsPerPage: string | number = 10; + page: string | number = 1; + + + + ngOnInit() { + this.getDismissedPlayers(); + } + + getDismissedPlayers() { + this._playerService.getDismissedPlayers(this.allianceId).subscribe({ + next: ((response) => { + if (response) { + this.dismissedPlayers = response; + } else { + this.dismissedPlayers = []; + } + }), + error: (error) => { + console.log(error); + this._toastr.error('Could not get dismissedPlayers', 'Get DismissedPlayers'); + } + }) + } + + onDeletePlayer(player: PlayerModel) { + Swal.fire({ + title: `Delete player ${player.playerName}?`, + text: `All data is permanently deleted and cannot be restored`, + icon: "warning", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes, delete it!" + }).then((result) => { + if (result.isConfirmed) { + this._playerService.deletePlayer(player.id).subscribe({ + next: ((response) => { + if (response) { + Swal.fire({ + title: "Deleted!", + text: "Player has been deleted", + icon: "success" + }).then(_ => this.getDismissedPlayers()); + } + }), + error: (error: Error) => { + console.log(error); + } + }); + } + }); + } + + onReactivePlayer(player: any) { + Swal.fire({ + title: `Reactive player ${player.playerName}?`, + text: `The player is accepted back into the alliance`, + icon: "info", + showCancelButton: true, + confirmButtonColor: "#3085d6", + cancelButtonColor: "#d33", + confirmButtonText: "Yes reactive player" + }).then((result) => { + if (result.isConfirmed) { + this._playerService.reactivePlayer(player.id).subscribe({ + next: ((response) => { + if (response) { + Swal.fire({ + title: "Reactivated!", + text: "Player has been reactivated", + icon: "success" + }).then(_ => this._router.navigate(['/players'])); + } + }), + error: (error: Error) => { + console.log(error); + } + }); + } + }); + } + + onPlayerInformation(player: PlayerModel) { + const modalRef = this._modalService.open(PlayerDismissInformationModalComponent, + {animation: true, backdrop: true, centered: true, size: 'lg', scrollable: true}); + modalRef.componentInstance.playerId = player.id; + } } diff --git a/Ui/src/app/pages/player/player.component.ts b/Ui/src/app/pages/player/player.component.ts index 4075314..dd2c0fc 100644 --- a/Ui/src/app/pages/player/player.component.ts +++ b/Ui/src/app/pages/player/player.component.ts @@ -158,7 +158,8 @@ export class PlayerComponent implements OnInit { rankId: '', createdOn: new Date(), rankName: '', - createdBy: '' + createdBy: '', + isDismissed: false, } const modalRef = this._modalService.open(PlayerEditModalComponent, {animation: true, backdrop: 'static', centered: true, size: 'lg'}); diff --git a/Ui/src/app/pages/vs-duel/vs-duel.component.html b/Ui/src/app/pages/vs-duel/vs-duel.component.html index 5104152..51aa177 100644 --- a/Ui/src/app/pages/vs-duel/vs-duel.component.html +++ b/Ui/src/app/pages/vs-duel/vs-duel.component.html @@ -42,7 +42,7 @@ {{vsDuel.eventDate | week}} {{vsDuel.opponentName}} {{vsDuel.opponentServer}} - {{vsDuel.opponentPower}} + {{vsDuel.opponentPower | number}} {{vsDuel.opponentSize}} @if (vsDuel.isInProgress) { diff --git a/Ui/src/app/services/player.service.ts b/Ui/src/app/services/player.service.ts index 016bf9c..d4a3c09 100644 --- a/Ui/src/app/services/player.service.ts +++ b/Ui/src/app/services/player.service.ts @@ -2,7 +2,12 @@ import {inject, Injectable} from '@angular/core'; import {environment} from "../../environments/environment"; import {HttpClient} from "@angular/common/http"; import {Observable} from "rxjs"; -import {CreatePlayerModel, PlayerModel, UpdatePlayerModel} from "../models/player.model"; +import { + CreatePlayerModel, + DismissPlayerInformationModel, + PlayerModel, + UpdatePlayerModel +} from "../models/player.model"; @Injectable({ providedIn: 'root' @@ -20,6 +25,14 @@ export class PlayerService { return this._httpClient.get(this._serviceUrl + 'Alliance/' + allianceId); } + public getDismissedPlayers(allianceId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'Alliance/dismiss/' + allianceId); + } + + public getDismissPlayerInformation(playerId: string): Observable { + return this._httpClient.get(this._serviceUrl + 'DismissInformation/' + playerId); + } + public updatePlayer(playerId: string, player: UpdatePlayerModel): Observable { return this._httpClient.put(this._serviceUrl + playerId, player); } @@ -32,6 +45,13 @@ export class PlayerService { return this._httpClient.put(this._serviceUrl + playerId + '/dismiss', dismissRequest); } + public reactivePlayer(playerId: string): Observable { + const reactiveRequest = { + id: playerId, + }; + return this._httpClient.put(this._serviceUrl + playerId + '/reactive', reactiveRequest); + } + public insertPlayer(player: CreatePlayerModel): Observable { return this._httpClient.post(this._serviceUrl, player); }