implement dismiss page

This commit is contained in:
Tomasi - Developing 2025-01-20 11:15:04 +01:00
parent c7bccfb9b0
commit 9931a9a3c2
15 changed files with 420 additions and 7 deletions

View File

@ -74,6 +74,25 @@ namespace Api.Controllers.v1
}
}
[HttpGet("DismissInformation/{playerId:guid}")]
public async Task<ActionResult<DismissPlayerInformationDto>> 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<ActionResult<List<PlayerMvpDto>>> GetAllianceMvpPlayers(Guid allianceId,

View File

@ -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<NoteDto> Notes { get; set; } = [];
public ICollection<AdmonitionDto> Admonitions { get; set; } = [];
public ICollection<DismissDesertStormParticipant> DesertStormParticipants { get; set; } = [];
public ICollection<DismissMarshalParticipant> MarshalGuardParticipants { get; set; } = [];
public ICollection<DismissVsDuelParticipant> VsDuelParticipants { get; set; } = [];
}
public record DismissDesertStormParticipant(DateTime EventDate, bool Participated);
public record DismissMarshalParticipant(DateTime EventDate, bool Participated);
public record DismissVsDuelParticipant(DateTime EventDate, long WeeklyPoints);

View File

@ -15,6 +15,8 @@ public interface IPlayerRepository
Task<Result<List<PlayerMvpDto>>> GetAllianceLeadershipMvp(Guid allianceId, CancellationToken cancellationToken);
Task<Result<DismissPlayerInformationDto>> GetDismissPlayerInformationAsync(Guid playerId, CancellationToken cancellationToken);
Task<Result<PlayerDto>> CreatePlayerAsync(CreatePlayerDto createPlayerDto, string createdBy, CancellationToken cancellationToken);
Task<Result<PlayerDto>> UpdatePlayerAsync(UpdatePlayerDto updatePlayerDto, string modifiedBy, CancellationToken cancellationToken);

View File

@ -20,5 +20,27 @@ public class PlayerProfile : Profile
CreateMap<UpdatePlayerDto, Player>()
.ForMember(des => des.ModifiedOn, opt => opt.MapFrom(src => DateTime.Now));
CreateMap<Player, DismissPlayerInformationDto>()
.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));
}
}

View File

@ -146,6 +146,19 @@ public class PlayerRepository(ApplicationContext context, IMapper mapper, ILogge
return playerMvps;
}
public async Task<Result<DismissPlayerInformationDto>> GetDismissPlayerInformationAsync(Guid playerId, CancellationToken cancellationToken)
{
var dismissPlayerInformation = await context.Players
.IgnoreQueryFilters()
.ProjectTo<DismissPlayerInformationDto>(mapper.ConfigurationProvider)
.AsNoTracking()
.FirstOrDefaultAsync(player => player.Id == playerId, cancellationToken);
return dismissPlayerInformation is null
? Result.Failure<DismissPlayerInformationDto>(PlayerErrors.NotFound)
: Result.Success(dismissPlayerInformation);
}
public async Task<Result<PlayerDto>> CreatePlayerAsync(CreatePlayerDto createPlayerDto, string createdBy, CancellationToken cancellationToken)
{
var newPlayer = mapper.Map<Player>(createPlayerDto);

View File

@ -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,

View File

@ -0,0 +1,99 @@
@if (dismissPlayerInformation) {
<div class="modal-header" xmlns="http://www.w3.org/1999/html" xmlns="http://www.w3.org/1999/html">
<h4 class="modal-title">Information for player {{dismissPlayerInformation.playerName}}</h4>
<button type="button" class="btn-close" aria-label="Close" (click)="activeModal.dismiss()"></button>
</div>
<div class="modal-body">
<p><b class="text-primary">Dismissed at:</b> {{dismissPlayerInformation.dismissedAt | date: 'dd.MM.yyyy'}}</p>
<p><b class="text-primary">Reason:</b> {{dismissPlayerInformation.dismissalReason}}</p>
<hr>
<h5 class="text-primary">Admonitions:</h5>
@for (admonition of dismissPlayerInformation.admonitions; track admonition.id) {
<ul>
<li><small>({{admonition.createdOn | date: 'dd.MM.yyyy'}})</small> {{admonition.reason}}</li>
</ul>
}
<hr>
<h5 class="text-primary">Notes:</h5>
@for (note of dismissPlayerInformation.notes; track note.id) {
<ul>
<li><small>({{note.createdOn | date: 'dd.MM.yyyy'}})</small> {{note.playerNote}}</li>
</ul>
}
<hr>
<div class="d-flex flex-row align-items-center">
<h5 class="text-primary">Marshal: </h5>
<small class="ps-2">(Player's last 3 available events)</small>
</div>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th scope="col">Event date</th>
<th scope="col">Participated</th>
</tr>
</thead>
<tbody>
@for (marshal of dismissPlayerInformation.marshalGuardParticipants; track marshal.eventDate) {
<tr>
<td>{{marshal.eventDate | date: 'dd.MM.yyyy'}}</td>
<td><i class="bi " [ngClass]="marshal.participated ? 'bi-check-lg text-success' : 'bi-x-lg text-danger'"></i></td>
</tr>
}
</tbody>
</table>
</div>
<hr>
<div class="d-flex flex-row align-items-center">
<h5 class="text-primary">VS Duel: </h5>
<small class="ps-2">(Player's last 3 available events)</small>
</div>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th scope="col">Event date</th>
<th scope="col">Weekly points</th>
</tr>
</thead>
<tbody>
@for (vsDuel of dismissPlayerInformation.vsDuelParticipants; track vsDuel.eventDate) {
<tr>
<td>{{vsDuel.eventDate | date: 'dd.MM.yyyy'}}</td>
<td>{{vsDuel.weeklyPoints | number}}</td>
</tr>
}
</tbody>
</table>
</div>
<hr>
<div class="d-flex flex-row align-items-center">
<h5 class="text-primary">Desert storm: </h5>
<small class="ps-2">(Player's last 3 available events)</small>
</div>
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th scope="col">Event date</th>
<th scope="col">Participated</th>
</tr>
</thead>
<tbody>
@for (desertStorm of dismissPlayerInformation.desertStormParticipants; track desertStorm.eventDate) {
<tr>
<td>{{desertStorm.eventDate | date: 'dd.MM.yyyy'}}</td>
<td><i class="bi " [ngClass]="desertStorm.participated ? 'bi-check-lg text-success' : 'bi-x-lg text-danger'"></i></td>
</tr>
}
</tbody>
</table>
</div>
</div>
}

View File

@ -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');
})
});
}
}

