Refactor New Transaction pages/modals to Razor components
This commit is contained in:
@ -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>
|
Reference in New Issue
Block a user