Added auto form stuff and started implementing auto crud and store admin things
This commit is contained in:
parent
f07e3c5a5a
commit
b0d9837256
11 changed files with 613 additions and 6 deletions
8
Moonlight/App/Extensions/Attributes/SelectorAttribute.cs
Normal file
8
Moonlight/App/Extensions/Attributes/SelectorAttribute.cs
Normal file
|
@ -0,0 +1,8 @@
|
|||
namespace Moonlight.App.Extensions.Attributes;
|
||||
|
||||
public class SelectorAttribute : Attribute
|
||||
{
|
||||
public string SelectorProp { get; set; } = "";
|
||||
public string DisplayProp { get; set; } = "";
|
||||
public bool UseDropdown { get; set; } = false;
|
||||
}
|
12
Moonlight/App/Helpers/ComponentHelper.cs
Normal file
12
Moonlight/App/Helpers/ComponentHelper.cs
Normal file
|
@ -0,0 +1,12 @@
|
|||
using Microsoft.AspNetCore.Components;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public static class ComponentHelper
|
||||
{
|
||||
public static RenderFragment FromType(Type type) => builder =>
|
||||
{
|
||||
builder.OpenComponent(0, type);
|
||||
builder.CloseComponent();
|
||||
};
|
||||
}
|
57
Moonlight/App/Helpers/PropBinder.cs
Normal file
57
Moonlight/App/Helpers/PropBinder.cs
Normal file
|
@ -0,0 +1,57 @@
|
|||
using System.Reflection;
|
||||
|
||||
namespace Moonlight.App.Helpers;
|
||||
|
||||
public class PropBinder<T>
|
||||
{
|
||||
private PropertyInfo PropertyInfo;
|
||||
private object DataObject;
|
||||
|
||||
public PropBinder(PropertyInfo propertyInfo, object dataObject)
|
||||
{
|
||||
PropertyInfo = propertyInfo;
|
||||
DataObject = dataObject;
|
||||
}
|
||||
|
||||
public string StringValue
|
||||
{
|
||||
get => (string)PropertyInfo.GetValue(DataObject)!;
|
||||
set => PropertyInfo.SetValue(DataObject, value);
|
||||
}
|
||||
|
||||
public int IntValue
|
||||
{
|
||||
get => (int)PropertyInfo.GetValue(DataObject)!;
|
||||
set => PropertyInfo.SetValue(DataObject, value);
|
||||
}
|
||||
|
||||
public long LongValue
|
||||
{
|
||||
get => (long)PropertyInfo.GetValue(DataObject)!;
|
||||
set => PropertyInfo.SetValue(DataObject, value);
|
||||
}
|
||||
|
||||
public bool BoolValue
|
||||
{
|
||||
get => (bool)PropertyInfo.GetValue(DataObject)!;
|
||||
set => PropertyInfo.SetValue(DataObject, value);
|
||||
}
|
||||
|
||||
public DateTime DateTimeValue
|
||||
{
|
||||
get => (DateTime)PropertyInfo.GetValue(DataObject)!;
|
||||
set => PropertyInfo.SetValue(DataObject, value);
|
||||
}
|
||||
|
||||
public T Class
|
||||
{
|
||||
get => (T)PropertyInfo.GetValue(DataObject)!;
|
||||
set => PropertyInfo.SetValue(DataObject, value);
|
||||
}
|
||||
|
||||
public double DoubleValue
|
||||
{
|
||||
get => (double)PropertyInfo.GetValue(DataObject)!;
|
||||
set => PropertyInfo.SetValue(DataObject, value);
|
||||
}
|
||||
}
|
16
Moonlight/App/Models/Forms/Store/AddCouponForm.cs
Normal file
16
Moonlight/App/Models/Forms/Store/AddCouponForm.cs
Normal file
|
@ -0,0 +1,16 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Moonlight.App.Models.Forms.Store;
|
||||
|
||||
public class AddCouponForm
|
||||
{
|
||||
[MinLength(5, ErrorMessage = "The code needs to be longer than 4")]
|
||||
[MaxLength(15, ErrorMessage = "The code should not be longer than 15 characters")]
|
||||
public string Code { get; set; } = "";
|
||||
|
||||
[Range(1, 99, ErrorMessage = "The percent needs to be between 1 and 99")]
|
||||
public int Percent { get; set; }
|
||||
|
||||
[Range(0, int.MaxValue, ErrorMessage = "The amount needs to be equals or greater than 0")]
|
||||
public int Amount { get; set; }
|
||||
}
|
|
@ -1,15 +1,19 @@
|
|||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Moonlight.App.Database.Entities.Store;
|
||||
using Moonlight.App.Database.Enums;
|
||||
using Moonlight.App.Extensions.Attributes;
|
||||
|
||||
namespace Moonlight.App.Models.Forms.Store;
|
||||
|
||||
public class AddProductForm
|
||||
{
|
||||
[Required(ErrorMessage = "You need to specify a category")]
|
||||
[Selector(DisplayProp = "Name", SelectorProp = "Name", UseDropdown = true)]
|
||||
public Category Category { get; set; }
|
||||
|
||||
[Required(ErrorMessage = "You need to specify a name")]
|
||||
[Description("Teeeeeeeeeeeeeeeeeeeeeeeeeest")]
|
||||
public string Name { get; set; } = "";
|
||||
|
||||
[Required(ErrorMessage = "You need to specify a description")]
|
||||
|
@ -19,16 +23,16 @@ public class AddProductForm
|
|||
[RegularExpression("^[a-z0-9-]+$", ErrorMessage = "You need to enter a valid slug only containing lowercase characters and numbers")]
|
||||
public string Slug { get; set; } = "";
|
||||
|
||||
[Range(0, double.MaxValue, ErrorMessage = "The price needs to be equals or above 0")]
|
||||
[Range(0, double.MaxValue, ErrorMessage = "The price needs to be equals or greater than 0")]
|
||||
public double Price { get; set; }
|
||||
|
||||
[Range(0, double.MaxValue, ErrorMessage = "The stock needs to be equals or above 0")]
|
||||
[Range(0, double.MaxValue, ErrorMessage = "The stock needs to be equals or greater than 0")]
|
||||
public int Stock { get; set; }
|
||||
|
||||
[Range(0, double.MaxValue, ErrorMessage = "The max per user amount needs to be equals or above 0")]
|
||||
[Range(0, double.MaxValue, ErrorMessage = "The max per user amount needs to be equals or greater than 0")]
|
||||
public int MaxPerUser { get; set; }
|
||||
|
||||
[Range(0, double.MaxValue, ErrorMessage = "The duration needs to be equals or above 0")]
|
||||
[Range(0, double.MaxValue, ErrorMessage = "The duration needs to be equals or greater than 0")]
|
||||
public int Duration { get; set; }
|
||||
|
||||
public ServiceType Type { get; set; }
|
||||
|
|
67
Moonlight/Shared/Components/Forms/AutoCrud.razor
Normal file
67
Moonlight/Shared/Components/Forms/AutoCrud.razor
Normal file
|
@ -0,0 +1,67 @@
|
|||
@using BlazorTable
|
||||
@using Moonlight.App.Repositories
|
||||
|
||||
@typeparam TItem where TItem : class
|
||||
|
||||
@inject Repository<TItem> ItemRepository
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">@(Title)</h3>
|
||||
<div class="card-toolbar">
|
||||
<button class="btn btn-icon btn-success">
|
||||
<i class="bx bx-sm bx-plus"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<LazyLoader Load="LoadItems">
|
||||
<Table TableItem="TItem"
|
||||
Items="Items"
|
||||
PageSize="50"
|
||||
TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6"
|
||||
TableHeadClass="fw-bold text-muted">
|
||||
@ChildContent
|
||||
</Table>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public string Title { get; set; } = "";
|
||||
|
||||
[Parameter]
|
||||
public Type CreateForm { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Type UpdateForm { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Repository<TItem>, TItem[]> Load { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment ChildContent { get; set; }
|
||||
|
||||
private TItem[] Items;
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
if (CreateForm == null)
|
||||
throw new ArgumentNullException(nameof(CreateForm));
|
||||
|
||||
if (UpdateForm == null)
|
||||
throw new ArgumentNullException(nameof(UpdateForm));
|
||||
|
||||
if (Load == null)
|
||||
throw new ArgumentNullException(nameof(Load));
|
||||
}
|
||||
|
||||
private Task LoadItems(LazyLoader _)
|
||||
{
|
||||
Items = Load.Invoke(ItemRepository);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
27
Moonlight/Shared/Components/Forms/AutoForm.razor
Normal file
27
Moonlight/Shared/Components/Forms/AutoForm.razor
Normal file
|
@ -0,0 +1,27 @@
|
|||
@using Moonlight.App.Extensions
|
||||
|
||||
@typeparam TForm
|
||||
|
||||
@foreach (var prop in typeof(TForm).GetProperties())
|
||||
{
|
||||
<div class="col-md-@(Columns) col-12">
|
||||
<CascadingValue Name="Property" Value="prop">
|
||||
<CascadingValue Name="Data" Value="(object)Model">
|
||||
@{
|
||||
var typeToCreate = typeof(AutoProperty<>).MakeGenericType(prop.PropertyType);
|
||||
}
|
||||
|
||||
@ComponentHelper.FromType(typeToCreate)
|
||||
</CascadingValue>
|
||||
</CascadingValue>
|
||||
</div>
|
||||
}
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public TForm Model { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public int Columns { get; set; } = 6;
|
||||
}
|
151
Moonlight/Shared/Components/Forms/AutoProperty.razor
Normal file
151
Moonlight/Shared/Components/Forms/AutoProperty.razor
Normal file
|
@ -0,0 +1,151 @@
|
|||
@using System.Reflection
|
||||
@using System.ComponentModel
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Moonlight.App.Extensions.Attributes
|
||||
@using Moonlight.App.Repositories
|
||||
|
||||
@typeparam TProp
|
||||
@inject IServiceProvider ServiceProvider
|
||||
|
||||
<label class="form-label">
|
||||
@(Formatter.ConvertCamelCaseToSpaces(Property.Name))
|
||||
</label>
|
||||
|
||||
@* Description using attribute *@
|
||||
|
||||
@{
|
||||
var attrs = Property.GetCustomAttributes(true);
|
||||
|
||||
var descAttr = attrs
|
||||
.FirstOrDefault(x => x.GetType() == typeof(DescriptionAttribute));
|
||||
}
|
||||
|
||||
@if (descAttr != null)
|
||||
{
|
||||
var attribute = descAttr as DescriptionAttribute;
|
||||
|
||||
<div class="form-text fs-5 mb-2 mt-0">
|
||||
@(attribute!.Description)
|
||||
</div>
|
||||
}
|
||||
|
||||
@* Actual value binding *@
|
||||
|
||||
<div class="input-group mb-5">
|
||||
@if (Property.PropertyType == typeof(string))
|
||||
{
|
||||
<div class="w-100">
|
||||
<InputText id="@Property.Name" @bind-Value="Binder.StringValue" class="form-control"/>
|
||||
</div>
|
||||
}
|
||||
else if (Property.PropertyType == typeof(int))
|
||||
{
|
||||
<InputNumber id="@Property.Name" @bind-Value="Binder.IntValue" class="form-control"/>
|
||||
}
|
||||
else if (Property.PropertyType == typeof(double))
|
||||
{
|
||||
<InputNumber id="@Property.Name" @bind-Value="Binder.DoubleValue" class="form-control"/>
|
||||
}
|
||||
else if (Property.PropertyType == typeof(long))
|
||||
{
|
||||
<InputNumber id="@Property.Name" @bind-Value="Binder.LongValue" class="form-control"/>
|
||||
}
|
||||
else if (Property.PropertyType == typeof(bool))
|
||||
{
|
||||
<div class="form-check">
|
||||
<InputCheckbox id="@Property.Name" @bind-Value="Binder.BoolValue" class="form-check-input"/>
|
||||
</div>
|
||||
}
|
||||
else if (Property.PropertyType == typeof(DateTime))
|
||||
{
|
||||
<InputDate id="@Property.Name" @bind-Value="Binder.DateTimeValue" class="form-control"/>
|
||||
}
|
||||
else if (Property.PropertyType == typeof(decimal))
|
||||
{
|
||||
<InputNumber id="@Property.Name" step="0.01" @bind-Value="Binder.DoubleValue" class="form-control"/>
|
||||
}
|
||||
else if (Property.PropertyType.IsEnum)
|
||||
{
|
||||
<select @bind="Binder.Class" class="form-select">
|
||||
@foreach (var status in (TProp[])Enum.GetValues(typeof(TProp)))
|
||||
{
|
||||
if (Binder.Class.ToString() == status.ToString())
|
||||
{
|
||||
<option value="@(status)" selected="">@(status)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(status)">@(status)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
}
|
||||
else if (Property.PropertyType.IsClass)
|
||||
{
|
||||
var attribute = Property.GetCustomAttributes(true)
|
||||
.FirstOrDefault(x => x.GetType() == typeof(SelectorAttribute)) as SelectorAttribute;
|
||||
|
||||
if (attribute != null)
|
||||
{
|
||||
if (attribute.UseDropdown)
|
||||
{
|
||||
var displayFunc = new Func<TProp, string>(x =>
|
||||
{
|
||||
var prop = typeof(TProp).GetProperties().First(x => x.Name == attribute.DisplayProp);
|
||||
return prop.GetValue(x) as string ?? "N/A";
|
||||
});
|
||||
|
||||
var searchFunc = new Func<TProp, string>(x =>
|
||||
{
|
||||
var prop = typeof(TProp).GetProperties().First(x => x.Name == attribute.SelectorProp);
|
||||
return prop.GetValue(x) as string ?? "N/A";
|
||||
});
|
||||
|
||||
<SmartDropdown @bind-Value="Binder.Class" DisplayFunc="displayFunc" SearchProp="searchFunc" Items="Items" />
|
||||
}
|
||||
else
|
||||
{
|
||||
var displayFunc = new Func<TProp, string>(x =>
|
||||
{
|
||||
var prop = typeof(TProp).GetProperties().First(x => x.Name == attribute.DisplayProp);
|
||||
return prop.GetValue(x) as string ?? "N/A";
|
||||
});
|
||||
|
||||
<SmartSelect @bind-Value="Binder.Class" DisplayField="displayFunc" Items="Items" CanBeNull="true" />
|
||||
}
|
||||
}
|
||||
}
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
[CascadingParameter(Name = "Data")]
|
||||
public object Data { get; set; }
|
||||
|
||||
[CascadingParameter(Name = "Property")]
|
||||
public PropertyInfo Property { get; set; }
|
||||
|
||||
private PropBinder<TProp> Binder;
|
||||
private TProp[] Items = Array.Empty<TProp>();
|
||||
|
||||
protected override void OnInitialized()
|
||||
{
|
||||
Binder = new(Property, Data);
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
if (Property.GetCustomAttributes(true).Any(x => x.GetType() == typeof(SelectorAttribute)))
|
||||
{
|
||||
var typeToGetByDi = typeof(Repository<>).MakeGenericType(typeof(TProp));
|
||||
var repo = ServiceProvider.GetRequiredService(typeToGetByDi);
|
||||
var dbSet = repo.GetType().GetMethods().First(x => x.Name == "Get").Invoke(repo, null) as IEnumerable<TProp>;
|
||||
Items = dbSet!.ToArray();
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
141
Moonlight/Shared/Components/Forms/SmartDropdown.razor
Normal file
141
Moonlight/Shared/Components/Forms/SmartDropdown.razor
Normal file
|
@ -0,0 +1,141 @@
|
|||
@using Microsoft.AspNetCore.Components.Forms
|
||||
|
||||
@typeparam T
|
||||
@inherits InputBase<T>
|
||||
|
||||
<div class="dropdown w-100">
|
||||
<div class="input-group">
|
||||
@if (CurrentValue == null)
|
||||
{
|
||||
<input class="form-control" type="text" @bind-value="SearchTerm" @bind-value:event="oninput" placeholder="@(Placeholder)">
|
||||
}
|
||||
else
|
||||
{
|
||||
<input class="form-control" type="text" value="@(DisplayFunc(CurrentValue))">
|
||||
<button class="btn btn-sm btn-primary" @onclick="() => SelectItem(default(T)!)">
|
||||
<i class="bx bx-sm bx-x"></i>
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
@{
|
||||
var anyItems = FilteredItems.Any();
|
||||
}
|
||||
|
||||
<div class="dropdown-menu w-100 @(anyItems ? "show" : "")" style="max-height: 200px; overflow-y: auto;">
|
||||
@if (anyItems)
|
||||
{
|
||||
foreach (var item in FilteredItems)
|
||||
{
|
||||
<button class="dropdown-item py-2" type="button" @onclick="() => SelectItem(item)">@DisplayFunc(item)</button>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
|
||||
[Parameter]
|
||||
public IEnumerable<T> Items { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, string> DisplayFunc { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<T, string> SearchProp { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public Func<Task>? OnSelected { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public string Placeholder { get; set; } = "Search...";
|
||||
|
||||
private string SearchTerm
|
||||
{
|
||||
get => searchTerm;
|
||||
set
|
||||
{
|
||||
searchTerm = value;
|
||||
FilteredItems = Items.OrderByDescending(x => Matches(SearchProp(x), searchTerm)).Take(30).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private string searchTerm = "";
|
||||
|
||||
private List<T> FilteredItems = new();
|
||||
|
||||
private async void SelectItem(T item)
|
||||
{
|
||||
CurrentValue = item;
|
||||
SearchTerm = "";
|
||||
FilteredItems.Clear();
|
||||
|
||||
if (OnSelected != null)
|
||||
await OnSelected.Invoke();
|
||||
}
|
||||
|
||||
protected override bool TryParseValueFromString(string? value, out T result, out string? validationErrorMessage)
|
||||
{
|
||||
// Check if the value is null or empty
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
result = default(T)!;
|
||||
validationErrorMessage = "Value cannot be null or empty";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try to find an item that matches the search term
|
||||
var item = FilteredItems.OrderByDescending(x => Matches(SearchProp(x), value)).FirstOrDefault();
|
||||
if (item != null)
|
||||
{
|
||||
result = item;
|
||||
validationErrorMessage = null;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = default(T)!;
|
||||
validationErrorMessage = $"No item found for search term '{value}'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private float Matches(string input, string search)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input) || string.IsNullOrEmpty(search))
|
||||
return 0;
|
||||
|
||||
var cleanedSearch = search
|
||||
.ToLower()
|
||||
.Replace(" ", "")
|
||||
.Replace("-", "");
|
||||
|
||||
var cleanedInput = input
|
||||
.ToLower()
|
||||
.Replace(" ", "")
|
||||
.Replace("-", "");
|
||||
|
||||
if (cleanedInput == cleanedSearch)
|
||||
return 10000;
|
||||
|
||||
float matches = 0;
|
||||
|
||||
int i = 0;
|
||||
foreach (var c in cleanedInput)
|
||||
{
|
||||
if (cleanedSearch.Length > i)
|
||||
{
|
||||
if (c == cleanedSearch[i])
|
||||
matches++;
|
||||
else
|
||||
matches--;
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
matches = matches / searchTerm.Length;
|
||||
|
||||
return matches;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
@using Moonlight.App.Models.Forms.Store
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Database.Entities.Store
|
||||
@using Mappy.Net
|
||||
|
||||
@inject Repository<Coupon> CouponRepository
|
||||
@inject ToastService ToastService
|
||||
|
||||
<SmartModal @ref="Modal" CssClasses="modal-dialog-centered modal-lg">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title fs-3">Add new modal</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<SmartForm Model="Form" OnValidSubmit="OnValidSubmit">
|
||||
<div class="modal-body">
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Code</label>
|
||||
<input @bind="Form.Code" class="form-control" type="text"/>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Percent</label>
|
||||
<input @bind="Form.Percent" class="form-control" type="number"/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label">Amount</label>
|
||||
<input @bind="Form.Amount" class="form-control" type="number"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
<button type="submit" class="btn btn-primary">Save changes</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</SmartModal>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public Func<Task> OnUpdate { get; set; }
|
||||
|
||||
private SmartModal Modal;
|
||||
private AddCouponForm Form = new();
|
||||
|
||||
public async Task Show()
|
||||
{
|
||||
await Modal.Show();
|
||||
}
|
||||
|
||||
private async Task OnValidSubmit()
|
||||
{
|
||||
var coupon = Mapper.Map<Coupon>(Form);
|
||||
CouponRepository.Add(coupon);
|
||||
|
||||
Form = new();
|
||||
await ToastService.Success("Successfully added new coupon");
|
||||
await Modal.Hide();
|
||||
await OnUpdate.Invoke();
|
||||
}
|
||||
}
|
|
@ -1,5 +1,70 @@
|
|||
@page "/admin/store/coupons"
|
||||
|
||||
@using BlazorTable
|
||||
@using Moonlight.App.Database.Entities.Store
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.Shared.Components.Modals.Store
|
||||
|
||||
@inject Repository<Coupon> CouponRepository
|
||||
@inject Repository<CouponUse> CouponUseRepository
|
||||
@inject ToastService ToastService
|
||||
|
||||
<AdminStoreNavigation Index="1" />
|
||||
|
||||
@*<div class="card card"*@
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">Coupons</h3>
|
||||
<div class="card-toolbar">
|
||||
<button @onclick="() => AddCouponModal.Show()" class="btn btn-icon btn-success"><i class="bx bx-sm bx-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<Table TableItem="Coupon"
|
||||
Items="AllCoupons"
|
||||
PageSize="50"
|
||||
TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3 fs-6"
|
||||
TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Coupon" Title="Id" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Coupon" Title="Code" Field="@(x => x.Code)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Coupon" Title="Amount" Field="@(x => x.Amount)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Coupon" Title="Percent" Field="@(x => x.Percent)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Coupon" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a @onclick="() => Remove(context)" @onclick:preventDefault href="#" class="text-danger">Remove</a>
|
||||
</Template>
|
||||
</Column>
|
||||
</Table>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AddCouponModal @ref="AddCouponModal" OnUpdate="() => LazyLoader.Reload()" />
|
||||
|
||||
@code
|
||||
{
|
||||
private Coupon[] AllCoupons;
|
||||
|
||||
private LazyLoader LazyLoader;
|
||||
private AddCouponModal AddCouponModal;
|
||||
|
||||
private Task Load(LazyLoader _)
|
||||
{
|
||||
AllCoupons = CouponRepository
|
||||
.Get()
|
||||
.ToArray();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task Remove(Coupon coupon)
|
||||
{
|
||||
if (CouponUseRepository.Get().Any(x => x.Coupon.Id == coupon.Id))
|
||||
throw new DisplayException("The coupon has been used so it cannot be deleted");
|
||||
|
||||
CouponRepository.Delete(coupon);
|
||||
|
||||
await ToastService.Success("Successfully deleted coupon");
|
||||
await LazyLoader.Reload();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue