refactor work date component - refactor inventory page

adding product from inventory page using modal
show current product prices above history
This commit is contained in:
Frede Hundewadt 2022-10-15 07:48:44 +02:00
parent 7f4c2cb297
commit fbc9e21461
43 changed files with 1077 additions and 650 deletions

View file

@ -1,2 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=kontrolleres/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Venligst/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View file

@ -36,5 +36,4 @@
</NotFound>
</Router>
</DraftStateProvider>
</CascadingAuthenticationState>
</CascadingAuthenticationState>

View file

@ -21,16 +21,9 @@
<PageTitle>Inno Web CRM</PageTitle>
<AuthorizeView Roles="Advisor">
<div class="alert bg-light border-dark">
<div class="row">
<div class="col">
<h2 style="font-variant: all-small-caps">@(DateTime.Now.ToLongDateString())</h2>
</div>
<div class="col">
<WorkDateComponent SelectedDate="@_today" OnChanged="GetWorkdayTasks" />
</div>
</div>
<div class="bg-dark text-white rounded-2 mb-2 py-2">
<WorkDateComponent OnChangedCallback="GetWorkdayTasks"/>
</div>
<TaskItemTableComponent TaskItemList="_taskItems" OnCompleteTask="OnCompleteTask"
OnDeleteTask="OnDeleteConfirmed" OnTaskCompleted="OnTaskCompleted" />
</AuthorizeView>
<TaskItemTableComponent TaskItemList="_taskItems" OnCompleteTask="OnCompleteTask"
OnDeleteTask="OnDeleteConfirmed" OnTaskCompleted="OnTaskCompleted"/>
</AuthorizeView>

View file

@ -46,16 +46,13 @@ public partial class LandingComponentAdvisor : IDisposable
};
private Preferences _prefs { get; set; } = new();
private string _workDate { get; set; } = $"{DateTime.Now:yyyy-MM-dd}";
private string _today { get; set; } = $"{DateTime.Now:yyyy-MM-dd}";
private DateTime SelectedDate { get; set; }
private List<TaskItemDto>? _taskItems { get; set; } = new();
protected override async Task OnInitializedAsync()
{
_prefs = await _preferenceService.GetPreferences();
if(!string.IsNullOrWhiteSpace(_prefs.WorkDate))
_workDate = _prefs.WorkDate;
SelectedDate = string.IsNullOrWhiteSpace(_prefs.WorkDate) ? DateTime.Now : DateTime.Parse(_prefs.WorkDate);
_interceptor.RegisterEvent();
_interceptor.RegisterBeforeSendEvent();
@ -78,9 +75,9 @@ public partial class LandingComponentAdvisor : IDisposable
private async Task GetWorkdayTasks(string workDate)
{
_workDate = workDate;
SelectedDate = DateTime.Parse(workDate);
_taskItems = new List<TaskItemDto>();
_taskItems = await CrmTaskItemRepo.GetTaskList(workDate);
_taskItems = await CrmTaskItemRepo.GetTaskList($"{SelectedDate:yyyy-MM-dd}");
}
private async Task OnTaskCompleted(string taskItemId)

View file

@ -48,10 +48,12 @@
@product.Quantity
</td>
<td class="align-middle">
<a class="btn btn-warning d-block" href="/companies/@CompanyId/h/p/@product.Sku">Linjer</a>
<button class="btn btn-info" type="button" @onclick="() => CallShowReorderModal(product.Sku)">Historik</button>
</td>
</tr>
}
</tbody>
</table>
}
}
<InventoryReorderModal OnSelected="@OnSelectedItem" CompanyId="@CompanyId" SalesItem="@SalesItem" @ref="ReorderModal"/>

View file

@ -13,7 +13,11 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System.Globalization;
using Microsoft.AspNetCore.Components;
using Wonky.Client.HttpInterfaces;
using Wonky.Client.Models;
using Wonky.Client.Shared;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
@ -21,6 +25,50 @@ namespace Wonky.Client.Components;
public partial class ProductInventoryTableComponent
{
[CascadingParameter] DraftStateProvider DraftStateProvider { get; set; }
[Parameter] public List<ProductInventoryView> Inventory { get; set; } = new();
[Parameter] public string CompanyId { get; set; } = "";
[Inject] public ICatalogHttpRepository Catalog { get; set; }
private SalesItemView SalesItem { get; set; } = new();
private string Price { get; set; } = "0";
private string Quantity { get; set; } = "1";
private string Sku { get; set; } = "";
private InventoryReorderModal ReorderModal { get; set; } = new();
private SelectedSku Item { get; set; } = new();
private async Task CallShowReorderModal(string sku)
{
SalesItem = await Catalog.GetSalesItemSku(sku);
ReorderModal.Show();
}
private async Task OnSelectedItem(DraftItem draftItem)
{
DraftStateProvider.Draft.Items.Add(draftItem);
await DraftStateProvider.SaveChangesAsync();
}
// private async Task AddItem(SalesItemView salesItem)
// {
// // create a new cart item
// var item = new DraftItem
// {
// Item = salesItem,
// Quantity = Convert.ToInt32(Quantity),
// Price = Convert.ToDecimal(Price, CultureInfo.InvariantCulture),
// Discount = Convert.ToDecimal(Discount, CultureInfo.InvariantCulture),
// Sas = Sas
// };
// // reset internals to initial state
// Sas = false;
// Quantity = "1";
// Price = "0";
// Discount = "0";
// // add it to the cart
// DraftStateProvider.Draft.Items.Add(item);
// // save the item using the CartStateProvider's save method
// await DraftStateProvider.SaveChangesAsync();
// }
}

View file

@ -41,6 +41,12 @@
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Vores Ref.</th>
<td class="fw-bold">@ReportItem.OurRef</td>
<th scope="row">EOrdre</th>
<td class="fw-bold">@ReportItem.ESalesNumber</td>
</tr>
<tr>
<th scope="row">Dato</th>
<td class="fw-bold">@ReportItem.OrderDate</td>

View file

@ -53,6 +53,9 @@
case "Meeting":
<span>Salgsmøde</span>
break;
case "Supervisor":
<span>Supervisor</span>
break;
default:
<span></span>
break;

View file

@ -63,6 +63,9 @@
case "Leave":
<span>Ferie</span>
break;
case "Supervisor":
<span>Supervisor</span>
break;
}
}
</div>

View file

@ -22,4 +22,4 @@
@context.User.Identity?.Name
</div>
</Authorized>
</AuthorizeView>
</AuthorizeView>

View file

@ -18,5 +18,15 @@
@using Blazored.LocalStorage
@using Wonky.Client.Services
<input type="date" class="form-control"
@bind-Value="SelectedDate" @bind-Value:event="oninput" @onchange="OnDateChanged" />
<EditForm EditContext="WorkDateContext">
<div class="container-fluid">
<div class="row align-items-center">
<div class="col-8 work-date fw-bold">
@SelectedDate.ToLongDateString()
</div>
<div class="col-4">
<InputDate class="form-control calendar" @bind-Value="SelectedDate" @oninput="OnDateChanged"/>
</div>
</div>
</div>
</EditForm>

View file

@ -17,6 +17,7 @@
using System.Globalization;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
using Wonky.Client.Services;
namespace Wonky.Client.Components;
@ -27,47 +28,43 @@ public partial class WorkDateComponent : IDisposable
/// User preference service
/// </summary>
[Inject] public PreferenceService Prefs { get; set; }
/// <summary>
/// OnChanged callback function
/// </summary>
[Parameter] public EventCallback<string> OnChanged { get; set; }
[Parameter] public EventCallback<string> OnChangedCallback { get; set; }
private EditContext WorkDateContext { get; set; }
/// <summary>
/// Selected data
/// </summary>
[Parameter] public string SelectedDate { get; set; } = $"{DateOnly.FromDateTime(DateTime.Now):yyyy-MM-dd}";
private DateTime SelectedDate { get; set; }
/// <summary>
/// user preferences
/// </summary>
private Preferences _prefs = new();
/// <summary>
/// Component Initialization
/// </summary>
protected override async Task OnInitializedAsync()
{
WorkDateContext = new EditContext(SelectedDate);
Prefs.OnChange += ProfileServiceOnOnChange;
_prefs = await Prefs.GetPreferences();
if (!string.IsNullOrWhiteSpace(_prefs.WorkDate))
SelectedDate = _prefs.WorkDate;
SelectedDate = string.IsNullOrWhiteSpace(_prefs.WorkDate)
? DateTime.Now
: DateTime.Parse(_prefs.WorkDate);
}
/// <summary>
/// OnDateChanged function call to invoke the event callback
/// </summary>
/// <param name="e"></param>
private async Task OnDateChanged(ChangeEventArgs e)
{
var val = $"{DateOnly.Parse(e.Value?.ToString()!):yyyy-MM-dd}";
await Prefs.SetWorkDate(DateTime.Parse(val));
await OnChanged.InvokeAsync(val);
var val = DateTime.Parse(e.Value.ToString());
await Prefs.SetWorkDate(val);
await OnChangedCallback.InvokeAsync($"{val:yyyy-MM-dd}");
}
/// <summary>
///
/// ProfileService
/// </summary>
/// <param name="newPreferences"></param>
private void ProfileServiceOnOnChange(Preferences newPreferences)
@ -75,7 +72,6 @@ public partial class WorkDateComponent : IDisposable
_prefs = newPreferences;
StateHasChanged();
}
/// <summary>
/// Component dispose
/// </summary>

View file

@ -0,0 +1,7 @@
.work-date {
font-size: 1.2rem;
font-variant: all-small-caps;
}
.calendar {
min-width: 250px;
}

View file

@ -24,4 +24,6 @@ public interface ICatalogHttpRepository
Task<PagingResponse<SalesItemView>> GetSalesItemsPaged(CatalogPagingParams pagingParameters);
Task<SalesItemView> GetSalesItemId(string salesItemId);
Task<SalesItemView> GetSalesVariantId(string variantId);
Task<SalesItemView> GetSalesItemSku(string sku);
Task<SalesItemView> GetSalesItemSku(string countryCode, string sku);
}

View file

@ -91,4 +91,15 @@ public class CatalogHttpRepository : ICatalogHttpRepository
.GetFromJsonAsync<SalesItemView>($"{_apiConfig.Catalog}/variant/{variantId}");
return salesItem ?? new SalesItemView();
}
public async Task<SalesItemView> GetSalesItemSku(string sku)
{
var salesItem = await _client.GetFromJsonAsync<SalesItemView>($"{_apiConfig.Catalog}/sku/{sku}");
return salesItem ?? new SalesItemView();
}
public async Task<SalesItemView> GetSalesItemSku(string countryCode, string sku)
{
var salesItem = await _client.GetFromJsonAsync<SalesItemView>($"{_apiConfig.Catalog}/{countryCode}/sku/{sku}");
return salesItem ?? new SalesItemView();
}
}

