Skip to content

Commit

Permalink
Use QuickGrid for custom entry admin page
Browse files Browse the repository at this point in the history
  • Loading branch information
NoahStolk committed Mar 4, 2024
1 parent ce94a81 commit f50efd1
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 119 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<ItemGroup>
<PackageReference Include="Blazored.LocalStorage" />
<PackageReference Include="Microsoft.AspNetCore.Components.QuickGrid" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" PrivateAssets="all" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" />
Expand Down
12 changes: 2 additions & 10 deletions src/DevilDaggersInfo.Web.Client/HttpClients/AdminApiHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,9 @@ public async Task<HttpResponseMessage> ClearCache(string cacheType)
return await SendRequest(HttpMethod.Post, "api/admin/cache/clear-cache", JsonContent.Create(cacheType));
}

public async Task<Page<GetCustomEntryForOverview>> GetCustomEntries(string? filter, int pageIndex, int pageSize, CustomEntrySorting? sortBy, bool ascending)
public async Task<List<GetCustomEntryForOverview>> GetCustomEntries()
{
Dictionary<string, object?> queryParameters = new()
{
{ nameof(filter), filter },
{ nameof(pageIndex), pageIndex },
{ nameof(pageSize), pageSize },
{ nameof(sortBy), sortBy },
{ nameof(ascending), ascending },
};
return await SendGetRequest<Page<GetCustomEntryForOverview>>(BuildUrlWithQuery("api/admin/custom-entries/", queryParameters));
return await SendGetRequest<List<GetCustomEntryForOverview>>("api/admin/custom-entries/");
}

public async Task<GetCustomEntry> GetCustomEntryById(int id)
Expand Down
107 changes: 55 additions & 52 deletions src/DevilDaggersInfo.Web.Client/Pages/Admin/CustomEntries/Index.razor
Original file line number Diff line number Diff line change
Expand Up @@ -2,60 +2,63 @@
@inherits BaseAdminPage
@using DevilDaggersInfo.Core.Common
@using DevilDaggersInfo.Web.ApiSpec.Admin.CustomEntries
@using DevilDaggersInfo.Web.Client.Components.Admin
@using DevilDaggersInfo.Web.Core.Claims
@using Microsoft.AspNetCore.Components.QuickGrid

