CompanyInventoryList sorting and tagging

This commit is contained in:
Frede Hundewadt 2023-06-01 17:28:51 +02:00
parent 8cd924e817
commit 1afbf20b15
56 changed files with 643 additions and 1028 deletions

View file

@ -23,37 +23,74 @@
<div class="list-group mt-2"> <div class="list-group mt-2">
<div class="list-group-item d-print-none"> <div class="list-group-item d-print-none">
<div class="row"> <div class="row">
<div class="col-sm-4 action-link-element" @onclick="@(() => SortProducts(ProductSort.Desc))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div> @*
<div class="col-sm-3 action-link-element" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div> <div class="col-sm-4 action-link-element" onclick="@(() => SortProducts(ProductSort.Description))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-2 text-center action-link-element" @onclick="@(() => SortProducts(ProductSort.Qty))"><i class="bi-sort-numeric-down"></i> Antal <i class="bi-sort-numeric-up-alt"></i></div> <div class="col-sm-3 action-link-element" onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-2"></div> *@
<div class="col-sm-1"></div> <div class="col-sm-7 text-end">
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="sortOrder" checked @onclick="@SetSortOrder"/>
<label class="form-check-label" for="sortOrder"><i class="@(Descending ? "bi-sort-up" : "bi-sort-down-alt") "></i></label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="description" value="description"
onclick="@(() => SortProducts(ProductSort.Description))">
<label class="form-check-label" for="description">Navn</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="itemNumber" value="itemNumber"
onclick="@(() => SortProducts(ProductSort.Sku))">
<label class="form-check-label" for="itemNumber">Vare Nr.</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="lastInvoiceDate" value="itemDate"
onclick="@(() => SortProducts(ProductSort.LastInvoiceDate))" checked>
<label class="form-check-label" for="lastInvoiceDate">Sidst leveret</label>
</div>
</div>
<div class="col-sm-5">
<SearchPhraseComponent OnChanged="OnSearchChanged"/>
</div>
</div> </div>
</div> </div>
@foreach (var product in Inventory) @foreach (var product in FilteredList)
{ {
<div class="list-group-item"> <div class="list-group-item">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col-sm-2">
<div class="position-relative">
@product.LastInvoiceDate
@if (product.AgedProduct() && !product.Discontinued)
{
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
Længe siden
<span class="visually-hidden">Længe siden</span>
</span>
}
</div>
</div>
<div class="col-sm-4"> <div class="col-sm-4">
<div class="position-relative"> <div class="position-relative">
@product.Description @product.Description
@if (product.Discontinued) @if (product.Discontinued)
{ {
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">Udgået</span> <span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-danger">
<span class="visually-hidden">Produktet er udgået</span> Udgået
<span class="visually-hidden">Produktet er udgået</span>
</span>
} }
</div> </div>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-2 text-sm-start">
@product.Sku @product.Sku
</div> </div>
<div class="col-sm-2 text-center"> <div class="col-sm-1 text-center">
@product.Quantity @product.Quantity
</div> </div>
<div class="col-sm-2 d-print-none"> <div class="col-sm-2 d-print-none">
<button type="button" class="btn btn-info d-block" @onclick="@(() => CallShowReorderModal(product.Sku))"><i class="bi-cart"></i> Genbestil</button> <button type="button" class="btn btn-info d-block" @onclick="@(() => CallShowReorderModal(product.Sku))"><i class="bi-cart"></i> Genbestil</button>
</div> </div>
<div class="col-sm-1 d-print-none" @onclick="@(() => ProductCheck(product.Sku))"> <div class="col-sm-1 d-print-none" @onclick="@(() => SetProductCheckmark(product.Sku))">
<input type="checkbox" class="btn-check" id="btn-@product.Sku.Replace(",", "")" autocomplete="off"/> <input type="checkbox" class="btn-check" id="btn-@product.Sku.Replace(",", "")" autocomplete="off"/>
@if (product.Check) @if (product.Check)
{ {

View file

@ -16,6 +16,7 @@
using Blazored.LocalStorage; using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Wonky.Client.Enums; using Wonky.Client.Enums;
using Wonky.Client.Helpers;
using Wonky.Client.Models; using Wonky.Client.Models;
using Wonky.Entity.Views; using Wonky.Entity.Views;
@ -24,71 +25,68 @@ namespace Wonky.Client.Components;
public partial class CustomerInventoryListComponent public partial class CustomerInventoryListComponent
{ {
// ##############################################3333############
[Inject] public ILocalStorageService Storage { get; set; } [Inject] public ILocalStorageService Storage { get; set; }
// Parameters [Inject] public ILogger<CustomerInventoryListComponent> Logger { get; set; }
// ##############################################3333############
[Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new(); [Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new();
[Parameter] public string CompanyId { get; set; } = ""; [Parameter] public string CompanyId { get; set; } = "";
[Parameter] public EventCallback<string> OnReorderSelected { get; set; } [Parameter] public EventCallback<string> OnReorderSelected { get; set; }
// ##############################################3333############
// private variables // private variables
private bool Descending { get; set; } private bool Descending { get; set; } = true;
private string SearchTerm { get; set; } = "";
protected override void OnParametersSet() private List<ProductInventoryItemView> FilteredList { get; set; }
private ProductSort SortColumn { get; set; } = ProductSort.LastInvoiceDate;
protected override void OnInitialized()
{ {
if(Inventory.Any()) // sort base list
Inventory = Inventory.OrderBy(x => x.Description).ToList(); Inventory = Utils.SortInventory(Inventory, SortColumn, Descending);
// initialize FilteredList
FilterItems(SearchTerm);
}
private void SetSortOrder()
{
Descending = !Descending;
FilteredList = Utils.SortInventory(FilteredList, SortColumn, Descending);
} }
private void OnSearchChanged(string searchTerm)
{
// use search input to filter list
FilterItems(searchTerm);
}
private void FilterItems(string filter)
{
SearchTerm = filter;
FilteredList = string.IsNullOrWhiteSpace(filter)
? Inventory
: Inventory.Where(i => i.Description.ToLower().Contains(filter.ToLower())).ToList();
}
private void SortProducts(ProductSort column) private void SortProducts(ProductSort column)
{ {
Descending = !Descending; FilteredList = Utils.SortInventory(FilteredList, column, Descending);
switch (column)
{
case ProductSort.Desc:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Description).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
case ProductSort.Sku:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Sku).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Sku).ToList();
break;
case ProductSort.Qty:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Quantity).ToList();
break;
case ProductSort.None:
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
case ProductSort.Abbr:
break;
default:
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
}
} }
private async Task CallShowReorderModal(string sku) private async Task CallShowReorderModal(string sku)
{ {
await ProductCheck(sku); await SetProductCheckmark(sku);
await OnReorderSelected.InvokeAsync(sku); await OnReorderSelected.InvokeAsync(sku);
} }
private async Task ProductCheck(string sku) private async Task SetProductCheckmark(string sku)
{ {
var x = Inventory.First(x => x.Sku == sku); var x = Inventory.First(x => x.Sku == sku);
x.Check = !x.Check; x.Check = !x.Check;

View file

@ -22,24 +22,50 @@
<div class="list-group mt-2"> <div class="list-group mt-2">
<div class="list-group-item"> <div class="list-group-item">
<div class="row"> <div class="row">
<div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Desc))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div> @*
<div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Description))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-3" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div> <div class="col-sm-3" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-2 text-center" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Qty))"><i class="bi-sort-numeric-down"></i> Antal <i class="bi-sort-numeric-up-alt"></i></div> *@
<div class="col-sm-2"></div> <div class="col-sm-7 text-end">
<div class="col-sm-1"></div> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="sortOrder" checked @onclick="@SetSortOrder"/>
<label class="form-check-label" for="sortOrder"><i class="@(Descending ? "bi-sort-up" : "bi-sort-down") "></i></label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="description" value="description"
onclick="@(() => SortProducts(ProductSort.Description))">
<label class="form-check-label" for="description">Navn</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="itemNumber" value="itemNumber"
onclick="@(() => SortProducts(ProductSort.Sku))">
<label class="form-check-label" for="itemNumber">Vare Nr.</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="lastInvoiceDate" value="itemDate"
onclick="@(() => SortProducts(ProductSort.LastInvoiceDate))" checked>
<label class="form-check-label" for="lastInvoiceDate">Sidst leveret</label>
</div>
</div>
<div class="col-sm-5">
<SearchPhraseComponent OnChanged="OnSearchChanged"/>
</div>
</div> </div>
</div> </div>
@foreach (var product in Inventory) @foreach (var product in Inventory)
{ {
<div class="list-group-item"> <div class="list-group-item">
<div class="row align-items-center"> <div class="row align-items-center">
<div class="col-sm-2">
@product.LastInvoiceDate
</div>
<div class="col-sm-4"> <div class="col-sm-4">
@product.Description @product.Description
</div> </div>
<div class="col-sm-3"> <div class="col-sm-2">
@product.Sku @product.Sku
</div> </div>
<div class="col-sm-2 text-center"> <div class="col-sm-1 text-center">
@product.Quantity @product.Quantity
</div> </div>
<div class="col-sm-2"> <div class="col-sm-2">

View file

@ -13,9 +13,11 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html] // along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
// //
using System.Security.Policy;
using Blazored.LocalStorage; using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Wonky.Client.Enums; using Wonky.Client.Enums;
using Wonky.Client.Helpers;
using Wonky.Client.Models; using Wonky.Client.Models;
using Wonky.Entity.Views; using Wonky.Entity.Views;
#pragma warning disable CS8618 #pragma warning disable CS8618
@ -27,57 +29,48 @@ public partial class CustomerProductCheckListComponent
[Parameter] public string CompanyId { get; set; } = ""; [Parameter] public string CompanyId { get; set; } = "";
[Inject] public ILocalStorageService Storage { get; set; } [Inject] public ILocalStorageService Storage { get; set; }
// private variables // private variables
private bool Descending { get; set; } private bool Descending { get; set; } = true;
private string SearchTerm { get; set; } = "";
private List<ProductInventoryItemView> FilteredList { get; set; } = new();
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
if(Inventory.Any()) Inventory = Utils.SortInventory(Inventory, ProductSort.LastInvoiceDate, Descending);
Inventory = Inventory.OrderBy(x => x.Description).ToList(); FilterItems(SearchTerm);
} }
private void SetSortOrder()
{
Descending = !Descending;
FilteredList = Utils.SortInventory(FilteredList, ProductSort.LastInvoiceDate, Descending);
}
private void OnSearchChanged(string searchTerm)
{
FilterItems(searchTerm);
}
private void FilterItems(string filter)
{
SearchTerm = filter;
FilteredList = string.IsNullOrWhiteSpace(filter)
? Inventory
: Inventory.Where(i => i.Description.ToLower().Contains(filter.ToLower())).ToList();
}
private void SortProducts(ProductSort column) private void SortProducts(ProductSort column)
{ {
Descending = !Descending; FilteredList = Utils.SortInventory(Inventory, column, Descending);
switch (column)
{
case ProductSort.Desc:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Description).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
case ProductSort.Sku:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Sku).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Sku).ToList();
break;
case ProductSort.Qty:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Quantity).ToList();
break;
case ProductSort.None:
break;
case ProductSort.Abbr:
break;
default:
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
} }
private async Task ProductCheck(string sku)
private void ProductCheck(string sku)
{ {
var x = Inventory.First(x => x.Sku == sku); var x = Inventory.First(x => x.Sku == sku);
x.Check = !x.Check; x.Check = !x.Check;
await Storage.SetItemAsync($"{CompanyId}-products", Inventory);
} }
} }