View file

@ -30,6 +30,7 @@ public class DraftItem
public class Draft
{
public string DraftId { get; set; } = "";
public List<DraftItem> Items { get; set; } = new ();
public decimal Total
{

View file

@ -20,60 +20,60 @@
@attribute [Authorize(Roles = "Advisor")]
@using Wonky.Client.Components
<PriceListModal OnSelected="SelectSku" @ref="PriceListModal"/>
<ProductHistoryModal CompanyId="@CompanyId" ItemSku="@_selectedItem.Sku" @ref="HistoryModal"/>
<ProductPriceHistoryModal OnSelected="SelectPrice" CompanyId="@CompanyId" ItemSku="@_selectedItem.Sku" @ref="PriceHistoryModal"/>
<div class="row align-items-center bg-dark text-white rounded-3 p-3">
<div class="col-9">
<h3 class="workDate">@_workDate.ToLongDateString()</h3>
</div>
<div class="col-3">
<WorkDateComponent SelectedDate="@_selectedDate" OnChanged="SetWorkDate"></WorkDateComponent>
<div class="row bg-dark text-white rounded-2 mb-2 py-2 align-items-center">
<div class="col">
<WorkDateComponent OnChangedCallback="WorkDateComponentCallback"/>
</div>
</div>
<div class="row bg-light border-1 border-dark rounded-3 p-3">
<div class="col">
<h3>@_activity.Name - @_activity.Account</h3>
<h3>@Activity.Name - @Activity.Account</h3>
</div>
</div>
@if (_reportClosed)
@if (ReportClosed)
{
<h5>Der kan ikke oprettes besøg når der findes rapport for @_workDate.ToShortDateString()</h5>
<h5>Der kan ikke oprettes besøg når der findes rapport for @SelectedDate.ToShortDateString()</h5>
}
else
{
<EditForm EditContext="_editContext">
<EditForm EditContext="ActivityContext">
<DataAnnotationsValidator/>
<div class="row mb-1">
<label for="activityType" class="col-md-2 col-form-label">Ordre Type</label>
<div class="col-md-4">
<InputSelect id="activityType" class="form-select" @bind-Value="@_activity.ActivityTypeEnum">
<InputSelect id="activityType" class="form-select" @bind-Value="@Activity.ActivityTypeEnum">
<option value="">&rarr; TAG MIG &larr;</option>
<option value="onSite">Besøg</option>
<option value="phone">Telefon</option>
</InputSelect>
<ValidationMessage For="@(() => _activity.ActivityTypeEnum)"></ValidationMessage>
<ValidationMessage For="@(() => Activity.ActivityTypeEnum)"></ValidationMessage>
</div>
<label for="statusType" class="col-md-2 col-form-label">Status</label>
<div class="col-md-4">
<InputSelect id="statusType" class="form-select" @bind-Value="@_activity.ActivityStatusEnum">
<option value="noSale" selected>Ingen salg</option>
@if (!string.IsNullOrEmpty(_activity.VatNumber) && !string.IsNullOrWhiteSpace(_activity.Address1) && _company.HasFolded == 0)
<InputSelect id="statusType" class="form-select" @bind-Value="@Activity.ActivityStatusEnum">
<option value="noSale">Ingen salg</option>
@if (!string.IsNullOrEmpty(Activity.VatNumber) && !string.IsNullOrWhiteSpace(Activity.Address1) && Company.HasFolded == 0)
{
<option value="order">Bestilling</option>
@if (DraftStateProvider.Draft.Items.Any())
{
<option value="order" selected>Bestilling</option>
}
else
{
<option value="order">Bestilling</option>
}
<option value="quote">Tilbud</option>
}
</InputSelect>
<ValidationMessage For="@(() => _activity.ActivityStatusEnum)"></ValidationMessage>
@if (_activity.ActivityStatusEnum == "order")
<ValidationMessage For="@(() => Activity.ActivityStatusEnum)"></ValidationMessage>
@if (Activity.ActivityStatusEnum == "order")
{
<div class="form-check">
<InputCheckbox id="express" class="form-check-input" @bind-Value="@_activity.Express"/>
<InputCheckbox id="express" class="form-check-input" @bind-Value="@Activity.Express"/>
<label class="form-check-label" for="express">Express</label>
</div>
}
@ -83,63 +83,178 @@ else
<div class="row mb-1">
<label for="demo" class="col-md-2 col-form-label">Demo</label>
<div class="col-md-4">
<InputText id="demo" class="form-control" @bind-Value="_activity.Demo"/>
<ValidationMessage For="@(() => _activity.Demo)"></ValidationMessage>
<InputText id="demo" class="form-control" @bind-Value="Activity.Demo"/>
<ValidationMessage For="@(() => Activity.Demo)"></ValidationMessage>
</div>
<label for="email" class="col-md-2 col-form-label">Epost</label>
<div class="col-md-4">
<InputText id="email" class="form-control" @bind-Value="_activity.Email"/>
<ValidationMessage For="@(() => _activity.Email)"></ValidationMessage>
<InputText id="email" class="form-control" @bind-Value="Activity.Email"/>
<ValidationMessage For="@(() => Activity.Email)"></ValidationMessage>
</div>
</div>
<div class="row mb-1">
<label for="referenceNumber" class="col-md-2 col-form-label">Rekvisition</label>
<div class="col-md-4">
<InputText id="referenceNumber" class="form-control" @bind-Value="_activity.ReferenceNumber"/>
<ValidationMessage For="@(() => _activity.ReferenceNumber)"></ValidationMessage>
<InputText id="referenceNumber" class="form-control" @bind-Value="Activity.ReferenceNumber"/>
<ValidationMessage For="@(() => Activity.ReferenceNumber)"></ValidationMessage>
</div>
<label for="yourRef" class="col-md-2 col-form-label">Indkøber</label>
<div class="col-md-4">
<InputText id="yourRef" class="form-control" @bind-Value="_activity.YourRef"/>
<ValidationMessage For="@(() => _activity.YourRef)"></ValidationMessage>
<InputText id="yourRef" class="form-control" @bind-Value="Activity.YourRef"/>
<ValidationMessage For="@(() => Activity.YourRef)"></ValidationMessage>
</div>
</div>
<div class="row mb-1">
<label for="orderMessage" class="col-md-2 col-form-label">Note /Kontor</label>
<div class="col-md-4">
<InputTextArea id="orderMessage" class="form-control" @bind-Value="_activity.OrderMessage"/>
<ValidationMessage For="@(() => _activity.OrderMessage)"></ValidationMessage>
<InputTextArea id="orderMessage" class="form-control" @bind-Value="Activity.OrderMessage"/>
<ValidationMessage For="@(() => Activity.OrderMessage)"></ValidationMessage>
</div>
<label for="crmNote" class="col-md-2 col-form-label">Note /Selv</label>
<div class="col-md-4">
<InputTextArea id="crmNote" class="form-control" @bind-Value="_activity.CrmNote"/>
<ValidationMessage For="@(() => _activity.CrmNote)"></ValidationMessage>
<InputTextArea id="crmNote" class="form-control" @bind-Value="Activity.CrmNote"/>
<ValidationMessage For="@(() => Activity.CrmNote)"></ValidationMessage>
</div>
</div>
<div class="row mb-1">
<div class="row mb-2">
<label for="attention" class="col-md-2 col-form-label">Att.</label>
<div class="col-md-4">
<InputText id="attention" class="form-control" @bind-Value="_activity.Attention"/>
<ValidationMessage For="@(() => _activity.Attention)"></ValidationMessage>
<InputText id="attention" class="form-control" @bind-Value="Activity.Attention"/>
<ValidationMessage For="@(() => Activity.Attention)"></ValidationMessage>
</div>
<label for="phone" class="col-md-2 col-form-label">Tlf.</label>
<div class="col-md-4">
<InputText id="phone" class="form-control" @bind-Value="_activity.Phone"/>
<ValidationMessage For="@(() => _activity.Phone)"></ValidationMessage>
<InputText id="phone" class="form-control" @bind-Value="Activity.Phone"/>
<ValidationMessage For="@(() => Activity.Phone)"></ValidationMessage>
</div>
</div>
<div id="this-draft" style="@(Activity.ActivityStatusEnum is "order" or "quote" ? "display: block" : "display:none")">
@* Order lines -----------------------------------------------------*@
<div class="row">
<div class="col">
<table class="sticky-top table table-hover table-striped table-bordered">
<thead>
<tr class="bg-dark text-white">
<th scope="col" colspan="7">
Ordrekladde <span class="mx-2 draft-expires-msg">Global kladde (udløber efter @(DraftStateProvider.Draft.TimeToLiveInSeconds / 60)m inaktivitet)</span>
</th>
<th scope="col" class="text-end">
<button type="button" class="btn btn-danger btn-sm" @onclick="@DeleteDraft" disabled="@(DraftStateProvider.Draft.Items.Count == 0)"><i class="bi-trash"></i> Slet kladde</button>
</th>
</tr>
<tr class="bg-dark opacity-75 text-white">
<th scope="col">Navn</th>
<th scope="col" class="text-nowrap">Varenr</th>
<th scope="col" class="text-end">Antal</th>
<th scope="col" class="text-end">Enhedspris</th>
<th scope="col" class="text-center">%</th>
<th scope="col" class="text-end">Linjesum</th>
<th scope="col" class="text-end">SAS</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@if (DraftStateProvider.Draft.Items.Count > 0)
{
@foreach (var cartItem in DraftStateProvider.Draft.Items)
{
<tr>
<td class="align-middle">@cartItem.Item.Name</td>
<td class="align-middle">@cartItem.Item.Sku</td>
<td class="align-middle text-end">@cartItem.Quantity</td>
<td class="align-middle text-end">@($"{cartItem.Price:N2}")</td>
<td class="align-middle text-end">@($"{cartItem.Discount:N2}")</td>
<td class="align-middle text-end">@($"{cartItem.LineTotal:N2}")</td>
<td class="align-middle text-center">
<input type="checkbox" checked="@cartItem.Sas" disabled/>
</td>
<td class="align-middle text-end">
<button type="button" class="btn btn-danger" @onclick="@(() => RemoveItem(cartItem))"><i class="bi-trash2"></i> Slet Linje</button>
</td>
</tr>
}
}
<tr>
<td colspan="4"></td>
<td class="align-middle text-black text-end fw-bold">Total</td>
<td class="align-middle text-black text-end fw-bold">@($"{DraftStateProvider.Draft.Total:N2}")</td>
<td></td>
<td class="align-middle text-end">
<button class="btn btn-primary" type="button" @onclick="CallPriceListModal">
<i class="bi-plus"></i> Ny linje
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
@* draft line ----------------------------------------------------- *@
<div class="row">
<div class="col">
@if (!string.IsNullOrWhiteSpace(SelectedItem.Name) && ShowItem)
{
<table id="draft-line" class="table table-bordered">
<thead>
<tr class="bg-dark text-white">
<th scope="col" colspan="6">Kladdelinje</th>
</tr>
<tr>
<th scope="col">Antal</th>
<th scope="col">Pris</th>
<th scope="col">Rabat</th>
<th class="align-content-center justify-content-center" scope="col">SAS</th>
<th scope="col">Varenr.</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr>
<td class="align-middle">
<input type="number" class="form-control" @bind-value="@Quantity"/>
</td>
<td class="align-middle">
<div class="input-group">
<input type="number" class="form-control" @bind-value="@Price"/>
<button class="btn btn-warning" type="button" @onclick="CallPriceHistoryModal">
<i class="bi-list-ul"></i>
</button>
@*
<button class="btn btn-info btn-sm" type="button" @onclick="CallHistoryModal">
<i class="oi oi-list"></i>
</button>
*@
</div>
</td>
<td class="align-middle">
<input type="number" class="form-control" @bind-value="@Discount"/>
</td>
<td class="align-middle align-content-center justify-content-center">
<input type="checkbox" class="form-check" @bind-value="@Sas"/>
</td>
<td class="align-middle">@SelectedItem.Sku</td>
<td class="align-middle">
<button type="button" class="btn btn-warning text-nowrap d-block" @onclick="@(() => AddItem(SelectedItem))">bestil @Quantity stk @SelectedItem.Name</button>
</td>
</tr>
</tbody>
</table>
}
</div>
</div>
</div>
<div class="accordion" id="crmActivity">
@* Order lines *@
<div class="accordion-item" style="@(_activity.ActivityStatusEnum is "order" or "quote" ? "display: block" : "display:none")">
@*
$1$ Order lines #1#
<div class="accordion-item" style="@(Activity.ActivityStatusEnum is "order" or "quote" ? "display: block" : "display:none")">
<h2 class="accordion-header" id="catalogHeader">
<button class="accordion-button collapsed bg-light" type="button"
data-bs-toggle="collapse" data-bs-target="#catalogBody"
@ -149,122 +264,13 @@ else
</h2>
<div id="catalogBody" class="accordion-collapse collapse" aria-labelledby="catalogHeader" data-bs-parent="#crmActivity">
<div class="accordion-body">
<div class="row">
<div class="col">
@* Order draft lines -----------------------------------------------------*@
<table class="sticky-top table table-hover table-striped table-bordered">
<thead>
<tr class="bg-dark text-white">
<th scope="col" colspan="6">
Ordrekladde <span class="mx-2 draft-expires-msg">Global kladde (udløber efter @(DraftStateProvider.Draft.TimeToLiveInSeconds / 60)m inaktivitet)</span>
</th>
<th scope="col" class="text-end">
<button type="button" class="btn btn-danger btn-sm" @onclick="@DeleteDraft" disabled="@(DraftStateProvider.Draft.Items.Count == 0)">Slet kladde</button>
</th>
</tr>
<tr class="bg-dark opacity-75 text-white">
<th scope="col">Navn</th>
<th scope="col" class="text-nowrap">Varenr</th>
<th scope="col" class="text-end">Antal</th>
<th scope="col" class="text-end">Enhedspris</th>
<th scope="col" class="text-end">Linjesum</th>
<th scope="col" class="text-end">SAS</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@if (DraftStateProvider.Draft.Items.Count > 0)
{
@foreach (var cartItem in DraftStateProvider.Draft.Items)
{
<tr>
<td class="align-middle">@cartItem.Item.Name</td>
<td class="align-middle">@cartItem.Item.Sku</td>
<td class="align-middle text-end">@cartItem.Quantity</td>
<td class="align-middle text-end">@cartItem.Price</td>
<td class="align-middle text-end">@cartItem.LineTotal</td>
<td class="align-middle text-center">
<input type="checkbox" checked="@cartItem.Sas" disabled/>
</td>
<td class="align-middle text-end">
<button type="button" class="btn btn-danger" @onclick="@(() => RemoveItem(cartItem))">Slet Linje</button>
</td>
</tr>
}
}
<tr>
<td colspan="3"></td>
<td class="align-middle text-black text-end fw-bold">Total</td>
<td class="align-middle text-black text-end fw-bold">@DraftStateProvider.Draft.Total</td>
<td></td>
<td class="align-middle text-end">
<button class="btn btn-primary" type="button" @onclick="CallPriceListModal">
<i class="oi oi-plus"></i>
</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col">
@* draft line ----------------------------------------------------- *@
@if (!string.IsNullOrWhiteSpace(_selectedItem.Name) && ShowItem)
{
<table id="draft-line" class="table table-bordered">
<thead>
<tr class="bg-dark text-white">
<th scope="col" colspan="6">Kladdelinje</th>
</tr>
<tr>
<th scope="col">Antal</th>
<th scope="col">Pris</th>
<th scope="col">Rabat</th>
<th class="align-content-center justify-content-center" scope="col">SAS</th>
<th scope="col">Varenr.</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
<tr>
<td class="align-middle">
<input type="number" class="form-control" @bind-value="@Quantity"/>
</td>
<td class="align-middle">
<div class="input-group">
<input type="number" class="form-control" @bind-value="@Price"/>
<button class="btn btn-warning btn-sm" type="button" @onclick="CallPriceHistoryModal">
<i class="oi oi-list"></i>
</button>
@*
<button class="btn btn-info btn-sm" type="button" @onclick="CallHistoryModal">
<i class="oi oi-list"></i>
</button>
*@
</div>
</td>
<td class="align-middle">
<input type="number" class="form-control" @bind-value="@Discount"/>
</td>
<td class="align-middle align-content-center justify-content-center">
<input type="checkbox" class="form-check" @bind-value="@Sas"/>
</td>
<td class="align-middle">@_selectedItem.Sku</td>
<td class="align-middle">
<button type="button" class="btn btn-warning text-nowrap d-block" @onclick="@(() => AddItem(_selectedItem))">bestil @Quantity stk @_selectedItem.Name</button>
</td>
</tr>
</tbody>
</table>
}
</div>
</div>
</div>
</div>
</div>
*@
@* Delivery address *@
<div class="accordion-item" style="@(_activity.ActivityStatusEnum == "order" ? "display: block" : "display:none")">
<div class="accordion-item" style="@(Activity.ActivityStatusEnum == "order" ? "display: block" : "display:none")">
<h2 class="accordion-header" id="deliveryHeader">
<button class="accordion-button collapsed bg-light" type="button" data-bs-toggle="collapse" data-bs-target="#deliveryBody" aria-expanded="false" aria-controls="deliveryBody">
Leveringsadresse
@ -276,31 +282,31 @@ else
<div class="row mb-1">
<label for="dlvName" class="col-md-2 col-form-label">Lev. Navn</label>
<div class="col-md-10">
<InputText id="dlvName" class="form-control" @bind-Value="_activity.DlvName"/>
<InputText id="dlvName" class="form-control" @bind-Value="Activity.DlvName"/>
</div>
</div>
<div class="row mb-1">
<label for="dlvAddress1" class="col-md-2 col-form-label">Lev. Adresse</label>
<div class="col-md-10">
<InputText id="dlvAddress1" class="form-control" @bind-Value="_activity.DlvAddress1"/>
<InputText id="dlvAddress1" class="form-control" @bind-Value="Activity.DlvAddress1"/>
</div>
</div>
<div class="row mb-1">
<label for="dlvAddress2" class="col-md-2 col-form-label">Lev. Adresse</label>
<div class="col-md-10">
<InputText id="dlvAddress2" class="form-control" @bind-Value="_activity.DlvAddress2"/>
<InputText id="dlvAddress2" class="form-control" @bind-Value="Activity.DlvAddress2"/>
</div>
</div>
<div class="row mb-1">
<label for="dlvZipCode" class="col-md-2 col-form-label">Lev. Postnr</label>
<div class="col-md-10">
<InputText id="dlvZipCode" class="form-control" @bind-Value="_activity.DlvZipCode"/>
<InputText id="dlvZipCode" class="form-control" @bind-Value="Activity.DlvZipCode"/>
</div>
</div>
<div class="row mb-1">
<label for="dlvCity" class="col-md-2 col-form-label">Lev. Bynavn</label>
<div class="col-md-10">
<InputText id="dlvCity" class="form-control" @bind-Value="_activity.DlvCity"/>
<InputText id="dlvCity" class="form-control" @bind-Value="Activity.DlvCity"/>
</div>
</div>
</div>
@ -313,12 +319,15 @@ else
<a class="btn btn-info" href="/companies">Til Oversigt</a>
</div>
<div class="col">
<a class="btn btn-warning" href="/companies/@_company.CompanyId">Tilbage</a>
<a class="btn btn-warning" href="/companies/@Company.CompanyId">Tilbage</a>
</div>
<div class="col">
<button type="button" class="btn btn-primary" @onclick="CreateActivity" disabled="@_poFormInvalid">Opret besøg</button>
<button type="button" class="btn btn-primary" @onclick="CreateActivity" disabled="@PoFormInvalid">Opret besøg</button>
</div>
</div>
}
<ConfirmationModal BodyMessage="@_confirmDatePrompt" OnOkClicked="WorkDateConfirmed" @ref="ConfirmWorkDate" />
<ConfirmWorkDateModal BodyMessage="@PromptDateConfirm" OnOkClicked="WorkDateConfirmCallback" @ref="ConfirmWorkDate"/>
<PriceListModal OnSelected="PriceListCallback" @ref="PriceListModal"/>
<ProductHistoryModal CompanyId="@CompanyId" ItemSku="@SelectedItem.Sku" @ref="HistoryModal"/>
<ProductPriceHistoryModal OnSelected="PriceHistoryCallback" CompanyId="@CompanyId" Sku="@SelectedItem.Sku" @ref="PriceHistoryModal"/>

View file

@ -38,7 +38,7 @@ public partial class CrmActivityNewPage : IDisposable
// Services
[Inject] public ILogger<CrmActivityNewPage> Logger { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public PreferenceService PreferenceService { get; set; }
[Inject] public PreferenceService Prefs { get; set; }
[Inject] public IToastService Toast { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public ILocalStorageService Storage { get; set; }
@ -47,13 +47,13 @@ public partial class CrmActivityNewPage : IDisposable
[Inject] public ICrmActivityHttpRepository CrmActivityRepo { get; set; }
[Inject] public ICrmReportHttpRepository CrmReportRepo { get; set; }
// variables
private readonly JsonSerializerOptions? _options = new JsonSerializerOptions{PropertyNameCaseInsensitive = true};
private SalesItemView _selectedItem { get; set; } = new();
private Preferences _prefs { get; set; } = new();
private ActivityDto _activity { get; set; } = new();
private CompanyDto _company = new();
private EditContext _editContext { get; set; }
private bool _poFormInvalid { get; set; } = true;
private readonly JsonSerializerOptions? _options = new() {PropertyNameCaseInsensitive = true};
private SalesItemView SelectedItem { get; set; } = new();
private Preferences UserPrefs { get; set; } = new();
private ActivityDto Activity { get; set; } = new();
private CompanyDto Company = new();
private EditContext ActivityContext { get; set; }
private bool PoFormInvalid { get; set; } = true;
private bool ShowItem { get; set; }
private string Quantity = "1";
private string Price = "0";
@ -64,129 +64,131 @@ public partial class CrmActivityNewPage : IDisposable
private bool InvalidActivity = true;
private bool InvalidCanvas = true;
private bool NoHistory = true;
private bool _reportClosed { get; set; }
private UserInfoView _ux { get; set; } = new();
private DateTime _workDate { get; set; } = DateTime.Now;
private string _selectedDate { get; set; } = "";
private string _phone { get; set; } = "";
private string _confirmDatePrompt { get; set; }
private bool ReportClosed { get; set; }
private UserInfoView ThisUserInfo { get; set; } = new();
private DateTime SelectedDate { get; set; }
private string OldPhone { get; set; } = "";
private string PromptDateConfirm { get; set; }
// MODAL DIALOGS
private PriceListModal PriceListModal { get; set; }
private ProductHistoryModal HistoryModal { get; set; }
private ProductPriceHistoryModal PriceHistoryModal { get; set; }
private ConfirmationModal ConfirmWorkDate { get; set; } = new();
private ConfirmWorkDateModal ConfirmWorkDate { get; set; } = new();
protected override async Task OnParametersSetAsync()
{
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
_prefs = await PreferenceService.GetPreferences();
if (!string.IsNullOrWhiteSpace(_prefs.WorkDate))
_workDate = DateTime.Parse(_prefs.WorkDate);
_selectedDate = $"{_workDate:yyyy-MM-dd}";
// raise flag if report is closed
_reportClosed = await CrmReportRepo.ReportExist(_selectedDate);
}
protected override async Task OnInitializedAsync()
{
_editContext = new EditContext(_activity);
_editContext.OnFieldChanged += HandleFieldChanged;
_editContext.OnValidationStateChanged += ValidationChanged;
_activity.ActivityDate = $"{_workDate:yyyy-MM-dd}" ;
_ux = await Storage.GetItemAsync<UserInfoView>("_xu");
// get company
_company = await CompanyRepo.GetCompanyById(CompanyId);
ActivityContext = new EditContext(Activity);
ActivityContext.OnFieldChanged += HandleFieldChanged;
ActivityContext.OnValidationStateChanged += ValidationChanged;
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
// User Preferences
UserPrefs = await Prefs.GetPreferences();
// User Info
ThisUserInfo = await Storage.GetItemAsync<UserInfoView>("_xu");
// Fetch Customer from http
Company = await CompanyRepo.GetCompanyById(CompanyId);
if (Company.HasFolded == 1)
// Company has shutdown activities
Activity.OrderMessage = "BEMÆRK: CVR nummer er ophørt.";
// variable to validate if customer needs phone number update
_phone = _company.Phone;
if (string.IsNullOrWhiteSpace(_company.Phone)
&& !string.IsNullOrWhiteSpace(_company.Account)
&& _company.Account != "NY" && _company.Account.Length > 7)
OldPhone = Company.Phone;
if (string.IsNullOrWhiteSpace(Company.Phone)
&& !string.IsNullOrWhiteSpace(Company.Account)
&& Company.Account != "NY" && Company.Account.Length > 7)
{
_company.Phone = _company.Account[..8];
Company.Phone = Company.Account[..8];
}
_activity.BcId = _company.BcId;
_activity.ActivityStatusEnum = "noSale";
_activity.VisitTypeEnum = _company.Account is "" or "NY" ? "new" : "recall";
if (_company.HasFolded == 1)
_activity.OrderMessage = "Virksomheden er ophørt.";
// permanent identifications
_activity.CompanyId = _company.CompanyId;
_activity.SalesRepId = _ux.Id;
_activity.SalesRep = _ux.Advisor;
_activity.CountryCode = _ux.CountryCode;
_activity.Account = _company.Account;
_activity.VatNumber = _company.VatNumber;
_activity.Email = _company.Email;
_activity.Phone = _company.Phone;
_activity.Mobile = _company.Mobile;
_activity.Name = _company.Name;
_activity.Address1 = _company.Address1;
_activity.Address2 = _company.Address2;
_activity.ZipCode = _company.ZipCode;
_activity.City = _company.City;
_activity.DlvName = _company.Name;
_activity.DlvAddress1 = _company.Address1;
_activity.DlvAddress2 = _company.Address2;
_activity.DlvZipCode = _company.ZipCode;
_activity.DlvCity = _company.City;
// Populate base activity information
Activity.BcId = Company.BcId;
Activity.ActivityStatusEnum = "noSale";
Activity.VisitTypeEnum = Company.Account is "" or "NY" ? "new" : "recall";
Activity.CompanyId = Company.CompanyId;
Activity.SalesRepId = ThisUserInfo.Id;
Activity.SalesRep = ThisUserInfo.Advisor;
Activity.CountryCode = ThisUserInfo.CountryCode;
Activity.Account = Company.Account;
Activity.VatNumber = Company.VatNumber;
Activity.Email = Company.Email;
Activity.Phone = Company.Phone;
Activity.Mobile = Company.Mobile;
Activity.Name = Company.Name;
Activity.Address1 = Company.Address1;
Activity.Address2 = Company.Address2;
Activity.ZipCode = Company.ZipCode;
Activity.City = Company.City;
Activity.DlvName = Company.Name;
Activity.DlvAddress1 = Company.Address1;
Activity.DlvAddress2 = Company.Address2;
Activity.DlvZipCode = Company.ZipCode;
Activity.DlvCity = Company.City;
// Initialize date variable
SelectedDate = string.IsNullOrWhiteSpace(UserPrefs.WorkDate) ? DateTime.Now : DateTime.Parse(UserPrefs.WorkDate);
// raise flag if report is closed
ReportClosed = await CrmReportRepo.ReportExist($"{SelectedDate:yyyy-MM-dd}");
// Ask for confirmation of date
Logger.LogDebug("Preferences.DateConfirmed => {}", UserPrefs.DateConfirmed);
if (!UserPrefs.DateConfirmed)
{
PromptDateConfirm = $"Aktiviteter oprettes med dato {SelectedDate.ToShortDateString()}. Er dette OK?";
ConfirmWorkDate.Show();
}
// Lines may already have been added from the company inventory page
if (DraftStateProvider.Draft.Items.Any())
{
// set dropdown selection accordingly
Activity.ActivityTypeEnum = "onSite";
Activity.ActivityStatusEnum = "order";
}
StateHasChanged();
}
/// <summary>
/// Work Date confirm callback
/// </summary>
private async Task WorkDateConfirmCallback()
{
await Prefs.SetDateConfirmed(true);
ConfirmWorkDate.Hide();
StateHasChanged();
}
/// <summary>
/// Show Price list modal
/// </summary>
private void CallPriceListModal()
{
PriceListModal.Show();
}
private void VerifyWorkDateConfirmed()
{
Logger.LogDebug("Preferences.DateConfirmed => {}", _prefs.DateConfirmed);
if (_prefs.DateConfirmed) return;
_confirmDatePrompt = $"Dato {_activity.ActivityDate}?";
ConfirmWorkDate.Show();
}
private async Task WorkDateConfirmed()
{
await PreferenceService.SetDateConfirmed(true);
ConfirmWorkDate.Hide();
}
private async Task SelectSku(SelectedSku sku)
/// <summary>
/// Price List modal callback
/// </summary>
/// <param name="sku"></param>
private async Task PriceListCallback(SelectedSku sku)
{
// get selected item
if (string.IsNullOrWhiteSpace(sku.ItemId))
return;
_selectedItem = await Catalog.GetSalesItemId(sku.ItemId);
SelectedItem = await Catalog.GetSalesItemId(sku.ItemId);
ShowItem = true;
Price = sku.Rate;
Quantity = sku.Quantity;
StateHasChanged();
}
/// <summary>
/// Show Price History modal
/// </summary>
private void CallPriceHistoryModal()
{
if(ShowItem)
PriceHistoryModal.Show();
}
private void SelectPrice(decimal price)
/// <summary>
/// Price History modal callback
/// </summary>
/// <param name="price"></param>
private void PriceHistoryCallback(decimal price)
{
if (price == 0)
return;
@ -194,31 +196,30 @@ public partial class CrmActivityNewPage : IDisposable
Price = price.ToString("N2", CultureInfo.InvariantCulture);
StateHasChanged();
}
private async Task SetWorkDate(string workDate)
/// <summary>
/// Work Date component callback
/// </summary>
/// <param name="workDate"></param>
private async Task WorkDateComponentCallback(string workDate)
{
_selectedDate = workDate;
_workDate = DateTime.Parse(_selectedDate);
_activity.ActivityDate = _selectedDate;
_reportClosed = await CrmReportRepo.ReportExist(_selectedDate);
ReportClosed = await CrmReportRepo.ReportExist(workDate);
SelectedDate = DateTime.Parse(workDate);
Activity.ActivityDate = workDate;
}
/// <summary>
/// Validate and Create Activity
/// </summary>
private async Task CreateActivity()
{
// if (!_prefs.DateConfirmed)
// {
// _confirmDatePrompt = $"Er arbejdsdato {_selectedDate} korrekt?";
// CallConfirmWorkDate();
// }
if (string.IsNullOrWhiteSpace(_activity.Address1))
if (string.IsNullOrWhiteSpace(Activity.Address1))
{
Toast.ShowError("Kunde adresse er ufuldstændig.");
return;
}
if (_activity.ActivityStatusEnum == "order")
if (Activity.ActivityStatusEnum == "order")
{
if (DraftStateProvider.Draft.Items.Count == 0)
{
@ -226,27 +227,36 @@ public partial class CrmActivityNewPage : IDisposable
return;
}
if (string.IsNullOrWhiteSpace(_activity.Phone))
if (string.IsNullOrWhiteSpace(Activity.Phone))
{
Toast.ShowError("Ved bestilling til ny kunde skal telefon nummer angives.");
return;
}
}
_poFormInvalid = true;
_activity.ActivityDate = $"{_workDate:yyyy-MM-dd}";
_activity.OurRef = _activity.ActivityTypeEnum switch
PoFormInvalid = true;
// reset selected item
SelectedItem = new SalesItemView();
// check if phone number need to be updated
if (OldPhone != Activity.Phone)
{
"phone" => $"T:{_ux.FullName.Split(" ")[0]}",
"onSite" => $"B:{_ux.FullName.Split(" ")[0]}",
Company.Phone = Activity.Phone;
// update company phone record
Logger.LogDebug("CrmNewActivityPage => \n New Phone Number \n {}", Activity.Phone);
await CompanyRepo.UpdateCompany(Company.CompanyId, Company);
}
// begin assembling activity
Activity.ActivityDate = $"{SelectedDate:yyyy-MM-dd}";
Activity.OurRef = Activity.ActivityTypeEnum switch
{
"phone" => $"T:{ThisUserInfo.FullName.Split(" ")[0]}",
"onSite" => $"B:{ThisUserInfo.FullName.Split(" ")[0]}",
_ => ""
};
if (_activity.Express)
_activity.OurRef = $"E{_activity.OurRef}";
_activity.Lines = new List<ActivityLineDto>();
if (Activity.Express)
Activity.OurRef = $"E{Activity.OurRef}";
// begin lines
Activity.Lines = new List<ActivityLineDto>();
var ln = 0;
if (DraftStateProvider.Draft.Items.Count != 0)
{
@ -264,22 +274,12 @@ public partial class CrmActivityNewPage : IDisposable
Location = item.Item.Location
})
.ToList();
_activity.Lines = lines;
Activity.Lines = lines;
}
if (_phone != _activity.Phone)
{
_company.Phone = _activity.Phone;
// update company phone record
await CompanyRepo.UpdateCompany(_company.CompanyId, _company);
}
Logger.LogDebug("CrmNewActivityPage => \n {}", JsonSerializer.Serialize(_activity));
Logger.LogDebug("CrmNewActivityPage => \n {}", JsonSerializer.Serialize(Activity));
// post to api
var result = await CrmActivityRepo.CreateActivity(_activity);
var result = await CrmActivityRepo.CreateActivity(Activity);
Logger.LogDebug("ApiResponseView => \n {}", JsonSerializer.Serialize(result));
// show result message
if (result.IsSuccess)
{
@ -289,21 +289,20 @@ public partial class CrmActivityNewPage : IDisposable
return;
}
_poFormInvalid = false;
PoFormInvalid = false;
Toast.ShowError(result.Message, "ORDRE FEJL");
_selectedItem = new SalesItemView();
}
private void CheckActivity()
{
InvalidActivityType = string.IsNullOrWhiteSpace(_activity.ActivityTypeEnum);
}
/// <summary>
/// Delete current draft
/// </summary>
private async Task DeleteDraft()
{
await DraftStateProvider.DeleteDraftAsync();
}
/// <summary>
/// Add item to draft
/// </summary>
/// <param name="salesItem"></param>
private async Task AddItem(SalesItemView salesItem)
{
ShowItem = false;
@ -326,6 +325,10 @@ public partial class CrmActivityNewPage : IDisposable
// save the item using the CartStateProvider's save method
await DraftStateProvider.SaveChangesAsync();
}
/// <summary>
/// Remove item from draft
/// </summary>
/// <param name="item"></param>
private async Task RemoveItem(DraftItem item)
{
// remove item
@ -333,53 +336,57 @@ public partial class CrmActivityNewPage : IDisposable
// save the remaining draft
await DraftStateProvider.SaveChangesAsync();
}
/// <summary>
/// Edit Context handle field change
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
// InvalidCanvas = InvalidActivityType;
InvalidActivity = InvalidActivityType
|| _poFormInvalid
|| PoFormInvalid
|| DraftStateProvider.Draft.Items.Count == 0
|| (_activity.ActivityStatusEnum == "offer" && string.IsNullOrWhiteSpace(_activity.Email));
if (_activity.YourRef.Length > 35 || _activity.ReferenceNumber.Length > 20)
|| (Activity.ActivityStatusEnum == "offer" && string.IsNullOrWhiteSpace(Activity.Email));
if (Activity.YourRef.Length > 35 || Activity.ReferenceNumber.Length > 20)
{
_poFormInvalid = true;
PoFormInvalid = true;
return;
}
if (InvalidActivity)
{
_poFormInvalid = true;
PoFormInvalid = true;
return;
}
_poFormInvalid = !_editContext.Validate();
PoFormInvalid = !ActivityContext.Validate();
StateHasChanged();
}
/// <summary>
/// Edit Context handle validation change
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e)
{
if (string.IsNullOrEmpty(_activity.ActivityTypeEnum) && !_reportClosed)
if (string.IsNullOrEmpty(Activity.ActivityTypeEnum) && !ReportClosed)
{
Toast.ShowWarning("Aktivitet type kan ikke være tom");
return;
}
_poFormInvalid = false;
_editContext.OnFieldChanged -= HandleFieldChanged;
_editContext.OnValidationStateChanged -= ValidationChanged;
_editContext = new EditContext(_activity);
_editContext.OnFieldChanged += HandleFieldChanged;
_editContext.OnValidationStateChanged += ValidationChanged;
VerifyWorkDateConfirmed();
PoFormInvalid = false;
ActivityContext.OnFieldChanged -= HandleFieldChanged;
ActivityContext.OnValidationStateChanged -= ValidationChanged;
ActivityContext = new EditContext(Activity);
ActivityContext.OnFieldChanged += HandleFieldChanged;
ActivityContext.OnValidationStateChanged += ValidationChanged;
}
/// <summary>
/// Implement Dispose from IDisposable
/// </summary>
public void Dispose()
{
Interceptor.DisposeEvent();
_editContext.OnFieldChanged -= HandleFieldChanged;
_editContext.OnValidationStateChanged -= ValidationChanged;
ActivityContext.OnFieldChanged -= HandleFieldChanged;
ActivityContext.OnValidationStateChanged -= ValidationChanged;
}
}

