Refactor New Transaction pages/modals to Razor components
This commit is contained in:
parent
74cb6109c9
commit
70a34b67d6
@ -1,27 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Groceries@(ViewBag.Title != null ? Html.Raw($" – {ViewBag.Title}") : "")</title>
|
||||
<link rel="stylesheet" type="text/css" href="/css/main.css" asp-append-version="true" data-turbo-track="reload" />
|
||||
<script type="module" src="/js/main.js" asp-append-version="true" data-turbo-track="reload"></script>
|
||||
<script type="module" src="/lib/hotwired/turbo/dist/turbo.es2017-esm.js"></script>
|
||||
|
||||
@RenderSection("head", required: false)
|
||||
</head>
|
||||
<body>
|
||||
<partial name="_LayoutSidebar" />
|
||||
|
||||
<main class="main-content">
|
||||
@*<turbo-frame id="main" target="_top">*@
|
||||
@RenderBody()
|
||||
@*</turbo-frame>*@
|
||||
</main>
|
||||
|
||||
<dialog class="modal" data-controller="modal" data-action="turbo:frame-load->modal#open turbo:before-cache@document->modal#close popstate@window->modal#close">
|
||||
<turbo-frame id="modal" data-modal-target="frame"></turbo-frame>
|
||||
</dialog>
|
||||
</body>
|
||||
</html>
|
@ -1,63 +0,0 @@
|
||||
@{
|
||||
var controller = ViewContext.RouteData.Values["controller"]?.ToString() ?? string.Empty;
|
||||
}
|
||||
|
||||
<input class="sidebar__toggle" id="sidebarToggle" type="checkbox" role="button" data-turbo-permanent />
|
||||
<label for="sidebarToggle">Menu</label>
|
||||
|
||||
<section class="sidebar">
|
||||
<header class="sidebar__header">
|
||||
Groceries
|
||||
</header>
|
||||
<nav class="sidebar__body">
|
||||
<ul>
|
||||
<li>
|
||||
<a class="sidebar__item @(controller == "Home" ? "sidebar__item--active" : "")" asp-controller="Home" asp-action="Index">
|
||||
@* dashboard squares icon *@
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 5v2h-4V5h4M9 5v6H5V5h4m10 8v6h-4v-6h4M9 17v2H5v-2h4M21 3h-8v6h8V3zM11 3H3v10h8V3zm10 8h-8v10h8V11zm-10 4H3v6h8v-6z"/></svg>
|
||||
Dashboard
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="sidebar__item @(controller == "Lists" ? "sidebar__item--active" : "")" asp-controller="Lists" asp-action="Index" asp-route-page="1">
|
||||
@* receipt long icon *@
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24"><path d="M0,0h24v24H0V0z" fill="none"/><g><path d="M19.5,3.5L18,2l-1.5,1.5L15,2l-1.5,1.5L12,2l-1.5,1.5L9,2L7.5,3.5L6,2v14H3v3c0,1.66,1.34,3,3,3h12c1.66,0,3-1.34,3-3V2 L19.5,3.5z M15,20H6c-0.55,0-1-0.45-1-1v-1h10V20z M19,19c0,0.55-0.45,1-1,1s-1-0.45-1-1v-3H8V5h11V19z"/><rect height="2" width="6" x="9" y="7"/><rect height="2" width="2" x="16" y="7"/><rect height="2" width="6" x="9" y="10"/><rect height="2" width="2" x="16" y="10"/></g></svg>
|
||||
Lists
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="sidebar__item @(controller == "Transactions" ? "sidebar__item--active" : "")" asp-controller="Transactions" asp-action="Index" asp-route-page="1">
|
||||
@* shopping cart icon *@
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M15.55 13c.75 0 1.41-.41 1.75-1.03l3.58-6.49c.37-.66-.11-1.48-.87-1.48H5.21l-.94-2H1v2h2l3.6 7.59-1.35 2.44C4.52 15.37 5.48 17 7 17h12v-2H7l1.1-2h7.45zM6.16 6h12.15l-2.76 5H8.53L6.16 6zM7 18c-1.1 0-1.99.9-1.99 2S5.9 22 7 22s2-.9 2-2-.9-2-2-2zm10 0c-1.1 0-1.99.9-1.99 2s.89 2 1.99 2 2-.9 2-2-.9-2-2-2z"/></svg>
|
||||
Transactions
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="sidebar__item @(controller == "Items" ? "sidebar__item--active" : "")" asp-controller="Items" asp-action="Index" asp-route-page="1">
|
||||
@* category shapes icon *@
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M12 2l-5.5 9h11L12 2zm0 3.84L13.93 9h-3.87L12 5.84zM17.5 13c-2.49 0-4.5 2.01-4.5 4.5s2.01 4.5 4.5 4.5 4.5-2.01 4.5-4.5-2.01-4.5-4.5-4.5zm0 7c-1.38 0-2.5-1.12-2.5-2.5s1.12-2.5 2.5-2.5 2.5 1.12 2.5 2.5-1.12 2.5-2.5 2.5zM3 21.5h8v-8H3v8zm2-6h4v4H5v-4z"/></svg>
|
||||
Items
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a class="sidebar__item" href="/stores?page=1">
|
||||
@* store building icon *@
|
||||
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M18.36 9l.6 3H5.04l.6-3h12.72M20 4H4v2h16V4zm0 3H4l-1 5v2h1v6h10v-6h4v6h2v-6h1v-2l-1-5zM6 18v-4h6v4H6z"/></svg>
|
||||
Stores
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
@*<div class="slide-toggle">
|
||||
<label class="slide-toggle__option">
|
||||
<svg class="icon icon--sm" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24"><rect fill="none" height="24" width="24"/><path d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"/></svg>
|
||||
Light
|
||||
<input class="slide-toggle__control" type="radio" name="colorScheme" value="light" />
|
||||
</label>
|
||||
<label class="slide-toggle__option">
|
||||
<svg class="icon icon--sm" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" viewBox="0 0 24 24"><rect fill="none" height="24" width="24"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/></svg>
|
||||
Dark
|
||||
<input class="slide-toggle__control" type="radio" name="colorScheme" value="dark" />
|
||||
</label>
|
||||
</div>*@
|
||||
</section>
|
@ -1,9 +0,0 @@
|
||||
<turbo-frame id="modal" data-modal-target="frame">
|
||||
<article class="card">
|
||||
<header class="card__header row">
|
||||
<h2 class="row__fill">@ViewBag.Title</h2>
|
||||
<button class="button modal__close-button" data-action="modal#close">🗙</button>
|
||||
</header>
|
||||
@RenderBody()
|
||||
</article>
|
||||
</turbo-frame>
|
@ -2,7 +2,6 @@ using DbUp;
|
||||
using Groceries.Data;
|
||||
using Microsoft.AspNetCore.DataProtection;
|
||||
using Microsoft.AspNetCore.HttpOverrides;
|
||||
using Microsoft.AspNetCore.Mvc.Razor;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
@ -44,13 +43,7 @@ if (env.IsProduction())
|
||||
}
|
||||
|
||||
builder.Services
|
||||
.AddControllersWithViews()
|
||||
.AddRazorOptions(options =>
|
||||
{
|
||||
options.ViewLocationFormats.Clear();
|
||||
options.ViewLocationFormats.Add("/{1}/{0}" + RazorViewEngine.ViewExtension);
|
||||
options.ViewLocationFormats.Add("/Common/{0}" + RazorViewEngine.ViewExtension);
|
||||
})
|
||||
.AddControllers()
|
||||
.AddSessionStateTempDataProvider();
|
||||
|
||||
builder.Services.AddDistributedMemoryCache();
|
||||
|
@ -1,32 +0,0 @@
|
||||
@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 class="form-field">
|
||||
@Model.Transaction.CreatedAt.ToShortDateString() @Model.Transaction.CreatedAt.ToShortTimeString() – @store
|
||||
</div>
|
||||
|
||||
<form id="editTransactionItem" method="post" asp-action="EditTransactionItem">
|
||||
<partial name="_TransactionItemForm" model="Model.TransactionItem" />
|
||||
</form>
|
||||
|
||||
<form id="deleteTransactionItem" method="post" asp-action="DeleteTransactionItem" asp-route-id="@Model.TransactionItem.ItemId"></form>
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit" form="editTransactionItem">Update</button>
|
||||
<a class="button" asp-action="NewTransactionItems">Cancel</a>
|
||||
<span class="row__fill"></span>
|
||||
<button class="button button--danger" type="submit" form="deleteTransactionItem">Remove</button>
|
||||
</div>
|
24
Groceries/Transactions/EditTransactionItemModal.razor
Normal file
24
Groceries/Transactions/EditTransactionItemModal.razor
Normal file
@ -0,0 +1,24 @@
|
||||
@using Groceries.Data
|
||||
|
||||
@layout Modal
|
||||
|
||||
<SectionContent SectionName="modalTitle">Edit Transaction Item</SectionContent>
|
||||
|
||||
<TransactionItemForm TransactionItem="TransactionItem" class="card__content" id="editTransactionItem" data-action="turbo:submit-end->modal#close" />
|
||||
|
||||
<form method="post" action="/transactions/new/items/delete/@TransactionItem.ItemId" id="deleteTransactionItem" 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>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Transaction Transaction { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public required TransactionItem TransactionItem { get; set; }
|
||||
}
|
43
Groceries/Transactions/EditTransactionItemPage.razor
Normal file
43
Groceries/Transactions/EditTransactionItemPage.razor
Normal file
@ -0,0 +1,43 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@layout Layout
|
||||
|
||||
@inject AppDbContext DbContext
|
||||
|
||||
<PageTitle>Groceries – Edit Transaction Item</PageTitle>
|
||||
|
||||
<h1>Edit Transaction Item</h1>
|
||||
|
||||
<div class="form-field">
|
||||
@Transaction.CreatedAt.ToShortDateString() @Transaction.CreatedAt.ToLongTimeString() – @store
|
||||
</div>
|
||||
|
||||
<TransactionItemForm TransactionItem="TransactionItem" id="editTransactionItem" />
|
||||
|
||||
<form method="post" action="/transactions/new/items/delete/@TransactionItem.ItemId" id="deleteTransactionItem" data-action="turbo:submit-end->modal#close"></form>
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit" form="editTransactionItem">Update</button>
|
||||
<a class="button" href="/transaction/new/items">Cancel</a>
|
||||
<span class="row__fill"></span>
|
||||
<button class="button button--danger" type="submit" form="deleteTransactionItem">Remove</button>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Transaction Transaction { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public required TransactionItem TransactionItem { get; set; }
|
||||
|
||||
private string store = string.Empty;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
store = await DbContext.Stores
|
||||
.Where(store => store.Id == Transaction.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
@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>
|
@ -1,32 +0,0 @@
|
||||
@using Groceries.Data;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@model (Transaction Transaction, TransactionPromotion Promotion)
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
ViewBag.Title = "Edit Transaction Promotion";
|
||||
|
||||
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 Promotion</h1>
|
||||
|
||||
<div class="form-field">
|
||||
@Model.Transaction.CreatedAt.ToShortDateString() @Model.Transaction.CreatedAt.ToShortTimeString() – @store
|
||||
</div>
|
||||
|
||||
<form id="editTransactionPromotion" method="post" asp-action="EditTransactionPromotion">
|
||||
<partial name="_TransactionPromotionForm" model="Model" />
|
||||
</form>
|
||||
|
||||
<form id="deleteTransactionPromotion" method="post" asp-action="DeleteTransactionPromotion" asp-route-id="@Model.Promotion.Id"></form>
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit" form="editTransactionPromotion">Update</button>
|
||||
<a class="button" asp-action="NewTransactionPromotions">Cancel</a>
|
||||
<span class="row__fill"></span>
|
||||
<button class="button button--danger" type="submit" form="deleteTransactionPromotion">Remove</button>
|
||||
</div>
|
24
Groceries/Transactions/EditTransactionPromotionModal.razor
Normal file
24
Groceries/Transactions/EditTransactionPromotionModal.razor
Normal file
@ -0,0 +1,24 @@
|
||||
@using Groceries.Data
|
||||
|
||||
@layout Modal
|
||||
|
||||
<SectionContent SectionName="modalTitle">Edit Transaction Promotion</SectionContent>
|
||||
|
||||
<TransactionPromotionForm Transaction="Transaction" Promotion="Promotion" class="card__content" id="editTransactionPromotion" data-action="turbo:submit-end->modal#close" />
|
||||
|
||||
<form method="post" action="/transactions/new/promotions/delete/@Promotion.Id" id="deleteTransactionPromotion" data-action="turbo:submit-end->modal#close"></form>
|
||||
|
||||
<footer class="card__footer card__footer--shaded row">
|
||||
<button class="button button--primary" type="submit" form="editTransactionPromotion">Update</button>
|
||||
<button class="button" data-action="modal#close">Cancel</button>
|
||||
<span class="row__fill"></span>
|
||||
<button class="button button--danger" type="submit" form="deleteTransactionPromotion">Remove</button>
|
||||
</footer>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Transaction Transaction { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public required TransactionPromotion Promotion { get; set; }
|
||||
}
|
43
Groceries/Transactions/EditTransactionPromotionPage.razor
Normal file
43
Groceries/Transactions/EditTransactionPromotionPage.razor
Normal file
@ -0,0 +1,43 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@layout Layout
|
||||
|
||||
@inject AppDbContext DbContext
|
||||
|
||||
<PageTitle>Groceries – Edit Transaction Promotion</PageTitle>
|
||||
|
||||
<h1>Edit Transaction Promotion</h1>
|
||||
|
||||
<div class="form-field">
|
||||
@Transaction.CreatedAt.ToShortDateString() @Transaction.CreatedAt.ToLongTimeString() – @store
|
||||
</div>
|
||||
|
||||
<TransactionPromotionForm Transaction="Transaction" Promotion="Promotion" id="editTransactionPromotion" />
|
||||
|
||||
<form method="post" action="/transactions/new/promotions/delete/@Promotion.Id" id="deleteTransactionPromotion"></form>
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit" form="editTransactionPromotion">Update</button>
|
||||
<a class="button" href="/transactions/new/promotions">Cancel</a>
|
||||
<span class="row__fill"></span>
|
||||
<button class="button button--danger" type="submit" form="deleteTransactionPromotion">Remove</button>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Transaction Transaction { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public required TransactionPromotion Promotion { get; set; }
|
||||
|
||||
private string store = string.Empty;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
store = await DbContext.Stores
|
||||
.Where(store => store.Id == Transaction.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
@using Groceries.Data
|
||||
|
||||
@model (Transaction Transaction, TransactionPromotion Promotion)
|
||||
@{
|
||||
Layout = "_Modal";
|
||||
ViewBag.Title = "Edit Transaction Promotion";
|
||||
}
|
||||
|
||||
<form class="card__content" id="editTransactionPromotion" method="post" asp-action="EditTransactionPromotion" data-action="turbo:submit-end->modal#close">
|
||||
<partial name="_TransactionPromotionForm" model="Model" />
|
||||
</form>
|
||||
|
||||
<form id="deleteTransactionPromotion" method="post" asp-action="DeleteTransactionPromotion" asp-route-id="@Model.Promotion.Id" data-action="turbo:submit-end->modal#close"></form>
|
||||
|
||||
<footer class="card__footer card__footer--shaded row">
|
||||
<button class="button button--primary" type="submit" form="editTransactionPromotion">Update</button>
|
||||
<button class="button" data-action="modal#close">Cancel</button>
|
||||
<span class="row__fill"></span>
|
||||
<button class="button button--danger" type="submit" form="deleteTransactionPromotion">Remove</button>
|
||||
</footer>
|
@ -1,28 +0,0 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@model (Transaction Transaction, TransactionItem? TransactionItem)
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
ViewBag.Title = "New 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>New Transaction Item</h1>
|
||||
|
||||
<div class="form-field">
|
||||
@Model.Transaction.CreatedAt.ToShortDateString() @Model.Transaction.CreatedAt.ToShortTimeString() – @store
|
||||
</div>
|
||||
|
||||
<form method="post" asp-action="NewTransactionItem">
|
||||
<partial name="_TransactionItemForm" model="Model.TransactionItem" />
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit">Add</button>
|
||||
<a class="button" asp-action="NewTransactionItems">Cancel</a>
|
||||
</div>
|
||||
</form>
|
20
Groceries/Transactions/NewTransactionItemModal.razor
Normal file
20
Groceries/Transactions/NewTransactionItemModal.razor
Normal file
@ -0,0 +1,20 @@
|
||||
@using Groceries.Data
|
||||
|
||||
@layout Modal
|
||||
|
||||
<SectionContent SectionName="modalTitle">New Transaction Item</SectionContent>
|
||||
|
||||
<TransactionItemForm TransactionItem="TransactionItem" class="card__content" id="newTransactionItem" data-action="turbo:submit-end->modal#close" />
|
||||
|
||||
<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>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Transaction Transaction { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public TransactionItem? TransactionItem { get; set; }
|
||||
}
|
39
Groceries/Transactions/NewTransactionItemPage.razor
Normal file
39
Groceries/Transactions/NewTransactionItemPage.razor
Normal file
@ -0,0 +1,39 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@layout Layout
|
||||
|
||||
@inject AppDbContext DbContext
|
||||
|
||||
<PageTitle>Groceries – New Transaction Item</PageTitle>
|
||||
|
||||
<h1>New Transaction Item</h1>
|
||||
|
||||
<div class="form-field">
|
||||
@Transaction.CreatedAt.ToShortDateString() @Transaction.CreatedAt.ToLongTimeString() – @store
|
||||
</div>
|
||||
|
||||
<TransactionItemForm TransactionItem="TransactionItem">
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit">Add</button>
|
||||
<a class="button" href="/transaction/new/items">Cancel</a>
|
||||
</div>
|
||||
</TransactionItemForm>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Transaction Transaction { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public TransactionItem? TransactionItem { get; set; }
|
||||
|
||||
private string store = string.Empty;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
store = await DbContext.Stores
|
||||
.Where(store => store.Id == Transaction.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
@using Groceries.Data
|
||||
|
||||
@model (Transaction Transaction, TransactionItem? TransactionItem)
|
||||
@{
|
||||
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="Model.TransactionItem" />
|
||||
</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>
|
@ -1,31 +1,23 @@
|
||||
@using Groceries.Data;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@model Transaction
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
ViewBag.Title = "New Transaction";
|
||||
@layout Layout
|
||||
|
||||
var store = await dbContext.Stores
|
||||
.Where(store => store.Id == Model.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
@inject AppDbContext DbContext
|
||||
|
||||
var itemIds = Model.Items.Select(item => item.ItemId);
|
||||
var itemNames = await dbContext.Items
|
||||
.Where(item => itemIds.Contains(item.Id))
|
||||
.ToDictionaryAsync(item => item.Id, item => string.Concat(item.Brand, " ", item.Name));
|
||||
}
|
||||
<PageTitle>Groceries – New Transaction</PageTitle>
|
||||
|
||||
<h1>New Transaction</h1>
|
||||
|
||||
<div class="form-field">@Model.CreatedAt.ToShortDateString() @Model.CreatedAt.ToLongTimeString() – @store</div>
|
||||
<div class="form-field">
|
||||
@Transaction.CreatedAt.ToShortDateString() @Transaction.CreatedAt.ToLongTimeString() – @store
|
||||
</div>
|
||||
|
||||
<form method="post" asp-action="NewTransactionItems">
|
||||
<form method="post">
|
||||
<div class="card form-field">
|
||||
<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>
|
||||
<a class="button button--primary" href="/transactions/new/items/new" autofocus data-turbo-frame="modal">New item</a>
|
||||
</div>
|
||||
|
||||
<div class="card__content card__content--table">
|
||||
@ -40,11 +32,11 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var item in Model.Items)
|
||||
@foreach (var item in Transaction.Items)
|
||||
{
|
||||
<tr>
|
||||
<td class="table__cell table__cell--compact">
|
||||
@itemNames[item.ItemId]
|
||||
@itemNames.GetValueOrDefault(item.ItemId)
|
||||
</td>
|
||||
<td class="table__cell table__cell--compact table__cell--numeric">
|
||||
@item.Price.ToString("c")
|
||||
@ -56,7 +48,7 @@
|
||||
@item.Amount.ToString("c")
|
||||
</td>
|
||||
<td class="table__cell table__cell--compact">
|
||||
<a class="link" asp-action="EditTransactionItem" asp-route-id="@item.ItemId" data-turbo-frame="modal">Edit</a>
|
||||
<a class="link" href="/transactions/new/items/edit/@item.ItemId" data-turbo-frame="modal">Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@ -65,7 +57,7 @@
|
||||
<tr>
|
||||
<td class="table__cell table__cell--compact table__cell--total" colspan="3">Subtotal</td>
|
||||
<td class="table__cell table__cell--compact table__cell--numeric table__cell--total">
|
||||
@Model.Items.Sum(item => item.Amount).ToString("c")
|
||||
@Transaction.Items.Sum(item => item.Amount).ToString("c")
|
||||
</td>
|
||||
<td class="table__cell table__cell--compact"></td>
|
||||
</tr>
|
||||
@ -75,7 +67,28 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit" @(Model.Items.Count == 0 ? "disabled" : "")>Next</button>
|
||||
<a class="button" asp-action="NewTransaction">Back</a>
|
||||
<button class="button button--primary" type="submit" disabled="@(Transaction.Items.Count == 0)">Next</button>
|
||||
<a class="button" href="/transactions/new">Back</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Transaction Transaction { get; set; }
|
||||
|
||||
private string store = string.Empty;
|
||||
private Dictionary<Guid, string> itemNames = new();
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
store = await DbContext.Stores
|
||||
.Where(store => store.Id == Transaction.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
|
||||
var itemIds = Transaction.Items.Select(item => item.ItemId);
|
||||
itemNames = await DbContext.Items
|
||||
.Where(item => itemIds.Contains(item.Id))
|
||||
.ToDictionaryAsync(item => item.Id, item => string.Concat(item.Brand, " ", item.Name));
|
||||
}
|
||||
}
|
@ -1,22 +1,15 @@
|
||||
@using Groceries.Data;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
ViewBag.Title = "New Transaction";
|
||||
@layout Layout
|
||||
|
||||
var datetime = DateTime.Now.ToString("s");
|
||||
@inject AppDbContext DbContext
|
||||
|
||||
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();
|
||||
}
|
||||
<PageTitle>Groceries – New Transaction</PageTitle>
|
||||
|
||||
<h1>New Transaction</h1>
|
||||
|
||||
<form method="post" asp-action="NewTransaction">
|
||||
<form method="post">
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="transactionCreatedAt">Date</label>
|
||||
<div class="form-field__control input">
|
||||
@ -36,6 +29,22 @@
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit">Next</button>
|
||||
<a class="button" asp-action="Index">Cancel</a>
|
||||
<a class="button" href="/transactions?page=1">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@code {
|
||||
private record StoreModel(Guid Id, string Name);
|
||||
|
||||
private string datetime = DateTime.Now.ToString("s");
|
||||
private StoreModel[] stores = [];
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
stores = await DbContext.Stores
|
||||
.OrderBy(store => store.Retailer!.Name)
|
||||
.ThenBy(store => store.Name)
|
||||
.Select(store => new StoreModel(store.Id, string.Concat(store.Retailer!.Name, " ", store.Name)))
|
||||
.ToArrayAsync();
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
@using Groceries.Data;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
|
||||
@model Transaction
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
ViewBag.Title = "New Transaction Promotion";
|
||||
|
||||
var store = await dbContext.Stores
|
||||
.Where(store => store.Id == Model.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
}
|
||||
|
||||
<h1>New Transaction Promotion</h1>
|
||||
|
||||
<div class="form-field">@Model.CreatedAt.ToShortDateString() @Model.CreatedAt.ToShortTimeString() – @store</div>
|
||||
|
||||
<form method="post" asp-action="NewTransactionPromotion">
|
||||
<partial name="_TransactionPromotionForm" model="(Model, (TransactionPromotion?)null)" />
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit">Add</button>
|
||||
<a class="button" asp-action="NewTransactionPromotions">Cancel</a>
|
||||
</div>
|
||||
</form>
|
17
Groceries/Transactions/NewTransactionPromotionModal.razor
Normal file
17
Groceries/Transactions/NewTransactionPromotionModal.razor
Normal file
@ -0,0 +1,17 @@
|
||||
@using Groceries.Data
|
||||
|
||||
@layout Modal
|
||||
|
||||
<SectionContent SectionName="modalTitle">New Transaction Promotion</SectionContent>
|
||||
|
||||
<TransactionPromotionForm Transaction="Transaction" class="card__content" id="newTransactionPromotion" data-action="turbo:submit-end->modal#close" />
|
||||
|
||||
<footer class="card__footer card__footer--shaded row">
|
||||
<button class="button button--primary" type="submit" form="newTransactionPromotion">Add</button>
|
||||
<button class="button" data-action="modal#close">Cancel</button>
|
||||
</footer>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Transaction Transaction { get; set; }
|
||||
}
|
36
Groceries/Transactions/NewTransactionPromotionPage.razor
Normal file
36
Groceries/Transactions/NewTransactionPromotionPage.razor
Normal file
@ -0,0 +1,36 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@layout Layout
|
||||
|
||||
@inject AppDbContext DbContext
|
||||
|
||||
<PageTitle>Groceries – New Transaction Promotion</PageTitle>
|
||||
|
||||
<h1>New Transaction Promotion</h1>
|
||||
|
||||
<div class="form-field">
|
||||
@Transaction.CreatedAt.ToShortDateString() @Transaction.CreatedAt.ToLongTimeString() – @store
|
||||
</div>
|
||||
|
||||
<TransactionPromotionForm Transaction="Transaction">
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit">Add</button>
|
||||
<a class="button" href="/transactions/new/promotions">Cancel</a>
|
||||
</div>
|
||||
</TransactionPromotionForm>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Transaction Transaction { get; set; }
|
||||
|
||||
private string store = string.Empty;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
store = await DbContext.Stores
|
||||
.Where(store => store.Id == Transaction.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
@using Groceries.Data
|
||||
|
||||
@model Transaction
|
||||
@{
|
||||
Layout = "_Modal";
|
||||
ViewBag.Title = "New Transaction Promotion";
|
||||
}
|
||||
|
||||
<form class="card__content" id="newTransactionPromotion" method="post" asp-action="NewTransactionPromotion" data-action="turbo:submit-end->modal#close">
|
||||
<partial name="_TransactionPromotionForm" model="(Model, (TransactionPromotion?)null)" />
|
||||
</form>
|
||||
|
||||
<footer class="card__footer card__footer--shaded row">
|
||||
<button class="button button--primary" type="submit" form="newTransactionPromotion">Add</button>
|
||||
<button class="button" data-action="modal#close">Cancel</button>
|
||||
</footer>
|
@ -1,26 +1,23 @@
|
||||
@using Groceries.Data;
|
||||
@using Microsoft.EntityFrameworkCore;
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@model Transaction
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
ViewBag.Title = "New Transaction";
|
||||
@layout Layout
|
||||
|
||||
var store = await dbContext.Stores
|
||||
.Where(store => store.Id == Model.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
}
|
||||
@inject AppDbContext DbContext
|
||||
|
||||
<PageTitle>Groceries – New Transaction</PageTitle>
|
||||
|
||||
<h1>New Transaction</h1>
|
||||
|
||||
<div class="form-field">@Model.CreatedAt.ToShortDateString() @Model.CreatedAt.ToLongTimeString() – @store</div>
|
||||
<div class="form-field">
|
||||
@Transaction.CreatedAt.ToShortDateString() @Transaction.CreatedAt.ToLongTimeString() – @store
|
||||
</div>
|
||||
|
||||
<form method="post" asp-action="NewTransactionPromotions">
|
||||
<form method="post">
|
||||
<div class="card form-field">
|
||||
<div class="card__header row">
|
||||
<h2 class="row__fill">Promotions</h2>
|
||||
<a class="button button--primary" asp-action="NewTransactionPromotion" autofocus data-turbo-frame="modal">
|
||||
<a class="button button--primary" href="/transactions/new/promotions/new" autofocus data-turbo-frame="modal">
|
||||
New promotion
|
||||
</a>
|
||||
</div>
|
||||
@ -36,20 +33,20 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var promotion in Model.Promotions)
|
||||
@foreach (var promotion in Transaction.Promotions)
|
||||
{
|
||||
<tr>
|
||||
<td class="table__cell table__cell--compact">
|
||||
@promotion.Name
|
||||
</td>
|
||||
<td class="table__cell table__cell--compact table__cell--numeric">
|
||||
@promotion.Items.Sum(item => Model.Items.Single(i => i.ItemId == item.Id).Quantity)
|
||||
@promotion.Items.Sum(item => Transaction.Items.Single(i => i.ItemId == i.ItemId).Quantity)
|
||||
</td>
|
||||
<td class="table__cell table__cell--compact table__cell--numeric">
|
||||
@((-promotion.Amount).ToString("c"))
|
||||
</td>
|
||||
<td class="table__cell table__cell--compact">
|
||||
<a class="link" asp-action="EditTransactionPromotion" asp-route-id="@promotion.Id" data-turbo-frame="modal">Edit</a>
|
||||
<a class="link" href="/transactions/new/promotions/edit/@promotion.Id" data-turbo-frame="modal">Edit</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
@ -58,7 +55,7 @@
|
||||
<tr>
|
||||
<td class="table__cell table__cell--compact table__cell--total" colspan="3">Total</td>
|
||||
<td class="table__cell table__cell--compact table__cell--numeric table__cell--total">
|
||||
@Model.Total.ToString("c")
|
||||
@Transaction.Total.ToString("c")
|
||||
</td>
|
||||
<td class="table__cell table__cell--compact"></td>
|
||||
</tr>
|
||||
@ -68,7 +65,22 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button class="button button--primary" type="submit" @(Model.Items.Count == 0 ? "disabled" : "")>Save</button>
|
||||
<a class="button" asp-action="NewTransactionItems">Back</a>
|
||||
<button class="button button--primary" type="submit" disabled="@(Transaction.Items.Count == 0)">Save</button>
|
||||
<a class="button" href="/transactions/new/items">Back</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Transaction Transaction { get; set; }
|
||||
|
||||
private string store = string.Empty;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
store = await DbContext.Stores
|
||||
.Where(store => store.Id == Transaction.StoreId)
|
||||
.Select(store => string.Concat(store.Retailer!.Name, " ", store.Name))
|
||||
.SingleAsync();
|
||||
}
|
||||
}
|
108
Groceries/Transactions/TransactionItemForm.razor
Normal file
108
Groceries/Transactions/TransactionItemForm.razor
Normal file
@ -0,0 +1,108 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@inject AppDbContext DbContext
|
||||
|
||||
<form method="post" @attributes="AdditionalAttributes">
|
||||
<div data-controller="transaction-item-form">
|
||||
<div class="form-field" data-transaction-item-form-target="barcodeFormField" hidden>
|
||||
<label class="form-field__label" for="transactionItemBarcode">Barcode</label>
|
||||
<div class="form-field__control input">
|
||||
<input type="hidden" name="barcodeFormat" value="@barcode?.Format" data-transaction-item-form-target="barcodeFormat" />
|
||||
<input class="input__control" id="transactionItemBarcode" name="barcodeData" value="@barcode?.BarcodeData" data-transaction-item-form-target="barcodeData" />
|
||||
<button class="input__addon button" formmethod="get" formnovalidate data-action="transaction-item-form#scanBarcode" data-transaction-item-form-target="barcodeButton">
|
||||
@* Barcode scanner icon *@
|
||||
<svg class="icon icon--sm" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M40-120v-200h80v120h120v80H40Zm680 0v-80h120v-120h80v200H720ZM160-240v-480h80v480h-80Zm120 0v-480h40v480h-40Zm120 0v-480h80v480h-80Zm120 0v-480h120v480H520Zm160 0v-480h40v480h-40Zm80 0v-480h40v480h-40ZM40-640v-200h200v80H120v120H40Zm800 0v-120H720v-80h200v200h-80Z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset class="form-field">
|
||||
<legend class="form-field__label">Item</legend>
|
||||
<div class="form-field__control input">
|
||||
<input class="input__control flex-2" name="brand" value="@selectedItem?.Brand" placeholder="Brand" list="itemBrands" autocomplete="off" required autofocus data-action="transaction-item-form#filterNames transaction-item-form#setPriceAndQuantity" data-transaction-item-form-target="brand" />
|
||||
<input class="input__control flex-5" name="name" value="@selectedItem?.Name" placeholder="Name" list="itemNames" autocomplete="off" required data-action="transaction-item-form#filterNames transaction-item-form#setPriceAndQuantity" />
|
||||
|
||||
<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)
|
||||
{
|
||||
<option value="@item.Name" data-transaction-item-form-target="option" data-brand="@item.Brand" data-price="@item.Price" data-quantity="@item.Quantity" />
|
||||
}
|
||||
</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="@price" type="number" min="0" step="0.01" required data-transaction-item-form-target="price" />
|
||||
</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="@quantity" type="number" min="1" required data-transaction-item-form-target="quantity" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ChildContent
|
||||
</form>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public TransactionItem? TransactionItem { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
[Parameter(CaptureUnmatchedValues = true)]
|
||||
public Dictionary<string, object>? AdditionalAttributes { get; set; }
|
||||
|
||||
private record ItemModel(Guid Id, string Brand, string Name, decimal? Price, int? Quantity);
|
||||
|
||||
private ItemBarcode? barcode;
|
||||
|
||||
private ItemModel[] items = [];
|
||||
private ItemModel? selectedItem;
|
||||
|
||||
private decimal? price;
|
||||
private int quantity;
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
barcode = TransactionItem?.Item?.Barcodes.FirstOrDefault();
|
||||
|
||||
items = await DbContext.Items
|
||||
.OrderBy(item => item.Brand)
|
||||
.ThenBy(item => item.Name)
|
||||
.GroupJoin(
|
||||
DbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase),
|
||||
item => item.Id,
|
||||
lastPurchase => lastPurchase.ItemId,
|
||||
(item, purchases) => new { item, purchases })
|
||||
.SelectMany(
|
||||
group => group.purchases.DefaultIfEmpty(),
|
||||
(group, lastPurchase) => new ItemModel(
|
||||
group.item.Id,
|
||||
group.item.Brand,
|
||||
group.item.Name,
|
||||
lastPurchase != null ? lastPurchase.Price : null,
|
||||
lastPurchase != null ? lastPurchase.Quantity : null))
|
||||
.ToArrayAsync();
|
||||
|
||||
selectedItem = items.SingleOrDefault(item => item.Id == TransactionItem?.ItemId);
|
||||
|
||||
price = TransactionItem?.Price >= 0 ? TransactionItem.Price : selectedItem?.Price;
|
||||
quantity = TransactionItem?.Quantity >= 1 ? TransactionItem.Quantity : (selectedItem?.Quantity ?? 1);
|
||||
}
|
||||
}
|
64
Groceries/Transactions/TransactionPromotionForm.razor
Normal file
64
Groceries/Transactions/TransactionPromotionForm.razor
Normal file
@ -0,0 +1,64 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@inject AppDbContext DbContext
|
||||
|
||||
<form method="post" @attributes="AdditionalAttributes">
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="transactionPromotionName">Name</label>
|
||||
<div class="form-field__control input">
|
||||
<input class="input__control" id="transactionPromotionName" name="name" value="@Promotion?.Name" required autofocus />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="transactionPromotionAmount">Amount</label>
|
||||
<div class="form-field__control input">
|
||||
@*<span class="input__inset">@CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol</span>*@
|
||||
<input class="input__control" id="transactionPromotionAmount" name="amount" value="@Promotion?.Amount" type="number" min="0" step="0.01" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="transactionPromotionItemIds">
|
||||
Items <span class="form-field__corner-hint">Optional</span>
|
||||
</label>
|
||||
<select class="form-field__control select" id="transactionPromotionItemIds" name="itemIds" multiple>
|
||||
@foreach (var item in Transaction.Items)
|
||||
{
|
||||
<option value="@item.ItemId" selected="@selectedItemIds.Contains(item.ItemId)">
|
||||
@itemNames.GetValueOrDefault(item.ItemId)
|
||||
</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@ChildContent
|
||||
</form>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
public required Transaction Transaction { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public TransactionPromotion? Promotion { get; set; }
|
||||
|
||||
[Parameter]
|
||||
public RenderFragment? ChildContent { get; set; }
|
||||
|
||||
[Parameter(CaptureUnmatchedValues = true)]
|
||||
public Dictionary<string, object>? AdditionalAttributes { get; set; }
|
||||
|
||||
private Guid[] selectedItemIds = [];
|
||||
private Dictionary<Guid, string> itemNames = new();
|
||||
|
||||
protected override async Task OnParametersSetAsync()
|
||||
{
|
||||
selectedItemIds = Promotion?.Items.Select(item => item.Id).ToArray() ?? [];
|
||||
|
||||
var itemIds = Transaction.Items.Select(item => item.ItemId);
|
||||
itemNames = await DbContext.Items
|
||||
.Where(item => itemIds.Contains(item.Id))
|
||||
.ToDictionaryAsync(item => item.Id, item => string.Concat(item.Brand, " ", item.Name));
|
||||
}
|
||||
}
|
@ -23,48 +23,48 @@ public class TransactionsController : Controller
|
||||
}
|
||||
|
||||
[HttpGet("new")]
|
||||
public IActionResult NewTransaction()
|
||||
public IResult NewTransaction()
|
||||
{
|
||||
return View();
|
||||
return new RazorComponentResult<NewTransactionPage>();
|
||||
}
|
||||
|
||||
[HttpPost("new")]
|
||||
public IActionResult NewTransaction(DateTime createdAt, Guid storeId)
|
||||
public IResult NewTransaction(DateTime createdAt, Guid storeId)
|
||||
{
|
||||
var transaction = new Transaction(createdAt.ToUniversalTime(), storeId);
|
||||
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
|
||||
|
||||
return RedirectToAction(nameof(NewTransactionItems));
|
||||
return Results.LocalRedirect("/transactions/new/items");
|
||||
}
|
||||
|
||||
[HttpGet("new/items")]
|
||||
public IActionResult NewTransactionItems()
|
||||
public IResult NewTransactionItems()
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
return View(transaction);
|
||||
return new RazorComponentResult<NewTransactionItemsPage>(new { Transaction = transaction });
|
||||
}
|
||||
|
||||
[HttpPost("new/items")]
|
||||
public IActionResult PostNewTransactionItems()
|
||||
public IResult PostNewTransactionItems()
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is null)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
return RedirectToAction(nameof(NewTransactionPromotions));
|
||||
return Results.LocalRedirect("/transactions/new/promotions");
|
||||
}
|
||||
|
||||
[HttpGet("new/items/new")]
|
||||
public async Task<IActionResult> NewTransactionItem(long? barcodeData, string? barcodeFormat)
|
||||
public async Task<IResult> NewTransactionItem(long? barcodeData, string? barcodeFormat)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
TransactionItem? transactionItem = null;
|
||||
@ -82,18 +82,18 @@ public class TransactionsController : Controller
|
||||
transactionItem = new TransactionItem(item.Id, decimal.MinValue, int.MinValue) { Item = item };
|
||||
}
|
||||
|
||||
var model = (transaction, transactionItem);
|
||||
var parameters = new { Transaction = transaction, TransactionItem = transactionItem };
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? View($"{nameof(NewTransactionItem)}_Modal", model)
|
||||
: View(model);
|
||||
? new RazorComponentResult<NewTransactionItemModal>(parameters)
|
||||
: new RazorComponentResult<NewTransactionItemPage>(parameters);
|
||||
}
|
||||
|
||||
[HttpPost("new/items/new")]
|
||||
public async Task<IActionResult> NewTransactionItem(string brand, string name, decimal price, int quantity, long? barcodeData, string? barcodeFormat)
|
||||
public async Task<IResult> NewTransactionItem(string brand, string name, decimal price, int quantity, long? barcodeData, string? barcodeFormat)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
var itemId = await dbContext.Items
|
||||
@ -124,42 +124,42 @@ public class TransactionsController : Controller
|
||||
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? NoContent()
|
||||
: RedirectToAction(nameof(NewTransactionItems));
|
||||
? Results.NoContent()
|
||||
: Results.LocalRedirect("/transactions/new/items");
|
||||
}
|
||||
|
||||
[HttpGet("new/items/edit/{id}")]
|
||||
public IActionResult EditTransactionItem(Guid id)
|
||||
public IResult EditTransactionItem(Guid id)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
var transactionItem = transaction.Items.SingleOrDefault(item => item.ItemId == id);
|
||||
if (transactionItem == null)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransactionItems));
|
||||
return Results.LocalRedirect("/transactions/new/items");
|
||||
}
|
||||
|
||||
var model = (transaction, transactionItem);
|
||||
var parameters = new { Transaction = transaction, TransactionItem = transactionItem };
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? View($"{nameof(EditTransactionItem)}_Modal", model)
|
||||
: View(model);
|
||||
? new RazorComponentResult<EditTransactionItemModal>(parameters)
|
||||
: new RazorComponentResult<EditTransactionItemPage>(parameters);
|
||||
}
|
||||
|
||||
[HttpPost("new/items/edit/{id}")]
|
||||
public async Task<IActionResult> EditTransactionItem(Guid id, string brand, string name, decimal price, int quantity)
|
||||
public async Task<IResult> 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));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
var transactionItem = transaction.Items.SingleOrDefault(item => item.ItemId == id);
|
||||
if (transactionItem == null)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransactionItems));
|
||||
return Results.LocalRedirect("/transactions/new/items");
|
||||
}
|
||||
|
||||
var itemId = await dbContext.Items
|
||||
@ -182,16 +182,16 @@ public class TransactionsController : Controller
|
||||
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? NoContent()
|
||||
: RedirectToAction(nameof(NewTransactionItems));
|
||||
? Results.NoContent()
|
||||
: Results.LocalRedirect("/transactions/new/items");
|
||||
}
|
||||
|
||||
[HttpPost("new/items/delete/{id}")]
|
||||
public IActionResult DeleteTransactionItem(Guid id)
|
||||
public IResult DeleteTransactionItem(Guid id)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
var transactionItem = transaction.Items.SingleOrDefault(item => item.ItemId == id);
|
||||
@ -203,27 +203,27 @@ public class TransactionsController : Controller
|
||||
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? NoContent()
|
||||
: RedirectToAction(nameof(NewTransactionItems));
|
||||
? Results.NoContent()
|
||||
: Results.LocalRedirect("/transactions/new/items");
|
||||
}
|
||||
|
||||
[HttpGet("new/promotions")]
|
||||
public IActionResult NewTransactionPromotions()
|
||||
public IResult NewTransactionPromotions()
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
return View(transaction);
|
||||
return new RazorComponentResult<NewTransactionPromotionsPage>(new { Transaction = transaction });
|
||||
}
|
||||
|
||||
[HttpPost("new/promotions")]
|
||||
public async Task<IActionResult> PostNewTransactionPromotions()
|
||||
public async Task<IResult> PostNewTransactionPromotions()
|
||||
{
|
||||
if (TempData["NewTransaction"] is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
// Work around EF trying to insert items by explicitly tracking them as unchanged
|
||||
@ -235,28 +235,29 @@ public class TransactionsController : Controller
|
||||
dbContext.Transactions.Add(transaction);
|
||||
await dbContext.SaveChangesAsync();
|
||||
|
||||
return RedirectToAction(nameof(Index), new { page = 1 });
|
||||
return Results.LocalRedirect("/transactions?page=1");
|
||||
}
|
||||
|
||||
[HttpGet("new/promotions/new")]
|
||||
public IActionResult NewTransactionPromotion()
|
||||
public IResult NewTransactionPromotion()
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
var parameters = new { Transaction = transaction };
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? View($"{nameof(NewTransactionPromotion)}_Modal", transaction)
|
||||
: View(transaction);
|
||||
? new RazorComponentResult<NewTransactionPromotionModal>(parameters)
|
||||
: new RazorComponentResult<NewTransactionPromotionPage>(parameters);
|
||||
}
|
||||
|
||||
[HttpPost("new/promotions/new")]
|
||||
public IActionResult NewTransactionPromotion(string name, decimal amount, Guid[] itemIds)
|
||||
public IResult NewTransactionPromotion(string name, decimal amount, Guid[] itemIds)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
// TODO: Handle promotion already in transaction - merge, replace, error?
|
||||
@ -267,42 +268,42 @@ public class TransactionsController : Controller
|
||||
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? NoContent()
|
||||
: RedirectToAction(nameof(NewTransactionPromotions));
|
||||
? Results.NoContent()
|
||||
: Results.LocalRedirect("/transactions/new/promotions");
|
||||
}
|
||||
|
||||
[HttpGet("new/promotions/edit/{id}")]
|
||||
public IActionResult EditTransactionPromotion(Guid id)
|
||||
public IResult EditTransactionPromotion(Guid id)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
var promotion = transaction.Promotions.SingleOrDefault(promotion => promotion.Id == id);
|
||||
if (promotion == null)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransactionPromotions));
|
||||
return Results.LocalRedirect("/transactions/new/promotions");
|
||||
}
|
||||
|
||||
var model = (transaction, promotion);
|
||||
var parameters = new { Transaction = transaction, Promotion = promotion };
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? View($"{nameof(EditTransactionPromotion)}_Modal", model)
|
||||
: View(model);
|
||||
? new RazorComponentResult<EditTransactionPromotionModal>(parameters)
|
||||
: new RazorComponentResult<EditTransactionPromotionPage>(parameters);
|
||||
}
|
||||
|
||||
[HttpPost("new/promotions/edit/{id}")]
|
||||
public IActionResult EditTransactionPromotion(Guid id, string name, decimal amount, Guid[] itemIds)
|
||||
public IResult EditTransactionPromotion(Guid id, string name, decimal amount, Guid[] itemIds)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
var promotion = transaction.Promotions.SingleOrDefault(promotion => promotion.Id == id);
|
||||
if (promotion == null)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransactionPromotions));
|
||||
return Results.LocalRedirect("/transactions/new/promotions");
|
||||
}
|
||||
|
||||
promotion.Name = name;
|
||||
@ -312,16 +313,16 @@ public class TransactionsController : Controller
|
||||
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? NoContent()
|
||||
: RedirectToAction(nameof(NewTransactionPromotions));
|
||||
? Results.NoContent()
|
||||
: Results.LocalRedirect("/transactions/new/promotions");
|
||||
}
|
||||
|
||||
[HttpPost("new/promotions/delete/{id}")]
|
||||
public IActionResult DeleteTransactionPromotion(Guid id)
|
||||
public IResult DeleteTransactionPromotion(Guid id)
|
||||
{
|
||||
if (TempData.Peek("NewTransaction") is not string json || JsonSerializer.Deserialize<Transaction>(json) is not Transaction transaction)
|
||||
{
|
||||
return RedirectToAction(nameof(NewTransaction));
|
||||
return Results.LocalRedirect("/transactions/new");
|
||||
}
|
||||
|
||||
var promotion = transaction.Promotions.SingleOrDefault(promotion => promotion.Id == id);
|
||||
@ -333,7 +334,7 @@ public class TransactionsController : Controller
|
||||
TempData["NewTransaction"] = JsonSerializer.Serialize(transaction);
|
||||
|
||||
return Request.IsTurboFrameRequest("modal")
|
||||
? NoContent()
|
||||
: RedirectToAction(nameof(NewTransactionPromotions));
|
||||
? Results.NoContent()
|
||||
: Results.LocalRedirect("/transactions/new/promotions");
|
||||
}
|
||||
}
|
||||
|
@ -1,84 +0,0 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@model TransactionItem?
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
var items = await dbContext.Items
|
||||
.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
|
||||
{
|
||||
group.item.Id,
|
||||
group.item.Brand,
|
||||
group.item.Name,
|
||||
Price = lastPurchase != null ? lastPurchase.Price : (decimal?)null,
|
||||
Quantity = lastPurchase != null ? lastPurchase.Quantity : (int?)null,
|
||||
})
|
||||
.ToArrayAsync();
|
||||
|
||||
var selectedItem = items.SingleOrDefault(item => item.Id == Model?.ItemId);
|
||||
|
||||
var barcode = Model?.Item?.Barcodes.FirstOrDefault();
|
||||
|
||||
var price = Model?.Price >= 0 ? Model.Price : selectedItem?.Price;
|
||||
var quantity = Model?.Quantity >= 1 ? Model.Quantity : (selectedItem?.Quantity ?? 1);
|
||||
}
|
||||
|
||||
<div data-controller="transaction-item-form">
|
||||
<div class="form-field" data-transaction-item-form-target="barcodeFormField" hidden>
|
||||
<label class="form-field__label" for="transactionItemBarcode">Barcode</label>
|
||||
<div class="form-field__control input">
|
||||
<input type="hidden" name="barcodeFormat" value="@barcode?.Format" data-transaction-item-form-target="barcodeFormat" />
|
||||
<input class="input__control" id="transactionItemBarcode" name="barcodeData" value="@barcode?.BarcodeData" data-transaction-item-form-target="barcodeData" />
|
||||
<button class="input__addon button" formmethod="get" formnovalidate data-action="transaction-item-form#scanBarcode" data-transaction-item-form-target="barcodeButton">
|
||||
@* Barcode scanner icon *@
|
||||
<svg class="icon icon--sm" xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M40-120v-200h80v120h120v80H40Zm680 0v-80h120v-120h80v200H720ZM160-240v-480h80v480h-80Zm120 0v-480h40v480h-40Zm120 0v-480h80v480h-80Zm120 0v-480h120v480H520Zm160 0v-480h40v480h-40Zm80 0v-480h40v480h-40ZM40-640v-200h200v80H120v120H40Zm800 0v-120H720v-80h200v200h-80Z" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset class="form-field">
|
||||
<legend class="form-field__label">Item</legend>
|
||||
<div class="form-field__control input">
|
||||
<input class="input__control flex-2" name="brand" value="@selectedItem?.Brand" placeholder="Brand" list="itemBrands" autocomplete="off" required autofocus data-action="transaction-item-form#filterNames transaction-item-form#setPriceAndQuantity" data-transaction-item-form-target="brand" />
|
||||
<input class="input__control flex-5" name="name" value="@selectedItem?.Name" placeholder="Name" list="itemNames" autocomplete="off" required data-action="transaction-item-form#setPriceAndQuantity" />
|
||||
|
||||
<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)
|
||||
{
|
||||
<option value="@item.Name" data-transaction-item-form-target="option" data-brand="@item.Brand" data-price="@item.Price" data-quantity="@item.Quantity" />
|
||||
}
|
||||
</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="@price" type="number" min="0" step="0.01" required data-transaction-item-form-target="price" />
|
||||
</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="@quantity" type="number" min="1" required data-transaction-item-form-target="quantity" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -1,41 +0,0 @@
|
||||
@using Groceries.Data
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
|
||||
@model (Transaction Transaction, TransactionPromotion? Promotion)
|
||||
@inject AppDbContext dbContext
|
||||
@{
|
||||
var selectedItemIds = Model.Promotion?.Items.Select(item => item.Id).ToArray() ?? Array.Empty<Guid>();
|
||||
|
||||
var itemIds = Model.Transaction.Items.Select(item => item.ItemId);
|
||||
var itemNames = await dbContext.Items
|
||||
.Where(item => itemIds.Contains(item.Id))
|
||||
.ToDictionaryAsync(item => item.Id, item => string.Concat(item.Brand, " ", item.Name));
|
||||
}
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="transactionPromotionName">Name</label>
|
||||
<div class="form-field__control input">
|
||||
<input class="input__control" id="transactionPromotionName" name="name" value="@Model.Promotion?.Name" required autofocus />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="transactionPromotionAmount">Amount</label>
|
||||
<div class="form-field__control input">
|
||||
@*<span class="input__inset">@CultureInfo.CurrentCulture.NumberFormat.CurrencySymbol</span>*@
|
||||
<input class="input__control" id="transactionPromotionAmount" name="amount" value="@Model.Promotion?.Amount" type="number" min="0" step="0.01" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field">
|
||||
<label class="form-field__label" for="transactionPromotionItemIds">
|
||||
Items
|
||||
<span class="form-field__corner-hint">Optional</span>
|
||||
</label>
|
||||
<select class="form-field__control select" id="transactionPromotionItemIds" name="itemIds" multiple>
|
||||
@foreach (var item in Model.Transaction.Items)
|
||||
{
|
||||
<option value="@item.ItemId" selected="@selectedItemIds.Contains(item.ItemId)">@itemNames[item.ItemId]</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
@ -1,3 +0,0 @@
|
||||
@using Groceries
|
||||
@namespace Groceries.Views
|
||||
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
|
@ -1,3 +0,0 @@
|
||||
@{
|
||||
Layout = ViewBag.RenderingToTurboStream == true ? null : "_Layout";
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user