toggle edit in customer view/edit page

This commit is contained in:
Frede Hundewadt 2023-02-26 09:17:59 +01:00
parent 1a0255a14a
commit 41eccd64bf
32 changed files with 1523 additions and 1483 deletions

View file

@ -26,31 +26,30 @@ using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Components
namespace Wonky.Client.Components;
public partial class AdvisorCustomerListComponent
{
public partial class AdvisorCustomerListComponent
[Parameter] public List<CompanyDto> CompanyList { get; set; } = new();
[Parameter] public EventCallback<string> OnDelete { get; set; }
[Parameter] public EventCallback<string> OnSelect { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IJSRuntime JsRuntime { get; set; }
private Lazy<IJSObjectReference> BsTooltip { get; set; } = new();
private InformationModal InformationModal { get; set; } = new();
private string InfoMessage { get; set; } = "";
private string CompanyId { get; set; } = "";
private void ViewCustomer(string companyId)
{
[Parameter] public List<CompanyDto> CompanyList { get; set; } = new();
[Parameter] public EventCallback<string> OnDelete { get; set; }
[Parameter] public EventCallback<string> OnSelect { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IJSRuntime JsRuntime { get; set; }
Navigator.NavigateTo($"/advisor/customers/{companyId}");
}
private Lazy<IJSObjectReference> BsTooltip { get; set; } = new();
private InformationModal InformationModal { get; set; } = new();
private string InfoMessage { get; set; } = "";
private string CompanyId { get; set; } = "";
private void ViewCustomer(string companyId)
{
Navigator.NavigateTo($"/advisor/customers/{companyId}");
}
private void CallInformationModal(string info)
{
InfoMessage = info;
InformationModal.Show();
}
}
}
private void CallInformationModal(string info)
{
InfoMessage = info;
InformationModal.Show();
}
}

View file

@ -17,35 +17,34 @@ using System.Timers;
using Microsoft.AspNetCore.Components;
using Timer = System.Timers.Timer;
namespace Wonky.Client.Components
namespace Wonky.Client.Components;
public partial class CatalogSearchPhraseComponent
{
public partial class CatalogSearchPhraseComponent
private Timer Timer { get; set; } = new();
private string SearchTerm { get; set; } = "";
[Parameter] public EventCallback<string> OnChanged { get; set; }
private void ClearSearch()
{
private Timer Timer { get; set; } = new();
private string SearchTerm { get; set; } = "";
[Parameter] public EventCallback<string> OnChanged { get; set; }
private void ClearSearch()
{
SearchTerm = "";
OnChanged.InvokeAsync("");
}
SearchTerm = "";
OnChanged.InvokeAsync("");
}
private void OnSearchChanged()
{
Timer.Dispose();
Timer = new Timer(500);
Timer.AutoReset = false;
Timer.Elapsed += OnTimerElapsed;
Timer.Enabled = true;
}
private void OnSearchChanged()
{
Timer.Dispose();
Timer = new Timer(500);
Timer.AutoReset = false;
Timer.Elapsed += OnTimerElapsed;
Timer.Enabled = true;
}
private void OnTimerElapsed(object? sender, ElapsedEventArgs e)
{
OnChanged.InvokeAsync(SearchTerm);
Timer.Elapsed -= OnTimerElapsed;
Timer.Enabled = false;
Timer.Dispose();
}
}
}
private void OnTimerElapsed(object? sender, ElapsedEventArgs e)
{
OnChanged.InvokeAsync(SearchTerm);
Timer.Elapsed -= OnTimerElapsed;
Timer.Enabled = false;
Timer.Dispose();
}
}

View file

@ -19,48 +19,47 @@ using Wonky.Client.Services;
using Timer = System.Timers.Timer;
#pragma warning disable CS8618
namespace Wonky.Client.Components
namespace Wonky.Client.Components;
public partial class CustomerSearchPhraseComponent
{
public partial class CustomerSearchPhraseComponent
private Timer InputTimer { get; set; } = new();
private string SearchTerm { get; set; } = "";
private UserProfile Profiles { get; set; } = new ();
[Inject] public UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
protected override async Task OnInitializedAsync()
{
private Timer InputTimer { get; set; } = new();
private string SearchTerm { get; set; } = "";
private UserProfile Profiles { get; set; } = new ();
[Inject] public UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
protected override async Task OnInitializedAsync()
{
Profiles = await ProfileService.GetProfile();
SearchTerm = string.IsNullOrWhiteSpace(Profiles.CompanyFilterPhrase) ? "" : Profiles.CompanyFilterPhrase.Trim();
Profiles = await ProfileService.GetProfile();
SearchTerm = string.IsNullOrWhiteSpace(Profiles.CompanyFilterPhrase) ? "" : Profiles.CompanyFilterPhrase.Trim();
if(!string.IsNullOrWhiteSpace(SearchTerm))
await OnChanged.InvokeAsync(SearchTerm);
}
private async Task ClearSearch()
{
InputTimer.Dispose();
SearchTerm = "";
await ProfileService.SetCompanyFilterPhrase(SearchTerm.Trim());
if(!string.IsNullOrWhiteSpace(SearchTerm))
await OnChanged.InvokeAsync(SearchTerm);
}
private async Task OnSearchChanged()
{
await ProfileService.SetCompanyFilterPhrase(SearchTerm.Trim());
InputTimer.Dispose();
InputTimer = new Timer(500);
InputTimer.AutoReset = false;
InputTimer.Elapsed += OnTimerElapsed;
InputTimer.Enabled = true;
}
}
private void OnTimerElapsed(object? sender, ElapsedEventArgs e)
{
InputTimer.Dispose();
OnChanged.InvokeAsync(SearchTerm);
}
}
}
private async Task ClearSearch()
{
InputTimer.Dispose();
SearchTerm = "";
await ProfileService.SetCompanyFilterPhrase(SearchTerm.Trim());
await OnChanged.InvokeAsync(SearchTerm);
}
private async Task OnSearchChanged()
{
await ProfileService.SetCompanyFilterPhrase(SearchTerm.Trim());
InputTimer.Dispose();
InputTimer = new Timer(500);
InputTimer.AutoReset = false;
InputTimer.Elapsed += OnTimerElapsed;
InputTimer.Enabled = true;
}
private void OnTimerElapsed(object? sender, ElapsedEventArgs e)
{
InputTimer.Dispose();
OnChanged.InvokeAsync(SearchTerm);
}
}

View file

@ -20,38 +20,36 @@ using Microsoft.AspNetCore.Components;
using Wonky.Client.Services;
#pragma warning disable CS8618
namespace Wonky.Client.Components
{
public partial class CustomerSortComponent : IDisposable
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
private Dictionary<string, string> Items { get; set; } = new();
private UserProfile _profiles = new();
private string SortCol { get; set; } = "name";
protected override async Task OnInitializedAsync()
{
ProfileService.OnChange += ProfileServiceOnOnChange;
_profiles = await ProfileService.GetProfile();
SortCol = _profiles.CompanySort;
}
private async Task OnSelectionChanged(ChangeEventArgs e)
{
var val = e.Value.ToString();
if (val == "-1") return;
await OnChanged.InvokeAsync(val);
await ProfileService.SetCompanySort(val);
}
private void ProfileServiceOnOnChange(UserProfile newUserProfile)
{
_profiles = newUserProfile;
StateHasChanged();
}
public void Dispose()
{
ProfileService.OnChange -= ProfileServiceOnOnChange;
}
}
}
namespace Wonky.Client.Components;
public partial class CustomerSortComponent : IDisposable
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
private Dictionary<string, string> Items { get; set; } = new();
private UserProfile _profiles = new();
private string SortCol { get; set; } = "name";
protected override async Task OnInitializedAsync()
{
ProfileService.OnChange += ProfileServiceOnOnChange;
_profiles = await ProfileService.GetProfile();
SortCol = _profiles.CompanySort;
}
private async Task OnSelectionChanged(ChangeEventArgs e)
{
var val = e.Value.ToString();
if (val == "-1") return;
await OnChanged.InvokeAsync(val);
await ProfileService.SetCompanySort(val);
}
private void ProfileServiceOnOnChange(UserProfile newUserProfile)
{
_profiles = newUserProfile;
StateHasChanged();
}
public void Dispose()
{
ProfileService.OnChange -= ProfileServiceOnOnChange;
}
}

View file

@ -27,77 +27,76 @@ using Utils = Wonky.Client.Helpers.Utils;
#pragma warning disable CS8618
namespace Wonky.Client.Components
namespace Wonky.Client.Components;
public partial class OfficeCountryCustomerListComponent
{
public partial class OfficeCountryCustomerListComponent
// ******************************************************
// parameters
[Parameter] public string CountryCode { get; set; } = "";
[Parameter] public List<CompanyDto> CompanyList { get; set; } = new();
// [Parameter] public EventCallback<DraftItem> OnOrderItem { get; set; }
[CascadingParameter] public DraftStateProvider DraftProvider { get; set; }
// ******************************************************
// injects
[Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; }
[Inject] public ICountryActivityRepository ActivityRepo { get; set; }
// ******************************************************
// overlays
private OfficeCustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new();
private OfficeCustomerActivityListOverlay ActivityListOverlay { get; set; } = new();
private OfficeCustomerProductListOverlay ProductListOverlay { get; set; } = new();
// ******************************************************
// variables
private InvoiceListView InvoiceList { get; set; } = new();
private List<ReportItemView> ActivityList { get; set; } = new();
private List<ProductInventoryView> ProductList { get; set; } = new();
private CompanyDto SelectedCompany { get; set; } = new();
// ******************************************************
// functions
private async Task ShowInvoiceList(string companyId)
{
// ******************************************************
// parameters
[Parameter] public string CountryCode { get; set; } = "";
[Parameter] public List<CompanyDto> CompanyList { get; set; } = new();
// [Parameter] public EventCallback<DraftItem> OnOrderItem { get; set; }
[CascadingParameter] public DraftStateProvider DraftProvider { get; set; }
// ******************************************************
// injects
[Inject] public ICountryCustomerHistoryRepository HistoryRepo { get; set; }
[Inject] public ICountryActivityRepository ActivityRepo { get; set; }
// ******************************************************
// overlays
private OfficeCustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new();
private OfficeCustomerActivityListOverlay ActivityListOverlay { get; set; } = new();
private OfficeCustomerProductListOverlay ProductListOverlay { get; set; } = new();
// ******************************************************
// variables
private InvoiceListView InvoiceList { get; set; } = new();
private List<ReportItemView> ActivityList { get; set; } = new();
private List<ProductInventoryView> ProductList { get; set; } = new();
private CompanyDto SelectedCompany { get; set; } = new();
// check for console manipulation
if (!Utils.Validate(VType.Id, companyId)) return;
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
// call erp to crm sync before requesting invoices
var newSyncDate = await HistoryRepo.RequestErpToCrmSync(CountryCode, companyId, SelectedCompany.HistorySync);
await Task.Delay(500);
InvoiceList = await HistoryRepo.RequestInvoiceList(CountryCode, companyId);
if(!string.IsNullOrWhiteSpace(newSyncDate)) SelectedCompany.HistorySync = newSyncDate;
InvoiceListOverlay.Show();
}
// ******************************************************
// functions
private async Task ShowInvoiceList(string companyId)
{
// check for console manipulation
if (!Utils.Validate(VType.Id, companyId)) return;
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
// call erp to crm sync before requesting invoices
var newSyncDate = await HistoryRepo.RequestErpToCrmSync(CountryCode, companyId, SelectedCompany.HistorySync);
await Task.Delay(500);
InvoiceList = await HistoryRepo.RequestInvoiceList(CountryCode, companyId);
if(!string.IsNullOrWhiteSpace(newSyncDate)) SelectedCompany.HistorySync = newSyncDate;
InvoiceListOverlay.Show();
}
private async Task ShowActivityList(string companyId)
{
// check for console manipulation
if (!Utils.Validate(VType.Id, companyId)) return;
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
ActivityList = await ActivityRepo.RequestActivityList(companyId);
ActivityListOverlay.Show();
}
private async Task ShowActivityList(string companyId)
{
// check for console manipulation
if (!Utils.Validate(VType.Id, companyId)) return;
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
ActivityList = await ActivityRepo.RequestActivityList(companyId);
ActivityListOverlay.Show();
}
private async Task ShowInventory(string companyId)
{
// check for console manipulation
if (!Utils.Validate(VType.Id, companyId)) return;
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
// call erp to crm sync before requesting products
var newSyncDate = await HistoryRepo.RequestErpToCrmSync(CountryCode, companyId, SelectedCompany.HistorySync);
await Task.Delay(500);
if(!string.IsNullOrWhiteSpace(newSyncDate)) SelectedCompany.HistorySync = newSyncDate;
ProductList = await HistoryRepo.RequestInventory(SelectedCompany.CountryCode, SelectedCompany.CompanyId);
ProductListOverlay.Show();
}
private async Task ShowInventory(string companyId)
{
// check for console manipulation
if (!Utils.Validate(VType.Id, companyId)) return;
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
// call erp to crm sync before requesting products
var newSyncDate = await HistoryRepo.RequestErpToCrmSync(CountryCode, companyId, SelectedCompany.HistorySync);
await Task.Delay(500);
if(!string.IsNullOrWhiteSpace(newSyncDate)) SelectedCompany.HistorySync = newSyncDate;
ProductList = await HistoryRepo.RequestInventory(SelectedCompany.CountryCode, SelectedCompany.CompanyId);
ProductListOverlay.Show();
}
private async Task ShowOrder(string companyId)
{
// check for console manipulation
if (!Utils.Validate(VType.Id, companyId)) return;
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
}
private async Task ShowOrder(string companyId)
{
// check for console manipulation
if (!Utils.Validate(VType.Id, companyId)) return;
SelectedCompany = CompanyList.First(x => x.CompanyId == companyId);
}
}

View file

@ -20,41 +20,42 @@ using Microsoft.AspNetCore.Components;
using Wonky.Client.Services;
#pragma warning disable CS8618
namespace Wonky.Client.Components
namespace Wonky.Client.Components;
public partial class PageSizeComponent : IDisposable
{
public partial class PageSizeComponent : IDisposable
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
private Dictionary<string, string> Items { get; set; } = new();
private UserProfile Profile { get; set; } = new();
private string PageSize { get; set; }
protected override async Task OnInitializedAsync()
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Parameter] public EventCallback<string> OnChanged { get; set; }
private Dictionary<string, string> Items { get; set; } = new();
private UserProfile _profiles = new();
private string PageSize { get; set; } = "";
protected override async Task OnInitializedAsync()
{
ProfileService.OnChange += ProfileServiceOnOnChange;
_profiles = await ProfileService.GetProfile();
PageSize = _profiles.PageSize;
}
private async Task OnSelectChanged(ChangeEventArgs e)
{
var val = e.Value.ToString();
if (val == "-1") return;
await OnChanged.InvokeAsync(val);
await ProfileService.SetPageSize(val);
}
private void ProfileServiceOnOnChange(UserProfile newUserProfile)
{
_profiles = newUserProfile;
StateHasChanged();
}
public void Dispose()
{
ProfileService.OnChange -= ProfileServiceOnOnChange;
}
ProfileService.OnChange += ProfileServiceOnOnChange;
Profile = await ProfileService.GetProfile();
PageSize = Profile.PageSize;
}
}
private async Task OnSelectChanged(ChangeEventArgs e)
{
var val = e.Value?.ToString();
if (val == "-1") return;
var cVal = Convert.ToInt32(val);
if (cVal > 50) val = "50"; // mitigate variable manipulation
await OnChanged.InvokeAsync(val);
await ProfileService.SetPageSize(val);
}
private void ProfileServiceOnOnChange(UserProfile newUserProfile)
{
Profile = newUserProfile;
StateHasChanged();
}
public void Dispose()
{
ProfileService.OnChange -= ProfileServiceOnOnChange;
}
}

