Initial commit
This commit is contained in:
26
Groceries/Transactions/EditTransactionItem.cshtml
Normal file
26
Groceries/Transactions/EditTransactionItem.cshtml
Normal file
@ -0,0 +1,26 @@
|
||||
@using Groceries.Data;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@model (Transaction Transaction, TransactionItem TransactionItem)
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
ViewBag.Title = "Edit Transaction Item";
|
||||
|
||||
var store = await dbContext.Stores
|
||||
.Where(store => store.Id == Model.Transaction.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
}
|
||||
|
||||
<h1>Edit Transaction Item</h1>
|
||||
|
||||
<div>@Model.Transaction.CreatedAt.ToShortDateString() @Model.Transaction.CreatedAt.ToShortTimeString() – @store</div>
|
||||
|
||||
<form method="post" asp-action="EditTransactionItem">
|
||||
<partial name="_TransactionItemForm" model="Model.TransactionItem" />
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit">Update</button>
|
||||
<a class="button" asp-action="EditTransactionItems">Cancel</a>
|
||||
</div>
|
||||
</form>
|
20
Groceries/Transactions/EditTransactionItem_Modal.cshtml
Normal file
20
Groceries/Transactions/EditTransactionItem_Modal.cshtml
Normal file
@ -0,0 +1,20 @@
|
||||
@using Groceries.Data
|
||||
|
||||
@model (Transaction Transaction, TransactionItem TransactionItem)
|
||||
@{
|
||||
Layout = "_Modal";
|
||||
ViewBag.Title = "Edit Transaction Item";
|
||||
}
|
||||
|
||||
<form class="card__content" id="editTransactionItem" method="post" asp-action="EditTransactionItem" data-action="turbo:submit-end->modal#close">
|
||||
<partial name="_TransactionItemForm" model="Model.TransactionItem" />
|
||||
</form>
|
||||
|
||||
<form id="deleteTransactionItem" method="post" asp-action="DeleteTransactionItem" asp-route-id="@Model.TransactionItem.ItemId" data-action="turbo:submit-end->modal#close"></form>
|
||||
|
||||
<footer class="card__footer card__footer--shaded row">
|
||||
<button class="button button--primary" type="submit" form="editTransactionItem">Update</button>
|
||||
<button class="button" data-action="modal#close">Cancel</button>
|
||||
<span class="row__fill"></span>
|
||||
<button class="button button--danger" type="submit" form="deleteTransactionItem">Remove</button>
|
||||
</footer>
|
81
Groceries/Transactions/Index.cshtml
Normal file
81
Groceries/Transactions/Index.cshtml
Normal file
@ -0,0 +1,81 @@
|
||||
@using Groceries.Transactions
|
||||
@using Microsoft.AspNetCore.Html;
|
||||
@model TransactionListModel
|
||||
@{
|
||||
ViewBag.Title = "Transactions";
|
||||
|
||||
string? GetNextSortDir(string col)
|
||||
{
|
||||
if (col != Model.Sort)
|
||||
{
|
||||
return "asc";
|
||||
}
|
||||
|
||||
return Model.Dir switch
|
||||
{
|
||||
null or "" => "asc",
|
||||
"asc" => "desc",
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
string? GetSortDir(string col)
|
||||
{
|
||||
if (col != Model.Sort)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return Model.Dir switch
|
||||
{
|
||||
"asc" or "desc" => Model.Dir,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
<div class="row">
|
||||
<h1 class="row__fill">Transactions</h1>
|
||||
<a class="button button--primary form-field" asp-action="NewTransaction">New transaction</a>
|
||||
</div>
|
||||
|
||||
<section class="table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="table__header table__header--sortable">
|
||||
<a asp-route-sort="@(GetNextSortDir("date") != null ? "date" : "")" asp-route-dir="@GetNextSortDir("date")" asp-route-page="1" data-dir="@GetSortDir("date")">
|
||||
Date
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="table__header" style="width: 100%">Store</th>
|
||||
<th scope="col" class="table__header table__header--sortable">
|
||||
<a asp-route-sort="@(GetNextSortDir("items") != null ? "items" : "")" asp-route-dir="@GetNextSortDir("items")" asp-route-page="1" data-dir="@GetSortDir("items")">
|
||||
Items
|
||||
</a>
|
||||
</th>
|
||||
<th scope="col" class="table__header table__header--sortable">
|
||||
<a asp-route-sort="@(GetNextSortDir("amount") != null ? "amount" : "")" asp-route-dir="@GetNextSortDir("amount")" asp-route-page="1" data-dir="@GetSortDir("amount")">
|
||||
Amount
|
||||
</a>
|
||||
</th>
|
||||
@*<th scope="col" class="table__header"></th>*@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var transaction in Model.Transactions)
|
||||
{
|
||||
<tr>
|
||||
<td class="table__cell">
|
||||
<time datetime="@transaction.CreatedAt.ToString("o")">@transaction.CreatedAt.ToLongDateString()</time>
|
||||
</td>
|
||||
<td class="table__cell">@transaction.Store</td>
|
||||
<td class="table__cell table__cell--numeric">@transaction.TotalItems</td>
|
||||
<td class="table__cell table__cell--numeric">@transaction.TotalAmount.ToString("c")</td>
|
||||
@*<td class="table__cell">View</td>*@
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<partial name="_TablePaginator" model="Model.Transactions" />
|
||||
</section>
|
41
Groceries/Transactions/NewTransaction.cshtml
Normal file
41
Groceries/Transactions/NewTransaction.cshtml
Normal file
@ -0,0 +1,41 @@
|
||||
@using Groceries.Data;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
ViewBag.Title = "New Transaction";
|
||||
|
||||
var datetime = DateTime.Now.ToString("s");
|
||||
|
||||
var stores = await dbContext.Stores
|
||||
.OrderBy(store => store.Retailer!.Name)
|
||||
.ThenBy(store => store.Name)
|
||||
.Select(store => new { store.Id, Name = string.Concat(store.Retailer!.Name, " ", store.Name) })
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
<h1>New Transaction</h1>
|
||||
|
||||
<form method="post" asp-action="NewTransaction">
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="transactionCreatedAt">Date</label>
|
||||
<div class="form-field__control input">
|
||||
<input class="input__control" id="transactionCreatedAt" name="createdAt" type="datetime-local" value="@datetime" max="@datetime" step="1" required autofocus />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="transactionStoreId">Store</label>
|
||||
<select class="form-field__control select" id="transactionStoreId" name="storeId" required>
|
||||
@foreach (var store in stores)
|
||||
{
|
||||
<option value="@store.Id">@store.Name</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit">Next</button>
|
||||
<a class="button" asp-action="Index">Cancel</a>
|
||||
</div>
|
||||
</form>
|
26
Groceries/Transactions/NewTransactionItem.cshtml
Normal file
26
Groceries/Transactions/NewTransactionItem.cshtml
Normal file
@ -0,0 +1,26 @@
|
||||
@using Groceries.Data;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@model Transaction
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
ViewBag.Title = "New Transaction Item";
|
||||
|
||||
var store = await dbContext.Stores
|
||||
.Where(store => store.Id == Model.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
}
|
||||
|
||||
<h1>New Transaction Item</h1>
|
||||
|
||||
<div>@Model.CreatedAt.ToShortDateString() @Model.CreatedAt.ToShortTimeString() – @store</div>
|
||||
|
||||
<form method="post" asp-action="NewTransactionItem">
|
||||
<partial name="_TransactionItemForm" model="null" />
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit">Add</button>
|
||||
<a class="button" asp-action="NewTransactionItems">Cancel</a>
|
||||
</div>
|
||||
</form>
|
16
Groceries/Transactions/NewTransactionItem_Modal.cshtml
Normal file
16
Groceries/Transactions/NewTransactionItem_Modal.cshtml
Normal file
@ -0,0 +1,16 @@
|
||||
@using Groceries.Data
|
||||
|
||||
@model Transaction
|
||||
@{
|
||||
Layout = "_Modal";
|
||||
ViewBag.Title = "New Transaction Item";
|
||||
}
|
||||
|
||||
<form class="card__content" id="newTransactionItem" method="post" asp-action="NewTransactionItem" data-action="turbo:submit-end->modal#close">
|
||||
<partial name="_TransactionItemForm" model="null" />
|
||||
</form>
|
||||
|
||||
<footer class="card__footer card__footer--shaded row">
|
||||
<button class="button button--primary" type="submit" form="newTransactionItem">Add</button>
|
||||
<button class="button" data-action="modal#close">Cancel</button>
|
||||
</footer>
|
56
Groceries/Transactions/NewTransactionItems.cshtml
Normal file
56
Groceries/Transactions/NewTransactionItems.cshtml
Normal file
@ -0,0 +1,56 @@
|
||||
@using Groceries.Data;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@model Transaction
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
ViewBag.Title = "New Transaction";
|
||||
|
||||
var store = await dbContext.Stores
|
||||
.Where(store => store.Id == Model.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
|
||||
var itemIds = Model.Items.Select(item => item.ItemId);
|
||||
var items = await dbContext.Items
|
||||
.Where(item => itemIds.Contains(item.Id))
|
||||
.Select(item => new { item.Id, Name = string.Concat(item.Brand, " ", item.Name) })
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
<h1>New Transaction</h1>
|
||||
|
||||
<div>@Model.CreatedAt.ToShortDateString() @Model.CreatedAt.ToLongTimeString() – @store</div>
|
||||
|
||||
<form method="post" asp-action="NewTransactionItems">
|
||||
<div class="card">
|
||||
<div class="card__header row">
|
||||
<h2 class="row__fill">Items</h2>
|
||||
<a class="button button--primary" asp-action="NewTransactionItem" autofocus data-turbo-frame="modal">New item</a>
|
||||
</div>
|
||||
|
||||
<div class="card__content">
|
||||
<ul>
|
||||
@foreach (var item in Model.Items)
|
||||
{
|
||||
<li class="row">
|
||||
<span class="row__fill">@items.Single(i => i.Id == item.ItemId).Name</span>
|
||||
<span>@item.Price.ToString("c")</span>
|
||||
<span>@item.Quantity</span>
|
||||
<span>@((item.Price * item.Quantity).ToString("c"))</span>
|
||||
<a class="link" asp-action="EditTransactionItem" asp-route-id="@item.ItemId" data-turbo-frame="modal">Edit</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="card__footer">
|
||||
Total: @Model.Items.Sum(item => item.Price * item.Quantity).ToString("c")
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit">Save</button>
|
||||
<a class="button" asp-action="Index">Cancel</a>
|
||||
</div>
|
||||
</form>
|
10
Groceries/Transactions/TransactionListModel.cs
Normal file
10
Groceries/Transactions/TransactionListModel.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Groceries.Transactions;
|
||||
|
||||
public record TransactionListModel(string? Sort, string? Dir, ListPageModel<TransactionListModel.Transaction> Transactions)
|
||||
{
|
||||
public record Transaction(Guid Id, DateTime CreatedAt, string Store)
|
||||
{
|
||||
public decimal TotalAmount { get; init; }
|
||||
public int TotalItems { get; init; }
|
||||
}
|
||||
}
|
237
Groceries/Transactions/TransactionsController.cs
Normal file
237
Groceries/Transactions/TransactionsController.cs
Normal file
@ -0,0 +1,237 @@
|
||||
namespace Groceries.Transactions;
|
||||
|
||||
using Groceries.Common;
|
||||
using Groceries.Data;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Text.Json;
|
||||
|
||||
[Route("/transactions")]
|
||||
public class TransactionsController : Controller
|
||||
{
|
||||
private readonly AppDbContext dbContext;
|
||||
|
||||
public TransactionsController(AppDbContext dbContext)
|
||||
{
|
||||
this.dbContext = dbContext;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index(int page, string? sort, string? dir)
|
||||
{
|
||||
var transactionsQuery = dbContext.Transactions
|
||||
.Join(
|
||||
dbContext.TransactionTotals,
|
||||
transaction => transaction.Id,
|
||||
transactionTotal => transactionTotal.TransactionId,
|
||||
(transaction, transactionTotal) => new
|
||||
{
|
||||
transaction.Id,
|
||||
transaction.CreatedAt,
|
||||
Store = string.Concat(transaction.Store!.Retailer!.Name, " ", transaction.Store.Name),
|
||||
TotalAmount = transactionTotal.Total,
|
||||
TotalItems = transaction.Items.Sum(item => item.Quantity),
|
||||
});
|
||||
|
||||
transactionsQuery = sort?.ToLowerInvariant() switch
|
||||
{
|
||||
"date" when dir == "desc" => transactionsQuery.OrderByDescending(transaction => transaction.CreatedAt),
|
||||
"amount" when dir == "desc" => transactionsQuery.OrderByDescending(transaction => transaction.TotalAmount),
|
||||
"items" when dir == "desc" => transactionsQuery.OrderByDescending(transaction => transaction.TotalItems),
|
||||
"date" => transactionsQuery.OrderBy(transaction => transaction.CreatedAt),
|
||||
"amount" => transactionsQuery.OrderBy(transaction => transaction.TotalAmount),
|
||||
"items" => transactionsQuery.OrderBy(transaction => transaction.TotalItems),
|
||||
_ => transactionsQuery.OrderByDescending(transaction => transaction.CreatedAt),
|
||||
};
|
||||
|
||||
var transactions = await transactionsQuery
|
||||
.Select(transaction => new TransactionListModel.Transaction(transaction.Id, transaction.CreatedAt, transaction.Store)
|
||||
{
|
||||
TotalAmount = transaction.TotalAmount,
|
||||
TotalItems = transaction.TotalItems,
|
||||
})
|
||||
.ToListPageModelAsync(page);
|
||||
|
||||
if (transactions.Page != page)
|
||||
{
|
||||
return RedirectToAction(nameof(Index), new { page = transactions.Page });
|
||||
}
|
||||
|
||||
var model = new TransactionListModel(sort, dir, transactions);
|
||||
return View(model);
|
||||
}
|
||||
|
||||
[HttpGet("new")]
|
||||
public IActionResult NewTransaction()
|
||||
{
|
||||
return View();
|
||||
}
|
||||
|
||||
[HttpPost("new")]
|
||||
public IActionResult NewTransaction(DateTime createdAt, Guid storeId)
|
||||
{
|
||||
var transaction = new Transaction(createdAt.ToUniversalTime(), storeId);
|
||||
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
|
||||
|
||||
return RedirectToAction(nameof(NewTransactionItems));
|
||||
}
|
||||
|
||||
[HttpGet("new/items")]
|
||||
public IActionResult NewTransactionItems()
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
}
|
||||
|
||||
return View(transaction);
|
||||
}
|
||||
|
||||
[HttpPost("new/items")]
|
||||
public async Task<IActionResult> PostNewTransactionItems()
|
||||
{
|
||||
if (TempData["NewTransaction"] is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
}
|
||||
|
||||
dbContext.Transactions.Add(transaction);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
return RedirectToAction(nameof(Index), new { page = 1 });
|
||||
}
|
||||
|
||||
[HttpGet("new/items/new")]
|
||||
public IActionResult NewTransactionItem()
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
}
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? View($"{nameof(NewTransactionItem)}_Modal", transaction)
|
||||
: View(transaction);
|
||||
}
|
||||
|
||||
[HttpPost("new/items/new")]
|
||||
public async Task<IActionResult> NewTransactionItem(string brand, string name, decimal price, int quantity)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
}
|
||||
|
||||
var itemId = await dbContext.Items
|
||||
.Where(item => EF.Functions.ILike(item.Brand, brand) && EF.Functions.ILike(item.Name, name))
|
||||
.Select(item => item.Id)
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
if (itemId == default)
|
||||
{
|
||||
var item = new Item(brand, name);
|
||||
dbContext.Items.Add(item);
|
||||
await dbContext.SaveChangesAsync();
|
||||
itemId = item.Id;
|
||||
}
|
||||
|
||||
// TODO: Handle item already in transaction - merge, replace, error?
|
||||
|
||||
var transactionItem = new TransactionItem(itemId, price, quantity);
|
||||
transaction.Items.Add(transactionItem);
|
||||
|
||||
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? NoContent()
|
||||
: RedirectToAction(nameof(NewTransactionItems));
|
||||
}
|
||||
|
||||
[HttpGet("new/items/edit/{id}")]
|
||||
public IActionResult EditTransactionItem(Guid id)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
}
|
||||
|
||||
var transactionItem = transaction.Items.SingleOrDefault(item => item.ItemId == id);
|
||||
if (transactionItem == null)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransactionItems));
|
||||
}
|
||||
|
||||
var model = (transaction, transactionItem);
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? View($"{nameof(EditTransactionItem)}_Modal", model)
|
||||
: View(model);
|
||||
}
|
||||
|
||||
[HttpPost("new/items/edit/{id}")]
|
||||
public async Task<IActionResult> EditTransactionItem(Guid id, string brand, string name, decimal price, int quantity)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
}
|
||||
|
||||
var transactionItem = transaction.Items.SingleOrDefault(item => item.ItemId == id);
|
||||
if (transactionItem == null)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransactionItems));
|
||||
}
|
||||
|
||||
var itemId = await dbContext.Items
|
||||
.Where(item => EF.Functions.ILike(item.Brand, brand) && EF.Functions.ILike(item.Name, name))
|
||||
.Select(item => item.Id)
|
||||
.SingleOrDefaultAsync();
|
||||
|
||||
if (itemId == transactionItem.ItemId)
|
||||
{
|
||||
transactionItem.Price = price;
|
||||
transactionItem.Quantity = quantity;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (itemId == default)
|
||||
{
|
||||
var item = new Item(brand, name);
|
||||
dbContext.Items.Add(item);
|
||||
await dbContext.SaveChangesAsync();
|
||||
itemId = item.Id;
|
||||
}
|
||||
|
||||
transaction.Items.Remove(transactionItem);
|
||||
|
||||
transactionItem = new TransactionItem(itemId, price, quantity);
|
||||
transaction.Items.Add(transactionItem);
|
||||
}
|
||||
|
||||
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? NoContent()
|
||||
: RedirectToAction(nameof(NewTransactionItems));
|
||||
}
|
||||
|
||||
[HttpPost("new/items/delete/{id}")]
|
||||
public IActionResult DeleteTransactionItem(Guid id)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
}
|
||||
|
||||
var transactionItem = transaction.Items.SingleOrDefault(item => item.ItemId == id);
|
||||
if (transactionItem != null)
|
||||
{
|
||||
transaction.Items.Remove(transactionItem);
|
||||
}
|
||||
|
||||
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? NoContent()
|
||||
: RedirectToAction(nameof(NewTransactionItems));
|
||||
}
|
||||
}
|
49
Groceries/Transactions/_TransactionItemForm.cshtml
Normal file
49
Groceries/Transactions/_TransactionItemForm.cshtml
Normal file
@ -0,0 +1,49 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@model TransactionItem?
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
var items = await dbContext.Items
|
||||
.OrderBy(item => item.Brand)
|
||||
.ToListAsync();
|
||||
|
||||
var selectedItem = items.SingleOrDefault(item => item.Id == Model?.ItemId);
|
||||
}
|
||||
|
||||
<fieldset class="form-field">
|
||||
<legend class="form-field__label">Item</legend>
|
||||
<div class="form-field__control input" data-controller="list-filter">
|
||||
<input class="input__control flex-2" name="brand" value="@selectedItem?.Brand" placeholder="Brand" list="itemBrands" autocomplete="off" required autofocus data-action="list-filter#filter" />
|
||||
<input class="input__control flex-5" name="name" value="@selectedItem?.Name" placeholder="Name" list="itemNames" autocomplete="off" required />
|
||||
|
||||
<datalist id="itemBrands">
|
||||
@foreach (var item in items.DistinctBy(item => item.Brand))
|
||||
{
|
||||
<option value="@item.Brand" />
|
||||
}
|
||||
</datalist>
|
||||
|
||||
<datalist id="itemNames">
|
||||
@foreach (var item in items.OrderBy(item => item.Name))
|
||||
{
|
||||
<option value="@item.Name" data-list-filter-target="option" data-list-filter-value="@item.Brand" />
|
||||
}
|
||||
</datalist>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="transactionItemPrice">Price</label>
|
||||
<div class="form-field__control input">
|
||||
@*<span class="input__inset">@CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol</span>*@
|
||||
<input class="input__control" id="transactionItemPrice" name="price" value="@Model?.Price" type="number" min="0" step="0.01" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="transactionItemQuantity">Quantity</label>
|
||||
<div class="form-field__control input">
|
||||
<input class="input__control" id="transactionItemQuantity" name="quantity" value="@(Model?.Quantity ?? 1)" type="number" min="1" required />
|
||||
</div>
|
||||
</div>
|
Reference in New Issue
Block a user