Merge pull request 'Add searchbar to cosplayer detail page for filtering albums' (#100) from feat/cosplayer-detail-search into develop
Reviewed-on: #100 Reviewed-by: Samuele Lorefice <aironenerowork@gmail.com>
This commit was merged in pull request #100.
This commit is contained in:
@@ -27,10 +27,10 @@ public class PersonController(
|
||||
/// Gets a person by ID with their associated albums, filtered by access level and paginated.
|
||||
/// </summary>
|
||||
/// <param name="id">The person ID.</param>
|
||||
/// <param name="albumPaging">Pagination parameters for albums.</param>
|
||||
/// <param name="albumPaging">Pagination, search, and sort parameters for albums.</param>
|
||||
/// <returns>The person details with albums, or 404 if not found.</returns>
|
||||
[HttpGet("{id}")]
|
||||
public ActionResult<PersonDetailedDto> Get([FromRoute] Guid id, [FromQuery] PagedParametersDto albumPaging) {
|
||||
public ActionResult<PersonDetailedDto> Get([FromRoute] Guid id, [FromQuery] PagedSearchParametersDto albumPaging) {
|
||||
var uid = authService.GetUserData(User)?.Id;
|
||||
var accessLevel = authService.GetUserData(User)?.AccessLevel ?? EAccessLevel.User;
|
||||
|
||||
@@ -56,6 +56,33 @@ public class PersonController(
|
||||
}
|
||||
|
||||
if (person.Albums != null) {
|
||||
if (!string.IsNullOrEmpty(albumPaging.Search))
|
||||
{
|
||||
person.Albums = person.Albums
|
||||
.Where(a => a.Title.Contains(albumPaging.Search, StringComparison.OrdinalIgnoreCase))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(albumPaging.SortBy))
|
||||
{
|
||||
person.Albums = albumPaging.SortBy.ToLowerInvariant() switch
|
||||
{
|
||||
"name" => albumPaging.SortAsc
|
||||
? [..person.Albums.OrderBy(a => a.Title)]
|
||||
: [..person.Albums.OrderByDescending(a => a.Title)],
|
||||
"created" => albumPaging.SortAsc
|
||||
? [..person.Albums.OrderBy(a => a.CreatedAt)]
|
||||
: [..person.Albums.OrderByDescending(a => a.CreatedAt)],
|
||||
"updated" => albumPaging.SortAsc
|
||||
? [..person.Albums.OrderBy(a => a.UpdatedAt)]
|
||||
: [..person.Albums.OrderByDescending(a => a.UpdatedAt)],
|
||||
"assets" => albumPaging.SortAsc
|
||||
? [..person.Albums.OrderBy(a => a.Assets?.Count(asset => asset.DeletedAt == null) ?? 0)]
|
||||
: [..person.Albums.OrderByDescending(a => a.Assets?.Count(asset => asset.DeletedAt == null) ?? 0)],
|
||||
_ => person.Albums
|
||||
};
|
||||
}
|
||||
|
||||
person.Albums = person.Albums
|
||||
.Skip(albumPaging.Page * albumPaging.PageSize)
|
||||
.Take(albumPaging.PageSize)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@using Microsoft.Extensions.Options
|
||||
@using Microsoft.JSInterop
|
||||
@using MilkStream.Client.Components.Layout
|
||||
@using MilkStream.Client.Components.Shared
|
||||
@using MilkStream.Client.Services
|
||||
@using System.Globalization
|
||||
@implements IAsyncDisposable
|
||||
@@ -69,8 +70,18 @@
|
||||
</div>
|
||||
|
||||
@* Albums header *@
|
||||
<div class="d-flex align-items-center justify-content-between mt-4 mb-3 px-2">
|
||||
<div class="d-flex align-items-center mt-4 mb-2 px-2">
|
||||
<h4 class="m-0"><i class="bi bi-collection"></i> Albums</h4>
|
||||
</div>
|
||||
|
||||
<SortFilterBar SearchQuery="@albumSearchQuery"
|
||||
SearchQueryChanged="@((string? v) => { albumSearchQuery = v; _ = ReloadAlbums(); })"
|
||||
SortBy="@albumSortBy"
|
||||
SortByChanged="@((string? v) => { albumSortBy = v; _ = ReloadAlbums(); })"
|
||||
SortAsc="@albumSortAsc"
|
||||
SortAscChanged="@((bool v) => { albumSortAsc = v; _ = ReloadAlbums(); })"
|
||||
SortOptions="@albumSortOptions">
|
||||
<ActionButtons>
|
||||
<div class="d-flex gap-2 align-items-start">
|
||||
@if (allAlbums.Count > 0) {
|
||||
<div class="btn-group btn-group-sm">
|
||||
@@ -107,7 +118,8 @@
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</ActionButtons>
|
||||
</SortFilterBar>
|
||||
|
||||
@if (allAlbums.Count > 0) {
|
||||
<div class="@("album-grid " + viewMode)">
|
||||
@@ -192,6 +204,19 @@
|
||||
HashSet<Guid> selectedAlbumIds = [];
|
||||
List<string> bannerCoverUrls = [];
|
||||
|
||||
// Search & sort
|
||||
string? albumSearchQuery;
|
||||
string? albumSortBy;
|
||||
bool albumSortAsc = true;
|
||||
|
||||
Dictionary<string, string> albumSortOptions = new()
|
||||
{
|
||||
["Name"] = "name",
|
||||
["Created"] = "created",
|
||||
["Updated"] = "updated",
|
||||
["Asset count"] = "assets"
|
||||
};
|
||||
|
||||
// Infinite scroll
|
||||
List<AlbumPreviewDto> allAlbums = [];
|
||||
int currentAlbumPage;
|
||||
@@ -236,7 +261,7 @@
|
||||
currentAlbumPage = 0;
|
||||
hasMoreAlbums = true;
|
||||
|
||||
person = await personService.GetByIdAsync(Id);
|
||||
person = await personService.GetByIdAsync(Id, 0, AlbumPageSize);
|
||||
if (person?.Albums != null) {
|
||||
allAlbums = person.Albums;
|
||||
hasMoreAlbums = person.Albums.Count >= AlbumPageSize;
|
||||
@@ -253,13 +278,28 @@
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
async Task ReloadAlbums() {
|
||||
currentAlbumPage = 0;
|
||||
hasMoreAlbums = true;
|
||||
|
||||
person = await personService.GetByIdAsync(Id, 0, AlbumPageSize, albumSearchQuery, albumSortBy, albumSortAsc);
|
||||
allAlbums.Clear();
|
||||
if (person?.Albums != null) {
|
||||
allAlbums = person.Albums;
|
||||
hasMoreAlbums = person.Albums.Count >= AlbumPageSize;
|
||||
currentAlbumPage = 1;
|
||||
}
|
||||
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task LoadMoreAlbums() {
|
||||
if (isLoadingMore || !hasMoreAlbums) return;
|
||||
isLoadingMore = true;
|
||||
StateHasChanged();
|
||||
|
||||
var fresh = await personService.GetByIdAsync(Id, currentAlbumPage, AlbumPageSize);
|
||||
var fresh = await personService.GetByIdAsync(Id, currentAlbumPage, AlbumPageSize, albumSearchQuery, albumSortBy, albumSortAsc);
|
||||
if (fresh?.Albums?.Count > 0) {
|
||||
allAlbums.AddRange(fresh.Albums);
|
||||
currentAlbumPage++;
|
||||
|
||||
@@ -43,11 +43,30 @@ public sealed class PersonService(
|
||||
/// <param name="id">The person ID.</param>
|
||||
/// <param name="albumPage">Optional zero-based page number for albums.</param>
|
||||
/// <param name="albumPageSize">Optional number of albums per page.</param>
|
||||
/// <param name="search">Optional search term to filter album titles.</param>
|
||||
/// <param name="sortBy">Optional field to sort albums by (e.g. "name", "created", "updated", "assets").</param>
|
||||
/// <param name="sortAsc">Whether to sort ascending. Default is <c>true</c>.</param>
|
||||
/// <returns>The person details, or null if not found.</returns>
|
||||
public async Task<PersonDetailedDto?> GetByIdAsync(Guid id, int? albumPage = null, int? albumPageSize = null) {
|
||||
public async Task<PersonDetailedDto?> GetByIdAsync(Guid id, int? albumPage = null, int? albumPageSize = null, string? search = null, string? sortBy = null, bool sortAsc = true) {
|
||||
var url = $"/api/person/{id}";
|
||||
if (albumPage.HasValue && albumPageSize.HasValue)
|
||||
var hasParams = false;
|
||||
|
||||
if (albumPage.HasValue && albumPageSize.HasValue) {
|
||||
url += $"?page={albumPage}&pageSize={albumPageSize}";
|
||||
hasParams = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(search)) {
|
||||
url += $"{(hasParams ? "&" : "?")}search={Uri.EscapeDataString(search)}";
|
||||
hasParams = true;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(sortBy)) {
|
||||
url += $"{(hasParams ? "&" : "?")}sortBy={Uri.EscapeDataString(sortBy)}";
|
||||
hasParams = true;
|
||||
}
|
||||
|
||||
url += $"{(hasParams ? "&" : "?")}sortAsc={sortAsc.ToString().ToLowerInvariant()}";
|
||||
|
||||
var response = await Client.GetAsync(url);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user