View file

@ -19,44 +19,43 @@ using Microsoft.AspNetCore.Components;
using Wonky.Client.Features;
using Wonky.Entity.Requests;
namespace Wonky.Client.Components
namespace Wonky.Client.Components;
public partial class PaginationComponent
{
public partial class PaginationComponent
[Parameter] public MetaData MetaData { get; set; } = new();
[Parameter] public int Spread { get; set; }
[Parameter] public EventCallback<int> SelectedPage { get; set; }
private List<PagingLink> Links { get; set; } = new();
protected override void OnParametersSet()
{
[Parameter] public MetaData MetaData { get; set; } = new();
[Parameter] public int Spread { get; set; }
[Parameter] public EventCallback<int> SelectedPage { get; set; }
private List<PagingLink> Links { get; set; } = new();
CreatePaginationLinks();
}
protected override void OnParametersSet()
private void CreatePaginationLinks()
{
Links = new List<PagingLink>
{
CreatePaginationLinks();
}
new(MetaData.CurrentPage - 1, MetaData.HasPrevious, "Forrige")
};
private void CreatePaginationLinks()
for (var i = 1; i <= MetaData.TotalPages; i++)
{
Links = new List<PagingLink>
if (i >= MetaData.CurrentPage - Spread && i <= MetaData.CurrentPage + Spread)
{
new(MetaData.CurrentPage - 1, MetaData.HasPrevious, "Forrige")
};
for (var i = 1; i <= MetaData.TotalPages; i++)
{
if (i >= MetaData.CurrentPage - Spread && i <= MetaData.CurrentPage + Spread)
{
Links.Add(new PagingLink(i, true, i.ToString()) {Active = MetaData.CurrentPage == i});
}
Links.Add(new PagingLink(i, true, i.ToString()) {Active = MetaData.CurrentPage == i});
}
Links.Add(new PagingLink(MetaData.CurrentPage + 1, MetaData.HasNext, "Næste"));
}
private async Task OnSelectedPage(PagingLink link)
{
if (link.Page == MetaData.CurrentPage || !link.Enabled)
return;
MetaData.CurrentPage = link.Page;
await SelectedPage.InvokeAsync(link.Page);
}
Links.Add(new PagingLink(MetaData.CurrentPage + 1, MetaData.HasNext, "Næste"));
}
private async Task OnSelectedPage(PagingLink link)
{
if (link.Page == MetaData.CurrentPage || !link.Enabled)
return;
MetaData.CurrentPage = link.Page;
await SelectedPage.InvokeAsync(link.Page);
}
}

View file

@ -21,57 +21,56 @@ using Wonky.Client.HttpRepository;
using Wonky.Client.Shared;
using Wonky.Entity.DTO;
namespace Wonky.Client.Components
namespace Wonky.Client.Components;
public partial class TaskItemTableComponent
{
public partial class TaskItemTableComponent
[Parameter] public List<TaskItemDto> TaskItemList { get; set; } = new();
[Parameter] public EventCallback<string> OnDeleteTask { get; set; }
[Parameter] public EventCallback<string> OnCompleteTask { get; set; }
[Parameter] public EventCallback<string> OnTaskCompleted { get; set; }
private ConfirmationModal _confirmationModal = new ();
private string _taskItemIdToDelete = "";
/// <summary>
/// Complete task callback
/// </summary>
/// <param name="taskItemId"></param>
private async Task CompleteTask(string taskItemId)
{
[Parameter] public List<TaskItemDto> TaskItemList { get; set; } = new();
[Parameter] public EventCallback<string> OnDeleteTask { get; set; }
[Parameter] public EventCallback<string> OnCompleteTask { get; set; }
[Parameter] public EventCallback<string> OnTaskCompleted { get; set; }
await OnCompleteTask.InvokeAsync(taskItemId);
}
private ConfirmationModal _confirmationModal = new ();
private string _taskItemIdToDelete = "";
/// <summary>
/// Task completed callback
/// </summary>
/// <param name="taskItemId"></param>
private async Task TaskCompleted(string taskItemId)
{
await OnTaskCompleted.InvokeAsync(taskItemId);
}
/// <summary>
/// Complete task callback
/// </summary>
/// <param name="taskItemId"></param>
private async Task CompleteTask(string taskItemId)
{
await OnCompleteTask.InvokeAsync(taskItemId);
}
/// <summary>
/// Task completed callback
/// </summary>
/// <param name="taskItemId"></param>
private async Task TaskCompleted(string taskItemId)
{
await OnTaskCompleted.InvokeAsync(taskItemId);
}
/// <summary>
/// Confirm delete
/// </summary>
/// <param name="taskItemId"></param>
private void CallConfirmationModal(string taskItemId)
{
_taskItemIdToDelete = taskItemId;
_confirmationModal.Show();
}
/// <summary>
/// Confirm delete
/// </summary>
/// <param name="taskItemId"></param>
private void CallConfirmationModal(string taskItemId)
{
_taskItemIdToDelete = taskItemId;
_confirmationModal.Show();
}
private void OnCancelCallback()
{
_confirmationModal.Hide();
}
/// <summary>
/// Delete task call back
/// </summary>
private async Task DeleteTask()
{
_confirmationModal.Hide();
await OnDeleteTask.InvokeAsync(_taskItemIdToDelete);
}
}
}
private void OnCancelCallback()
{
_confirmationModal.Hide();
}
/// <summary>
/// Delete task call back
/// </summary>
private async Task DeleteTask()
{
_confirmationModal.Hide();
await OnDeleteTask.InvokeAsync(_taskItemIdToDelete);
}
}

View file

