WIP: supervisor

This commit is contained in:
Frede Hundewadt 2023-03-25 15:59:34 +01:00
parent 65556383fd
commit 01e4cc60fd
19 changed files with 937 additions and 7 deletions

View file

@ -0,0 +1,74 @@
@* Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@using Wonky.Client.Helpers
@if (ActivityList.Any())
{
<table class="table table-sm table-bordered d-print-table table-striped">
<thead>
<tr class="bg-dark text-white opacity-75 border-bottom">
<th scope="col">Kunde</th>
<th scope="col">Bynavn</th>
<th scope="col">Demo</th>
<th scope="col">Salg</th>
<th scope="col">Note</th>
<th class="text-end" scope="col">sas</th>
<th class="text-end" scope="col">Beløb</th>
<th class="text-center" scope="col"><i class="bi-phone"></i></th>
<th class="text-center" scope="col"><i class="bi-lightning"></i></th>
<th class="text-center" scope="col"><i class="bi-calculator"></i></th>
<th class="text-center" scope="col"><i class="bi-truck"></i></th>
</tr>
</thead>
<tbody>
@foreach (var activity in ActivityList)
{
<tr class="border-bottom">
<td class="align-middle"><a href="/office/customers/@activity.Company.CompanyId/orders/@activity.ActivityId">@activity.Company.Name</a></td>
<td class="align-middle">@activity.Company.City</td>
<td class="align-middle">@activity.Demo</td>
<td class="align-middle">@activity.Sales</td>
<td class="align-middle fw-bold">@activity.OfficeNote</td>
<td class="align-middle text-end">@($"{activity.SasAmount:N2}")</td>
<td class="align-middle text-end">@(activity.StatusTypeEnum == "Quote" ? $"{0:N2}" : $"{activity.OrderAmount:N2}")</td>
<td class="align-middle text-center">
@if (activity.OurRef.Contains("T:"))
{<i style="font-size:1.3em;" class="bi-phone"></i>}
</td>
<td class="align-middle text-center">
@if (activity.Express)
{<i style="font-size:1.3em;" class="bi-lightning"></i>}
</td>
<td class="align-middle text-end">
@if (activity.StatusTypeEnum == "Quote")
{<i style="font-size:1.3em;" class="bi-calculator"></i>}
</td>
<td class="align-middle state">
@if (activity.Lines.Any() && activity.StatusTypeEnum == "Order")
{
<ProcessStateComponent StateClass="@Utils.GetProcessStatus(activity.ProcessStatusEnum)"/>
}
</td>
</tr>
}
</tbody>
</table>
}
else
{
<div>Ingen data</div>
}

View file

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

View file

@ -24,7 +24,7 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Pages;
public partial class OfficeUserAdvisorListPage :IDisposable
public partial class OfficeAdvisorListPage :IDisposable
{
// #############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }

View file

@ -23,7 +23,7 @@ using Wonky.Entity.Views;
namespace Wonky.Client.Pages;
public partial class OfficeUserAdvisorReportListPage : IDisposable
public partial class OfficeAdvisorReportListPage : IDisposable
{
// #############################################################
[Inject] public ICountryReportRepository ReportRepo { get; set; }

View file

@ -0,0 +1,71 @@
@* Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@using Wonky.Client.Components
@using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Models
@attribute [Authorize(Roles = "Admin,Office,Supervisor")]
@page "/office/users/advisors/{CountryCode}/{UserId}/reports/{ReportDate}"
<div class="row pt-2 pb-2 mb-3 rounded-2 d-print-none bg-dark text-white">
<div class="col-sm-6">
<WorkDateComponent OnChangedCallback="FetchUserReport"/>
</div>
<div class="col-sm-2 d-grid">
<button class="btn btn-warning" @onclick="() => Print(PrintTarget.FrontPage)"><i class="bi-printer"></i> Forside</button>
</div>
<div class="col-sm-2 d-grid">
<button class="btn btn-success" @onclick="() => Print(PrintTarget.OrderPage)"><i class="bi-printer"></i> Ordrer</button>
</div>
<div class="col-sm-2 d-grid">
<button class="btn btn-primary" type="button" @onclick="Print"><i class="bi-printer"></i> Rapport</button>
</div>
</div>
<div class="report-main d-print-print">
@if (!string.IsNullOrWhiteSpace(Report.ReportData.DayTypeEnum))
{
<PageTitle>@Report.ReportData.Name</PageTitle>
<div class="row">
<div class="alert border border-1 border-dark text-center align-content-center">
<h3>@Report.ReportData.Name</h3>
</div>
</div>
<div class="row">
<div class="w-75">
<ReportSummaryComponent ReportData="Report.ReportData"/>
</div>
<div class="w-25">
<ReportDistanceLedgerComponent ReportData="Report.ReportData"/>
</div>
</div>
<OfficeReportActivityListComponent ActivityList="Report.ReportItems"/>
<ReportActivityLedgerComponent ReportData="Report.ReportData"/>
}
else
{
<div class="row">
<div class="col">Ingen rapport data</div>
</div>
}
</div>
@if (Activities.Any())
{
foreach (var item in Activities.Where(item => item.StatusTypeEnum.ToLower() == "order" && item.ProcessStatusEnum.ToLower() == "none"))
{
<ReportItemComponent ReportItem="@item" />
}
}

View file

@ -0,0 +1,181 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System.Text.Json;
using Blazored.LocalStorage;
using Blazored.Toast.Services;
using Microsoft.AspNetCore.Components;
using Microsoft.JSInterop;
using Wonky.Client.Helpers;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Client.Local.Services;
using Wonky.Client.Models;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages;
public partial class OfficeAdvisorReportViewPage : IDisposable
{
// #############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public ICountryReportRepository ReportRepo { get; set; }
[Inject] public NavigationManager Navigator { get; set; }
[Inject] public ILogger<OfficeAdvisorReportViewPage> Logger { get; set; }
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public UserPreferenceService PreferenceService { get; set; }
[Inject] public IJSRuntime JsRuntime { get; set; }
[Inject] public IToastService Toaster { get; set; }
[Inject] public IOrderProcessRepository ProcessRepo { get; set; }
// #############################################################
[Parameter] public string CountryCode { get; set; } = "";
[Parameter] public string UserId { get; set; } = "";
[Parameter] public string ReportDate { get; set; } = "";
// #############################################################
private IJSObjectReference JsModule { get; set; }
private ReportView Report { get; set; } = new();
private List<ReportItemView> Activities { get; set; } = new();
private bool Working { get; set; } = true;
private UserPreference Profile { get; set; } = new();
private string _returnUrl = "";
protected override async Task OnParametersSetAsync()
{
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
PreferenceService.OnChange += ProfileServiceOnOnChange;
await PreferenceService.SetWorkDate(DateTime.Parse(ReportDate));
await FetchUserReport(ReportDate);
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
JsModule = await JsRuntime
.InvokeAsync<IJSObjectReference>("import", "/scripts/print-invoke.js");
}
}
private void Print(PrintTarget target)
{
_returnUrl = new Uri(Navigator.Uri).AbsolutePath;
switch (target)
{
case PrintTarget.OrderPage:
Navigator.NavigateTo($"/report/print/orders/{CountryCode}/{UserId}/{ReportDate}?returnUrl={_returnUrl}");
break;
case PrintTarget.FrontPage:
Navigator.NavigateTo($"/report/print/summary/{CountryCode}/{UserId}/{ReportDate}?returnUrl={_returnUrl}");
break;
case PrintTarget.None:
break;
case PrintTarget.All:
break;
default:
throw new ArgumentOutOfRangeException(nameof(target), target, null);
}
}
private async Task Print()
{
var current = 0;
var orders = Report.ReportItems
.Where(x => x is { StatusTypeEnum: "Order", ProcessStatusEnum: "None" } );
var count = orders.Count();
foreach (var item in orders)
{
Toaster.ShowInfo($"Behandler {current++} af {count} ordrer. Vent venligst");
await ProcessRepo.UpdateWarehouseOrderStatus(new OrderProcessState
{
OrderId = item.ActivityId,
ProcessStatusEnum = Utils.EnumToString(ProcessStatus.Printed)
});
}
Toaster.ClearAll();
await JsModule.InvokeVoidAsync("printInvoke");
}
/// <summary>
/// Work date component event handler
/// </summary>
/// <param name="workDate"></param>
private async Task FetchUserReport(string workDate)
{
// remove busy signal if report is empty
if (string.IsNullOrWhiteSpace(Report.ReportData.ReportDate))
{
Working = false;
}
ReportDate = workDate;
// ensure the browser address bar contains the correct link
Navigator.NavigateTo($"/office/users/advisors/{CountryCode}/{UserId}/reports/{workDate}", false, true);
// return if we are already at it
if (Working)
{
return;
}
// reset variables
Report = new ReportView();
Activities = new List<ReportItemView>();
// set busy signal
Working = true;
Logger.LogDebug("UserId => {}", UserId);
// fetch report
Report = await ReportRepo.GetCountryReport(UserId, workDate);
Logger.LogDebug("Report => {}", JsonSerializer.Serialize(Report, new JsonSerializerOptions(JsonSerializerDefaults.Web)));
// extract activities
Activities = Report.ReportItems.Where(x => x.Lines.Any()).ToList();
// store locally
if (!string.IsNullOrWhiteSpace(Report.ReportData.ReportDate))
{
await Storage.SetItemAsync($"{UserId}-{workDate}", Report);
}
// remove busy signal
Working = false;
}
private void ProfileServiceOnOnChange(UserPreference userPreference)
{
Logger.LogDebug("OfficeReportViewPage => ProfileServiceOnOnChange");
Profile = userPreference;
Logger.LogDebug("OfficeReportViewPage => ProfileServiceOnOnChange => Prefs.WorkDate <= {}", Profile.WorkDate);
ReportDate = Profile.WorkDate;
StateHasChanged();
}
public void Dispose()
{
Interceptor.DisposeEvent();
PreferenceService.OnChange -= ProfileServiceOnOnChange;
}
}

View file

@ -0,0 +1,5 @@
<h3>SupervisorAdvisorVewPage</h3>
@code {
}

View file

@ -0,0 +1,6 @@
namespace Wonky.Client.Pages;
public class SupervisorAdvisorViewPage_razor
{
}

View file

@ -0,0 +1,5 @@
<h3>SupervisorHomePage</h3>
@code {
}

View file

@ -0,0 +1,6 @@
namespace Wonky.Client.Pages;
public class SupervisorHomePage_razor
{
}

View file

@ -15,7 +15,7 @@
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Roles = "Supervisor")]
@page "/supervisor/salesReps"
@page "/supervisor/advisors"
<PageTitle>Supervisor Sælger Oversigt</PageTitle>
<div class="row">
@ -58,7 +58,7 @@
{
foreach (var user in Users)
{
<a class="list-group-item list-group-item-action" href="/supervisor/salesReps/@user.UserId">
<a class="list-group-item list-group-item-action" href="/supervisor/advisors/@user.UserId">
<div class="row">
<div class="col-sm-1">
@user.CountryCode @user.SalesRep

View file

@ -8,7 +8,7 @@ namespace Wonky.Client.Pages;
#pragma warning disable CS8618
public partial class SupervisorAdvisorListPage : IDisposable
public partial class SupervisorUserListPage : IDisposable
{
// #############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }
@ -24,9 +24,14 @@ public partial class SupervisorAdvisorListPage : IDisposable
Interceptor.RegisterBeforeSendEvent();
Users = await UserRepo.GetSupervisorUsers();
if (Users.Any())
{
Users = Users
.OrderBy(x => x.FullName)
.ThenBy(x => x.CountryCode)
.ToList();
}
Working = false;
}
public void Dispose()

View file

@ -0,0 +1,158 @@
@* Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@using Microsoft.AspNetCore.Authorization
@using Wonky.Client.Components
@attribute [Authorize(Roles = "Admin,Office,Warehouse")]
@page "/office/customers/{CompanyId}/orders/{OrderId}"
<PageTitle>@ReportItem.ESalesNumber - @ReportItem.Company.Name</PageTitle>
<table class="table table-sm table-striped d-print-table">
<thead>
<tr>
<th class="p-0" colspan="4">
<div class="d-print-none">
<div class="row">
<div class="col-sm-1">
<button type="button" class="btn btn-warning d-block" onclick="window.print();">PRINT</button>
</div>
@if (ReportItem is { Express: true, ProcessStatusEnum: "None" })
{
<AuthorizeView Roles="Admin,Office,Warehouse">
<Authorized>
<div class="col-sm-5">
<button type="button" class="btn btn-warning d-block" @onclick="SetExpressState" disabled="@IsNotified">Kvitter for modtagelse</button>
</div>
</Authorized>
</AuthorizeView>
}
</div>
</div>
<div class="bg-light text-dark border border-1 rounded-3 pt-3 mb-2">
<h2 class="fw-bold text-center">@ReportItem.Company.Name</h2>
@if (ReportItem.Express)
{
<h2 class="fw-bold text-center"><i class="bi-lightning-charge text-dark" style="font-size: 2rem;"></i> HASTER</h2>
}
@if (ReportItem.VisitTypeEnum.ToLower() == "phone" || ReportItem.OurRef.Contains("T:"))
{
<h5 class="text-center">TELEFONORDRE</h5>
}
@if (ReportItem.StatusTypeEnum is "Quote")
{
<h5 class="text-center">TILBUD</h5>
}
</div>
</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Dato</th>
<td>@ReportItem.OrderDate</td>
<th scope="row">Konto</th>
<td>@ReportItem.Company.Account</td>
</tr>
<tr>
<th scope="col">Telefon</th>
<td>@ReportItem.Company.Phone</td>
<th scope="col">Køber</th>
<td>@ReportItem.YourRef</td>
</tr>
<tr>
<th scope="col">CVR/VAT</th>
<td>@ReportItem.Company.VatNumber</td>
<th scope="col">Rekvisition</th>
<td>@ReportItem.ReferenceNumber</td>
</tr>
<tr>
<th scope="col">Navn</th>
<td>@ReportItem.Company.Name</td>
<th scope="col">Lev.Navn</th>
<td>@ReportItem.DlvName</td>
</tr>
<tr>
<th scope="col">Adresse</th>
<td>@ReportItem.Company.Address1</td>
<th scope="col">Lev.Adresse</th>
<td>@ReportItem.DlvAddress1</td>
</tr>
<tr>
<th scope="col">Adresse</th>
<td>@ReportItem.Company.Address2</td>
<th scope="col">Lev.Adresse</th>
<td>@ReportItem.DlvAddress2</td>
</tr>
<tr>
<th scope="col">Postnr By</th>
<td>@ReportItem.Company.ZipCode @ReportItem.Company.City</td>
<th scope="col">Lev.Postnr By</th>
<td>@ReportItem.DlvZipCity</td>
</tr>
<tr>
<th scope="col">Email</th>
<td colspan="3">@ReportItem.Company.Email</td>
</tr>
</tbody>
</table>
<table class="table table-sm table-striped table-bordered">
<thead>
<tr class="bg-light text-black">
<th scope="col">Antal</th>
<th scope="col">Varnr</th>
<th scope="col">Beskrivelse</th>
<th class="text-end" scope="col">Pris</th>
<th class="text-end" scope="col">R%</th>
<th class="text-end" scope="col">Beløb</th>
</tr>
</thead>
<tbody>
@foreach (var line in ReportItem.Lines)
{
<tr>
<td>@line.Quantity</td>
<td>@line.Sku</td>
<td>@line.Description</td>
<td class="text-end">@($"{line.Price:N2}")</td>
<td class="text-end">@($"{line.Discount:N2}")</td>
<td class="text-end">@($"{line.LineSum:N2}")</td>
</tr>
}
<tr>
<td colspan="4"></td>
<td>Ordresum</td>
<td class="text-end">@ReportItem.OrderAmount</td>
</tr>
@if (ReportItem.Express)
{
<td colspan="4"></td>
<td class="text-end" colspan="2">
<h5 class="fw-bold"><i class="bi-lightning-charge the-fast" style="font-size: 2rem;"></i> HASTER</h5>
</td>
}
</tbody>
</table>
@if (!string.IsNullOrWhiteSpace(ReportItem.OfficeNote))
{
<div class="alert border border-1">
<h4 class="text-center">@ReportItem.OfficeNote</h4>
</div>
}
@if (Working)
{
<WorkingThreeDots/>
}

View file

@ -0,0 +1,140 @@
// Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
//
using System.Text;
using System.Text.Json;
using Blazored.LocalStorage;
using Blazored.Toast.Services;
using Microsoft.AspNetCore.Components;
using Wonky.Client.HttpInterceptors;
using Wonky.Client.HttpRepository;
using Wonky.Client.Local.Services;
using Wonky.Entity.DTO;
using Wonky.Entity.Views;
#pragma warning disable CS8618
namespace Wonky.Client.Pages;
public partial class OfficeOrderViewPage : IDisposable
{
// #############################################################
[Inject] public HttpInterceptorService Interceptor { get; set; }
[Inject] public IAdvisorActivityRepository AdvisorActivityRepo { get; set; }
[Inject] public ISystemSendMailService MailService { get; set; }
[Inject] public ILocalStorageService Storage { get; set; }
[Inject] public IOfficeUserInfoRepository UserRepo { get; set; }
[Inject] public ILogger<OfficeOrderViewPage> Logger { get; set; }
[Inject] public IToastService Toast { get; set; }
[Inject] public IUserInfoService UserInfoService { get; set; }
// #############################################################
[Parameter] public string CompanyId { get; set; } = "";
[Parameter] public string OrderId { get; set; } = "";
// #############################################################
private ReportItemView ReportItem { get; set; } = new();
private bool IsNotified { get; set; }
private bool Working { get; set; } = true;
private readonly JsonSerializerOptions _options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
protected override async Task OnInitializedAsync()
{
Interceptor.RegisterEvent();
Interceptor.RegisterBeforeSendEvent();
// fetch order from backend
ReportItem = await AdvisorActivityRepo.GetReportItem(OrderId);
Logger.LogDebug("ReportItem => \n {}", JsonSerializer.Serialize(ReportItem, _options));
Working = false;
}
/// <summary>
/// Set activity process state to express. Send confirmation notification to salesRep
/// </summary>
private async Task SetExpressState()
{
// disable doubled actions
if (Working)
return;
Working = true;
Logger.LogDebug("GetExpressState => {}", JsonSerializer.Serialize(ReportItem, _options));
// send request to backend
var responseView = await AdvisorActivityRepo.GetExpressState(ReportItem.ActivityId);
Logger.LogDebug("SetExpressState => responseView <= {} ", JsonSerializer.Serialize(responseView));
// get user info from storage
var user = await UserInfoService.GetUserInfo();
// fetch sales rep from response
var salesRep = await UserRepo.GetUserInfo(responseView.Id);
Logger.LogDebug("SetExpressState => salesRep => {}", JsonSerializer.Serialize(salesRep));
// create email notification body
var body = new StringBuilder();
body.AppendLine($"Kvittering for modtagelse af hasteordre {ReportItem.ESalesNumber}");
body.AppendLine($"Konto : {ReportItem.Company.Account}");
body.AppendLine($"Navn : {ReportItem.Company.Name}");
body.AppendLine(
$"Post By : {salesRep.CountryCode.ToUpper()}-{ReportItem.Company.ZipCode} {ReportItem.Company.City}");
body.AppendLine();
body.AppendLine("Med venlig hilsen");
body.AppendLine($"{user.FirstName} {user.LastName}");
body.AppendLine($"{user.PhoneNumber}");
// create a list of mail addresses
var sendTo = new List<EmailContact>
{
new()
{
Email = salesRep.Email,
Name = $"{salesRep.FirstName} {salesRep.LastName}"
}
};
// create an email
var msg = new EmailMessage
{
Body = body.ToString(),
Subject = $"Haste ordre til {ReportItem.Company.Name} er modtaget.",
To = sendTo,
IsBodyHtml = false
};
Logger.LogDebug("SetExpressState Notification => \n {}", JsonSerializer.Serialize(msg));
// send a system generated email
var sendMail = await MailService.SendMail("System", msg);
// result notification
if (sendMail.IsSuccess)
{
Toast
.ShowSuccess(
$"Status er opdateret og notifikation sendt til {salesRep.FirstName}.", "OK");
}
else
{
Toast
.ShowWarning(
$"Notifikation til {salesRep.FirstName} kunne ikke sendes. {sendMail.Message}", "ADVARSEL");
}
Logger.LogDebug("SendMail Result => \n {}", JsonSerializer.Serialize(sendMail));
// disable further notifications
IsNotified = true;
Working = false;
}
public void Dispose()
{
Interceptor.DisposeEvent();
}
}

View file

@ -0,0 +1,21 @@
/* Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*/
#watermark {
position: absolute;
z-index: -999;
top: 10px;
right: 0;
}

View file

@ -0,0 +1,146 @@
@* Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*@
@inject IWebAssemblyHostEnvironment HostEnvironment
@using Wonky.Client.Components;
@using Wonky.Entity.Views
@using Blazored.LocalStorage
<div class="top-row ps-3 navbar navbar-dark">
<div class="container-fluid">
<a class="navbar-brand" href="/"><TopbarDisplayUser /></a>
<button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<nav class="flex-column">
<AuthorizeView>
<NotAuthorized>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/" Match="NavLinkMatch.All">
<i class="bi-person-workspace pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Start
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/login">
<i class="bi-lock pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Log ind
</NavLink>
</div>
</NotAuthorized>
</AuthorizeView>
<AuthorizeView Roles="Admin,Office,Warehouse">
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/office/country">
<i class="bi-people pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Sælgere
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/office/customers/dk">
<i class="bi-building pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Kunder DK
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/office/customers/no">
<i class="bi-building pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Kunder NO
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/office/customers/se">
<i class="bi-building pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Kunder SE
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/warehouse/orders/none">
<i class="bi-box pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Forsendelse
</NavLink>
</div>
</AuthorizeView>
<AuthorizeView Roles="Advisor">
<Authorized>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/home">
<i class="bi-calendar pe-2" style="font-size:1.3em;" aria-hidden="true"></i> ToDo
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/catalog">
<i class="bi-file-spreadsheet pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Priskatalog
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/advisor/customers">
<i class="bi-building pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Firmaer
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/advisor/agreements">
<i class="bi-calculator pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Aftaler/Tilbud
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/advisor/activity-today">
<i class="bi-activity pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Aktivitet
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/advisor/reports">
<i class="bi-file-earmark-spreadsheet pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Dagsrapporter
</NavLink>
</div>
</Authorized>
</AuthorizeView>
<AuthorizeView Roles="Admin">
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/system">
<i class="bi-gear-wide-connected pe-2" style="font-size:1.3em;" aria-hidden="true"></i> System
</NavLink>
</div>
</AuthorizeView>
<AuthorizeView Roles="Admin,Advisor,Office,Supervisor,Warehouse">
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/info">
<i class="bi-question pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Hjælp
</NavLink>
</div>
<div class="nav-item px-3">
<NavLink class="nav-link ps-2" href="/logout">
<i class="bi-lock pe-2" style="font-size:1.3em;" aria-hidden="true"></i> Log af
</NavLink>
</div>
</AuthorizeView>
</nav>
</div>
@code {
private bool collapseNavMenu = true;
private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;
private void ToggleNavMenu()
{
collapseNavMenu = !collapseNavMenu;
}
}

View file

@ -0,0 +1,81 @@
/* Copyright (C) 2022 FCS Frede's Computer Services.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see [https://www.gnu.org/licenses/agpl-3.0.en.html]
*/
.navbar-toggler {
background-color: rgba(255, 255, 255, 0.1);
}
.top-row {
height: 3.5rem;
background-color: rgba(0,0,0,0.4);
}
.navbar-brand {
font-size: 1.1rem;
}
.oi {
width: 2rem;
font-size: 1.1rem;
vertical-align: text-top;
top: -2px;
}
.nav-item {
font-size: 0.9rem;
padding-bottom: 0.5rem;
}
.nav-item:first-of-type {
padding-top: 1rem;
}
.nav-item:last-of-type {
padding-bottom: 1rem;
}
.nav-item ::deep a {
color: #d7d7d7;
border-radius: 4px;
height: 3rem;
display: flex;
align-items: center;
line-height: 3rem;
}
.nav-item ::deep a.active {
background-color: rgba(255,255,255,0.25);
color: white;
}
.nav-item ::deep a:hover {
background-color: rgba(255,255,255,0.1);
color: white;
}
.list-group.panel > .list-group-item {
border-bottom-right-radius: 4px;
border-bottom-left-radius: 4px;
}
@media (min-width: 1025px) {
.navbar-toggler {
display: none;
}
.collapse {
/* Never collapse the sidebar for wide screens */
display: block;
}
}