Files

196 lines
7.7 KiB
Plaintext

@page "/admin/products"
@layout AdminLayout
@using Generic
@inject ProductAPIConsumer productAPIConsumer
@inject ISnackbar Snackbar
@inject IJSRuntime JS
<div class="flex flex-col h-full w-full gap-5 p-6">
<div class="flex flex-row justify-between items-center">
<h1 class="text-2xl font-bold">Manage Products</h1>
<a class="btn btn-primary" href="/manage-product">Add Product</a>
</div>
<div class="bg-base-200 p-4 rounded-lg">
<div class="flex flex-col sm:flex-row gap-4 items-end">
<div class="form-control w-full max-w-xs">
<label class="label"><span class="label-text">Search Products</span></label>
<div class="relative">
<input type="text" placeholder="Search by name..." class="input input-bordered w-full pr-10"
@bind="productFilter.Name" @bind:event="oninput" @onkeyup="HandleKeyUp" />
<button type="button" class="absolute right-0 top-0 h-full px-3 flex items-center justify-center"
@onclick="LoadProducts">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 opacity-50" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
</svg>
</button>
</div>
</div>
<button class="btn btn-ghost" @onclick="ClearFilters">Clear</button>
</div>
</div>
<div class="overflow-x-auto bg-base-100 rounded-lg shadow">
<table class="table w-full">
<thead>
<tr>
<th class="w-16">Pic</th>
<th>Name</th>
<th>Price</th>
<th class="hidden md:table-cell">Description</th>
<th class="text-right">Actions</th>
</tr>
</thead>
<tbody>
@if (_loading)
{
<tr>
<td colspan="5" class="text-center py-10">
<span class="loading loading-spinner loading-lg text-primary"></span>
</td>
</tr>
}
else if (products == null || !products.Any())
{
<tr>
<td colspan="5" class="text-center py-10 opacity-50">No products found.</td>
</tr>
}
else
{
@foreach (var product in products)
{
<tr class="hover">
<td>
<div class="avatar">
<div class="mask mask-squircle w-12 h-12 bg-base-200">
@if (product.Photos != null && product.Photos.Any())
{
<img src="@product.Photos.First()" alt="Product image"
class="object-fill h-full w-full" />
}
else
{
<div class="flex items-center justify-center h-full w-full opacity-30">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
</svg>
</div>
}
</div>
</div>
</td>
<td class="font-medium text-primary">@product.Translations?.FirstOrDefault()?.Info?.Name ?? string.Empty)</td>
<td>@product.Price?.ToString("C")</td>
<td class="hidden md:table-cell max-w-xs truncate">@product.Translations?.FirstOrDefault()?.Info?.Description ?? string.Empty)</td>
<td class="text-right flex justify-end gap-2">
<a class="btn btn-ghost btn-xs btn-primary" href="@($"/manage-product/{product.Id}")">
Edit
</a>
<button class="btn btn-ghost btn-xs btn-error" @onclick="@(() => Remove(product))">
Delete
</button>
</td>
</tr>
}
}
</tbody>
</table>
</div>
<div class="mt-4">
<PaginationComp PaginationModel="@pagination" OnSearch="HandlePageChange" />
</div>
</div>
@code {
ProductFilter productFilter = new();
Pagination pagination = new() { PageSize = 10, CurrentPage = 1 };
bool _loading = false;
List<ProductVM> products = new();
protected override async Task OnInitializedAsync()
{
await LoadProducts();
}
async Task LoadProducts()
{
_loading = true;
try
{
var result = await productAPIConsumer.FetchProduct(productFilter, pagination);
products = result.Item1 ?? new List<ProductVM>();
pagination.ItemsCount = result.Item2;
}
catch (Exception ex)
{
Console.WriteLine($"Error loading products: {ex.Message}");
products = new List<ProductVM>();
ToastService.ShowToast("Failed to load products", ToastLevel.Error);
}
finally
{
_loading = false;
}
}
async Task HandlePageChange(int page)
{
pagination.CurrentPage = page;
await LoadProducts();
}
async Task HandleKeyUp(KeyboardEventArgs e)
{
if (e.Key == "Enter")
{
pagination.CurrentPage = 1;
await LoadProducts();
}
}
async Task ClearFilters()
{
productFilter = new();
pagination.CurrentPage = 1;
await LoadProducts();
}
async Task Remove(ProductVM product)
{
var confirmed = await JS.InvokeAsync<bool>("confirm", $"Are you sure you want to delete '{product.Translations?.FirstOrDefault()?.Info?.Name ?? string.Empty}'?");
if (confirmed)
{
if (product.Id == null) return;
try
{
var isSuccess = await productAPIConsumer.DeleteProduct(product.Id.Value.ToString());
if (isSuccess)
{
ToastService.ShowToast("Product deleted successfully", ToastLevel.Success);
await LoadProducts(); // Reload to refresh list and pagination
}
else
{
ToastService.ShowToast("Failed to delete product", ToastLevel.Error);
}
}
catch (Exception ex)
{
ToastService.ShowToast($"Error: {ex.Message}", ToastLevel.Error);
}
}
}
}