@ -15,6 +15,7 @@
using System.Globalization;
using System.Text.Json;
using Blazored.LocalStorage;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Forms;
@ -69,8 +70,8 @@ public partial class WorkDateComponent : IDisposable
/// <param name="e"></param>
private async Task OnDateChanged(ChangeEventArgs e)
{
var x = DateTime.TryParse(e.Value.ToString(), out var setDate);
if (x)
if (string.IsNullOrWhiteSpace(e.Value.ToString())) return;
if (DateTime.TryParse(e.Value.ToString(), out var setDate))
{
await UserProfile.SetWorkDate(setDate);
await OnChangedCallback.InvokeAsync($"{setDate:yyyy-MM-dd}");

View file

@ -26,337 +26,336 @@
using System.Diagnostics;
namespace Wonky.Client.Helpers
namespace Wonky.Client.Helpers;
/// <summary>
/// Squid is guid string shortened and url safe
/// </summary>
[DebuggerDisplay("{" + nameof(Value) + "}")]
public readonly struct Squid : IEquatable<Squid>
{
/// <summary>
/// Squid is guid string shortened and url safe
/// Return Empty value
/// </summary>
[DebuggerDisplay("{" + nameof(Value) + "}")]
public readonly struct Squid : IEquatable<Squid>
// ReSharper disable once MemberCanBePrivate.Global
public static readonly Squid Empty = new(Guid.Empty);
/// <summary>
/// Decode Squid to Guid
/// </summary>
/// <param name="value"></param>
public Squid(string value)
{
/// <summary>
/// Return Empty value
/// </summary>
// ReSharper disable once MemberCanBePrivate.Global
public static readonly Squid Empty = new(Guid.Empty);
Value = value;
Guid = DecodeSquid(value);
}
/// <summary>
/// Decode Squid to Guid
/// </summary>
/// <param name="value"></param>
public Squid(string value)
/// <summary>
/// Generate Squid from Guid object
/// </summary>
/// <param name="obj"></param>
public Squid(Guid obj)
{
Value = EncodeGuid(obj);
Guid = obj;
}
/// <summary>
/// Guid
/// </summary>
// ReSharper disable once MemberCanBePrivate.Global
public Guid Guid { get; }
public string Value { get; }
/// <summary>
/// ToString() function
/// </summary>
/// <returns></returns>
public override string ToString()
{
return Value;
}
/// <summary>
/// Equality
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object? obj)
{
return obj is Squid other && Equals(other);
}
/// <summary>
/// EQuality
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool Equals(Squid obj)
{
return Guid.Equals(obj.Guid) && Value == obj.Value;
}
/// <summary>
/// Get hashcode
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
unchecked
{
Value = value;
Guid = DecodeSquid(value);
return (Guid.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0);
}
}
/// <summary>
/// Generate Squid from Guid object
/// </summary>
/// <param name="obj"></param>
public Squid(Guid obj)
/// <summary>
/// Create Squid from new Guid
/// </summary>
/// <returns></returns>
public static Squid NewGuid()
{
return new Squid(Guid.NewGuid());
}
/// <summary>
/// Encode string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string EncodeString(string value)
{
var guid = new Guid(value);
return EncodeGuid(guid);
}
/// <summary>
/// Encode Guid
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static string EncodeGuid(Guid obj)
{
var encoded = Convert.ToBase64String(obj.ToByteArray());
encoded = encoded
.Replace("/", "_")
.Replace("+", "-");
return encoded.Substring(0, 22);
}
/// <summary>
/// Decode Squid
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static Guid DecodeSquid(string value)
{
if (!value.Any()) return Empty;
value = value
.Replace("_", "/")
.Replace("-", "+");
var blob = Convert.FromBase64String(value + "==");
return new Guid(blob);
}
/// <summary>
/// Guid From Squid
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static Guid FromSquid(Squid obj)
{
return obj.Guid;
}
/// <summary>
/// Squid From String
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static Squid FromString(string value)
{
if (string.IsNullOrEmpty(value))
return Empty;
return TryParse(value, out Squid obj) ? obj : Empty;
}
/// <summary>
/// TryDecode
/// </summary>
/// <param name="value"></param>
/// <param name="obj"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static bool TryDecode(string value, out Guid obj)
{
try
{
Value = EncodeGuid(obj);
Guid = obj;
// Decode as Squid
obj = DecodeSquid(value);
return true;
}
/// <summary>
/// Guid
/// </summary>
// ReSharper disable once MemberCanBePrivate.Global
public Guid Guid { get; }
public string Value { get; }
/// <summary>
/// ToString() function
/// </summary>
/// <returns></returns>
public override string ToString()
catch (Exception)
{
return Value;
}
/// <summary>
/// Equality
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public override bool Equals(object? obj)
{
return obj is Squid other && Equals(other);
}
/// <summary>
/// EQuality
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public bool Equals(Squid obj)
{
return Guid.Equals(obj.Guid) && Value == obj.Value;
}
/// <summary>
/// Get hashcode
/// </summary>
/// <returns></returns>
public override int GetHashCode()
{
unchecked
{
return (Guid.GetHashCode() * 397) ^ (Value != null ? Value.GetHashCode() : 0);
}
}
/// <summary>
/// Create Squid from new Guid
/// </summary>
/// <returns></returns>
public static Squid NewGuid()
{
return new Squid(Guid.NewGuid());
}
/// <summary>
/// Encode string
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static string EncodeString(string value)
{
var guid = new Guid(value);
return EncodeGuid(guid);
}
/// <summary>
/// Encode Guid
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static string EncodeGuid(Guid obj)
{
var encoded = Convert.ToBase64String(obj.ToByteArray());
encoded = encoded
.Replace("/", "_")
.Replace("+", "-");
return encoded.Substring(0, 22);
}
/// <summary>
/// Decode Squid
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static Guid DecodeSquid(string value)
{
if (!value.Any()) return Empty;
value = value
.Replace("_", "/")
.Replace("-", "+");
var blob = Convert.FromBase64String(value + "==");
return new Guid(blob);
}
/// <summary>
/// Guid From Squid
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static Guid FromSquid(Squid obj)
{
return obj.Guid;
}
/// <summary>
/// Squid From String
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static Squid FromString(string value)
{
if (string.IsNullOrEmpty(value))
return Empty;
return TryParse(value, out Squid obj) ? obj : Empty;
}
/// <summary>
/// TryDecode
/// </summary>
/// <param name="value"></param>
/// <param name="obj"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static bool TryDecode(string value, out Guid obj)
{
try
{
// Decode as Squid
obj = DecodeSquid(value);
return true;
}
catch (Exception)
{
// Return empty Guid
obj = Guid.Empty;
return false;
}
}
/// <summary>
/// TryParse
/// </summary>
/// <param name="value"></param>
/// <param name="obj"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static bool TryParse(string value, out Squid obj)
{
// Parse as Squid string.
if (TryDecode(value, out var oGuid))
{
obj = oGuid;
return true;
}
// Parse as Guid string.
if (Guid.TryParse(value, out oGuid))
{
obj = oGuid;
return true;
}
obj = Empty;
return false;
}
/// <summary>
/// TryParse
/// </summary>
/// <param name="value"></param>
/// <param name="obj"></param>
/// <returns></returns>
public static bool TryParse(string value, out Guid obj)
{
// Try a Squid string.
if (TryDecode(value, out obj))
return true;
// Try a Guid string.
if (Guid.TryParse(value, out obj))
return true;
// Return empty Guid
obj = Guid.Empty;
return false;
}
}
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator ==(Squid x, Squid y)
/// <summary>
/// TryParse
/// </summary>
/// <param name="value"></param>
/// <param name="obj"></param>
/// <returns></returns>
// ReSharper disable once MemberCanBePrivate.Global
public static bool TryParse(string value, out Squid obj)
{
// Parse as Squid string.
if (TryDecode(value, out var oGuid))
{
return x.Guid == y.Guid;
obj = oGuid;
return true;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator ==(Squid x, Guid y)
// Parse as Guid string.
if (Guid.TryParse(value, out oGuid))
{
return x.Guid == y;
obj = oGuid;
return true;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator ==(Guid x, Squid y)
{
return y == x; // NB: order of arguments
}
obj = Empty;
return false;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator !=(Squid x, Squid y)
{
return !(x == y);
}
/// <summary>
/// TryParse
/// </summary>
/// <param name="value"></param>
/// <param name="obj"></param>
/// <returns></returns>
public static bool TryParse(string value, out Guid obj)
{
// Try a Squid string.
if (TryDecode(value, out obj))
return true;
// Try a Guid string.
if (Guid.TryParse(value, out obj))
return true;
obj = Guid.Empty;
return false;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator !=(Squid x, Guid y)
{
return !(x == y);
}
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator ==(Squid x, Squid y)
{
return x.Guid == y.Guid;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator !=(Guid x, Squid y)
{
return !(x == y);
}
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator ==(Squid x, Guid y)
{
return x.Guid == y;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="oSquid"></param>
/// <returns></returns>
public static implicit operator string(Squid oSquid)
{
return oSquid.Value;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator ==(Guid x, Squid y)
{
return y == x; // NB: order of arguments
}
/// <summary>
/// Operator
/// </summary>
/// <param name="oSquid"></param>
/// <returns></returns>
public static implicit operator Guid(Squid oSquid)
{
return oSquid.Guid;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator !=(Squid x, Squid y)
{
return !(x == y);
}
/// <summary>
/// Operator
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static implicit operator Squid(string value)
{
if (string.IsNullOrEmpty(value))
return Empty;
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator !=(Squid x, Guid y)
{
return !(x == y);
}
return TryParse(value, out Squid oSquid) ? oSquid : Empty;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
public static bool operator !=(Guid x, Squid y)
{
return !(x == y);
}
/// <summary>
/// Operator
/// </summary>
/// <param name="oGuid"></param>
/// <returns></returns>
public static implicit operator Squid(Guid oGuid)
{
return oGuid == Guid.Empty ? Empty : new Squid(oGuid);
}
/// <summary>
/// Operator
/// </summary>
/// <param name="oSquid"></param>
/// <returns></returns>
public static implicit operator string(Squid oSquid)
{
return oSquid.Value;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="oSquid"></param>
/// <returns></returns>
public static implicit operator Guid(Squid oSquid)
{
return oSquid.Guid;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
public static implicit operator Squid(string value)
{
if (string.IsNullOrEmpty(value))
return Empty;
return TryParse(value, out Squid oSquid) ? oSquid : Empty;
}
/// <summary>
/// Operator
/// </summary>
/// <param name="oGuid"></param>
/// <returns></returns>
public static implicit operator Squid(Guid oGuid)
{
return oGuid == Guid.Empty ? Empty : new Squid(oGuid);
}
}

View file

@ -23,104 +23,102 @@ using Wonky.Client.Services;
using Microsoft.AspNetCore.Components;
using Toolbelt.Blazor;
namespace Wonky.Client.HttpInterceptors
namespace Wonky.Client.HttpInterceptors;
public class HttpInterceptorService
{
public class HttpInterceptorService
private readonly HttpClientInterceptor _interceptor;
private readonly NavigationManager _navigation;
private readonly IToastService _toast;
private readonly RefreshTokenService _refreshTokenService;
private readonly ILogger<HttpInterceptorService> _logger;
private readonly ILocalStorageService _storage;
private readonly IAuthenticationService _authenticationService;
public HttpInterceptorService(HttpClientInterceptor interceptor,
NavigationManager navigation, IToastService toast,
RefreshTokenService refreshTokenService, ILogger<HttpInterceptorService> logger,
ILocalStorageService storage, IAuthenticationService authenticationService)
{
private readonly HttpClientInterceptor _interceptor;
private readonly NavigationManager _navigation;
private readonly IToastService _toast;
private readonly RefreshTokenService _refreshTokenService;
private readonly ILogger<HttpInterceptorService> _logger;
private readonly ILocalStorageService _storage;
private readonly IAuthenticationService _authenticationService;
_interceptor = interceptor;
_navigation = navigation;
_toast = toast;
_refreshTokenService = refreshTokenService;
_logger = logger;
_storage = storage;
_authenticationService = authenticationService;
}
public HttpInterceptorService(HttpClientInterceptor interceptor,
NavigationManager navigation, IToastService toast,
RefreshTokenService refreshTokenService, ILogger<HttpInterceptorService> logger,
ILocalStorageService storage, IAuthenticationService authenticationService)
{
_interceptor = interceptor;
_navigation = navigation;
_toast = toast;
_refreshTokenService = refreshTokenService;
_logger = logger;
_storage = storage;
_authenticationService = authenticationService;
}
public void RegisterEvent()
{
_interceptor.AfterSend += AfterSend;
}
public void RegisterEvent()
{
_interceptor.AfterSend += AfterSend;
}
public void RegisterBeforeSendEvent()
{
_interceptor.BeforeSendAsync += InterceptBeforeSend;
}
public void RegisterBeforeSendEvent()
{
_interceptor.BeforeSendAsync += InterceptBeforeSend;
}
public void DisposeEvent()
{
_interceptor.AfterSend -= AfterSend;
_interceptor.BeforeSendAsync -= InterceptBeforeSend;
}
public void DisposeEvent()
{
_interceptor.AfterSend -= AfterSend;
_interceptor.BeforeSendAsync -= InterceptBeforeSend;
}
private async Task InterceptBeforeSend(object sender, HttpClientInterceptorEventArgs e)
private async Task InterceptBeforeSend(object sender, HttpClientInterceptorEventArgs e)
{
var absolutePath = e.Request.RequestUri.AbsolutePath;
if (!absolutePath.Contains("token"))
{
var absolutePath = e.Request.RequestUri.AbsolutePath;
if (!absolutePath.Contains("token"))
// call TryRefreshToken
var token = await _refreshTokenService.TryRefreshToken();
if (!string.IsNullOrEmpty(token))
{
// call TryRefreshToken
var token = await _refreshTokenService.TryRefreshToken();
if (!string.IsNullOrEmpty(token))
{
// set new token
e.Request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
// set new token
e.Request.Headers.Authorization = new AuthenticationHeaderValue("bearer", token);
}
}
}
private void AfterSend (object sender, HttpClientInterceptorEventArgs e)
private void AfterSend (object sender, HttpClientInterceptorEventArgs e)
{
if (e.Response == null || e.Response.IsSuccessStatusCode)
return;
var message = $"En fejl er opstået \n {JsonSerializer.Serialize(e)}";
var currDoc = _navigation.ToBaseRelativePath(_navigation.Uri);
if (currDoc.Contains("login/"))
currDoc = "";
switch (e.Response.StatusCode)
{
if (e.Response == null || e.Response.IsSuccessStatusCode)
return;
var message = $"En fejl er opstået \n {JsonSerializer.Serialize(e)}";
var currDoc = _navigation.ToBaseRelativePath(_navigation.Uri);
if (currDoc.Contains("login/"))
currDoc = "";
switch (e.Response.StatusCode)
{
case HttpStatusCode.NotFound:
_logger.LogDebug("NotFound <= {}", currDoc);
break;
case HttpStatusCode.BadRequest:
_logger.LogDebug("BadRequest <= {}", currDoc);
_logger.LogDebug("{}", message);
break;
case HttpStatusCode.Unauthorized:
_logger.LogDebug("Unauthorized <= {}", currDoc);
_logger.LogDebug("{}", message);
_authenticationService.Logout();
_navigation.NavigateTo($"/login/{currDoc}");
_toast.ShowInfo("Venligst Login. Tak.");
break;
case HttpStatusCode.Conflict:
_logger.LogDebug("Conflict <= {}", currDoc);
_logger.LogDebug("{}", message);
break;
case HttpStatusCode.InternalServerError:
_logger.LogDebug("InternalServerError <= {}", currDoc);
_logger.LogDebug("{}", message);
break;
default:
_logger.LogDebug("{}", message);
break;
}
// throw new HttpResponseException(message);
case HttpStatusCode.NotFound:
_logger.LogDebug("NotFound <= {}", currDoc);
break;
case HttpStatusCode.BadRequest:
_logger.LogDebug("BadRequest <= {}", currDoc);
_logger.LogDebug("{}", message);
break;
case HttpStatusCode.Unauthorized:
_logger.LogDebug("Unauthorized <= {}", currDoc);
_logger.LogDebug("{}", message);
_authenticationService.Logout();
_navigation.NavigateTo($"/login/{currDoc}");
_toast.ShowInfo("Venligst Login. Tak.");
break;
case HttpStatusCode.Conflict:
_logger.LogDebug("Conflict <= {}", currDoc);
_logger.LogDebug("{}", message);
break;
case HttpStatusCode.InternalServerError:
_logger.LogDebug("InternalServerError <= {}", currDoc);
_logger.LogDebug("{}", message);
break;
default:
_logger.LogDebug("{}", message);
break;
}
}
}
// throw new HttpResponseException(message);
}
}

View file

@ -16,26 +16,25 @@
using System.Runtime.Serialization;
namespace Wonky.Client.HttpInterceptors
namespace Wonky.Client.HttpInterceptors;
[Serializable]
public class HttpResponseException : Exception
{
[Serializable]
public class HttpResponseException : Exception
public HttpResponseException()
{
public HttpResponseException()
{
}
public HttpResponseException(string message)
: base(message)
{
}
public HttpResponseException(string message, Exception innerException)
: base(message, innerException)
{
}
public HttpResponseException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}
}
public HttpResponseException(string message)
: base(message)
{
}
public HttpResponseException(string message, Exception innerException)
: base(message, innerException)
{
}
public HttpResponseException(SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
}

View file

@ -30,7 +30,7 @@
<div class="row g-3">
<label for="date" class="col-form-label-sm col-sm-1">Dato</label>
<div class="col-sm-3">
<input id="date" class="form-control" type="text" value="@(DateTime.Parse(ReportItem.CreateTimestamp).ToShortDateString())" readonly/>
<input id="date" class="form-control" type="text" value="@ReportItem.CreateTimestamp" readonly/>
</div>
<label for="account" class="col-form-label-sm col-sm-1">Konto</label>

View file

@ -26,7 +26,7 @@
<div class="row bg-dark text-white rounded-2 mb-2 py-2 align-items-center">
<div class="col">
<WorkDateComponent OnChangedCallback="WorkDateComponentCallback" />
<WorkDateComponent OnChangedCallback="WorkDateComponentCallback"/>
</div>
</div>
@ -34,7 +34,7 @@
{
<div class="alert alert-danger">
<h4>Ring til kontoret. Denne konto er spærret med kode '@Company.Blocked'</h4>
</div>
</div>
}
<div class="row mb-2 bg-dark text-white rounded-3 p-3">
<div class="col">
@ -46,10 +46,9 @@
{
<div class="row">
<div class="col">
<h3>Der kan ikke oprettes besøg når der findes rapport for @SelectedDate.ToShortDateString()</h3>
<h3>Der kan ikke oprettes besøg når der findes rapport for @SelectedDate.ToShortDateString()</h3>
</div>
</div>
}
else
{
@ -81,8 +80,8 @@ else
{
<option value="order">Bestilling</option>
}
@if(DraftProvider.Draft.DraftType == "offer")
@if (DraftProvider.Draft.DraftType == "offer")
{
<option selected value="quote">Tilbud</option>
}
@ -137,7 +136,7 @@ else
<InputText id="phone" class="form-control" @bind-Value="Activity.Phone"/>
<ValidationMessage For="@(() => Activity.Phone)"></ValidationMessage>
</div>
<label for="orderMessage" class="col-sm-2 col-form-label-sm">Note /Kontor</label>
<div class="col-sm-4">
<InputTextArea id="orderMessage" class="form-control" @bind-Value="Activity.OrderMessage"/>
@ -149,15 +148,15 @@ else
<InputTextArea id="crmNote" class="form-control" @bind-Value="Activity.CrmNote"/>
<ValidationMessage For="@(() => Activity.CrmNote)"></ValidationMessage>
</div>
<div class="col-sm-6"></div>
<div class="col-sm-6"></div>
<label for="vatNumber" class="col-sm-2 col-form-label-sm">Cvr/Org nr.</label>
<div class="col-sm-4">
<InputText id="vatNumber" class="form-control" @bind-Value="Activity.VatNumber" />
<ValidationMessage For="@(() => Activity.VatNumber)" />
<InputText id="vatNumber" class="form-control" @bind-Value="Activity.VatNumber"/>
<ValidationMessage For="@(() => Activity.VatNumber)"/>
</div>
</div>
<div class="row g-2 mb-3">
<div class="col-sm-3 d-grid mx-auto">
@*
@ -178,7 +177,7 @@ else
<button class="btn btn-success" disabled="@string.IsNullOrWhiteSpace(Activity.ActivityTypeEnum)" @onclick="ShowInventoryOverlay">Produkter</button>
</div>
</div>
<div id="this-draft" style="@(Activity.ActivityStatusEnum is "order" or "quote" ? "display: block" : "display:none")">
@* Draft lines in draft -----------------------------------------------------*@
<div class="row">
@ -275,7 +274,7 @@ else
</td>
<td class="align-middle" style="min-width:200px;">
<div class="input-group">
<input type="number" class="form-control" @bind-value="@Price"/>
<input type="number" class="form-control" @bind-value="@Price"/>
@*
***************** Price history overlay button *****************************
*@
@ -285,10 +284,10 @@ else
</div>
</td>
<td class="align-middle" style="min-width:100px;">
<input type="number" class="form-control" @bind-value="@Discount"/>
<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"/>
<input type="checkbox" class="form-check" @bind-value="@Sas"/>
</td>
<td class="align-middle">@SelectedItem.Sku</td>
<td class="align-middle">
@ -360,26 +359,27 @@ else
***************** Confirm product check overlay button *****************************
***************** Continue by submitton order to erp *****************************
*@
<button type="button" class="btn btn-warning" @onclick="CallConfirmCheckOverlay" disabled="@(PoFormInvalid || Working)"><i class="bi-cloud-arrow-up"></i> @ButtonText</button>
<button type="button" class="btn btn-warning" @onclick="CallConfirmCheckOverlay" disabled="@(PoFormInvalid || Working)">
<i class="bi-cloud-arrow-up"></i> @ButtonText
</button>
</div>
</div>
}
<ProductCheckConfirmationOverlay BodyMessage="" CompanyId="@CompanyId" Products="CheckList"
OnOkClicked="ConfirmProductCheckCallback" @ref="ConfirmationCheckOverlay" />
<ConfirmWorkDateModal BodyMessage="@PromptDateConfirm"
<ProductCheckConfirmationOverlay BodyMessage="" CompanyId="@CompanyId" Products="CheckList"
OnOkClicked="ConfirmProductCheckCallback" @ref="ConfirmationCheckOverlay"/>
<ConfirmWorkDateModal BodyMessage="@PromptDateConfirm"
OnOkClicked="WorkDateConfirmCallback" @ref="ConfirmWorkDate"/>
<ProductHistoryOverlay CompanyId="@CompanyId" ItemSku="@SelectedItem.Sku" @ref="ProductOverlay"/>
<PriceCatalogOverlay CountryCode="@Company.CountryCode.ToLower()" OnSelected="PriceListCallback" @ref="CatalogOverlay"/>
<PriceCatalogOverlay CountryCode="@Company.CountryCode.ToLower()" OnSelected="PriceListCallback" @ref="CatalogOverlay"/>
<ProductPriceHistoryOverlay CompanyId="@CompanyId" Sku="@SelectedItem.Sku" OnSelected="PriceHistoryCallback" @ref="PriceOverlay"/>
<CustomerInvoiceListOverlay CustomerInvoices="CompanyInvoices" @ref="InvoiceListOverlay" />
<CustomerInvoiceListOverlay CustomerInvoices="CompanyInvoices" @ref="InvoiceListOverlay"/>
<CustomerActivityListOverlay Activities="Activities" CompanyName="@Company.Name" @ref="ActivityListOverlay" />
<CustomerActivityListOverlay Activities="Activities" CompanyName="@Company.Name" @ref="ActivityListOverlay"/>
<CustomerInventoryListOverlay CompanyName="@Company.Name" CompanyId="@CompanyId" CountryCode="@Company.CountryCode"
OnSelected="OnInventoryCallback" Inventory="Inventory" @ref="InventoryListOverlay" />
<CustomerInventoryListOverlay CompanyName="@Company.Name" CompanyId="@CompanyId" CountryCode="@Company.CountryCode"
OnSelected="OnInventoryCallback" Inventory="Inventory" @ref="InventoryListOverlay"/>

View file

@ -1,5 +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
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
@ -30,6 +29,7 @@ using Wonky.Client.Services;
using Wonky.Client.Shared;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages;
@ -49,18 +49,22 @@ public partial class AdvisorActivityCreatePage : IDisposable
[Inject] public IAdvisorActivityRepository ActivityRepo { get; set; }
[Inject] public IAdvisorReportRepository ReportRepo { get; set; }
[Inject] public IAdvisorCustomerHistoryRepository HistoryRepo { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
[Inject] public IUserInfoService UserService { get; set; }
// *************************************************************
// Parameters
[CascadingParameter] private DraftStateProvider DraftProvider { get; set; } = new();
[Parameter] public string CompanyId { get; set; } = "";
// *************************************************************
// Variables
private readonly JsonSerializerOptions _options = new() {PropertyNameCaseInsensitive = true};
private readonly JsonSerializerOptions _options = new() { PropertyNameCaseInsensitive = true };
private SalesItemView SelectedItem { get; set; } = new();
private UserProfile UserProfile { get; set; } = new();
private ActivityDto Activity { get; set; } = new();
private CompanyDto Company = new();
private CompanyDto Company { get; set; } = new();
private EditContext? ActivityContext { get; set; }
private bool PoFormInvalid { get; set; } = true;
private bool ShowItem { get; set; }
@ -69,7 +73,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
private string Discount { get; set; } = "0";
private bool Sas { get; set; }
private bool InvalidActivityType { get; set; } = true;
private bool InvalidActivity { get; set; } = true;
private bool InvalidActivity { get; set; } = true;
private bool ReportClosed { get; set; }
private bool Working { get; set; } = true;
private UserManagerEditView SalesRep { get; set; } = new();
@ -78,7 +82,9 @@ public partial class AdvisorActivityCreatePage : IDisposable
private string PromptDateConfirm { get; set; } = "";
private string ButtonText { get; set; } = "Gem besøg";
private bool OrgWarning { get; set; }
private const string PromptDemoForgotten = "Har du glemt demo?";
// *************************************************************
// Overlays
private PriceCatalogOverlay CatalogOverlay { get; set; } = new();
@ -88,14 +94,16 @@ public partial class AdvisorActivityCreatePage : IDisposable
private ProductCheckConfirmationOverlay ConfirmationCheckOverlay { get; set; } = new();
private CustomerInvoiceListOverlay InvoiceListOverlay { get; set; } = new();
private CustomerInventoryListOverlay InventoryListOverlay { get; set; } = new();
private CustomerActivityListOverlay ActivityListOverlay { get; set; } = new();
// *************************************************************
// Lists
private List<ProductInventoryView> Inventory { get; set; } = new();
private List<ProductInventoryView> CheckList { get; set; } = new();
private InvoiceListView CompanyInvoices { get; set; } = new();
private List<ReportItemView> Activities { get; set; } = new();
/// <summary>
/// Page initialization
/// </summary>
@ -110,13 +118,13 @@ public partial class AdvisorActivityCreatePage : IDisposable
// User Preferences
UserProfile = await ProfileService.GetProfile();
// User Info
SalesRep = await UserInfoService.GetUserInfo();
SalesRep = await UserService.GetUserInfo();
// Fetch Customer from http
Company = await CompanyRepo.GetCompanyById(CompanyId);
if (Company.HasFolded == 1)
// Company has shutdown activities
// Company has shut down
Activity.OrderMessage = "BEMÆRK: CVR nummer er ophørt.";
// variable to validate if customer needs phone number update
OldPhone = Company.Phone;
if (string.IsNullOrWhiteSpace(Company.Phone)
@ -125,7 +133,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
{
Company.Phone = Company.Account[..8];
}
// Populate base activity information
Activity.BcId = Company.BcId;
Activity.ActivityStatusEnum = "noSale";
@ -141,7 +149,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
Activity.Mobile = Company.Mobile;
Activity.Name = Company.Name;
Activity.Address1 = Company.Address1;
Activity.Address2 = Company.Address2;
Activity.Address2 = Company.Address2;
Activity.ZipCode = Company.ZipCode;
Activity.City = Company.City;
Activity.DlvName = Company.Name;
@ -150,7 +158,10 @@ public partial class AdvisorActivityCreatePage : IDisposable
Activity.DlvZipCode = Company.ZipCode;
Activity.DlvCity = Company.City;
// Initialize date variable
SelectedDate = string.IsNullOrWhiteSpace(UserProfile.WorkDate) ? DateTime.Now : DateTime.Parse(UserProfile.WorkDate);
Logger.LogDebug("AdvisorActivityCreatePage => DateTime parser => {}", UserProfile.WorkDate);
SelectedDate = string.IsNullOrWhiteSpace(UserProfile.WorkDate)
? DateTime.Now
: DateTime.Parse(UserProfile.WorkDate);
// raise flag if report is closed
ReportClosed = await ReportRepo.ReportExist($"{SelectedDate:yyyy-MM-dd}");
// Ask for confirmation of date
@ -160,6 +171,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
PromptDateConfirm = $"Aktiviteter oprettes med dato {SelectedDate.ToShortDateString()}. Er dette OK?";
ConfirmWorkDate.Show();
}
// Lines may already have been added from the company inventory page
if (DraftProvider.Draft.DraftType == "order")
{
@ -168,26 +180,27 @@ public partial class AdvisorActivityCreatePage : IDisposable
Activity.ActivityStatusEnum = "order";
PoFormInvalid = false;
}
Working = false;
}
private async Task ShowVisitOverlay()
{
Logger.LogDebug("ShowInventoryOverlay - wait for visits");
ActivityListOverlay.Show();
Activities = await ActivityRepo.GetCustomerActivities(CompanyId);
await Task.Delay(500);
}
private async Task ShowInventoryOverlay()
{
Logger.LogDebug("ShowInventoryOverlay - wait for inventory");
InventoryListOverlay.Show();
Inventory = await HistoryRepo.FetchInventory(CompanyId);
Inventory = Inventory.OrderBy(x => x.Description).ToList();
await Task.Delay(500);
@ -200,31 +213,32 @@ public partial class AdvisorActivityCreatePage : IDisposable
DraftProvider.Draft.Items.Add(item);
StateHasChanged();
}
private async Task ShowInvoiceOverlay()
{
Logger.LogDebug("ShowInvoiceOverlay - wait for invoices");
InvoiceListOverlay.Show();
CompanyInvoices = await FetchCompanyInvoices();
await Task.Delay(500);
}
private async Task<InvoiceListView> FetchCompanyInvoices()
{
// fetch from storage
var storage = await Storage.GetItemAsStringAsync($"{CompanyId}-invoices");
await Task.Delay(500);
var iDate = await Storage.GetItemAsStringAsync($"{CompanyId}-iDate");
// if we have a list and iDate was today return the list
if (!string.IsNullOrWhiteSpace(storage) && (!string.IsNullOrWhiteSpace(iDate) && DateTime.Parse(iDate.Replace("\"", "")) >= DateTime.Now))
if (!string.IsNullOrWhiteSpace(storage) && (!string.IsNullOrWhiteSpace(iDate) &&
DateTime.Parse(iDate.Replace("\"", "")) >= DateTime.Now))
{
Logger.LogDebug("fetching invoices from storage");
Logger.LogDebug("storage contains <= {}", storage);
return JsonSerializer.Deserialize<InvoiceListView>(storage);
}
Logger.LogDebug("pulling invoices from backend");
// pull invoices
var companyInvoices = await HistoryRepo.FetchInvoiceList(CompanyId);
@ -236,30 +250,31 @@ public partial class AdvisorActivityCreatePage : IDisposable
Logger.LogDebug("backend contains <= {}", JsonSerializer.Serialize(companyInvoices));
return companyInvoices;
}
private void ShowOrgWarning()
{
if (OrgWarning)
return;
OrgWarning = true;
if (Company.CountryCode.ToLower() == "se" && VatUtils.SanitizeVatNumber(Activity.VatNumber).Length < 10 && Activity.ActivityStatusEnum == "order")
if (Company.CountryCode.ToLower() == "se" && VatUtils.SanitizeVatNumber(Activity.VatNumber).Length < 10 &&
Activity.ActivityStatusEnum == "order")
{
Toaster.ShowWarning("Org nummer er ufuldstændig. Skal opdateres før bestilling kan sendes. ", "ADVARSEL");
}
}
private async Task CallConfirmCheckOverlay()
{
// check if new account
if (string.IsNullOrWhiteSpace(Company.Account)
|| Company.Account.ToLower() == "ny"
if (string.IsNullOrWhiteSpace(Company.Account)
|| Company.Account.ToLower() == "ny"
|| Activity.ActivityStatusEnum.ToLower() == "quote")
{
// proceed to create activity - as there is no product check to be done
await CreateActivity();
return;
}
// check if product has been checked
// fetch products from storage
var pStorage = await Storage.GetItemAsStringAsync($"{CompanyId}-products");
@ -269,13 +284,15 @@ public partial class AdvisorActivityCreatePage : IDisposable
if (string.IsNullOrWhiteSpace(pDate))
pDate = $"{DateTime.Now.AddDays(-1):yyyy-MM-dd}";
Logger.LogDebug("pDate => {}", pDate);
// check if product data is valid and updated today
if (string.IsNullOrWhiteSpace(pStorage) || pDate.Replace("\"", "") != $"{DateTime.Now:yyyy-MM-dd}")
{
Working = true;
// pop a message
Toaster.ShowError("Produkt gennemgang mangler. Vent mens produkt oversigt indlæses. Gå ikke væk fra siden!", "Produkt check ...");
Toaster.ShowError(
"Produkt gennemgang mangler. Vent mens produkt oversigt indlæses. Gå ikke væk fra siden!",
"Produkt check ...");
// product inventory has not been updated
// send rpc call to sync ERP to CRM
Toaster.ShowInfo("Vent mens data synkroniseres ...", "ERP til CRM ...");
@ -286,11 +303,11 @@ public partial class AdvisorActivityCreatePage : IDisposable
await Storage.SetItemAsync($"{CompanyId}-pDate", ts);
// request products from backend
Toaster.ShowInfo("Vent mens produkt oversigt hentes", "CRM produkt liste");
CheckList = await HistoryRepo.FetchInventory(CompanyId);
if(CheckList.Any())
if (CheckList.Any())
CheckList = CheckList.OrderBy(x => x.Description).ToList();
await Storage.SetItemAsync($"{CompanyId}-products", CheckList);
Working = false;
}
@ -298,14 +315,14 @@ public partial class AdvisorActivityCreatePage : IDisposable
{
// deserialize storage data
CheckList = JsonSerializer.Deserialize<List<ProductInventoryView>>(pStorage);
if(CheckList.Any())
if (CheckList.Any())
CheckList = CheckList.OrderBy(x => x.Description).ToList();
}
// Show CheckList modal
ConfirmationCheckOverlay.Show();
}
private async Task ConfirmProductCheckCallback()
{
ConfirmationCheckOverlay.Hide();
@ -314,10 +331,10 @@ public partial class AdvisorActivityCreatePage : IDisposable
{
item.Check = false;
}
await Storage.SetItemAsync($"{CompanyId}-products", CheckList);
}
private async Task WorkDateConfirmCallback()
{
await ProfileService.SetDateConfirmed(true);
@ -332,12 +349,12 @@ public partial class AdvisorActivityCreatePage : IDisposable
SelectedDate = DateTime.Parse(workDate);
Activity.ActivityDate = workDate;
}
private void ShowPriceListOverlay()
{
CatalogOverlay.Show();
}
private async Task PriceListCallback(SelectedSku sku)
{
// get selected item
@ -349,13 +366,13 @@ public partial class AdvisorActivityCreatePage : IDisposable
Quantity = sku.Quantity;
StateHasChanged();
}
private void ShowPriceHistoryOverlay()
{
if(ShowItem)
if (ShowItem)
PriceOverlay.Show();
}
private void PriceHistoryCallback(decimal price)
{
if (price == 0)
@ -376,6 +393,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
Toaster.ShowError("Kunde adresse er ufuldstændig.");
return;
}
// validate org number
// - this is a required input
// - must validate according to country rules.
@ -384,6 +402,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
Toaster.ShowError("Firma registreringsnummer er ikke korrekt.");
return;
}
// validate input according to status
switch (Activity.ActivityStatusEnum)
{
@ -400,6 +419,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
Toaster.ShowError("Ved tilbud skal en gyldig email adresse angives.");
return;
}
// raise working flag
Working = true;
@ -412,11 +432,12 @@ public partial class AdvisorActivityCreatePage : IDisposable
Activity.OrderMessage = $"Telefonnr. opdateret.\n{Activity.OrderMessage}";
await CompanyRepo.UpdateErpData(Company.CompanyId, Company);
}
// begin assembling activity
Activity.ActivityDate = $"{SelectedDate:yyyy-MM-dd}";
Activity.OurRef = Activity.ActivityTypeEnum switch
{
"phone" => $"T:{SalesRep.FirstName}",
"phone" => $"T:{SalesRep.FirstName}",
"onSite" => $"B:{SalesRep.FirstName}",
_ => ""
};
@ -443,6 +464,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
.ToList();
Activity.Lines = lines;
}
// debug logging
Logger.LogDebug("CrmNewActivityPage => \n {}", JsonSerializer.Serialize(Activity));
// post to api
@ -458,18 +480,19 @@ public partial class AdvisorActivityCreatePage : IDisposable
Navigator.NavigateTo($"/advisor/customers");
return;
}
// lower working flag
Working = false;
// show error message
Toaster.ShowError(result.Message, "ORDRE FEJL");
}
private async Task DeleteDraft()
{
await DraftProvider.DeleteDraftAsync();
Activity.ActivityStatusEnum = "noSale";
}
private async Task AddItem(SalesItemView salesItem)
{
ShowItem = false;
@ -489,12 +512,12 @@ public partial class AdvisorActivityCreatePage : IDisposable
Discount = "0";
// add it to the cart
DraftProvider.Draft.Items.Add(item);
if(Activity.ActivityStatusEnum != "quote")
if (Activity.ActivityStatusEnum != "quote")
Activity.ActivityStatusEnum = "order";
// save the item using the CartStateProvider's save method
await DraftProvider.SaveChangesAsync();
}
private async Task RemoveItem(DraftItem item)
{
// remove item
@ -504,10 +527,11 @@ public partial class AdvisorActivityCreatePage : IDisposable
if (!DraftProvider.Draft.Items.Any())
Activity.ActivityStatusEnum = "noSale";
}
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
Logger.LogDebug("ActivityNewPage => HandleFieldChanged => ActivityStatusEnum <= '{}'", Activity.ActivityStatusEnum);
Logger.LogDebug("ActivityNewPage => HandleFieldChanged => ActivityStatusEnum <= '{}'",
Activity.ActivityStatusEnum);
DraftProvider.Draft.DraftType = Activity.ActivityStatusEnum;
if (Activity.ActivityStatusEnum == "noSale")
{
@ -523,19 +547,20 @@ public partial class AdvisorActivityCreatePage : IDisposable
};
// InvalidCanvas = InvalidActivityType;
InvalidActivity = InvalidActivityType
|| PoFormInvalid
|| DraftProvider.Draft.Items.Count == 0
|| (Activity.ActivityStatusEnum == "offer" && string.IsNullOrWhiteSpace(Activity.Email));
if (Activity.YourRef.Length > 35 || Activity.ReferenceNumber.Length > 20 || InvalidActivity)
InvalidActivity = InvalidActivityType
|| PoFormInvalid
|| DraftProvider.Draft.Items.Count == 0
|| (Activity.ActivityStatusEnum == "offer" && string.IsNullOrWhiteSpace(Activity.Email));
if (Activity.YourRef.Length > 35 || Activity.ReferenceNumber.Length > 20 || InvalidActivity)
{
PoFormInvalid = true;
return;
}
PoFormInvalid = !ActivityContext.Validate();
StateHasChanged();
}
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e)
{
if (string.IsNullOrEmpty(Activity.ActivityTypeEnum) && !ReportClosed)
@ -544,16 +569,16 @@ public partial class AdvisorActivityCreatePage : IDisposable
PoFormInvalid = true;
return;
}
if (Activity.ActivityStatusEnum.ToLower() is "order" or "quote"
&& Company.CountryCode.ToLower() == "se"
if (Activity.ActivityStatusEnum.ToLower() is "order" or "quote"
&& Company.CountryCode.ToLower() == "se"
&& VatUtils.SanitizeVatNumber(Activity.VatNumber).Length < 10)
{
ShowOrgWarning();
PoFormInvalid = true;
return;
}
PoFormInvalid = false;
ActivityContext.OnFieldChanged -= HandleFieldChanged;
ActivityContext.OnValidationStateChanged -= ValidationChanged;
@ -561,7 +586,7 @@ public partial class AdvisorActivityCreatePage : IDisposable
ActivityContext.OnFieldChanged += HandleFieldChanged;
ActivityContext.OnValidationStateChanged += ValidationChanged;
}
public void Dispose()
{
Interceptor.DisposeEvent();

View file

@ -45,4 +45,4 @@
@if (Working)
{
<WorkingThreeDots/>
}
}

View file

@ -1,4 +1,3 @@
// 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
@ -14,6 +13,7 @@
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Blazored.Toast.Services;
using Microsoft.AspNetCore.Components;
@ -21,6 +21,7 @@ using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Client.Services;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages;
@ -34,7 +35,7 @@ public partial class AdvisorActivityTodayListPage : IDisposable
[Inject] public IAdvisorActivityRepository ActivityRepo { get; set; }
[Inject] public IAdvisorReportRepository ReportRepo { get; set; }
[Inject] public IToastService Toaster { get; set; }
private ReportStatusView ReportStatusView { get; set; } = new();
private ReportStatusView ReportStatusView { get; set; } = new();
private UserProfile UserProfile { get; set; } = new();
private DateTime SelectedDate { get; set; }
private bool ReportExist { get; set; }
@ -45,12 +46,14 @@ public partial class AdvisorActivityTodayListPage : IDisposable
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
UserProfile = await ProfileService.GetProfile();
SelectedDate = string.IsNullOrWhiteSpace(UserProfile.WorkDate) ? DateTime.Now : DateTime.Parse(UserProfile.WorkDate);
SelectedDate = string.IsNullOrWhiteSpace(UserProfile.WorkDate)
? DateTime.Now
: DateTime.Parse(UserProfile.WorkDate);
ReportExist = await ReportRepo.ReportExist($"{SelectedDate:yyyy-MM-dd}");
await GetActivities($"{SelectedDate:yyyy-MM-dd}");
Working = false;
}
private async Task GetActivities(string workDate)
{
Working = true;
@ -67,6 +70,5 @@ public partial class AdvisorActivityTodayListPage : IDisposable
public void Dispose()
{
Interceptor.DisposeEvent();
}
}
}

View file

@ -23,7 +23,6 @@
@page "/advisor/customers/{CompanyId}/quotes/{OrderId}"
<PageTitle>@ReportItem.Company.Name @ReportItem.OrderDate</PageTitle>
@* <ReportItemComponent ReportItem="@_item" /> *@
<table class="table table-sm table-striped d-print-table">
<thead>
@ -124,7 +123,9 @@
{
<tr>
<td colspan="4"></td>
<td colspan="2"><h5 class="fw-bold text-center"><i class="bi-lightning-charge the-fast" style="font-size: 2rem;"></i> HASTER</h5></td>
<td colspan="2">
<h5 class="fw-bold text-center"><i class="bi-lightning-charge the-fast" style="font-size: 2rem;"></i> HASTER</h5>
</td>
</tr>
}
</tbody>
@ -145,7 +146,7 @@
<button class="btn btn-primary btn-lg" type="button" @onclick="UpdateOfficeNote" disabled="@Disabled">Opdater Note /Kontor</button>
</div>
</EditForm>
</div>
</div>
}
else
{
@ -159,4 +160,4 @@ else
@if (Working)
{
<WorkingThreeDots/>
}
}

View file

@ -1,4 +1,3 @@
// 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
@ -22,6 +21,7 @@ using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages;
@ -41,6 +41,7 @@ public partial class AdvisorActivityViewEditPage : IDisposable
private bool Disabled { get; set; }
private int GraceTime { get; set; } = 60;
private bool Working { get; set; } = true;
protected override async Task OnInitializedAsync()
{
@ -59,7 +60,7 @@ public partial class AdvisorActivityViewEditPage : IDisposable
{
StateHasChanged();
}
private async Task UpdateOfficeNote()
{
Working = true;
@ -76,7 +77,7 @@ public partial class AdvisorActivityViewEditPage : IDisposable
return false;
return DateTime.UtcNow < createTs.AddMinutes(GraceTime);
}
public void Dispose()
{
Interceptor.DisposeEvent();

View file

@ -35,10 +35,10 @@
<a class="btn btn-primary d-block" href="/advisor/customers/@Company.CompanyId/activities/new"><i class="bi-arrow-right"></i> Besøg</a>
</div>
</div>
<CustomerActivityListComponent Activities="ActivityList" />
<CustomerActivityListComponent Activities="ActivityList"/>
}
@if (Working)
{
<WorkingThreeDots/>
}
}

View file

@ -1,4 +1,3 @@
// 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
@ -20,6 +19,7 @@ using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages;
@ -33,12 +33,12 @@ public partial class AdvisorCustomerActivityListPage : IDisposable
private List<ReportItemView> ActivityList { get; set; } = new();
private CompanyDto Company { get; set; } = new();
private bool Working { get; set; } = true;
protected override async Task OnInitializedAsync()
{
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
Company = await CompanyRepo.GetCompanyById(CompanyId);
await GetActivities();
Working = false;
@ -48,11 +48,11 @@ public partial class AdvisorCustomerActivityListPage : IDisposable
{
Working = true;
ActivityList = await AdvisorActivityRepo.GetCustomerActivities(CompanyId);
if(ActivityList.Any())
if (ActivityList.Any())
ActivityList = ActivityList.OrderByDescending(x => x.OrderDate).ToList();
Working = false;
}
public void Dispose()
{
Interceptor.DisposeEvent();

View file

@ -14,7 +14,6 @@
//
using System.Text.Json;
using System.Xml;
using Blazored.LocalStorage;
using Blazored.Toast.Services;
using Microsoft.AspNetCore.Components;
@ -27,162 +26,161 @@ using Wonky.Client.Services;
using Wonky.Client.Shared;
using Wonky.Entity.DTO;
using Wonky.Entity.Models;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages
namespace Wonky.Client.Pages;
public partial class AdvisorCustomerCreatePage : IDisposable
{
public partial class AdvisorCustomerCreatePage : IDisposable
[Inject] public IToastService Toaster { get; set; }
[Inject] public ILogger<AdvisorCustomerCreatePage> Logger { get; set; }
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public VatInfoLookupService VatService { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
private EditContext CompanyContext { get; set; }
private CompanyDto Company { get; set; } = new();
private VatAddress CompanyVatAddress { get; set; } = new();
private VatLookupDkModal VatLookupPopup { get; set; } = new();
private bool FormInvalid { get; set; } = true;
private string RegState { get; set; } = "";
private DateTime LastVisit { get; set; }
private DateTime NextVisit { get; set; }
private bool Dk { get; set; } = true;
private bool Working { get; set; }
protected override async Task OnInitializedAsync()
{
[Inject] public IToastService Toaster { get; set; }
[Inject] public ILogger<AdvisorCustomerCreatePage> Logger { get; set; }
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public VatInfoLookupService VatService { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
private EditContext CompanyContext { get; set; }
private CompanyDto Company { get; set; } = new();
private VatAddress CompanyVatAddress { get; set; } = new();
private VatLookupDkModal VatLookupPopup { get; set; } = new();
CompanyContext = new EditContext(Company);
private bool FormInvalid { get; set; } = true;
private string RegState { get; set; } = "";
private DateTime LastVisit { get; set; }
private DateTime NextVisit { get; set; }
private bool Dk { get; set; } = true;
private bool Working { get; set; }
CompanyContext.OnFieldChanged += HandleFieldChanged;
CompanyContext.OnValidationStateChanged += ValidationChanged;
protected override async Task OnInitializedAsync()
var xu = await UserInfoService.GetUserInfo();
Dk = xu.CountryCode.ToLower() == "dk";
Company.SalesRepId = xu.UserId;
Company.CountryCode = xu.CountryCode.ToLower();
LastVisit = DateTime.Now;
NextVisit = DateTime.Now.AddDays(Company.Interval * 7);
Company.LastVisit = $"{LastVisit:yyyy-MM-dd}";
Company.NextVisit = $"{NextVisit:yyyy-MM-dd}";
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
}
/// <summary>
/// Show Vat Lookup modal
/// </summary>
private void CallVatLookupModal()
{
VatLookupPopup.Show();
}
/// <summary>
/// Modal callback to update company properties
/// </summary>
/// <param name="regInfo"></param>
private void SelectCompanyCallback(VirkRegInfo regInfo)
{
Logger.LogDebug("CrmCompanyView => SelectCompanyCallback => {}", JsonSerializer.Serialize(regInfo));
// this can be removed in favor of the new data returned from updating the VatNumber
RegState = regInfo.States[0].State.ToLower() == "normal" ? "the-good" : "the-dead";
if (regInfo.SyncAll)
{
CompanyContext = new EditContext(Company);
CompanyContext.OnFieldChanged += HandleFieldChanged;
CompanyContext.OnValidationStateChanged += ValidationChanged;
var xu = await UserInfoService.GetUserInfo();
Dk = xu.CountryCode.ToLower() == "dk";
Company.SalesRepId = xu.UserId;
Company.CountryCode = xu.CountryCode.ToLower();
LastVisit = DateTime.Now;
NextVisit = DateTime.Now.AddDays(Company.Interval * 7);
Company.LastVisit = $"{LastVisit:yyyy-MM-dd}";
Company.NextVisit = $"{NextVisit:yyyy-MM-dd}";
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
Company.Name = regInfo.Name;
Company.Address1 = regInfo.Address;
Company.Address2 = regInfo.CoName;
Company.ZipCode = regInfo.ZipCode;
Company.City = regInfo.City;
}
/// <summary>
/// Show Vat Lookup modal
/// </summary>
private void CallVatLookupModal()
Company.VatNumber = regInfo.VatNumber;
Company.ValidVat = 1;
FormInvalid = false;
}
private async Task SubmitCompanyForm()
{
Working = true;
FormInvalid = true;
Company.LastVisit = $"{LastVisit:yyyy-MM-dd}";
Company.NextVisit = $"{NextVisit:yyyy-MM-dd}";
var newId = await CompanyRepo.CreateCompany(Company);
if (!string.IsNullOrWhiteSpace(newId))
{
VatLookupPopup.Show();
Toaster.ShowSuccess($"'{Company.Name}' er oprettet i CRM.");
Navigator.NavigateTo($"/advisor/customers/{newId}");
}
/// <summary>
/// Modal callback to update company properties
/// </summary>
/// <param name="regInfo"></param>
private void SelectCompanyCallback(VirkRegInfo regInfo)
else
{
Logger.LogDebug("CrmCompanyView => SelectCompanyCallback => {}", JsonSerializer.Serialize(regInfo));
// this can be removed in favor of the new data returned from updating the VatNumber
RegState = regInfo.States[0].State.ToLower() == "normal" ? "the-good" : "the-dead";
if (regInfo.SyncAll)
{
Company.Name = regInfo.Name;
Company.Address1 = regInfo.Address;
Company.Address2 = regInfo.CoName;
Company.ZipCode = regInfo.ZipCode;
Company.City = regInfo.City;
}
Company.VatNumber = regInfo.VatNumber;
Company.ValidVat = 1;
Toaster.ShowWarning($"'{Company.Name}' IKKE oprettet.");
FormInvalid = false;
}
private async Task SubmitCompanyForm()
Working = false;
}
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
NextVisit = LastVisit.AddDays(7 * Company.Interval);
// invalid vat number id not accepted by the ERP system
// but is removed without warning
// it is necessary to validate if vat number has been added
// as the format should conform to country rule of generation
if (!string.IsNullOrWhiteSpace(Company.VatNumber))
{
Working = true;
FormInvalid = true;
Company.LastVisit = $"{LastVisit:yyyy-MM-dd}";
Company.NextVisit = $"{NextVisit:yyyy-MM-dd}";
var newId = await CompanyRepo.CreateCompany(Company);
if (!string.IsNullOrWhiteSpace(newId))
// validate vat number according to country
if (!VatUtils.ValidateFormat(Company.CountryCode, Company.VatNumber))
{
Toaster.ShowSuccess($"'{Company.Name}' er oprettet i CRM.");
Navigator.NavigateTo($"/advisor/customers/{newId}");
}
else
{
Toaster.ShowWarning($"'{Company.Name}' IKKE oprettet.");
FormInvalid = false;
}
Working = false;
}
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
NextVisit = LastVisit.AddDays(7 * Company.Interval);
// invalid vat number id not accepted by the ERP system
// but is removed without warning
// it is necessary to validate if vat number has been added
// as the format should conform to country rule of generation
if (!string.IsNullOrWhiteSpace(Company.VatNumber))
{
// validate vat number according to country
if (!VatUtils.ValidateFormat(Company.CountryCode, Company.VatNumber))
{
Toaster.ShowError("Momsnummber er ikke korrekt.");
FormInvalid = true;
Company.ValidVat = 0;
RegState = "the-ugly";
}
}
if (!Company.ValidDateSpan())
{
Toaster.ShowError("Dato for næste besøg skal ligge efter sidste besøg.");
Toaster.ShowError("Momsnummber er ikke korrekt.");
FormInvalid = true;
Company.ValidVat = 0;
RegState = "the-ugly";
}
else
{
FormInvalid = !CompanyContext.Validate();
}
StateHasChanged();
}
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e)
if (!Company.ValidDateSpan())
{
Toaster.ShowError("Dato for næste besøg skal ligge efter sidste besøg.");
FormInvalid = true;
CompanyContext.OnFieldChanged -= HandleFieldChanged;
CompanyContext = new EditContext(Company);
FormInvalid = !CompanyContext.Validate();
CompanyContext.OnFieldChanged += HandleFieldChanged;
CompanyContext.OnValidationStateChanged -= ValidationChanged;
}
public void Dispose()
else
{
Interceptor.DisposeEvent();
CompanyContext.OnFieldChanged -= HandleFieldChanged;
CompanyContext.OnValidationStateChanged -= ValidationChanged;
FormInvalid = !CompanyContext.Validate();
}
StateHasChanged();
}
private void ValidationChanged(object sender, ValidationStateChangedEventArgs e)
{
FormInvalid = true;
CompanyContext.OnFieldChanged -= HandleFieldChanged;
CompanyContext = new EditContext(Company);
FormInvalid = !CompanyContext.Validate();
CompanyContext.OnFieldChanged += HandleFieldChanged;
CompanyContext.OnValidationStateChanged -= ValidationChanged;
}
public void Dispose()
{
Interceptor.DisposeEvent();
CompanyContext.OnFieldChanged -= HandleFieldChanged;
CompanyContext.OnValidationStateChanged -= ValidationChanged;
}
}

View file

@ -23,40 +23,42 @@
<div class="sticky-top bg-dark text-light rounded-2 px-3">
<div class="row g-3">
<div class="col-sm-2">
<CustomerSearchColumnComponent OnChanged="SetSearchCol" />
<CustomerSearchColumnComponent OnChanged="SetSearchCol"/>
</div>
<div class="col-sm-6">
<CustomerSearchPhraseComponent OnChanged="SetSearchPhrase" />
<CustomerSearchPhraseComponent OnChanged="SetSearchPhrase"/>
</div>
<div class="col-sm-2">
<CustomerSortComponent OnChanged="SetSortCol" />
<CustomerSortComponent OnChanged="SetSortCol"/>
</div>
<div class="col-sm-2">
<PageSizeComponent OnChanged="SetPageSize" />
<PageSizeComponent OnChanged="SetPageSize"/>
</div>
<div class="col-sm-3">
<button type button class="btn btn-warning @(ShowFolded ? "active" : "")"
data-bs-toggle="button" aria-pressed="@ShowFolded" @onclick="OnFoldedClick">
@ButtonFoldedText
</button>
<button type button class="btn btn-warning @(@ShowHidden ? "active" : "")"
data-bs-toggle="button" aria-pressed="@ShowHidden" @onclick="OnHiddenClick">
@ButtonHiddenText
<div class="col-sm-2 mx-auto">
<button type button class="btn btn-warning @(ShowFolded ? "active" : "")"
data-bs-toggle="button" aria-pressed="@ShowFolded" @onclick="ToggleFolded">
@ToggleFoldedText
</button>
</div>
<div class="col-sm-7">
<div class="col-sm-2 mx-auto">
<button type button class="btn btn-warning @(@ShowHidden ? "active" : "")"
data-bs-toggle="button" aria-pressed="@ShowHidden" @onclick="ToggleHidden">
@ToggleHiddenText
</button>
</div>
<div class="col-sm-5">
<PaginationComponent MetaData="PageData" Spread="2" SelectedPage="SelectedPage"/>
</div>
<div class="col-sm-2 text-end">
<div class="col-sm-1">@* placeholder *@</div>
<div class="col-sm-2 mx-auto">
<a class="btn btn-success text-nowrap" href="/advisor/customers/new">Opret kunde <i class="bi-plus"></i></a>
</div>
</div>
</div>
<AdvisorCustomerListComponent CompanyList="CompanyList" OnDelete="DeleteCompany" />
<AdvisorCustomerListComponent CompanyList="CompanyList" OnDelete="DeleteCompany"/>
@if (Working)
{
<WorkingThreeDots />
}
<WorkingThreeDots/>
}

View file

@ -1,4 +1,3 @@
// 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
@ -22,147 +21,147 @@ using Wonky.Client.Services;
using Wonky.Entity.DTO;
using Wonky.Entity.Requests;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages
namespace Wonky.Client.Pages;
public partial class AdvisorCustomerListPage : IDisposable
{
public partial class AdvisorCustomerListPage : IDisposable
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
private List<CompanyDto> CompanyList { get; set; } = new();
private UserProfile Profile { get; set; } = new();
private UserManagerEditView UserInfo { get; set; } = new();
private string SavedSearch { get; set; } = "";
private bool Working { get; set; } = true;
private MetaData PageData { get; set; } = new();
private CustomerPaging Paging { get; set; } = new();
private string ToggleFoldedText { get; set; } = "Vis Lukkede";
private bool ShowFolded { get; set; }
private string ToggleHiddenText { get; set; } = "Inkl. Skjulte";
private bool ShowHidden { get; set; }
protected override void OnParametersSet()
{
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] public IAdvisorCustomerRepository CompanyRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
}
protected override async Task OnInitializedAsync()
{
// set preferences
Profile = await ProfileService.GetProfile();
UserInfo = await UserInfoService.GetUserInfo();
Paging.OrderBy = Profile.CompanySort;
Paging.SearchColumn = Profile.CompanySearch;
Paging.PageSize = Convert.ToInt32(Profile.PageSize);
Paging.HasFolded = ShowFolded ? 1 : 0;
// load saved search
SavedSearch = string.IsNullOrWhiteSpace(Profile.CompanyFilterPhrase) ? "" : Profile.CompanyFilterPhrase;
Paging.SearchTerm = SavedSearch;
private List<CompanyDto> CompanyList { get; set; } = new();
private UserProfile Profiles { get; set; } = new();
private UserManagerEditView XUserInfo { get; set; } = new();
private string SavedSearch { get; set; } = "";
private bool Working { get; set; } = true;
private MetaData PageData { get; set; } = new();
private CustomerPaging Paging { get; set; } = new();
private string ButtonFoldedText { get; set; } = "Vis Ophørte";
private bool ShowFolded { get; set; }
private string ButtonHiddenText { get; set; } = "Vis Skjulte";
private bool ShowHidden { get; set; }
protected override void OnParametersSet()
// get companies
await FetchCustomers();
Working = false;
}
private async Task ToggleFolded()
{
Working = true;
ShowFolded = !ShowFolded;
ToggleFoldedText = ShowFolded ? "Normal Visning" : "Vis Lukkede";
CompanyList = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.HasFolded = ShowFolded ? 1 : 0;
await FetchCustomers();
}
private async Task ToggleHidden()
{
Working = true;
ShowHidden = !ShowHidden;
ToggleHiddenText = ShowHidden ? "Normal Visning" : "Inkl. Skjulte";
CompanyList = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.IsHidden = ShowHidden ? 1 : 0;
await FetchCustomers();
}
private async Task SelectedPage(int page)
{
CompanyList = new List<CompanyDto>();
Paging.PageNumber = page;
await FetchCustomers();
}
private async Task SetSearchCol(string searchColumn)
{
CompanyList = new List<CompanyDto>();
Paging.SearchColumn = searchColumn;
Paging.PageNumber = 1;
await FetchCustomers();
}
private async Task SetPageSize(string pageSize)
{
CompanyList = new List<CompanyDto>();
Paging.PageSize = Convert.ToInt32(pageSize);
Paging.PageNumber = 1;
await FetchCustomers();
}
private async Task SetSearchPhrase(string searchTerm)
{
CompanyList = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.SearchTerm = searchTerm;
await FetchCustomers();
}
private async Task SetSortCol(string orderBy)
{
CompanyList = new List<CompanyDto>();
Paging.OrderBy = orderBy;
await FetchCustomers();
}
/// <summary>
/// Removes a company from CRM
/// </summary>
/// <param name="companyId"></param>
private async Task DeleteCompany(string companyId)
{
CompanyList = new List<CompanyDto>();
await CompanyRepo.DeleteCompany(companyId);
if (Paging.PageNumber > 1 && CompanyList.Count == 1)
Paging.PageNumber--;
await FetchCustomers();
}
private async Task FetchCustomers()
{
Working = true;
var pageRes = await CompanyRepo.GetCompanies(Paging);
Working = false;
if (pageRes.Items.Any())
{
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
CompanyList = pageRes.Items;
PageData = pageRes.MetaData;
}
protected override async Task OnInitializedAsync()
{
// set preferences
Profiles = await ProfileService.GetProfile();
XUserInfo = await UserInfoService.GetUserInfo();
Paging.OrderBy = Profiles.CompanySort;
Paging.SearchColumn = Profiles.CompanySearch;
Paging.PageSize = Convert.ToInt32(Profiles.PageSize);
Paging.HasFolded = ShowFolded ? 1 : 0;
// load saved search
SavedSearch = string.IsNullOrWhiteSpace(Profiles.CompanyFilterPhrase) ? "" : Profiles.CompanyFilterPhrase;
Paging.SearchTerm = SavedSearch;
// get companies
await FetchCustomers();
Working = false;
}
private async Task OnFoldedClick()
{
Working = true;
ShowFolded = !ShowFolded;
ButtonFoldedText = ShowFolded ? "Vis Aktive" : "Vis Ophørte";
CompanyList = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.HasFolded = ShowFolded ? 1 : 0;
await FetchCustomers();
}
private async Task OnHiddenClick()
{
Working = true;
ShowHidden = !ShowHidden;
ButtonHiddenText = ShowHidden ? "Vis Normale" : "Vis skjulte";
CompanyList = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.IsHidden = ShowHidden ? 1 : 0;
await FetchCustomers();
}
private async Task SelectedPage(int page)
else
{
CompanyList = new List<CompanyDto>();
Paging.PageNumber = page;
await FetchCustomers();
}
private async Task SetSearchCol(string searchColumn)
{
CompanyList = new List<CompanyDto>();
Paging.SearchColumn = searchColumn;
Paging.PageNumber = 1;
await FetchCustomers();
}
private async Task SetPageSize(string pageSize)
{
CompanyList = new List<CompanyDto>();
Paging.PageSize = Convert.ToInt32(pageSize);
Paging.PageNumber = 1;
await FetchCustomers();
PageData = new MetaData();
}
}
private async Task SetSearchPhrase(string searchTerm)
{
CompanyList = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.SearchTerm = searchTerm;
await FetchCustomers();
}
private async Task SetSortCol(string orderBy)
{
CompanyList = new List<CompanyDto>();
Paging.OrderBy = orderBy;
await FetchCustomers();
}
/// <summary>
/// Removes a company from CRM
/// </summary>
/// <param name="companyId"></param>
private async Task DeleteCompany(string companyId)
{
CompanyList = new List<CompanyDto>();
await CompanyRepo.DeleteCompany(companyId);
if (Paging.PageNumber > 1 && CompanyList.Count == 1)
Paging.PageNumber--;
await FetchCustomers();
}
private async Task FetchCustomers()
{
Working = true;
var pageRes = await CompanyRepo.GetCompanies(Paging);
Working = false;
if (pageRes.Items.Any())
{
CompanyList = pageRes.Items;
PageData = pageRes.MetaData;
}
else
{
CompanyList = new List<CompanyDto>();
PageData = new MetaData();
}
}
public void Dispose() => Interceptor.DisposeEvent();
}
}
public void Dispose() => Interceptor.DisposeEvent();
}

