Add credentials alphabetical sort option to web app (#1207)

This commit is contained in:
Leendert de Borst
2025-09-14 16:35:20 +02:00
committed by Leendert de Borst
parent 56b6753320
commit 9a97a904fb
5 changed files with 142 additions and 27 deletions

View File

@@ -1,8 +1,9 @@
@using AliasVault.RazorComponents.Tables
@using AliasVault.Client.Main.Models
@inject NavigationManager NavigationManager
<SortableTable Columns="@_tableColumns" SortColumn="@SortColumn" SortDirection="@SortDirection" OnSortChanged="HandleSortChanged">
@foreach (var credential in SortedCredentials)
<SortableTable Columns="@_tableColumns" SortColumn="@SortColumn" SortDirection="@CurrentSortDirection" OnSortChanged="HandleSortChanged">
@foreach (var credential in DisplayedCredentials)
{
<SortableTableRow Class="cursor-pointer" OnClick="@(() => NavigateToCredential(credential.Id))">
<SortableTableColumn Padding="false">
@@ -26,16 +27,26 @@
public List<CredentialListEntry> Credentials { get; set; } = [];
/// <summary>
/// Gets or sets the default sort direction that is applied to the provided credentials list.
/// Gets or sets the sort order for the credentials (used to determine initial table state).
/// </summary>
[Parameter]
public SortDirection SortDirection { get; set; } = SortDirection.Ascending;
public CredentialSortOrder SortOrder { get; set; } = CredentialSortOrder.OldestFirst;
/// <summary>
/// Gets or sets the column to sort by.
/// </summary>
private string SortColumn { get; set; } = "CreatedAt";
/// <summary>
/// Gets or sets the current sort direction for table column sorting.
/// </summary>
private SortDirection CurrentSortDirection { get; set; } = SortDirection.Ascending;
/// <summary>
/// Gets or sets whether the user has explicitly clicked a column to sort.
/// </summary>
private bool UserHasClickedColumn { get; set; } = false;
/// <summary>
/// Gets or sets the columns to show in the table.
/// </summary>
@@ -47,9 +58,64 @@
];
/// <summary>
/// Gets or sets the sorted credentials.
/// Gets the credentials to display, with optional column-based sorting applied.
/// </summary>
private IEnumerable<CredentialListEntry> SortedCredentials => SortList(Credentials, SortColumn, SortDirection);
private IEnumerable<CredentialListEntry> DisplayedCredentials
{
get
{
// If user has explicitly clicked a column header to sort, apply that sorting
if (UserHasClickedColumn)
{
return SortList(Credentials, SortColumn, CurrentSortDirection);
}
// Otherwise use the pre-sorted credentials passed from parent
return Credentials;
}
}
/// <summary>
/// Gets the default sort column based on the sort order.
/// </summary>
private string GetDefaultSortColumn()
{
return SortOrder switch
{
CredentialSortOrder.Alphabetical => "Service",
_ => "CreatedAt", // OldestFirst and NewestFirst
};
}
/// <summary>
/// Gets the default sort direction based on the sort order.
/// </summary>
private SortDirection GetDefaultSortDirection()
{
return SortOrder switch
{
CredentialSortOrder.NewestFirst => SortDirection.Descending,
CredentialSortOrder.Alphabetical => SortDirection.Ascending,
_ => SortDirection.Ascending, // OldestFirst (default)
};
}
/// <inheritdoc />
protected override void OnParametersSet()
{
base.OnParametersSet();
// Only reset if the SortOrder parameter has changed
var newDefaultColumn = GetDefaultSortColumn();
var newDefaultDirection = GetDefaultSortDirection();
if (SortColumn != newDefaultColumn || (!UserHasClickedColumn && CurrentSortDirection != newDefaultDirection))
{
SortColumn = newDefaultColumn;
CurrentSortDirection = newDefaultDirection;
UserHasClickedColumn = false; // Reset user interaction state when parameters change
}
}
/// <summary>
/// Handles the sort changed event.
@@ -58,7 +124,8 @@
private void HandleSortChanged((string column, SortDirection direction) sort)
{
SortColumn = sort.column;
SortDirection = sort.direction;
CurrentSortDirection = sort.direction;
UserHasClickedColumn = true;
StateHasChanged();
}

View File

@@ -0,0 +1,29 @@
//-----------------------------------------------------------------------
// <copyright file="CredentialSortOrder.cs" company="aliasvault">
// Copyright (c) aliasvault. All rights reserved.
// Licensed under the AGPLv3 license. See LICENSE.md file in the project root for full license information.
// </copyright>
//-----------------------------------------------------------------------
namespace AliasVault.Client.Main.Models;
/// <summary>
/// Defines the sort order options for credentials.
/// </summary>
public enum CredentialSortOrder
{
/// <summary>
/// Sort by creation date, oldest first.
/// </summary>
OldestFirst,
/// <summary>
/// Sort by creation date, newest first.
/// </summary>
NewestFirst,
/// <summary>
/// Sort alphabetically by service name.
/// </summary>
Alphabetical,
}

View File

@@ -2,6 +2,7 @@
@inherits MainBase
@inject CredentialService CredentialService
@using AliasVault.RazorComponents.Tables
@using AliasVault.Client.Main.Models
@using Microsoft.Extensions.Localization
<LayoutPageTitle>Home</LayoutPageTitle>
@@ -33,8 +34,9 @@
<div class="mb-4">
<label class="block mb-2 text-sm font-medium text-gray-900 dark:text-white">@Localizer["SortOrderLabel"]</label>
<select @bind="SortOrder" @bind:after="CloseSettingsPopup" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500">
<option value="asc">@Localizer["OldestFirstOption"]</option>
<option value="desc">@Localizer["NewestFirstOption"]</option>
<option value="@CredentialSortOrder.OldestFirst">@Localizer["OldestFirstOption"]</option>
<option value="@CredentialSortOrder.NewestFirst">@Localizer["NewestFirstOption"]</option>
<option value="@CredentialSortOrder.Alphabetical">@Localizer["AlphabeticalOption"]</option>
</select>
</div>
</div>
@@ -55,7 +57,7 @@ else
@if (DbService.Settings.CredentialsViewMode == "table")
{
<div class="px-4 min-h-[250px]">
<CredentialsTable Credentials="Credentials" SortDirection="@(SortOrder == "desc" ? SortDirection.Descending : SortDirection.Ascending)" />
<CredentialsTable Credentials="@SortedCredentials.ToList()" SortOrder="@SortOrder" />
</div>
}
else
@@ -88,7 +90,7 @@ else
</div>
</div>
}
@foreach (var credential in Credentials)
@foreach (var credential in SortedCredentials)
{
<CredentialCard Obj="@credential"/>
}
@@ -126,12 +128,28 @@ else
/// <summary>
/// Gets or sets the sort order for the credentials.
/// </summary>
private string SortOrder
private CredentialSortOrder SortOrder
{
get => DbService.Settings.CredentialsSortOrder;
set => DbService.Settings.SetCredentialsSortOrder(value);
}
/// <summary>
/// Gets the credentials sorted according to the current sort order.
/// </summary>
private IEnumerable<CredentialListEntry> SortedCredentials
{
get
{
return SortOrder switch
{
CredentialSortOrder.NewestFirst => Credentials.OrderByDescending(x => x.CreatedAt),
CredentialSortOrder.Alphabetical => Credentials.OrderBy(x => x.Service ?? string.Empty),
_ => Credentials.OrderBy(x => x.CreatedAt), // OldestFirst (default)
};
}
}
/// <summary>
/// Toggles the settings dropdown.
/// </summary>
@@ -187,18 +205,7 @@ else
return;
}
// Apply sort based on config.
switch (DbService.Settings.CredentialsSortOrder)
{
case "desc":
credentialListEntries = credentialListEntries.OrderByDescending(x => x.CreatedAt).ToList();
break;
default:
credentialListEntries = credentialListEntries.OrderBy(x => x.CreatedAt).ToList();
break;
}
// Pass unsorted list to the view - sorting will be handled by the table/grid components
Credentials = credentialListEntries;
IsLoading = false;
StateHasChanged();