View file

@ -1,60 +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 GNU Affero 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
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@using Wonky.Client.Components
<h3>Pakning / Forsendelse</h3>
<table class="table">
<thead>
<tr>
<th>Symbol</th>
<th>Betydning</th>
</tr>
</thead>
<tbody>
<tr>
<td class="align-middle">
<div class="color-code">
<DisplayStateComponent StateClass="the-good"/>
</div>
</td>
<td class="align-middle">Ubehandlet</td>
</tr>
<tr>
<td class="align-middle">
<div class="color-code">
<DisplayStateComponent StateClass="the-bad"/>
</div>
</td>
<td class="align-middle">Varer er plukket</td>
</tr>
<tr>
<td class="align-middle">
<div class="color-code">
<DisplayStateComponent StateClass="the-ugly"/>
</div>
</td>
<td class="align-middle">Varer er pakket</td>
</tr>
<tr>
<td class="align-middle">
<div class="color-code">
<DisplayStateComponent StateClass="the-dead"/>
</div>
</td>
<td class="align-middle">Varer er afsendt</td>
</tr>
</tbody>
</table>

View file

@ -36,6 +36,7 @@
<td><i class="bi-printer" style="font-size:1.3rem"></i></td> <td><i class="bi-printer" style="font-size:1.3rem"></i></td>
<td>Udskrevet</td> <td>Udskrevet</td>
</tr> </tr>
@*
<tr> <tr>
<td><i class="bi-file-earmark-check" style="font-size:1.3rem"></i></td> <td><i class="bi-file-earmark-check" style="font-size:1.3rem"></i></td>
<td>Plukket</td> <td>Plukket</td>
@ -48,5 +49,6 @@
<td><i class="bi-truck" style="font-size:1.3rem"></i></td> <td><i class="bi-truck" style="font-size:1.3rem"></i></td>
<td>Leveret</td> <td>Leveret</td>
</tr> </tr>
*@
</thead> </thead>
</table> </table>

View file

@ -70,7 +70,7 @@
</div> </div>
<OfficeCustomerInvoiceListOverlay Company="@SelectedCompany" InvoiceList="@InvoiceList" @ref="InvoiceListOverlay" /> <OfficeCustomerInvoiceListOverlay Company="@SelectedCompany" InvoiceList="@InvoiceList" @ref="InvoiceListOverlay" />
<OfficeCustomerActivityListOverlay Company="SelectedCompany" ActivityList="ActivityList" @ref="ActivityListOverlay" /> <OfficeCustomerActivityListOverlay Company="SelectedCompany" ActivityList="ActivityList" @ref="ActivityListOverlay" />
<OfficeCustomerProductListOverlay Company="SelectedCompany" Inventory="ProductList" @ref="ProductListOverlay" /> <OfficeCustomerInventoryListOverlay Company="SelectedCompany" Inventory="ProductList" @ref="InventoryListOverlay" />
} }
else else
{ {

View file

@ -48,7 +48,7 @@ public partial class OfficeCountryCustomerListComponent
// overlays // overlays
private OfficeCustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new(); private OfficeCustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new();
private OfficeCustomerActivityListOverlay ActivityListOverlay { get; set; } = new(); private OfficeCustomerActivityListOverlay ActivityListOverlay { get; set; } = new();
private OfficeCustomerProductListOverlay ProductListOverlay { get; set; } = new(); private OfficeCustomerInventoryListOverlay InventoryListOverlay { get; set; } = new();
// ****************************************************** // ******************************************************
// variables // variables
@ -108,7 +108,7 @@ public partial class OfficeCountryCustomerListComponent
SelectedCompany.HistorySync = newSyncDate; SelectedCompany.HistorySync = newSyncDate;
} }
ProductList = await HistoryRepo.GetInventory(SelectedCompany.CountryCode, SelectedCompany.CompanyId); ProductList = await HistoryRepo.GetInventory(SelectedCompany.CountryCode, SelectedCompany.CompanyId);
ProductListOverlay.Show(); InventoryListOverlay.Show();
} }
private void ShowOrder(string companyId) private void ShowOrder(string companyId)

View file

@ -24,11 +24,32 @@
<div class="list-group mt-2"> <div class="list-group mt-2">
<div class="list-group-item"> <div class="list-group-item">
<div class="row"> <div class="row">
<div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Desc))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div> @* <div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Description))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div> *@
<div class="col-sm-3" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div> @* <div class="col-sm-3" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div> *@
<div class="col-sm-2 text-center" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Qty))"><i class="bi-sort-numeric-down"></i> Antal <i class="bi-sort-numeric-up-alt"></i></div> <div class="col-sm-7 text-end">
<div class="col-sm-2"></div> <div class="form-check form-check-inline">
<div class="col-sm-1"></div> <input class="form-check-input" type="checkbox" id="sortOrder" checked @onclick="@SetSortOrder"/>
<label class="form-check-label" for="sortOrder"><i class="@(Descending ? "bi-sort-up" : "bi-sort-down") "></i></label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="description" value="description"
onclick="@(() => SortProducts(ProductSort.Description))">
<label class="form-check-label" for="description">Navn</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="itemNumber" value="itemNumber"
onclick="@(() => SortProducts(ProductSort.Sku))">
<label class="form-check-label" for="itemNumber">Vare Nr.</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="lastInvoiceDate" value="itemDate"
onclick="@(() => SortProducts(ProductSort.LastInvoiceDate))" checked>
<label class="form-check-label" for="lastInvoiceDate">Sidst leveret</label>
</div>
</div>
<div class="col-sm-5">
<SearchPhraseComponent OnChanged="OnSearchChanged" />
</div>
</div> </div>
</div> </div>
@foreach (var product in Inventory) @foreach (var product in Inventory)

View file