View file

@ -36,85 +36,86 @@
// erp context
<EditForm EditContext="ErpContext">
<DataAnnotationsValidator/>
<div class="row g-3">
<div class="row g-1">
@* Company Name *@
<label for="name" class="col-sm-1 col-form-label-sm">Navn</label>
<div class="col-sm-5">
<InputText id="name" class="form-control" @bind-Value="Company.Name"/>
<InputText id="name" class="form-control" @bind-Value="Company.Name" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Name)"></ValidationMessage>
</div>
@* Company Attention *@
<label for="attention" class="col-sm-1 col-form-label-sm">Att.</label>
<div class="col-sm-5">
<InputText id="attention" class="form-control" @bind-Value="Company.Attention"/>
<InputText id="attention" class="form-control" @bind-Value="Company.Attention" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Attention)"></ValidationMessage>
</div>
@* Address 1 *@
<label for="address1" class="col-sm-1 col-form-label-sm">Adresse</label>
<div class="col-sm-5">
<InputText id="address1" class="form-control" @bind-Value="Company.Address1"/>
<InputText id="address1" class="form-control" @bind-Value="Company.Address1" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Address1)"></ValidationMessage>
</div>
@* Address 2 *@
<label for="address2" class="col-sm-1 col-form-label-sm">Adresse</label>
<div class="col-sm-5">
<InputText id="address2" class="form-control" @bind-Value="Company.Address2"/>
<InputText id="address2" class="form-control" @bind-Value="Company.Address2" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Address2)"></ValidationMessage>
</div>
@* Post Code *@
<label for="zipCode" class="col-sm-1 col-form-label-sm">PostNr</label>
<div class="col-sm-2">
<InputText id="zipCode" class="form-control" @bind-Value="Company.ZipCode"/>
<InputText id="zipCode" class="form-control" @bind-Value="Company.ZipCode" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.ZipCode)"></ValidationMessage>
</div>
@* City Name *@
<label for="city" class="col-sm-1 col-form-label-sm">Bynavn</label>
<div class="col-sm-8">
<InputText id="city" class="form-control" @bind-Value="Company.City"/>
<InputText id="city" class="form-control" @bind-Value="Company.City" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.City)"></ValidationMessage>
</div>
@* Phone *@
<label for="phone" class="col-sm-1 col-form-label-sm">Telefon</label>
<div class="col-sm-3">
<InputText id="phone" class="form-control" @bind-Value="Company.Phone"/>
<div class="col-sm-2">
<InputText id="phone" class="form-control" @bind-Value="Company.Phone" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Phone)"></ValidationMessage>
</div>
@* Mobile *@
<label for="mobile" class="col-sm-1 col-form-label-sm">Mobil</label>
<div class="col-sm-3">
<InputText id="mobile" class="form-control" @bind-Value="Company.Mobile"/>
<div class="col-sm-2">
<InputText id="mobile" class="form-control" @bind-Value="Company.Mobile" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Mobile)"></ValidationMessage>
</div>
<div class="col-sm-4 d-grid mx-auto">
<button type="button" class="btn btn-danger" @onclick="ToggleVisibility">@ToggleButtonText</button>
</div>
@* Email *@
<label for="email" class="col-sm-1 col-form-label-sm">Epost</label>
<div class="col-sm-5">
<InputText id="email" class="form-control" @bind-Value="Company.Email"/>
<InputText id="email" class="form-control" @bind-Value="Company.Email" readonly="@(ErpEditDisabled)"/>
<ValidationMessage For="@(() => Company.Email)"></ValidationMessage>
</div>
<div class="col-sm-3 d-grid mx-auto">
<button type="button" class="btn btn-primary d-block" disabled="@(Company.HasFolded == 0 || Company.Name == "ERROR")" onclick="@ForceActivity">Aktiver besøg</button>
<div class="col-sm-4">@* ---- placeholder --- *@</div>
<div class="col-sm-2 d-grid mx-auto">
<button type="button" class="btn btn-edit" @onclick="ToggleErpEdit"><i class="bi-pencil"></i> STAM data</button>
</div>
<div class="col-sm-3 d-grid mx-auto">
<button type="button" class="btn btn-danger d-block" onclick="@UpdateErpData" disabled="@(Working || Company.Name == "ERROR")"><i class="bi-cloud-arrow-up"></i> STAM data </button>
<button type="button" class="btn btn-primary d-block" disabled="@(Company.HasFolded == 0 || Company.Name == "ERROR")" @onclick="ForceActivity">Aktiver besøg</button>
</div>
<div class="col-sm-3 d-grid mx-auto">
<button type="button" class="btn btn-danger d-block" onclick="@UpdateErpData" disabled="@(Working || Company.Name == "ERROR" || ErpEditDisabled)"><i class="bi-cloud-arrow-up"></i> STAM data </button>
</div>
</div>
<hr class="mb-3"/>
<div class="row">
@* vat number*@
<label for="vatNumber" class="col-sm-2 col-form-label-sm">CVR/Org nr.</label>
<div class="col-sm-4">
<label for="vatNumber" class="col-sm-1 col-form-label-sm">CVR/Org nr.</label>
<div class="col-sm-3">
<div class="input-group">
<span class="input-group-text">
<DisplayStateComponent StateClass="@VatState"/>
</span>
<InputText id="vatNumber" class="form-control" @bind-Value="Company.VatNumber"/>
<InputText id="vatNumber" class="form-control" @bind-Value="Company.VatNumber" readonly="@(VatEditDisabled)"/>
<ValidationMessage For="@(() => Company.VatNumber)"></ValidationMessage>
</div>
</div>
<div class="col-sm-2 d-grid mx-auto">
<button type="button" class="btn btn-edit" @onclick="ToggleVatEdit"><i class="bi-pencil"></i> Moms/Org Nr.</button>
</div>
@* vat lookup *@
<div class="col-sm-3 d-grid mx-auto">
@switch (CountryCode)
@ -132,7 +133,7 @@
</div>
@* save vat number *@
<div class="col-sm-3 d-grid mx-auto">
<button type="button" class="btn btn-warning d-block" @onclick="UpdateVatNumber"><i class="bi-cloud-arrow-up"></i> Moms/Org Nr.</button>
<button type="button" class="btn btn-warning d-block" @onclick="UpdateVatNumber" disabled="@(VatEditDisabled)"><i class="bi-cloud-arrow-up"></i> Moms/Org Nr.</button>
</div>
</div>
@ -240,6 +241,11 @@
<InputTextArea id="crmNotes" class="form-control" @bind-Value="Company.CrmNotes"/>
</div>
</div>
<div class="row pt-3">
<div class="col-sm-3 d-grid">
<button type="button" class="btn btn-danger" @onclick="ToggleVisibility">@ToggleButtonText</button>
</div>
</div>
</EditForm>
}

