WIP - order draft with state

This commit is contained in:
Frede Hundewadt 2022-03-15 12:04:15 +01:00
parent 4c37b15f29
commit 734dd2768a
25 changed files with 289 additions and 470 deletions

View file

@ -2,8 +2,8 @@ namespace Wonky.Client;
public class AppId
{
public string Version { get; set; } = "0.2.1";
public string Name { get; set; } = "Inno Client";
public string Version { get; set; } = "0.2.2";
public string Name { get; set; } = "Wonky Online";
public bool IsBeta { get; set; } = false;
}

View file

@ -0,0 +1,3 @@
<div>
<img class="spinner px-3" src="loader.gif" alt="Afventer svar fra service ..."/>Afventer svar fra service ...
</div>

View file

@ -21,8 +21,7 @@ namespace Wonky.Client.Components
{
public partial class CompanySortColumn
{
[Parameter]
public EventCallback<string> OnSortChanged { get; set; }
[Parameter] public EventCallback<string> OnSortChanged { get; set; }
private string SortColumn { get; set; } = "name";
private async Task ApplySort(ChangeEventArgs eventArgs)

View file

@ -67,5 +67,6 @@
}
else
{
<div><img class="spinner" src="loader.gif" alt="Vent venligst..."/> Henter data...</div>
<AppSpinner/>
}

View file

@ -57,5 +57,5 @@
}
else
{
<div><img class="spinner" src="loader.gif" alt="Vent venligst..."/> Henter data...</div>
<AppSpinner/>
}

View file

@ -16,6 +16,7 @@
using Blazored.Toast.Services;
using Microsoft.AspNetCore.Components;
using Wonky.Entity.DTO;
using Wonky.Entity.Models;
namespace Wonky.Client.Components;

View file

@ -1,69 +0,0 @@
@using Microsoft.AspNetCore.Components
@*
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the Affero GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// Affero GNU General Public License for more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
*@
<EditForm EditContext="_editContext" OnValidSubmit="CreateLine" class="card card-body bg-light mt-5">
<DataAnnotationsValidator />
<div class="row mb-2">
<label for="sku" class="col-md-2 col-form-label">Varenummer</label>
<div class="col-md-10">
<InputText id="sku" class="form-control" @bind-Value="_orderLine.Sku" />
<ValidationMessage For="@(() => _orderLine.Sku)"></ValidationMessage>
</div>
</div>
<div class="row mb-2">
<label for="text" class="col-md-2 col-form-label">Tekst</label>
<div class="col-md-10">
<InputText id="text" class="form-control" @bind-Value="_orderLine.Text" />
<ValidationMessage For="@(() => _orderLine.Text)"></ValidationMessage>
</div>
</div>
<div class="row mb-2">
<label for="qty" class="col-md-2 col-form-label">Antal</label>
<div class="col-md-10">
<InputNumber id="qty" class="form-control" @bind-Value="_orderLine.Qty" />
<ValidationMessage For="@(() => _orderLine.Qty)"></ValidationMessage>
</div>
</div>
<div class="row mb-2">
<label for="price" class="col-md-2 col-form-label">Stk.pris</label>
<div class="col-md-10">
<InputNumber id="price" class="form-control" @bind-Value="_orderLine.Price" />
<ValidationMessage For="@(() => _orderLine.Price)"></ValidationMessage>
</div>
</div>
<div class="row mb-2">
<label for="discount" class="col-md-2 col-form-label">Rabat</label>
<div class="col-md-10">
<InputNumber id="discount" class="form-control" @bind-Value="_orderLine.Discount" />
<ValidationMessage For="@(() => _orderLine.Discount)"></ValidationMessage>
</div>
</div>
<div class="row mb-2">
<div class="col-md-12">
<button type="submit" class="btn btn-success">Opret</button>
</div>
</div>
</EditForm>

View file