@ -15,72 +15,70 @@
using Blazored.LocalStorage; using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Microsoft.VisualBasic.CompilerServices;
using Wonky.Client.Enums; using Wonky.Client.Enums;
using Wonky.Client.Models; using Wonky.Client.Models;
using Wonky.Entity.Views; using Wonky.Entity.Views;
using Utils = Wonky.Client.Helpers.Utils;
namespace Wonky.Client.Components; namespace Wonky.Client.Components;
#pragma warning disable CS8618 #pragma warning disable CS8618
public partial class OfficeInventoryListComponent public partial class OfficeCustomerInventoryListComponent
{ {
// ************************************************************* // *************************************************************
// Injections // Injections
[Inject] public ILocalStorageService Storage { get; set; } [Inject] public ILocalStorageService Storage { get; set; }
// ************************************************************* // *************************************************************
// Parameters // Parameters
[Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new(); [Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new();
[Parameter] public string CompanyId { get; set; } = ""; [Parameter] public string CompanyId { get; set; } = "";
[Parameter] public EventCallback<string> OnReorderSelected { get; set; } [Parameter] public EventCallback<string> OnReorderSelected { get; set; }
// ************************************************************* // *************************************************************
// private variables // private variables
private bool Descending { get; set; } private bool Descending { get; set; } = true;
private string SearchTerm { get; set; } = "";
private List<ProductInventoryItemView> FilteredList { get; set; } = new();
private ProductSort SortColumn { get; set; } = ProductSort.LastInvoiceDate;
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
if(Inventory.Any()) Inventory = Utils.SortInventory(Inventory, SortColumn, Descending);
Inventory = Inventory.OrderBy(x => x.Description).ToList(); FilterItems(SearchTerm);
} }
private void SetSortOrder()
{
Descending = !Descending;
FilteredList = Utils.SortInventory(FilteredList, SortColumn, Descending);
}
private void OnSearchChanged(string searchTerm)
{
FilterItems(searchTerm);
}
private void FilterItems(string filter)
{
SearchTerm = filter;
FilteredList = string.IsNullOrWhiteSpace(filter)
? Inventory
: Inventory.Where(i => i.Description.ToLower().Contains(filter.ToLower())).ToList();
}
private void SortProducts(ProductSort column) private void SortProducts(ProductSort column)
{ {
Descending = !Descending; FilteredList = Utils.SortInventory(FilteredList, column, Descending);
switch (column)
{
case ProductSort.Desc:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Description).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
case ProductSort.Sku:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Sku).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Sku).ToList();
break;
case ProductSort.Qty:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Quantity).ToList();
break;
case ProductSort.None:
break;
case ProductSort.Abbr:
break;
default:
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
} }
private async Task CallShowReorderModal(string sku) private async Task CallShowReorderModal(string sku)
{ {
// await ProductCheck(sku); // await ProductCheck(sku);

View file

@ -13,20 +13,9 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html] // along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@ *@
<h3>Lager</h3> <div class="input-group">
<table class="table"> <input id="search-input" type="text" class="form-control" placeholder="Søg ..." aria-described-by="search-addon"
<thead> @bind-value="SearchTerm" @bind-value:event="oninput" onkeyup="@OnSearchChanged" />
<tr> <span class="input-group-text" id="search-addon"><i class="oi oi-delete" @onclick="@ClearSearch"></i></span>
<th>Symbol</th> </div>
<th>Betydning</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<i class="bi-box" style="font-size:1.3rem"></i>
</td>
<td>Pakning / Forsendelse</td>
</tr>
</tbody>
</table>

View file

@ -1,4 +1,4 @@
/* Copyright (C) 2022 FCS Frede's Computer Services. // Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify // This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as // it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the // published by the Free Software Foundation, either version 3 of the
@ -11,10 +11,27 @@
// //
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html] // along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*/ //
.pictogram {
max-width: 30px; using System.Timers;
} using Microsoft.AspNetCore.Components;
.color-code { using Timer = System.Timers.Timer;
max-width: 30px;
namespace Wonky.Client.Components;
public partial class SearchPhraseComponent
{
private string SearchTerm { get; set; } = "";
[Parameter] public EventCallback<string> OnChanged { get; set; }
private void ClearSearch()
{
SearchTerm = "";
OnChanged.InvokeAsync("");
}
private void OnSearchChanged()
{
OnChanged.InvokeAsync(SearchTerm);
}
} }

View file

@ -1,102 +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 GNU Affero 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
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@using Wonky.Client.Models
@using Wonky.Client.Enums
@if (OrderList != null)
{
<div class="row mb-3">
<div class="col-sm-4">
<div class="btn-group" role="group" aria-label="Ordre status">
<input type="radio" class="btn-check" name="btn-order" id="btn-order1" @onchange="() => GetWithStatus(ProcessStatus.None)" autocomplete="off" checked="checked"/>
<label class="btn btn-success" for="btn-order1">Ubehandlet</label>
<input type="radio" class="btn-check" name="btn-order" id="btn-order2" @onchange="() => GetWithStatus(ProcessStatus.Picked)" autocomplete="off"/>
<label class="btn btn-warning" for="btn-order2">Plukket</label>
<input type="radio" class="btn-check" name="btn-order" id="btn-order3" @onchange="() => GetWithStatus(ProcessStatus.Packed)" autocomplete="off"/>
<label class="btn btn-danger" for="btn-order3">Pakket</label>
</div>
</div>
<div class="col-sm-4 text-end">
<label class="btn btn-outline-dark position-relative">
@Header
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill bg-info">@Orders.Count</span>
<span class="visually-hidden">ordrer i listen</span>
</label>
</div>
<div class="col-sm-4 text-end">
@if (ReadyToShip && Orders.Any())
{
<button type="button" class="btn btn-primary text-sm-center" @onclick="@SetShipStatus">Sæt alle afsendt</button>
}
</div>
</div>
<div class="list-group list-group-flush">
<div class="list-group-item">
<div class="row">
<div class="col-sm-4">
<h4>Modtager</h4>
</div>
<div class="col-sm-3">
<h5>Post By</h5>
</div>
<div class="col-sm-2">
<h5>Dato</h5>
</div>
<div class="col-sm-3 text-end">
<h5><i class="bi-lightning-fill"></i> Status</h5>
</div>
</div>
</div>
@foreach (var order in Orders)
{
<div class="list-group-item list-group-item-action" style="cursor: hand;">
<div class="row">
<div class="col-sm-4">
@order.Company.Name
</div>
<div class="col-sm-3">
@order.Company.ZipCode @order.Company.City
</div>
<div class="col-sm-2">
@order.OrderDate
</div>
<div class="col-sm-3 d-flex">
@if (order.Express)
{
<i class="bi-lightning-fill text-warning"></i>
}
@switch (order.ProcessStatusEnum.ToLower())
{
case "none":
<button class="btn btn-outline-danger me-4" @onclick="@(() => QuickPak(order.OrderId))">QuickPak</button>
<a class="btn btn-warning" href="warehouse/orders/process/@order.OrderId">Pluk varer</a>
break;
case "picked":
<a class="btn btn-warning" href="warehouse/orders/process/@order.OrderId">Pak varer</a>
break;
case "packed":
<a class="btn btn-warning" href="warehouse/orders/process/@order.OrderId">Afhent varer</a>
break;
}
</div>
</div>
</div>
}
</div>
}

View file

@ -1,56 +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 GNU Affero 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
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero 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.Enums;
using Wonky.Client.Models;
using Wonky.Entity.Views;
namespace Wonky.Client.Components;
public partial class WarehouseListComponent
{
[Parameter] public string Header { get; set; } = "";
[Parameter] public List<WarehouseOrderView> OrderList { get; set; } = new();
[Parameter] public bool ReadyToShip { get; set; }
[Parameter] public EventCallback<ProcessStatus> OnGetStatus { get; set; }
[Parameter] public EventCallback OnSetShipped { get; set; }
[Parameter] public EventCallback<string> OnQPak { get; set; }
private List<WarehouseOrderView> Orders { get; set; } = new();
protected override async Task OnParametersSetAsync()
{
while (OrderList == null)
await Task.Delay(500);
Orders = OrderList;
}
private async Task SetShipStatus()
{
await OnSetShipped.InvokeAsync();
}
private async Task GetWithStatus(ProcessStatus status)
{
await OnGetStatus.InvokeAsync(status);
}
private async Task QuickPak(string orderId)
{
await OnQPak.InvokeAsync(orderId);
}
}

View file

@ -18,9 +18,7 @@ namespace Wonky.Client.Enums;
public enum ProductSort public enum ProductSort
{ {
None, Description,
Desc,
Sku, Sku,
Qty, LastInvoiceDate
Abbr
} }

View file

@ -27,11 +27,37 @@ namespace Wonky.Client.Helpers;
/// </summary> /// </summary>
public static class Utils public static class Utils
{ {
public static List<ProductInventoryItemView> SortInventory(IEnumerable<ProductInventoryItemView> inventory,
ProductSort column, bool descending)
{
return column switch
{
ProductSort.Description => descending
? inventory.OrderByDescending(x => x.Description).ToList()
: inventory.OrderBy(x => x.Description).ToList(),
ProductSort.Sku => descending
? inventory.OrderByDescending(x => x.Sku).ToList()
: inventory.OrderBy(x => x.Sku).ToList(),
ProductSort.LastInvoiceDate => descending
? inventory.OrderByDescending(x => x.LastInvoiceDate).ToList()
: inventory.OrderBy(x => x.LastInvoiceDate).ToList(),
_ => inventory.OrderByDescending(x => x.LastInvoiceDate).ToList()
};
}
public static List<ProductVariant> GenerateVariantListDto(IEnumerable<DocView> items) public static List<ProductVariant> GenerateVariantListDto(IEnumerable<DocView> items)
{ {
return items.Select(item => new ProductVariant { VariantId = item.VariantId, S5A = item.S5A, S9A = item.S9A }).ToList(); return items.Select(item => new ProductVariant
{
VariantId = item.VariantId,
S5A = item.S5A,
S9A = item.S9A
})
.ToList();
} }
public static List<DocView> GenerateRevListView(IEnumerable<WorkplaceProduct> products) public static List<DocView> GenerateRevListView(IEnumerable<WorkplaceProduct> products)
{ {
var result = new List<DocView>(); var result = new List<DocView>();
@ -64,17 +90,19 @@ public static class Utils
break; break;
} }
} }
result.Add(newDoc); result.Add(newDoc);
} }
} }
return result.OrderBy(x => x.VariantName).ToList(); return result.OrderBy(x => x.VariantName).ToList();
} }
public static List<DocView> GenerateDocListView(IEnumerable<WorkplaceProduct> products) public static List<DocView> GenerateDocListView(IEnumerable<WorkplaceProduct> products)
{ {
var result = new List<DocView>(); var result = new List<DocView>();
var docProducts = products.OrderBy(x => x.TradingName).ToList(); var docProducts = products.OrderBy(x => x.TradingName).ToList();
foreach (var product in docProducts) foreach (var product in docProducts)
@ -104,45 +132,47 @@ public static class Utils
break; break;
} }
} }
result.Add(newDoc); result.Add(newDoc);
} }
} }
return result.OrderBy(x => x.VariantName).ToList(); return result.OrderBy(x => x.VariantName).ToList();
} }
public static List<EmailContact> ParseRecipientsFromString(string recipients) public static List<EmailContact> ParseRecipientsFromString(string recipients)
{ {
var addresses = recipients var addresses = recipients
.Replace(" ", ",") .Replace(" ", ",")
.Replace(",,",",") .Replace(",,", ",")
.Split(","); .Split(",");
return (from address return (from address
in addresses in addresses
where IsValidEmail(address) where IsValidEmail(address)
select new EmailContact select new EmailContact
{ {
Name = address, Email = address Name = address, Email = address
}).ToList(); }).ToList();
} }
public static List<UserRoleAssignment> MapSaveAssignedRoles(RoleAssignment model) public static List<UserRoleAssignment> MapSaveAssignedRoles(RoleAssignment model)
{ {
return new List<UserRoleAssignment>() return new List<UserRoleAssignment>()
{ {
new (){ Name = "Admin", Assigned = model.Admin}, new() { Name = "Admin", Assigned = model.Admin },
new (){ Name = "Advisor", Assigned = model.Advisor}, new() { Name = "Advisor", Assigned = model.Advisor },
new (){ Name = "EDoc", Assigned = model.EDoc}, new() { Name = "EDoc", Assigned = model.EDoc },
new (){ Name = "EShop", Assigned = model.EShop}, new() { Name = "EShop", Assigned = model.EShop },
new (){ Name = "Management", Assigned = model.Management}, new() { Name = "Management", Assigned = model.Management },
new (){ Name = "Office", Assigned = model.Office}, new() { Name = "Office", Assigned = model.Office },
new (){ Name = "Supervisor", Assigned = model.Supervisor}, new() { Name = "Supervisor", Assigned = model.Supervisor },
new (){ Name = "Warehouse", Assigned = model.Warehouse} new() { Name = "Warehouse", Assigned = model.Warehouse }
}; };
} }
public static RoleAssignment MapEditAssignedRoles(UserManagerEditView model) public static RoleAssignment MapEditAssignedRoles(UserManagerEditView model)
{ {
@ -177,9 +207,10 @@ public static class Utils
break; break;
} }
} }
return x; return x;
} }
public static string StringToDigits(string digitString) public static string StringToDigits(string digitString)
{ {
@ -195,7 +226,7 @@ public static class Utils
return check.All(c => c is >= '0' and <= '9'); return check.All(c => c is >= '0' and <= '9');
} }
public static bool Validate(ValidateType validateType, string toValidate) public static bool Validate(ValidateType validateType, string toValidate)
{ {
return validateType switch return validateType switch
@ -231,7 +262,7 @@ public static class Utils
{ {
validConditions++; validConditions++;
} }
return validConditions == 3; return validConditions == 3;
} }

View file