View file

@ -1,4 +1,3 @@
// 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
@ -28,7 +27,7 @@ using Wonky.Client.Services;
using Wonky.Client.Shared;
using Wonky.Entity.DTO;
using Wonky.Entity.Models;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages;
@ -46,8 +45,8 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
[Inject] public VatInfoLookupService VatService { get; set; }
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
private readonly JsonSerializerOptions _options = new () { PropertyNameCaseInsensitive = true };
private readonly JsonSerializerOptions _options = new() { PropertyNameCaseInsensitive = true };
private CompanyDto Company { get; set; } = new();
private EditContext ErpContext { get; set; }
private DateTime LastVisit { get; set; }
@ -63,6 +62,8 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
private string ActionLink { get; set; } = "";
private bool Working { get; set; } = true;
private bool CountryIsDk { get; set; } = true;
private bool ErpEditDisabled { get; set; } = true;
private bool VatEditDisabled { get; set; } = true;
private List<ContactDto> Contacts { get; set; } = new();
private VatLookupDkModal VatLookupPopup { get; set; } = new();
private ContactDto SelectedContact { get; set; } = new();
@ -70,7 +71,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
private ContactModal ContactPopup { get; set; } = new();
private UserManagerEditView UserInfo { get; set; } = new();
private string ToggleButtonText { get; set; } = "";
protected override async Task OnInitializedAsync()
{
// setup interceptor
@ -78,25 +79,26 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Interceptor.RegisterBeforeSendEvent();
// initialize default contact
DefaultContact = new ContactDto { CompanyId = CompanyId, ContactId = "", FirstName = ""};
DefaultContact = new ContactDto { CompanyId = CompanyId, ContactId = "", FirstName = "" };
// setup form context
ErpContext = new EditContext(Company);
// assign event handlers to context
ErpContext.OnFieldChanged += HandleFieldChanged;
ErpContext.OnValidationStateChanged += ValidationChanged;
// fetch user info from local storage
UserInfo = await UserInfoService.GetUserInfo();
CountryCode = UserInfo.CountryCode.ToLower();
CountryIsDk = CountryCode == "dk";
Logger.LogDebug("companyId => {}", CompanyId);
Company = await CustomerRepo.GetCompanyById(CompanyId);
Logger.LogDebug("company => {}", JsonSerializer.Serialize(Company));
ToggleButtonText = Company.IsHidden == 0 ? "Skjul kunde" : "Vis kunde";
// toggle view button text
ToggleButtonText = Company.IsHidden == 0 ? "Udelad kunde i oversigt" : "Brug Normal Visning";
CurrentVat = Company.VatNumber;
Company.CountryCode = UserInfo.CountryCode.ToLower();
// internal flag
@ -121,7 +123,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
// action link passed to activity button component
ActionLink = $"/advisor/customers/{CompanyId}/activities/new"; // used when drawing visit button
// handle company out of business case
if(Company.HasFolded == 1)
if (Company.HasFolded == 1)
{
// this is only used if user has selected to show closed companies
HasFolded = true;
@ -135,36 +137,49 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
// valid vat flag
ValidVat = Company.ValidVat == 1; // true/false flag set if company has a valid vatNumber
// vat state css class
VatState = Company.ValidVat == 1 ? "the-good" : "no-vat"; // assign css class
VatState = Company.ValidVat == 1 ? "the-good" : "no-vat"; // assign css class
}
// create search address from address
if (CountryIsDk)
CompanyVatAddress = PrepareVatAddress(Company);
await FetchContacts(CompanyId);
// remove loading image
Working = false;
await Task.Delay(100);
await RequestErpUpdate();
}
private void ToggleErpEdit()
{
ErpEditDisabled = !ErpEditDisabled;
}
private void ToggleVatEdit()
{
VatEditDisabled = !VatEditDisabled;
}
private async Task ToggleVisibility()
{
Company.IsHidden = Company.IsHidden == 0 ? 1 : 0;
ToggleButtonText = Company.IsHidden == 0 ? "Skjul kunde" : "Vis kunde";
// toggle view button text
ToggleButtonText = Company.IsHidden == 0 ? "Udelad kunde i oversigt" : "Brug Normal Visning";
Logger.LogDebug("ToggleVisibility => Company.IsHidden == {}", Company.IsHidden);
await CustomerRepo.UpdateCrmData(CompanyId, Company);
}
private async Task RequestErpUpdate()
{
if(Working)
if (Working)
return;
Working = true;
Company.HistorySync = await HistoryRepo.InvoiceErpToCrmRpc(CompanyId, Company.HistorySync);
Working = false;
}
/// <summary>
/// Fetch contacts from backend
/// </summary>
@ -173,7 +188,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
{
// load contacts
Contacts = await AdvisorContactRepo.GetContacts(companyId);
if(Contacts.Any() && Contacts.Count > 1)
if (Contacts.Any() && Contacts.Count > 1)
Contacts = Contacts.OrderBy(x => x.FirstName).ToList();
}
@ -183,8 +198,8 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
private void OpenVatLookupModal()
{
VatLookupPopup.Show();
}
}
/// <summary>
/// Callback to update company properties
/// </summary>
@ -206,9 +221,10 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Company.ZipCode = regInfo.ZipCode;
Company.City = regInfo.City;
}
Company.VatNumber = regInfo.VatNumber;
}
/// <summary>
/// Open contact edit popup
/// </summary>
@ -245,15 +261,16 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
// contact modified
Logger.LogDebug("update => {}", jsonContact);
// send put request to backend
await AdvisorContactRepo.UpdateContact(contact);
await AdvisorContactRepo.UpdateContact(contact);
}
// reset selected contact
SelectedContact = new ContactDto();
// reload contacts from backend
await FetchContacts(CompanyId);
Working = false;
}
/// <summary>
/// Delete contact callback
/// </summary>
@ -270,7 +287,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
await FetchContacts(CompanyId);
Working = false;
}
/// <summary>
/// Update CRM data
/// </summary>
@ -290,6 +307,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Company = result;
StateHasChanged();
}
Working = false;
Toaster.ClearAll();
}
@ -302,6 +320,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
{
if (Working)
return;
ErpEditDisabled = true;
Working = true;
Toaster.ShowInfo("Vent venligst ...", "OPDATERER STAM DATA");
var result = await CustomerRepo.UpdateErpData(CompanyId, Company);
@ -310,6 +329,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Company = result;
StateHasChanged();
}
Working = false;
Toaster.ClearAll();
}
@ -326,8 +346,10 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Toaster.ShowError($"Moms Nummer ugyldigt");
return;
}
if (Working)
return;
VatEditDisabled = true;
Working = true;
Toaster.ShowInfo("Vent venligst ...", "OPDATERER MOMS NUMMER");
var result = await CustomerRepo.UpdateCompanyVat(CompanyId, Company.VatNumber);
@ -336,10 +358,11 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
Company = result;
StateHasChanged();
}
Toaster.ClearAll();
Working = false;
}
/// <summary>
/// Prepare vat address from company model
/// </summary>
@ -359,6 +382,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
HouseNumber = Regex.Replace(model.Address1[pos1..], "[^0-9]", "").Trim()
};
}
// process address2
var pos2 = model.Address2.IndexOfAny(digits);
if (pos2 > 0)
@ -370,10 +394,11 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
HouseNumber = Regex.Replace(model.Address2[pos2..], "[^0-9]", "").Trim()
};
}
// return empty model
return new VatAddress();
}
/// <summary>
/// Change activity enabled state
/// </summary>
@ -381,7 +406,7 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
{
EnableActivity = EnableActivity == 0 ? 1 : 0;
}
/// <summary>
/// Context field change event callback
/// </summary>
@ -398,9 +423,10 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
ValidVat = true;
EnableActivity = 1;
}
StateHasChanged();
}
/// <summary>
/// Context validation change event callback
/// </summary>
@ -410,13 +436,13 @@ public partial class AdvisorCustomerViewEditPage : IDisposable
{
ErpContext.OnFieldChanged -= HandleFieldChanged;
ErpContext.OnValidationStateChanged -= ValidationChanged!;
ErpContext = new EditContext(Company);
ErpContext.OnFieldChanged += HandleFieldChanged;
ErpContext.OnValidationStateChanged += ValidationChanged;
}
/// <summary>
/// Dispose
/// </summary>