<AdminAuthorizer Base="this" RequiredRole="@Roles.Admin">
<AdminOverview @ref="_overview" TGetDto="GetCustomEntryForOverview" TSorting="CustomEntrySorting" Title="Admin - Custom Entries" ApiCall="Http.GetCustomEntries" DeletionApiCall="(int id) => Http.DeleteCustomEntryById(id)" GridConfiguration="grid-cols-admin-custom-entries-sm md:grid-cols-admin-custom-entries-md lg:grid-cols-admin-custom-entries-lg xl:grid-cols-admin-custom-entries-xl">
<TableHeader>
<a class="link overflow-hidden text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.Id)" @onclick:preventDefault>Id</a>
<a class="link overflow-hidden text-left" href="" @onclick="() => _overview.Sort(CustomEntrySorting.SpawnsetName)" @onclick:preventDefault>Name</a>
<a class="link overflow-hidden text-left" href="" @onclick="() => _overview.Sort(CustomEntrySorting.PlayerName)" @onclick:preventDefault>Player</a>
<a class="link overflow-hidden hidden md:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.Time)" @onclick:preventDefault>Time</a>
<a class="link overflow-hidden hidden md:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.GemsCollected)" @onclick:preventDefault>Gems</a>
<a class="link overflow-hidden hidden md:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.GemsDespawned)" @onclick:preventDefault>Gems desp</a>
<a class="link overflow-hidden hidden md:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.GemsEaten)" @onclick:preventDefault>Gems eaten</a>
<a class="link overflow-hidden hidden md:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.GemsTotal)" @onclick:preventDefault>Gems total</a>
<a class="link overflow-hidden hidden lg:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.EnemiesKilled)" @onclick:preventDefault>Kills</a>
<a class="link overflow-hidden hidden lg:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.EnemiesAlive)" @onclick:preventDefault>Alive</a>
<a class="link overflow-hidden hidden lg:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.DaggersFired)" @onclick:preventDefault>Fired</a>
<a class="link overflow-hidden hidden lg:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.DaggersHit)" @onclick:preventDefault>Hit</a>
<a class="link overflow-hidden hidden lg:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.HomingStored)" @onclick:preventDefault>Homing</a>
<a class="link overflow-hidden hidden lg:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.HomingEaten)" @onclick:preventDefault>Homing eaten</a>
<a class="link overflow-hidden hidden lg:block text-left" href="" @onclick="() => _overview.Sort(CustomEntrySorting.DeathType)" @onclick:preventDefault>Death</a>
<a class="link overflow-hidden hidden xl:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.LevelUpTime2)" @onclick:preventDefault>Level 2</a>
<a class="link overflow-hidden hidden xl:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.LevelUpTime3)" @onclick:preventDefault>Level 3</a>
<a class="link overflow-hidden hidden xl:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.LevelUpTime4)" @onclick:preventDefault>Level 4</a>
<a class="link overflow-hidden hidden xl:block text-left" href="" @onclick="() => _overview.Sort(CustomEntrySorting.SubmitDate)" @onclick:preventDefault>Date</a>
<a class="link overflow-hidden hidden xl:block text-right" href="" @onclick="() => _overview.Sort(CustomEntrySorting.ClientVersion)" @onclick:preventDefault>Version</a>
</TableHeader>
<RowTemplate Context="customLeaderboard">
<div class="overflow-hidden text-right">@customLeaderboard.Id</div>
<div class="overflow-hidden text-left">@customLeaderboard.SpawnsetName</div>
<div class="overflow-hidden text-left">@customLeaderboard.PlayerName</div>
<div class="overflow-hidden hidden md:block text-right">@customLeaderboard.Time.ToString(StringFormats.TimeFormat)</div>
<div class="overflow-hidden hidden md:block text-right">@customLeaderboard.GemsCollected</div>
<div class="overflow-hidden hidden md:block text-right">@customLeaderboard.GemsDespawned</div>
<div class="overflow-hidden hidden md:block text-right">@customLeaderboard.GemsEaten</div>
<div class="overflow-hidden hidden md:block text-right">@customLeaderboard.GemsTotal</div>
<div class="overflow-hidden hidden lg:block text-right">@customLeaderboard.EnemiesKilled</div>
<div class="overflow-hidden hidden lg:block text-right">@customLeaderboard.EnemiesAlive</div>
<div class="overflow-hidden hidden lg:block text-right">@customLeaderboard.DaggersFired</div>
<div class="overflow-hidden hidden lg:block text-right">@customLeaderboard.DaggersHit</div>
<div class="overflow-hidden hidden lg:block text-right">@customLeaderboard.HomingStored</div>
<div class="overflow-hidden hidden lg:block text-right">@customLeaderboard.HomingEaten</div>
<div class="overflow-hidden hidden lg:block text-left">@customLeaderboard.DeathType</div>
<div class="overflow-hidden hidden xl:block text-right">@customLeaderboard.LevelUpTime2.ToString(StringFormats.TimeFormat)</div>
<div class="overflow-hidden hidden xl:block text-right">@customLeaderboard.LevelUpTime3.ToString(StringFormats.TimeFormat)</div>
<div class="overflow-hidden hidden xl:block text-right">@customLeaderboard.LevelUpTime4.ToString(StringFormats.TimeFormat)</div>
<div class="overflow-hidden hidden xl:block text-left">@customLeaderboard.SubmitDate.ToString(StringFormats.DateTimeUtcFormat)</div>
<div class="overflow-hidden hidden xl:block text-right">@customLeaderboard.ClientVersion</div>
</RowTemplate>
</AdminOverview>
</AdminAuthorizer>
<div class="grid">
<QuickGrid Items="@FilteredItems" Pagination="@_pagination">
<PropertyColumn Property="@(ce => ce.Id)" Sortable="true" />
<PropertyColumn Property="@(ce => ce.SpawnsetName)" Sortable="true" Align="Align.Left" Class="search-box-name">
<ColumnOptions>
<div class="search-box">
<input type="search" autofocus @bind="_spawnsetNameFilter" @bind:event="oninput" placeholder="Spawnset name..." />
</div>
</ColumnOptions>
</PropertyColumn>
<PropertyColumn Property="@(ce => ce.PlayerName)" Sortable="true" Align="Align.Left">
<ColumnOptions>
<div class="search-box">
<input type="search" autofocus @bind="_playerNameFilter" @bind:event="oninput" placeholder="Player name..." />
</div>
</ColumnOptions>
</PropertyColumn>
<PropertyColumn Property="@(ce => ce.Time)" Sortable="true" Align="Align.Right" Format="@StringFormats.TimeFormat" />
<PropertyColumn Property="@(ce => ce.GemsCollected)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(ce => ce.GemsDespawned)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(ce => ce.GemsEaten)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(ce => ce.GemsTotal)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(ce => ce.EnemiesKilled)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(ce => ce.EnemiesAlive)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(ce => ce.DaggersFired)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(ce => ce.DaggersHit)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(ce => ce.HomingStored)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(ce => ce.HomingEaten)" Sortable="true" Align="Align.Right" />
<PropertyColumn Property="@(ce => ce.DeathType)" Sortable="true" Align="Align.Left" />
<PropertyColumn Property="@(ce => ce.LevelUpTime2)" Sortable="true" Align="Align.Right" Format="@StringFormats.TimeFormat" />
<PropertyColumn Property="@(ce => ce.LevelUpTime3)" Sortable="true" Align="Align.Right" Format="@StringFormats.TimeFormat" />
<PropertyColumn Property="@(ce => ce.LevelUpTime4)" Sortable="true" Align="Align.Right" Format="@StringFormats.TimeFormat" />
<PropertyColumn Property="@(ce => ce.SubmitDate)" Sortable="true" Align="Align.Left" Format="@StringFormats.DateTimeUtcFormat" />
<PropertyColumn Property="@(ce => ce.ClientVersion)" Sortable="true" Align="Align.Right" />
</QuickGrid>
</div>

