mirror of
https://github.com/TomasiDeveloping/PlayerManagement.git
synced 2026-04-16 17:22:21 +00:00
v 0.9.0
This commit is contained in:
parent
c48d8d8211
commit
711fb4adaa
134
Api/Controllers/v1/CustomEventCategoriesController.cs
Normal file
134
Api/Controllers/v1/CustomEventCategoriesController.cs
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
using Application.DataTransferObjects.CustomEventCategory;
|
||||||
|
using Application.Errors;
|
||||||
|
using Application.Interfaces;
|
||||||
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Api.Controllers.v1
|
||||||
|
{
|
||||||
|
[Route("api/v{version:apiVersion}/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
[ApiVersion("1.0")]
|
||||||
|
[Authorize]
|
||||||
|
public class CustomEventCategoriesController(ICustomEventCategoryRepository customEventCategoryRepository, ILogger<CustomEventCategoriesController> logger) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet("{customEventCategoryId:guid}")]
|
||||||
|
public async Task<ActionResult<CustomEventCategoryDto>> GetCustomEventCategory(Guid customEventCategoryId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var customEventCategoryResult =
|
||||||
|
await customEventCategoryRepository.GetCustomEventCategoryAsync(customEventCategoryId, cancellationToken);
|
||||||
|
|
||||||
|
return customEventCategoryResult.IsFailure
|
||||||
|
? BadRequest(customEventCategoryResult.Error)
|
||||||
|
: Ok(customEventCategoryResult.Value);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "{ErrorMessage}", e.Message);
|
||||||
|
return Problem(
|
||||||
|
detail: $"Failed to process {nameof(GetCustomEventCategory)}",
|
||||||
|
statusCode: StatusCodes.Status500InternalServerError,
|
||||||
|
title: "Internal server error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("Alliance/{allianceId:guid}")]
|
||||||
|
public async Task<ActionResult<List<CustomEventCategoryDto>>> GetAllianceCustomEventCategories(Guid allianceId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var allianceCustomEventCategoriesResult =
|
||||||
|
await customEventCategoryRepository.GetAllianceCustomEventCategoriesAsync(allianceId, cancellationToken);
|
||||||
|
|
||||||
|
if (allianceCustomEventCategoriesResult.IsFailure) return BadRequest(allianceCustomEventCategoriesResult.Error);
|
||||||
|
|
||||||
|
return allianceCustomEventCategoriesResult.Value.Count > 0
|
||||||
|
? Ok(allianceCustomEventCategoriesResult.Value)
|
||||||
|
: NoContent();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "{ErrorMessage}", e.Message);
|
||||||
|
return Problem(
|
||||||
|
detail: $"Failed to process {nameof(GetAllianceCustomEventCategories)}",
|
||||||
|
statusCode: StatusCodes.Status500InternalServerError,
|
||||||
|
title: "Internal server error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost]
|
||||||
|
public async Task<ActionResult<CustomEventCategoryDto>> CreateCustomEventCategory(CreateCustomEventCategoryDto createCustomEventCategoryDto,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid) return UnprocessableEntity(ModelState);
|
||||||
|
|
||||||
|
var createResult = await customEventCategoryRepository.CreateCustomEventCategoryAsync(createCustomEventCategoryDto, cancellationToken);
|
||||||
|
|
||||||
|
return createResult.IsFailure
|
||||||
|
? BadRequest(createResult.Error)
|
||||||
|
: CreatedAtAction(nameof(GetCustomEventCategory), new { customEventCategoryId = createResult.Value.Id },
|
||||||
|
createResult.Value);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "{ErrorMessage}", e.Message);
|
||||||
|
return Problem(
|
||||||
|
detail: $"Failed to process {nameof(CreateCustomEventCategory)}",
|
||||||
|
statusCode: StatusCodes.Status500InternalServerError,
|
||||||
|
title: "Internal server error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPut("{customEventCategoryId:guid}")]
|
||||||
|
public async Task<ActionResult<CustomEventCategoryDto>> UpdateCustomEventCategory(Guid customEventCategoryId,
|
||||||
|
UpdateCustomEventCategoryDto updateCustomEventCategoryDto, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid) return UnprocessableEntity(ModelState);
|
||||||
|
|
||||||
|
if (updateCustomEventCategoryDto.Id != customEventCategoryId) return Conflict(CustomEventCategoryErrors.IdConflict);
|
||||||
|
|
||||||
|
var updateResult = await customEventCategoryRepository.UpdateCustomEventCategoryAsync(updateCustomEventCategoryDto, cancellationToken);
|
||||||
|
|
||||||
|
return updateResult.IsFailure
|
||||||
|
? BadRequest(updateResult.Error)
|
||||||
|
: Ok(updateResult.Value);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "{ErrorMessage}", e.Message);
|
||||||
|
return Problem(
|
||||||
|
detail: $"Failed to process {nameof(UpdateCustomEventCategory)}",
|
||||||
|
statusCode: StatusCodes.Status500InternalServerError,
|
||||||
|
title: "Internal server error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{customEventCategoryId:guid}")]
|
||||||
|
public async Task<ActionResult<bool>> DeleteCustomEventCategory(Guid customEventCategoryId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var deleteResult = await customEventCategoryRepository.DeleteCustomEventAsync(customEventCategoryId, cancellationToken);
|
||||||
|
|
||||||
|
return deleteResult.IsFailure
|
||||||
|
? BadRequest(deleteResult.Error)
|
||||||
|
: Ok(deleteResult.Value);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "{ErrorMessage}", e.Message);
|
||||||
|
return Problem(
|
||||||
|
detail: $"Failed to process {nameof(DeleteCustomEventCategory)}",
|
||||||
|
statusCode: StatusCodes.Status500InternalServerError,
|
||||||
|
title: "Internal server error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
83
Api/Controllers/v1/CustomEventLeaderboardsController.cs
Normal file
83
Api/Controllers/v1/CustomEventLeaderboardsController.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
using Application.DataTransferObjects.CustomEventLeaderboard;
|
||||||
|
using Application.Repositories;
|
||||||
|
using Asp.Versioning;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
||||||
|
namespace Api.Controllers.v1
|
||||||
|
{
|
||||||
|
[Route("api/v{version:apiVersion}/[controller]")]
|
||||||
|
[ApiController]
|
||||||
|
[ApiVersion("1.0")]
|
||||||
|
[Authorize]
|
||||||
|
public class CustomEventLeaderboardsController(ICustomEventLeaderBoardRepository customEventLeaderBoardRepository, ILogger<CustomEventLeaderboardsController> logger) : ControllerBase
|
||||||
|
{
|
||||||
|
[HttpGet("point/{customEventCategoryId:guid}")]
|
||||||
|
public async Task<ActionResult<LeaderboardPointEventDto>> GetPointEvent(Guid customEventCategoryId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pointEventLeaderboardResult = await customEventLeaderBoardRepository.GetPointEventLeaderboardAsync(customEventCategoryId, cancellationToken);
|
||||||
|
|
||||||
|
if (pointEventLeaderboardResult.IsFailure) return BadRequest(pointEventLeaderboardResult.Error);
|
||||||
|
|
||||||
|
return pointEventLeaderboardResult.Value.Count > 0
|
||||||
|
? Ok(pointEventLeaderboardResult.Value)
|
||||||
|
: NoContent();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "{ErrorMessage}", e.Message);
|
||||||
|
return Problem(
|
||||||
|
detail: $"Failed to process {nameof(GetPointEvent)}",
|
||||||
|
statusCode: StatusCodes.Status500InternalServerError,
|
||||||
|
title: "Internal server error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("participation/{customEventCategoryId:guid}")]
|
||||||
|
public async Task<ActionResult<LeaderboardParticipationEventDto>> GetParticipationEvent(Guid customEventCategoryId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var participationEventLeaderboardResult = await customEventLeaderBoardRepository.GetParticipationEventLeaderboardAsync(customEventCategoryId, cancellationToken);
|
||||||
|
if (participationEventLeaderboardResult.IsFailure) return BadRequest(participationEventLeaderboardResult.Error);
|
||||||
|
return participationEventLeaderboardResult.Value.Count > 0
|
||||||
|
? Ok(participationEventLeaderboardResult.Value)
|
||||||
|
: NoContent();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "{ErrorMessage}", e.Message);
|
||||||
|
return Problem(
|
||||||
|
detail: $"Failed to process {nameof(GetParticipationEvent)}",
|
||||||
|
statusCode: StatusCodes.Status500InternalServerError,
|
||||||
|
title: "Internal server error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("point-and-participation/{customEventCategoryId:guid}")]
|
||||||
|
public async Task<ActionResult<LeaderboardPointAndParticipationEventDto>> GetPointAndParticipationEvent(Guid customEventCategoryId,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var pointAndParticipationEventLeaderboardResult = await customEventLeaderBoardRepository.GetPointAndParticipationEventLeaderboardAsync(customEventCategoryId, cancellationToken);
|
||||||
|
if (pointAndParticipationEventLeaderboardResult.IsFailure) return BadRequest(pointAndParticipationEventLeaderboardResult.Error);
|
||||||
|
return pointAndParticipationEventLeaderboardResult.Value.Count > 0
|
||||||
|
? Ok(pointAndParticipationEventLeaderboardResult.Value)
|
||||||
|
: NoContent();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "{ErrorMessage}", e.Message);
|
||||||
|
return Problem(
|
||||||
|
detail: $"Failed to process {nameof(GetPointAndParticipationEvent)}",
|
||||||
|
statusCode: StatusCodes.Status500InternalServerError,
|
||||||
|
title: "Internal server error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -34,6 +34,8 @@ public static class ApplicationDependencyInjection
|
|||||||
services.AddScoped<IZombieSiegeParticipantRepository, ZombieSiegeParticipantRepository>();
|
services.AddScoped<IZombieSiegeParticipantRepository, ZombieSiegeParticipantRepository>();
|
||||||
services.AddScoped<IVsDuelLeagueRepository, VsDuelLeagueRepository>();
|
services.AddScoped<IVsDuelLeagueRepository, VsDuelLeagueRepository>();
|
||||||
services.AddScoped<IApiKeyRepository, ApiKeyRepository>();
|
services.AddScoped<IApiKeyRepository, ApiKeyRepository>();
|
||||||
|
services.AddScoped<ICustomEventCategoryRepository, CustomEventCategoryRepository>();
|
||||||
|
services.AddScoped<ICustomEventLeaderBoardRepository, CustomEventLeaderboardRepository>();
|
||||||
|
|
||||||
|
|
||||||
services.AddTransient<IJwtService, JwtService>();
|
services.AddTransient<IJwtService, JwtService>();
|
||||||
|
|||||||
@ -21,6 +21,8 @@ public class CreateCustomEventDto
|
|||||||
[Required]
|
[Required]
|
||||||
public Guid AllianceId { get; set; }
|
public Guid AllianceId { get; set; }
|
||||||
|
|
||||||
|
public Guid? CustomEventCategoryId { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
public required string EventDate { get; set; }
|
public required string EventDate { get; set; }
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,10 @@ public class CustomEventDto
|
|||||||
|
|
||||||
public Guid AllianceId { get; set; }
|
public Guid AllianceId { get; set; }
|
||||||
|
|
||||||
|
public Guid? CustomEventCategoryId { get; set; }
|
||||||
|
|
||||||
|
public string? CategoryName { get; set; }
|
||||||
|
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|
||||||
public required string Description { get; set; }
|
public required string Description { get; set; }
|
||||||
|
|||||||
@ -7,6 +7,8 @@ public class UpdateCustomEventDto
|
|||||||
[Required]
|
[Required]
|
||||||
public Guid Id { get; set; }
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public Guid? CustomEventCategoryId { get; set; }
|
||||||
|
|
||||||
[Required]
|
[Required]
|
||||||
[MaxLength(150)]
|
[MaxLength(150)]
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Application.DataTransferObjects.CustomEventCategory;
|
||||||
|
|
||||||
|
public class CreateCustomEventCategoryDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public Guid AllianceId { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(250)]
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public bool IsPointsEvent { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public bool IsParticipationEvent { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
namespace Application.DataTransferObjects.CustomEventCategory;
|
||||||
|
|
||||||
|
public class CustomEventCategoryDto
|
||||||
|
{
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
public Guid AllianceId { get; set; }
|
||||||
|
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
public bool IsPointsEvent { get; set; }
|
||||||
|
|
||||||
|
public bool IsParticipationEvent { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,19 @@
|
|||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
|
namespace Application.DataTransferObjects.CustomEventCategory;
|
||||||
|
|
||||||
|
public class UpdateCustomEventCategoryDto
|
||||||
|
{
|
||||||
|
[Required]
|
||||||
|
public Guid Id { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
[MaxLength(250)]
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public bool IsPointsEvent { get; set; }
|
||||||
|
|
||||||
|
[Required]
|
||||||
|
public bool IsParticipationEvent { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
namespace Application.DataTransferObjects.CustomEventLeaderboard;
|
||||||
|
|
||||||
|
public class LeaderboardParticipationEventDto
|
||||||
|
{
|
||||||
|
public required string PlayerName { get; set; }
|
||||||
|
|
||||||
|
public int Participations { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
namespace Application.DataTransferObjects.CustomEventLeaderboard;
|
||||||
|
|
||||||
|
public class LeaderboardPointAndParticipationEventDto
|
||||||
|
{
|
||||||
|
public required string PlayerName { get; set; }
|
||||||
|
|
||||||
|
public long Points { get; set; }
|
||||||
|
|
||||||
|
public int Participations { get; set; }
|
||||||
|
|
||||||
|
public double TotalPoints { get; set; }
|
||||||
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
namespace Application.DataTransferObjects.CustomEventLeaderboard;
|
||||||
|
|
||||||
|
public class LeaderboardPointEventDto
|
||||||
|
{
|
||||||
|
public required string PlayerName { get; set; }
|
||||||
|
|
||||||
|
public long Points { get; set; }
|
||||||
|
}
|
||||||
9
Application/Errors/CustomEventCategoryErrors.cs
Normal file
9
Application/Errors/CustomEventCategoryErrors.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
namespace Application.Errors;
|
||||||
|
|
||||||
|
public class CustomEventCategoryErrors
|
||||||
|
{
|
||||||
|
public static readonly Error NotFound = new("Error.CustomEventCategory.NotFound",
|
||||||
|
"The custom event category with the specified identifier was not found");
|
||||||
|
|
||||||
|
public static readonly Error IdConflict = new("Error.CustomEventCategory.IdConflict", "There is a conflict with the id's");
|
||||||
|
}
|
||||||
17
Application/Interfaces/ICustomEventCategoryRepository.cs
Normal file
17
Application/Interfaces/ICustomEventCategoryRepository.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using Application.Classes;
|
||||||
|
using Application.DataTransferObjects.CustomEventCategory;
|
||||||
|
|
||||||
|
namespace Application.Interfaces;
|
||||||
|
|
||||||
|
public interface ICustomEventCategoryRepository
|
||||||
|
{
|
||||||
|
Task<Result<CustomEventCategoryDto>> GetCustomEventCategoryAsync(Guid customEventCategoryId, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<Result<List<CustomEventCategoryDto>>> GetAllianceCustomEventCategoriesAsync(Guid allianceId, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<Result<CustomEventCategoryDto>> CreateCustomEventCategoryAsync(CreateCustomEventCategoryDto createCustomEventCategoryDto, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<Result<CustomEventCategoryDto>> UpdateCustomEventCategoryAsync(UpdateCustomEventCategoryDto updateCustomEventCategoryDto, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<Result<bool>> DeleteCustomEventAsync(Guid customEventId, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
18
Application/Profiles/CustomEventCategoryProfile.cs
Normal file
18
Application/Profiles/CustomEventCategoryProfile.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using Application.DataTransferObjects.CustomEventCategory;
|
||||||
|
using AutoMapper;
|
||||||
|
using Database.Entities;
|
||||||
|
|
||||||
|
namespace Application.Profiles;
|
||||||
|
|
||||||
|
public class CustomEventCategoryProfile : Profile
|
||||||
|
{
|
||||||
|
public CustomEventCategoryProfile()
|
||||||
|
{
|
||||||
|
CreateMap<CustomEventCategory, CustomEventCategoryDto>();
|
||||||
|
|
||||||
|
CreateMap<CreateCustomEventCategoryDto, CustomEventCategory>()
|
||||||
|
.ForMember(des => des.Id, opt => opt.MapFrom(src => Guid.CreateVersion7()));
|
||||||
|
|
||||||
|
CreateMap<UpdateCustomEventCategoryDto, CustomEventCategory>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -8,7 +8,8 @@ public class CustomEventProfile : Profile
|
|||||||
{
|
{
|
||||||
public CustomEventProfile()
|
public CustomEventProfile()
|
||||||
{
|
{
|
||||||
CreateMap<CustomEvent, CustomEventDto>();
|
CreateMap<CustomEvent, CustomEventDto>()
|
||||||
|
.ForMember(des => des.CategoryName, opt => opt.MapFrom(src => src.CustomEventCategory!.Name));
|
||||||
|
|
||||||
CreateMap<CustomEvent, CustomEventDetailDto>()
|
CreateMap<CustomEvent, CustomEventDetailDto>()
|
||||||
.ForMember(des => des.CustomEventParticipants, opt => opt.MapFrom(src => src.CustomEventParticipants));
|
.ForMember(des => des.CustomEventParticipants, opt => opt.MapFrom(src => src.CustomEventParticipants));
|
||||||
|
|||||||
111
Application/Repositories/CustomEventCategoryRepository.cs
Normal file
111
Application/Repositories/CustomEventCategoryRepository.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
using Application.Classes;
|
||||||
|
using Application.DataTransferObjects.CustomEventCategory;
|
||||||
|
using Application.Errors;
|
||||||
|
using Application.Interfaces;
|
||||||
|
using AutoMapper;
|
||||||
|
using AutoMapper.QueryableExtensions;
|
||||||
|
using Database;
|
||||||
|
using Database.Entities;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Application.Repositories;
|
||||||
|
|
||||||
|
public class CustomEventCategoryRepository(ApplicationContext dbContext, IMapper mapper, ILogger<CustomEventCategoryRepository> logger) : ICustomEventCategoryRepository
|
||||||
|
{
|
||||||
|
public async Task<Result<CustomEventCategoryDto>> GetCustomEventCategoryAsync(Guid customEventCategoryId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var customEventCategoryById = await dbContext.CustomEventCategories
|
||||||
|
.ProjectTo<CustomEventCategoryDto>(mapper.ConfigurationProvider)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(customEventCategory => customEventCategory.Id == customEventCategoryId, cancellationToken);
|
||||||
|
|
||||||
|
return customEventCategoryById is null
|
||||||
|
? Result.Failure<CustomEventCategoryDto>(CustomEventCategoryErrors.NotFound)
|
||||||
|
: Result.Success(customEventCategoryById);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<List<CustomEventCategoryDto>>> GetAllianceCustomEventCategoriesAsync(Guid allianceId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
|
||||||
|
var customEventCategories = await dbContext.CustomEventCategories
|
||||||
|
.Where(customEventCategory => customEventCategory.AllianceId == allianceId)
|
||||||
|
.ProjectTo<CustomEventCategoryDto>(mapper.ConfigurationProvider)
|
||||||
|
.OrderByDescending(customEventCategory => customEventCategory.Id)
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
return Result.Success(customEventCategories);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<CustomEventCategoryDto>> CreateCustomEventCategoryAsync(CreateCustomEventCategoryDto createCustomEventCategoryDto,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var newCustomEventCategory = mapper.Map<CustomEventCategory>(createCustomEventCategoryDto);
|
||||||
|
await dbContext.CustomEventCategories.AddAsync(newCustomEventCategory, cancellationToken);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
var customEventCategoryDto = mapper.Map<CustomEventCategoryDto>(newCustomEventCategory);
|
||||||
|
return Result.Success(customEventCategoryDto);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "{ErrorMessage}", e.Message);
|
||||||
|
return Result.Failure<CustomEventCategoryDto>(GeneralErrors.DatabaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<CustomEventCategoryDto>> UpdateCustomEventCategoryAsync(UpdateCustomEventCategoryDto updateCustomEventCategoryDto,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var customEventCategoryToUpdate = await dbContext.CustomEventCategories
|
||||||
|
.FirstOrDefaultAsync(customEventCategory => customEventCategory.Id == updateCustomEventCategoryDto.Id, cancellationToken);
|
||||||
|
if (customEventCategoryToUpdate is null)
|
||||||
|
{
|
||||||
|
return Result.Failure<CustomEventCategoryDto>(CustomEventCategoryErrors.NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
mapper.Map(updateCustomEventCategoryDto, customEventCategoryToUpdate);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
var customEventCategoryDto = mapper.Map<CustomEventCategoryDto>(customEventCategoryToUpdate);
|
||||||
|
return Result.Success(customEventCategoryDto);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "{ErrorMessage}", e.Message);
|
||||||
|
return Result.Failure<CustomEventCategoryDto>(GeneralErrors.DatabaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<bool>> DeleteCustomEventAsync(Guid customEventId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var customEventToDelete = await dbContext.CustomEventCategories
|
||||||
|
.FirstOrDefaultAsync(customEvent => customEvent.Id == customEventId, cancellationToken);
|
||||||
|
if (customEventToDelete is null)
|
||||||
|
{
|
||||||
|
return Result.Failure<bool>(CustomEventCategoryErrors.NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var customEvents = await dbContext.CustomEvents
|
||||||
|
.Where(customEvent => customEvent.CustomEventCategoryId == customEventToDelete.Id)
|
||||||
|
.ToListAsync(cancellationToken);
|
||||||
|
if (customEvents.Any())
|
||||||
|
{
|
||||||
|
dbContext.CustomEvents.RemoveRange(customEvents);
|
||||||
|
}
|
||||||
|
dbContext.CustomEventCategories.Remove(customEventToDelete);
|
||||||
|
await dbContext.SaveChangesAsync(cancellationToken);
|
||||||
|
return Result.Success(true);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
logger.LogError(e, "{ErrorMessage}", e.Message);
|
||||||
|
return Result.Failure<bool>(GeneralErrors.DatabaseError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
155
Application/Repositories/CustomEventLeaderboardRepository.cs
Normal file
155
Application/Repositories/CustomEventLeaderboardRepository.cs
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
using Application.Classes;
|
||||||
|
using Application.DataTransferObjects.CustomEventLeaderboard;
|
||||||
|
using Database;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace Application.Repositories;
|
||||||
|
|
||||||
|
public class CustomEventLeaderboardRepository(ApplicationContext dbContext) : ICustomEventLeaderBoardRepository
|
||||||
|
{
|
||||||
|
public async Task<Result<List<LeaderboardPointEventDto>>> GetPointEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var category = await dbContext.CustomEventCategories
|
||||||
|
.Where(category => category.Id == customEventCategoryId)
|
||||||
|
.Include(c => c.CustomEvents)
|
||||||
|
.ThenInclude(e => e.CustomEventParticipants)
|
||||||
|
.ThenInclude(p => p.Player)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (category == null)
|
||||||
|
{
|
||||||
|
return Result.Success(new List<LeaderboardPointEventDto>());
|
||||||
|
}
|
||||||
|
|
||||||
|
var leaderboard = category.CustomEvents
|
||||||
|
.SelectMany(e => e.CustomEventParticipants)
|
||||||
|
.GroupBy(p => new { p.Player.Id, p.Player.PlayerName })
|
||||||
|
.Select(g => new LeaderboardPointEventDto
|
||||||
|
{
|
||||||
|
PlayerName = g.Key.PlayerName,
|
||||||
|
Points = g.Sum(p => p.AchievedPoints ?? 0)
|
||||||
|
})
|
||||||
|
.OrderByDescending(l => l.Points)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return Result.Success(leaderboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<List<LeaderboardParticipationEventDto>>> GetParticipationEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var category = await dbContext.CustomEventCategories
|
||||||
|
.Where(category => category.Id == customEventCategoryId)
|
||||||
|
.Include(c => c.CustomEvents)
|
||||||
|
.ThenInclude(e => e.CustomEventParticipants)
|
||||||
|
.ThenInclude(p => p.Player)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (category == null)
|
||||||
|
{
|
||||||
|
return Result.Success(new List<LeaderboardParticipationEventDto>());
|
||||||
|
}
|
||||||
|
|
||||||
|
var leaderboard = category.CustomEvents
|
||||||
|
.SelectMany(e => e.CustomEventParticipants)
|
||||||
|
.GroupBy(p => new { p.Player.Id, p.Player.PlayerName })
|
||||||
|
.Select(g => new LeaderboardParticipationEventDto()
|
||||||
|
{
|
||||||
|
PlayerName = g.Key.PlayerName,
|
||||||
|
Participations = g.Count(z => z.Participated!.Value)
|
||||||
|
})
|
||||||
|
.OrderByDescending(l => l.Participations)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return Result.Success(leaderboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<Result<List<LeaderboardPointAndParticipationEventDto>>> GetPointAndParticipationEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var category = await dbContext.CustomEventCategories
|
||||||
|
.Where(category => category.Id == customEventCategoryId)
|
||||||
|
.Include(c => c.CustomEvents)
|
||||||
|
.ThenInclude(e => e.CustomEventParticipants)
|
||||||
|
.ThenInclude(p => p.Player)
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (category == null)
|
||||||
|
return Result.Success(new List<LeaderboardPointAndParticipationEventDto>());
|
||||||
|
|
||||||
|
// Punktetabelle: Platz 1 = 100, Platz 2 = 99, ..., Platz 100 = 1
|
||||||
|
var pointTable = Enumerable.Range(1, 100).Select(i => 101 - i).ToArray();
|
||||||
|
|
||||||
|
// Gewichtungen
|
||||||
|
const double baseParticipationValue = 10.0;
|
||||||
|
const double placeWeight = 2.0;
|
||||||
|
const double scoreWeight = 1.0;
|
||||||
|
|
||||||
|
// Dictionaries zur Akkumulation
|
||||||
|
var playerPoints = new Dictionary<Guid, long>();
|
||||||
|
var playerParticipationCount = new Dictionary<Guid, int>();
|
||||||
|
var playerTotalPoints = new Dictionary<Guid, double>();
|
||||||
|
var playerNames = new Dictionary<Guid, string>();
|
||||||
|
|
||||||
|
foreach (var ev in category.CustomEvents)
|
||||||
|
{
|
||||||
|
// Vorab Platzierung berechnen
|
||||||
|
var ranked = ev.CustomEventParticipants
|
||||||
|
.Where(p => p.Participated == true)
|
||||||
|
.OrderByDescending(p => p.AchievedPoints ?? 0)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var participant in ev.CustomEventParticipants)
|
||||||
|
{
|
||||||
|
if (participant.Player == null) continue;
|
||||||
|
|
||||||
|
var playerId = participant.Player.Id;
|
||||||
|
var playerName = participant.Player.PlayerName;
|
||||||
|
|
||||||
|
// Spielername merken – egal ob teilgenommen oder nicht
|
||||||
|
playerNames[playerId] = playerName;
|
||||||
|
|
||||||
|
// Nur wenn teilgenommen
|
||||||
|
if (participant.Participated == true)
|
||||||
|
{
|
||||||
|
var score = participant.AchievedPoints ?? 0;
|
||||||
|
|
||||||
|
// Index in Platzierungsliste finden
|
||||||
|
var place = ranked.FindIndex(p => p.Player?.Id == playerId);
|
||||||
|
var placePoints = (place >= 0 && place < pointTable.Length) ? pointTable[place] : 1;
|
||||||
|
var normalizedPlace = placePoints / 100.0;
|
||||||
|
var scoreBonus = Math.Log10(score + 1);
|
||||||
|
|
||||||
|
var total = baseParticipationValue + (normalizedPlace * placeWeight) + (scoreBonus * scoreWeight);
|
||||||
|
|
||||||
|
playerTotalPoints[playerId] = playerTotalPoints.GetValueOrDefault(playerId) + total;
|
||||||
|
playerParticipationCount[playerId] = playerParticipationCount.GetValueOrDefault(playerId) + 1;
|
||||||
|
playerPoints[playerId] = playerPoints.GetValueOrDefault(playerId) + score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Leaderboard für alle Spieler, die in Events vorkommen
|
||||||
|
var leaderboard = playerNames.Select(entry =>
|
||||||
|
{
|
||||||
|
var playerId = entry.Key;
|
||||||
|
var playerName = entry.Value;
|
||||||
|
|
||||||
|
return new LeaderboardPointAndParticipationEventDto
|
||||||
|
{
|
||||||
|
PlayerName = playerName,
|
||||||
|
Points = playerPoints.GetValueOrDefault(playerId),
|
||||||
|
Participations = playerParticipationCount.GetValueOrDefault(playerId),
|
||||||
|
TotalPoints = Math.Round(playerTotalPoints.GetValueOrDefault(playerId), 2)
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.OrderByDescending(x => x.TotalPoints)
|
||||||
|
.ThenByDescending(x => x.Participations)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return Result.Success(leaderboard);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
using Application.Classes;
|
||||||
|
using Application.DataTransferObjects.CustomEventLeaderboard;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Application.Repositories;
|
||||||
|
|
||||||
|
public interface ICustomEventLeaderBoardRepository
|
||||||
|
{
|
||||||
|
Task<Result<List<LeaderboardPointEventDto>>> GetPointEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<Result<List<LeaderboardParticipationEventDto>>> GetParticipationEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken);
|
||||||
|
|
||||||
|
Task<Result<List<LeaderboardPointAndParticipationEventDto>>> GetPointAndParticipationEventLeaderboardAsync(Guid customEventCategoryId, CancellationToken cancellationToken);
|
||||||
|
}
|
||||||
@ -44,6 +44,8 @@ public class ApplicationContext(DbContextOptions<ApplicationContext> options) :
|
|||||||
|
|
||||||
public DbSet<ApiKey> ApiKeys { get; set; }
|
public DbSet<ApiKey> ApiKeys { get; set; }
|
||||||
|
|
||||||
|
public DbSet<CustomEventCategory> CustomEventCategories { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
base.OnConfiguring(optionsBuilder);
|
base.OnConfiguring(optionsBuilder);
|
||||||
|
|||||||
24
Database/Configurations/CustomEventCategoryConfiguration.cs
Normal file
24
Database/Configurations/CustomEventCategoryConfiguration.cs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
using Database.Entities;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace Database.Configurations;
|
||||||
|
|
||||||
|
public class CustomEventCategoryConfiguration : IEntityTypeConfiguration<CustomEventCategory>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<CustomEventCategory> builder)
|
||||||
|
{
|
||||||
|
builder.HasKey(customEventCategory => customEventCategory.Id);
|
||||||
|
builder.Property(customEventCategory => customEventCategory.Id).ValueGeneratedNever();
|
||||||
|
|
||||||
|
builder.Property(customEventCategory => customEventCategory.AllianceId).IsRequired();
|
||||||
|
builder.Property(customEventCategory => customEventCategory.Name).IsRequired().HasMaxLength(255);
|
||||||
|
builder.Property(customEventCategory => customEventCategory.IsPointsEvent).IsRequired();
|
||||||
|
builder.Property(customEventCategory => customEventCategory.IsParticipationEvent).IsRequired();
|
||||||
|
|
||||||
|
builder.HasOne(customEventCategory => customEventCategory.Alliance)
|
||||||
|
.WithMany(alliance => alliance.CustomEventCategories)
|
||||||
|
.HasForeignKey(customEventCategory => customEventCategory.AllianceId)
|
||||||
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -25,5 +25,10 @@ public class CustomEventConfiguration : IEntityTypeConfiguration<CustomEvent>
|
|||||||
.WithMany(a => a.CustomEvents)
|
.WithMany(a => a.CustomEvents)
|
||||||
.HasForeignKey(c => c.AllianceId)
|
.HasForeignKey(c => c.AllianceId)
|
||||||
.OnDelete(DeleteBehavior.Cascade);
|
.OnDelete(DeleteBehavior.Cascade);
|
||||||
|
|
||||||
|
builder.HasOne(customEvent => customEvent.CustomEventCategory)
|
||||||
|
.WithMany(customEventCategory => customEventCategory.CustomEvents)
|
||||||
|
.HasForeignKey(customEvent => customEvent.CustomEventCategoryId)
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -29,4 +29,6 @@ public class Alliance : BaseEntity
|
|||||||
public ICollection<VsDuel> VsDuels { get; set; } = [];
|
public ICollection<VsDuel> VsDuels { get; set; } = [];
|
||||||
|
|
||||||
public ICollection<ZombieSiege> ZombieSieges { get; set; } = [];
|
public ICollection<ZombieSiege> ZombieSieges { get; set; } = [];
|
||||||
|
|
||||||
|
public ICollection<CustomEventCategory> CustomEventCategories { get; set; } = [];
|
||||||
}
|
}
|
||||||
@ -6,6 +6,10 @@ public class CustomEvent : BaseEntity
|
|||||||
|
|
||||||
public Alliance Alliance { get; set; } = null!;
|
public Alliance Alliance { get; set; } = null!;
|
||||||
|
|
||||||
|
public Guid? CustomEventCategoryId { get; set; }
|
||||||
|
|
||||||
|
public CustomEventCategory? CustomEventCategory { get; set; }
|
||||||
|
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
|
|
||||||
public required string Description { get; set; }
|
public required string Description { get; set; }
|
||||||
|
|||||||
16
Database/Entities/CustomEventCategory.cs
Normal file
16
Database/Entities/CustomEventCategory.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
namespace Database.Entities;
|
||||||
|
|
||||||
|
public class CustomEventCategory : BaseEntity
|
||||||
|
{
|
||||||
|
public Guid AllianceId { get; set; }
|
||||||
|
|
||||||
|
public Alliance Alliance { get; set; } = null!;
|
||||||
|
|
||||||
|
public required string Name { get; set; }
|
||||||
|
|
||||||
|
public bool IsPointsEvent { get; set; }
|
||||||
|
|
||||||
|
public bool IsParticipationEvent { get; set; }
|
||||||
|
|
||||||
|
public ICollection<CustomEvent> CustomEvents { get; set; } = [];
|
||||||
|
}
|
||||||
1286
Database/Migrations/20250417062713_Add_CustomEventCategory.Designer.cs
generated
Normal file
1286
Database/Migrations/20250417062713_Add_CustomEventCategory.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,93 @@
|
|||||||
|
//<auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||||
|
|
||||||
|
namespace Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Add_CustomEventCategory : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.AddColumn<Guid>(
|
||||||
|
name: "CustomEventCategoryId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEvents",
|
||||||
|
type: "uniqueidentifier",
|
||||||
|
nullable: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "CustomEventCategory",
|
||||||
|
schema: "dbo",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
AllianceId = table.Column<Guid>(type: "uniqueidentifier", nullable: false),
|
||||||
|
Name = table.Column<string>(type: "nvarchar(255)", maxLength: 255, nullable: false),
|
||||||
|
IsPointsEvent = table.Column<bool>(type: "bit", nullable: false),
|
||||||
|
IsParticipationEvent = table.Column<bool>(type: "bit", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_CustomEventCategory", x => x.Id);
|
||||||
|
table.ForeignKey(
|
||||||
|
name: "FK_CustomEventCategory_Alliances_AllianceId",
|
||||||
|
column: x => x.AllianceId,
|
||||||
|
principalSchema: "dbo",
|
||||||
|
principalTable: "Alliances",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CustomEvents_CustomEventCategoryId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEvents",
|
||||||
|
column: "CustomEventCategoryId");
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_CustomEventCategory_AllianceId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEventCategory",
|
||||||
|
column: "AllianceId");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CustomEvents_CustomEventCategory_CustomEventCategoryId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEvents",
|
||||||
|
column: "CustomEventCategoryId",
|
||||||
|
principalSchema: "dbo",
|
||||||
|
principalTable: "CustomEventCategory",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CustomEvents_CustomEventCategory_CustomEventCategoryId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEvents");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "CustomEventCategory",
|
||||||
|
schema: "dbo");
|
||||||
|
|
||||||
|
migrationBuilder.DropIndex(
|
||||||
|
name: "IX_CustomEvents_CustomEventCategoryId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEvents");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "CustomEventCategoryId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEvents");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1286
Database/Migrations/20250417071312_Update_CustomEventCategory.Designer.cs
generated
Normal file
1286
Database/Migrations/20250417071312_Update_CustomEventCategory.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
128
Database/Migrations/20250417071312_Update_CustomEventCategory.cs
Normal file
128
Database/Migrations/20250417071312_Update_CustomEventCategory.cs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
// <auto-generated />
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional
|
||||||
|
|
||||||
|
namespace Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class Update_CustomEventCategory : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CustomEventCategory_Alliances_AllianceId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEventCategory");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CustomEvents_CustomEventCategory_CustomEventCategoryId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEvents");
|
||||||
|
|
||||||
|
migrationBuilder.DropPrimaryKey(
|
||||||
|
name: "PK_CustomEventCategory",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEventCategory");
|
||||||
|
|
||||||
|
migrationBuilder.RenameTable(
|
||||||
|
name: "CustomEventCategory",
|
||||||
|
schema: "dbo",
|
||||||
|
newName: "CustomEventCategories",
|
||||||
|
newSchema: "dbo");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_CustomEventCategory_AllianceId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEventCategories",
|
||||||
|
newName: "IX_CustomEventCategories_AllianceId");
|
||||||
|
|
||||||
|
migrationBuilder.AddPrimaryKey(
|
||||||
|
name: "PK_CustomEventCategories",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEventCategories",
|
||||||
|
column: "Id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CustomEventCategories_Alliances_AllianceId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEventCategories",
|
||||||
|
column: "AllianceId",
|
||||||
|
principalSchema: "dbo",
|
||||||
|
principalTable: "Alliances",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CustomEvents_CustomEventCategories_CustomEventCategoryId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEvents",
|
||||||
|
column: "CustomEventCategoryId",
|
||||||
|
principalSchema: "dbo",
|
||||||
|
principalTable: "CustomEventCategories",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CustomEventCategories_Alliances_AllianceId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEventCategories");
|
||||||
|
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_CustomEvents_CustomEventCategories_CustomEventCategoryId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEvents");
|
||||||
|
|
||||||
|
migrationBuilder.DropPrimaryKey(
|
||||||
|
name: "PK_CustomEventCategories",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEventCategories");
|
||||||
|
|
||||||
|
migrationBuilder.RenameTable(
|
||||||
|
name: "CustomEventCategories",
|
||||||
|
schema: "dbo",
|
||||||
|
newName: "CustomEventCategory",
|
||||||
|
newSchema: "dbo");
|
||||||
|
|
||||||
|
migrationBuilder.RenameIndex(
|
||||||
|
name: "IX_CustomEventCategories_AllianceId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEventCategory",
|
||||||
|
newName: "IX_CustomEventCategory_AllianceId");
|
||||||
|
|
||||||
|
migrationBuilder.AddPrimaryKey(
|
||||||
|
name: "PK_CustomEventCategory",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEventCategory",
|
||||||
|
column: "Id");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CustomEventCategory_Alliances_AllianceId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEventCategory",
|
||||||
|
column: "AllianceId",
|
||||||
|
principalSchema: "dbo",
|
||||||
|
principalTable: "Alliances",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_CustomEvents_CustomEventCategory_CustomEventCategoryId",
|
||||||
|
schema: "dbo",
|
||||||
|
table: "CustomEvents",
|
||||||
|
column: "CustomEventCategoryId",
|
||||||
|
principalSchema: "dbo",
|
||||||
|
principalTable: "CustomEventCategory",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Restrict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
// <auto-generated />
|
|
||||||
using System;
|
using System;
|
||||||
using Database;
|
using Database;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -19,7 +18,7 @@ namespace Database.Migrations
|
|||||||
#pragma warning disable 612, 618
|
#pragma warning disable 612, 618
|
||||||
modelBuilder
|
modelBuilder
|
||||||
.HasDefaultSchema("dbo")
|
.HasDefaultSchema("dbo")
|
||||||
.HasAnnotation("ProductVersion", "9.0.0")
|
.HasAnnotation("ProductVersion", "9.0.4")
|
||||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||||
|
|
||||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||||
@ -140,6 +139,9 @@ namespace Database.Migrations
|
|||||||
.HasMaxLength(150)
|
.HasMaxLength(150)
|
||||||
.HasColumnType("nvarchar(150)");
|
.HasColumnType("nvarchar(150)");
|
||||||
|
|
||||||
|
b.Property<Guid?>("CustomEventCategoryId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasMaxLength(500)
|
.HasMaxLength(500)
|
||||||
@ -173,9 +175,37 @@ namespace Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("AllianceId");
|
b.HasIndex("AllianceId");
|
||||||
|
|
||||||
|
b.HasIndex("CustomEventCategoryId");
|
||||||
|
|
||||||
b.ToTable("CustomEvents", "dbo");
|
b.ToTable("CustomEvents", "dbo");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Database.Entities.CustomEventCategory", b =>
|
||||||
|
{
|
||||||
|
b.Property<Guid>("Id")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<Guid>("AllianceId")
|
||||||
|
.HasColumnType("uniqueidentifier");
|
||||||
|
|
||||||
|
b.Property<bool>("IsParticipationEvent")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<bool>("IsPointsEvent")
|
||||||
|
.HasColumnType("bit");
|
||||||
|
|
||||||
|
b.Property<string>("Name")
|
||||||
|
.IsRequired()
|
||||||
|
.HasMaxLength(255)
|
||||||
|
.HasColumnType("nvarchar(255)");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("AllianceId");
|
||||||
|
|
||||||
|
b.ToTable("CustomEventCategories", "dbo");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Database.Entities.CustomEventParticipant", b =>
|
modelBuilder.Entity("Database.Entities.CustomEventParticipant", b =>
|
||||||
{
|
{
|
||||||
b.Property<Guid>("Id")
|
b.Property<Guid>("Id")
|
||||||
@ -624,19 +654,19 @@ namespace Database.Migrations
|
|||||||
b.HasData(
|
b.HasData(
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
Id = new Guid("0194d053-12b9-7818-9417-4d4eaa3b5ec1"),
|
Id = new Guid("01964298-2d6d-7f54-8e1f-ab23ec343378"),
|
||||||
Code = 1,
|
Code = 1,
|
||||||
Name = "Silver League"
|
Name = "Silver League"
|
||||||
},
|
},
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
Id = new Guid("0194d053-12b9-7c64-9efb-e88745510c35"),
|
Id = new Guid("01964298-2d6d-7620-bcc5-08103cbfd5de"),
|
||||||
Code = 2,
|
Code = 2,
|
||||||
Name = "Gold League"
|
Name = "Gold League"
|
||||||
},
|
},
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
Id = new Guid("0194d053-12b9-7217-a040-88ba1bbc7d69"),
|
Id = new Guid("01964298-2d6d-7e0b-b002-532458cbe7bd"),
|
||||||
Code = 3,
|
Code = 3,
|
||||||
Name = "Diamond League"
|
Name = "Diamond League"
|
||||||
});
|
});
|
||||||
@ -911,6 +941,24 @@ namespace Database.Migrations
|
|||||||
.OnDelete(DeleteBehavior.Cascade)
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
.IsRequired();
|
.IsRequired();
|
||||||
|
|
||||||
|
b.HasOne("Database.Entities.CustomEventCategory", "CustomEventCategory")
|
||||||
|
.WithMany("CustomEvents")
|
||||||
|
.HasForeignKey("CustomEventCategoryId")
|
||||||
|
.OnDelete(DeleteBehavior.Restrict);
|
||||||
|
|
||||||
|
b.Navigation("Alliance");
|
||||||
|
|
||||||
|
b.Navigation("CustomEventCategory");
|
||||||
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Database.Entities.CustomEventCategory", b =>
|
||||||
|
{
|
||||||
|
b.HasOne("Database.Entities.Alliance", "Alliance")
|
||||||
|
.WithMany("CustomEventCategories")
|
||||||
|
.HasForeignKey("AllianceId")
|
||||||
|
.OnDelete(DeleteBehavior.Cascade)
|
||||||
|
.IsRequired();
|
||||||
|
|
||||||
b.Navigation("Alliance");
|
b.Navigation("Alliance");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1156,6 +1204,8 @@ namespace Database.Migrations
|
|||||||
{
|
{
|
||||||
b.Navigation("ApiKey");
|
b.Navigation("ApiKey");
|
||||||
|
|
||||||
|
b.Navigation("CustomEventCategories");
|
||||||
|
|
||||||
b.Navigation("CustomEvents");
|
b.Navigation("CustomEvents");
|
||||||
|
|
||||||
b.Navigation("DesertStorms");
|
b.Navigation("DesertStorms");
|
||||||
@ -1176,6 +1226,11 @@ namespace Database.Migrations
|
|||||||
b.Navigation("CustomEventParticipants");
|
b.Navigation("CustomEventParticipants");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Database.Entities.CustomEventCategory", b =>
|
||||||
|
{
|
||||||
|
b.Navigation("CustomEvents");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Database.Entities.DesertStorm", b =>
|
modelBuilder.Entity("Database.Entities.DesertStorm", b =>
|
||||||
{
|
{
|
||||||
b.Navigation("DesertStormParticipants");
|
b.Navigation("DesertStormParticipants");
|
||||||
|
|||||||
17
README.md
17
README.md
@ -17,15 +17,28 @@ All notable changes to this project are documented here.
|
|||||||
This project is currently in the **Beta Phase**.
|
This project is currently in the **Beta Phase**.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### **[0.9.0]** - *2025-04-23*
|
||||||
|
#### ✨ Added
|
||||||
|
- **Event Categories:** Custom events can now be assigned to specific categories.
|
||||||
|
- **Category-Based Leaderboards:** Each category can have its own leaderboard, depending on the event type (e.g., participation-only, points-only, or a combination).
|
||||||
|
|
||||||
|
🛠️ **Fixed**
|
||||||
|
- Minor display issues in the event and leaderboard sections have been resolved.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### **[0.8.3]** - *2025-04-10*
|
### **[0.8.3]** - *2025-04-10*
|
||||||
#### ♻️ Changed
|
#### ♻️ Changed
|
||||||
- **NuGet Packages:** Updated all dependencies, including Serilog and Seq, to their latest stable versions.
|
- **NuGet Packages:** Updated all dependencies, including Serilog and Seq, to their latest stable versions.
|
||||||
- **Logging Setup:** Cleaned up and reorganized the Serilog configuration for improved clarity and maintainability.
|
- **Logging Setup:** Cleaned up and reorganized the Serilog configuration for improved clarity and maintainability.
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
🛠️ **Fixed**
|
🛠️ **Fixed**
|
||||||
- (N/A)
|
- (N/A)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
### **[0.8.0]** - *2025-03-11*
|
### **[0.8.0]** - *2025-03-11*
|
||||||
#### ✨ Added
|
#### ✨ Added
|
||||||
- **Feedback Page:** Users can now submit feedback, including bug reports and feature requests.
|
- **Feedback Page:** Users can now submit feedback, including bug reports and feature requests.
|
||||||
|
|||||||
@ -61,6 +61,9 @@ import { AllianceApiKeyComponent } from './pages/alliance/alliance-api-key/allia
|
|||||||
import { AllianceUserAdministrationComponent } from './pages/alliance/alliance-user-administration/alliance-user-administration.component';
|
import { AllianceUserAdministrationComponent } from './pages/alliance/alliance-user-administration/alliance-user-administration.component';
|
||||||
import { FeedbackComponent } from './pages/feedback/feedback.component';
|
import { FeedbackComponent } from './pages/feedback/feedback.component';
|
||||||
import { ImprintComponent } from './pages/imprint/imprint.component';
|
import { ImprintComponent } from './pages/imprint/imprint.component';
|
||||||
|
import { CustomEventCategoryComponent } from './pages/custom-event/custom-event-category/custom-event-category.component';
|
||||||
|
import { CustomEventLeaderboardComponent } from './pages/custom-event/custom-event-leaderboard/custom-event-leaderboard.component';
|
||||||
|
import { CustomEventEventsComponent } from './pages/custom-event/custom-event-events/custom-event-events.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
@ -111,7 +114,10 @@ import { ImprintComponent } from './pages/imprint/imprint.component';
|
|||||||
AllianceApiKeyComponent,
|
AllianceApiKeyComponent,
|
||||||
AllianceUserAdministrationComponent,
|
AllianceUserAdministrationComponent,
|
||||||
FeedbackComponent,
|
FeedbackComponent,
|
||||||
ImprintComponent
|
ImprintComponent,
|
||||||
|
CustomEventCategoryComponent,
|
||||||
|
CustomEventLeaderboardComponent,
|
||||||
|
CustomEventEventsComponent
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
BrowserModule,
|
BrowserModule,
|
||||||
|
|||||||
@ -4,6 +4,8 @@ export interface CustomEventModel {
|
|||||||
id: string;
|
id: string;
|
||||||
allianceId: string;
|
allianceId: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
categoryName?: string;
|
||||||
|
customEventCategoryId?: string;
|
||||||
description: string;
|
description: string;
|
||||||
isPointsEvent: boolean;
|
isPointsEvent: boolean;
|
||||||
isParticipationEvent: boolean;
|
isParticipationEvent: boolean;
|
||||||
@ -25,5 +27,6 @@ export interface CreateCustomEventModel {
|
|||||||
isParticipationEvent: boolean;
|
isParticipationEvent: boolean;
|
||||||
eventDate: string;
|
eventDate: string;
|
||||||
allianceId: string;
|
allianceId: string;
|
||||||
|
customEventCategoryId?: string;
|
||||||
isInProgress: boolean;
|
isInProgress: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
21
Ui/src/app/models/customEventCategory.model.ts
Normal file
21
Ui/src/app/models/customEventCategory.model.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export interface CustomEventCategoryModel {
|
||||||
|
id: string;
|
||||||
|
allianceId: string;
|
||||||
|
name: string;
|
||||||
|
isPointsEvent: boolean;
|
||||||
|
isParticipationEvent: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CreateCustomEventCategoryModel {
|
||||||
|
allianceId: string;
|
||||||
|
name: string;
|
||||||
|
isPointsEvent: boolean;
|
||||||
|
isParticipationEvent: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface UpdateCustomEventCategoryModel {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
isPointsEvent: boolean;
|
||||||
|
isParticipationEvent: boolean;
|
||||||
|
}
|
||||||
16
Ui/src/app/models/customEventLeaderboard.model.ts
Normal file
16
Ui/src/app/models/customEventLeaderboard.model.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export interface LeaderboardPointEventModel {
|
||||||
|
playerName: string;
|
||||||
|
points: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LeaderboardParticipationEventModel {
|
||||||
|
playerName: string;
|
||||||
|
participations: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LeaderboardPointAndParticipationEventModel {
|
||||||
|
playerName: string;
|
||||||
|
participations: number;
|
||||||
|
points: number;
|
||||||
|
totalPoints: number;
|
||||||
|
}
|
||||||
@ -0,0 +1,94 @@
|
|||||||
|
@if (!isCreateCustomEventCategory) {
|
||||||
|
<div class="d-grid gap-2 col-6 mx-auto">
|
||||||
|
<button (click)="onCreateCategory()" class="btn btn-primary" type="button">Create new Category</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (isCreateCustomEventCategory) {
|
||||||
|
<form [formGroup]="customEventCategoryForm">
|
||||||
|
<!-- Event name-->
|
||||||
|
<div class="form-floating mb-3 is-invalid">
|
||||||
|
<input [ngClass]="{
|
||||||
|
'is-invalid': f['name'].invalid && (f['name'].dirty || !f['name'].untouched),
|
||||||
|
'is-valid': f['name'].valid}"
|
||||||
|
type="text" class="form-control" maxlength="151" id="name" placeholder="name" formControlName="name">
|
||||||
|
<label for="name">Event name</label>
|
||||||
|
@if (f['name'].invalid && (f['name'].dirty || !f['name'].untouched)) {
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
@if (f['name'].hasError('required')) {
|
||||||
|
<p>Name is required</p>
|
||||||
|
}
|
||||||
|
@if (f['name'].hasError('maxlength')) {
|
||||||
|
<p>Maximum 150 characters allowed</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<!-- Is point event-->
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" formControlName="isPointsEvent" id="isPointsEvent">
|
||||||
|
<label class="form-check-label" for="isPointsEvent">
|
||||||
|
Points Event
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Is participation event-->
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" formControlName="isParticipationEvent" id="isParticipationEvent">
|
||||||
|
<label class="form-check-label" for="isParticipationEvent">
|
||||||
|
Participation Event
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<button (click)="onCancel()" type="button" class="btn btn-warning">Cancel</button>
|
||||||
|
<button [disabled]="customEventCategoryForm.invalid" (click)="onSubmit()" type="submit" class="btn btn-success">{{isUpdate ? 'Update': 'Create'}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!isCreateCustomEventCategory) {
|
||||||
|
@if (customEventCategories.length > 0) {
|
||||||
|
<div class="table-responsive mt-5">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Point event</th>
|
||||||
|
<th scope="col">Participation event</th>
|
||||||
|
<th scope="col">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (customEventCategory of customEventCategories | paginate: { id: 'customEventTable', itemsPerPage: 5, currentPage: pageNumber}; track customEventCategory.id) {
|
||||||
|
<tr>
|
||||||
|
<td>{{customEventCategory.name}}</td>
|
||||||
|
<td>
|
||||||
|
<i class="bi " [ngClass]="customEventCategory.isPointsEvent ? 'bi-check-lg text-success' : 'bi-x-lg text-danger'"></i>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<i class="bi " [ngClass]="customEventCategory.isParticipationEvent ? 'bi-check-lg text-success' : 'bi-x-lg text-danger'"></i>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex gap-3 justify-content-around">
|
||||||
|
<i ngbTooltip="Edit" placement="auto" (click)="onEditCustomEventCategory(customEventCategory)" class="bi custom-edit-icon bi-pencil-fill"></i>
|
||||||
|
<i ngbTooltip="Delete" placement="auto" (click)="onDeleteCustomEventCategory(customEventCategory)" class="bi custom-delete-icon bi-trash3"></i>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Pagination Controls -->
|
||||||
|
<div class="d-flex justify-content-between mt-3 flex-column flex-md-row">
|
||||||
|
<pagination-controls class="custom-pagination" [id]="'customEventTable'" [responsive]="true" [autoHide]=" true"
|
||||||
|
(pageChange)="pageChanged($event)"></pagination-controls>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div class="alert alert-secondary text-center mt-5" role="alert">
|
||||||
|
No saved categories
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,156 @@
|
|||||||
|
import {Component, inject, OnInit} from '@angular/core';
|
||||||
|
import {
|
||||||
|
CreateCustomEventCategoryModel,
|
||||||
|
CustomEventCategoryModel,
|
||||||
|
UpdateCustomEventCategoryModel
|
||||||
|
} from "../../../models/customEventCategory.model";
|
||||||
|
import {FormControl, FormGroup, Validators} from "@angular/forms";
|
||||||
|
import {JwtTokenService} from "../../../services/jwt-token.service";
|
||||||
|
import {CustomEventCategoryService} from "../../../services/custom-event-category.service";
|
||||||
|
import {ToastrService} from "ngx-toastr";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-custom-event-category',
|
||||||
|
templateUrl: './custom-event-category.component.html',
|
||||||
|
styleUrl: './custom-event-category.component.css'
|
||||||
|
})
|
||||||
|
export class CustomEventCategoryComponent implements OnInit {
|
||||||
|
isCreateCustomEventCategory: any;
|
||||||
|
customEventCategories: CustomEventCategoryModel[] = [];
|
||||||
|
customEventCategoryForm!: FormGroup;
|
||||||
|
isUpdate: boolean = false;
|
||||||
|
private readonly _tokenService: JwtTokenService = inject(JwtTokenService);
|
||||||
|
private readonly _customEventCategoryService: CustomEventCategoryService = inject(CustomEventCategoryService);
|
||||||
|
private readonly _toastr: ToastrService = inject(ToastrService);
|
||||||
|
private allianceId: string = this._tokenService.getAllianceId()!;
|
||||||
|
pageNumber: number = 1;
|
||||||
|
|
||||||
|
get f() {
|
||||||
|
return this.customEventCategoryForm.controls;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getCustomEventCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomEventCategories() {
|
||||||
|
this._customEventCategoryService.getAllianceCustomEventCategories(this.allianceId).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this.customEventCategories = response;
|
||||||
|
} else {
|
||||||
|
this.customEventCategories = [];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCreateCategory() {
|
||||||
|
this.createCustomEventCategoryForm(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
createCustomEventCategoryForm(isUpdate: boolean, customEventCategory: CustomEventCategoryModel | null = null) {
|
||||||
|
this.customEventCategoryForm = new FormGroup({
|
||||||
|
id: new FormControl<string>(isUpdate ? customEventCategory!.id : ''),
|
||||||
|
allianceId: new FormControl<string>(this.allianceId),
|
||||||
|
name: new FormControl<string>(isUpdate ? customEventCategory!.name : '', [Validators.required, Validators.maxLength(150)]),
|
||||||
|
isPointsEvent: new FormControl<boolean>(isUpdate ? customEventCategory!.isPointsEvent : false),
|
||||||
|
isParticipationEvent: new FormControl(isUpdate ? customEventCategory!.isParticipationEvent : false),
|
||||||
|
});
|
||||||
|
if (isUpdate) {
|
||||||
|
this.customEventCategoryForm.controls['isPointsEvent'].disable();
|
||||||
|
this.customEventCategoryForm.controls['isParticipationEvent'].disable();
|
||||||
|
}
|
||||||
|
this.isCreateCustomEventCategory = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel() {
|
||||||
|
this.isUpdate = false;
|
||||||
|
this.isCreateCustomEventCategory = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditCustomEventCategory(customEventCategory: CustomEventCategoryModel) {
|
||||||
|
this.createCustomEventCategoryForm(true, customEventCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteCustomEventCategory(customEventCategory: CustomEventCategoryModel) {
|
||||||
|
Swal.fire({
|
||||||
|
title: `Delete category: ${customEventCategory.name}`,
|
||||||
|
text: `Do you really want to delete the category? This will also delete all events associated with this category.`,
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Yes, delete it!"
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
this._customEventCategoryService.deleteCustomEventCategory(customEventCategory.id).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Deleted!",
|
||||||
|
text: "Custom event has been deleted",
|
||||||
|
icon: "success"
|
||||||
|
}).then(_ => this.resetAndGetCustomEventCategories());
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
error: (error: Error) => {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
if (this.customEventCategoryForm.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isUpdate) {
|
||||||
|
this.updateCustomEventCategory();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const customEventCategory: CreateCustomEventCategoryModel = this.customEventCategoryForm.value as CreateCustomEventCategoryModel;
|
||||||
|
this._customEventCategoryService.createCustomEventCategory(customEventCategory).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this._toastr.success('Successfully created!', 'Successfully');
|
||||||
|
this.onCancel();
|
||||||
|
this.resetAndGetCustomEventCategories();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
error: ((error: any) => {
|
||||||
|
console.error(error);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pageChanged(event: number) {
|
||||||
|
this.pageNumber = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCustomEventCategory() {
|
||||||
|
const customEventCategory: UpdateCustomEventCategoryModel = this.customEventCategoryForm.value as UpdateCustomEventCategoryModel;
|
||||||
|
this._customEventCategoryService.updateCustomEvent(customEventCategory.id, customEventCategory).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this._toastr.success('Successfully updated!', 'Successfully');
|
||||||
|
this.onCancel();
|
||||||
|
this.resetAndGetCustomEventCategories();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
error: ((error: any) => {
|
||||||
|
console.error(error);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
resetAndGetCustomEventCategories() {
|
||||||
|
this.pageNumber = 1;
|
||||||
|
this.getCustomEventCategories();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -48,7 +48,7 @@
|
|||||||
<h6 class="mb-1 text-success">{{player.playerName}}</h6>
|
<h6 class="mb-1 text-success">{{player.playerName}}</h6>
|
||||||
@if (customEventDetail.isPointsEvent) {
|
@if (customEventDetail.isPointsEvent) {
|
||||||
<div class="col mb-1">
|
<div class="col mb-1">
|
||||||
Achieved Points: <span class="text-primary">{{player.achievedPoints}}</span>
|
Achieved Points: <span class="text-primary">{{player.achievedPoints | number}}</span>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@if (customEventDetail.isParticipationEvent) {
|
@if (customEventDetail.isParticipationEvent) {
|
||||||
|
|||||||
@ -0,0 +1,176 @@
|
|||||||
|
@if (!isCreateCustomEvent) {
|
||||||
|
<div class="d-grid gap-2 col-6 mx-auto">
|
||||||
|
<button (click)="onCreateEvent()" class="btn btn-primary" type="button">Create new Event</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (isCreateCustomEvent) {
|
||||||
|
<form [formGroup]="customEventForm">
|
||||||
|
<!-- Event date-->
|
||||||
|
<div class="form-floating mb-3 is-invalid">
|
||||||
|
<input [ngClass]="{
|
||||||
|
'is-invalid': f['eventDate'].invalid && (f['eventDate'].dirty || !f['eventDate'].untouched),
|
||||||
|
'is-valid': f['eventDate'].valid}"
|
||||||
|
type="date" class="form-control" id="eventDate" placeholder="eventDate" formControlName="eventDate">
|
||||||
|
<label for="eventDate">Event date</label>
|
||||||
|
@if (f['eventDate'].invalid && (f['eventDate'].dirty || !f['eventDate'].untouched)) {
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
@if (f['eventDate'].hasError('required')) {
|
||||||
|
<p>eventDate is required</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<!-- Event name-->
|
||||||
|
<div class="form-floating mb-3 is-invalid">
|
||||||
|
<input [ngClass]="{
|
||||||
|
'is-invalid': f['name'].invalid && (f['name'].dirty || !f['name'].untouched),
|
||||||
|
'is-valid': f['name'].valid}"
|
||||||
|
type="text" class="form-control" maxlength="151" id="name" placeholder="name" formControlName="name">
|
||||||
|
<label for="name">Event name</label>
|
||||||
|
@if (f['name'].invalid && (f['name'].dirty || !f['name'].untouched)) {
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
@if (f['name'].hasError('required')) {
|
||||||
|
<p>Name is required</p>
|
||||||
|
}
|
||||||
|
@if (f['name'].hasError('maxlength')) {
|
||||||
|
<p>Maximum 150 characters allowed</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<!-- Description-->
|
||||||
|
<div class="form-floating mb-3">
|
||||||
|
<textarea [ngClass]="{
|
||||||
|
'is-valid': f['description'].valid,
|
||||||
|
'is-invalid': f['description'].invalid && (!f['description'].untouched || f['description'].dirty)
|
||||||
|
}" class="form-control" maxlength="501" formControlName="description" placeholder="description" id="description" style="height: 100px"></textarea>
|
||||||
|
<label for="description">description</label>
|
||||||
|
@if (f['description'].invalid && (!f['description'].untouched || f['description'].dirty)) {
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
@if (f['description'].hasError('required')) {
|
||||||
|
<p>Description is required</p>
|
||||||
|
}
|
||||||
|
@if (f['description'].hasError('maxlength')) {
|
||||||
|
<p>Maximum 500 characters allowed</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<!-- Category-->
|
||||||
|
<div class="form-floating mb-3" (change)="onCategoryChange()">
|
||||||
|
<select class="form-select" id="customEventCategoryId" formControlName="customEventCategoryId">
|
||||||
|
<option [ngValue]="null">No Category</option>
|
||||||
|
@for (customEventCategory of customEventCategories; track customEventCategory.id) {
|
||||||
|
<option [ngValue]="customEventCategory.id">{{customEventCategory.name}}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<label for="customEventCategoryId">Category</label>
|
||||||
|
</div>
|
||||||
|
<!-- In progress-->
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" formControlName="isInProgress" id="isInProgress">
|
||||||
|
<label class="form-check-label" for="isInProgress">
|
||||||
|
In Progress
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<!-- Is point event-->
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" formControlName="isPointsEvent" id="isPointsEvent">
|
||||||
|
<label class="form-check-label" for="isPointsEvent">
|
||||||
|
Points Event
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<!-- Is participation event-->
|
||||||
|
<div class="form-check mb-3">
|
||||||
|
<input class="form-check-input" type="checkbox" formControlName="isParticipationEvent" id="isParticipationEvent">
|
||||||
|
<label class="form-check-label" for="isParticipationEvent">
|
||||||
|
Participation Event
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@if (!playerSelected) {
|
||||||
|
<div class="d-flex justify-content-center">
|
||||||
|
<p class="text-warning">Please add participants</p>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="d-grid gap-2 col-6 mx-auto">
|
||||||
|
<button (click)="onAddParticipants()" class="btn btn-primary" type="button">{{isUpdate ? 'Update Participants' : 'Add Participants'}}</button>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<button (click)="onCancel()" type="button" class="btn btn-warning">Cancel</button>
|
||||||
|
<button [disabled]="customEventForm.invalid || playerParticipated.length <= 0" (click)="onSubmit()" type="submit" class="btn btn-success">{{isUpdate ? 'Update': 'Create'}}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (!isCreateCustomEvent) {
|
||||||
|
@if (customEvents.length > 0) {
|
||||||
|
<div class="table-responsive mt-5">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Event Date</th>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Category</th>
|
||||||
|
<th scope="col">Point event</th>
|
||||||
|
<th scope="col">Participation event</th>
|
||||||
|
<th scope="col">Status</th>
|
||||||
|
<th scope="col">Creator</th>
|
||||||
|
<th scope="col">Action</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (customEvent of customEvents | paginate: { id: 'customEventTable', itemsPerPage: pageSize, totalItems: totalRecord, currentPage: pageNumber}; track customEvent.id) {
|
||||||
|
<tr>
|
||||||
|
<td>{{customEvent.eventDate | date: 'dd.MM.yyyy'}}</td>
|
||||||
|
<td>{{customEvent.name}}</td>
|
||||||
|
<td>{{customEvent.categoryName ?? ' - '}}</td>
|
||||||
|
<td>
|
||||||
|
<i class="bi " [ngClass]="customEvent.isPointsEvent ? 'bi-check-lg text-success' : 'bi-x-lg text-danger'"></i>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<i class="bi " [ngClass]="customEvent.isParticipationEvent ? 'bi-check-lg text-success' : 'bi-x-lg text-danger'"></i>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
@if (customEvent.isInProgress) {
|
||||||
|
<i class="bi bi-hourglass-split"></i> In Progress
|
||||||
|
} @else {
|
||||||
|
Done
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>{{customEvent.createdBy}}</td>
|
||||||
|
<td>
|
||||||
|
<div class="d-flex gap-3 justify-content-around">
|
||||||
|
<i ngbTooltip="Show details" placement="auto" (click)="onGoToCustomEventDetail(customEvent)" class="bi custom-info-icon bi-info-circle-fill"></i>
|
||||||
|
<i ngbTooltip="Edit" placement="auto" (click)="onEditCustomEvent(customEvent)" class="bi custom-edit-icon bi-pencil-fill"></i>
|
||||||
|
<i ngbTooltip="Delete" placement="auto" (click)="onDeleteCustomEvent(customEvent)" class="bi custom-delete-icon bi-trash3"></i>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<!-- Pagination Controls -->
|
||||||
|
<div class="d-flex justify-content-between mt-3 flex-column flex-md-row">
|
||||||
|
<pagination-controls class="custom-pagination" [id]="'customEventTable'" [responsive]="true" [autoHide]=" true"
|
||||||
|
(pageChange)="pageChanged($event)"></pagination-controls>
|
||||||
|
|
||||||
|
<!-- Showing total results with improved styling -->
|
||||||
|
<div class="align-self-center text-muted mt-2 mt-md-0">
|
||||||
|
<small>
|
||||||
|
Showing
|
||||||
|
<strong>{{ (pageNumber - 1) * pageSize + 1 }} - {{ pageNumber * pageSize > totalRecord ? totalRecord : pageNumber * pageSize }}</strong>
|
||||||
|
of <strong>{{ totalRecord }}</strong> results
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
} @else {
|
||||||
|
<div class="alert alert-secondary text-center mt-5" role="alert">
|
||||||
|
No saved custom events
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,312 @@
|
|||||||
|
import {Component, inject, OnInit} from '@angular/core';
|
||||||
|
import {CreateCustomEventModel, CustomEventDetailModel, CustomEventModel} from "../../../models/customEvent.model";
|
||||||
|
import {FormControl, FormGroup, Validators} from "@angular/forms";
|
||||||
|
import {CustomEventParticipantModel} from "../../../models/customEventParticipant.model";
|
||||||
|
import {CustomEventCategoryModel} from "../../../models/customEventCategory.model";
|
||||||
|
import {JwtTokenService} from "../../../services/jwt-token.service";
|
||||||
|
import {NgbModal} from "@ng-bootstrap/ng-bootstrap";
|
||||||
|
import {CustomEventService} from "../../../services/custom-event.service";
|
||||||
|
import {CustomEventParticipantService} from "../../../services/custom-event-participant.service";
|
||||||
|
import {CustomEventCategoryService} from "../../../services/custom-event-category.service";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
import {ToastrService} from "ngx-toastr";
|
||||||
|
import {
|
||||||
|
CustomEventParticipantsModelComponent
|
||||||
|
} from "../../../modals/custom-event-participants-model/custom-event-participants-model.component";
|
||||||
|
import Swal from "sweetalert2";
|
||||||
|
import {forkJoin, Observable} from "rxjs";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-custom-event-events',
|
||||||
|
templateUrl: './custom-event-events.component.html',
|
||||||
|
styleUrl: './custom-event-events.component.css'
|
||||||
|
})
|
||||||
|
export class CustomEventEventsComponent implements OnInit {
|
||||||
|
|
||||||
|
customEvents: CustomEventModel[] = [];
|
||||||
|
customEventDetail!: CustomEventDetailModel;
|
||||||
|
isCreateCustomEvent: boolean = false;
|
||||||
|
customEventForm!: FormGroup;
|
||||||
|
playerSelected: boolean = false;
|
||||||
|
playerParticipated: { playerId: string, playerName: string, participated: boolean, achievedPoints: number }[] = [];
|
||||||
|
customEventParticipants: CustomEventParticipantModel[] = [];
|
||||||
|
customEventCategories: CustomEventCategoryModel[] = [];
|
||||||
|
isUpdate: boolean = false;
|
||||||
|
private readonly _tokenService: JwtTokenService = inject(JwtTokenService);
|
||||||
|
private readonly _modalService: NgbModal = inject(NgbModal);
|
||||||
|
private readonly _customEventService: CustomEventService = inject(CustomEventService);
|
||||||
|
private readonly _customEventParticipantService: CustomEventParticipantService = inject(CustomEventParticipantService);
|
||||||
|
private readonly _customEventCategoryService: CustomEventCategoryService = inject(CustomEventCategoryService);
|
||||||
|
private readonly _router: Router = inject(Router);
|
||||||
|
private readonly _toastr: ToastrService = inject(ToastrService);
|
||||||
|
private allianceId: string = this._tokenService.getAllianceId()!;
|
||||||
|
|
||||||
|
public totalRecord: number = 0;
|
||||||
|
public pageNumber: number = 1;
|
||||||
|
public pageSize: number = 10
|
||||||
|
|
||||||
|
get f() {
|
||||||
|
return this.customEventForm.controls;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getCustomEvents();
|
||||||
|
this.getCustomEventCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomEvents() {
|
||||||
|
this.customEvents = [];
|
||||||
|
this._customEventService.getAllianceCustomEvents(this.allianceId, this.pageNumber, this.pageSize).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this.customEvents = response.data;
|
||||||
|
this.totalRecord = response.totalRecords;
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomEventCategories() {
|
||||||
|
this._customEventCategoryService.getAllianceCustomEventCategories(this.allianceId).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this.customEventCategories = response;
|
||||||
|
} else {
|
||||||
|
this.customEventCategories = [];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
onCreateEvent() {
|
||||||
|
this.createCustomEventForm(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
createCustomEventForm(isUpdate: boolean, customEventDetail: CustomEventDetailModel | null = null) {
|
||||||
|
if (isUpdate) {
|
||||||
|
this.playerSelected = true;
|
||||||
|
this.playerParticipated = [...customEventDetail!.customEventParticipants];
|
||||||
|
} else {
|
||||||
|
this.playerSelected = false;
|
||||||
|
}
|
||||||
|
const d = isUpdate ? new Date(customEventDetail!.eventDate) : new Date();
|
||||||
|
this.customEventForm = new FormGroup({
|
||||||
|
id: new FormControl<string>(isUpdate ? customEventDetail!.id : ''),
|
||||||
|
allianceId: new FormControl<string>(this.allianceId),
|
||||||
|
customEventCategoryId: new FormControl<string | null>(null),
|
||||||
|
name: new FormControl<string>(isUpdate ? customEventDetail!.name : '', [Validators.required, Validators.maxLength(150)]),
|
||||||
|
description: new FormControl<string>(isUpdate ? customEventDetail!.description : '', [Validators.required, Validators.maxLength(500)]),
|
||||||
|
isPointsEvent: new FormControl<boolean>(isUpdate ? customEventDetail!.isPointsEvent : false),
|
||||||
|
isParticipationEvent: new FormControl(isUpdate ? customEventDetail!.isParticipationEvent : false),
|
||||||
|
eventDate: new FormControl<string>(new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())).toISOString().substring(0, 10)),
|
||||||
|
isInProgress: new FormControl<boolean>(isUpdate ? customEventDetail!.isInProgress : true)
|
||||||
|
});
|
||||||
|
this.isCreateCustomEvent = true;
|
||||||
|
if (isUpdate) {
|
||||||
|
this.customEventForm.controls['customEventCategoryId'].disable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onAddParticipants() {
|
||||||
|
const modalRef = this._modalService.open(CustomEventParticipantsModelComponent,
|
||||||
|
{animation: true, backdrop: 'static', centered: true, size: 'lg', scrollable: true});
|
||||||
|
if (this.playerSelected) {
|
||||||
|
this.playerParticipated.sort((a, b) => a.playerName.localeCompare(b.playerName));
|
||||||
|
modalRef.componentInstance.players = [...this.playerParticipated];
|
||||||
|
}
|
||||||
|
modalRef.componentInstance.allianceId = this.allianceId;
|
||||||
|
modalRef.componentInstance.isParticipationEvent = this.customEventForm.controls['isParticipationEvent'].value;
|
||||||
|
modalRef.componentInstance.isPointEvent = this.customEventForm.controls['isPointsEvent'].value;
|
||||||
|
modalRef.closed.subscribe({
|
||||||
|
next: ((response: any[]) => {
|
||||||
|
if (response) {
|
||||||
|
this.playerSelected = true;
|
||||||
|
this.playerParticipated = [...response];
|
||||||
|
this.playerParticipated.sort((a, b) => a.playerName.localeCompare(b.playerName));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel() {
|
||||||
|
this.isUpdate = false;
|
||||||
|
this.isCreateCustomEvent = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
if (this.customEventForm.invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.isUpdate) {
|
||||||
|
this.updateCustomEvent();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const customEvent: CreateCustomEventModel = this.customEventForm.getRawValue() as CreateCustomEventModel;
|
||||||
|
this._customEventService.createCustomEvent(customEvent).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this.insertCustomEventParticipants(response.id);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
error: ((error: any) => {
|
||||||
|
console.error(error);
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
insertCustomEventParticipants(customEventId: string) {
|
||||||
|
const customEventParticipants: CustomEventParticipantModel[] = [];
|
||||||
|
this.playerParticipated.forEach((player) => {
|
||||||
|
const participant: CustomEventParticipantModel = {
|
||||||
|
id: '',
|
||||||
|
customEventId: customEventId,
|
||||||
|
participated: player.participated,
|
||||||
|
playerId: player.playerId,
|
||||||
|
achievedPoints: player.achievedPoints,
|
||||||
|
playerName: player.playerName
|
||||||
|
};
|
||||||
|
customEventParticipants.push(participant);
|
||||||
|
});
|
||||||
|
this._customEventParticipantService.insertCustomEventParticipants(customEventParticipants).subscribe({
|
||||||
|
next: (() => {
|
||||||
|
this.playerParticipated = [];
|
||||||
|
this.onCancel();
|
||||||
|
this.resetAndGetCustomEvents();
|
||||||
|
}),
|
||||||
|
error: (error) => {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onGoToCustomEventDetail(customEvent: CustomEventModel) {
|
||||||
|
this._router.navigate(['custom-event-detail', customEvent.id]).then();
|
||||||
|
}
|
||||||
|
|
||||||
|
onEditCustomEvent(customEvent: CustomEventModel) {
|
||||||
|
this._customEventService.getCustomEventDetail(customEvent.id).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this.customEventParticipants = structuredClone(response.customEventParticipants);
|
||||||
|
this.isUpdate = true;
|
||||||
|
this.createCustomEventForm(true, response);
|
||||||
|
|
||||||
|
this.customEventDetail = response;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onDeleteCustomEvent(customEvent: CustomEventModel) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Delete Custom event ?",
|
||||||
|
text: `Do you really want to delete the custom event`,
|
||||||
|
icon: "warning",
|
||||||
|
showCancelButton: true,
|
||||||
|
confirmButtonColor: "#3085d6",
|
||||||
|
cancelButtonColor: "#d33",
|
||||||
|
confirmButtonText: "Yes, delete it!"
|
||||||
|
}).then((result) => {
|
||||||
|
if (result.isConfirmed) {
|
||||||
|
this._customEventService.deleteCustomEvent(customEvent.id).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
Swal.fire({
|
||||||
|
title: "Deleted!",
|
||||||
|
text: "Custom event has been deleted",
|
||||||
|
icon: "success"
|
||||||
|
}).then(_ => this.resetAndGetCustomEvents());
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
error: (error: Error) => {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCustomEvent() {
|
||||||
|
const participantsToUpdate: any[] = [];
|
||||||
|
const customEvent: CustomEventModel = this.customEventForm.getRawValue() as CustomEventModel;
|
||||||
|
this.customEventParticipants.forEach((participant) => {
|
||||||
|
const player = this.playerParticipated.find(p => p.playerId === participant.playerId);
|
||||||
|
if (player!.participated !== participant.participated || player!.achievedPoints !== participant.achievedPoints) {
|
||||||
|
const playerToUpdate: CustomEventParticipantModel = {
|
||||||
|
playerId: player!.playerId,
|
||||||
|
achievedPoints: player!.achievedPoints,
|
||||||
|
playerName: player!.playerName,
|
||||||
|
participated: player!.participated,
|
||||||
|
id: participant.id,
|
||||||
|
customEventId: participant.customEventId,
|
||||||
|
}
|
||||||
|
participantsToUpdate.push(playerToUpdate);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
this._customEventService.updateCustomEvent(customEvent.id, customEvent).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this.updateCustomEventParticipants(participantsToUpdate);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCustomEventParticipants(participantsToUpdate: any[]) {
|
||||||
|
if (participantsToUpdate.length <= 0) {
|
||||||
|
this._toastr.success('Successfully updated!', 'Successfully');
|
||||||
|
this.onCancel();
|
||||||
|
this.resetAndGetCustomEvents();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const requests: Observable<CustomEventParticipantModel>[] = [];
|
||||||
|
participantsToUpdate.forEach(participant => {
|
||||||
|
const request = this._customEventParticipantService.updateCustomEventParticipant(participant.id, participant);
|
||||||
|
requests.push(request);
|
||||||
|
})
|
||||||
|
forkJoin(requests).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this._toastr.success('Successfully updated!', 'Successfully');
|
||||||
|
this.onCancel();
|
||||||
|
this.resetAndGetCustomEvents();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pageChanged(event: number) {
|
||||||
|
this.pageNumber = event;
|
||||||
|
this.getCustomEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
resetAndGetCustomEvents() {
|
||||||
|
this.pageNumber = 1;
|
||||||
|
this.getCustomEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
onCategoryChange() {
|
||||||
|
const categoryId = this.customEventForm.get('customEventCategoryId')?.value;
|
||||||
|
if (categoryId !== null) {
|
||||||
|
const category = this.customEventCategories.find(e => e.id === categoryId);
|
||||||
|
this.customEventForm.controls['isPointsEvent'].setValue(category?.isPointsEvent);
|
||||||
|
this.customEventForm.controls['isPointsEvent'].disable();
|
||||||
|
this.customEventForm.controls['isParticipationEvent'].setValue(category?.isParticipationEvent);
|
||||||
|
this.customEventForm.controls['isParticipationEvent'].disable();
|
||||||
|
} else {
|
||||||
|
this.customEventForm.controls['isPointsEvent'].setValue(false);
|
||||||
|
this.customEventForm.controls['isParticipationEvent'].setValue(false);
|
||||||
|
this.customEventForm.controls['isPointsEvent'].enable();
|
||||||
|
this.customEventForm.controls['isParticipationEvent'].enable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,278 @@
|
|||||||
|
<h3 class="text-center mb-4">🏆 Leaderboard</h3>
|
||||||
|
<!-- Info-Toggle Button -->
|
||||||
|
<div class="d-flex justify-content-center mb-3">
|
||||||
|
<button class="btn btn-info" type="button" (click)="toggleInfo()">
|
||||||
|
{{ showInfo ? 'Hide' : 'Show' }} How the Leaderboard is Generated
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (showInfo) {
|
||||||
|
<div class="alert alert-light border p-3">
|
||||||
|
<h5>📊 How the Event Leaderboard is Calculated</h5>
|
||||||
|
<ul #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs" ngbNav>
|
||||||
|
<li [ngbNavItem]="1">
|
||||||
|
<button ngbNavLink>Point event</button>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
|
<p>
|
||||||
|
The leaderboard for the <strong>Points Event</strong> is based purely on the total points
|
||||||
|
(<code>AchievedPoints</code>) manually entered per event. There is no participation tracking or bonus logic applied.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h6>🔹 Step 1: Enter Points Per Event</h6>
|
||||||
|
<p>
|
||||||
|
For each event, administrators enter the exact number of points a player has earned.
|
||||||
|
These values are saved and linked to the respective player.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h6>🔹 Step 2: Total Points Are Calculated</h6>
|
||||||
|
<p>
|
||||||
|
The system adds up all points a player has earned across all events. This sum determines their leaderboard position.
|
||||||
|
No participation status, score bonus, or placement logic is applied.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><strong>Formula:</strong></p>
|
||||||
|
<code>Total Points = Sum of AchievedPoints from all events</code>
|
||||||
|
|
||||||
|
<h5>📌 Example Calculation</h5>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Event 1:</strong> 1,200,000 points</li>
|
||||||
|
<li><strong>Event 2:</strong> 950,000 points</li>
|
||||||
|
<li><strong>Event 3:</strong> 2,300,000 points</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Total Points:</strong> <code>1,200,000 + 950,000 + 2,300,000 = 4,450,000</code></p>
|
||||||
|
|
||||||
|
<h5>🔍 How the Leaderboard Works</h5>
|
||||||
|
<p>
|
||||||
|
Players are ranked based on their total accumulated points across all events.
|
||||||
|
The player with the most points is ranked first.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>There is no weighting or adjustment applied.</li>
|
||||||
|
<li>Only point totals matter – no bonus logic is used.</li>
|
||||||
|
<li>All players with at least one entered score are shown in the leaderboard.</li>
|
||||||
|
</ul>
|
||||||
|
</ng-template>
|
||||||
|
</li>
|
||||||
|
<li [ngbNavItem]="2">
|
||||||
|
<button ngbNavLink>Participation event</button>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
|
<p>
|
||||||
|
The leaderboard for the <strong>Participation Event</strong> is based solely on whether a player took part in each event.
|
||||||
|
No scores or placements are used. The more events a player attends, the higher their ranking.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h6>🔹 Step 1: Mark Participation per Event</h6>
|
||||||
|
<p>
|
||||||
|
For each event, administrators simply mark whether a player participated (<code>Participated = true</code>) or not.
|
||||||
|
No further input (like scores or ranks) is required.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h6>🔹 Step 2: Total Participations Are Counted</h6>
|
||||||
|
<p>
|
||||||
|
The system counts the number of events each player has participated in. This count becomes the player’s total score.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p><strong>Formula:</strong></p>
|
||||||
|
<code>Total Participations = Number of Events where Participated = true</code>
|
||||||
|
|
||||||
|
<h5>📌 Example Calculation</h5>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Event 1:</strong> Participated ✅</li>
|
||||||
|
<li><strong>Event 2:</strong> Not Participated ❌</li>
|
||||||
|
<li><strong>Event 3:</strong> Participated ✅</li>
|
||||||
|
<li><strong>Event 4:</strong> Participated ✅</li>
|
||||||
|
</ul>
|
||||||
|
<p><strong>Total Participations:</strong> <code>3</code></p>
|
||||||
|
|
||||||
|
<h5>🔍 How the Leaderboard Works</h5>
|
||||||
|
<p>
|
||||||
|
Players are ranked by the number of events they have participated in.
|
||||||
|
The player with the highest number of participations is listed first.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>There are no scores or ranking bonuses.</li>
|
||||||
|
<li>Only actual participation is counted.</li>
|
||||||
|
<li>All players who are part of the event list are shown, even with 0 participations.</li>
|
||||||
|
</ul>
|
||||||
|
</ng-template>
|
||||||
|
</li>
|
||||||
|
<li [ngbNavItem]="3">
|
||||||
|
<button ngbNavLink>Point and participation</button>
|
||||||
|
<ng-template ngbNavContent>
|
||||||
|
<p>
|
||||||
|
The leaderboard ranks players based on their <strong>participation</strong> and <strong>performance</strong> in events.
|
||||||
|
The calculation rewards consistency and effort, while still recognizing high individual scores.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h6>🔹 Step 1: Participation Bonus (High Weighting)</h6>
|
||||||
|
<p>
|
||||||
|
Every time a player is marked as <code>Participated = true</code>, they receive a fixed bonus of <strong>10 points</strong>.
|
||||||
|
This ensures that active players gain a strong foundation in the leaderboard.
|
||||||
|
</p>
|
||||||
|
<p><strong>Formula:</strong></p>
|
||||||
|
<code>Total Participation Bonus = Number of Participations × 10</code>
|
||||||
|
|
||||||
|
<h6>🔹 Step 2: Placement Bonus (Moderate Weighting)</h6>
|
||||||
|
<p>
|
||||||
|
Players are ranked within each event based on <code>AchievedPoints</code>. The top 100 ranked participants receive bonus points:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>1st place = 100 points</li>
|
||||||
|
<li>2nd place = 99 points</li>
|
||||||
|
<li>...</li>
|
||||||
|
<li>100th place = 1 point</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
This value is normalized (divided by 100) and then multiplied by <strong>2.0</strong>.
|
||||||
|
</p>
|
||||||
|
<p><strong>Formula (per event):</strong></p>
|
||||||
|
<code>Placement Bonus = (Rank Points / 100) × 2</code>
|
||||||
|
|
||||||
|
<h6>🔹 Step 3: Score Bonus (Lower Weighting)</h6>
|
||||||
|
<p>
|
||||||
|
Each player's raw score (<code>AchievedPoints</code>) is converted into a bonus using a logarithmic scale:
|
||||||
|
<code>log10(Score + 1)</code>. This prevents extremely high scores from dominating the leaderboard.
|
||||||
|
</p>
|
||||||
|
<p><strong>Formula (per event):</strong></p>
|
||||||
|
<code>Score Bonus = log10(AchievedPoints + 1) × 1.0</code>
|
||||||
|
|
||||||
|
<h5>📌 Example Calculation</h5>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Events Participated:</strong> 3</li>
|
||||||
|
<li><strong>Event 1:</strong> 1st place, 1,200,000 points</li>
|
||||||
|
<li><strong>Event 2:</strong> 8th place, 950,000 points</li>
|
||||||
|
<li><strong>Event 3:</strong> Participated, 10,000 points</li>
|
||||||
|
</ul>
|
||||||
|
<p>Final Calculation:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Participation Bonus: <code>3 × 10 = 30</code></li>
|
||||||
|
<li>Placement Bonus: <code>(1.00 + 0.93 + 0.21) × 2 = 4.28</code></li>
|
||||||
|
<li>Score Bonus: <code>log10(1,200,001) + log10(950,001) + log10(10,001) ≈ 6.08 + 5.98 + 4.00 = 16.06</code></li>
|
||||||
|
<li><strong>Total Points: 30 + 4.28 + 16.06 = 50.34</strong></li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h5>🔍 How the Leaderboard Works</h5>
|
||||||
|
<p>
|
||||||
|
Players are ranked based on their total points accumulated across all events. The system encourages players to join events regularly and perform well.
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Consistent participation</strong> is the best way to climb the leaderboard.</li>
|
||||||
|
<li><strong>High scores</strong> matter, but do not outweigh multiple solid participations.</li>
|
||||||
|
<li><strong>All players assigned to events</strong> appear on the leaderboard, even with no participations.</li>
|
||||||
|
</ul>
|
||||||
|
</ng-template>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
<div class="form-floating mb-3" (change)="onCategoryChange($event)">
|
||||||
|
<select class="form-select" id="customEventCategoryId">
|
||||||
|
<option [value]="null">Select a category</option>
|
||||||
|
@for (customEventCategory of customEventCategories; track customEventCategory.id) {
|
||||||
|
<option [value]="customEventCategory.id">{{customEventCategory.name}}</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
<label for="customEventCategoryId">Category</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (selectedCustomEventCategory) {
|
||||||
|
<div class="d-flex justify-content-between mt-3">
|
||||||
|
<p>Category: <span class="text-primary">{{selectedCustomEventCategory.name}}</span></p>
|
||||||
|
<p>
|
||||||
|
Point event:
|
||||||
|
<i class="bi" [ngClass]="selectedCustomEventCategory.isPointsEvent ? 'bi-check-lg text-success' : 'bi-x-lg text-danger'"></i>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Participation event:
|
||||||
|
<i class="bi" [ngClass]="selectedCustomEventCategory.isParticipationEvent ? 'bi-check-lg text-success' : 'bi-x-lg text-danger'"></i>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (pointEventLeaderboard) {
|
||||||
|
@if (pointEventLeaderboard.length > 0) {
|
||||||
|
<div class="table-responsive mt-5">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Player</th>
|
||||||
|
<th scope="col">Total points</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (leader of pointEventLeaderboard | paginate: { id: 'pointEventTable', itemsPerPage: 8, currentPage: pointPageNumber}; track leader.playerName) {
|
||||||
|
<tr>
|
||||||
|
<td>{{leader.playerName}}</td>
|
||||||
|
<td>{{leader.points |number}}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<pagination-controls class="custom-pagination" [id]="'pointEventTable'" [responsive]="true" [autoHide]=" true"
|
||||||
|
(pageChange)="pointPageChanged($event)"></pagination-controls>
|
||||||
|
} @else {
|
||||||
|
<p>No events in category: {{selectedCustomEventCategory?.name}}</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (participationEventLeaderboard) {
|
||||||
|
@if (participationEventLeaderboard.length > 0) {
|
||||||
|
<div class="table-responsive mt-5">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Player</th>
|
||||||
|
<th scope="col">Total participations</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (leader of participationEventLeaderboard | paginate: { id: 'participationEventTable', itemsPerPage: 8, currentPage: participationPageNumber}; track leader.playerName) {
|
||||||
|
<tr>
|
||||||
|
<td>{{leader.playerName}}</td>
|
||||||
|
<td>{{leader.participations}}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<pagination-controls class="custom-pagination" [id]="'participationEventTable'" [responsive]="true" [autoHide]=" true"
|
||||||
|
(pageChange)="participationPageChanged($event)"></pagination-controls>
|
||||||
|
} @else {
|
||||||
|
<p>No events in category: {{selectedCustomEventCategory?.name}}</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (pointAndParticipationEventLeaderboard) {
|
||||||
|
@if (pointAndParticipationEventLeaderboard.length > 0) {
|
||||||
|
<div class="table-responsive mt-5">
|
||||||
|
<table class="table table-striped table-bordered">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Player</th>
|
||||||
|
<th scope="col">Score points</th>
|
||||||
|
<th scope="col">Total participations</th>
|
||||||
|
<th scope="col">Total points</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@for (leader of pointAndParticipationEventLeaderboard | paginate: { id: 'pointAndParticipationEventTable', itemsPerPage: 8, currentPage: pointAndParticipationPageNumber}; track leader.playerName) {
|
||||||
|
<tr>
|
||||||
|
<td>{{leader.playerName}}</td>
|
||||||
|
<td>{{leader.totalPoints}}</td>
|
||||||
|
<td>{{leader.participations}}</td>
|
||||||
|
<td>{{leader.points | number}}</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<pagination-controls class="custom-pagination" [id]="'pointAndParticipationEventTable'" [responsive]="true" [autoHide]=" true"
|
||||||
|
(pageChange)="pointAndParticipationPageChanged($event)"></pagination-controls>
|
||||||
|
} @else {
|
||||||
|
<p>No events in category: {{selectedCustomEventCategory?.name}}</p>
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,130 @@
|
|||||||
|
import {Component, inject, OnInit} from '@angular/core';
|
||||||
|
import {CustomEventCategoryModel} from "../../../models/customEventCategory.model";
|
||||||
|
import {CustomEventCategoryService} from "../../../services/custom-event-category.service";
|
||||||
|
import {ToastrService} from "ngx-toastr";
|
||||||
|
import {JwtTokenService} from "../../../services/jwt-token.service";
|
||||||
|
import {CustomEventLeaderboardService} from "../../../services/custom-event-leaderboard.service";
|
||||||
|
import {
|
||||||
|
LeaderboardParticipationEventModel, LeaderboardPointAndParticipationEventModel,
|
||||||
|
LeaderboardPointEventModel
|
||||||
|
} from "../../../models/customEventLeaderboard.model";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-custom-event-leaderboard',
|
||||||
|
templateUrl: './custom-event-leaderboard.component.html',
|
||||||
|
styleUrl: './custom-event-leaderboard.component.css'
|
||||||
|
})
|
||||||
|
export class CustomEventLeaderboardComponent implements OnInit {
|
||||||
|
|
||||||
|
private readonly _customEventCategoryService: CustomEventCategoryService = inject(CustomEventCategoryService);
|
||||||
|
private readonly _toastr: ToastrService = inject(ToastrService);
|
||||||
|
private readonly _tokenService: JwtTokenService = inject(JwtTokenService);
|
||||||
|
private readonly _customLeaderboardService: CustomEventLeaderboardService = inject(CustomEventLeaderboardService);
|
||||||
|
|
||||||
|
private allianceId: string = this._tokenService.getAllianceId()!;
|
||||||
|
|
||||||
|
customEventCategories: CustomEventCategoryModel[] = [];
|
||||||
|
selectedCustomEventCategory: CustomEventCategoryModel | undefined;
|
||||||
|
pointEventLeaderboard: LeaderboardPointEventModel[] | undefined;
|
||||||
|
participationEventLeaderboard: LeaderboardParticipationEventModel[] | undefined;
|
||||||
|
pointAndParticipationEventLeaderboard: LeaderboardPointAndParticipationEventModel[] | undefined;
|
||||||
|
pointPageNumber: number = 1;
|
||||||
|
participationPageNumber: number = 1;
|
||||||
|
pointAndParticipationPageNumber: number = 1;
|
||||||
|
showInfo: boolean = false;
|
||||||
|
activeTab: number = 1;
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getCustomEventCategories();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomEventCategories() {
|
||||||
|
this._customEventCategoryService.getAllianceCustomEventCategories(this.allianceId).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this.customEventCategories = response;
|
||||||
|
} else {
|
||||||
|
this.customEventCategories = [];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCategoryChange(event: any) {
|
||||||
|
this.pointEventLeaderboard = undefined;
|
||||||
|
this.participationEventLeaderboard = undefined;
|
||||||
|
this.pointAndParticipationEventLeaderboard = undefined;
|
||||||
|
const categoryId = event.target.value;
|
||||||
|
if (categoryId !== 'null') {
|
||||||
|
this.selectedCustomEventCategory = this.customEventCategories.find(e => e.id === categoryId);
|
||||||
|
if (this.selectedCustomEventCategory!.isPointsEvent && this.selectedCustomEventCategory!.isParticipationEvent) {
|
||||||
|
this.getPointAndParticipationEventLeaderboard(this.selectedCustomEventCategory!.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.selectedCustomEventCategory!.isParticipationEvent) {
|
||||||
|
this.getParticipationEventLeaderboard(this.selectedCustomEventCategory!.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.selectedCustomEventCategory!.isPointsEvent) {
|
||||||
|
this.getPointEventLeaderboard(this.selectedCustomEventCategory!.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.selectedCustomEventCategory = undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPointEventLeaderboard(customEventCategoryId: string) {
|
||||||
|
this._customLeaderboardService.getPointEvent(customEventCategoryId).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this.pointEventLeaderboard = response;
|
||||||
|
} else {
|
||||||
|
this.pointEventLeaderboard = [];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getParticipationEventLeaderboard(customEventCategoryId: string) {
|
||||||
|
this._customLeaderboardService.getParticipationEvent(customEventCategoryId).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this.participationEventLeaderboard = response;
|
||||||
|
} else {
|
||||||
|
this.participationEventLeaderboard = [];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
getPointAndParticipationEventLeaderboard(customEventCategoryId: string) {
|
||||||
|
this._customLeaderboardService.getPointAndParticipationEvent(customEventCategoryId).subscribe({
|
||||||
|
next: ((response) => {
|
||||||
|
if (response) {
|
||||||
|
this.pointAndParticipationEventLeaderboard = response;
|
||||||
|
} else {
|
||||||
|
this.pointAndParticipationEventLeaderboard = [];
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pointPageChanged(event: number) {
|
||||||
|
this.pointPageNumber = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
participationPageChanged(event: number) {
|
||||||
|
this.participationPageNumber = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointAndParticipationPageChanged(event: number) {
|
||||||
|
this.pointAndParticipationPageNumber = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleInfo() {
|
||||||
|
this.showInfo = !this.showInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,169 +1,25 @@
|
|||||||
<div class="container mt-3 pb-5">
|
<div class="container mt-3 pb-5">
|
||||||
<h2 class="text-center">Custom Event</h2>
|
<h2 class="text-center">Custom Event</h2>
|
||||||
|
|
||||||
@if (!isCreateCustomEvent) {
|
<ul #nav="ngbNav" [(activeId)]="activeTab" class="nav-tabs" ngbNav>
|
||||||
<div class="d-grid gap-2 col-6 mx-auto">
|
<li [ngbNavItem]="1">
|
||||||
<button (click)="onCreateEvent()" class="btn btn-primary" type="button">Create new Event</button>
|
<button ngbNavLink>Events</button>
|
||||||
</div>
|
<ng-template ngbNavContent>
|
||||||
}
|
<app-custom-event-events></app-custom-event-events>
|
||||||
|
</ng-template>
|
||||||
@if (isCreateCustomEvent) {
|
</li>
|
||||||
<form [formGroup]="customEventForm">
|
<li [ngbNavItem]="2">
|
||||||
<!-- Event date-->
|
<button ngbNavLink>Category</button>
|
||||||
<div class="form-floating mb-3 is-invalid">
|
<ng-template ngbNavContent>
|
||||||
<input [ngClass]="{
|
<app-custom-event-category></app-custom-event-category>
|
||||||
'is-invalid': f['eventDate'].invalid && (f['eventDate'].dirty || f['eventDate'].touched),
|
</ng-template>
|
||||||
'is-valid': f['eventDate'].valid}"
|
</li>
|
||||||
type="date" class="form-control" id="eventDate" placeholder="eventDate" formControlName="eventDate">
|
<li [ngbNavItem]="3">
|
||||||
<label for="eventDate">Event date</label>
|
<button ngbNavLink>Leaderboard</button>
|
||||||
@if (f['eventDate'].invalid && (f['eventDate'].dirty || f['eventDate'].touched)) {
|
<ng-template ngbNavContent>
|
||||||
<div class="invalid-feedback">
|
<app-custom-event-leaderboard></app-custom-event-leaderboard>
|
||||||
@if (f['eventDate'].hasError('required')) {
|
</ng-template>
|
||||||
<p>eventDate is required</p>
|
</li>
|
||||||
}
|
</ul>
|
||||||
</div>
|
<div [ngbNavOutlet]="nav" class="mt-2"></div>
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<!-- Event name-->
|
|
||||||
<div class="form-floating mb-3 is-invalid">
|
|
||||||
<input [ngClass]="{
|
|
||||||
'is-invalid': f['name'].invalid && (f['name'].dirty || f['name'].touched),
|
|
||||||
'is-valid': f['name'].valid}"
|
|
||||||
type="text" class="form-control" maxlength="151" id="name" placeholder="name" formControlName="name">
|
|
||||||
<label for="name">Event name</label>
|
|
||||||
@if (f['name'].invalid && (f['name'].dirty || f['name'].touched)) {
|
|
||||||
<div class="invalid-feedback">
|
|
||||||
@if (f['name'].hasError('required')) {
|
|
||||||
<p>Name is required</p>
|
|
||||||
}
|
|
||||||
@if (f['name'].hasError('maxlength')) {
|
|
||||||
<p>Maximum 150 characters allowed</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<!-- Description-->
|
|
||||||
<div class="form-floating mb-3">
|
|
||||||
<textarea [ngClass]="{
|
|
||||||
'is-valid': f['description'].valid,
|
|
||||||
'is-invalid': f['description'].invalid && (f['description'].touched || f['description'].dirty)
|
|
||||||
}" class="form-control" maxlength="501" formControlName="description" placeholder="description" id="description" style="height: 100px"></textarea>
|
|
||||||
<label for="description">description</label>
|
|
||||||
@if (f['description'].invalid && (f['description'].touched || f['description'].dirty)) {
|
|
||||||
<div class="invalid-feedback">
|
|
||||||
@if (f['description'].hasError('required')) {
|
|
||||||
<p>Description is required</p>
|
|
||||||
}
|
|
||||||
@if (f['description'].hasError('maxlength')) {
|
|
||||||
<p>Maximum 500 characters allowed</p>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<!-- In progress-->
|
|
||||||
<div class="form-check mb-3">
|
|
||||||
<input class="form-check-input" type="checkbox" formControlName="isInProgress" id="isInProgress">
|
|
||||||
<label class="form-check-label" for="isInProgress">
|
|
||||||
In Progress
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<!-- Is point event-->
|
|
||||||
<div class="form-check mb-3">
|
|
||||||
<input class="form-check-input" type="checkbox" formControlName="isPointsEvent" id="isPointsEvent">
|
|
||||||
<label class="form-check-label" for="isPointsEvent">
|
|
||||||
Points Event
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<!-- Is participation event-->
|
|
||||||
<div class="form-check mb-3">
|
|
||||||
<input class="form-check-input" type="checkbox" formControlName="isParticipationEvent" id="isParticipationEvent">
|
|
||||||
<label class="form-check-label" for="isParticipationEvent">
|
|
||||||
Participation Event
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
@if (!playerSelected) {
|
|
||||||
<div class="d-flex justify-content-center">
|
|
||||||
<p class="text-warning">Please add participants</p>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="d-grid gap-2 col-6 mx-auto">
|
|
||||||
<button (click)="onAddParticipants()" class="btn btn-primary" type="button">{{isUpdate ? 'Update Participants' : 'Add Participants'}}</button>
|
|
||||||
</div>
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<button (click)="onCancel()" type="button" class="btn btn-warning">Cancel</button>
|
|
||||||
<button [disabled]="customEventForm.invalid || playerParticipated.length <= 0" (click)="onSubmit()" type="submit" class="btn btn-success">{{isUpdate ? 'Update': 'Create'}}</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (!isCreateCustomEvent) {
|
|
||||||
@if (customEvents.length > 0) {
|
|
||||||
<div class="table-responsive mt-5">
|
|
||||||
<table class="table table-striped table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th scope="col">Event Date</th>
|
|
||||||
<th scope="col">Name</th>
|
|
||||||
<th scope="col">Point event</th>
|
|
||||||
<th scope="col">Participation event</th>
|
|
||||||
<th scope="col">Status</th>
|
|
||||||
<th scope="col">Creator</th>
|
|
||||||
<th scope="col">Action</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
@for (customEvent of customEvents | paginate: { id: 'customEventTable', itemsPerPage: pageSize, totalItems: totalRecord, currentPage: pageNumber}; track customEvent.id) {
|
|
||||||
<tr>
|
|
||||||
<td>{{customEvent.eventDate | date: 'dd.MM.yyyy'}}</td>
|
|
||||||
<td>{{customEvent.name}}</td>
|
|
||||||
<td>
|
|
||||||
<i class="bi " [ngClass]="customEvent.isPointsEvent ? 'bi-check-lg text-success' : 'bi-x-lg text-danger'"></i>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<i class="bi " [ngClass]="customEvent.isParticipationEvent ? 'bi-check-lg text-success' : 'bi-x-lg text-danger'"></i>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@if (customEvent.isInProgress) {
|
|
||||||
<i class="bi bi-hourglass-split"></i> In Progress
|
|
||||||
} @else {
|
|
||||||
Done
|
|
||||||
}
|
|
||||||
</td>
|
|
||||||
<td>{{customEvent.createdBy}}</td>
|
|
||||||
<td>
|
|
||||||
<div class="d-flex gap-3 justify-content-around">
|
|
||||||
<i ngbTooltip="Show details" placement="auto" (click)="onGoToCustomEventDetail(customEvent)" class="bi custom-info-icon bi-info-circle-fill"></i>
|
|
||||||
<i ngbTooltip="Edit" placement="auto" (click)="onEditCustomEvent(customEvent)" class="bi custom-edit-icon bi-pencil-fill"></i>
|
|
||||||
<i ngbTooltip="Delete" placement="auto" (click)="onDeleteCustomEvent(customEvent)" class="bi custom-delete-icon bi-trash3"></i>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
<!-- Pagination Controls -->
|
|
||||||
<div class="d-flex justify-content-between mt-3 flex-column flex-md-row">
|
|
||||||
<pagination-controls class="custom-pagination" [id]="'customEventTable'" [responsive]="true" [autoHide]=" true"
|
|
||||||
(pageChange)="pageChanged($event)"></pagination-controls>
|
|
||||||
|
|
||||||
<!-- Showing total results with improved styling -->
|
|
||||||
<div class="align-self-center text-muted mt-2 mt-md-0">
|
|
||||||
<small>
|
|
||||||
Showing
|
|
||||||
<strong>{{ (pageNumber - 1) * pageSize + 1 }} - {{ pageNumber * pageSize > totalRecord ? totalRecord : pageNumber * pageSize }}</strong>
|
|
||||||
of <strong>{{ totalRecord }}</strong> results
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
} @else {
|
|
||||||
<div class="alert alert-secondary text-center mt-5" role="alert">
|
|
||||||
No saved custom events
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,272 +1,12 @@
|
|||||||
import {Component, inject, OnInit} from '@angular/core';
|
import {Component} from '@angular/core';
|
||||||
import {FormControl, FormGroup, Validators} from "@angular/forms";
|
|
||||||
import {JwtTokenService} from "../../services/jwt-token.service";
|
|
||||||
import {CreateCustomEventModel, CustomEventDetailModel, CustomEventModel} from "../../models/customEvent.model";
|
|
||||||
import {NgbModal} from "@ng-bootstrap/ng-bootstrap";
|
|
||||||
import {
|
|
||||||
CustomEventParticipantsModelComponent
|
|
||||||
} from "../../modals/custom-event-participants-model/custom-event-participants-model.component";
|
|
||||||
import {CustomEventService} from "../../services/custom-event.service";
|
|
||||||
import {CustomEventParticipantModel} from "../../models/customEventParticipant.model";
|
|
||||||
import {CustomEventParticipantService} from "../../services/custom-event-participant.service";
|
|
||||||
import {Router} from "@angular/router";
|
|
||||||
import Swal from "sweetalert2";
|
|
||||||
import {ToastrService} from "ngx-toastr";
|
|
||||||
import {forkJoin, Observable} from "rxjs";
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-custom-event',
|
selector: 'app-custom-event',
|
||||||
templateUrl: './custom-event.component.html',
|
templateUrl: './custom-event.component.html',
|
||||||
styleUrl: './custom-event.component.css'
|
styleUrl: './custom-event.component.css'
|
||||||
})
|
})
|
||||||
export class CustomEventComponent implements OnInit {
|
export class CustomEventComponent {
|
||||||
|
|
||||||
customEvents: CustomEventModel[] = [];
|
public activeTab: number = 1;
|
||||||
customEventDetail!: CustomEventDetailModel;
|
|
||||||
isCreateCustomEvent: boolean = false;
|
|
||||||
customEventForm!: FormGroup;
|
|
||||||
playerSelected: boolean = false;
|
|
||||||
playerParticipated: { playerId: string, playerName: string, participated: boolean, achievedPoints: number }[] = [];
|
|
||||||
customEventParticipants: CustomEventParticipantModel[] = [];
|
|
||||||
isUpdate: boolean = false;
|
|
||||||
private readonly _tokenService: JwtTokenService = inject(JwtTokenService);
|
|
||||||
private readonly _modalService: NgbModal = inject(NgbModal);
|
|
||||||
private readonly _customEventService: CustomEventService = inject(CustomEventService);
|
|
||||||
private readonly _customEventParticipantService: CustomEventParticipantService = inject(CustomEventParticipantService);
|
|
||||||
private readonly _router: Router = inject(Router);
|
|
||||||
private readonly _toastr: ToastrService = inject(ToastrService);
|
|
||||||
private allianceId: string = this._tokenService.getAllianceId()!;
|
|
||||||
|
|
||||||
public totalRecord: number = 0;
|
|
||||||
public pageNumber: number = 1;
|
|
||||||
public pageSize: number = 10
|
|
||||||
|
|
||||||
get f() {
|
|
||||||
return this.customEventForm.controls;
|
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit() {
|
|
||||||
this.getCustomEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
getCustomEvents() {
|
|
||||||
this.customEvents = [];
|
|
||||||
this._customEventService.getAllianceCustomEvents(this.allianceId, this.pageNumber, this.pageSize).subscribe({
|
|
||||||
next: ((response) => {
|
|
||||||
if (response) {
|
|
||||||
this.customEvents = response.data;
|
|
||||||
this.totalRecord = response.totalRecords;
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
error: (error) => {
|
|
||||||
console.error(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
onCreateEvent() {
|
|
||||||
this.createCustomEventForm(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
createCustomEventForm(isUpdate: boolean, customEventDetail: CustomEventDetailModel | null = null) {
|
|
||||||
if (isUpdate) {
|
|
||||||
this.playerSelected = true;
|
|
||||||
this.playerParticipated = [...customEventDetail!.customEventParticipants];
|
|
||||||
} else {
|
|
||||||
this.playerSelected = false;
|
|
||||||
}
|
|
||||||
const d = isUpdate ? new Date(customEventDetail!.eventDate) : new Date();
|
|
||||||
this.customEventForm = new FormGroup({
|
|
||||||
id: new FormControl<string>(isUpdate ? customEventDetail!.id : ''),
|
|
||||||
allianceId: new FormControl<string>(this.allianceId),
|
|
||||||
name: new FormControl<string>(isUpdate ? customEventDetail!.name : '', [Validators.required, Validators.maxLength(150)]),
|
|
||||||
description: new FormControl<string>(isUpdate ? customEventDetail!.description : '', [Validators.required, Validators.maxLength(500)]),
|
|
||||||
isPointsEvent: new FormControl<boolean>(isUpdate ? customEventDetail!.isPointsEvent : false),
|
|
||||||
isParticipationEvent: new FormControl(isUpdate ? customEventDetail!.isParticipationEvent : false),
|
|
||||||
eventDate: new FormControl<string>(new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())).toISOString().substring(0, 10)),
|
|
||||||
isInProgress: new FormControl<boolean>(isUpdate ? customEventDetail!.isInProgress : true)
|
|
||||||
});
|
|
||||||
this.isCreateCustomEvent = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onAddParticipants() {
|
|
||||||
const modalRef = this._modalService.open(CustomEventParticipantsModelComponent,
|
|
||||||
{animation: true, backdrop: 'static', centered: true, size: 'lg', scrollable: true});
|
|
||||||
if (this.playerSelected) {
|
|
||||||
this.playerParticipated.sort((a, b) => a.playerName.localeCompare(b.playerName));
|
|
||||||
modalRef.componentInstance.players = [...this.playerParticipated];
|
|
||||||
}
|
|
||||||
modalRef.componentInstance.allianceId = this.allianceId;
|
|
||||||
modalRef.componentInstance.isParticipationEvent = this.customEventForm.controls['isParticipationEvent'].value;
|
|
||||||
modalRef.componentInstance.isPointEvent = this.customEventForm.controls['isPointsEvent'].value;
|
|
||||||
modalRef.closed.subscribe({
|
|
||||||
next: ((response: any[]) => {
|
|
||||||
if (response) {
|
|
||||||
this.playerSelected = true;
|
|
||||||
this.playerParticipated = [...response];
|
|
||||||
this.playerParticipated.sort((a, b) => a.playerName.localeCompare(b.playerName));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
onCancel() {
|
|
||||||
this.isUpdate = false;
|
|
||||||
this.isCreateCustomEvent = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit() {
|
|
||||||
if (this.customEventForm.invalid) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (this.isUpdate) {
|
|
||||||
this.updateCustomEvent();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const customEvent: CreateCustomEventModel = this.customEventForm.value as CreateCustomEventModel;
|
|
||||||
this._customEventService.createCustomEvent(customEvent).subscribe({
|
|
||||||
next: ((response) => {
|
|
||||||
if (response) {
|
|
||||||
this.insertCustomEventParticipants(response.id);
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
error: ((error: any) => {
|
|
||||||
console.error(error);
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
insertCustomEventParticipants(customEventId: string) {
|
|
||||||
const customEventParticipants: CustomEventParticipantModel[] = [];
|
|
||||||
this.playerParticipated.forEach((player) => {
|
|
||||||
const participant: CustomEventParticipantModel = {
|
|
||||||
id: '',
|
|
||||||
customEventId: customEventId,
|
|
||||||
participated: player.participated,
|
|
||||||
playerId: player.playerId,
|
|
||||||
achievedPoints: player.achievedPoints,
|
|
||||||
playerName: player.playerName
|
|
||||||
};
|
|
||||||
customEventParticipants.push(participant);
|
|
||||||
});
|
|
||||||
this._customEventParticipantService.insertCustomEventParticipants(customEventParticipants).subscribe({
|
|
||||||
next: (() => {
|
|
||||||
this.playerParticipated = [];
|
|
||||||
this.onCancel();
|
|
||||||
this.resetAndGetCustomEvents();
|
|
||||||
}),
|
|
||||||
error: (error) => {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onGoToCustomEventDetail(customEvent: CustomEventModel) {
|
|
||||||
this._router.navigate(['custom-event-detail', customEvent.id]).then();
|
|
||||||
}
|
|
||||||
|
|
||||||
onEditCustomEvent(customEvent: CustomEventModel) {
|
|
||||||
this._customEventService.getCustomEventDetail(customEvent.id).subscribe({
|
|
||||||
next: ((response) => {
|
|
||||||
if (response) {
|
|
||||||
this.customEventParticipants = structuredClone(response.customEventParticipants);
|
|
||||||
this.isUpdate = true;
|
|
||||||
this.createCustomEventForm(true, response);
|
|
||||||
|
|
||||||
this.customEventDetail = response;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeleteCustomEvent(customEvent: CustomEventModel) {
|
|
||||||
Swal.fire({
|
|
||||||
title: "Delete Custom event ?",
|
|
||||||
text: `Do you really want to delete the custom event`,
|
|
||||||
icon: "warning",
|
|
||||||
showCancelButton: true,
|
|
||||||
confirmButtonColor: "#3085d6",
|
|
||||||
cancelButtonColor: "#d33",
|
|
||||||
confirmButtonText: "Yes, delete it!"
|
|
||||||
}).then((result) => {
|
|
||||||
if (result.isConfirmed) {
|
|
||||||
this._customEventService.deleteCustomEvent(customEvent.id).subscribe({
|
|
||||||
next: ((response) => {
|
|
||||||
if (response) {
|
|
||||||
Swal.fire({
|
|
||||||
title: "Deleted!",
|
|
||||||
text: "Custom event has been deleted",
|
|
||||||
icon: "success"
|
|
||||||
}).then(_ => this.resetAndGetCustomEvents());
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
error: (error: Error) => {
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateCustomEvent() {
|
|
||||||
const participantsToUpdate: any[] = [];
|
|
||||||
const customEvent: CustomEventModel = this.customEventForm.value as CustomEventModel;
|
|
||||||
this.customEventParticipants.forEach((participant) => {
|
|
||||||
const player = this.playerParticipated.find(p => p.playerId === participant.playerId);
|
|
||||||
if (player!.participated !== participant.participated || player!.achievedPoints !== participant.achievedPoints) {
|
|
||||||
const playerToUpdate: CustomEventParticipantModel = {
|
|
||||||
playerId: player!.playerId,
|
|
||||||
achievedPoints: player!.achievedPoints,
|
|
||||||
playerName: player!.playerName,
|
|
||||||
participated: player!.participated,
|
|
||||||
id: participant.id,
|
|
||||||
customEventId: participant.customEventId,
|
|
||||||
}
|
|
||||||
participantsToUpdate.push(playerToUpdate);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this._customEventService.updateCustomEvent(customEvent.id, customEvent).subscribe({
|
|
||||||
next: ((response) => {
|
|
||||||
if (response) {
|
|
||||||
this.updateCustomEventParticipants(participantsToUpdate);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private updateCustomEventParticipants(participantsToUpdate: any[]) {
|
|
||||||
if (participantsToUpdate.length <= 0) {
|
|
||||||
this._toastr.success('Successfully updated!', 'Successfully');
|
|
||||||
this.onCancel();
|
|
||||||
this.resetAndGetCustomEvents();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const requests: Observable<CustomEventParticipantModel>[] = [];
|
|
||||||
participantsToUpdate.forEach(participant => {
|
|
||||||
const request = this._customEventParticipantService.updateCustomEventParticipant(participant.id, participant);
|
|
||||||
requests.push(request);
|
|
||||||
})
|
|
||||||
forkJoin(requests).subscribe({
|
|
||||||
next: ((response) => {
|
|
||||||
if (response) {
|
|
||||||
this._toastr.success('Successfully updated!', 'Successfully');
|
|
||||||
this.onCancel();
|
|
||||||
this.resetAndGetCustomEvents();
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pageChanged(event: number) {
|
|
||||||
this.pageNumber = event;
|
|
||||||
this.getCustomEvents();
|
|
||||||
}
|
|
||||||
|
|
||||||
resetAndGetCustomEvents() {
|
|
||||||
this.pageNumber = 1;
|
|
||||||
this.getCustomEvents();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
38
Ui/src/app/services/custom-event-category.service.ts
Normal file
38
Ui/src/app/services/custom-event-category.service.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import {inject, Injectable} from '@angular/core';
|
||||||
|
import {environment} from "../../environments/environment";
|
||||||
|
import {HttpClient} from "@angular/common/http";
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
import {
|
||||||
|
CreateCustomEventCategoryModel,
|
||||||
|
CustomEventCategoryModel,
|
||||||
|
UpdateCustomEventCategoryModel
|
||||||
|
} from "../models/customEventCategory.model";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CustomEventCategoryService {
|
||||||
|
|
||||||
|
private readonly _serviceUrl = environment.apiBaseUrl + 'customEventCategories/';
|
||||||
|
private readonly _httpClient: HttpClient = inject(HttpClient);
|
||||||
|
|
||||||
|
getAllianceCustomEventCategories(allianceId: string): Observable<CustomEventCategoryModel[]> {
|
||||||
|
return this._httpClient.get<CustomEventCategoryModel[]>(this._serviceUrl + 'Alliance/' + allianceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCustomEventCategory(customEventCategoryId: string): Observable<CustomEventCategoryModel> {
|
||||||
|
return this._httpClient.get<CustomEventCategoryModel>(this._serviceUrl + customEventCategoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
createCustomEventCategory(customEventCategory: CreateCustomEventCategoryModel): Observable<CustomEventCategoryModel> {
|
||||||
|
return this._httpClient.post<CustomEventCategoryModel>(this._serviceUrl, customEventCategory);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCustomEvent(customEventCategoryId: string, customEvent: UpdateCustomEventCategoryModel): Observable<CustomEventCategoryModel> {
|
||||||
|
return this._httpClient.put<CustomEventCategoryModel>(this._serviceUrl + customEventCategoryId, customEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteCustomEventCategory(customEventCategoryId: string): Observable<boolean> {
|
||||||
|
return this._httpClient.delete<boolean>(this._serviceUrl + customEventCategoryId );
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Ui/src/app/services/custom-event-leaderboard.service.ts
Normal file
31
Ui/src/app/services/custom-event-leaderboard.service.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import {inject, Injectable} from '@angular/core';
|
||||||
|
import {environment} from "../../environments/environment";
|
||||||
|
import {HttpClient} from "@angular/common/http";
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
import {
|
||||||
|
LeaderboardParticipationEventModel,
|
||||||
|
LeaderboardPointAndParticipationEventModel,
|
||||||
|
LeaderboardPointEventModel
|
||||||
|
} from "../models/customEventLeaderboard.model";
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class CustomEventLeaderboardService {
|
||||||
|
|
||||||
|
private readonly _serviceUrl = environment.apiBaseUrl + 'customEventLeaderboards/';
|
||||||
|
private readonly _httpClient: HttpClient = inject(HttpClient);
|
||||||
|
|
||||||
|
getPointEvent(customEventCategoryId: string): Observable<LeaderboardPointEventModel[]> {
|
||||||
|
return this._httpClient.get<LeaderboardPointEventModel[]>(this._serviceUrl + 'point/' + customEventCategoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getParticipationEvent(customEventCategoryId: string): Observable<LeaderboardParticipationEventModel[]> {
|
||||||
|
return this._httpClient.get<LeaderboardParticipationEventModel[]>(this._serviceUrl + 'participation/' + customEventCategoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
getPointAndParticipationEvent(customEventCategoryId: string): Observable<LeaderboardPointAndParticipationEventModel[]> {
|
||||||
|
return this._httpClient.get<LeaderboardPointAndParticipationEventModel[]>(this._serviceUrl + 'point-and-participation/' + customEventCategoryId);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user