View file

@ -16,13 +16,12 @@
using Microsoft.AspNetCore.Components;
namespace Wonky.Client.Pages
{
public partial class ErrorReportPage
{
[Parameter]
public int ErrorCode { get; set; }
namespace Wonky.Client.Pages;
[Parameter] public string ErrorDescription { get; set; } = "";
}
}
public partial class ErrorReportPage
{
[Parameter]
public int ErrorCode { get; set; }
[Parameter] public string ErrorDescription { get; set; } = "";
}

View file

@ -24,123 +24,120 @@ using Wonky.Entity.Requests;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages
namespace Wonky.Client.Pages;
public partial class OfficeCountryCustomerListPage : IDisposable
{
public partial class OfficeCountryCustomerListPage : IDisposable
[Parameter] public string CountryCode { get; set; } = "";
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] public ICountryCustomerRepository CustomerRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
private List<CompanyDto> Companies { get; set; } = new();
private UserProfile Profiles { get; set; } = new();
private UserManagerEditView XUserInfo { get; set; } = new();
private string SavedSearch { get; set; } = "";
private bool ShowFolded { get; set; }
private bool Working { get; set; } = true;
private MetaData PageData { get; set; } = new();
private CustomerPaging Paging { get; set; } = new();
private string ButtonFoldedText { get; set; } = "Vis Ophørte";
protected override async Task OnParametersSetAsync()
{
[Parameter] public string CountryCode { get; set; } = "";
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserProfileService ProfileService { get; set; }
[Inject] public ICountryCustomerRepository CustomerRepo { get; set; }
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
private List<CompanyDto> Companies { get; set; } = new();
private UserProfile Profiles { get; set; } = new();
private UserManagerEditView XUserInfo { get; set; } = new();
private string SavedSearch { get; set; } = "";
private bool ShowFolded { get; set; }
private bool Working { get; set; } = true;
private MetaData PageData { get; set; } = new();
private CustomerPaging Paging { get; set; } = new();
private string ButtonFoldedText { get; set; } = "Vis Ophørte";
protected override async Task OnParametersSetAsync()
{
Working = true;
Working = true;
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
// set preferences
Profiles = await ProfileService.GetProfile();
XUserInfo = await UserInfoService.GetUserInfo();
Paging.OrderBy = Profiles.CompanySort;
Paging.SearchColumn = Profiles.CompanySearch;
Paging.PageSize = Convert.ToInt32(Profiles.PageSize);
Paging.HasFolded = ShowFolded ? 1 : 0;
// set preferences
Profiles = await ProfileService.GetProfile();
XUserInfo = await UserInfoService.GetUserInfo();
Paging.OrderBy = Profiles.CompanySort;
Paging.SearchColumn = Profiles.CompanySearch;
Paging.PageSize = Convert.ToInt32(Profiles.PageSize);
Paging.HasFolded = ShowFolded ? 1 : 0;
// load saved search
SavedSearch = string.IsNullOrWhiteSpace(Profiles.CompanyFilterPhrase) ? "" : Profiles.CompanyFilterPhrase;
Paging.SearchTerm = SavedSearch;
// load saved search
SavedSearch = string.IsNullOrWhiteSpace(Profiles.CompanyFilterPhrase) ? "" : Profiles.CompanyFilterPhrase;
Paging.SearchTerm = SavedSearch;
// get companies
await FetchCustomers();
}
// get companies
await FetchCustomers();
}
private async Task OnFoldedClick()
{
Working = true;
ShowFolded = !ShowFolded;
ButtonFoldedText = ShowFolded ? "Vis Aktive" : "Vis Ophørte";
Companies = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.HasFolded = ShowFolded ? 1 : 0;
await FetchCustomers();
}
private async Task OnFoldedClick()
{
Working = true;
ShowFolded = !ShowFolded;
ButtonFoldedText = ShowFolded ? "Vis Aktive" : "Vis Ophørte";
Companies = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.HasFolded = ShowFolded ? 1 : 0;
await FetchCustomers();
}
private async Task SelectedPage(int page)
{
Working = true;
Companies = new List<CompanyDto>();
Paging.PageNumber = page;
await FetchCustomers();
}
private async Task SelectedPage(int page)
{
Working = true;
Companies = new List<CompanyDto>();
Paging.PageNumber = page;
await FetchCustomers();
}
private async Task SetSearchCol(string searchColumn)
{
Working = true;
Companies = new List<CompanyDto>();
Paging.SearchColumn = searchColumn;
Paging.PageNumber = 1;
await FetchCustomers();
}
private async Task SetPageSize(string pageSize)
{
Working = true;
Companies = new List<CompanyDto>();
Paging.PageSize = Convert.ToInt32(pageSize);
Paging.PageNumber = 1;
await FetchCustomers();
}
private async Task SetSearchCol(string searchColumn)
{
Working = true;
Companies = new List<CompanyDto>();
Paging.SearchColumn = searchColumn;
Paging.PageNumber = 1;
await FetchCustomers();
}
private async Task SetPageSize(string pageSize)
{
Working = true;
Companies = new List<CompanyDto>();
Paging.PageSize = Convert.ToInt32(pageSize);
Paging.PageNumber = 1;
await FetchCustomers();
}
private async Task SetSearchPhrase(string searchTerm)
{
Working = true;
Companies = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.SearchTerm = searchTerm;
await FetchCustomers();
}
private async Task SetSearchPhrase(string searchTerm)
{
Working = true;
Companies = new List<CompanyDto>();
Paging.PageNumber = 1;
Paging.SearchTerm = searchTerm;
await FetchCustomers();
}
private async Task SetSortCol(string orderBy)
private async Task SetSortCol(string orderBy)
{
Working = true;
Companies = new List<CompanyDto>();
Paging.OrderBy = orderBy;
await FetchCustomers();
}
private async Task FetchCustomers()
{
Working = true;
var response = await CustomerRepo.GetCompaniesPaged(CountryCode, Paging);
Working = false;
if (response.Items.Any())
{
Companies = response.Items;
PageData = response.MetaData;
}
else
{
Working = true;
Companies = new List<CompanyDto>();
Paging.OrderBy = orderBy;
await FetchCustomers();
PageData = new MetaData();
}
}
private async Task FetchCustomers()
{
Working = true;
var response = await CustomerRepo.GetCompaniesPaged(CountryCode, Paging);
Working = false;
if (response.Items.Any())
{
Companies = response.Items;
PageData = response.MetaData;
}
else
{
Companies = new List<CompanyDto>();
PageData = new MetaData();
}
}
public void Dispose() => Interceptor.DisposeEvent();
}
}
public void Dispose() => Interceptor.DisposeEvent();
}