View File

@@ -94,6 +94,10 @@
<value>Newest First</value>
<comment>Newest first sort option</comment>
</data>
<data name="AlphabeticalOption" xml:space="preserve">
<value>Alphabetical</value>
<comment>Alphabetical sort option</comment>
</data>
<!-- Empty State -->
<data name="NoCredentialsTitle" xml:space="preserve">

View File

@@ -13,6 +13,7 @@ using System.Globalization;
using System.Text.Json;
using System.Threading.Tasks;
using AliasClientDb;
using AliasVault.Client.Main.Models;
using Microsoft.EntityFrameworkCore;
/// <summary>
@@ -66,8 +67,15 @@ public sealed class SettingsService
/// <summary>
/// Gets the CredentialsSortOrder setting.
/// </summary>
/// <returns>Credentials sort order as string.</returns>
public string CredentialsSortOrder => GetSetting("CredentialsSortOrder", "asc")!;
/// <returns>Credentials sort order as enum.</returns>
public CredentialSortOrder CredentialsSortOrder
{
get
{
var value = GetSetting("CredentialsSortOrder", "OldestFirst")!;
return Enum.TryParse<CredentialSortOrder>(value, out var result) ? result : CredentialSortOrder.OldestFirst;
}
}
/// <summary>
/// Gets the AppLanguage setting.
@@ -154,7 +162,7 @@ public sealed class SettingsService
/// </summary>
/// <param name="value">The new value.</param>
/// <returns>Task.</returns>
public Task SetCredentialsSortOrder(string value) => SetSettingAsync("CredentialsSortOrder", value);
public Task SetCredentialsSortOrder(CredentialSortOrder value) => SetSettingAsync("CredentialsSortOrder", value.ToString());
/// <summary>
/// Sets the AppLanguage setting.