Added sorting
This commit is contained in:
@@ -5,6 +5,8 @@ namespace TestApplication.Models;
|
|||||||
public class User {
|
public class User {
|
||||||
[Key]
|
[Key]
|
||||||
public Guid Id { get; } = Guid.CreateVersion7();
|
public Guid Id { get; } = Guid.CreateVersion7();
|
||||||
|
|
||||||
|
public int Index { get; set; }
|
||||||
|
|
||||||
[EmailAddress, MaxLength(25)]
|
[EmailAddress, MaxLength(25)]
|
||||||
public required string Email { get; set; }
|
public required string Email { get; set; }
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ builder.Services.AddHopFrame(config => {
|
|||||||
|
|
||||||
table.Property(u => u.Password)
|
table.Property(u => u.Password)
|
||||||
.Listable(false);
|
.Listable(false);
|
||||||
|
|
||||||
|
table.SetPreferredProperty(u => u.Username);
|
||||||
});
|
});
|
||||||
|
|
||||||
config.Table<Post>(table => {
|
config.Table<Post>(table => {
|
||||||
@@ -42,7 +44,7 @@ if (!app.Environment.IsDevelopment()) {
|
|||||||
await using (var scope = app.Services.CreateAsyncScope()) {
|
await using (var scope = app.Services.CreateAsyncScope()) {
|
||||||
var context = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
var context = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
|
||||||
|
|
||||||
foreach (var _ in Enumerable.Range(1, 100)) {
|
foreach (var i in Enumerable.Range(1, 100)) {
|
||||||
var firstName = Faker.Name.First();
|
var firstName = Faker.Name.First();
|
||||||
var lastName = Faker.Name.Last();
|
var lastName = Faker.Name.Last();
|
||||||
|
|
||||||
@@ -53,9 +55,22 @@ await using (var scope = app.Services.CreateAsyncScope()) {
|
|||||||
LastName = lastName,
|
LastName = lastName,
|
||||||
Description = Faker.Lorem.Paragraph(),
|
Description = Faker.Lorem.Paragraph(),
|
||||||
Birth = DateOnly.FromDateTime(Faker.Identification.DateOfBirth()),
|
Birth = DateOnly.FromDateTime(Faker.Identification.DateOfBirth()),
|
||||||
Password = Faker.RandomNumber.Next(100000L, 99999999999999999L).ToString()
|
Password = Faker.RandomNumber.Next(100000L, 99999999999999999L).ToString(),
|
||||||
|
Index = i
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
|
||||||
|
context.Posts.Add(new() {
|
||||||
|
Message = Faker.Lorem.Paragraph(),
|
||||||
|
Sender = context.Users.First()
|
||||||
|
});
|
||||||
|
|
||||||
|
context.Posts.Add(new() {
|
||||||
|
Message = Faker.Lorem.Paragraph(),
|
||||||
|
Sender = context.Users.Skip(1).First()
|
||||||
|
});
|
||||||
|
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ public class PropertyConfig {
|
|||||||
|
|
||||||
/// [GENERATED] The place (from left to right) that the property will appear in the table and editor
|
/// [GENERATED] The place (from left to right) that the property will appear in the table and editor
|
||||||
public int OrderIndex { get; set; }
|
public int OrderIndex { get; set; }
|
||||||
|
|
||||||
|
/// [GENERATED] The table that owns this property
|
||||||
|
public TableConfig Table { get; set; }
|
||||||
|
|
||||||
internal PropertyConfig() {}
|
internal PropertyConfig() {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,5 +28,8 @@ public class TableConfig {
|
|||||||
/// [GENERATED] The place (from top to bottom) that the table will appear in on the sidebar
|
/// [GENERATED] The place (from top to bottom) that the table will appear in on the sidebar
|
||||||
public int OrderIndex { get; set; }
|
public int OrderIndex { get; set; }
|
||||||
|
|
||||||
|
/// [GENERATED] The identifier of the property that should be displayed if the model is used as a relation
|
||||||
|
public string? PreferredProperty { get; set; }
|
||||||
|
|
||||||
internal TableConfig() {}
|
internal TableConfig() {}
|
||||||
}
|
}
|
||||||
@@ -46,8 +46,8 @@ public class TableConfigurator<TModel>(TableConfig config) where TModel : class
|
|||||||
return new PropertyConfigurator(prop);
|
return new PropertyConfigurator(prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="Property"/>
|
/// <inheritdoc cref="Property(string)"/>
|
||||||
public PropertyConfigurator Property<TProp>(Expression<Func<TModel, TProp>> propertyExpression) {
|
public PropertyConfigurator Property(Expression<Func<TModel, object>> propertyExpression) {
|
||||||
var propertyName = ExpressionHelper.GetPropertyInfo(propertyExpression).Name;
|
var propertyName = ExpressionHelper.GetPropertyInfo(propertyExpression).Name;
|
||||||
var prop = Config.Properties.FirstOrDefault(p => p.Identifier == propertyName);
|
var prop = Config.Properties.FirstOrDefault(p => p.Identifier == propertyName);
|
||||||
|
|
||||||
@@ -56,4 +56,16 @@ public class TableConfigurator<TModel>(TableConfig config) where TModel : class
|
|||||||
|
|
||||||
return new PropertyConfigurator(prop);
|
return new PropertyConfigurator(prop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc cref="TableConfig.PreferredProperty"/>
|
||||||
|
public TableConfigurator<TModel> SetPreferredProperty(Expression<Func<TModel, object>> propertyExpression) {
|
||||||
|
var propertyName = ExpressionHelper.GetPropertyInfo(propertyExpression).Name;
|
||||||
|
var prop = Config.Properties.FirstOrDefault(p => p.Identifier == propertyName);
|
||||||
|
|
||||||
|
if (prop is null)
|
||||||
|
throw new ArgumentException($"No attribute '{propertyName}' found in '{Config.Identifier}'!");
|
||||||
|
|
||||||
|
Config.PreferredProperty = prop.Identifier;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,29 @@
|
|||||||
using HopFrame.Core.Repositories;
|
using HopFrame.Core.Configuration;
|
||||||
|
using HopFrame.Core.Repositories;
|
||||||
|
using HopFrame.Core.Services;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace HopFrame.Core.EFCore;
|
namespace HopFrame.Core.EFCore;
|
||||||
|
|
||||||
internal class EfCoreRepository<TModel, TContext>(TContext context) : HopFrameRepository<TModel> where TModel : class where TContext : DbContext {
|
internal class EfCoreRepository<TModel, TContext>(TContext context, IConfigAccessor accessor) : HopFrameRepository<TModel> where TModel : class where TContext : DbContext {
|
||||||
|
|
||||||
public override async Task<IEnumerable<TModel>> LoadPageAsync(int page, int perPage, CancellationToken ct = default) {
|
public override async Task<IEnumerable<TModel>> LoadPageAsync(int page, int perPage, CancellationToken ct = default) {
|
||||||
var set = context.Set<TModel>();
|
var set = context.Set<TModel>();
|
||||||
return await set
|
var query = set
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Skip(page * perPage)
|
.Skip(page * perPage)
|
||||||
.Take(perPage)
|
.Take(perPage);
|
||||||
.ToArrayAsync(ct); //TODO: Implement FK loading
|
|
||||||
|
return await IncludeForeignKeys(query)
|
||||||
|
.ToArrayAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IQueryable<TModel> IncludeForeignKeys(IQueryable<TModel> query) {
|
||||||
|
var table = accessor.GetTableByType(typeof(TModel))!;
|
||||||
|
|
||||||
|
return table.Properties
|
||||||
|
.Where(p => (p.PropertyType & PropertyType.Relation) != 0)
|
||||||
|
.Aggregate(query, (current, property) => current.Include(property.Identifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override async Task<int> CountAsync(CancellationToken ct = default) {
|
public override async Task<int> CountAsync(CancellationToken ct = default) {
|
||||||
|
|||||||
@@ -42,9 +42,13 @@ internal static class ConfigurationHelper {
|
|||||||
Type = property.PropertyType,
|
Type = property.PropertyType,
|
||||||
DisplayName = property.Name,
|
DisplayName = property.Name,
|
||||||
OrderIndex = table.Properties.Count,
|
OrderIndex = table.Properties.Count,
|
||||||
PropertyType = InferPropertyType(property.PropertyType, property)
|
PropertyType = InferPropertyType(property.PropertyType, property),
|
||||||
|
Table = table
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (property.CustomAttributes.Any(a => a.AttributeType == typeof(KeyAttribute)))
|
||||||
|
table.PreferredProperty = config.Identifier;
|
||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ public abstract class HopFrameRepository<TModel> : IHopFrameRepository where TMo
|
|||||||
public abstract Task DeleteAsync(TModel entry, CancellationToken ct = default);
|
public abstract Task DeleteAsync(TModel entry, CancellationToken ct = default);
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<IEnumerable> LoadPageGenericAsync(int page, int perPage, CancellationToken ct) {
|
public async Task<IEnumerable<object>> LoadPageGenericAsync(int page, int perPage, CancellationToken ct) {
|
||||||
return await LoadPageAsync(page, perPage, ct);
|
return await LoadPageAsync(page, perPage, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async Task<IEnumerable> SearchGenericAsync(string searchTerm, int page, int perPage, CancellationToken ct) {
|
public async Task<IEnumerable<object>> SearchGenericAsync(string searchTerm, int page, int perPage, CancellationToken ct) {
|
||||||
return await SearchAsync(searchTerm, page, perPage, ct);
|
return await SearchAsync(searchTerm, page, perPage, ct);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Collections;
|
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
||||||
|
|
||||||
#pragma warning disable CS1573 // Parameter has no matching param tag in the XML comment (but other parameters do)
|
|
||||||
namespace HopFrame.Core.Repositories;
|
namespace HopFrame.Core.Repositories;
|
||||||
|
|
||||||
/// The generic repository that provides access to the model dataset
|
/// The generic repository that provides access to the model dataset
|
||||||
@@ -11,7 +9,7 @@ public interface IHopFrameRepository {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="page">The index of the current page (starts at 0)</param>
|
/// <param name="page">The index of the current page (starts at 0)</param>
|
||||||
/// <param name="perPage">The amount of entries that should be loaded</param>
|
/// <param name="perPage">The amount of entries that should be loaded</param>
|
||||||
public Task<IEnumerable> LoadPageGenericAsync(int page, int perPage, CancellationToken ct);
|
public Task<IEnumerable<object>> LoadPageGenericAsync(int page, int perPage, CancellationToken ct);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the total amount of entries in the dataset
|
/// Returns the total amount of entries in the dataset
|
||||||
@@ -24,7 +22,7 @@ public interface IHopFrameRepository {
|
|||||||
/// <param name="searchTerm">The search text provided by the user</param>
|
/// <param name="searchTerm">The search text provided by the user</param>
|
||||||
/// <param name="page">The index of the current page (starts at 0)</param>
|
/// <param name="page">The index of the current page (starts at 0)</param>
|
||||||
/// <param name="perPage">The amount of entries that should be loaded</param>
|
/// <param name="perPage">The amount of entries that should be loaded</param>
|
||||||
public Task<IEnumerable> SearchGenericAsync(string searchTerm, int page, int perPage, CancellationToken ct);
|
public Task<IEnumerable<object>> SearchGenericAsync(string searchTerm, int page, int perPage, CancellationToken ct);
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -10,7 +10,14 @@ public interface IEntityAccessor {
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="model">The model to pull the property from</param>
|
/// <param name="model">The model to pull the property from</param>
|
||||||
/// <param name="property">The property that shall be extracted</param>
|
/// <param name="property">The property that shall be extracted</param>
|
||||||
public Task<string?> GetValue(object model, PropertyConfig property);
|
public string? GetValue(object model, PropertyConfig property);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Formats the property to be displayed properly
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value of the property</param>
|
||||||
|
/// <param name="property">The property that shall be extracted</param>
|
||||||
|
public string? FormatValue(object? value, PropertyConfig property);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Properly formats and sets the new value of the property
|
/// Properly formats and sets the new value of the property
|
||||||
@@ -18,6 +25,14 @@ public interface IEntityAccessor {
|
|||||||
/// <param name="model">The model to save the property to</param>
|
/// <param name="model">The model to save the property to</param>
|
||||||
/// <param name="property">The property that shall be modified</param>
|
/// <param name="property">The property that shall be modified</param>
|
||||||
/// <param name="value">The new value of the property</param>
|
/// <param name="value">The new value of the property</param>
|
||||||
public Task SetValue(object model, PropertyConfig property, object value);
|
public void SetValue(object model, PropertyConfig property, object value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sorts the provided dataset by the specified property
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="data">The dataset that needs to be sorted</param>
|
||||||
|
/// <param name="property">The property that defines the sort order</param>
|
||||||
|
/// <param name="descending">Determines if the resulting order should be flipped</param>
|
||||||
|
public IEnumerable<object> SortDataByProperty(IEnumerable<object> data, PropertyConfig property, bool descending = false);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,91 @@
|
|||||||
using HopFrame.Core.Configuration;
|
using System.Linq.Expressions;
|
||||||
|
using System.Reflection;
|
||||||
|
using HopFrame.Core.Configuration;
|
||||||
|
|
||||||
namespace HopFrame.Core.Services.Implementation;
|
namespace HopFrame.Core.Services.Implementation;
|
||||||
|
|
||||||
internal class EntityAccessor : IEntityAccessor {
|
internal class EntityAccessor(IConfigAccessor accessor) : IEntityAccessor {
|
||||||
|
|
||||||
public async Task<string?> GetValue(object model, PropertyConfig property) {
|
public string? GetValue(object model, PropertyConfig property) {
|
||||||
var prop = model.GetType().GetProperty(property.Identifier);
|
var prop = model.GetType().GetProperty(property.Identifier);
|
||||||
|
|
||||||
if (prop is null)
|
if (prop is null)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return prop.GetValue(model)?.ToString();
|
var value = prop.GetValue(model);
|
||||||
|
return FormatValue(value, property);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SetValue(object model, PropertyConfig property, object value) {
|
|
||||||
var prop = model.GetType().GetProperty(property.Identifier);
|
|
||||||
|
|
||||||
|
public string? FormatValue(object? value, PropertyConfig property) {
|
||||||
|
if (value is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if ((property.PropertyType & PropertyType.List) != 0) {
|
||||||
|
return (value as IEnumerable<object>)!.Count().ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((property.PropertyType & PropertyType.Relation) != 0) {
|
||||||
|
var table = accessor.GetTableByType(property.Type);
|
||||||
|
if (table?.PreferredProperty != null) {
|
||||||
|
var tableProp = table.Properties.First(p => p.Identifier == table.PreferredProperty);
|
||||||
|
return GetValue(value, tableProp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return value.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetValue(object model, PropertyConfig property, object value) {
|
||||||
|
var prop = model.GetType().GetProperty(property.Identifier);
|
||||||
if (prop is null)
|
if (prop is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (value.GetType() != property.Type)
|
||||||
|
value = Convert.ChangeType(value, property.Type);
|
||||||
|
|
||||||
prop.SetValue(model, Convert.ChangeType(value, property.Type));
|
prop.SetValue(model, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IEnumerable<object> SortDataByProperty(IEnumerable<object> data, PropertyConfig property, bool descending = false) {
|
||||||
|
var prop = property.Table.TableType.GetProperty(property.Identifier);
|
||||||
|
if (prop is null)
|
||||||
|
return data;
|
||||||
|
|
||||||
|
var parameter = Expression.Parameter(property.Table.TableType);
|
||||||
|
Expression expression = Expression.Property(parameter, prop);
|
||||||
|
var targetType = prop.PropertyType;
|
||||||
|
|
||||||
|
if ((property.PropertyType & PropertyType.Relation) != 0) {
|
||||||
|
var relationTable = accessor.GetTableByType(property.Type);
|
||||||
|
PropertyInfo? relationPropInfo = null;
|
||||||
|
|
||||||
|
if (relationTable?.PreferredProperty != null) {
|
||||||
|
var relationProp = relationTable.Properties.First(p => p.Identifier == relationTable.PreferredProperty);
|
||||||
|
|
||||||
|
if ((relationProp.PropertyType & PropertyType.List) == 0)
|
||||||
|
relationPropInfo = relationProp.Type.GetProperty(relationProp.Identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (relationPropInfo == null) {
|
||||||
|
var formatMethod = GetType().GetMethod(nameof(FormatValue))!;
|
||||||
|
targetType = typeof(string);
|
||||||
|
expression = Expression.Call(Expression.Constant(this), formatMethod, expression, Expression.Constant(property));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
targetType = relationPropInfo.PropertyType;
|
||||||
|
expression = Expression.Property(expression, relationPropInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var lambda = Expression.Lambda(expression, parameter);
|
||||||
|
|
||||||
|
var methodName = descending ? nameof(Enumerable.OrderByDescending) : nameof(Enumerable.OrderBy);
|
||||||
|
var method = typeof(Enumerable)
|
||||||
|
.GetMethods()
|
||||||
|
.Single(m => m.Name == methodName && m.GetParameters().Length == 2)
|
||||||
|
.MakeGenericMethod(property.Table.TableType, targetType);
|
||||||
|
|
||||||
|
var result = method.Invoke(null, [data, lambda.Compile()]);
|
||||||
|
return (IEnumerable<object>)result!;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,8 @@
|
|||||||
<ToolBarContent>
|
<ToolBarContent>
|
||||||
<MudText Typo="Typo.h6">@Config.DisplayName</MudText>
|
<MudText Typo="Typo.h6">@Config.DisplayName</MudText>
|
||||||
<MudSpacer />
|
<MudSpacer />
|
||||||
<MudStack Row="true" Spacing="2" Style="min-width: 500px">
|
<MudStack Row="true" Spacing="2" Style="min-width: 600px">
|
||||||
|
<MudButton OnClick="@(Manager.ReloadServerData)">Reload</MudButton>
|
||||||
<MudTextField
|
<MudTextField
|
||||||
T="string"
|
T="string"
|
||||||
Placeholder="Search"
|
Placeholder="Search"
|
||||||
@@ -30,12 +31,13 @@
|
|||||||
<HeaderContent>
|
<HeaderContent>
|
||||||
@foreach (var prop in OrderedProperties) {
|
@foreach (var prop in OrderedProperties) {
|
||||||
<MudTh>
|
<MudTh>
|
||||||
@if (prop.Sortable) {
|
<MudTableSortLabel
|
||||||
<MudTableSortLabel SortLabel="@prop.Identifier" T="string">@prop.DisplayName</MudTableSortLabel>
|
T="object"
|
||||||
}
|
@ref="SortDirections[prop.Identifier]"
|
||||||
else {
|
SortDirectionChanged="@(dir => OnSort(prop, dir))"
|
||||||
|
Enabled="@prop.Sortable">
|
||||||
@prop.DisplayName
|
@prop.DisplayName
|
||||||
}
|
</MudTableSortLabel>
|
||||||
</MudTh>
|
</MudTh>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,12 @@ public partial class Table(IEntityAccessor accessor, IConfigAccessor configAcces
|
|||||||
|
|
||||||
private MudTable<Dictionary<string, string>> Manager { get; set; } = null!;
|
private MudTable<Dictionary<string, string>> Manager { get; set; } = null!;
|
||||||
|
|
||||||
|
private Dictionary<string, MudTableSortLabel<object>> SortDirections { get; set; } = new();
|
||||||
|
|
||||||
|
private KeyValuePair<string, SortDirection>? _currentSort;
|
||||||
|
|
||||||
|
private string _searchText = string.Empty;
|
||||||
|
|
||||||
protected override void OnInitialized() {
|
protected override void OnInitialized() {
|
||||||
base.OnInitialized();
|
base.OnInitialized();
|
||||||
|
|
||||||
@@ -26,22 +32,19 @@ public partial class Table(IEntityAccessor accessor, IConfigAccessor configAcces
|
|||||||
.Where(p => p.Listable)
|
.Where(p => p.Listable)
|
||||||
.OrderBy(p => p.OrderIndex)
|
.OrderBy(p => p.OrderIndex)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var property in OrderedProperties) {
|
||||||
|
SortDirections.Add(property.Identifier, null!);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<Dictionary<string, string>>> PrepareData(object[] entries) {
|
private List<Dictionary<string, string>> PrepareData(object[] entries) {
|
||||||
var list = new List<Dictionary<string, string>>();
|
var list = new List<Dictionary<string, string>>();
|
||||||
|
|
||||||
foreach (var entry in entries) {
|
foreach (var entry in entries) {
|
||||||
var taskDict = new Dictionary<string, Task<string?>>();
|
|
||||||
foreach (var prop in OrderedProperties) {
|
|
||||||
taskDict.Add(prop.Identifier, accessor.GetValue(entry, prop));
|
|
||||||
}
|
|
||||||
|
|
||||||
await Task.WhenAll(taskDict.Values);
|
|
||||||
|
|
||||||
var dict = new Dictionary<string, string>();
|
var dict = new Dictionary<string, string>();
|
||||||
foreach (var prop in taskDict) {
|
foreach (var prop in OrderedProperties) {
|
||||||
dict.Add(prop.Key, prop.Value.Result ?? string.Empty);
|
dict.Add(prop.Identifier, accessor.GetValue(entry, prop) ?? string.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Add(dict);
|
list.Add(dict);
|
||||||
@@ -51,8 +54,19 @@ public partial class Table(IEntityAccessor accessor, IConfigAccessor configAcces
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task<TableData<Dictionary<string, string>>> Reload(TableState state, CancellationToken ct) {
|
private async Task<TableData<Dictionary<string, string>>> Reload(TableState state, CancellationToken ct) {
|
||||||
var entries = await Repository.LoadPageGenericAsync(state.Page, state.PageSize, ct);
|
IEnumerable<object> entries;
|
||||||
var data = await PrepareData(entries.Cast<object>().ToArray());
|
|
||||||
|
if (string.IsNullOrWhiteSpace(_searchText))
|
||||||
|
entries = await Repository.LoadPageGenericAsync(state.Page, state.PageSize, ct);
|
||||||
|
else
|
||||||
|
entries = await Repository.SearchGenericAsync(_searchText, state.Page, state.PageSize, ct);
|
||||||
|
|
||||||
|
if (_currentSort.HasValue) {
|
||||||
|
var sortProp = Config.Properties.First(p => p.Identifier == _currentSort.Value.Key);
|
||||||
|
entries = accessor.SortDataByProperty(entries, sortProp, _currentSort.Value.Value == SortDirection.Descending);
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = PrepareData(entries.ToArray());
|
||||||
var total = await Repository.CountAsync(ct);
|
var total = await Repository.CountAsync(ct);
|
||||||
|
|
||||||
return new TableData<Dictionary<string, string>> {
|
return new TableData<Dictionary<string, string>> {
|
||||||
@@ -62,7 +76,27 @@ public partial class Table(IEntityAccessor accessor, IConfigAccessor configAcces
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task OnSearch(string searchText) {
|
private async Task OnSearch(string searchText) {
|
||||||
Console.WriteLine(searchText);
|
_searchText = searchText;
|
||||||
|
await Manager.ReloadServerData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task OnSort(PropertyConfig property, SortDirection direction) {
|
||||||
|
if (direction != SortDirection.None) {
|
||||||
|
foreach (var reference in SortDirections
|
||||||
|
.Where(d => d.Key != property.Identifier)) {
|
||||||
|
#pragma warning disable BL0005
|
||||||
|
reference.Value.SortDirection = SortDirection.None;
|
||||||
|
#pragma warning restore BL0005
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (direction == SortDirection.None) {
|
||||||
|
_currentSort = null;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
_currentSort = new(property.Identifier, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Manager.ReloadServerData();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
@page "/admin/{TableRoute}"
|
@page "/admin/{TableRoute}"
|
||||||
@using HopFrame.Web.Components.Components
|
@using HopFrame.Web.Components.Components
|
||||||
@inherits CancellableComponent
|
|
||||||
@rendermode InteractiveServer
|
@rendermode InteractiveServer
|
||||||
@layout HopFrameLayout
|
@layout HopFrameLayout
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using Microsoft.AspNetCore.Components;
|
|||||||
|
|
||||||
namespace HopFrame.Web.Components.Pages;
|
namespace HopFrame.Web.Components.Pages;
|
||||||
|
|
||||||
public partial class TablePage(IConfigAccessor accessor, NavigationManager navigator) : CancellableComponent {
|
public partial class TablePage(IConfigAccessor accessor, NavigationManager navigator) : ComponentBase {
|
||||||
private const int PerPage = 25;
|
private const int PerPage = 25;
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections;
|
using HopFrame.Core.Configuration;
|
||||||
using HopFrame.Core.Configuration;
|
|
||||||
using HopFrame.Core.Configurators;
|
using HopFrame.Core.Configurators;
|
||||||
using HopFrame.Core.Repositories;
|
using HopFrame.Core.Repositories;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -8,13 +7,13 @@ namespace HopFrame.Tests.Core.Configurators;
|
|||||||
|
|
||||||
public class HopFrameConfiguratorTests {
|
public class HopFrameConfiguratorTests {
|
||||||
private class TestRepository : IHopFrameRepository {
|
private class TestRepository : IHopFrameRepository {
|
||||||
public Task<IEnumerable> LoadPageGenericAsync(int page, int perPage, CancellationToken ct) {
|
public Task<IEnumerable<object>> LoadPageGenericAsync(int page, int perPage, CancellationToken ct) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public Task<int> CountAsync(CancellationToken ct) {
|
public Task<int> CountAsync(CancellationToken ct) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public Task<IEnumerable> SearchGenericAsync(string searchTerm, int page, int perPage, CancellationToken ct) {
|
public Task<IEnumerable<object>> SearchGenericAsync(string searchTerm, int page, int perPage, CancellationToken ct) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public Task CreateGenericAsync(object entry, CancellationToken ct) {
|
public Task CreateGenericAsync(object entry, CancellationToken ct) {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System.Collections;
|
using HopFrame.Core.Configuration;
|
||||||
using HopFrame.Core.Configuration;
|
|
||||||
using HopFrame.Core.Repositories;
|
using HopFrame.Core.Repositories;
|
||||||
using HopFrame.Core.Services.Implementation;
|
using HopFrame.Core.Services.Implementation;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
@@ -9,13 +8,13 @@ namespace HopFrame.Tests.Core.Services.Implementation;
|
|||||||
|
|
||||||
public class ConfigAccessorTests {
|
public class ConfigAccessorTests {
|
||||||
private class TestRepository : IHopFrameRepository {
|
private class TestRepository : IHopFrameRepository {
|
||||||
public Task<IEnumerable> LoadPageGenericAsync(int page, int perPage, CancellationToken ct) {
|
public Task<IEnumerable<object>> LoadPageGenericAsync(int page, int perPage, CancellationToken ct) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public Task<int> CountAsync(CancellationToken ct) {
|
public Task<int> CountAsync(CancellationToken ct) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public Task<IEnumerable> SearchGenericAsync(string searchTerm, int page, int perPage, CancellationToken ct) {
|
public Task<IEnumerable<object>> SearchGenericAsync(string searchTerm, int page, int perPage, CancellationToken ct) {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
public Task CreateGenericAsync(object entry, CancellationToken ct) {
|
public Task CreateGenericAsync(object entry, CancellationToken ct) {
|
||||||
|
|||||||
Reference in New Issue
Block a user