View file

@ -24,123 +24,121 @@ using Wonky.Entity.Configuration;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
namespace Wonky.Client.Services
namespace Wonky.Client.Services;
public class AuthenticationService : IAuthenticationService
{
public class AuthenticationService : IAuthenticationService
private readonly JsonSerializerOptions _options = new() { PropertyNameCaseInsensitive = true };
private readonly HttpClient _client;
private readonly AuthenticationStateProvider _authStateProvider;
private readonly IOptions<ApiConfig> _apiConfig;
private readonly ILogger<AuthenticationService> _logger;
private readonly IUserInfoService _infoService;
private readonly UserProfileService _profile;
private readonly ILocalStorageService _localStorage;
public AuthenticationService(
HttpClient client,
AuthenticationStateProvider authStateProvider,
IOptions<ApiConfig> apiConfig,
ILogger<AuthenticationService> logger,
IUserInfoService infoService,
UserProfileService profile,
ILocalStorageService localStorage
)
{
private readonly JsonSerializerOptions _options = new() { PropertyNameCaseInsensitive = true };
private readonly HttpClient _client;
private readonly AuthenticationStateProvider _authStateProvider;
private readonly IOptions<ApiConfig> _apiConfig;
private readonly ILogger<AuthenticationService> _logger;
private readonly IUserInfoService _infoService;
private readonly UserProfileService _profile;
private readonly ILocalStorageService _localStorage;
public AuthenticationService(
HttpClient client,
AuthenticationStateProvider authStateProvider,
IOptions<ApiConfig> apiConfig,
ILogger<AuthenticationService> logger,
IUserInfoService infoService,
UserProfileService profile,
ILocalStorageService localStorage
)
{
_client = client;
_authStateProvider = authStateProvider;
_apiConfig = apiConfig;
_logger = logger;
_infoService = infoService;
_profile = profile;
_localStorage = localStorage;
}
_client = client;
_authStateProvider = authStateProvider;
_apiConfig = apiConfig;
_logger = logger;
_infoService = infoService;
_profile = profile;
_localStorage = localStorage;
}
public async Task<AuthResponseView> Login(CredentialDto credentials)
public async Task<AuthResponseView> Login(CredentialDto credentials)
{
var credForm = new Dictionary<string, string>
{
var credForm = new Dictionary<string, string>
["grant_type"] = "password",
["username"] = credentials.Email,
["password"] = credentials.Password
};
var response = await _client
.PostAsync(_apiConfig.Value.ServicesAuth, new FormUrlEncodedContent(credForm));
var resContent = await response.Content.ReadAsStringAsync();
if (!response.IsSuccessStatusCode)
return new AuthResponseView
{
["grant_type"] = "password",
["username"] = credentials.Email,
["password"] = credentials.Password
IsSuccess = false, ErrorMessage = $"Kontroller indtastning"
};
var response = await _client
.PostAsync(_apiConfig.Value.ServicesAuth, new FormUrlEncodedContent(credForm));
// process response content
var data = JsonSerializer.Deserialize<AuthResponseView>(resContent, _options);
await _infoService.SetAccessToken(data.AccessToken);
await _infoService.SetRefreshToken(data.RefreshToken);
await _infoService.SetExpiration((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds + data.ExpiresIn - 60);
var resContent = await response.Content.ReadAsStringAsync();
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", data.AccessToken);
if (!response.IsSuccessStatusCode)
return new AuthResponseView
{
IsSuccess = false, ErrorMessage = $"Kontroller indtastning"
};
var userInfo = await UserInfo();
// process response content
var data = JsonSerializer.Deserialize<AuthResponseView>(resContent, _options);
await _infoService.SetUserInfo(userInfo);
await _infoService.SetAccessToken(data.AccessToken);
await _infoService.SetRefreshToken(data.RefreshToken);
await _infoService.SetExpiration((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds + data.ExpiresIn - 60);
// notify system on state change
((AuthStateProvider)_authStateProvider).NotifyUserAuthenticationAsync(data.AccessToken);
data.IsSuccess = true;
return data;
}
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", data.AccessToken);
var userInfo = await UserInfo();
await _infoService.SetUserInfo(userInfo);
// notify system on state change
((AuthStateProvider)_authStateProvider).NotifyUserAuthenticationAsync(data.AccessToken);
data.IsSuccess = true;
return data;
}
public async Task<string> RefreshToken()
public async Task<string> RefreshToken()
{
var refreshToken = await _infoService.GetRefreshToken();
var credentials = new Dictionary<string, string>
{
var refreshToken = await _infoService.GetRefreshToken();
var credentials = new Dictionary<string, string>
{
["grant_type"] = "refresh_token",
["refresh_token"] = refreshToken
};
var response = await _client.PostAsync(_apiConfig.Value.ServicesAuth, new FormUrlEncodedContent(credentials));
if (!response.IsSuccessStatusCode)
return string.Empty;
["grant_type"] = "refresh_token",
["refresh_token"] = refreshToken
};
var response = await _client.PostAsync(_apiConfig.Value.ServicesAuth, new FormUrlEncodedContent(credentials));
if (!response.IsSuccessStatusCode)
return string.Empty;
var resContent = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<AuthResponseView>(resContent, _options);
var resContent = await response.Content.ReadAsStringAsync();
var data = JsonSerializer.Deserialize<AuthResponseView>(resContent, _options);
if (string.IsNullOrWhiteSpace(data.AccessToken))
return string.Empty;
if (string.IsNullOrWhiteSpace(data.AccessToken))
return string.Empty;
// set default request headers using access_token
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", data.AccessToken);
await _infoService.SetAccessToken(data.AccessToken);
await _infoService.SetRefreshToken(data.RefreshToken);
await _infoService.SetExpiration((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds + data.ExpiresIn - 60);
return data.AccessToken;
}
// set default request headers using access_token
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", data.AccessToken);
await _infoService.SetAccessToken(data.AccessToken);
await _infoService.SetRefreshToken(data.RefreshToken);
await _infoService.SetExpiration((int)DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1)).TotalSeconds + data.ExpiresIn - 60);
return data.AccessToken;
}
public async Task Logout()
{
var profileBackup = await _profile.GetProfile();
Task.Delay(150);
await _localStorage.ClearAsync();
Task.Delay(150);
await _profile.SetProfile(profileBackup);
_client.DefaultRequestHeaders.Authorization = null;
((AuthStateProvider)_authStateProvider).NotifyUserLogout();
}
public async Task<UserManagerEditView> UserInfo(bool write = false)
{
var response = await _client.GetAsync(_apiConfig.Value.UserInfo).ConfigureAwait(true);
var content = await response.Content.ReadAsStringAsync();
var userInfo = JsonSerializer.Deserialize<UserManagerEditView>(content, _options);
if(write)
await _infoService.SetUserInfo(userInfo);
return userInfo ?? new UserManagerEditView();
}
}
}
public async Task Logout()
{
var profileBackup = await _profile.GetProfile();
Task.Delay(150);
await _localStorage.ClearAsync();
Task.Delay(150);
await _profile.SetProfile(profileBackup);
_client.DefaultRequestHeaders.Authorization = null;
((AuthStateProvider)_authStateProvider).NotifyUserLogout();
}
public async Task<UserManagerEditView> UserInfo(bool write = false)
{
var response = await _client.GetAsync(_apiConfig.Value.UserInfo).ConfigureAwait(true);
var content = await response.Content.ReadAsStringAsync();
var userInfo = JsonSerializer.Deserialize<UserManagerEditView>(content, _options);
if(write)
await _infoService.SetUserInfo(userInfo);
return userInfo ?? new UserManagerEditView();
}
}

