-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from aforesti/feature/cashierUI
Cashier UI remodel
- Loading branch information
Showing
4 changed files
with
160 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,5 +10,6 @@ | |
</LayoutView> | ||
</NotFound> | ||
</Router> | ||
<script src="https://cdn.tailwindcss.com"></script> | ||
|
||
<AntContainer /> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,23 @@ | ||
namespace NCafe.Web.Models; | ||
|
||
public class Order | ||
{ | ||
public Guid Id { get; set; } | ||
public string CustomerName { get; set; } | ||
public List<OrderItem> Items { get; set; } = new(); | ||
public decimal Total => Items.Sum(x => x.Total); | ||
public void Clear() | ||
{ | ||
Items.Clear(); | ||
Id = Guid.Empty; | ||
CustomerName = string.Empty; | ||
} | ||
} | ||
|
||
public class OrderItem | ||
{ | ||
public Guid ProductId { get; set; } | ||
public int Quantity { get; set; } | ||
public decimal Price { get; set; } | ||
public decimal Total => Quantity * Price; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
@page "/cashier" | ||
@page "/cashier" | ||
@using NCafe.Web.Models | ||
@inject HttpClient Http | ||
@inject IConfiguration Configuration | ||
|
@@ -7,181 +7,172 @@ | |
|
||
<PageTitle>Cashier - NCafe</PageTitle> | ||
|
||
<h2>New Order</h2> | ||
|
||
@if (products == null) | ||
@if (_products == null) | ||
{ | ||
<Spin /> | ||
return; | ||
} | ||
else if (!products.Any()) | ||
|
||
@if (!_products.Any()) | ||
{ | ||
<Result Title="You need products in order to place an order."> | ||
<Extra> | ||
<Button Type="@ButtonType.Primary" OnClick="GoAdmin">Go Admin</Button> | ||
</Extra> | ||
</Result> | ||
return; | ||
} | ||
else | ||
{ | ||
if (cashierState == CashierState.Idle) | ||
{ | ||
<Button Type="@ButtonType.Primary" OnClick="CreateOrder">Create Order</Button> | ||
} | ||
else if (cashierState == CashierState.OrderCreated) | ||
{ | ||
<Form Loading="isLoading" | ||
Model="@orderItem" | ||
OnFinish="AddOrderItem" | ||
LabelColSpan="8" | ||
WrapperColSpan="16"> | ||
<FormItem Label="Product"> | ||
<AutoComplete @ref="productAutoComplete" | ||
TOption="Product" | ||
@bind-Value="@value" | ||
OnSelectionChange="OnSelectionChange" | ||
Options="products" | ||
Placeholder="Product..." /> | ||
</FormItem> | ||
<FormItem Label="Quantity"> | ||
<AntDesign.InputNumber @bind-Value="@context.Quantity" /> | ||
</FormItem> | ||
<FormItem WrapperColOffset="8" WrapperColSpan="16"> | ||
<Button Type="@ButtonType.Primary" HtmlType="submit"> | ||
Add Item | ||
</Button> | ||
</FormItem> | ||
</Form> | ||
|
||
<Card> | ||
<Input Placeholder="Customer Name" @bind-Value="@customer" /> | ||
<Button Type="@ButtonType.Primary" HtmlType="submit" OnClick="PlaceOrder"> | ||
Place Order | ||
</Button> | ||
</Card> | ||
} | ||
} | ||
<div class="grid grid-cols-[1fr,2fr] h-full"> | ||
<div class="grid grid-rows-[3rem,auto,3rem,5rem]"> | ||
<h2 class="font-bold text-xl">Order Summary</h2> | ||
<ul> | ||
@foreach (var item in _order.Items) | ||
{ | ||
<li class="grid grid-cols-2 p-2 border-b-dashed border-b border-dashed last:border-b-0 text-lg"> | ||
<span>@item.Quantity x @(_products.FirstOrDefault(p => p.Id == item.ProductId)?.Name)</span> | ||
<span class="text-right">@(item.Total.ToString("C"))</span> | ||
</li> | ||
} | ||
</ul> | ||
@if (_order.Items.Any()) { | ||
<div class="grid grid-cols-2 p-2 text-right font-bold text-xl"> | ||
<span>Total</span> | ||
<span>@(_order.Total.ToString("C"))</span> | ||
</div> | ||
|
||
<button class="m-2 shadow border-black border-1 rounded bg-sky-800 text-white text-xl font-bold active:bg-sky-600" | ||
type="@ButtonType.Primary" @onclick="ShowModal">Place Order</button> | ||
} | ||
</div> | ||
<div class="grid grid-cols-4 grid-rows-4 gap-2 p-4"> | ||
@foreach (var product in _products) | ||
{ | ||
<button type="button" @onclick="() => AddItemToOrder(product.Id)" | ||
class="shadow hover:shadow-lg p-2 text-xl border-solid border-black border-1 active:bg-sky-600 hover:bg-sky-800 hover:text-white hover:border-slate-500 rounded"> | ||
<strong>@product.Name<br/>@product.Price.ToString("C")</strong> | ||
</button> | ||
} | ||
</div> | ||
|
||
@code { | ||
private bool isLoading; | ||
|
||
private CashierState cashierState = CashierState.Idle; | ||
</div> | ||
|
||
private Product[] products; | ||
<NCafe.Web.Shared.Modal IsVisible="@_isModalVisible" OnClose="HideModal" OnCancel="CancelOrder" Title="Confirm order" OnConfirm="PlaceOrder"> | ||
<Form @onsubmit="PlaceOrder"> | ||
<Form.Item Label="Customer name"> | ||
Check failure on line 62 in src/UI/NCafe.Web/Pages/Cashier/Index.razor GitHub Actions / build
Check failure on line 62 in src/UI/NCafe.Web/Pages/Cashier/Index.razor GitHub Actions / build
|
||
<Input required @bind-Value="_order.CustomerName" /> | ||
</Form.Item> | ||
</Form> | ||
<label for="customer-name" class="block text">Customer name</label> | ||
<input autofocus type="text" @bind="_order.CustomerName" class="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm"/> | ||
|
||
private AutoComplete<Product> productAutoComplete; | ||
private string value; | ||
<div class="grid grid-cols-2 p-2 text-right font-bold text-xl"> | ||
<span>Total</span> | ||
<span>@(_order.Total.ToString("C"))</span> | ||
</div> | ||
</NCafe.Web.Shared.Modal> | ||
|
||
private Guid orderId; | ||
private string customer = string.Empty; | ||
private OrderItem orderItem = new OrderItem { Quantity = 1 }; | ||
@code { | ||
|
||
private string baseAddress; | ||
private Product[] _products; | ||
private readonly Order _order = new(); | ||
private string _baseAddress; | ||
private bool _isModalVisible; | ||
|
||
protected override async Task OnInitializedAsync() | ||
{ | ||
baseAddress = Configuration["CashierBaseAddress"]; | ||
_baseAddress = Configuration["CashierBaseAddress"]; | ||
|
||
products = await Http.GetFromJsonAsync<Product[]>($"{baseAddress}/products"); | ||
_products = await Http.GetFromJsonAsync<Product[]>($"{_baseAddress}/products"); | ||
} | ||
|
||
void OnSelectionChange(AutoCompleteOption item) | ||
void ShowModal() => _isModalVisible = true; | ||
void HideModal() => _isModalVisible = false; | ||
|
||
void CancelOrder() | ||
{ | ||
if (item.Value is Product product) | ||
{ | ||
orderItem.ProductId = product.Id; | ||
} | ||
_order.Clear(); | ||
_isModalVisible = false; | ||
} | ||
|
||
async Task CreateOrder() | ||
async Task AddItemToOrder(Guid productId) | ||
{ | ||
isLoading = true; | ||
if (_order.Id == Guid.Empty) | ||
{ | ||
await CreateOrder(); | ||
} | ||
|
||
var product = _products.FirstOrDefault(p => p.Id == productId); | ||
var item = _order.Items.FirstOrDefault(i => i.ProductId == productId); | ||
if (item == null) | ||
{ | ||
item = new OrderItem { ProductId = productId, Price = product?.Price ?? 0, Quantity = 1 }; | ||
_order.Items.Add(item); | ||
} else | ||
{ | ||
item.Quantity++; | ||
} | ||
|
||
try | ||
{ | ||
var response = await Http.PostAsJsonAsync($"{baseAddress}/orders", new { CreatedBy = "cashier-1" }); | ||
var response = await Http.PostAsJsonAsync( | ||
$"{_baseAddress}/orders/add-item", | ||
new { OrderId = _order.Id, item.ProductId, item.Quantity }); | ||
response.EnsureSuccessStatusCode(); | ||
|
||
orderId = await response.Content.ReadFromJsonAsync<Guid>(); | ||
|
||
cashierState = CashierState.OrderCreated; | ||
} | ||
catch (Exception ex) | ||
{ | ||
_ = NotificationService.Open(new NotificationConfig | ||
{ | ||
Message = "Error", | ||
Duration = 0, | ||
Description = $"There was an error while attempting to save the product: {ex.Message}.", | ||
NotificationType = NotificationType.Error | ||
}); | ||
} | ||
finally | ||
{ | ||
isLoading = false; | ||
{ | ||
Message = "Error", | ||
Duration = 0, | ||
Description = $"There was an error while attempting to add item: {ex.Message}.", | ||
NotificationType = NotificationType.Error | ||
}); | ||
} | ||
} | ||
|
||
async Task AddOrderItem() | ||
async Task CreateOrder() | ||
{ | ||
isLoading = true; | ||
|
||
try | ||
{ | ||
var response = await Http.PostAsJsonAsync($"{baseAddress}/orders/add-item", new { OrderId = orderId, orderItem.ProductId, orderItem.Quantity }); | ||
var response = await Http.PostAsJsonAsync( | ||
$"{_baseAddress}/orders", new { CreatedBy = "cashier-1" }); | ||
response.EnsureSuccessStatusCode(); | ||
|
||
_ = NotificationService.Open(new NotificationConfig | ||
{ | ||
Message = "Item Added", | ||
Description = $"{orderItem.Quantity}x {productAutoComplete.SelectedValue} was added.", | ||
NotificationType = NotificationType.Success | ||
}); | ||
|
||
orderItem.Quantity = 1; | ||
|
||
// The next 2 lines will reset the product selection | ||
value = ""; | ||
productAutoComplete.SelectedValue = null; | ||
_order.Id = await response.Content.ReadFromJsonAsync<Guid>(); | ||
} | ||
catch (Exception ex) | ||
{ | ||
_ = NotificationService.Open(new NotificationConfig | ||
{ | ||
Message = "Error", | ||
Duration = 0, | ||
Description = $"There was an error while attempting to add item: {ex.Message}.", | ||
Description = $"There was an error while attempting to create the order: {ex.Message}.", | ||
NotificationType = NotificationType.Error | ||
}); | ||
} | ||
finally | ||
{ | ||
isLoading = false; | ||
} | ||
} | ||
|
||
async Task PlaceOrder() | ||
{ | ||
isLoading = true; | ||
|
||
try | ||
{ | ||
var response = await Http.PostAsJsonAsync($"{baseAddress}/orders/place", new { OrderId = orderId, Customer = new { Name = customer } }); | ||
var response = await Http.PostAsJsonAsync( | ||
$"{_baseAddress}/orders/place", | ||
new { OrderId = _order.Id, Customer = new { Name = _order.CustomerName } }); | ||
response.EnsureSuccessStatusCode(); | ||
|
||
_ = NotificationService.Open(new NotificationConfig | ||
{ | ||
Message = "Order Created", | ||
Description = $"Order created successfully.", | ||
Description = "Order created successfully.", | ||
NotificationType = NotificationType.Success | ||
}); | ||
|
||
orderId = Guid.Empty; | ||
customer = string.Empty; | ||
cashierState = CashierState.Idle; | ||
_order.Clear(); | ||
_isModalVisible = false; | ||
|
||
// The next 2 lines will reset the product selection | ||
value = ""; | ||
productAutoComplete.SelectedValue = null; | ||
} | ||
catch (Exception ex) | ||
{ | ||
|
@@ -193,20 +184,7 @@ else | |
NotificationType = NotificationType.Error | ||
}); | ||
} | ||
finally | ||
{ | ||
isLoading = false; | ||
} | ||
} | ||
|
||
void GoAdmin() | ||
{ | ||
NavigationManager.NavigateTo("admin"); | ||
} | ||
|
||
public enum CashierState | ||
{ | ||
Idle, | ||
OrderCreated | ||
} | ||
void GoAdmin() => NavigationManager.NavigateTo("admin"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
@code { | ||
[Parameter] | ||
public bool IsVisible { get; set; } | ||
|
||
[Parameter] | ||
public EventCallback OnClose { get; set; } | ||
|
||
[Parameter] | ||
public EventCallback OnConfirm { get; set; } | ||
|
||
[Parameter] | ||
public EventCallback OnCancel { get; set; } | ||
|
||
[Parameter] | ||
public string Title { get; set; } | ||
|
||
[Parameter] | ||
public RenderFragment ChildContent { get; set; } | ||
|
||
private void Close() => OnClose.InvokeAsync(); | ||
private void Confirm() => OnConfirm.InvokeAsync(); | ||
private void Cancel() => OnCancel.InvokeAsync(); | ||
} | ||
|
||
@if (IsVisible) | ||
{ | ||
<div class="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50"> | ||
<div class="bg-white rounded-lg shadow-lg w-1/3"> | ||
<div class="border-b px-4 py-2 flex justify-between items-center"> | ||
<h5 class="font-bold">@Title</h5> | ||
<button type="button" class="text-black close" @onclick="Close">×</button> | ||
</div> | ||
<div class="p-4"> | ||
@ChildContent | ||
</div> | ||
<div class="border-t px-4 py-2 flex justify-end gap-2"> | ||
<button type="button" class="bg-red-300 text-white px-4 py-2 rounded" @onclick="Cancel">Cancel</button> | ||
<button type="button" class="bg-gray-500 text-white px-4 py-2 rounded" @onclick="Confirm">Confirm</button> | ||
</div> | ||
</div> | ||
</div> | ||
} |