Refactor Items page to Razor component
This commit is contained in:
@ -1,25 +0,0 @@
|
||||
@using Groceries.Data
|
||||
@model Item
|
||||
@{
|
||||
Layout = ViewBag.RenderingToTurboStream == true ? null : "_Modal";
|
||||
ViewBag.Title = "Edit Item";
|
||||
}
|
||||
|
||||
<h1>Edit Item</h1>
|
||||
|
||||
<form asp-action="EditItem" method="post">
|
||||
<div class="form-field">
|
||||
<div class="form-field__control input">
|
||||
<input class="input__control" name="brand" value="@Model.Brand" placeholder="Brand" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<div class="form-field__control input">
|
||||
<input class="input__control" name="name" value="@Model.Name" placeholder="Name" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="button button--flat" type="reset" data-action="modal#close">Cancel</button>
|
||||
<button class="button button--flat">Save</button>
|
||||
</form>
|
@ -1,55 +0,0 @@
|
||||
@using Groceries.Items
|
||||
@model ItemListModel
|
||||
@{
|
||||
ViewBag.Title = "Items";
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<h1 class="row__fill">Items</h1>
|
||||
<form method="get" data-controller="search-form" data-turbo-frame="table" data-turbo-action="advance">
|
||||
<input type="hidden" name="page" value="1" />
|
||||
<div class="form-field">
|
||||
<div class="form-field__control input">
|
||||
<div class="input__inset">
|
||||
@* Search icon *@
|
||||
<svg class="icon icon--sm" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none" /><path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" /></svg>
|
||||
</div>
|
||||
<input class="input__control" type="search" name="search" value="@Model.Search" placeholder="Search" autocomplete="off" data-action="search-form#input" />
|
||||
<button class="input__addon button" data-search-form-target="button">Search</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<turbo-frame id="table" target="_top">
|
||||
<section class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="table__header table__header--shaded">Brand</th>
|
||||
<th scope="col" class="table__header table__header--shaded" style="width: 100%">Name</th>
|
||||
<th scope="col" class="table__header table__header--shaded">Last Purchased</th>
|
||||
<th scope="col" class="table__header table__header--shaded">Barcode</th>
|
||||
@*<th scope="col" class="table__header table__header--shaded"></th>*@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<tr>
|
||||
<td class="table__cell">@item.Brand</td>
|
||||
<td class="table__cell">@item.Name</td>
|
||||
<td class="table__cell">
|
||||
<time datetime="@item.LastPurchasedAt?.ToString("o")">@item.LastPurchasedAt?.ToLongDateString()</time>
|
||||
</td>
|
||||
<td class="table__cell table__cell--icon" style="width: fit-content">@(item.HasBarcode ? "✓" : "")</td>
|
||||
@*<td class="table__cell">
|
||||
<a class="link" asp-action="EditItem" asp-route-id="@item.Id">Edit</a>
|
||||
</td>*@
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<partial name="_TablePaginator" model="Model.Items" />
|
||||
</section>
|
||||
</turbo-frame>
|
@ -1,10 +0,0 @@
|
||||
namespace Groceries.Items;
|
||||
|
||||
public record ItemListModel(string? Search, ListPageModel<ItemListModel.Item> Items)
|
||||
{
|
||||
public record Item(Guid Id, string Brand, string Name)
|
||||
{
|
||||
public bool HasBarcode { get; init; }
|
||||
public DateTime? LastPurchasedAt { get; init; }
|
||||
}
|
||||
}
|
@ -1,86 +1,14 @@
|
||||
namespace Groceries.Items;
|
||||
|
||||
using Groceries.Common;
|
||||
using Groceries.Data;
|
||||
using Microsoft.AspNetCore.Http.HttpResults;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
[Route("/items")]
|
||||
public class ItemsController : Controller
|
||||
public class ItemsController : ControllerBase
|
||||
{
|
||||
private readonly AppDbContext dbContext;
|
||||
|
||||
public ItemsController(AppDbContext dbContext)
|
||||
{
|
||||
this.dbContext = dbContext;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index(int page, string? search)
|
||||
public IResult Index()
|
||||
{
|
||||
var itemsQuery = dbContext.Items.AsQueryable();
|
||||
if (!string.IsNullOrEmpty(search))
|
||||
{
|
||||
var searchPattern = $"%{search}%";
|
||||
itemsQuery = itemsQuery.Where(item => EF.Functions.ILike(item.Brand + ' ' + item.Name, searchPattern));
|
||||
}
|
||||
|
||||
var items = await itemsQuery
|
||||
.OrderBy(item => item.Brand)
|
||||
.ThenBy(item => item.Name)
|
||||
.GroupJoin(
|
||||
dbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase),
|
||||
item => item.Id,
|
||||
lastPurchase => lastPurchase.ItemId,
|
||||
(item, lastPurchase) => new { item, lastPurchase })
|
||||
.SelectMany(
|
||||
group => group.lastPurchase.DefaultIfEmpty(),
|
||||
(group, lastPurchase) => new ItemListModel.Item(group.item.Id, group.item.Brand, group.item.Name)
|
||||
{
|
||||
HasBarcode = group.item.Barcodes.Count != 0,
|
||||
LastPurchasedAt = lastPurchase != null ? lastPurchase.CreatedAt : null,
|
||||
})
|
||||
.ToListPageModelAsync(page, cancellationToken: HttpContext.RequestAborted);
|
||||
|
||||
if (items.Page != page)
|
||||
{
|
||||
return RedirectToAction(nameof(Index), new { page = items.Page, search });
|
||||
}
|
||||
|
||||
var model = new ItemListModel(search, items);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet("{id}")]
|
||||
public async Task<IActionResult> EditItem(Guid id)
|
||||
{
|
||||
var item = await dbContext.Items
|
||||
.SingleOrDefaultAsync(item => item.Id == id, HttpContext.RequestAborted);
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return View(item);
|
||||
}
|
||||
|
||||
[HttpPost("{id}")]
|
||||
public async Task<IActionResult> EditItem(Guid id, string brand, string name)
|
||||
{
|
||||
var item = await dbContext.Items
|
||||
.SingleOrDefaultAsync(item => item.Id == id, HttpContext.RequestAborted);
|
||||
|
||||
if (item == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (Request.AcceptsTurboStream())
|
||||
{
|
||||
return this.TurboStream(TurboStreamAction.Replace, "modal-body", item);
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(EditItem));
|
||||
return new RazorComponentResult<ItemsPage>();
|
||||
}
|
||||
}
|
||||
|
95
Groceries/Items/ItemsPage.razor
Normal file
95
Groceries/Items/ItemsPage.razor
Normal file
@ -0,0 +1,95 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@layout Layout
|
||||
|
||||
@inject AppDbContext DbContext
|
||||
@inject NavigationManager Navigation
|
||||
@inject IHttpContextAccessor HttpContextAccessor
|
||||
|
||||
<PageTitle>Groceries – Items</PageTitle>
|
||||
|
||||
<div class="row">
|
||||
<h1 class="row__fill">Items</h1>
|
||||
<SearchForm data-turbo-frame="table" data-turbo-action="advance">
|
||||
<input type="hidden" name="page" value="1" />
|
||||
</SearchForm>
|
||||
</div>
|
||||
|
||||
<turbo-frame id="table" target="top">
|
||||
<section class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="table__header table__header--shaded">Brand</th>
|
||||
<th scope="col" class="table__header table__header--shaded" style="width: 100%">Name</th>
|
||||
<th scope="col" class="table__header table__header--shaded">Last Purchased</th>
|
||||
<th scope="col" class="table__header table__header--shaded">Barcode</th>
|
||||
@*<th scope="col" class="table__header table__header--shaded"></th>*@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in items)
|
||||
{
|
||||
<tr>
|
||||
<td class="table__cell">@item.Brand</td>
|
||||
<td class="table__cell">@item.Name</td>
|
||||
<td class="table__cell">
|
||||
<time datetime="@item.LastPurchasedAt?.ToString("o")">@item.LastPurchasedAt?.ToLongDateString()</time>
|
||||
</td>
|
||||
<td class="table__cell table__cell--icon" style="width: fit-content">@(item.HasBarcode ? "✓" : "")</td>
|
||||
@*<td class="table__cell">
|
||||
<a class="link" href="/items/edit/@item.Id">Edit</a>
|
||||
</td>*@
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<TablePaginator Model="items" />
|
||||
</section>
|
||||
</turbo-frame>
|
||||
|
||||
@code {
|
||||
[SupplyParameterFromQuery]
|
||||
public int? Page { get; set; }
|
||||
|
||||
[SupplyParameterFromQuery]
|
||||
public string? Search { get; set; }
|
||||
|
||||
private record ItemModel(Guid Id, string Brand, string Name, bool HasBarcode, DateTime? LastPurchasedAt);
|
||||
|
||||
private ListPageModel<ItemModel> items = ListPageModel.Empty<ItemModel>();
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
var itemsQuery = DbContext.Items.AsQueryable();
|
||||
if (!string.IsNullOrEmpty(Search))
|
||||
{
|
||||
var searchPattern = $"%{Search}%";
|
||||
itemsQuery = itemsQuery.Where(item => EF.Functions.ILike(item.Brand + ' ' + item.Name, searchPattern));
|
||||
}
|
||||
|
||||
items = await itemsQuery
|
||||
.OrderBy(item => item.Brand)
|
||||
.ThenBy(item => item.Name)
|
||||
.GroupJoin(
|
||||
DbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase),
|
||||
item => item.Id,
|
||||
lastPurchase => lastPurchase.ItemId,
|
||||
(item, lastPurchase) => new { item, lastPurchase })
|
||||
.SelectMany(
|
||||
group => group.lastPurchase.DefaultIfEmpty(),
|
||||
(group, lastPurchase) => new ItemModel(
|
||||
group.item.Id,
|
||||
group.item.Brand,
|
||||
group.item.Name,
|
||||
group.item.Barcodes.Count != 0,
|
||||
lastPurchase != null ? lastPurchase.CreatedAt : null))
|
||||
.ToListPageModelAsync(Page.GetValueOrDefault(), cancellationToken: HttpContextAccessor.HttpContext!.RequestAborted);
|
||||
|
||||
if (items.Page != Page)
|
||||
{
|
||||
Navigation.NavigateTo(Navigation.GetUriWithQueryParameter("page", items.Page));
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user