View file

@ -17,14 +17,12 @@
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
namespace Wonky.Client.Services
{
public interface IAuthenticationService
{
Task<AuthResponseView> Login(CredentialDto credentials);
Task Logout();
Task<string> RefreshToken();
Task<UserManagerEditView> UserInfo(bool write = false);
}
}
namespace Wonky.Client.Services;
public interface IAuthenticationService
{
Task<AuthResponseView> Login(CredentialDto credentials);
Task Logout();
Task<string> RefreshToken();
Task<UserManagerEditView> UserInfo(bool write = false);
}

View file

@ -22,86 +22,84 @@ using Wonky.Client.Services;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
namespace Wonky.Client.Shared
namespace Wonky.Client.Shared;
public class AuthStateProvider : AuthenticationStateProvider
{
public class AuthStateProvider : AuthenticationStateProvider
private readonly HttpClient _client;
// private readonly ILocalStorageService _storage;
private readonly AuthenticationState _anonymous;
private readonly IUserInfoService _infoService;
public AuthStateProvider(HttpClient client, IUserInfoService infoService)
{
private readonly HttpClient _client;
// private readonly ILocalStorageService _storage;
private readonly AuthenticationState _anonymous;
private readonly IUserInfoService _infoService;
public AuthStateProvider(HttpClient client, IUserInfoService infoService)
{
_client = client;
_infoService = infoService;
_anonymous = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await _infoService.GetAccessToken();
if (string.IsNullOrEmpty(token))
return _anonymous;
var userInfo = await _infoService.GetUserInfo();
if (userInfo == null)
return _anonymous;
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var exp = await _infoService.GetExpiration();
var claims = new List<Claim>
{
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
new(ClaimTypes.Email, userInfo.Email),
new(ClaimTypes.Country, userInfo.CountryCode),
new(ClaimTypes.MobilePhone, userInfo.PhoneNumber),
new(ClaimTypes.Expiration, exp.ToString())
};
claims.AddRange(
from role in userInfo.AssignedRoles
where role.Assigned select new Claim(ClaimTypes.Role, role.Name));
// return the authState for the user
return new AuthenticationState(
new ClaimsPrincipal(
new ClaimsIdentity(claims, "token")));
}
public async void NotifyUserAuthenticationAsync(string token)
{
if (string.IsNullOrEmpty(token))
return;
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var userInfo = await _infoService.GetUserInfo();
var exp = await _infoService.GetExpiration();
var claims = new List<Claim>
{
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
new(ClaimTypes.Email, userInfo.Email),
new(ClaimTypes.Country, userInfo.CountryCode),
new(ClaimTypes.MobilePhone, userInfo.PhoneNumber),
new(ClaimTypes.Expiration, exp.ToString())
};
claims.AddRange(
from role in userInfo.AssignedRoles
where role.Assigned select new Claim(ClaimTypes.Role, role.Name));
var authState = Task.FromResult(
new AuthenticationState(
new ClaimsPrincipal(
new ClaimsIdentity(claims, "token"))));
NotifyAuthenticationStateChanged(authState);
}
public void NotifyUserLogout()
{
var authState = Task.FromResult(_anonymous);
NotifyAuthenticationStateChanged(authState);
}
_client = client;
_infoService = infoService;
_anonymous = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await _infoService.GetAccessToken();
if (string.IsNullOrEmpty(token))
return _anonymous;
var userInfo = await _infoService.GetUserInfo();
if (userInfo == null)
return _anonymous;
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var exp = await _infoService.GetExpiration();
var claims = new List<Claim>
{
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
new(ClaimTypes.Email, userInfo.Email),
new(ClaimTypes.Country, userInfo.CountryCode),
new(ClaimTypes.MobilePhone, userInfo.PhoneNumber),
new(ClaimTypes.Expiration, exp.ToString())
};
claims.AddRange(
from role in userInfo.AssignedRoles
where role.Assigned select new Claim(ClaimTypes.Role, role.Name));
// return the authState for the user
return new AuthenticationState(
new ClaimsPrincipal(
new ClaimsIdentity(claims, "token")));
}
public async void NotifyUserAuthenticationAsync(string token)
{
if (string.IsNullOrEmpty(token))
return;
_client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", token);
var userInfo = await _infoService.GetUserInfo();
var exp = await _infoService.GetExpiration();
var claims = new List<Claim>
{
new(ClaimTypes.Name, $"{userInfo.FirstName} {userInfo.LastName}"),
new(ClaimTypes.Email, userInfo.Email),
new(ClaimTypes.Country, userInfo.CountryCode),
new(ClaimTypes.MobilePhone, userInfo.PhoneNumber),
new(ClaimTypes.Expiration, exp.ToString())
};
claims.AddRange(
from role in userInfo.AssignedRoles
where role.Assigned select new Claim(ClaimTypes.Role, role.Name));
var authState = Task.FromResult(
new AuthenticationState(
new ClaimsPrincipal(
new ClaimsIdentity(claims, "token"))));
NotifyAuthenticationStateChanged(authState);
}
public void NotifyUserLogout()
{
var authState = Task.FromResult(_anonymous);
NotifyAuthenticationStateChanged(authState);
}
}

View file

@ -1,7 +1,7 @@
{
"appInfo": {
"name": "Wonky Online",
"version": "0.117.1",
"version": "0.118.0",
"rc": true,
"sandBox": false,
"image": "grumpy-coder.png"
@ -19,7 +19,7 @@
}
},
"apiConfig": {
"baseUrl": "https://dev.innotec.dk",
"baseUrl": "https://zeta.innotec.dk",
"catalog": "api/v2/catalog/country",
"crmCustomers": "api/v2/crm/companies",
"crmInventoryExt": "history/inventory",