@ -69,7 +69,8 @@ public class HttpInterceptorService
var absolutePath = e.Request.RequestUri.AbsolutePath; var absolutePath = e.Request.RequestUri.AbsolutePath;
if (!absolutePath.Contains("token")) if (!absolutePath.Contains("token"))
{ {
// call TryRefreshToken // we are using the tryRefreshToken
// the service returns the current access token or a new token using refreshToken
var token = await _refreshTokenService.TryRefreshToken(); var token = await _refreshTokenService.TryRefreshToken();
if (!string.IsNullOrEmpty(token)) if (!string.IsNullOrEmpty(token))
{ {

View file

@ -34,6 +34,7 @@ public class CrmCustomerHistoryRepository : ICrmCustomerHistoryRepository
private readonly HttpClient _client; private readonly HttpClient _client;
private readonly ApiConfig _api; private readonly ApiConfig _api;
public CrmCustomerHistoryRepository( public CrmCustomerHistoryRepository(
HttpClient client, ILogger<CrmCustomerHistoryRepository> logger, HttpClient client, ILogger<CrmCustomerHistoryRepository> logger,
NavigationManager navigation, IOptions<ApiConfig> configuration) NavigationManager navigation, IOptions<ApiConfig> configuration)
@ -44,92 +45,80 @@ public class CrmCustomerHistoryRepository : ICrmCustomerHistoryRepository
_api = configuration.Value; _api = configuration.Value;
} }
/// <summary>
/// Fetch Invoice LIst
/// </summary>
/// <param name="companyId"></param>
/// <returns></returns>
public async Task<InvoiceListView> FetchInvoiceList(string companyId) public async Task<InvoiceListView> FetchInvoiceList(string companyId)
{ {
var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/invoices"); var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/invoices");
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
return response.IsSuccessStatusCode if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
? JsonSerializer.Deserialize<InvoiceListView>(content, _options) {
: new InvoiceListView(); return new InvoiceListView();
}
return JsonSerializer.Deserialize<InvoiceListView>(content, _options) ?? new InvoiceListView();
} }
/// <summary>
/// Fetch given invoice for given customer
/// </summary>
/// <param name="companyId"></param>
/// <param name="invoiceId"></param>
/// <returns></returns>
public async Task<InvoiceView> FetchInvoice(string companyId, string invoiceId) public async Task<InvoiceView> FetchInvoice(string companyId, string invoiceId)
{ {
return await _client var content = await _client
.GetFromJsonAsync<InvoiceView>($"{_api.CrmCustomers}/{companyId}/invoices/{invoiceId}", _options); .GetFromJsonAsync<InvoiceView>($"{_api.CrmCustomers}/{companyId}/invoices/{invoiceId}", _options);
return content ?? new InvoiceView();
} }
/// <summary>
/// Fetch inventory from given customer
/// </summary>
/// <param name="companyId"></param>
/// <returns></returns>
public async Task<List<ProductInventoryItemView>> FetchInventory(string companyId) public async Task<List<ProductInventoryItemView>> FetchInventory(string companyId)
{ {
var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmInventoryExt}"); var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmInventoryExt}");
if (!response.IsSuccessStatusCode)
return new List<ProductInventoryItemView>();
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
return string.IsNullOrWhiteSpace(content) if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
? new List<ProductInventoryItemView>() return new List<ProductInventoryItemView>();
: JsonSerializer.Deserialize<List<ProductInventoryItemView>>(content, _options); return JsonSerializer.Deserialize<List<ProductInventoryItemView>>(content, _options) ?? new List<ProductInventoryItemView>();
} }
/// <summary>
/// Fetch History for given customer public async Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId)
/// </summary>
/// <param name="companyId"></param>
/// <returns></returns>
public async Task<List<ProductHistoryView>> FetchHistory(string companyId)
{ {
var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmProductExt}"); var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmProductExt}");
if (!response.IsSuccessStatusCode)
return new List<ProductHistoryView>();
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
return string.IsNullOrWhiteSpace(content) if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
? new List<ProductHistoryView>() {
: JsonSerializer.Deserialize<List<ProductHistoryView>>(content, _options); return new List<ProductHistoryView>();
}
return JsonSerializer.Deserialize<List<ProductHistoryView>>(content, _options) ?? new List<ProductHistoryView>();
} }
/// <summary>
/// Fetch history for given customer and a given product public async Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId, int months)
/// </summary> {
/// <param name="companyId"></param> var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmProductExt}/statistic?months={months}");
/// <param name="sku"></param> var content = await response.Content.ReadAsStringAsync();
/// <returns></returns> if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
public async Task<List<ProductHistoryView>> FetchHistory(string companyId, string sku) {
return new List<ProductHistoryView>();
}
return JsonSerializer.Deserialize<List<ProductHistoryView>>(content, _options) ?? new List<ProductHistoryView>();
}
public async Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId, string sku)
{ {
var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmProductExt}/{sku}"); var response = await _client.GetAsync($"{_api.CrmCustomers}/{companyId}/{_api.CrmProductExt}/{sku}");
if (!response.IsSuccessStatusCode)
return new List<ProductHistoryView>();
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
return string.IsNullOrWhiteSpace(content) if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
? new List<ProductHistoryView>() {
: JsonSerializer.Deserialize<List<ProductHistoryView>>(content, _options); return new List<ProductHistoryView>();
}
return JsonSerializer.Deserialize<List<ProductHistoryView>>(content, _options) ?? new List<ProductHistoryView>();
} }
/// <summary>
/// RPC call to initiate remote server sync for given customer
/// </summary>
/// <param name="companyId"></param>
/// <param name="syncDate"></param>
/// <returns></returns>
public async Task<string> InvoiceErpToCrmRpc(string companyId, string syncDate) public async Task<string> InvoiceErpToCrmRpc(string companyId, string syncDate)
{ {
var x = await _client.GetAsync($"{_api.SyncRpc}/companies/{companyId}/invoices/{syncDate}"); var x = await _client.GetAsync($"{_api.SyncRpc}/companies/{companyId}/invoices/{syncDate}");
if (!x.IsSuccessStatusCode) if (!x.IsSuccessStatusCode)
{
return string.Empty; return string.Empty;
}
return await x.Content.ReadAsStringAsync(); return await x.Content.ReadAsStringAsync();
} }
} }

View file

@ -49,15 +49,23 @@ public interface ICrmCustomerHistoryRepository
/// </summary> /// </summary>
/// <param name="companyId"></param> /// <param name="companyId"></param>
/// <returns></returns> /// <returns></returns>
Task<List<ProductHistoryView>> FetchHistory(string companyId); Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId);
/// <summary>
/// Fetch history for customer for the past months. A hard limit on 24 months is enforced by the backed
/// </summary>
/// <param name="companyId"></param>
/// <param name="months"></param>
/// <returns></returns>
Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId, int months);
/// <summary> /// <summary>
/// Fetch history for given customer and a given product /// Fetch history for given customer and a given product
/// </summary> /// </summary>
/// <param name="companyId"></param> /// <param name="companyId"></param>
/// <param name="sku"></param> /// <param name="sku"></param>
/// <returns></returns> /// <returns></returns>
Task<List<ProductHistoryView>> FetchHistory(string companyId, string sku); Task<List<ProductHistoryView>> GetProductInvoiceLines(string companyId, string sku);
/// <summary> /// <summary>
/// RPC call to initiate remote server sync for given customer /// RPC call to initiate remote server sync for given customer

View file

@ -150,11 +150,22 @@ public class AuthenticationService : IAuthenticationService
((AuthStateProvider)_authStateProvider).NotifyUserLogout(); ((AuthStateProvider)_authStateProvider).NotifyUserLogout();
} }
public async Task<string> AccessToken()
{
return await _infoService.GetAccessToken();
}
public async Task<UserManagerEditView> UserInfo(bool write = false) public async Task<UserManagerEditView> UserInfo(bool write = false)
{ {
// use http to fetch userInfo for authenticated user
var response = await _client.GetAsync(_apiConfig.Value.UserInfoAuth).ConfigureAwait(true); var response = await _client.GetAsync(_apiConfig.Value.UserInfoAuth).ConfigureAwait(true);
var content = await response.Content.ReadAsStringAsync(); var content = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode || string.IsNullOrWhiteSpace(content))
{
return new UserManagerEditView();
}
var userInfo = JsonSerializer.Deserialize<UserManagerEditView>(content, _options) ?? new UserManagerEditView(); var userInfo = JsonSerializer.Deserialize<UserManagerEditView>(content, _options) ?? new UserManagerEditView();
if (write) if (write)
{ {

View file

@ -23,6 +23,7 @@ public interface IAuthenticationService
{ {
Task<AuthResponseView> Login(CredentialDto credentials); Task<AuthResponseView> Login(CredentialDto credentials);
Task Logout(); Task Logout();
Task<string> AccessToken();
Task<string> RefreshToken(); Task<string> RefreshToken();
Task<UserManagerEditView> UserInfo(bool write = false); Task<UserManagerEditView> UserInfo(bool write = false);
} }

View file

@ -39,14 +39,13 @@ public class RefreshTokenService
var user = authState.User; var user = authState.User;
var expClaim = user.FindFirst(c => c.Type.Contains("exp"))?.Value; var expClaim = user.FindFirst(c => c.Type.Contains("exp"))?.Value;
//var expTime = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(expClaim));
var expTime = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(expClaim)); var expTime = DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(expClaim));
var diff = expTime - DateTime.UtcNow; var diff = expTime - DateTime.UtcNow;
return diff.TotalMinutes <= 2 return diff.TotalMinutes <= 10
? await _authService.RefreshToken() //.ConfigureAwait(true) ? await _authService.RefreshToken()
: string.Empty; : await _authService.AccessToken();
} }
} }

View file

@ -47,7 +47,7 @@ public partial class CustomerInventoryReorderOverlay
if (string.IsNullOrWhiteSpace(SalesItem.Sku)) if (string.IsNullOrWhiteSpace(SalesItem.Sku))
return; return;
History = await HistoryRepo.FetchHistory(CompanyId, SalesItem.Sku); History = await HistoryRepo.GetProductInvoiceLines(CompanyId, SalesItem.Sku);
if (!History.Any()) if (!History.Any())
await Task.Delay(1000); await Task.Delay(1000);
SelectedItem.Item = SalesItem; SelectedItem.Item = SalesItem;

View file