<Paginator State="@_pagination" />

@code
{
// ! Blazor ref.
private AdminOverview<GetCustomEntryForOverview, CustomEntrySorting> _overview = null!;
private readonly PaginationState _pagination = new() { ItemsPerPage = 25 };

private IQueryable<GetCustomEntryForOverview>? _itemsQueryable;
private string _spawnsetNameFilter = string.Empty;
private string _playerNameFilter = string.Empty;

IQueryable<GetCustomEntryForOverview>? FilteredItems => _itemsQueryable?
.Where(ce =>
ce.SpawnsetName.Contains(_spawnsetNameFilter, StringComparison.CurrentCultureIgnoreCase) &&
ce.PlayerName.Contains(_playerNameFilter, StringComparison.CurrentCultureIgnoreCase));

protected override async Task OnInitializedAsync()
{
List<GetCustomEntryForOverview> customEntries = await Http.GetCustomEntries();
_itemsQueryable = customEntries.AsQueryable();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/* Assign default width to country name column */
::deep thead .search-box-name { width: 15rem; }

/* Stop country name text from wrapping, and truncate with ellipsis */
::deep tbody .search-box-name {
white-space: nowrap;
overflow: hidden;
max-width: 0;
text-overflow: ellipsis;
}

/* Stop the last page from collapsing to the number of rows on it */
::deep tr { height: 1.8rem; }

/* Subtle stripe effect */
::deep tr:nth-child(even) { background: rgba(255,255,255,0.1); }

/* Magnifying glass */
::deep .search-box-name .col-options-button {
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"> <path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" /> </svg>');
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
using DevilDaggersInfo.Web.ApiSpec.Admin;
using DevilDaggersInfo.Web.ApiSpec.Admin.CustomEntries;
using DevilDaggersInfo.Web.Server.Domain.Admin.Converters.DomainToApi;
using DevilDaggersInfo.Web.Server.Domain.Entities;
using DevilDaggersInfo.Web.Server.Domain.Exceptions;
using DevilDaggersInfo.Web.Server.Domain.Extensions;
using Microsoft.EntityFrameworkCore;

namespace DevilDaggersInfo.Web.Server.Domain.Admin.Repositories;
Expand All @@ -17,56 +15,17 @@ public CustomEntryRepository(ApplicationDbContext dbContext)
_dbContext = dbContext;
}

public async Task<Page<GetCustomEntryForOverview>> GetCustomEntriesAsync(string? filter, int pageIndex, int pageSize, CustomEntrySorting? sortBy, bool ascending)
public async Task<List<GetCustomEntryForOverview>> GetCustomEntriesAsync(CancellationToken cancellationToken)
{
// ! Navigation property.
IQueryable<CustomEntryEntity> customEntriesQuery = _dbContext.CustomEntries
List<CustomEntryEntity> customEntries = await _dbContext.CustomEntries
.AsNoTracking()
.Include(ce => ce.Player)
.Include(ce => ce.Player)
.Include(ce => ce.CustomLeaderboard)
.ThenInclude(cl => cl!.Spawnset);
.ThenInclude(cl => cl!.Spawnset)
.ToListAsync(cancellationToken);

if (!string.IsNullOrWhiteSpace(filter))
{
// ! Navigation property.
customEntriesQuery = customEntriesQuery.Where(ce => ce.Player!.PlayerName.Contains(filter) || ce.CustomLeaderboard!.Spawnset!.Name.Contains(filter));
}

// ! Navigation property.
customEntriesQuery = sortBy switch
{
CustomEntrySorting.ClientVersion => customEntriesQuery.OrderBy(ce => ce.ClientVersion, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.DaggersFired => customEntriesQuery.OrderBy(ce => ce.DaggersFired, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.DaggersHit => customEntriesQuery.OrderBy(ce => ce.DaggersHit, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.DeathType => customEntriesQuery.OrderBy(ce => ce.DeathType, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.EnemiesAlive => customEntriesQuery.OrderBy(ce => ce.EnemiesAlive, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.EnemiesKilled => customEntriesQuery.OrderBy(ce => ce.EnemiesKilled, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.GemsCollected => customEntriesQuery.OrderBy(ce => ce.GemsCollected, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.GemsDespawned => customEntriesQuery.OrderBy(ce => ce.GemsDespawned, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.GemsEaten => customEntriesQuery.OrderBy(ce => ce.GemsEaten, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.GemsTotal => customEntriesQuery.OrderBy(ce => ce.GemsTotal, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.HomingStored => customEntriesQuery.OrderBy(ce => ce.HomingStored, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.HomingEaten => customEntriesQuery.OrderBy(ce => ce.HomingEaten, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.LevelUpTime2 => customEntriesQuery.OrderBy(ce => ce.LevelUpTime2, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.LevelUpTime3 => customEntriesQuery.OrderBy(ce => ce.LevelUpTime3, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.LevelUpTime4 => customEntriesQuery.OrderBy(ce => ce.LevelUpTime4, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.PlayerName => customEntriesQuery.OrderBy(ce => ce.Player!.PlayerName, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.SpawnsetName => customEntriesQuery.OrderBy(ce => ce.CustomLeaderboard!.Spawnset!.Name, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.SubmitDate => customEntriesQuery.OrderBy(ce => ce.SubmitDate, ascending).ThenBy(ce => ce.Id),
CustomEntrySorting.Time => customEntriesQuery.OrderBy(ce => ce.Time, ascending).ThenBy(ce => ce.Id),
_ => customEntriesQuery.OrderBy(ce => ce.Id, ascending),
};

List<CustomEntryEntity> customEntries = await customEntriesQuery
.Skip(pageIndex * pageSize)
.Take(pageSize)
.ToListAsync();

return new Page<GetCustomEntryForOverview>
{
Results = customEntries.ConvertAll(ce => ce.ToAdminApiOverview()),
TotalResults = customEntriesQuery.Count(),
};
return customEntries.ConvertAll(ce => ce.ToAdminApiOverview());
}

public async Task<GetCustomEntry> GetCustomEntryAsync(int id)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
using DevilDaggersInfo.Web.ApiSpec.Admin;
using DevilDaggersInfo.Web.ApiSpec.Admin.CustomEntries;
using DevilDaggersInfo.Web.Client;
using DevilDaggersInfo.Web.Core.Claims;
using DevilDaggersInfo.Web.Server.Domain.Admin.Repositories;
using DevilDaggersInfo.Web.Server.Domain.Admin.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.ComponentModel.DataAnnotations;

namespace DevilDaggersInfo.Web.Server.Controllers.Admin;

Expand All @@ -26,13 +23,10 @@ public CustomEntriesController(CustomEntryRepository customEntryRepository, Cust

[HttpGet]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task<ActionResult<Page<GetCustomEntryForOverview>>> GetCustomEntries(
string? filter = null,
[Range(0, 1000)] int pageIndex = 0,
[Range(Constants.PageSizeMin, Constants.PageSizeMax)] int pageSize = Constants.PageSizeDefault,
CustomEntrySorting? sortBy = null,
bool ascending = false)
=> await _customEntryRepository.GetCustomEntriesAsync(filter, pageIndex, pageSize, sortBy, ascending);
public async Task<ActionResult<List<GetCustomEntryForOverview>>> GetCustomEntries(CancellationToken cancellationToken)
{
return await _customEntryRepository.GetCustomEntriesAsync(cancellationToken);
}

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
Expand Down
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

<ItemGroup Label="AspNetCore client">
<PackageVersion Include="Blazored.LocalStorage" Version="4.5.0" />
<PackageVersion Include="Microsoft.AspNetCore.Components.QuickGrid" Version="8.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.Web" Version="8.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.2" />
<PackageVersion Include="Microsoft.AspNetCore.Components.WebAssembly.Authentication" Version="8.0.2" />
Expand Down

0 comments on commit f50efd1

Please sign in to comment.