@ -1,41 +0,0 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the Affero GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// Affero GNU General Public License for more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Wonky.Client.Models;
using Wonky.Entity.DTO;
namespace Wonky.Client.Components;
public partial class PoLineCreate
{
private List<CrmActivityLine> Lines = new();
private CrmActivityLine _orderLine = new();
private EditContext _editContext;
[Parameter] public PurchaseOrder PurchaseOrder { get; set; }
protected override async Task OnInitializedAsync()
{
}
private void CreateLine()
{
Lines.Add(_orderLine);
}
}

View file

@ -1,66 +0,0 @@
@*
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the Affero GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// Affero GNU General Public License for more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
*@
<div class="row">
<div class="col">
Varenr.
</div>
<div class="col">
Tekst
</div>
<div class="col">
Antal
</div>
<div class="col">
Stk.pris
</div>
<div class="col">
Rabat
</div>
<div class="col">
Linjesum
</div>
</div>
@if (Lines.Any())
{
foreach (var line in Lines)
{
<div class="row">
<div class="col">
@line.Sku
</div>
<div class="col">
@line.Text
</div>
<div class="col">
@line.Qty
</div>
<div class="col">
@line.Price
</div>
<div class="col">
@line.Discount
</div>
<div class="col">
@(line.Qty * (line.Price - (line.Price * line.Discount / 100 )))
</div>
<div class="col">
<button class="btn btn-warning">Slet linje</button>
</div>
</div>
}
}

View file

@ -1,25 +0,0 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the Affero GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// Affero GNU General Public License for more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using Microsoft.AspNetCore.Components;
using Wonky.Entity.DTO;
namespace Wonky.Client.Components;
public partial class PurchaseOrderLinesTable
{
[Parameter] public List<CrmActivityLine> Lines { get; set; } = new();
}

View file

@ -1,51 +0,0 @@
@*
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the Affero GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// Affero GNU General Public License for more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
*@
<div class="row">
<div class="col">
Id
</div>
<div class="col">
Konto
</div>
<div class="col">Firma</div>
<div class="col-sm-1"></div>
<div class="col-sm-1"></div>
<div class="col-sm-1"></div>
<div class="col-sm-1"></div>
</div>
@if (PurchaseOrders.Any())
{
@foreach (var order in PurchaseOrders)
{
<div class="row">
<div class="col">
@order.ActivityId
</div>
<div class="col">
@order.Account
</div>
<div class="col">
@order.Name
</div>
<div class="col-sm-1"><button class="btn btn-light">Vis</button></div>
<div class="col-sm-1"><button class="btn btn-warning">Slet</button></div>
<div class="col-sm-1"><button class="btn btn-primary">Tilbud</button></div>
<div class="col-sm-1"><button class="btn btn-success">Bestil</button></div>
</div>
}
}

View file

@ -1,25 +0,0 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the Affero GNU General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// Affero GNU General Public License for more details.
//
// You should have received a copy of the Affero GNU General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using Microsoft.AspNetCore.Components;
using Wonky.Client.Models;
namespace Wonky.Client.Components;
public partial class PurchaseOrderTable
{
[Parameter] public List<PurchaseOrder> PurchaseOrders { get; set; } = new();
}

View file

@ -1,2 +0,0 @@
body {
}

View file

@ -106,8 +106,6 @@
}
else
{
<div>
<img class="spinner" src="/loader.gif" alt="Vent venligst..."/> Henter data...
</div>
<AppSpinner/>
}
</div>

View file

@ -1,11 +0,0 @@
@media (min-width: 768px) and (max-width:991px){
.card-columns {
column-count: 3;
}
}
@media (min-width: 992px){
.card-columns {
column-count: 4;
}
}

View file

@ -96,8 +96,6 @@
}
else
{
<div>
<img class="spinner" src="/loader.gif" alt="Vent venligst..."/> Henter data...
</div>
<AppSpinner/>
}
</div>

View file