View File

@ -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}[],
}

View File

@ -1,5 +1,42 @@
<div class="container mt-3 pb-5">
<h2 class="text-center">Dismissed Players</h2>
<app-under-development></app-under-development>
@if (dismissedPlayers.length > 0) {
<div class="table-responsive">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th scope="col">Player</th>
<th scope="col">Dismissed on</th>
<th scope="col">Dismissed by</th>
<th scope="col">Reason</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
@for (player of dismissedPlayers | paginate: { itemsPerPage: itemsPerPage, currentPage: page, id: 'dismissedTable'}; track player.id) {
<tr>
<td>{{player.playerName}}</td>
<td>{{player.dismissedAt | date: 'dd.MM.yyyy'}}</td>
<td>{{player.modifiedBy}}</td>
<td>{{player.dismissalReason}}</td>
<td>
<div class="d-flex gap-3 justify-content-around">
<i (click)="onPlayerInformation(player)" class="bi custom-info-icon bi-info-circle-fill"></i>
<i (click)="onReactivePlayer(player)" class="bi custom-edit-icon bi-bootstrap-reboot"></i>
<i (click)="onDeletePlayer(player)" class="bi custom-delete-icon bi bi-trash3"></i>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
<pagination-controls class="custom-pagination" [responsive]="true" [id]="'dismissedTable'" (pageChange)="page = $event"></pagination-controls>
} @else {
<div class="alert alert-secondary text-center mt-5" role="alert">
No dismissed players
</div>
}
</div>

View File

@ -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;
}
}

View File

@ -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'});

View File

@ -42,7 +42,7 @@
<td>{{vsDuel.eventDate | week}}</td>
<td>{{vsDuel.opponentName}}</td>
<td>{{vsDuel.opponentServer}}</td>
<td>{{vsDuel.opponentPower}}</td>
<td>{{vsDuel.opponentPower | number}}</td>
<td>{{vsDuel.opponentSize}}</td>
<td>
@if (vsDuel.isInProgress) {

View File

@ -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<PlayerModel[]>(this._serviceUrl + 'Alliance/' + allianceId);
}
public getDismissedPlayers(allianceId: string): Observable<PlayerModel[]> {
return this._httpClient.get<PlayerModel[]>(this._serviceUrl + 'Alliance/dismiss/' + allianceId);
}
public getDismissPlayerInformation(playerId: string): Observable<DismissPlayerInformationModel> {
return this._httpClient.get<DismissPlayerInformationModel>(this._serviceUrl + 'DismissInformation/' + playerId);
}
public updatePlayer(playerId: string, player: UpdatePlayerModel): Observable<PlayerModel> {
return this._httpClient.put<PlayerModel>(this._serviceUrl + playerId, player);
}
@ -32,6 +45,13 @@ export class PlayerService {
return this._httpClient.put<PlayerModel>(this._serviceUrl + playerId + '/dismiss', dismissRequest);
}
public reactivePlayer(playerId: string): Observable<PlayerModel> {
const reactiveRequest = {
id: playerId,
};
return this._httpClient.put<PlayerModel>(this._serviceUrl + playerId + '/reactive', reactiveRequest);
}
public insertPlayer(player: CreatePlayerModel): Observable<PlayerModel> {
return this._httpClient.post<PlayerModel>(this._serviceUrl, player);
}