View file

@ -20,12 +20,9 @@
@attribute [Authorize(Roles = "Advisor")]
@page "/activity-today"
<div class="row mb-1 align-items-center">
<div class="col-md-5">
<h3 class="workDate">@(string.IsNullOrWhiteSpace(_workDate) ? "" : $"{DateTime.Parse(_workDate).ToLongDateString()}")</h3>
</div>
<div class="col-md-2">
<WorkDateComponent OnChanged="GetActivities" />
<div class="row bg-dark text-white rounded-2 mb-2 py-2 align-items-center">
<div class="col">
<WorkDateComponent OnChangedCallback="GetActivities" />
</div>
<div class="col-md-3">
<AdvisorActivityKmStartComponent />
@ -33,7 +30,7 @@
<div class="col-md-2">
@if (_reportExist)
{
<a class="btn btn-primary" href="/sales-reports/view/@_workDate">RAPPORT</a>
<a class="btn btn-primary" href="/sales-reports/view/@($"{SelectedDate:yyyy-MM-dd}")">RAPPORT</a>
}
else
{

View file

@ -36,30 +36,28 @@ public partial class CrmActivityTodayPage : IDisposable
[Inject] public IToastService _toast { get; set; }
private ReportStatusView? ReportStatusView { get; set; } = new();
private Preferences _prefs { get; set; } = new();
private string _workDate { get; set; } = $"{DateTime.Now:yyyy-MM-dd}";
private DateTime SelectedDate { get; set; }
private bool _reportExist = false;
private bool Loading { get; set; } = true;
protected override async Task OnInitializedAsync()
{
_prefs = await _preferenceService.GetPreferences();
if(!string.IsNullOrWhiteSpace(_prefs.WorkDate))
_workDate = _prefs.WorkDate;
SelectedDate = string.IsNullOrWhiteSpace(_prefs.WorkDate) ? DateTime.Now : DateTime.Parse(_prefs.WorkDate);
_interceptor.RegisterEvent();
_interceptor.RegisterBeforeSendEvent();
_reportExist = await CrmReportRepo.ReportExist(_workDate);
await GetActivities(_workDate);
_reportExist = await CrmReportRepo.ReportExist($"{SelectedDate:yyyy-MM-dd}");
await GetActivities($"{SelectedDate:yyyy-MM-dd}");
}
private async Task GetActivities(string workDate)
{
_toast.ShowInfo("Vent nogle sekunder for data");
_workDate = workDate;
SelectedDate = DateTime.Parse(workDate);
ReportStatusView = new ReportStatusView();
ReportStatusView = await CrmActivityRepo.GetActivities(workDate);
ReportStatusView = await CrmActivityRepo.GetActivities($"{SelectedDate:yyyy-MM-dd}");
_logger.LogDebug("Activities => {}", JsonSerializer.Serialize(ReportStatusView));
Loading = false;
}

View file

@ -19,23 +19,21 @@
@using Microsoft.AspNetCore.Authorization
@page "/companies/{CompanyId}/h/i"
@attribute [Authorize(Roles = "Advisor")]
<div class="card">
<div class="card-header">
<div class="row">
<div class="col-6">
<h3>@_company.Name</h3>
</div>
<div class="col-3 align-content-end">
<a class="btn btn-primary" href="/companies/@_company.CompanyId">Kundekort</a>
</div>
<div class="col-3">
<button class="btn btn-warning" @onclick="RefreshHistory" disabled="@_working">@_btnUpdateText</button>
<label class="fw-bold ms-2">@_company.HistorySync</label>
</div>
@if (!Loading)
{
<div class="row">
<div class="col-6">
<h3>@Company.Name</h3>
</div>
<div class="col-6 align-content-end">
<a class="btn btn-primary" href="/companies/@Company.CompanyId">Kundekort</a>
<a class="btn btn-primary" href="/companies/@Company.CompanyId/activities/new">Besøg</a>
</div>
</div>
<div class="card-body">
<ProductInventoryTableComponent CompanyId="@CompanyId" Inventory="_inventory"></ProductInventoryTableComponent>
</div>
</div>
<ProductInventoryTableComponent CompanyId="@CompanyId" Inventory="Inventory" />
}
@if (Loading)
{
<LoaderThreeDots Loading="Loading" />
}

View file

@ -28,39 +28,39 @@ public partial class CrmCompanyInventoryPage : IDisposable
{
[Parameter] public string CompanyId { get; set; } = "";
[Inject] public ICrmHistoryHttpRepository CrmHistoryRepo { get; set; }
[Inject] public ICrmCompanyHttpRepository _companyRepo { get; set; }
[Inject] public HttpInterceptorService _interceptor { get; set; }
[Inject] public IToastService _toast { get; set; }
private CompanyDto _company { get; set; } = new();
private List<ProductInventoryView> _inventory { get; set; } = new();
private string _btnUpdateText { get; set; } = "check";
private bool _working { get; set; }
[Inject] public ICrmCompanyHttpRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public IToastService Toaster { get; set; }
private CompanyDto Company { get; set; } = new();
private List<ProductInventoryView> Inventory { get; set; } = new();
private bool Loading { get; set; } = true;
protected override async Task OnInitializedAsync()
{
Console.WriteLine($"CompanyId => {CompanyId}");
_interceptor.RegisterEvent();
_interceptor.RegisterBeforeSendEvent();
_company = await _companyRepo.GetCompanyById(CompanyId);
_inventory = await CrmHistoryRepo.FetchInventory(CompanyId);
_inventory = _inventory.OrderBy(x => x.Description).ToList();
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
Company = await CompanyRepo.GetCompanyById(CompanyId);
var ts = await CrmHistoryRepo.UpdateProductHistory(Company.CompanyId, $"{DateTime.Parse(Company.HistorySync):yyyy-MM-dd}");;
Inventory = await CrmHistoryRepo.FetchInventory(CompanyId);
Inventory = Inventory.OrderBy(x => x.Description).ToList();
Loading = false;
}
private async Task RefreshHistory()
{
_working = true;
_btnUpdateText = "vent venligst ...";
_toast.ShowInfo("Vent mens data checkes ...");
var ts = await CrmHistoryRepo.UpdateProductHistory(_company.CompanyId, $"{DateTime.Parse(_company.HistorySync):yyyy-MM-dd}");;
_company.HistorySync = ts.Replace("\"", "");
_btnUpdateText = "check";
_inventory = await CrmHistoryRepo.FetchInventory(CompanyId);
_inventory = _inventory.OrderBy(x => x.Description).ToList();
_working = false;
}
// private async Task RefreshHistory()
// {
// _working = true;
// _btnUpdateText = "vent venligst ...";
// _toast.ShowInfo("Vent mens data checkes ...");
// var ts = await CrmHistoryRepo.UpdateProductHistory(_company.CompanyId, $"{DateTime.Parse(_company.HistorySync):yyyy-MM-dd}");;
// _company.HistorySync = ts.Replace("\"", "");
// _btnUpdateText = "check";
// _inventory = await CrmHistoryRepo.FetchInventory(CompanyId);
// _inventory = _inventory.OrderBy(x => x.Description).ToList();
// _working = false;
// }
public void Dispose()
{
_interceptor.DisposeEvent();
Interceptor.DisposeEvent();
}
}

View file

@ -20,25 +20,19 @@
@attribute [Authorize(Roles = "Advisor")]
@page "/sales-reports/new"
<EditForm EditContext="_editContext">
<div class="card">
<div class="card-header bg-dark text-white rounded-2">
<div class="row">
<div class="col-md-6 justify-content-center">
<h3 class="workDate">@(_workDate.ToLongDateString())</h3>
</div>
<div class="col-md-4 justify-content-center">
<WorkDateComponent SelectedDate="@($"{_workDate:yyyy-MM-dd}")" OnChanged="SetWorkDate"></WorkDateComponent>
</div>
<div class="col-md-2 justify-content-center">
@if (_working)
{
<LoaderThreeDots />
}
</div>
<div class="row bg-dark text-white rounded-2 mb-2 py-2 align-items-center">
<div class="col">
<WorkDateComponent OnChangedCallback="SetWorkDate"/>
</div>
<div class="col-3 justify-content-center">
@if (_working)
{
<LoaderThreeDots/>
}
</div>
</div>
<div class="card-body">
<EditForm EditContext="_editContext">
<div class="sticky-top bg-success bg-opacity-50 rounded-2 px-3">
<table class="table">
<thead>
@ -100,167 +94,188 @@
</tr>
</tbody>
</table>
</div>
@if (!_report.DayTypeEnum.ToLower().Contains("leave"))
{
<table class="table">
<thead>
<tr>
<th scope="col" style="width:60%">Tekst</th>
<th scope="col">Medkørende Supervisor</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<InputTextArea id="description" class="form-control" @bind-Value="_report.Description"/>
<ValidationMessage For="@(() => _report.Description)"/>
</td>
<td>
<InputText id="supervisedBy" class="form-control" @bind-Value="_report.SupervisedBy"/>
<ValidationMessage For="@(() => _report.SupervisedBy)"/>
</td>
</tr>
</tbody>
</table>
<table class="table">
<thead>
<tr>
<th scope="col">Km aften</th>
<th scope="col">Km morgen</th>
<th scope="col">Km privat</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<InputNumber class="form-control" @bind-Value="@_report.Figures.KmEvening"
disabled="@(_noFigures)"/>
</td>
<td>
<InputNumber class="form-control" @bind-Value="@_report.Figures.KmMorning"
disabled="@(_noFigures)"/>
</td>
<td>
<InputNumber class="form-control" @bind-Value="@_report.Figures.DistancePrivate"
disabled="@(_noFigures)"/>
</td>
</tr>
</tbody>
</table>
<table class="table table-striped">
<thead>
<tr class="bg-dark text-white opacity-75">
<th></th>
<th class="text-center" colspan="2" scope="col">Dagens Demo @(_report.Figures.NewDemoCount + _report.Figures.RecallDemoCount)</th>
<th class="text-center border-end" colspan="2" scope="col">Dagens Resultat</th>
<th class="text-center" colspan="4" scope="col">Måneds Resultat</th>
</tr>
</thead>
<tbody>
<tr class="bg-dark bg-opacity-50">
<td></td>
<th class="text-end text-white" scope="col">Besøg</th>
<th class="text-end text-white" scope="col">Demo</th>
<th class="text-end text-white" scope="col">Salg</th>
<th class="text-end text-white border-end" scope="col">Beløb</th>
<th class="text-end text-white" scope="col">Besøg</th>
<th class="text-end text-white" scope="col">Demo</th>
<th class="text-end text-white" scope="col">Salg</th>
<th class="text-end text-white" scope="col">Beløb</th>
</tr>
<tr>
<th scope="row">N</th>
<td class="text-end">@_report.Figures.NewVisitCount</td>
<td class="text-end">@_report.Figures.NewDemoCount</td>
<td class="text-end">@_report.Figures.NewSaleCount</td>
<td class="text-end border-end">@_report.Figures.NewTurnover</td>
<td class="text-end">@_report.Figures.NewVisitCountMonth</td>
<td class="text-end">@_report.Figures.NewDemoCountMonth</td>
<td class="text-end">@_report.Figures.NewSaleCountMonth</td>
<td class="text-end">@_report.Figures.NewTurnoverMonth</td>
</tr>
<tr>
<th scope="row">R</th>
<td class="text-end">@_report.Figures.RecallVisitCount</td>
<td class="text-end">@_report.Figures.RecallDemoCount</td>
<td class="text-end">@_report.Figures.RecallSaleCount</td>
<td class="text-end border-end">@_report.Figures.RecallTurnover</td>
<td class="text-end">@_report.Figures.RecallVisitCountMonth</td>
<td class="text-end">@_report.Figures.RecallDemoCountMonth</td>
<td class="text-end">@_report.Figures.RecallSaleCountMonth</td>
<td class="text-end">@_report.Figures.RecallTurnoverMonth</td>
</tr>
<tr>
<th scope="row">SAS</th>
<td class="bg-light"></td>
<td class="bg-light"></td>
<td class="text-end">@_report.Figures.SasCount</td>
<td class="text-end border-end">@_report.Figures.SasTurnover</td>
<td class="bg-light"></td>
<td class="bg-light"></td>
<td class="text-end">@_report.Figures.SasCountMonth</td>
<td class="text-end">@_report.Figures.SasTurnoverMonth</td>
</tr>
<tr>
<th scope="row">TOTAL</th>
<td class="text-end">@_report.Figures.TotalVisitCount</td>
<td class="text-end">@_report.Figures.TotalDemoCount</td>
<td class="text-end">@_report.Figures.TotalSaleCount</td>
<td class="text-end border-end">@_report.Figures.TotalTurnover</td>
<td class="text-end">@_report.Figures.TotalVisitCountMonth</td>
<td class="text-end">@_report.Figures.TotalDemoCountMonth</td>
<td class="text-end">@_report.Figures.TotalSaleCountMonth</td>
<td class="text-end">@_report.Figures.TotalTurnoverMonth</td>
</tr>
</tbody>
</table>
@if (_activities != null)
@if (!_report.DayTypeEnum.ToLower().Contains("leave"))
{
<table class="table">
<thead>
<tr class="bg-black opacity-75 text-white">
<th scope="col">Kunde</th>
<th scope="col">Bynavn</th>
<th scope="col">Demo</th>
<th scope="col">Salg</th>
<th scope="col">Note</th>
<th scope="col" class="text-end">sas</th>
<th scope="col" class="text-end">Beløb</th>
<th scope="col" class="text-center"><i class="oi oi-phone"></i></th>
<th scope="col" class="text-center"><i class="oi oi-flash"></i></th>
<th scope="col" class="text-center"><i class="oi oi-calculator"></i></th>
<tr>
<th scope="col" style="width:60%">Tekst</th>
<th scope="col">Medkørende Supervisor</th>
</tr>
</thead>
<tbody>
@foreach (var activity in _activities)
{
<tr>
<td class="text-sm-start">@activity.Company.Name</td>
<td class="text-sm-start">@activity.Company.City</td>
<td class="text-sm-start">@activity.Demo</td>
<td class="text-sm-start">@activity.Sales</td>
<td class="text-sm-start">@activity.OfficeNote</td>
<td class="text-end">@($"{activity.SasAmount:N2}")</td>
<td class="text-center">@(activity.StatusTypeEnum.Contains("Quote") ? $"{0:N2}" : $"{activity.OrderAmount:N2}")</td>
<td class="text-center">@if(activity.OurRef.Contains("T:")){ <i class="oi oi-phone"></i> }</td>
<td class="text-center">@if(activity.Express){ <i class="oi oi-flash"></i> }</td>
<td class="text-center">@if(activity.StatusTypeEnum == "Quote"){ <i class="oi oi-calculator"></i> }</td>
</tr>
}
<tr>
<td colspan="5"></td>
<td class="text-end">Total</td>
<td class="text-end">@_activities.Where(x => x.StatusTypeEnum != "Quote").Sum(x => x.OrderAmount)</td>
<td colspan="2"></td>
<td>
<InputTextArea id="description" class="form-control" @bind-Value="_report.Description"/>
<ValidationMessage For="@(() => _report.Description)"/>
</td>
<td>
<InputText id="supervisedBy" class="form-control" @bind-Value="_report.SupervisedBy"/>
<ValidationMessage For="@(() => _report.SupervisedBy)"/>
</td>
</tr>
</tbody>
</table>
<table class="table">
<thead>
<tr>
<th scope="col">Km aften</th>
<th scope="col">Km morgen</th>
<th scope="col">Km privat</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<InputNumber class="form-control" @bind-Value="@_report.Figures.KmEvening"
disabled="@(_noFigures)"/>
</td>
<td>
<InputNumber class="form-control" @bind-Value="@_report.Figures.KmMorning"
disabled="@(_noFigures)"/>
</td>
<td>
<InputNumber class="form-control" @bind-Value="@_report.Figures.DistancePrivate"
disabled="@(_noFigures)"/>
</td>
</tr>
</tbody>
</table>
<table class="table table-striped">
<thead>
<tr class="bg-dark text-white opacity-75">
<th></th>
<th class="text-center" colspan="2" scope="col">Dagens Demo @(_report.Figures.NewDemoCount + _report.Figures.RecallDemoCount)</th>
<th class="text-center border-end" colspan="2" scope="col">Dagens Resultat</th>
<th class="text-center" colspan="4" scope="col">Måneds Resultat</th>
</tr>
</thead>
<tbody>
<tr class="bg-dark bg-opacity-50">
<td></td>
<th class="text-end text-white" scope="col">Besøg</th>
<th class="text-end text-white" scope="col">Demo</th>
<th class="text-end text-white" scope="col">Salg</th>
<th class="text-end text-white border-end" scope="col">Beløb</th>
<th class="text-end text-white" scope="col">Besøg</th>
<th class="text-end text-white" scope="col">Demo</th>
<th class="text-end text-white" scope="col">Salg</th>
<th class="text-end text-white" scope="col">Beløb</th>
</tr>
<tr>
<th scope="row">N</th>
<td class="text-end">@_report.Figures.NewVisitCount</td>
<td class="text-end">@_report.Figures.NewDemoCount</td>
<td class="text-end">@_report.Figures.NewSaleCount</td>
<td class="text-end border-end">@_report.Figures.NewTurnover</td>
<td class="text-end">@_report.Figures.NewVisitCountMonth</td>
<td class="text-end">@_report.Figures.NewDemoCountMonth</td>
<td class="text-end">@_report.Figures.NewSaleCountMonth</td>
<td class="text-end">@_report.Figures.NewTurnoverMonth</td>
</tr>
<tr>
<th scope="row">R</th>
<td class="text-end">@_report.Figures.RecallVisitCount</td>
<td class="text-end">@_report.Figures.RecallDemoCount</td>
<td class="text-end">@_report.Figures.RecallSaleCount</td>
<td class="text-end border-end">@_report.Figures.RecallTurnover</td>
<td class="text-end">@_report.Figures.RecallVisitCountMonth</td>
<td class="text-end">@_report.Figures.RecallDemoCountMonth</td>
<td class="text-end">@_report.Figures.RecallSaleCountMonth</td>
<td class="text-end">@_report.Figures.RecallTurnoverMonth</td>
</tr>
<tr>
<th scope="row">SAS</th>
<td class="bg-light"></td>
<td class="bg-light"></td>
<td class="text-end">@_report.Figures.SasCount</td>
<td class="text-end border-end">@_report.Figures.SasTurnover</td>
<td class="bg-light"></td>
<td class="bg-light"></td>
<td class="text-end">@_report.Figures.SasCountMonth</td>
<td class="text-end">@_report.Figures.SasTurnoverMonth</td>
</tr>
<tr>
<th scope="row">TOTAL</th>
<td class="text-end">@_report.Figures.TotalVisitCount</td>
<td class="text-end">@_report.Figures.TotalDemoCount</td>
<td class="text-end">@_report.Figures.TotalSaleCount</td>
<td class="text-end border-end">@_report.Figures.TotalTurnover</td>
<td class="text-end">@_report.Figures.TotalVisitCountMonth</td>
<td class="text-end">@_report.Figures.TotalDemoCountMonth</td>
<td class="text-end">@_report.Figures.TotalSaleCountMonth</td>
<td class="text-end">@_report.Figures.TotalTurnoverMonth</td>
</tr>
</tbody>
</table>
@if (_activities != null)
{
<table class="table">
<thead>
<tr class="bg-black opacity-75 text-white">
<th scope="col">Kunde</th>
<th scope="col">Bynavn</th>
<th scope="col">Demo</th>
<th scope="col">Salg</th>
<th scope="col">Note</th>
<th scope="col" class="text-end">sas</th>
<th scope="col" class="text-end">Beløb</th>
<th scope="col" class="text-center">
<i class="oi oi-phone"></i>
</th>
<th scope="col" class="text-center">
<i class="oi oi-flash"></i>
</th>
<th scope="col" class="text-center">
<i class="oi oi-calculator"></i>
</th>
</tr>
</thead>
<tbody>
@foreach (var activity in _activities)
{
<tr>
<td class="text-sm-start">@activity.Company.Name</td>
<td class="text-sm-start">@activity.Company.City</td>
<td class="text-sm-start">@activity.Demo</td>
<td class="text-sm-start">@activity.Sales</td>
<td class="text-sm-start">@activity.OfficeNote</td>
<td class="text-end">@($"{activity.SasAmount:N2}")</td>
<td class="text-center">@(activity.StatusTypeEnum.Contains("Quote") ? $"{0:N2}" : $"{activity.OrderAmount:N2}")</td>
<td class="text-center">
@if (activity.OurRef.Contains("T:"))
{
<i class="oi oi-phone"></i>
}
</td>
<td class="text-center">
@if (activity.Express)
{
<i class="oi oi-flash"></i>
}
</td>
<td class="text-center">
@if (activity.StatusTypeEnum == "Quote")
{
<i class="oi oi-calculator"></i>
}
</td>
</tr>
}
<tr>
<td colspan="5"></td>
<td class="text-end">Total</td>
<td class="text-end">@_activities.Where(x => x.StatusTypeEnum != "Quote").Sum(x => x.OrderAmount)</td>
<td colspan="2"></td>
</tr>
</tbody>
</table>
}
}
}
</div>
</div>
</EditForm>
<ConfirmationModal BodyMessage="@_prompt" OnOkClicked="ReportSaveConfirmed" @ref="_confirmReport"/>
<ConfirmationModal BodyMessage="@_prompt" OnOkClicked="ReportSaveConfirmed" @ref="_confirmReport"/>

View file

@ -21,17 +21,15 @@
@attribute [Authorize(Roles = "Advisor,Admin,Supervisor")]
<div class="report-main">
<div class="row mb-3 d-print-none">
<div class="col-md-6 align-content-center">
<h3 class="workDate">@DateTime.Parse(ReportDate).ToLongDateString()</h3>
<div class="row bg-dark text-white rounded-2 mb-2 py-2 align-items-center d-print-none">
<div class="col">
<WorkDateComponent OnChangedCallback="GetReport" />
</div>
<div class="col col-md-4 align-content-center">
<WorkDateComponent SelectedDate="@ReportDate" OnChanged="GetReport" />
</div>
<div class="col col-md-1 align-content-center">
<div class="col col-md-3 align-content-center">
<button class="btn btn-warning" type="button" onclick="window.print();">Print</button>
</div>
</div>
</div>
@if (!string.IsNullOrWhiteSpace(_report.ReportData.DayTypeEnum))
{

View file

@ -41,6 +41,7 @@ public partial class CrmReportViewPage
{
_report = new ReportView();
_items = new List<ReportItemView>();
if(workDate != ReportDate)
_navigator.NavigateTo($"/sales-reports/view/{workDate}");
_report = await CrmReportRepo.GetReport(workDate);

View file

@ -23,13 +23,13 @@
<div class="card">
<div class="card-header">
<div class="row mb-1 align-items-center">
<div class="col">
<h3 class="workDate">@(string.IsNullOrWhiteSpace(_workDate) ? "" : $"{DateTime.Parse(_workDate).ToLongDateString()}")</h3>
@* <div class="col"> *@
@* <h3 class="workDate">@(string.IsNullOrWhiteSpace(_workDate) ? "" : $"{DateTime.Parse(_workDate).ToLongDateString()}")</h3> *@
@* </div> *@
<div class="col-md-6">
<WorkDateComponent OnChangedCallback="GetTaskItems" />
</div>
<div class="col">
<WorkDateComponent SelectedDate="_workDate" OnChanged="GetTaskItems"></WorkDateComponent>
</div>
<div class="col">
<div class="col-md-6">
<a class="btn btn-primary" href="/task-items/new">NY OPGAVE</a>
</div>
</div>

View file

@ -22,13 +22,13 @@
<div class="report-main">
<div class="row mb-3 d-print-none">
<div class="col-md-6 align-content-center">
<h3 class="workDate">@DateTime.Parse(ReportDate).ToLongDateString()</h3>
@* <div class="col-md-6 align-content-center"> *@
@* <h3 class="workDate">@DateTime.Parse(ReportDate).ToLongDateString()</h3> *@
@* </div> *@
<div class="col col-md-6 align-content-center">
<WorkDateComponent OnChangedCallback="GetReport" />
</div>
<div class="col col-md-4 align-content-center">
<WorkDateComponent SelectedDate="@ReportDate" OnChanged="GetReport" />
</div>
<div class="col col-md-1 align-content-center">
<div class="col col-md-6 align-content-center">
<button class="btn btn-warning" type="button" onclick="window.print();">Print</button>
</div>
</div>

View file

@ -78,6 +78,7 @@ builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();
builder.Services.AddScoped<RefreshTokenService>();
builder.Services.AddScoped<VatInfoLookupService>();
builder.Services.AddScoped<PreferenceService>();
builder.Services.AddScoped<OrderDraftService>();
// ---------------------------------------

View file

@ -0,0 +1,39 @@
// 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 Blazored.LocalStorage;
using Wonky.Client.Models;
namespace Wonky.Client.Services;
public class OrderDraftService
{
private readonly ILocalStorageService _localStorageService;
public OrderDraftService(ILocalStorageService localStorageService)
{
_localStorageService = localStorageService;
}
public async Task<Draft> GetDraft(string companyId)
{
return await _localStorageService.GetItemAsync<Draft>(companyId) ?? new Draft();
}
public async Task SaveDraft(string companyId, Draft draft)
{
await _localStorageService.SetItemAsync(companyId, draft);
}
}

View file

@ -0,0 +1,38 @@
@*
// 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]
//
*@
<div class="modal" tabindex="-1" role="dialog" style="display:@_modalDisplay">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Bekræft Venligst</h3>
<button type="button" class="btn-close" @onclick="Hide" data-bs-dismiss="modal" aria-label="Luk"></button>
</div>
<div class="modal-body">
<p class="fw-bold">@BodyMessage</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" @onclick="Hide">Afbryd</button>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="() => OnOkClicked.InvokeAsync()">OK</button>
</div>
</div>
</div>
</div>
@if (_showBackdrop)
{
<div class="modal-backdrop fade show"></div>
}

View file

@ -0,0 +1,42 @@
// 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;
namespace Wonky.Client.Shared;
public partial class ConfirmWorkDateModal
{
private string _modalDisplay = "";
private bool _showBackdrop;
[Parameter] public string BodyMessage { get; set; } = "";
[Parameter] public EventCallback OnOkClicked { get; set; }
public void Show()
{
_modalDisplay = "block;";
_showBackdrop = true;
StateHasChanged();
}
public void Hide()
{
_modalDisplay = "none;";
_showBackdrop = false;
StateHasChanged();
}
}

View file

@ -22,7 +22,7 @@ namespace Wonky.Client.Shared;
public partial class DraftStateProvider
{
[Parameter] public RenderFragment ChildContent { get; set; }
[Parameter] public string Account { get; set; } = "MyCart";
[Parameter] public string DraftId { get; set; } = "";
[Inject] public ILocalStorageService LocalStorageService { get; set; }
public Draft Draft { get; set; } = new();
@ -30,8 +30,10 @@ public partial class DraftStateProvider
protected override async Task OnParametersSetAsync()
{
Draft = await LocalStorageService.GetItemAsync<Draft>(Account);
if (string.IsNullOrWhiteSpace(DraftId))
DraftId = "default";
Draft = await LocalStorageService.GetItemAsync<Draft>(DraftId);
if (Draft == null || Draft.Items.Count == 0)
{
Draft = new Draft();
@ -49,12 +51,12 @@ public partial class DraftStateProvider
public async Task SaveChangesAsync()
{
await LocalStorageService.SetItemAsync(Account, Draft);
await LocalStorageService.SetItemAsync(DraftId, Draft);
}
public async Task DeleteDraftAsync()
{
Draft.Items.Clear();
await LocalStorageService.SetItemAsync(Account, Draft);
await LocalStorageService.SetItemAsync(DraftId, Draft);
}
}

View file

@ -15,7 +15,8 @@
//
*@
<div class="modal" tabindex="-1" role="dialog" style="display:@_modalDisplay">
@* <div class="modal" tabindex="-1" role="dialog" style="display:@_modalDisplay"> *@
<div class="modal fade" tabindex="-1" style="display:@_modalDisplay">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header bg-info">
@ -24,7 +25,7 @@
</div>
<div class="modal-body">
<div class="alert alert-info">
<p class="fw-bold">@BodyMessage</p>
<p class="fw-bold">@BodyMessage</p>
</div>
</div>
<div class="modal-footer">

View file

@ -0,0 +1,94 @@
@*
// 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 Wonky.Client.Helpers
<div class="modal" tabindex="-1" role="dialog" style="display:@_modalDisplay">
<div class="modal-dialog modal-dialog-scrollable modal-lg modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">@ProductName</h5>
<button type="button" class="btn-close" @onclick="Hide" data-bs-dismiss="modal" aria-label="Luk"></button>
</div>
<div class="modal-body">
<div class="row align-middle">
<div class="col-sm-4 text-sm-start">
@SalesItem.Name
</div>
<div class="col-sm-2 text-sm-start">
@SalesItem.Sku
</div>
<div class="col-sm-2 text-sm-start">
@SalesItem.ShortName
</div>
<div class="col-sm-4">
<ul class="list-group">
@foreach (var rate in SalesItem.Rates)
{
<li class="list-group-item d-flex justify-content-between align-items-end">
<div class="text-sm-end me-1">@rate.Quantity</div>
<div class="text-sm-end me-1">@rate.Rate</div>
<div>
<a class="btn btn-primary btn-sm" data-bs-dismiss="modal" @onclick="@(() => SelectItem(rate.Quantity, rate.Rate))">
<i class="oi oi-plus"></i>
</a>
</div>
</li>
}
</ul>
</div>
</div>
@if (!string.IsNullOrWhiteSpace(ProductName))
{
<table class="table table-striped">
<thead>
<tr>
<th scope="col">Dato</th>
<th scope="col">Antal</th>
<th scope="col">Rabat</th>
<th scope="col">Pris</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
@foreach (var entry in History)
{
<tr>
<td>@entry.DeliveryDate</td>
<td>@entry.Quantity</td>
<td>@entry.Discount</td>
<td>@entry.Price</td>
<td>
<button type="button" class="btn btn-primary" data-bs-dismiss="modal" @onclick="() => SelectLine(entry)">Overfør</button>
</td>
</tr>
}
</tbody>
</table>
}
else
{
<h3>Ingen data</h3>
}
</div>
</div>
</div>
</div>
@if (_showBackdrop)
{
<div class="modal-backdrop fade show"></div>
}

View file

@ -0,0 +1,92 @@
// 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.Globalization;
using Microsoft.AspNetCore.Components;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpInterfaces;
using Wonky.Client.HttpRepository;
using Wonky.Client.Models;
using Wonky.Client.Services;
using Wonky.Entity.Requests;
using Wonky.Entity.Views;
namespace Wonky.Client.Shared;
public partial class InventoryReorderModal
{
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public SalesItemView SalesItem { get; set; } = new();
[Inject] public ICrmHistoryHttpRepository CrmHistoryRepo { get; set; }
[Parameter] public EventCallback<DraftItem> OnSelected { get; set; }
private List<ProductHistoryView> History { get; set; }
private string ProductName { get; set; } = "";
private string _modalDisplay = "";
private bool _showBackdrop;
protected override async Task OnParametersSetAsync()
{
if (string.IsNullOrWhiteSpace(SalesItem.Sku))
return;
History = await CrmHistoryRepo.FetchHistory(CompanyId, SalesItem.Sku);
if (History.Any())
{
ProductName = History[0].Description;
}
}
private void SelectItem(string quantity, string rate)
{
var result = new DraftItem
{
Discount = 0,
Item = SalesItem,
Price = decimal.Parse(rate, CultureInfo.InvariantCulture),
Quantity = int.Parse(quantity)
};
OnSelected.InvokeAsync(result);
Hide();
}
private void SelectLine(ProductHistoryView item)
{
var result = new DraftItem
{
Discount = item.Discount,
Item = SalesItem,
Price = item.Price,
Quantity = item.Quantity,
};
OnSelected.InvokeAsync(result);
Hide();
}
public void Show()
{
_modalDisplay = "block;";
_showBackdrop = true;
StateHasChanged();
}
private void Hide()
{
_modalDisplay = "none;";
_showBackdrop = false;
StateHasChanged();
}
}

View file

@ -11,7 +11,17 @@ main {
.sidebar {
background-image: linear-gradient(180deg, rgb(22, 21, 23) 10%, #ffaa00 100%);
}
.bar-system-date {
color: #ffaa00;
background-color: #0e0e0e;
border-bottom: 1px solid #d6d5d5;
justify-content: flex-start;
height: 3.5rem;
display: flex;
align-items: center;
font-size: 2rem;
font-variant: all-petite-caps;
}
.top-row {
color: #ffaa00;
background-color: #0e0e0e;

View file

@ -20,9 +20,7 @@
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/">
<TopbarDisplayUser></TopbarDisplayUser>
</a>
<a class="navbar-brand" href="/"><TopbarDisplayUser /></a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>

View file

@ -28,7 +28,7 @@ public partial class ProductPriceHistoryModal
{
[Parameter] public EventCallback<decimal> OnSelected { get; set; }
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string ItemSku { get; set; } = "";
[Parameter] public string Sku { get; set; } = "";
[Inject] public ICrmHistoryHttpRepository CrmHistoryRepo { get; set; }
private List<ProductHistoryView> History { get; set; }
private string ProductName { get; set; } = "";
@ -37,10 +37,10 @@ public partial class ProductPriceHistoryModal
protected override async Task OnParametersSetAsync()
{
if (string.IsNullOrWhiteSpace(ItemSku))
if (string.IsNullOrWhiteSpace(Sku))
return;
History = await CrmHistoryRepo.FetchHistory(CompanyId, ItemSku);
History = await CrmHistoryRepo.FetchHistory(CompanyId, Sku);
if (History.Any())
{
ProductName = History[0].Description;

View file

@ -1,13 +1,13 @@
{
"appInfo": {
"name": "Wonky Client",
"version": "0.26.5",
"version": "0.27.1",
"rc": true,
"sandBox": false,
"image": "grumpy-coder.png"
},
"apiConfig": {
"innoBaseUrl": "https://zeta.innotec.dk",
"innoBaseUrl": "https://dev.innotec.dk",
"glsTrackUrl": "https://www.gls-group.eu/276-I-PORTAL-WEB/content/GLS/DK01/DA/5004.htm?txtAction=71000&txtRefNo=",
"glsId": "",
"serviceVirk": "api/v2/services/virk",
@ -34,9 +34,9 @@
},
"Logging": {
"LogLevel": {
"Default": "Information",
"System": "Information",
"Microsoft": "None"
"Default": "Debug",
"System": "Debug",
"Microsoft": "Information"
},
"Debug": {
"LogLevel": {

View file

@ -36,7 +36,7 @@ a, .btn-link {
border-color: #1861ac;
}
.text-inno {
.inno {
color: #ffaa00;
}

View file

@ -1,6 +1,5 @@
<!DOCTYPE html>
<html lang="da">
<head>
<meta charset="utf-8" />
<title>Inno Web CRM</title>
@ -29,7 +28,6 @@
<img src="/svg-loaders/three-dots.svg" alt="loading ..."/>
</div>
</div>
<script src="/bootstrap/js/bootstrap.bundle.js"></script>
<!--<script src="/scripts/bsTooltip.js"></script>-->
<script src="_framework/blazor.webassembly.js"></script>

View file

@ -17,7 +17,16 @@ namespace Wonky.Entity.Views;
public class ProductInventoryView
{
/// <summary>
/// entity description
/// </summary>
public string Description { get; set; } = "";
/// <summary>
/// entity item number
/// </summary>
public string Sku { get; set; } = "";
/// <summary>
/// quantity bought over time
/// </summary>
public int Quantity { get; set; }
}

View file

@ -25,5 +25,6 @@ public class SalesItemView
public string ProductGroup { get; set; } = "";
public string PictureLink { get; set; } = "";
public string Location { get; set; } = "";
public bool Discontinued { get; set; }
public List<SalesRateView> Rates { get; set; } = new();
}