Updated Blazor UI tag/creator views. Altered logic for sorting favorite/blacklisted fields for tags/creators.
This commit is contained in:
@@ -44,8 +44,8 @@ public class CreatorSearchProvider(AppDbContext context) : SearchProvider<Creato
|
|||||||
Expression<Func<CreatorSearchItem, object?>> selector = field switch
|
Expression<Func<CreatorSearchItem, object?>> selector = field switch
|
||||||
{
|
{
|
||||||
CreatorSortField.VoiceWorkCount => x => x.VoiceWorkCount,
|
CreatorSortField.VoiceWorkCount => x => x.VoiceWorkCount,
|
||||||
CreatorSortField.Favorite => x => !x.Favorite,
|
CreatorSortField.Favorite => x => x.Favorite,
|
||||||
CreatorSortField.Blacklisted => x => !x.Blacklisted,
|
CreatorSortField.Blacklisted => x => x.Blacklisted,
|
||||||
_ => x => x.Name
|
_ => x => x.Name
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class CreatorSearchProviderTests(CreatorSearchProviderFixture fixture) :
|
|||||||
{
|
{
|
||||||
var options = new SearchOptions<CreatorSearchCriteria, CreatorSortField>()
|
var options = new SearchOptions<CreatorSearchCriteria, CreatorSortField>()
|
||||||
{
|
{
|
||||||
SortOptions = [new(CreatorSortField.Favorite, SortDirection.Ascending)]
|
SortOptions = [new(CreatorSortField.Favorite, SortDirection.Descending)]
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await SearchAsync(options);
|
var result = await SearchAsync(options);
|
||||||
@@ -67,7 +67,7 @@ public class CreatorSearchProviderTests(CreatorSearchProviderFixture fixture) :
|
|||||||
{
|
{
|
||||||
var options = new SearchOptions<CreatorSearchCriteria, CreatorSortField>()
|
var options = new SearchOptions<CreatorSearchCriteria, CreatorSortField>()
|
||||||
{
|
{
|
||||||
SortOptions = [new(CreatorSortField.Blacklisted, SortDirection.Ascending)]
|
SortOptions = [new(CreatorSortField.Blacklisted, SortDirection.Descending)]
|
||||||
};
|
};
|
||||||
|
|
||||||
var result = await SearchAsync(options);
|
var result = await SearchAsync(options);
|
||||||
|
|||||||
@@ -84,4 +84,30 @@ public class TagSearchProviderTests(TagSearchProviderFixture fixture) : IClassFi
|
|||||||
result.TotalItems.ShouldBe(1);
|
result.TotalItems.ShouldBe(1);
|
||||||
result.Items.ShouldContain(tagView => tagView.EnglishName == "Heartwarming");
|
result.Items.ShouldContain(tagView => tagView.EnglishName == "Heartwarming");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Filter_None_Sort_Favorite_Descending()
|
||||||
|
{
|
||||||
|
var options = new SearchOptions<TagSearchCriteria, TagSortField>()
|
||||||
|
{
|
||||||
|
SortOptions = [new(TagSortField.Favorite, SortDirection.Descending)]
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await SearchAsync(options);
|
||||||
|
|
||||||
|
result.Items[0].EnglishName.ShouldBe("Heartwarming");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task Filter_None_Sort_Blacklisted_Descending()
|
||||||
|
{
|
||||||
|
var options = new SearchOptions<TagSearchCriteria, TagSortField>()
|
||||||
|
{
|
||||||
|
SortOptions = [new(TagSortField.Blacklisted, SortDirection.Descending)]
|
||||||
|
};
|
||||||
|
|
||||||
|
var result = await SearchAsync(options);
|
||||||
|
|
||||||
|
result.Items[0].EnglishName.ShouldBe("Tsundere");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
@using JSMR.UI.Blazor.Enums
|
@using JSMR.UI.Blazor.Enums
|
||||||
@using JSMR.UI.Blazor.Filters
|
@using JSMR.UI.Blazor.Filters
|
||||||
@using JSMR.UI.Blazor.Services
|
@using JSMR.UI.Blazor.Services
|
||||||
|
@using AntDesign
|
||||||
|
|
||||||
<div class="search-toolbar">
|
<div class="search-toolbar">
|
||||||
<BitMenuButton TItem="BitMenuButtonItem" Text="Presets" Items="presets" Variant="BitVariant.Outline" OnClick="test" />
|
<BitMenuButton TItem="BitMenuButtonItem" Text="Presets" Items="presets" Variant="BitVariant.Outline" OnClick="test" />
|
||||||
@@ -59,9 +60,18 @@
|
|||||||
Value="@Value.SaleStatus"
|
Value="@Value.SaleStatus"
|
||||||
ValueChanged="@(value => Update(Value with { SaleStatus = value }))">
|
ValueChanged="@(value => Update(Value with { SaleStatus = value }))">
|
||||||
<PrefixTemplate>
|
<PrefixTemplate>
|
||||||
<InputPrefix Graphic="Graphic.Cart" Tooltip="Keywords"></InputPrefix>
|
<InputPrefix Graphic="Graphic.Cart" Tooltip="Sale Status"></InputPrefix>
|
||||||
</PrefixTemplate>
|
</PrefixTemplate>
|
||||||
</BitDropdown>
|
</BitDropdown>
|
||||||
|
@* <Select TItem="SelectOption<SaleStatus?>"
|
||||||
|
TItemValue="SaleStatus?"
|
||||||
|
Placeholder="All (Placeholder)"
|
||||||
|
ValueName="Value"
|
||||||
|
LabelName="Label"
|
||||||
|
DataSource="saleStatuses2"
|
||||||
|
Value="@Value.SaleStatus"
|
||||||
|
ValueChanged="@(value => Update(Value with { SaleStatus = value }))">
|
||||||
|
</Select> *@
|
||||||
</div>
|
</div>
|
||||||
<div class="search-filter-control-span-1">
|
<div class="search-filter-control-span-1">
|
||||||
<BitDropdown Prefix="Circles"
|
<BitDropdown Prefix="Circles"
|
||||||
@@ -219,6 +229,7 @@
|
|||||||
|
|
||||||
List<BitDropdownItem<Locale>> locales = [];
|
List<BitDropdownItem<Locale>> locales = [];
|
||||||
List<BitDropdownItem<SaleStatus?>> saleStatuses = [];
|
List<BitDropdownItem<SaleStatus?>> saleStatuses = [];
|
||||||
|
List<SelectOption<SaleStatus?>> saleStatuses2 = [];
|
||||||
List<BitDropdownItem<CircleStatus?>> circleStatuses = [];
|
List<BitDropdownItem<CircleStatus?>> circleStatuses = [];
|
||||||
List<BitDropdownItem<TagStatus?>> tagStatuses = [];
|
List<BitDropdownItem<TagStatus?>> tagStatuses = [];
|
||||||
List<BitDropdownItem<CreatorStatus?>> creatorStatuses = [];
|
List<BitDropdownItem<CreatorStatus?>> creatorStatuses = [];
|
||||||
@@ -242,6 +253,7 @@
|
|||||||
{
|
{
|
||||||
locales = Lookups.GetLocales();
|
locales = Lookups.GetLocales();
|
||||||
saleStatuses = Lookups.GetSaleStatuses();
|
saleStatuses = Lookups.GetSaleStatuses();
|
||||||
|
saleStatuses2 = Lookups.GetSaleStatuses2();
|
||||||
circleStatuses = Lookups.GetCircleStatuses();
|
circleStatuses = Lookups.GetCircleStatuses();
|
||||||
tagStatuses = Lookups.GetTagStatuses();
|
tagStatuses = Lookups.GetTagStatuses();
|
||||||
creatorStatuses = Lookups.GetCreatorStatuses();
|
creatorStatuses = Lookups.GetCreatorStatuses();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AntDesign" Version="1.6.1" />
|
||||||
<PackageReference Include="Bit.BlazorUI" Version="10.4.3" />
|
<PackageReference Include="Bit.BlazorUI" Version="10.4.3" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.6" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="10.0.6" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.6" PrivateAssets="all" />
|
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="10.0.6" PrivateAssets="all" />
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<MudLayout>
|
<MudLayout>
|
||||||
<MudAppBar Elevation="1" Dense="@_dense">
|
<MudAppBar Elevation="1" Dense="@_dense">
|
||||||
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="@ToggleDrawer" />
|
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="MudBlazor.Color.Inherit" Edge="Edge.Start" OnClick="@ToggleDrawer" />
|
||||||
<MudText>JSMR</MudText>
|
<MudText>JSMR</MudText>
|
||||||
<MudSpacer />
|
<MudSpacer />
|
||||||
@* <MudIconButton Icon="@Icons.Custom.Brands.GitHub" Color="Color.Inherit" Href="https://github.com/MudBlazor/MudBlazor" Target="_blank" /> *@
|
@* <MudIconButton Icon="@Icons.Custom.Brands.GitHub" Color="Color.Inherit" Href="https://github.com/MudBlazor/MudBlazor" Target="_blank" /> *@
|
||||||
@@ -52,6 +52,7 @@
|
|||||||
</MudLayout>
|
</MudLayout>
|
||||||
|
|
||||||
<RadzenComponents @rendermode="RenderMode.InteractiveAuto" />
|
<RadzenComponents @rendermode="RenderMode.InteractiveAuto" />
|
||||||
|
<AntContainer />
|
||||||
|
|
||||||
@code{
|
@code{
|
||||||
private bool _open = false;
|
private bool _open = false;
|
||||||
|
|||||||
@@ -39,15 +39,15 @@ else
|
|||||||
|
|
||||||
@if (item.Favorite)
|
@if (item.Favorite)
|
||||||
{
|
{
|
||||||
<MudChip T="string" Label="true" Color="Color.Info" Style="width: 100%" Variant="MudBlazor.Variant.Outlined">Favorite</MudChip>
|
<MudChip T="string" Label="true" Color="MudBlazor.Color.Info" Style="width: 100%" Variant="MudBlazor.Variant.Outlined">Favorite</MudChip>
|
||||||
}
|
}
|
||||||
else if (item.Blacklisted)
|
else if (item.Blacklisted)
|
||||||
{
|
{
|
||||||
<MudChip T="string" Label="true" Color="Color.Warning" Style="width: 100%" Variant="MudBlazor.Variant.Outlined">Blacklisted</MudChip>
|
<MudChip T="string" Label="true" Color="MudBlazor.Color.Warning" Style="width: 100%" Variant="MudBlazor.Variant.Outlined">Blacklisted</MudChip>
|
||||||
}
|
}
|
||||||
else if (item.Spam)
|
else if (item.Spam)
|
||||||
{
|
{
|
||||||
<MudChip T="string" Label="true" Color="Color.Error" Style="width: 100%" Variant="MudBlazor.Variant.Outlined">Spam</MudChip>
|
<MudChip T="string" Label="true" Color="MudBlazor.Color.Error" Style="width: 100%" Variant="MudBlazor.Variant.Outlined">Spam</MudChip>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,37 +13,48 @@
|
|||||||
|
|
||||||
<MudTextField T="string" Value="Keywords" ValueChanged="OnKeywordsChanged" Immediate="true" DebounceInterval="500" Label="Filter" Variant="MudBlazor.Variant.Text" Adornment="@Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.Search" />
|
<MudTextField T="string" Value="Keywords" ValueChanged="OnKeywordsChanged" Immediate="true" DebounceInterval="500" Label="Filter" Variant="MudBlazor.Variant.Text" Adornment="@Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.Search" />
|
||||||
|
|
||||||
@if (searchResults is null)
|
|
||||||
{
|
|
||||||
<p>Loading…</p>
|
|
||||||
}
|
|
||||||
else if (searchResults.Items.Length == 0)
|
|
||||||
{
|
|
||||||
<p>No results.</p>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudTable Items="@searchResults.Items" Style="table-layout: fixed" Virtualize="true">
|
|
||||||
<HeaderContent>
|
|
||||||
<MudTh>Name</MudTh>
|
|
||||||
<MudTh>Voice Works</MudTh>
|
|
||||||
<MudTh>Favorite</MudTh>
|
|
||||||
<MudTh>Blacklisted</MudTh>
|
|
||||||
</HeaderContent>
|
|
||||||
<RowTemplate>
|
|
||||||
<MudTd DataLabel="Name">@context.Name</MudTd>
|
|
||||||
<MudTd DataLabel="Voice Works">@context.VoiceWorkCount</MudTd>
|
|
||||||
<MudTd DataLabel="Favorite">
|
|
||||||
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Favorite" Color="@(context.Favorite? Color.Secondary: Color.Dark)" />
|
|
||||||
</MudTd>
|
|
||||||
<MudTd DataLabel="Blacklisted">
|
|
||||||
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Block" Color="@(context.Blacklisted? Color.Secondary: Color.Dark)" />
|
|
||||||
</MudTd>
|
|
||||||
</RowTemplate>
|
|
||||||
</MudTable>
|
|
||||||
|
|
||||||
<JPagination PageNumber="PageNumber" PageNumberChanged="OnPageNumberChanged" PageSize="PageSize" PageSizeChanged="OnPageSizeChanged" @bind-TotalItems="searchResults.TotalItems" />
|
<AntDesign.Table DataSource="@(searchResults?.Items ?? Enumerable.Empty<CreatorSearchItem>())"
|
||||||
}
|
Total="@(searchResults?.TotalItems ?? 0)"
|
||||||
|
TItem="CreatorSearchItem"
|
||||||
|
Loading="LoadingData"
|
||||||
|
HidePagination="@true"
|
||||||
|
RemoteDataSource="@true"
|
||||||
|
RowKey="x=>x.CreatorId"
|
||||||
|
OnChange="HandleTableChange">
|
||||||
|
<ColumnDefinitions>
|
||||||
|
<AntDesign.PropertyColumn Property="c => c.Name" Title="Name" Sortable="true" SorterMultiple="4" />
|
||||||
|
<AntDesign.PropertyColumn Property="c => c.VoiceWorkCount" Title="Voice Works" Format="n0" HeaderStyle="width: 14em" Sortable="true" SorterMultiple="4" />
|
||||||
|
<AntDesign.PropertyColumn Property="c => c.Favorite" Title="Favorite" HeaderStyle="width: 8em" Sortable="true" SortDirections="new[] { AntDesign.SortDirection.Descending }" SorterMultiple="4">
|
||||||
|
@if (context.Favorite)
|
||||||
|
{
|
||||||
|
<AntDesign.Tag Color="AntDesign.TagColor.PurpleInverse">Favorite</AntDesign.Tag>
|
||||||
|
}
|
||||||
|
</AntDesign.PropertyColumn>
|
||||||
|
<AntDesign.PropertyColumn Property="c => c.Blacklisted" Title="Blacklisted" HeaderStyle="width: 8em" Sortable="true" SortDirections="new[] { AntDesign.SortDirection.Descending }" SorterMultiple="4">
|
||||||
|
@if (context.Blacklisted)
|
||||||
|
{
|
||||||
|
<AntDesign.Tag Color="AntDesign.TagColor.RedInverse">Blacklisted</AntDesign.Tag>
|
||||||
|
}
|
||||||
|
</AntDesign.PropertyColumn>
|
||||||
|
@* <AntDesign.ActionColumn HeaderStyle="width: 2em">
|
||||||
|
<AntDesign.Dropdown Trigger="@(new AntDesign.Trigger[] { AntDesign.Trigger.Click })">
|
||||||
|
<Overlay>
|
||||||
|
<AntDesign.Menu>
|
||||||
|
<AntDesign.MenuItem>
|
||||||
|
<span>Some Menu Item</span>
|
||||||
|
</AntDesign.MenuItem>
|
||||||
|
</AntDesign.Menu>
|
||||||
|
</Overlay>
|
||||||
|
<ChildContent>
|
||||||
|
<AntDesign.Button Shape="AntDesign.ButtonShape.Circle" Icon="@AntDesign.IconType.Outline.More" Type="AntDesign.ButtonType.Default"></AntDesign.Button>
|
||||||
|
</ChildContent>
|
||||||
|
</AntDesign.Dropdown>
|
||||||
|
</AntDesign.ActionColumn> *@
|
||||||
|
</ColumnDefinitions>
|
||||||
|
</AntDesign.Table>
|
||||||
|
|
||||||
|
<JPagination PageNumber="PageNumber" PageNumberChanged="OnPageNumberChanged" PageSize="PageSize" PageSizeChanged="OnPageSizeChanged" TotalItems="TotalItems" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.mud-table-root {
|
.mud-table-root {
|
||||||
@@ -66,12 +77,15 @@ else
|
|||||||
public string? Keywords { get; set; }
|
public string? Keywords { get; set; }
|
||||||
public int PageNumber { get; set; } = 1;
|
public int PageNumber { get; set; } = 1;
|
||||||
public int PageSize { get; set; } = 100;
|
public int PageSize { get; set; } = 100;
|
||||||
|
public int TotalItems => searchResults?.TotalItems ?? 0;
|
||||||
|
|
||||||
|
public bool LoadingData { get; set; }
|
||||||
|
|
||||||
SearchResult<CreatorSearchItem>? searchResults;
|
SearchResult<CreatorSearchItem>? searchResults;
|
||||||
|
|
||||||
protected override async Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
await UpdateDataAsync(true);
|
//await UpdateDataAsync(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task OnKeywordsChanged(string? newKeywords)
|
public async Task OnKeywordsChanged(string? newKeywords)
|
||||||
@@ -102,6 +116,8 @@ else
|
|||||||
|
|
||||||
private async Task LoadCreatorsAsync()
|
private async Task LoadCreatorsAsync()
|
||||||
{
|
{
|
||||||
|
LoadingData = true;
|
||||||
|
|
||||||
SearchCreatorsRequest request = new(
|
SearchCreatorsRequest request = new(
|
||||||
Options: new()
|
Options: new()
|
||||||
{
|
{
|
||||||
@@ -111,10 +127,7 @@ else
|
|||||||
{
|
{
|
||||||
Name = Keywords
|
Name = Keywords
|
||||||
},
|
},
|
||||||
SortOptions =
|
SortOptions = [.. _sortOptions]
|
||||||
[
|
|
||||||
new(CreatorSortField.Name, Application.Common.Search.SortDirection.Ascending)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -123,6 +136,68 @@ else
|
|||||||
|
|
||||||
searchResults = result?.Results ?? new();
|
searchResults = result?.Results ?? new();
|
||||||
|
|
||||||
|
LoadingData = false;
|
||||||
|
|
||||||
//await InvokeAsync(StateHasChanged);
|
//await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private List<SortOption<CreatorSortField>> _sortOptions =
|
||||||
|
[
|
||||||
|
new(CreatorSortField.Name, Application.Common.Search.SortDirection.Ascending)
|
||||||
|
];
|
||||||
|
|
||||||
|
private async Task HandleTableChange(AntDesign.TableModels.QueryModel<CreatorSearchItem> queryModel)
|
||||||
|
{
|
||||||
|
//PageNumber = queryModel.PageIndex;
|
||||||
|
//PageSize = queryModel.PageSize;
|
||||||
|
|
||||||
|
_sortOptions = MapSortOptions(queryModel);
|
||||||
|
|
||||||
|
await LoadCreatorsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SortOption<CreatorSortField>> MapSortOptions(AntDesign.TableModels.QueryModel<CreatorSearchItem> queryModel)
|
||||||
|
{
|
||||||
|
var requestedSorts = queryModel.SortModel
|
||||||
|
.Select(sort => new
|
||||||
|
{
|
||||||
|
Field = sort.FieldName switch
|
||||||
|
{
|
||||||
|
nameof(CreatorSearchItem.Favorite) => CreatorSortField.Favorite,
|
||||||
|
nameof(CreatorSearchItem.Blacklisted) => CreatorSortField.Blacklisted,
|
||||||
|
nameof(CreatorSearchItem.VoiceWorkCount) => CreatorSortField.VoiceWorkCount,
|
||||||
|
nameof(CreatorSearchItem.Name) => CreatorSortField.Name,
|
||||||
|
_ => (CreatorSortField?)null
|
||||||
|
},
|
||||||
|
Direction = sort.SortDirection switch
|
||||||
|
{
|
||||||
|
AntDesign.SortDirection.Ascending => Application.Common.Search.SortDirection.Ascending,
|
||||||
|
AntDesign.SortDirection.Descending => Application.Common.Search.SortDirection.Descending,
|
||||||
|
_ => (Application.Common.Search.SortDirection?)null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Where(x => x.Field is not null && x.Direction is not null)
|
||||||
|
.ToDictionary(x => x.Field!.Value, x => x.Direction!.Value);
|
||||||
|
|
||||||
|
var finalSorts = new List<SortOption<CreatorSortField>>();
|
||||||
|
|
||||||
|
// Force your preferred precedence
|
||||||
|
if (requestedSorts.TryGetValue(CreatorSortField.Favorite, out var favoriteDir))
|
||||||
|
finalSorts.Add(new(CreatorSortField.Favorite, favoriteDir));
|
||||||
|
|
||||||
|
if (requestedSorts.TryGetValue(CreatorSortField.Blacklisted, out var blacklistedDir))
|
||||||
|
finalSorts.Add(new(CreatorSortField.Blacklisted, blacklistedDir));
|
||||||
|
|
||||||
|
if (requestedSorts.TryGetValue(CreatorSortField.VoiceWorkCount, out var countDir))
|
||||||
|
finalSorts.Add(new(CreatorSortField.VoiceWorkCount, countDir));
|
||||||
|
|
||||||
|
if (requestedSorts.TryGetValue(CreatorSortField.Name, out var nameDir))
|
||||||
|
finalSorts.Add(new(CreatorSortField.Name, nameDir));
|
||||||
|
|
||||||
|
// Default fallback
|
||||||
|
if (finalSorts.Count == 0)
|
||||||
|
finalSorts.Add(new(CreatorSortField.Name, Application.Common.Search.SortDirection.Ascending));
|
||||||
|
|
||||||
|
return finalSorts;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,44 +11,54 @@
|
|||||||
|
|
||||||
<h1>Tags</h1>
|
<h1>Tags</h1>
|
||||||
|
|
||||||
<MudTextField @bind-Value="Keywords" Immediate="true" DebounceInterval="500" Label="Filter" Variant="MudBlazor.Variant.Text" Adornment="@Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.Search" />
|
<MudTextField T="string" Value="Keywords" ValueChanged="OnKeywordsChanged" Immediate="true" DebounceInterval="500" Label="Filter" Variant="MudBlazor.Variant.Text" Adornment="@Adornment.Start" AdornmentIcon="@Icons.Material.Outlined.Search" />
|
||||||
|
|
||||||
@if (searchResults is null)
|
@* <AntDesign.Input @bind-Value="Keywords" DebounceMilliseconds="500" Placeholder="Filter">
|
||||||
{
|
<Prefix>
|
||||||
<p>Loading…</p>
|
<AntDesign.Icon Type="@AntDesign.IconType.Outline.Search" />
|
||||||
}
|
</Prefix>
|
||||||
else if (searchResults.Items.Length == 0)
|
</AntDesign.Input> *@
|
||||||
{
|
|
||||||
<p>No results.</p>
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<MudDataGrid Items="@searchResults.Items" Style="table-layout: fixed">
|
|
||||||
<Columns>
|
|
||||||
<PropertyColumn Property="x => x.Name" Title="Name" />
|
|
||||||
<PropertyColumn Property="x => x.EnglishName" Title="English Name" />
|
|
||||||
<PropertyColumn Property="x => x.VoiceWorkCount" Title="Voice Works" HeaderStyle="width: 14em" />
|
|
||||||
<TemplateColumn Title="Favorite" HeaderStyle="width: 8em">
|
|
||||||
<CellTemplate>
|
|
||||||
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Favorite" Color="@(context.Item.Favorite ? Color.Secondary : Color.Dark)" @onclick="@(e => IncrementCount(context.Item))" />
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
|
||||||
<TemplateColumn Title="Blacklisted" HeaderStyle="width: 8em">
|
|
||||||
<CellTemplate>
|
|
||||||
<MudIconButton Size="@Size.Small" Icon="@Icons.Material.Filled.Block" Color="@(context.Item.Blacklisted? Color.Secondary: Color.Dark)" />
|
|
||||||
</CellTemplate>
|
|
||||||
</TemplateColumn>
|
|
||||||
</Columns>
|
|
||||||
</MudDataGrid>
|
|
||||||
|
|
||||||
<JPagination @bind-PageNumber="PageNumber" @bind-PageSize="PageSize" @bind-TotalItems="searchResults.TotalItems" />
|
<AntDesign.Table
|
||||||
}
|
Responsive
|
||||||
|
DataSource="@(searchResults?.Items ?? Enumerable.Empty<TagSearchItem>())"
|
||||||
|
Total="@(searchResults?.TotalItems ?? 0)"
|
||||||
|
TItem="TagSearchItem"
|
||||||
|
Loading="LoadingData"
|
||||||
|
HidePagination="@true"
|
||||||
|
RemoteDataSource="@true"
|
||||||
|
RowKey="x=>x.TagId"
|
||||||
|
OnChange="HandleTableChange">
|
||||||
|
<ColumnDefinitions>
|
||||||
|
<AntDesign.PropertyColumn Property="c => c.Name" Title="Name" Sortable SorterMultiple="5" />
|
||||||
|
<AntDesign.PropertyColumn Property="c => c.EnglishName" Title="English Name" Sortable SorterMultiple="5" />
|
||||||
|
<AntDesign.PropertyColumn Property="c => c.VoiceWorkCount" Title="Voice Works" Format="n0" HeaderStyle="width: 14em" Sortable SorterMultiple="5" />
|
||||||
|
<AntDesign.PropertyColumn Property="c => c.Favorite" Title="Favorite" HeaderStyle="width: 8em" Sortable SortDirections="new[] { AntDesign.SortDirection.Descending }" SorterMultiple="5">
|
||||||
|
@if (context.Favorite)
|
||||||
|
{
|
||||||
|
<AntDesign.Tag Color="AntDesign.TagColor.PurpleInverse">Favorite</AntDesign.Tag>
|
||||||
|
}
|
||||||
|
</AntDesign.PropertyColumn>
|
||||||
|
<AntDesign.PropertyColumn Property="c => c.Blacklisted" Title="Blacklisted" HeaderStyle="width: 9em" Sortable SortDirections="new[] { AntDesign.SortDirection.Descending }" SorterMultiple="5">
|
||||||
|
@if (context.Blacklisted)
|
||||||
|
{
|
||||||
|
<AntDesign.Tag Color="AntDesign.TagColor.RedInverse">Blacklisted</AntDesign.Tag>
|
||||||
|
}
|
||||||
|
</AntDesign.PropertyColumn>
|
||||||
|
</ColumnDefinitions>
|
||||||
|
</AntDesign.Table>
|
||||||
|
|
||||||
|
<JPagination PageNumber="PageNumber" PageNumberChanged="OnPageNumberChanged" PageSize="PageSize" PageSizeChanged="OnPageSizeChanged" TotalItems="TotalItems" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.mud-table-root {
|
.mud-table-root {
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-table table {
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
.j-pager {
|
.j-pager {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
@@ -62,74 +72,60 @@ else
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
@code {
|
@code {
|
||||||
private string? keywords;
|
public string? Keywords { get; set; }
|
||||||
|
public int PageNumber { get; set; } = 1;
|
||||||
|
public int PageSize { get; set; } = 100;
|
||||||
|
public int TotalItems => searchResults?.TotalItems ?? 0;
|
||||||
|
|
||||||
public string? Keywords
|
public bool LoadingData { get; set; }
|
||||||
{
|
|
||||||
get { return keywords; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
keywords = value;
|
|
||||||
_ = UpdateDataAsync(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private int pageNumber = 1;
|
|
||||||
|
|
||||||
public int PageNumber
|
|
||||||
{
|
|
||||||
get { return pageNumber; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
pageNumber = value;
|
|
||||||
_ = UpdateDataAsync(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int pageSize = 100;
|
|
||||||
|
|
||||||
public int PageSize
|
|
||||||
{
|
|
||||||
get { return pageSize; }
|
|
||||||
set
|
|
||||||
{
|
|
||||||
pageSize = value;
|
|
||||||
_ = UpdateDataAsync(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SearchResult<TagSearchItem>? searchResults;
|
SearchResult<TagSearchItem>? searchResults;
|
||||||
|
|
||||||
protected override Task OnInitializedAsync()
|
protected override async Task OnInitializedAsync()
|
||||||
{
|
{
|
||||||
_ = LoadTagsAsync();
|
//await UpdateDataAsync(true);
|
||||||
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
public async Task OnKeywordsChanged(string? newKeywords)
|
||||||
|
{
|
||||||
|
Keywords = newKeywords;
|
||||||
|
await UpdateDataAsync(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnPageNumberChanged(int newPageNumber)
|
||||||
|
{
|
||||||
|
PageNumber = newPageNumber;
|
||||||
|
await UpdateDataAsync(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task OnPageSizeChanged(int newPageSize)
|
||||||
|
{
|
||||||
|
PageSize = newPageSize;
|
||||||
|
await UpdateDataAsync(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task UpdateDataAsync(bool resetPageNumber)
|
private async Task UpdateDataAsync(bool resetPageNumber)
|
||||||
{
|
{
|
||||||
if (resetPageNumber)
|
if (resetPageNumber)
|
||||||
pageNumber = 1;
|
PageNumber = 1;
|
||||||
|
|
||||||
await LoadTagsAsync();
|
await LoadTagsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadTagsAsync()
|
private async Task LoadTagsAsync()
|
||||||
{
|
{
|
||||||
|
LoadingData = true;
|
||||||
|
|
||||||
SearchTagsRequest request = new(
|
SearchTagsRequest request = new(
|
||||||
Options: new()
|
Options: new()
|
||||||
{
|
{
|
||||||
PageNumber = PageNumber,
|
PageNumber = PageNumber,
|
||||||
PageSize = pageSize,
|
PageSize = PageSize,
|
||||||
Criteria = new()
|
Criteria = new()
|
||||||
{
|
{
|
||||||
Name = Keywords
|
Name = Keywords
|
||||||
},
|
},
|
||||||
SortOptions =
|
SortOptions = [.. _sortOptions]
|
||||||
[
|
|
||||||
new(TagSortField.Name, Application.Common.Search.SortDirection.Ascending)
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -138,11 +134,72 @@ else
|
|||||||
|
|
||||||
searchResults = result?.Results ?? new();
|
searchResults = result?.Results ?? new();
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
LoadingData = false;
|
||||||
|
|
||||||
|
//await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void IncrementCount(TagSearchItem item)
|
private List<SortOption<TagSortField>> _sortOptions =
|
||||||
|
[
|
||||||
|
new(TagSortField.Name, Application.Common.Search.SortDirection.Ascending)
|
||||||
|
];
|
||||||
|
|
||||||
|
private async Task HandleTableChange(AntDesign.TableModels.QueryModel<TagSearchItem> queryModel)
|
||||||
{
|
{
|
||||||
//item.Favorite = !item.Favorite;
|
//PageNumber = queryModel.PageIndex;
|
||||||
|
//PageSize = queryModel.PageSize;
|
||||||
|
|
||||||
|
_sortOptions = MapSortOptions(queryModel);
|
||||||
|
|
||||||
|
await LoadTagsAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<SortOption<TagSortField>> MapSortOptions(AntDesign.TableModels.QueryModel<TagSearchItem> queryModel)
|
||||||
|
{
|
||||||
|
var requestedSorts = queryModel.SortModel
|
||||||
|
.Select(sort => new
|
||||||
|
{
|
||||||
|
Field = sort.FieldName switch
|
||||||
|
{
|
||||||
|
nameof(TagSearchItem.Favorite) => TagSortField.Favorite,
|
||||||
|
nameof(TagSearchItem.Blacklisted) => TagSortField.Blacklisted,
|
||||||
|
nameof(TagSearchItem.VoiceWorkCount) => TagSortField.VoiceWorkCount,
|
||||||
|
nameof(TagSearchItem.EnglishName) => TagSortField.EnglishName,
|
||||||
|
nameof(TagSearchItem.Name) => TagSortField.Name,
|
||||||
|
_ => (TagSortField?)null
|
||||||
|
},
|
||||||
|
Direction = sort.SortDirection switch
|
||||||
|
{
|
||||||
|
AntDesign.SortDirection.Ascending => Application.Common.Search.SortDirection.Ascending,
|
||||||
|
AntDesign.SortDirection.Descending => Application.Common.Search.SortDirection.Descending,
|
||||||
|
_ => (Application.Common.Search.SortDirection?)null
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Where(x => x.Field is not null && x.Direction is not null)
|
||||||
|
.ToDictionary(x => x.Field!.Value, x => x.Direction!.Value);
|
||||||
|
|
||||||
|
var finalSorts = new List<SortOption<TagSortField>>();
|
||||||
|
|
||||||
|
// Force your preferred precedence
|
||||||
|
if (requestedSorts.TryGetValue(TagSortField.Favorite, out var favoriteDir))
|
||||||
|
finalSorts.Add(new(TagSortField.Favorite, favoriteDir));
|
||||||
|
|
||||||
|
if (requestedSorts.TryGetValue(TagSortField.Blacklisted, out var blacklistedDir))
|
||||||
|
finalSorts.Add(new(TagSortField.Blacklisted, blacklistedDir));
|
||||||
|
|
||||||
|
if (requestedSorts.TryGetValue(TagSortField.VoiceWorkCount, out var countDir))
|
||||||
|
finalSorts.Add(new(TagSortField.VoiceWorkCount, countDir));
|
||||||
|
|
||||||
|
if (requestedSorts.TryGetValue(TagSortField.EnglishName, out var englishNameDir))
|
||||||
|
finalSorts.Add(new(TagSortField.EnglishName, englishNameDir));
|
||||||
|
|
||||||
|
if (requestedSorts.TryGetValue(TagSortField.Name, out var nameDir))
|
||||||
|
finalSorts.Add(new(TagSortField.Name, nameDir));
|
||||||
|
|
||||||
|
// Default fallback
|
||||||
|
if (finalSorts.Count == 0)
|
||||||
|
finalSorts.Add(new(TagSortField.Name, Application.Common.Search.SortDirection.Ascending));
|
||||||
|
|
||||||
|
return finalSorts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,6 +47,7 @@ builder.Services.AddScoped<SessionState>();
|
|||||||
builder.Services.AddMudServices();
|
builder.Services.AddMudServices();
|
||||||
builder.Services.AddRadzenComponents();
|
builder.Services.AddRadzenComponents();
|
||||||
builder.Services.AddBitBlazorUIServices();
|
builder.Services.AddBitBlazorUIServices();
|
||||||
|
builder.Services.AddAntDesign();
|
||||||
|
|
||||||
builder.Services.AddScoped<VoiceWorksClient>();
|
builder.Services.AddScoped<VoiceWorksClient>();
|
||||||
builder.Services.AddScoped<ILookupDataService, LookupDataService>();
|
builder.Services.AddScoped<ILookupDataService, LookupDataService>();
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public interface ILookupDataService
|
|||||||
{
|
{
|
||||||
List<BitDropdownItem<Locale>> GetLocales();
|
List<BitDropdownItem<Locale>> GetLocales();
|
||||||
List<BitDropdownItem<SaleStatus?>> GetSaleStatuses();
|
List<BitDropdownItem<SaleStatus?>> GetSaleStatuses();
|
||||||
|
List<SelectOption<SaleStatus?>> GetSaleStatuses2();
|
||||||
List<BitDropdownItem<CircleStatus?>> GetCircleStatuses();
|
List<BitDropdownItem<CircleStatus?>> GetCircleStatuses();
|
||||||
List<BitDropdownItem<TagStatus?>> GetTagStatuses();
|
List<BitDropdownItem<TagStatus?>> GetTagStatuses();
|
||||||
List<BitDropdownItem<CreatorStatus?>> GetCreatorStatuses();
|
List<BitDropdownItem<CreatorStatus?>> GetCreatorStatuses();
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ public sealed class LookupDataService(VoiceWorksClient client) : ILookupDataServ
|
|||||||
new() { Text = "All", Value = null }
|
new() { Text = "All", Value = null }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
public List<SelectOption<SaleStatus?>> GetSaleStatuses2() =>
|
||||||
|
[
|
||||||
|
new() { Label = "Available", Value = SaleStatus.Available },
|
||||||
|
new() { Label = "Upcoming", Value = SaleStatus.Upcoming },
|
||||||
|
new() { Label = "All", Value = null }
|
||||||
|
];
|
||||||
|
|
||||||
public List<BitDropdownItem<CircleStatus?>> GetCircleStatuses() =>
|
public List<BitDropdownItem<CircleStatus?>> GetCircleStatuses() =>
|
||||||
[
|
[
|
||||||
new() { Text = "Not Blacklisted", Value = CircleStatus.NotBlacklisted },
|
new() { Text = "Not Blacklisted", Value = CircleStatus.NotBlacklisted },
|
||||||
@@ -132,3 +139,9 @@ public sealed class LookupDataService(VoiceWorksClient client) : ILookupDataServ
|
|||||||
return _creators;
|
return _creators;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class SelectOption<T>
|
||||||
|
{
|
||||||
|
public string? Label { get; set; }
|
||||||
|
public T? Value { get; set; }
|
||||||
|
}
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
<link rel="stylesheet" href="css/mud-blazor.css" />
|
<link rel="stylesheet" href="css/mud-blazor.css" />
|
||||||
<link rel="stylesheet" href="css/radzen.css" />
|
<link rel="stylesheet" href="css/radzen.css" />
|
||||||
<link href="_content/Bit.BlazorUI/styles/bit.blazorui.css" rel="stylesheet" />
|
<link href="_content/Bit.BlazorUI/styles/bit.blazorui.css" rel="stylesheet" />
|
||||||
|
<link href="_content/AntDesign/css/ant-design-blazor.dark.css" rel="stylesheet" />
|
||||||
<link rel="stylesheet" href="css/bit-blazor.css" />
|
<link rel="stylesheet" href="css/bit-blazor.css" />
|
||||||
<link rel="stylesheet" href="css/font.css" />
|
<link rel="stylesheet" href="css/font.css" />
|
||||||
<link rel="stylesheet" href="css/app.css" />
|
<link rel="stylesheet" href="css/app.css" />
|
||||||
|
|||||||
Reference in New Issue
Block a user