@ -24,10 +24,15 @@ namespace Wonky.Client.OverlayCustomer;
public partial class CustomerInvoiceViewOverlay : IDisposable public partial class CustomerInvoiceViewOverlay : IDisposable
{ {
[Parameter] public string CompanyId { get; set; } = ""; // ##############################################################
[Parameter] public string InvoiceId { get; set; } = "";
[Inject] public HttpInterceptorService Interceptor { get; set; } [Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public ICrmCustomerHistoryRepository HistoryRepo { get; set; } [Inject] public ICrmCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string InvoiceId { get; set; } = "";
// ##############################################################
private string _modalDisplay = ""; private string _modalDisplay = "";
private bool _showBackdrop; private bool _showBackdrop;
private InvoiceView Invoice { get; set; } = new(); private InvoiceView Invoice { get; set; } = new();

View file

@ -31,22 +31,13 @@ public partial class ProductSelectionOverlay
private List<DocView> FilteredList { get; set; } = new(); private List<DocView> FilteredList { get; set; } = new();
private string SearchTerm { get; set; } = ""; private string SearchTerm { get; set; } = "";
protected override void OnInitialized() protected override void OnInitialized()
{ {
FilterItems(SearchTerm); FilterItems(SearchTerm);
} }
private void ClearSearch()
{
SearchTerm = "";
FilterItems(SearchTerm);
}
private void OnSearchChanged()
{
FilterItems(SearchTerm);
}
private async Task SelectItem(DocView item) private async Task SelectItem(DocView item)
{ {
item.Added = !item.Added; item.Added = !item.Added;
@ -54,6 +45,19 @@ public partial class ProductSelectionOverlay
} }
private void ClearSearch()
{
SearchTerm = "";
FilterItems(SearchTerm);
}
private void OnSearchChanged()
{
FilterItems(SearchTerm);
}
private void FilterItems(string filter) private void FilterItems(string filter)
{ {
SearchTerm = filter; SearchTerm = filter;

View file

@ -22,9 +22,11 @@ namespace Wonky.Client.OverlayOffice;
public partial class OfficeCustomerActivityListOverlay public partial class OfficeCustomerActivityListOverlay
{ {
// ##############################################################
[Parameter] public CompanyDto Company { get; set; } = new(); [Parameter] public CompanyDto Company { get; set; } = new();
[Parameter] public List<ReportItemView> ActivityList { get; set; } = new(); [Parameter] public List<ReportItemView> ActivityList { get; set; } = new();
// ##############################################################
private string _modalDisplay = ""; private string _modalDisplay = "";
private bool _showBackdrop; private bool _showBackdrop;

View file

@ -21,7 +21,10 @@ namespace Wonky.Client.OverlayOffice;
public partial class OfficeCustomerActivityViewOverlay public partial class OfficeCustomerActivityViewOverlay
{ {
// ##############################################################
[Parameter] public ReportItemView Activity { get; set; } = new(); [Parameter] public ReportItemView Activity { get; set; } = new();
// ##############################################################
private string _modalDisplay = ""; private string _modalDisplay = "";
private bool _showBackdrop; private bool _showBackdrop;

View file

@ -30,11 +30,36 @@
<div class="list-group mt-2"> <div class="list-group mt-2">
<div class="list-group-item bg-dark text-white"> <div class="list-group-item bg-dark text-white">
<div class="row"> <div class="row">
<div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Desc))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div> @*
<div class="col-sm-4" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Description))"><i class="bi-sort-alpha-down"></i> Navn <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-3" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div> <div class="col-sm-3" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Sku))"><i class="bi-sort-alpha-down"></i> Varenr <i class="bi-sort-alpha-up-alt"></i></div>
<div class="col-sm-2 text-center" style="cursor: pointer;" @onclick="@(() => SortProducts(ProductSort.Qty))"><i class="bi-sort-numeric-down"></i> Antal <i class="bi-sort-numeric-up-alt"></i></div> *@
<div class="col-sm-2"></div> <div class="col-sm-7 text-end">
<div class="col-sm-1"></div> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="sortOrder" checked @onclick="@SetSortOrder"/>
<label class="form-check-label" for="sortOrder">
<i class="@(Descending ? "bi-sort-up" : "bi-sort-down-alt")"></i>
</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="description" value="description"
onclick="@(() => SortProducts(ProductSort.Description))">
<label class="form-check-label" for="description">Navn</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="itemNumber" value="itemNumber"
onclick="@(() => SortProducts(ProductSort.Sku))">
<label class="form-check-label" for="itemNumber">Vare Nr.</label>
</div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="sortCol" id="lastInvoiceDate" value="itemDate"
onclick="@(() => SortProducts(ProductSort.LastInvoiceDate))" checked>
<label class="form-check-label" for="lastInvoiceDate">Sidst leveret</label>
</div>
</div>
<div class="col-sm-5">
<SearchPhraseComponent OnChanged="OnSearchChanged"/>
</div>
</div> </div>
</div> </div>
@foreach (var product in Inventory) @foreach (var product in Inventory)

View file

@ -16,6 +16,7 @@
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Wonky.Client.Enums; using Wonky.Client.Enums;
using Wonky.Client.Helpers;
using Wonky.Client.HttpInterceptors; using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository; using Wonky.Client.HttpRepository;
using Wonky.Client.Models; using Wonky.Client.Models;
@ -26,81 +27,75 @@ using Wonky.Entity.Views;
namespace Wonky.Client.OverlayOffice; namespace Wonky.Client.OverlayOffice;
public partial class OfficeCustomerProductListOverlay : IDisposable public partial class OfficeCustomerInventoryListOverlay : IDisposable
{ {
// ##############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; } [Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public ICountryCatalogRepository CatalogRepo { get; set; } [Inject] public ICountryCatalogRepository CatalogRepo { get; set; }
[Inject] public ILogger<OfficeCustomerProductListOverlay> Logger { get; set; } [Inject] public ILogger<OfficeCustomerInventoryListOverlay> Logger { get; set; }
// ##############################################################
[Parameter] public CompanyDto Company { get; set; } = new();
[Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new();
// ##############################################################
private string _modalDisplay = ""; private string _modalDisplay = "";
private bool _showBackdrop; private bool _showBackdrop;
private DraftItem DraftItem { get; set; } = new(); private DraftItem DraftItem { get; set; } = new();
private SalesItemView SalesItem { get; set; } = new(); private SalesItemView SalesItem { get; set; } = new();
private OfficeCustomerInventoryReorderOverlay ReorderOverlay { get; set; } = new(); private OfficeCustomerInventoryReorderOverlay ReorderOverlay { get; set; } = new();
private bool Descending { get; set; } private bool Descending { get; set; } = true;
private string SearchTerm { get; set; } = "";
private List<ProductInventoryItemView> FilteredList { get; set; } = new();
private ProductSort SortColumn { get; set; } = ProductSort.LastInvoiceDate;
[Parameter] public CompanyDto Company { get; set; } = new();
[Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new();
protected override void OnParametersSet()
{
if(Inventory.Any())
Inventory = Inventory.OrderBy(x => x.Description).ToList();
}
protected override void OnInitialized() protected override void OnInitialized()
{ {
Interceptor.RegisterEvent(); Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent(); Interceptor.RegisterBeforeSendEvent();
Inventory = Utils.SortInventory(Inventory, SortColumn, Descending);
FilterItems(SearchTerm);
StateHasChanged(); StateHasChanged();
} }
private void OnSearchChanged(string searchTerm)
{
FilterItems(searchTerm);
}
private void SortProducts(ProductSort column)
{
Descending = !Descending;
FilteredList = Utils.SortInventory(FilteredList, column, Descending);
}
private void SetSortOrder()
{
Descending = !Descending;
FilteredList = Utils.SortInventory(FilteredList, SortColumn, Descending);
}
private void FilterItems(string filter)
{
SearchTerm = filter;
FilteredList = string.IsNullOrWhiteSpace(filter)
? Inventory
: Inventory.Where(i => i.Description.ToLower().Contains(filter.ToLower())).ToList();
}
private async Task ShowSkuReorder(string sku) private async Task ShowSkuReorder(string sku)
{ {
SalesItem = await CatalogRepo.GetSalesItemSku(Company.CountryCode.ToLower(), sku); SalesItem = await CatalogRepo.GetSalesItemSku(Company.CountryCode.ToLower(), sku);
ReorderOverlay.Show(); ReorderOverlay.Show();
} }
private void SortProducts(ProductSort column)
{
Descending = !Descending;
switch (column)
{
case ProductSort.Desc:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Description).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Description).ToList();
break;
case ProductSort.Sku:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Sku).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Sku).ToList();
break;
case ProductSort.Qty:
if (Descending)
{
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
Inventory = Inventory.OrderBy(x => x.Quantity).ToList();
break;
case ProductSort.None:
break;
case ProductSort.Abbr:
break;
default:
Inventory = Inventory.OrderByDescending(x => x.Quantity).ToList();
break;
}
}
public void Show() public void Show()
{ {
_modalDisplay = "block;"; _modalDisplay = "block;";
@ -108,6 +103,7 @@ public partial class OfficeCustomerProductListOverlay : IDisposable
StateHasChanged(); StateHasChanged();
} }
private void Hide() private void Hide()
{ {
_modalDisplay = "none;"; _modalDisplay = "none;";
@ -115,9 +111,9 @@ public partial class OfficeCustomerProductListOverlay : IDisposable
StateHasChanged(); StateHasChanged();
} }
public void Dispose() public void Dispose()
{ {
Interceptor.DisposeEvent(); Interceptor.DisposeEvent();
} }
} }

View file

@ -26,10 +26,15 @@ namespace Wonky.Client.OverlayOffice;
public partial class OfficeCustomerInventoryReorderOverlay public partial class OfficeCustomerInventoryReorderOverlay
{ {
// ##############################################################
[Parameter] public CompanyDto Company { get; set; } [Parameter] public CompanyDto Company { get; set; }
[Parameter] public SalesItemView SalesItem { get; set; } = new(); [Parameter] public SalesItemView SalesItem { get; set; } = new();
[Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; } [Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
[Parameter] public EventCallback<DraftItem> OrderItemCallback { get; set; } [Parameter] public EventCallback<DraftItem> OrderItemCallback { get; set; }
// ##############################################################
private List<ProductHistoryView> History { get; set; } = new(); private List<ProductHistoryView> History { get; set; } = new();
private DraftItem SelectedItem { get; set; } = new(); private DraftItem SelectedItem { get; set; } = new();
private string ProductName { get; set; } = ""; private string ProductName { get; set; } = "";

View file

@ -24,12 +24,16 @@ namespace Wonky.Client.OverlayOffice;
public partial class OfficeCustomerInvoiceViewOverlay : IDisposable public partial class OfficeCustomerInvoiceViewOverlay : IDisposable
{ {
// ##############################################################
[Parameter] public string CompanyId { get; set; } = ""; [Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string InvoiceId { get; set; } = ""; [Parameter] public string InvoiceId { get; set; } = "";
[Parameter] public string CountryCode { get; set; } = ""; [Parameter] public string CountryCode { get; set; } = "";
// ##############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; } [Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; } [Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
private string _modalDisplay = ""; private string _modalDisplay = "";
private bool _showBackdrop; private bool _showBackdrop;
private InvoiceView Invoice { get; set; } = new(); private InvoiceView Invoice { get; set; } = new();

View file

@ -23,7 +23,7 @@
<button type="button" class="btn-close" @onclick="@Hide" data-bs-dismiss="modal" aria-label="Luk"></button> <button type="button" class="btn-close" @onclick="@Hide" data-bs-dismiss="modal" aria-label="Luk"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<OfficeInventoryListComponent OnReorderSelected="OnReorderCallback" CompanyId="@Company.CompanyId" Inventory="@Inventory"/> <OfficeCustomerInventoryListComponent OnReorderSelected="OnReorderCallback" CompanyId="@Company.CompanyId" Inventory="@Inventory"/>
</div> </div>
</div> </div>
</div> </div>
@ -33,4 +33,4 @@
<div class="modal-backdrop fade show"></div> <div class="modal-backdrop fade show"></div>
} }
<OfficeOrderInventoryReorderOverlay Company="@Company" SalesItem="SalesItem" OnSelected="OnSelected" @ref="ReorderOverlay" /> <OfficeCustomerOrderInventoryReorderOverlay Company="@Company" SalesItem="SalesItem" OnSelected="OnSelected" @ref="ReorderOverlay" />

View file

@ -24,23 +24,26 @@ using Wonky.Entity.Views;
namespace Wonky.Client.OverlayOffice; namespace Wonky.Client.OverlayOffice;
public partial class OfficeOrderInventoryListOverlay : IDisposable public partial class OfficeCustomerOrderInventoryListOverlay : IDisposable
{ {
// ##############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; } [Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public ICountryCatalogRepository CatalogRepo { get; set; } [Inject] public ICountryCatalogRepository CatalogRepo { get; set; }
[Inject] public ILogger<OfficeOrderInventoryListOverlay> Logger { get; set; } [Inject] public ILogger<OfficeCustomerOrderInventoryListOverlay> Logger { get; set; }
// ##############################################################
[Parameter] public CompanyDto Company { get; set; } = new(); [Parameter] public CompanyDto Company { get; set; } = new();
[Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new(); [Parameter] public List<ProductInventoryItemView> Inventory { get; set; } = new();
[Parameter] public EventCallback<DraftItem> OnSelected { get; set; } [Parameter] public EventCallback<DraftItem> OnSelected { get; set; }
// ##############################################################
private string _modalDisplay = ""; private string _modalDisplay = "";
private bool _showBackdrop; private bool _showBackdrop;
// private List<ProductInventoryView> ProductList { get; set; } = new(); // private List<ProductInventoryView> ProductList { get; set; } = new();
private DraftItem DraftItem { get; set; } = new(); private DraftItem DraftItem { get; set; } = new();
private SalesItemView SalesItem { get; set; } = new(); private SalesItemView SalesItem { get; set; } = new();
private OfficeOrderInventoryReorderOverlay ReorderOverlay { get; set; } = new(); private OfficeCustomerOrderInventoryReorderOverlay ReorderOverlay { get; set; } = new();
protected override void OnParametersSet() protected override void OnParametersSet()
{ {

View file

@ -24,12 +24,17 @@ using Wonky.Entity.Views;
namespace Wonky.Client.OverlayOffice; namespace Wonky.Client.OverlayOffice;
public partial class OfficeOrderInventoryReorderOverlay public partial class OfficeCustomerOrderInventoryReorderOverlay
{ {
// ##############################################################
[Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; } [Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
[Parameter] public CompanyDto Company { get; set; } = new(); [Parameter] public CompanyDto Company { get; set; } = new();
[Parameter] public SalesItemView SalesItem { get; set; } = new(); [Parameter] public SalesItemView SalesItem { get; set; } = new();
[Parameter] public EventCallback<DraftItem> OnSelected { get; set; } [Parameter] public EventCallback<DraftItem> OnSelected { get; set; }
// ##############################################################
private List<ProductHistoryView>? ProductHistory { get; set; } = new(); private List<ProductHistoryView>? ProductHistory { get; set; } = new();
private DraftItem SelectedItem { get; set; } = new(); private DraftItem SelectedItem { get; set; } = new();
private string ProductName { get; set; } = ""; private string ProductName { get; set; } = "";

View file

@ -29,17 +29,17 @@ namespace Wonky.Client.OverlayOrderCreate;
public partial class CatalogPagedOverlay : IDisposable public partial class CatalogPagedOverlay : IDisposable
{ {
// injections // ##############################################################
[Inject] public ICountryCatalogRepository CatalogRepo { get; set; } [Inject] public ICountryCatalogRepository CatalogRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; } [Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public UserPreferenceService PreferenceService { get; set; } [Inject] public UserPreferenceService PreferenceService { get; set; }
[Inject] public ILogger<CatalogPagedOverlay> Logger { get; set; } [Inject] public ILogger<CatalogPagedOverlay> Logger { get; set; }
// parameters // ##############################################################
[Parameter] public string CountryCode { get; set; } = ""; [Parameter] public string CountryCode { get; set; } = "";
[Parameter] public EventCallback<SelectedSku> OnSelected { get; set; } [Parameter] public EventCallback<SelectedSku> OnSelected { get; set; }
// variables // ##############################################################
private string _modalDisplay = ""; private string _modalDisplay = "";
private bool _showBackdrop; private bool _showBackdrop;
private List<SalesItemView> Items { get; set; } = new(); private List<SalesItemView> Items { get; set; } = new();

View file

@ -21,12 +21,15 @@ namespace Wonky.Client.OverlayOrderCreate;
public partial class ProductCheckConfirmationOverlay public partial class ProductCheckConfirmationOverlay
{ {
private string _modalDisplay = ""; // ##############################################################
private bool _showBackdrop;
[Parameter] public string BodyMessage { get; set; } = ""; [Parameter] public string BodyMessage { get; set; } = "";
[Parameter] public string CompanyId { get; set; } = ""; [Parameter] public string CompanyId { get; set; } = "";
[Parameter] public List<ProductInventoryItemView> Products { get; set; } = new(); [Parameter] public List<ProductInventoryItemView> Products { get; set; } = new();
[Parameter] public EventCallback OnOkClicked { get; set; } [Parameter] public EventCallback OnOkClicked { get; set; }
// ##############################################################
private string _modalDisplay = "";
private bool _showBackdrop;
public void Show() public void Show()
{ {

View file

@ -24,10 +24,14 @@ namespace Wonky.Client.OverlayOrderCreate;
public partial class ProductHistoryOverlay public partial class ProductHistoryOverlay
{ {
// [Parameter] public EventCallback<decimal> OnSelected { get; set; } // ##############################################################
[Inject] public ICrmCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
[Parameter] public string CompanyId { get; set; } = ""; [Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string ItemSku { get; set; } = ""; [Parameter] public string ItemSku { get; set; } = "";
[Inject] public ICrmCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
private List<ProductHistoryView>? History { get; set; } private List<ProductHistoryView>? History { get; set; }
private string ProductName { get; set; } = ""; private string ProductName { get; set; } = "";
private string _modalDisplay = ""; private string _modalDisplay = "";
@ -35,18 +39,13 @@ public partial class ProductHistoryOverlay
protected override async Task OnParametersSetAsync() protected override async Task OnParametersSetAsync()
{ {
History = await HistoryRepo.FetchHistory(CompanyId, ItemSku); History = await HistoryRepo.GetProductInvoiceLines(CompanyId, ItemSku);
if (History.Any()) if (History.Any())
{ {
ProductName = History[0].Description; ProductName = History[0].Description;
} }
} }
// private void SelectPrice(decimal price)
// {
// OnSelected.InvokeAsync(price);
// Hide();
// }
public void Show() public void Show()
{ {
@ -55,6 +54,7 @@ public partial class ProductHistoryOverlay
StateHasChanged(); StateHasChanged();
} }
private void Hide() private void Hide()
{ {
_modalDisplay = "none;"; _modalDisplay = "none;";

View file

@ -24,10 +24,15 @@ namespace Wonky.Client.OverlayOrderCreate;
public partial class ProductPriceHistoryOverlay public partial class ProductPriceHistoryOverlay
{ {
// ##############################################################
[Inject] public ICrmCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
[Parameter] public EventCallback<decimal> OnSelected { get; set; } [Parameter] public EventCallback<decimal> OnSelected { get; set; }
[Parameter] public string CompanyId { get; set; } = ""; [Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string Sku { get; set; } = ""; [Parameter] public string Sku { get; set; } = "";
[Inject] public ICrmCustomerHistoryRepository HistoryRepo { get; set; }
// ##############################################################
private List<ProductHistoryView>? ProductHistory { get; set; } private List<ProductHistoryView>? ProductHistory { get; set; }
private string ProductName { get; set; } = ""; private string ProductName { get; set; } = "";
private string _modalDisplay = ""; private string _modalDisplay = "";
@ -38,19 +43,21 @@ public partial class ProductPriceHistoryOverlay
if (string.IsNullOrWhiteSpace(Sku)) if (string.IsNullOrWhiteSpace(Sku))
return; return;
ProductHistory = await HistoryRepo.FetchHistory(CompanyId, Sku); ProductHistory = await HistoryRepo.GetProductInvoiceLines(CompanyId, Sku);
if (ProductHistory.Any()) if (ProductHistory.Any())
{ {
ProductName = ProductHistory[0].Description; ProductName = ProductHistory[0].Description;
} }
} }
private void SelectPrice(decimal price) private void SelectPrice(decimal price)
{ {
OnSelected.InvokeAsync(price); OnSelected.InvokeAsync(price);
Hide(); Hide();
} }
public void Show() public void Show()
{ {
_modalDisplay = "block;"; _modalDisplay = "block;";
@ -58,6 +65,7 @@ public partial class ProductPriceHistoryOverlay
StateHasChanged(); StateHasChanged();
} }
private void Hide() private void Hide()
{ {
_modalDisplay = "none;"; _modalDisplay = "none;";

View file

@ -22,7 +22,7 @@
<PageTitle>Produkt oversigt for @Company.Name</PageTitle> <PageTitle>Produkt oversigt for @Company.Name</PageTitle>
<div class="row ps-3 pt-2 pb-1 rounded-2 bg-dark text-white"> <div class="row ps-3 pt-2 pb-1 rounded-2 bg-dark text-white">
<div class="col-sm-6"> <div class="col-sm-6">
<h4 class="pt-1">Produkt oversigt @Company.Name</h4> <h4 class="pt-1">Produkt køb @Company.Name</h4>
</div> </div>
<div class="col-sm-3 align-content-end d-print-none"> <div class="col-sm-3 align-content-end d-print-none">
<a class="btn btn-primary d-block" href="/advisor/customers/@CompanyId"><i class="bi-chevron-left"></i> Stamkort</a> <a class="btn btn-primary d-block" href="/advisor/customers/@CompanyId"><i class="bi-chevron-left"></i> Stamkort</a>
@ -33,10 +33,9 @@
</div> </div>
<CustomerInventoryListComponent OnReorderSelected="OnReorderCallback" CompanyId="@CompanyId" Inventory="@Inventory"/> <CustomerInventoryListComponent OnReorderSelected="OnReorderCallback" CompanyId="@CompanyId" Inventory="@Inventory"/>
<CustomerInventoryReorderOverlay OnSelected="@OnSelectedItem" CompanyId="@CompanyId" SalesItem="@SalesItem" @ref="ReorderOverlay"/>
@if (Working) @if (Working)
{ {
<WorkingThreeDots/> <WorkingThreeDots/>
} }
<CustomerInventoryReorderOverlay OnSelected="@OnSelectedItem" CompanyId="@CompanyId" SalesItem="@SalesItem" @ref="ReorderOverlay"/>

View file

@ -19,6 +19,8 @@ using System.Xml;
using Blazored.LocalStorage; using Blazored.LocalStorage;
using Blazored.Toast.Services; using Blazored.Toast.Services;
using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components;
using Wonky.Client.Enums;
using Wonky.Client.Helpers;
using Wonky.Client.HttpInterceptors; using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository; using Wonky.Client.HttpRepository;
using Wonky.Client.Models; using Wonky.Client.Models;
@ -65,7 +67,8 @@ public partial class AdvisorCustomerInventoryListPage : IDisposable
Working = false; Working = false;
} }
private async Task OnReorderCallback(string sku) private async Task OnReorderCallback(string sku)
{ {
// fetch item from http repo // fetch item from http repo

View file

@ -311,12 +311,9 @@
<button type="button" class="btn btn-outline-dark" @onclick="@ToggleVisibility">@ToggleButtonText</button> <button type="button" class="btn btn-outline-dark" @onclick="@ToggleVisibility">@ToggleButtonText</button>
</div> </div>
<div class="col-sm-4 d-grid"> <div class="col-sm-4 d-grid">
@if (AppInfo!.Value!.Rc) @if (UserInfo.CountryCode is "DK")
{ {
@if (UserInfo.CountryCode is "DK") <a class="btn btn-info" href="@($"/advisor/customers/{CompanyId}/workplaces")">Kemi Dokumentation</a>
{
<a class="btn btn-info" href="@($"/advisor/customers/{CompanyId}/workplaces")">Kemi Dokumentation</a>
}
} }
</div> </div>
</div> </div>

View file

@ -33,12 +33,11 @@
<div class="col-sm-12 col-md-6"> <div class="col-sm-12 col-md-6">
<InfoColorCustomerComponent/> <InfoColorCustomerComponent/>
</div> </div>
@*
<div class="col-sm-12 col-md-6"> <div class="col-sm-12 col-md-6">
<InfoColorPackageComponent/> <InfoColorPackageComponent/>
</div> </div>
<div class="col-sm-12 col-md-6"> *@
<InfoCommonComponent />
</div>
<div class="col-sm-12 col-md-6"> <div class="col-sm-12 col-md-6">
<InfoProcessStateComponent /> <InfoProcessStateComponent />
</div> </div>
@ -48,12 +47,17 @@
<div class="col-sm-12 col-md-6"> <div class="col-sm-12 col-md-6">
<InfoQuoteToolbarComponent/> <InfoQuoteToolbarComponent/>
</div> </div>
<div class="col-sm-12 col-md-6">
<InfoCommonComponent />
</div>
<div class="col-sm-12 col-md-6"> <div class="col-sm-12 col-md-6">
<InfoOfficeComponent /> <InfoOfficeComponent />
</div> </div>
@*
<div class="col-sm-12 col-md-6"> <div class="col-sm-12 col-md-6">
<InfoWarehouseComponent /> <InfoWarehouseComponent />
</div> </div>
*@
<div class="col-sm-12 col-md-6"> <div class="col-sm-12 col-md-6">
<InfoBrowserComponent /> <InfoBrowserComponent />
</div> </div>

View file

@ -245,4 +245,4 @@
<CatalogPagedOverlay @ref="CatalogOverlay" CountryCode="@CountryCode" OnSelected="PriceListCallback"/> <CatalogPagedOverlay @ref="CatalogOverlay" CountryCode="@CountryCode" OnSelected="PriceListCallback"/>
<OfficeCustomerInvoiceListOverlay @ref="InvoiceListOverlay" Company="Company" InvoiceList="CompanyInvoices"/> <OfficeCustomerInvoiceListOverlay @ref="InvoiceListOverlay" Company="Company" InvoiceList="CompanyInvoices"/>
<OfficeCustomerActivityListOverlay @ref="ActivityListOverlay" Company="Company" ActivityList="CompanyActivities"/> <OfficeCustomerActivityListOverlay @ref="ActivityListOverlay" Company="Company" ActivityList="CompanyActivities"/>
<OfficeOrderInventoryListOverlay @ref="InventoryListOverlay" Company="Company" Inventory="CompanyInventory" OnSelected="InventoryCallback"/> <OfficeCustomerOrderInventoryListOverlay @ref="InventoryListOverlay" Company="Company" Inventory="CompanyInventory" OnSelected="InventoryCallback"/>

View file

@ -80,7 +80,7 @@ public partial class OfficeOrderCreatePage : IDisposable
private CatalogPagedOverlay CatalogOverlay { get; set; } = new(); private CatalogPagedOverlay CatalogOverlay { get; set; } = new();
private OfficeCustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new(); private OfficeCustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new();
private OfficeCustomerActivityListOverlay ActivityListOverlay { get; set; } = new(); private OfficeCustomerActivityListOverlay ActivityListOverlay { get; set; } = new();
private OfficeOrderInventoryListOverlay InventoryListOverlay { get; set; } = new(); private OfficeCustomerOrderInventoryListOverlay InventoryListOverlay { get; set; } = new();
// ############################################################# // #############################################################
// lists // lists

View file

@ -37,6 +37,7 @@ public partial class SupervisorDocumentNewPage : IDisposable
[Inject] public ILogger<SupervisorDocumentNewPage> Logger { get; set; } [Inject] public ILogger<SupervisorDocumentNewPage> Logger { get; set; }
[Inject] public IToastService Toaster { get; set; } [Inject] public IToastService Toaster { get; set; }
[Inject] public NavigationManager Navigator { get; set; } [Inject] public NavigationManager Navigator { get; set; }
[Inject] public ISystemSendMailService SendMail { get; set; }
// ############################################################ // ############################################################
@ -102,6 +103,25 @@ public partial class SupervisorDocumentNewPage : IDisposable
else else
{ {
Toaster.ShowSuccess("Ok"); Toaster.ShowSuccess("Ok");
var sendTo = new List<EmailContact>
{
new () { Email = "paul@innotec.dk", Name = "Paul Vendelbo" },
new () { Email = "eddie@innotec.dk", Name = "Eddie Broch" }
};
var msg = new EmailMessage
{
Body = Document.Content,
Subject = Document.Description,
To = sendTo,
IsBodyHtml = false
};
Toaster.ShowInfo("Sender email ...");
var result = await SendMail.SendMail("system", msg);
if (result.IsSuccess)
{
Toaster.ClearAll();
Toaster.ShowInfo("Email sendt.");
}
Navigator.NavigateTo($"/supervisor/advisors/{AdvisorId}/documents"); Navigator.NavigateTo($"/supervisor/advisors/{AdvisorId}/documents");
} }
Toaster.ClearAll(); Toaster.ClearAll();

View file

@ -82,7 +82,7 @@
<div class="card-footer"> <div class="card-footer">
<div class="row"> <div class="row">
<div class="col text-start"> <div class="col text-start">
<button type="button" class="btn btn-warning" nclick="@RemoveDocument"><i class="bi-trash"></i> Slet</button> <button type="button" class="btn btn-warning" @onclick="@RemoveDocument"><i class="bi-trash"></i> Slet</button>
</div> </div>
<div class="col text-end"> <div class="col text-end">
<button type="submit" class="btn btn-primary"><i class="bi-cloud-upload"></i> Gem</button> <button type="submit" class="btn btn-primary"><i class="bi-cloud-upload"></i> Gem</button>

View file

@ -1,36 +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 GNU Affero 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
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@using Wonky.Client.Components
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Roles = "Admin,Office,Warehouse")]
@page "/warehouse/orders/{Status}"
<div class="row">
<div class="col">
<h2>Forsendelser</h2>
</div>
<div class="col">
<div class="busy-signal" style="display:@(Working ? "block" : "none")">
<div class="spinner-grow text-info" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
<WarehouseListComponent Header="@Header" OrderList="@OrderList" ReadyToShip="@ReadyToShip"
OnGetStatus="GetStatusCallback" OnSetShipped="@SetShippedCallback" OnQPak="QPakCallback" />

View file

@ -1,133 +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 GNU Affero 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
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero 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.Enums;
using Wonky.Client.Helpers;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Client.Models;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages;
public partial class WarehouseOrderListPage : IDisposable
{
// #############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public IOrderProcessRepository OrderProcessRepo { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
// #############################################################
[Parameter] public string Status { get; set; } = "none";
// #############################################################
private List<WarehouseOrderView> OrderList { get; set; } = new();
private bool Working { get; set; } = true;
private string Header { get; set; } = "Ubehandlet";
private bool ReadyToShip { get; set; }
protected override async Task OnParametersSetAsync()
{
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
OrderList = await FetchOrders(Status);
}
private async Task GetStatusCallback(ProcessStatus status)
{
Working = true;
OrderList = new List<WarehouseOrderView>();
switch (status)
{
case ProcessStatus.None or ProcessStatus.Printed:
Header = "Ubehandlede ordrer";
ReadyToShip = false;
break;
case ProcessStatus.Picked:
Header = "Plukkede ordrer";
ReadyToShip = false;
break;
case ProcessStatus.Packed:
Header = "Pakkede ordrer";
ReadyToShip = true;
break;
case ProcessStatus.Shipped:
break;
case ProcessStatus.All:
break;
case ProcessStatus.Express:
break;
default:
throw new ArgumentOutOfRangeException(nameof(status), status, null);
}
Status = Utils.EnumToString(status).ToLower();
OrderList = await FetchOrders(Status);
Working = false;
}
/// <summary>
/// QPak - quick mark an order as shipped
/// </summary>
/// <param name="orderId"></param>
private async Task QPakCallback(string orderId)
{
Working = true;
var order = OrderList.First(x => x.OrderId == orderId);
order.ProcessStatusEnum = "packed";
var process = new OrderProcessState
{
OrderId = order.OrderId,
ProcessStatusEnum = "packed"
};
await OrderProcessRepo.UpdateWarehouseOrderStatus(process);
OrderList.Remove(order);
Working = false;
}
/// <summary>
/// Set status shipped where status is packed
/// </summary>
private async Task SetShippedCallback()
{
Working = true;
foreach (var order in OrderList.Where(order => order.ProcessStatusEnum.ToLower() == "packed"))
{
order.ProcessStatusEnum = "shipped";
var process = new OrderProcessState
{
OrderId = order.OrderId,
ProcessStatusEnum = "shipped"
};
await OrderProcessRepo.UpdateWarehouseOrderStatus(process);
}
Working = false;
}
private async Task<List<WarehouseOrderView>> FetchOrders(string status)
{
Working = true;
var orderList = await OrderProcessRepo.GetWarehouseOrderListByStatus(status.ToLower());
if(orderList.Any(x => x.Express))
orderList = orderList.OrderByDescending(x => x.Express).ToList();
Working = false;
return orderList;
}
public void Dispose() => Interceptor.DisposeEvent();
}

View file

@ -1,129 +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 GNU Affero 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
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components
@attribute [Authorize(Roles = "Admin,Office,Warehouse")]
@page "/warehouse/orders/process/{OrderId}"
@if (!string.IsNullOrWhiteSpace(Order.OrderDate))
{
<table class="table">
<thead>
<tr>
<th colspan="4">
@if (Order.Express)
{
<h2 class="text-center fw-bold">HASTE ORDRE</h2>
}
<h2 class="text-center">@Order.Company.Name</h2>
</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Konto</th>
<td>@Order.Company.Account</td>
<th scope="row">Telefon</th>
<td>@Order.Company.Phone</td>
</tr>
<tr>
<th scope="row">Kunde</th>
<td>@Order.Company.Name</td>
<th scope="row">Lev.Navn</th>
<td>@Order.DlvName</td>
</tr>
<tr>
<th scope="row">Adresse</th>
<td>@Order.Company.Address1</td>
<th scope="row">Lev.Adresse</th>
<td>@Order.DlvAddress1</td>
</tr>
<tr>
<th scope="row">Adresse</th>
<td>@Order.Company.Address2</td>
<th scope="row">Lev.Adresse</th>
<td>@Order.DlvAddress2</td>
</tr>
<tr>
<th scope="row">Postnr By</th>
<td>@Order.Company.ZipCode @Order.Company.City</td>
<th scope="row">Lev.Postnr By</th>
<td>@Order.DlvZipCity</td>
</tr>
</tbody>
</table>
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Placering</th>
<th scope="col">Antal</th>
<th scope="col">Varenr</th>
<th scope="col">Beskrivelse</th>
</tr>
</thead>
<tbody>
@foreach (var line in Order.Lines)
{
<tr>
<td class="fw-bold">@line.Location</td>
<td>@line.Quantity</td>
<td>@line.Sku</td>
<td>
<div class="form-check">
<input class="form-check-input" type="checkbox" id="@line.LineNumber"/>
<label class="form-check-label" for="@line.LineNumber">@line.Description</label>
</div>
</td>
</tr>
}
</tbody>
</table>
@if (!string.IsNullOrWhiteSpace(Order.OfficeNote))
{
<div class="alert bg-light border-dark border-2">
<h3 class="text-center">@Order.OfficeNote</h3>
</div>
}
<div class="row">
<div class="col-md-3">
<a class="btn btn-outline-success text-nowrap" href="/warehouse/orders/none">Ubehandlet</a>
</div>
<div class="col-md-3">
</div>
<div class="col-md-4">
@if (Order.ProcessStatusEnum.ToLower() is "none" or "printed")
{
<button class="btn btn-lg btn-warning text-nowrap" type="button" @onclick="@SetProcessStatusPicked" disabled="@Working">Sæt status plukket</button>
}
@if (Order.ProcessStatusEnum.ToLower() == "picked")
{
<button class="btn btn-lg btn-danger text-nowrap" type="button" @onclick="@SetProcessStatusPacked" disabled="@Working">Sæt status pakket</button>
}
@if (Order.ProcessStatusEnum.ToLower() == "packed")
{
<button class="btn btn-lg btn-primary text-nowrap" type="button" @onclick="@SetProcessStatusShipped" disabled="@Working">Sæt status afsendt</button>
}
</div>
</div>
}
@if (Working)
{
<WorkingThreeDots/>
}

View file

@ -1,103 +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 GNU Affero 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
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System.Text.Json;
using Blazored.Toast.Services;
using Microsoft.AspNetCore.Components;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages;
public partial class WarehouseOrderViewPage : IDisposable
{
// #############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public IOrderProcessRepository OrderProcessRepo { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IToastService Toast { get; set; }
[Inject] public ILogger<WarehouseOrderViewPage> Logger { get; set; }
// #############################################################
[Parameter] public string OrderId { get; set; } = "";
// #############################################################
private WarehouseOrderView Order { get; set; } = new();
private bool Working { get; set; } = true;
protected override async Task OnParametersSetAsync()
{
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
if (!string.IsNullOrWhiteSpace(OrderId))
Order = await OrderProcessRepo.GetWarehouseOrder(OrderId);
Logger.LogDebug("Warehouse OrderView =>\n{}", JsonSerializer.Serialize(Order));
Working = false;
}
private async Task SetProcessStatusPicked()
{
if (Working)
return;
Working = true;
var process = new OrderProcessState
{
OrderId = Order.OrderId,
ProcessStatusEnum = "picked"
};
await OrderProcessRepo.UpdateWarehouseOrderStatus(process);
Navigator.NavigateTo("/warehouse/orders/none");
}
private async Task SetProcessStatusPacked()
{
if (Working)
return;
Working = true;
var process = new OrderProcessState
{
OrderId = Order.OrderId,
ProcessStatusEnum = "packed"
};
await OrderProcessRepo.UpdateWarehouseOrderStatus(process);
Navigator.NavigateTo("/warehouse/orders/picked");
}
private async Task SetProcessStatusShipped()
{
if (Working)
return;
Working = true;
var process = new OrderProcessState
{
OrderId = Order.OrderId,
ProcessStatusEnum = "shipped"
};
await OrderProcessRepo.UpdateWarehouseOrderStatus(process);
Navigator.NavigateTo("/warehouse/orders/none");
}
public void Dispose()
{
Interceptor.DisposeEvent();
}
}

View file

@ -15,6 +15,7 @@
using System.Net.Http.Headers; using System.Net.Http.Headers;
using System.Security.Claims; using System.Security.Claims;
using System.Text.Json;
using Blazored.LocalStorage; using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Components.Authorization;
using Wonky.Client.Local.Services; using Wonky.Client.Local.Services;
@ -26,35 +27,43 @@ namespace Wonky.Client.Shared;
public class AuthStateProvider : AuthenticationStateProvider public class AuthStateProvider : AuthenticationStateProvider
{ {
private readonly HttpClient _client; private readonly HttpClient _client;
// private readonly ILocalStorageService _storage;
private readonly AuthenticationState _anonymous; private readonly AuthenticationState _anonymous;
private readonly IUserInfoService _infoService; private readonly IUserInfoService _infoService;
private readonly ILogger<AuthStateProvider> _logger;
public AuthStateProvider(HttpClient client, IUserInfoService infoService)
public AuthStateProvider(HttpClient client, IUserInfoService infoService, ILogger<AuthStateProvider> logger)
{ {
_client = client; _client = client;
_infoService = infoService; _infoService = infoService;
_logger = logger;
_anonymous = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); _anonymous = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
} }
public override async Task<AuthenticationState> GetAuthenticationStateAsync() public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{ {
var token = await _infoService.GetAccessToken(); var token = await _infoService.GetAccessToken();
_logger.LogDebug("accessToken {}", token);
if (string.IsNullOrEmpty(token)) if (string.IsNullOrEmpty(token))
{ {
return _anonymous; return _anonymous;
} }
var userInfo = await _infoService.GetUserInfo(); var userInfo = await _infoService.GetUserInfo();
if (string.IsNullOrWhiteSpace(userInfo.UserId)) _logger.LogDebug("userInfo {}", JsonSerializer.Serialize(userInfo));
if (userInfo == null || string.IsNullOrWhiteSpace(userInfo.FirstName))
{ {
NotifyUserLogout();
return _anonymous; return _anonymous;
} }
_logger.LogDebug("userInfo.FirstName {}", userInfo.FirstName);
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var exp = await _infoService.GetExpiration(); var exp = await _infoService.GetExpiration();
_logger.LogDebug("expiration {}", exp);
var claims = new List<Claim> var claims = new List<Claim>
{ {
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"), new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
@ -64,30 +73,37 @@ public class AuthStateProvider : AuthenticationStateProvider
new(ClaimTypes.Expiration, exp.ToString()) new(ClaimTypes.Expiration, exp.ToString())
}; };
claims.AddRange( claims.AddRange(
from role in userInfo.AssignedRoles from role in userInfo.AssignedRoles
where role.Assigned select new Claim(ClaimTypes.Role, role.Name) where role.Assigned
); select new Claim(ClaimTypes.Role, role.Name)
claims.Add( new Claim(ClaimTypes.Role, userInfo.CountryCode)); );
// return the authState for the user // return the authState for the user
return new AuthenticationState( return new AuthenticationState(
new ClaimsPrincipal( new ClaimsPrincipal(
new ClaimsIdentity(claims, "token"))); new ClaimsIdentity(claims, "token"))
);
} }
public async void NotifyUserAuthenticationAsync(string token) public async void NotifyUserAuthenticationAsync(string token)
{ {
if (string.IsNullOrEmpty(token)) if (string.IsNullOrEmpty(token))
{ {
NotifyUserLogout();
return; return;
} }
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var userInfo = await _infoService.GetUserInfo(); var userInfo = await _infoService.GetUserInfo();
if (string.IsNullOrWhiteSpace(userInfo.UserId))
{
NotifyUserLogout();
return;
}
var exp = await _infoService.GetExpiration(); var exp = await _infoService.GetExpiration();
var claims = new List<Claim> var claims = new List<Claim>
{ {
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"), new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
@ -97,18 +113,22 @@ public class AuthStateProvider : AuthenticationStateProvider
new(ClaimTypes.Expiration, exp.ToString()) new(ClaimTypes.Expiration, exp.ToString())
}; };
claims.AddRange( claims.AddRange(
from role in userInfo.AssignedRoles from role in userInfo.AssignedRoles
where role.Assigned select new Claim(ClaimTypes.Role, role.Name)); where role.Assigned
select new Claim(ClaimTypes.Role, role.Name)
);
var authState = Task.FromResult( var authState = Task.FromResult(
new AuthenticationState( new AuthenticationState(
new ClaimsPrincipal( new ClaimsPrincipal(
new ClaimsIdentity(claims, "token")))); new ClaimsIdentity(claims, "token"))
)
);
NotifyAuthenticationStateChanged(authState); NotifyAuthenticationStateChanged(authState);
} }
public void NotifyUserLogout() public void NotifyUserLogout()
{ {
_client.DefaultRequestHeaders.Authorization = null; _client.DefaultRequestHeaders.Authorization = null;

View file

@ -66,11 +66,13 @@
<i class="bi-building pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Kunder SE <i class="bi-building pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Kunder SE
</NavLink> </NavLink>
</div> </div>
@*
<div class="nav-item px-3"> <div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/warehouse/orders/none"> <NavLink class="nav-link ps-2" href="/warehouse/orders/none">
<i class="bi-box pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Forsendelse <i class="bi-box pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Forsendelse
</NavLink> </NavLink>
</div> </div>
*@
</AuthorizeView> </AuthorizeView>
<AuthorizeView Roles="Advisor"> <AuthorizeView Roles="Advisor">

View file

@ -1,15 +1,15 @@
{ {
"appInfo": { "appInfo": {
"name": "Wonky Online", "name": "Wonky Online",
"version": "153.0", "version": "157.1",
"rc": true, "rc": true,
"sandBox": false, "sandBox": false,
"image": "grumpy-coder.png" "image": "grumpy-coder.png"
}, },
"Logging": { "Logging": {
"LogLevel": { "LogLevel": {
"Default": "Information", "Default": "Debug",
"System": "Information", "System": "Debug",
"Microsoft": "Information" "Microsoft": "Information"
}, },
"Debug": { "Debug": {
@ -19,7 +19,7 @@
} }
}, },
"apiConfig": { "apiConfig": {
"baseUrl": "https://zeta.innotec.dk", "baseUrl": "https://dev.innotec.dk",
"catalog": "api/v2/catalog/country", "catalog": "api/v2/catalog/country",
"crmCustomers": "api/v2/crm/companies", "crmCustomers": "api/v2/crm/companies",
"crmInventoryExt": "history/inventory", "crmInventoryExt": "history/inventory",

View file

@ -16,6 +16,14 @@ body {
.workDate { .workDate {
font-variant: small-caps; font-variant: small-caps;
} }
.action-link-element {
cursor: pointer;
}
.i-larger {
font-size: 1.3rem;
}
.btn.btn-edit { .btn.btn-edit {
color: #030303; color: #030303;
background-color: #a2a2ec; background-color: #a2a2ec;
@ -219,13 +227,6 @@ a, .btn-link {
} }
/* end led elements */ /* end led elements */
.action-link-element {
cursor: pointer;
}
.i-larger {
font-size: 1.3rem;
}
.footer { .footer {
border-top: 2px solid orange; border-top: 2px solid orange;
position: relative; position: relative;

View file

@ -17,11 +17,17 @@ namespace Wonky.Entity.Views;
public class ProductInventoryItemView public class ProductInventoryItemView
{ {
public virtual bool AgedProduct()
{
return DateTime.Parse(LastInvoiceDate) < DateTime.Now.AddMonths(-12);
}
public virtual bool Check { get; set; } public virtual bool Check { get; set; }
public string Description { get; set; } = ""; public string Description { get; set; } = "";
public bool Discontinued { get; set; } public bool Discontinued { get; set; }
public string PictureLink { get; set; } = ""; public string PictureLink { get; set; } = "";
public int Quantity { get; set; } public int Quantity { get; set; }
public string LastInvoiceDate { get; set; } = "";
public string Sku { get; set; } = ""; public string Sku { get; set; } = "";
public string VendorItemNo { get; set; } = ""; public string VendorItemNo { get; set; } = "";
} }