@ -87,8 +87,9 @@ namespace Wonky.Client.HttpInterceptors
switch (e.Response.StatusCode)
{
case HttpStatusCode.NotFound:
_navigation.NavigateTo("/404");
message = "404 - Page not found.";
//_navigation.NavigateTo("/404");
message = "404 - Siden blev ikke fundet.";
_toast.ShowInfo(message);
break;
case HttpStatusCode.BadRequest:
_navigation.NavigateTo($"/login/{currDoc}");

View file

@ -0,0 +1,159 @@
@using Wonky.Client.Components
@using System.ComponentModel.DataAnnotations
@page "/OrdreKladde"
<div class="row mb-3">
<div class="col">
<ItemSearchColumn OnSearchColumnChanged="SearchColumnChanged"></ItemSearchColumn>
</div>
<div class="col">
<SearchPhrase OnSearchPhraseChanged="SearchPhraseChanged"></SearchPhrase>
</div>
<div class="col">
<ItemGroupFilter OnGroupFilterChanged="GroupFilterChanged"></ItemGroupFilter>
</div>
<div class="col">
</div>
</div>
@if (SalesItems.Any())
{
<table class="table table-hover table-striped justify-content-center">
<thead>
<tr>
<th scope="col" style="width: 50%;">Navn</th>
<th scope="col" style="width: 30%;" class="text-nowrap">Varenr</th>
<th scope="col" style="width: 20%">Stk / Pris</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@foreach (var item in SalesItems)
{
<tr>
<td>
@item.Name
</td>
<td>
@item.Sku
</td>
<td>
<ul class="list-group">
@foreach (var rate in item.Rates)
{
<li class="list-group-item d-flex justify-content-between align-items-end">
<div class="text-sm-start px-2">@rate.Quantity</div>
<div class="text-sm-end">@rate.Rate</div>
</li>
}
</ul>
</td>
<td>
<button class="btn btn-primary" @onclick="@(() => ItemSelect(item.ItemId))">Vælg</button>
</td>
</tr>
}
</tbody>
</table>
}
else
{
<AppSpinner/>
}
@if (_selectedItem != null && ShowItem)
{
<div class="card mb-3 mt-3">
<div class="card-header bg-info fw-bold text-white">Kladdelinje</div>
<div class="card-body">
<div class="row">
<div class="col col-md-4 fw-bold">
Varenavn
</div>
<div class="col fw-bold">
Varenr
</div>
<div class="col fw-bold">
Antal
</div>
<div class="col fw-bold">
Pris
</div>
<div class="col">
</div>
</div>
<div class="row">
<div class="col col-md-4">
@_selectedItem.Name
</div>
<div class="col">
@_selectedItem.Sku
</div>
<div class="col">
<input class="form-control" type="number" @bind-value="@Quantity"/>
</div>
<div class="col">
<input class="form-control" type="number" @bind-value="@Price"/>
</div>
<div class="col">
<button class="btn btn-info" @onclick="@(() => AddToCart(_selectedItem))">Læg til</button>
</div>
</div>
</div>
</div>
}
@* Show the cart contents if there are items in it. *@
@if (CartStateProvider != null && CartStateProvider.ShoppingCart.Items.Count > 0)
{
<div class="card mt-3 mb-3">
<div class="card-header bg-success text-white fw-bold">Ordrekladde</div>
<div class="card-body">
<table class="table table-hover table-striped justify-content-center">
<thead>
<tr>
<th scope="col">Navn</th>
<th scope="col" class="text-nowrap">Varenr</th>
<th scope="col" class="text-end">Antal</th>
<th scope="col" class="text-end">Enhedspris</th>
<th scope="col" class="text-end">Linjesum</th>
<th scope="col">&nbsp;</th>
</tr>
</thead>
<tbody>
@foreach (var cItem in CartStateProvider.ShoppingCart.Items)
{
<tr>
<td>
@cItem.Item.Name
</td>
<td>
@cItem.Item.Sku
</td>
<td class="text-end">
@cItem.Quantity
</td>
<td class="text-end">
@cItem.Price
</td>
<td class="text-end">
@cItem.Total
</td>
<td>
<button class="btn btn-warning" @onclick="@(() => RemoveItem(@cItem))">Slet</button>
</td>
</tr>
}
<tr>
<td></td>
<td></td>
<td></td>
<td class="text-black text-end fw-bold">Total</td>
<td class="text-black text-end fw-bold">@CartStateProvider.ShoppingCart.Total</td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
}

View file

@ -0,0 +1,95 @@
using Microsoft.AspNetCore.Components;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Client.Shared;
using Wonky.Entity.DTO;
using Wonky.Entity.Models;
using Wonky.Entity.Requests;
namespace Wonky.Client.Pages;
public partial class OrderDraft : IDisposable
{
[CascadingParameter] CartStateProvider CartStateProvider { get; set; }
private bool ShowItem;
private string Quantity = "1";
private string Price = "0";
private SalesItemDto _selectedItem { get; set; } = new();
private List<SalesItemDto> SalesItems { get; set; } = new();
private SalesItemDto SelectedItem { get; set; } = new();
private MetaData _meta { get; set; } = new();
private PagingParams _paging { get; set; } = new();
[Inject] private ISalesItemHttpRepository ItemRepo { get; set; }
[Inject] private HttpInterceptorService Interceptor { get; set; }
protected override async Task OnInitializedAsync()
{
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
await GetSalesItems();
}
private async Task GetSalesItems()
{
var response = await ItemRepo.GetSalesItems(_paging);
SalesItems = response.Items!;
_meta = response.MetaData;
}
private void ItemSelect(string itemId)
{
ShowItem = true;
_selectedItem = (from x in SalesItems where x.ItemId == itemId select x).First();
Price = _selectedItem.Rates[0].Rate;
Quantity = "1";
}
private async Task AddToCart(SalesItemDto salesItem)
{
ShowItem = false;
// create a new cart item
var item = new CartItem
{
Item = salesItem,
Quantity = Convert.ToInt32(Quantity),
Price = Convert.ToDecimal(Price)
};
// add it to the cart
CartStateProvider.ShoppingCart.Items.Add(item);
// reset variables
_selectedItem = new();
Quantity = "1";
Price = "0";
// save the item using the CartStateProvider's save method
await CartStateProvider.SaveChangesAsync();
}
private async Task RemoveItem(CartItem item)
{
// click on remove
CartStateProvider.ShoppingCart.Items.Remove(item);
// save the cart
await CartStateProvider.SaveChangesAsync();
}
private async Task GroupFilterChanged(string groupFilter)
{
_paging.PageNumber = 1;
_paging.SelectGroup = groupFilter;
await GetSalesItems();
}
private async Task SearchColumnChanged(string columnName)
{
_paging.PageNumber = 1;
_paging.SearchTerm = "";
_paging.SearchColumn = columnName;
await GetSalesItems();
}
private async Task SearchPhraseChanged(string searchTerm)
{
_paging.PageNumber = 1;
_paging.SearchTerm = searchTerm;
await GetSalesItems();
}
public void Dispose() => Interceptor.DisposeEvent();
}

View file

@ -1,153 +0,0 @@
@using Wonky.Entity.Models
@page "/cart"
<h3>ShoppingCart</h3>
@if (AllItems != null)
{
<h2>select item</h2>
<select size="4" style="width:100%" @onchange="ItemSelected">
@foreach (var item in AllItems)
{
<option value="@item.Id.ToString()">@item.Name</option>
}
</select>
<br/>
@if (SelectedItem != null && ShowItem)
{
<div style="padding: 1vw;background-color: lightgray">
<table>
<tr>
<td><strong>Name:</strong></td>
<td>@SelectedItem.Name</td>
</tr>
<tr>
<td><strong>Description:</strong></td>
<td>@SelectedItem.Description</td>
</tr>
<tr>
<td><strong>Price:</strong></td>
<td>$@SelectedItem.Price</td>
</tr>
<tr>
<td><strong>Add To Cart:</strong></td>
<td>
Quantity:
<input @bind="Quantity"/>
<button @onclick="AddToCart">Add</button>
</td>
</tr>
</table>
</div>
}
// Show the cart contents if there are items in it.
@if (CartStateProvider != null && CartStateProvider.ShoppingCart.Items.Count > 0)
{
<br/>
<h3>Your Cart:</h3>
<h4>Total: $@CartStateProvider.ShoppingCart.Total</h4>
<table>
@foreach (var item in CartStateProvider.ShoppingCart.Items)
{
<tr>
<td colspan="2">
<hr/>
</td>
</tr>
<tr>
<td><strong>Name:</strong></td>
<td>@item.Item.Name</td>
</tr>
<tr>
<td><strong>Description:</strong></td>
<td>@item.Item.Description</td>
</tr>
<tr>
<td><strong>Price:</strong></td>
<td>$@item.Item.Price</td>
</tr>
<tr>
<td><strong>Quantity:</strong></td>
<td>@item.Quantity</td>
</tr>
<tr>
<td><strong>Total:</strong></td>
<td>$@item.Total</td>
</tr>
<tr>
<td colspan="2">
@*Clicking this button passes the item so we can remove it*@
<button @onclick="@(() => RemoveItem(@item))">Remove</button>
</td>
</tr>
}
</table>
<br/>
<h4>Total: $@CartStateProvider.ShoppingCart.Total</h4>
}
}
@code {
[CascadingParameter] CartStateProvider CartStateProvider { get; set; }
private bool ShowItem;
private string Quantity = "1";
private List<Item> AllItems { get; set; } = new();
private Item SelectedItem { get; set; } = new();
void ItemSelected(ChangeEventArgs args)
{
// Item has been selected
SelectedItem = (from x in AllItems where x.Id == Convert.ToInt32(args.Value) select x).First();
Quantity = "1";
ShowItem = true;
}
async Task AddToCart()
{
// create a new cart item
var item = new CartItem
{
Item = SelectedItem,
Quantity = Convert.ToInt32(Quantity)
};
// add it to the cart
CartStateProvider.ShoppingCart.Items.Add(item);
// save the item using the CartStateProvider's save method
await CartStateProvider.SaveChangesAsync();
// hide the selected item
ShowItem = false;
}
async Task RemoveItem(CartItem item)
{
// click on remove
CartStateProvider.ShoppingCart.Items.Remove(item);
// save the cart
await CartStateProvider.SaveChangesAsync();
}
protected override void OnInitialized()
{
// TODO load the items from server
// just create some for now
AllItems = new List<Item>();
AllItems.Add(new Item()
{
Id = 1,
Name = "TEST 1",
Description = "Description 1",
Price = (decimal)10.99
});
AllItems.Add(new Item()
{
Id = 2,
Name = "TEST 2",
Description = "Description 2",
Price = (decimal)19.99
});
}
}

View file

@ -59,7 +59,7 @@ main {
}
.sidebar {
width: 250px;
width: 200px;
height: 100vh;
position: sticky;
top: 0;

View file

@ -20,7 +20,9 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href=""><AppVersion /></a>
<a class="navbar-brand" href="">
<AppVersion/>
</a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
@ -36,6 +38,12 @@
</div>
<AuthorizeView Roles="Adviser,Admin">
<Authorized>
<div class="nav-item px-3">
<NavLink class="nav-link" href="/OrdreKladde">
<span class="oi oi-list-rich" aria-hidden="true"></span> Ordrekladde
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link" href="Companies">
<span class="oi oi-list-rich" aria-hidden="true"></span> Firmaer

View file

@ -4,7 +4,7 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Innotec App</title>
<title>Wonky Online</title>
<base href="/" />
<link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="css/app.css" rel="stylesheet" />

View file

@ -1,23 +1,22 @@
namespace Wonky.Entity.Models;
using Wonky.Entity.DTO;
public class Item
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
}
namespace Wonky.Entity.Models;
public class CartItem
{
public int Quantity { get; set; }
public Item Item { get; set; }
public SalesItemDto Item { get; set; }
public decimal Price { get; set; }
public decimal Total
{
get
{
return Item.Price * Quantity;
var price = (from x in Item.Rates where x.Quantity == Quantity.ToString() select x.Rate).First();
if (string.IsNullOrWhiteSpace(price))
price = Item.Rates[0].Rate;
Price = Convert.ToDecimal(price);
return Price * Quantity;
}
}
}