Browse Source

Merge pull request 'BISK2023-21-develop-the-frontend-for-the-dashboard' (#7) from BISK2023-21-develop-the-frontend-for-the-dashboard into dev

Reviewed-on: https://scm.barhendev.com/barhen/Biskilog_Accounting/pulls/7
BISKI2023-18
Benjamin Arhen 2 years ago
parent
commit
b02765e6ab
  1. 4
      .editorconfig
  2. 5
      Biskilog Accounting.sln
  3. 10
      Client/Biskilog Accounting.Client.csproj
  4. 2
      Client/Elements/Footer.razor
  5. 16
      Client/Elements/Sidebar.razor
  6. 25
      Client/Elements/Sidebar.razor.cs
  7. 36
      Client/Layouts/MainLayout.razor
  8. 24
      Client/Layouts/MainLayout.razor.cs
  9. 105
      Client/Models/NavItem.cs
  10. 13
      Client/Pages/Auth/Login.razor
  11. 107
      Client/Pages/Auth/Login.razor.cs
  12. 143
      Client/Pages/Dashboard/Dashboard.razor
  13. 41
      Client/Pages/Dashboard/Dashboard.razor.cs
  14. 28
      Client/Pages/Dashboard/Elements/AnalyticsItemSmall.razor
  15. 16
      Client/Pages/Dashboard/Elements/AnalyticsItemSmall.razor.cs
  16. 25
      Client/Pages/Dashboard/Elements/WelcomeCard.razor
  17. 41
      Client/Pages/Dashboard/Elements/WelcomeCard.razor.cs
  18. 10
      Client/Program.cs
  19. 22
      Client/wwwroot/assets/vendor/css/core.css
  20. 20
      Client/wwwroot/assets/vendor/css/theme-default.css
  21. 4
      Server/Biskilog Accounting.Server.csproj
  22. 9
      Server/Controllers/AnalyticsController.cs
  23. 42
      Server/Services/AnalyticalService.cs
  24. 2
      Shared/Biskilog Accounting.Shared.csproj
  25. 16
      Shared/CustomModels/TradeSummary.cs
  26. 6
      Shared/Interfaces/IAnalytics.cs
  27. 16
      Shared/Interfaces/ICalculator.cs
  28. 4
      Shared/Interfaces/ITokenService.cs
  29. 39
      Shared/ServiceRepo/CalculatorService.cs
  30. 41
      Shared/ServiceRepo/TokenService.cs

4
.editorconfig

@ -0,0 +1,4 @@
[*.cs]
# IDE0021: Use block body for constructor
csharp_style_expression_bodied_constructors = when_on_single_line

5
Biskilog Accounting.sln

@ -9,6 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Biskilog Accounting.Client"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Biskilog Accounting.Shared", "Shared\Biskilog Accounting.Shared.csproj", "{48490CB4-D5CE-46FE-9A7A-FBC91980D262}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{84405340-FCB0-46CB-B730-10E47D9F54C2}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU

10
Client/Biskilog Accounting.Client.csproj

@ -8,8 +8,14 @@
<AssemblyName>$(AssemblyName.Replace(' ', '_'))</AssemblyName>
</PropertyGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Blazored.LocalStorage" Version="4.3.0" />
<PackageReference Include="Blazored.SessionStorage" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
@ -23,8 +29,4 @@
<ServiceWorker Include="wwwroot\service-worker.js" PublishedContent="wwwroot\service-worker.published.js" />
</ItemGroup>
<ItemGroup>
<Folder Include="Pages\Dashboard\Elements\" />
</ItemGroup>
</Project>

2
Client/Elements/Footer.razor

@ -1,4 +1,4 @@
<footer class="content-footer footer bg-footer-theme">
<footer class="content-footer footer bg-footer-theme" style="position:relative;">
<div class="container-xxl d-flex flex-wrap justify-content-between py-2 flex-md-row flex-column">
<div class="mb-2 mb-md-0">
Biskilog © @DateTime.Now.Year

16
Client/Elements/Sidebar.razor

@ -1,5 +1,5 @@
@using Biskilog_Accounting.Client.Models;
<aside id="layout-menu" class="layout-menu menu-vertical menu bg-menu-theme">
<aside id="layout-menu" class="@m_class menu-vertical menu bg-menu-theme">
<div class="app-brand">
<a href="index.html" class="app-brand-link">
<span class="app-brand-logo">
@ -8,7 +8,7 @@
<span class="app-brand-text menu-text fw-bolder ms-2">BISKILOG</span>
</a>
<a href="javascript:void(0);" class="layout-menu-toggle menu-link text-large ms-auto d-block d-xl-none">
<a href="javascript:void(0);" class="layout-menu-toggle menu-link text-large ms-auto d-block" @onclick="Collapse">
<i class="bx bx-chevron-left bx-sm align-middle"></i>
</a>
</div>
@ -26,15 +26,3 @@
</ul>
</aside>
@code {
private double m_activeId = 1;
private void HandleMenuClick(double a_selectedId)
{
m_activeId = a_selectedId;
StateHasChanged();
}
}

25
Client/Elements/Sidebar.razor.cs

@ -0,0 +1,25 @@
namespace Biskilog_Accounting.Client.Elements
{
public partial class Sidebar
{
private double m_activeId = 1;
private string m_class { get; set; } = "layout-menu";
private void HandleMenuClick(double a_selectedId)
{
m_activeId = a_selectedId;
StateHasChanged();
}
private void Collapse()
{
//if (m_class == "layout-menu")
//{
// m_class = "layout-menu-collapsed";
//}
//else
//{
// m_class = "layout-menu";
//}
}
}
}

36
Client/Layouts/MainLayout.razor

@ -1,5 +1,11 @@
@inherits LayoutComponentBase
@using Biskilog_Accounting.Client.Elements
@using Biskilog_Accounting.Client.Elements
@using Biskilog_Accounting.Shared.Interfaces;
@inherits LayoutComponentBase
@inject NavigationManager m_navigationManager
@layout AuthLayout
@inject HttpClient m_http
@inject ITokenService m_tokenService
<!-- Layout wrapper -->
<div class="layout-wrapper layout-content-navbar">
<div class="layout-container">
@ -7,22 +13,22 @@
<Sidebar />
<!-- / Menu -->
<!-- Body Layout container -->
<div class="layout-page">
<Headbar />
<!-- Content wrapper -->
<div class="content-wrapper">
<!-- Content -->
@Body
<!-- / Content -->
<!-- Footer -->
<div style="width: 100vh;display: flex;flex: 1 1 auto;flex-direction: column;flex-wrap: nowrap;">
<div class="layout-page">
<Headbar />
<!-- Content wrapper -->
<div class="content-wrapper">
<Footer />
<!-- / Footer -->
<!-- Content -->
@Body
<!-- / Content -->
<!-- Footer -->
</div>
<!-- Content wrapper -->
</div>
<!-- Content wrapper -->
<!-- / Layout page -->
<Footer />
</div>
<!-- / Layout page -->
</div>
</div>
<!-- / Layout wrapper -->

24
Client/Layouts/MainLayout.razor.cs

@ -0,0 +1,24 @@
using System.Net.Http.Headers;
namespace Biskilog_Accounting.Client.Layouts
{
public partial class MainLayout
{
protected override async Task OnInitializedAsync()
{
//Checks if user token is set else redirect user to login page
if (!await m_tokenService.IsTokenSet())
{
m_navigationManager.NavigateTo("/login");
}
else
{
string token = await m_tokenService.GetToken();
var authHeader = new AuthenticationHeaderValue("Bearer", token.Substring(6).Trim());
m_http.DefaultRequestHeaders.Authorization = authHeader;
}
return;
}
}
}

105
Client/Models/NavItem.cs

@ -46,53 +46,112 @@
Link = "",
Title = "Dashboard"
},
new NavItem()
new NavItem()
{
Id = 2,
Description = "Transactions",
Title = "Transactions",
Icon = "bxs-cart",
Children = new List<NavItem>
{
Id = 2,
Description = "Customers",
Icon = "bx-users",
Link = "customers",
Title = "Customers"
new NavItem()
{
Id = 2.1,
Title = "Sales",
},
new NavItem()
{
Id = 2.2,
Title = "Cancelled Sales",
},
new NavItem()
{
Id = 2.3,
Title = "Statistics",
},
},
},
new NavItem()
new NavItem()
{
Id = 3,
Description = "Employees",
Icon = "bx-users",
Link = "employees",
Title = "Employees"
},
new NavItem()
{
Id = 4,
Description = "Products",
Icon = "bx-box",
Link = "",
Title = "Products",
Children = new List<NavItem>
{
new NavItem
{
Id = 4.1,
Id = 3.1,
Description = "Brands",
Title = "Brands",
Link = "",
},
new NavItem
{
Id = 3.2,
Description = "Category",
Title = "Category",
Link = "",
},
new NavItem
{
Id = 3.3,
Description = "Inventory",
Title = "Inventory"
Title = "Inventory",
Link = "",
},
new NavItem
{
Id = 4.2,
Id = 3.4,
Description = "Inventory",
Title = "Bulk Price Changes"
Title = "Bulk Price Changes",
Link = "",
},
new NavItem
{
Id = 4.3,
Id = 3.5,
Description = "Inventory",
Title = "Products Statistics",
Icon = "bx-detail"
Link = "",
}
}
},
};
new NavItem()
{
Id = 4,
Description = "Customers",
Icon = "bxs-user",
Link = "customers",
Title = "Customers"
},
new NavItem()
{
Id = 5,
Description = "Employees",
Icon = "bxs-user-account",
Link = "employees",
Title = "Employees"
},
new NavItem()
{
Id = 6,
Description = "Warehouse Management",
Icon = "bxs-factory",
Title = "Warehouse Management",
Children = new List<NavItem> {
new NavItem(){
Id = 6.1,
Description = "Item Inventory for the warehouse",
Title = "Inventory",
Link = "",
},
},
},
};
var uniqueIds = new HashSet<double>();

13
Client/Pages/Auth/Login.razor

@ -1,5 +1,10 @@
@layout AuthLayout
@using Biskilog_Accounting.Shared.Interfaces
@inject NavigationManager m_navigationManager
@layout AuthLayout
@inject HttpClient m_http
@inject ITokenService m_tokenService
@page "/login"
<style>
.gradient-custom-2 {
/* fallback for old browsers */
@ -78,17 +83,17 @@
<div class="form-outline mb-4">
<input type="email" id="form2Example11" class="form-control"
placeholder="Phone number or email address" />
placeholder="Phone number or email address" @oninput="@(args => usernameInput(args.Value.ToString()))" @onkeydown="@Enter" />
<label class="form-label" for="form2Example11">Username</label>
</div>
<div class="form-outline mb-4">
<input type="password" id="form2Example22" class="form-control" />
<input type="password" id="form2Example22" class="form-control" @oninput="@(args => passwordInput(args.Value.ToString()))" @onkeydown="@Enter" />
<label class="form-label" for="form2Example22">Password</label>
</div>
<div class="text-center pt-1 mb-5 pb-1">
<button class="btn btn-primary btn-block fa-lg gradient-custom-2 mb-3" type="button">
<button class="btn btn-primary btn-block fa-lg gradient-custom-2 mb-3" type="button" @onclick="pagaAuth">
Log
in
</button>

107
Client/Pages/Auth/Login.razor.cs

@ -1,7 +1,112 @@
namespace Biskilog_Accounting.Client.Pages.Auth
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components;
using System.Net.Http.Headers;
using Biskilog_Accounting.Shared.ClientContractModels;
using Biskilog_Accounting.Shared.Interfaces;
using System.Net.Http.Json;
namespace Biskilog_Accounting.Client.Pages.Auth
{
public partial class Login
{
private string m_email, m_password;
private bool m_remember { get; set; }
protected bool IsVisible { get; set; }
//NotificationMessage notificationMessage = new NotificationMessage();
private Userauth authenticatedUser;
/// <summary>
/// Handles the click or press event of the enter key
/// </summary>
/// <param name="e"></param>
public async void Enter(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter")
{
await pagaAuth();
}
}
/// <summary>
/// Authenticates the user and determines the type of page layout to show
/// </summary>
/// <returns></returns>
async Task pagaAuth()
{
ShowSpinner();
try
{
authenticatedUser = new Userauth
{
UserId = 0,
Username = m_email,
Email = m_email,
Passsword = m_password
};
var responseMain = await m_http.PostAsJsonAsync("api/authentication/type-a", authenticatedUser);
if (responseMain.IsSuccessStatusCode)
{
string token = await responseMain.Content.ReadAsStringAsync();
await m_tokenService.SetToken(token, m_remember);
var authHeader = new AuthenticationHeaderValue("Bearer", token);
m_http.DefaultRequestHeaders.Authorization = authHeader;
m_navigationManager.NavigateTo("/");
}
else if (responseMain.StatusCode == System.Net.HttpStatusCode.BadRequest)
{
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
HideSpinner();
}
/// <summary>
/// Shows the loading spinner
/// </summary>
public void ShowSpinner()
{
IsVisible = true;
StateHasChanged();
}
/// <summary>
/// Hides the loading spinner
/// </summary>
public void HideSpinner()
{
IsVisible = false;
StateHasChanged();
}
/// <summary>
/// Shows a notification message
/// </summary>
/// <param name="message"></param>
/// <returns></returns>
//async Task ShowNotification(NotificationMessage message)
//{
// notificationService.Notify(message);
// await InvokeAsync(() => { StateHasChanged(); });
//}
/// <summary>
/// Sets the username value
/// </summary>
/// <param name="value"></param>
void usernameInput(string value)
{
m_email = value;
StateHasChanged();
}
/// <summary>
/// Sets the password value
/// </summary>
/// <param name="value"></param>
void passwordInput(string value)
{
m_password = value;
StateHasChanged();
}
}
}

143
Client/Pages/Dashboard/Dashboard.razor

@ -1,94 +1,21 @@
@page "/"
@using Biskilog_Accounting.Client.Pages.Dashboard.Elements
@using Biskilog_Accounting.Shared.Interfaces
@inject HttpClient m_http
@inject ITokenService m_tokenService
@page "/"
<div class="container-xxl flex-grow-1 container-p-y">
<div class="row">
<div class="col-lg-8 mb-4 order-0">
<div class="card">
<div class="d-flex align-items-end row">
<div class="col-sm-7">
<div class="card-body">
<h5 class="card-title text-primary">Congratulations John! 🎉</h5>
<p class="mb-4">
You have done <span class="fw-bold">72%</span> more sales today. Check your new badge in
your profile.
</p>
<a href="javascript:;" class="btn btn-sm btn-outline-primary">View Badges</a>
</div>
</div>
<div class="col-sm-5 text-center text-sm-left">
<div class="card-body pb-0 px-0 px-md-4">
<img src="../assets/img/illustrations/man-with-laptop-light.png"
height="140"
alt="View Badge User"
data-app-dark-img="illustrations/man-with-laptop-dark.png"
data-app-light-img="illustrations/man-with-laptop-light.png" />
</div>
</div>
</div>
</div>
<WelcomeCard CurrentTradeSales="@m_tradeSummary.CurrentTradeSales" PreviousTradeSales="@m_tradeSummary.LastTradeSales" Username="@m_username" />
</div>
<div class="col-lg-4 col-md-4 order-1">
<div class="row">
<div class="col-lg-6 col-md-12 col-6 mb-4">
<div class="card">
<div class="card-body">
<div class="card-title d-flex align-items-start justify-content-between">
<div class="avatar flex-shrink-0">
<img src="../assets/img/icons/unicons/chart-success.png"
alt="chart success"
class="rounded" />
</div>
<div class="dropdown">
<button class="btn p-0"
type="button"
id="cardOpt3"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
<i class="bx bx-dots-vertical-rounded"></i>
</button>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="cardOpt3">
<a class="dropdown-item" href="javascript:void(0);">View More</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>
</div>
<span class="fw-semibold d-block mb-1">Profit</span>
<h3 class="card-title mb-2">$12,628</h3>
<small class="text-success fw-semibold"><i class="bx bx-up-arrow-alt"></i> +72.80%</small>
</div>
</div>
<AnalyticsItemSmall Icon="../assets/img/icons/unicons/chart-success.png" Title="Transactions" Value="2000" Percentage="70"/>
</div>
<div class="col-lg-6 col-md-12 col-6 mb-4">
<div class="card">
<div class="card-body">
<div class="card-title d-flex align-items-start justify-content-between">
<div class="avatar flex-shrink-0">
<img src="../assets/img/icons/unicons/wallet-info.png"
alt="Credit Card"
class="rounded" />
</div>
<div class="dropdown">
<button class="btn p-0"
type="button"
id="cardOpt6"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
<i class="bx bx-dots-vertical-rounded"></i>
</button>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="cardOpt6">
<a class="dropdown-item" href="javascript:void(0);">View More</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>
</div>
<span>Sales</span>
<h3 class="card-title text-nowrap mb-1">$4,679</h3>
<small class="text-success fw-semibold"><i class="bx bx-up-arrow-alt"></i> +28.42%</small>
</div>
</div>
<AnalyticsItemSmall Icon="../assets/img/icons/unicons/wallet-info.png" Title="Cancelled Sales" Value="4679" Percentage="28.42" />
</div>
</div>
</div>
@ -151,60 +78,10 @@
<div class="col-12 col-md-8 col-lg-4 order-3 order-md-2">
<div class="row">
<div class="col-6 mb-4">
<div class="card">
<div class="card-body">
<div class="card-title d-flex align-items-start justify-content-between">
<div class="avatar flex-shrink-0">
<img src="../assets/img/icons/unicons/paypal.png" alt="Credit Card" class="rounded" />
</div>
<div class="dropdown">
<button class="btn p-0"
type="button"
id="cardOpt4"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
<i class="bx bx-dots-vertical-rounded"></i>
</button>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="cardOpt4">
<a class="dropdown-item" href="javascript:void(0);">View More</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>
</div>
<span class="d-block mb-1">Payments</span>
<h3 class="card-title text-nowrap mb-2">$2,456</h3>
<small class="text-danger fw-semibold"><i class="bx bx-down-arrow-alt"></i> -14.82%</small>
</div>
</div>
<AnalyticsItemSmall Icon="../assets/img/icons/unicons/paypal.png" Title="Out Of Stock" Value="2456" Percentage="-22" />
</div>
<div class="col-6 mb-4">
<div class="card">
<div class="card-body">
<div class="card-title d-flex align-items-start justify-content-between">
<div class="avatar flex-shrink-0">
<img src="../assets/img/icons/unicons/cc-primary.png" alt="Credit Card" class="rounded" />
</div>
<div class="dropdown">
<button class="btn p-0"
type="button"
id="cardOpt1"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
<i class="bx bx-dots-vertical-rounded"></i>
</button>
<div class="dropdown-menu" aria-labelledby="cardOpt1">
<a class="dropdown-item" href="javascript:void(0);">View More</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>
</div>
<span class="fw-semibold d-block mb-1">Transactions</span>
<h3 class="card-title mb-2">$14,857</h3>
<small class="text-success fw-semibold"><i class="bx bx-up-arrow-alt"></i> +28.14%</small>
</div>
</div>
<AnalyticsItemSmall Icon="../assets/img/icons/unicons/cc-primary.png" Title="Transactions" Value="2456" Percentage="-22" />
</div>
<!-- </div>
<div class="row"> -->

41
Client/Pages/Dashboard/Dashboard.razor.cs

@ -0,0 +1,41 @@
using Biskilog_Accounting.Shared.CustomModels;
using System.Net.Http.Json;
using System.Text.Json;
namespace Biskilog_Accounting.Client.Pages.Dashboard
{
public partial class Dashboard
{
private TradeSummary m_tradeSummary { get; set; } = new TradeSummary();
private string m_username { get; set; } = string.Empty;
protected override async Task OnInitializedAsync()
{
m_username = m_tokenService.GetUserNameFromToken(await m_tokenService.GetToken())!;
await GetSummary();
return;
}
/// <summary>
/// Gets the tade summary
/// </summary>
/// <returns></returns>
async Task GetSummary()
{
try
{
var response = await m_http.GetAsync("api/analytics/tradesummary");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var tradeSummary = JsonSerializer.Deserialize<TradeSummary>(jsonContent, options);
m_tradeSummary = tradeSummary;
StateHasChanged();
}
}catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
}

28
Client/Pages/Dashboard/Elements/AnalyticsItemSmall.razor

@ -0,0 +1,28 @@
<div class="card">
<div class="card-body">
<div class="card-title d-flex align-items-start justify-content-between">
<div class="avatar flex-shrink-0">
<img src="@Icon"
alt="chart success"
class="rounded" />
</div>
<div class="dropdown">
<button class="btn p-0"
type="button"
id="cardOpt3"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
<i class="bx bx-dots-vertical-rounded"></i>
</button>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="cardOpt3">
<a class="dropdown-item" href="javascript:void(0);">View More</a>
<a class="dropdown-item" href="javascript:void(0);">Delete</a>
</div>
</div>
</div>
<span class="fw-semibold d-block mb-1">@Title</span>
<h3 class="card-title mb-2">@Value</h3>
<small class="@(Percentage > 0 ? "text-success" : Percentage == 0 ? "bx-forward-arrow-alt" : "text-danger") fw-semibold"><i class="bx @(Percentage > 0 ? "bx-up-arrow-alt" : Percentage == 0 ? "bx-forward-arrow-alt" : "bx-down-arrow-alt")"></i> @Percentage %</small>
</div>
</div>

16
Client/Pages/Dashboard/Elements/AnalyticsItemSmall.razor.cs

@ -0,0 +1,16 @@
using Microsoft.AspNetCore.Components;
namespace Biskilog_Accounting.Client.Pages.Dashboard.Elements
{
public partial class AnalyticsItemSmall
{
[Parameter]
public string Icon { get; set; } = string.Empty;
[Parameter]
public string Title { get; set; }= string.Empty;
[Parameter]
public double Value{ get; set; }
[Parameter]
public double Percentage { get;set; }
}
}

25
Client/Pages/Dashboard/Elements/WelcomeCard.razor

@ -0,0 +1,25 @@
@using Biskilog_Accounting.Shared.Interfaces;
@inject ICalculator m_calculator;
<div class="card">
<div class="d-flex align-items-end row">
<div class="col-sm-7">
<div class="card-body">
<h5 class="card-title text-primary">@(CurrentTradeSales > PreviousTradeSales ? "Congratulations 🎉" : PreviousTradeSales > CurrentTradeSales ? "Tough shift there" : "Well Done") @Username!</h5>
<p class="mb-4">
@m_remarks
</p>
<a href="javascript:;" class="btn btn-sm btn-outline-primary">View Trade Summary</a>
</div>
</div>
<div class="col-sm-5 text-center text-sm-left">
<div class="card-body pb-0 px-0 px-md-4">
<img src="../assets/img/illustrations/man-with-laptop-light.png"
height="140"
alt="View Badge User"
data-app-dark-img="illustrations/man-with-laptop-dark.png"
data-app-light-img="illustrations/man-with-laptop-light.png" />
</div>
</div>
</div>
</div>

41
Client/Pages/Dashboard/Elements/WelcomeCard.razor.cs

@ -0,0 +1,41 @@
using Microsoft.AspNetCore.Components;
namespace Biskilog_Accounting.Client.Pages.Dashboard.Elements
{
public partial class WelcomeCard
{
[Parameter]
public string Username { get; set; } = string.Empty;
[Parameter]
public double CurrentTradeSales { get; set; } = 0;
[Parameter]
public double PreviousTradeSales { get; set; } = 0;
private string m_remarks { get; set; } = "";
protected override void OnParametersSet()
{
CalculateStatistic();
}
private void CalculateStatistic()
{
double change = ((CurrentTradeSales - PreviousTradeSales) / PreviousTradeSales) * 100;
if (CurrentTradeSales > PreviousTradeSales)
{
string changePercent = change.ToString("0.00") + "%";
m_remarks = $"You made a total of {m_calculator.FormatMoneyWithCurrency(CurrentTradeSales)} in the current trade, {changePercent} more than the previous trade sales";
}
else if (PreviousTradeSales > CurrentTradeSales)
{
string changePercent = (change * -1).ToString("0.00") + "%";
m_remarks = $"You made a total of {m_calculator.FormatMoneyWithCurrency(CurrentTradeSales)} in the current trade, {changePercent} less than the previous trade sales";
}
else
{
m_remarks = $"You made a total of {m_calculator.FormatMoneyWithCurrency(CurrentTradeSales)} in the current trade, same as the previous trade sales";
}
StateHasChanged();
}
}
}

10
Client/Program.cs

@ -1,4 +1,9 @@
using Biskilog_Accounting.Client;
using Biskilog_Accounting.ServiceRepo;
using Biskilog_Accounting.Shared.Interfaces;
using Biskilog_Accounting.Shared.ServiceRepo;
using Blazored.LocalStorage;
using Blazored.SessionStorage;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
@ -10,5 +15,10 @@ builder.Services.AddHttpClient("Biskilog_Accounting.ServerAPI", client => client
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Biskilog_Accounting.ServerAPI"));
builder.Services.AddBlazoredLocalStorageAsSingleton();
builder.Services.AddBlazoredSessionStorageAsSingleton();
builder.Services.AddScoped<ICalculator, CalculatorService>();
builder.Services.AddScoped<ITokenService, TokenService>();
await builder.Build().RunAsync();

22
Client/wwwroot/assets/vendor/css/core.css

@ -235,7 +235,7 @@ a {
}
a:hover {
color: #5f61e6;
color: #11726d;
}
a:not([href]):not([class]), a:not([href]):not([class]):hover {
@ -2925,7 +2925,7 @@ textarea.form-control-lg {
}
.btn-link:hover {
color: #5f61e6;
color: #11726d;
}
.btn-link:disabled, .btn-link.disabled {
@ -3466,7 +3466,7 @@ textarea.form-control-lg {
}
.nav-link:hover, .nav-link:focus {
color: #5f61e6;
color: #11726d;
}
.nav-link.disabled {
@ -6038,7 +6038,7 @@ textarea.form-control-lg {
}
.link-primary:hover, .link-primary:focus {
color: #5f61e6;
color: #11726d;
}
.link-secondary {
@ -15902,6 +15902,7 @@ html:not(.layout-menu-fixed) .menu-inner-shadow {
.menu-vertical .menu-block,
.menu-vertical .menu-inner > .menu-item,
.menu-vertical .menu-inner > .menu-header {
max-width: 16.25rem;
width: 16.25rem;
}
@ -16273,7 +16274,6 @@ html:not(.layout-menu-fixed) .menu-inner-shadow {
.layout-content-navbar .layout-page {
flex-basis: 100%;
flex-direction: column;
width: 0;
min-width: 0;
max-width: 100%;
}
@ -17778,6 +17778,7 @@ html:not(.layout-footer-fixed) .content-wrapper {
overflow-x: hidden;
max-height: 100vh; /* Adjust as needed */
}
.layout-page::-webkit-scrollbar {
width: 6px;
}
@ -17789,11 +17790,14 @@ html:not(.layout-footer-fixed) .content-wrapper {
.layout-page::-webkit-scrollbar-thumb:hover {
background-color: #555; /* Color of the scrollbar thumb on hover */
})
}
)
.layout-page::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgb(245,245,249);
}
.layout-page::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgb(245,245,249);
}
.layout-wrapper {
overflow-y: auto;
height: 100vh;

20
Client/wwwroot/assets/vendor/css/theme-default.css

@ -100,7 +100,7 @@
}
.text-body[href]:hover {
color: #5f61e6 !important;
color: #11726d !important;
}
.bg-primary {
@ -213,14 +213,14 @@ button.list-group-item-primary.active {
}
.btn-primary:hover {
color: #fff;
background-color: #5f61e6;
border-color: #5f61e6;
background-color: #11726d;
border-color: #11726d;
transform: translateY(-1px);
}
.btn-check:focus + .btn-primary, .btn-primary:focus, .btn-primary.focus {
color: #fff;
background-color: #5f61e6;
border-color: #5f61e6;
background-color: #11726d;
border-color: #11726d;
transform: translateY(0);
box-shadow: none;
}
@ -243,15 +243,15 @@ button.list-group-item-primary.active {
}
.btn-outline-primary:hover {
color: #fff;
background-color: #5f61e6;
border-color: #5f61e6;
background-color: #11726d;
border-color: #11726d;
box-shadow: 0 0.125rem 0.25rem 0 rgba(105, 108, 255, 0.4);
transform: translateY(-1px);
}
.btn-check:focus + .btn-outline-primary, .btn-outline-primary:focus {
color: #fff;
background-color: #5f61e6;
border-color: #5f61e6;
background-color: #11726d;
border-color: #11726d;
box-shadow: none;
transform: translateY(0);
}
@ -296,7 +296,7 @@ button.list-group-item-primary.active {
}
.nav .nav-link:hover, .nav .nav-link:focus {
color: #5f61e6;
color: #11726d;
}
.nav-pills .nav-link.active, .nav-pills .nav-link.active:hover, .nav-pills .nav-link.active:focus {

4
Server/Biskilog Accounting.Server.csproj

@ -9,6 +9,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Blazored.LocalStorage" Version="4.3.0" />
<PackageReference Include="Blazored.SessionStorage" Version="2.3.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="7.0.5" />
<PackageReference Include="BCrypt.Net" Version="0.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="7.0.5" />
@ -28,7 +30,7 @@
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="7.0.6" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="7.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.2" />
<PackageReference Include="Microsoft.Diagnostics.Tracing.TraceEvent" Version="3.1.3" />
</ItemGroup>
<ItemGroup>

9
Server/Controllers/AnalyticsController.cs

@ -97,5 +97,14 @@ namespace Biskilog_Accounting.Server.Controllers
{
return m_analyticService.GetMostPurchasedItem(a_start, a_end);
}
/// <summary>
/// Endpoint to return analysis on trade summary
/// </summary>
[Authorize]
[HttpGet, Route("tradesummary")]
public TradeSummary GetTradeSummary()
{
return m_analyticService.GetTradeSummary();
}
}
}

42
Server/Services/AnalyticalService.cs

@ -8,6 +8,7 @@ using Microsoft.Net.Http.Headers;
using MySqlConnector;
using System.Collections.Generic;
using System.Data.Entity;
using System.Drawing.Drawing2D;
namespace Biskilog_Accounting.Server.Services
{
@ -184,5 +185,46 @@ namespace Biskilog_Accounting.Server.Services
return m_context.Tblcarts.Where(t => t.Date >= a_start && t.Date <= a_end && accessiblebranches.Contains(t.BranchId));
}
public TradeSummary GetTradeSummary()
{
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
if (AuthEnums.Valid == m_tokenService.ValidateToken(token))
{
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token);
using (var command = m_context.Database.GetDbConnection().CreateCommand())
{
command.CommandText = "CALL GetTradeSummary(@p0)";
command.Parameters.Add(new MySqlParameter("@p0", string.Join(", ", accessiblebranches.ToArray())));
m_context.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
int i = 0;
TradeSummary result = new TradeSummary();
while (reader.Read())
{
if (i == 0)
{
result.CurrentTradeDate = reader.GetDateTime(0);
result.CurrentTradeSales = reader.GetDouble(1);
}
else if (i == 1)
{
result.LastTradeDate = reader.GetDateTime(0);
result.LastTradeSales = reader.GetDouble(1);
}
i++;
}
return result;
}
}
}
return new TradeSummary();
}
}
}

2
Shared/Biskilog Accounting.Shared.csproj

@ -8,6 +8,8 @@
<ItemGroup>
<PackageReference Include="AutoMapper.Extensions.Microsoft.DependencyInjection" Version="12.0.1" />
<PackageReference Include="Blazored.LocalStorage" Version="4.3.0" />
<PackageReference Include="Blazored.SessionStorage" Version="2.3.0" />
<PackageReference Include="EntityFramework" Version="6.4.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="7.0.5" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />

16
Shared/CustomModels/TradeSummary.cs

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.CustomModels
{
public class TradeSummary
{
public DateTime CurrentTradeDate { get; set; } = DateTime.Now;
public DateTime LastTradeDate { get; set; } = DateTime.Now.AddDays(-1);
public double CurrentTradeSales { get; set; } = 0;
public double LastTradeSales { get; set; } = 0;
}
}

6
Shared/Interfaces/IAnalytics.cs

@ -49,6 +49,10 @@ namespace Biskilog_Accounting.Shared.Interfaces
/// <param name="a_end"></param>
/// <returns></returns>
IEnumerable<ProductPriceChange> GetPriceChanges(DateTime a_start, DateTime a_end);
//void SetContraints(string a_token);
/// <summary>
/// Fetch the trade summary which is made of the total sales made currently and previous trade
/// </summary>
/// <returns></returns>
TradeSummary GetTradeSummary();
}
}

16
Shared/Interfaces/ICalculator.cs

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.Interfaces
{
public interface ICalculator
{
double CalculatePercentage();
string FormatMoneyWithCurrency(double a_amount);
NumberFormatInfo GetCurrencyCode();
}
}

4
Shared/Interfaces/ITokenService.cs

@ -14,5 +14,9 @@ namespace Biskilog_Accounting.Shared.Interfaces
bool? GetComparison(string a_token);
IEnumerable<string> BranchIds(string a_token);
string? GetAllBranch(string a_token);
Task SetToken(string a_token, bool a_remember);
Task<string> GetToken();
Task ClearToken();
Task<bool> IsTokenSet();
}
}

39
Shared/ServiceRepo/CalculatorService.cs

@ -0,0 +1,39 @@
using Biskilog_Accounting.Shared.Interfaces;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.ServiceRepo
{
public class CalculatorService : ICalculator
{
public double CalculatePercentage()
{
throw new NotImplementedException();
}
public string FormatMoneyWithCurrency(double a_amount)
{
return string.Format(GetCurrencyCode(), " {0:C2}", a_amount);
}
public NumberFormatInfo GetCurrencyCode()
{
//TODO have a better implementation
// Specify the locale for Ghana
string locale = "en-GH";
// Get the NumberFormatInfo for the specified locale
NumberFormatInfo numberFormatInfo = new CultureInfo(locale).NumberFormat;
// Set the currency symbol to Ghanaian cedi
numberFormatInfo.CurrencySymbol = "GH₵";
return numberFormatInfo;
}
}
}

41
Shared/ServiceRepo/TokenService.cs

@ -1,6 +1,8 @@
using Biskilog_Accounting.Shared.ClientContractModels;
using Biskilog_Accounting.Shared.Enums;
using Biskilog_Accounting.Shared.Interfaces;
using Blazored.LocalStorage;
using Blazored.SessionStorage;
using Microsoft.Extensions.Configuration;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
@ -12,9 +14,13 @@ namespace Biskilog_Accounting.ServiceRepo
public class TokenService : ITokenService
{
private IConfiguration m_configuration { get; }
public TokenService(IConfiguration a_configuration)
private readonly ISessionStorageService m_sessionStorage;
private readonly ILocalStorageService m_localStorage;
public TokenService(IConfiguration a_configuration, ISessionStorageService a_sessionStorage = null, ILocalStorageService a_localStorage = null)
{
m_configuration = a_configuration;
m_sessionStorage = a_sessionStorage;
m_localStorage = a_localStorage;
}
/// <summary>
@ -196,5 +202,38 @@ namespace Biskilog_Accounting.ServiceRepo
}
return branchIds.AsEnumerable();
}
public async Task SetToken(string a_token, bool a_remember)
{
if (a_remember)
{
await m_localStorage.SetItemAsStringAsync("token", $"Bearer {a_token}");
}
else
{
await m_sessionStorage.SetItemAsStringAsync("token", $"Bearer {a_token}");
}
}
public async Task<string> GetToken()
{
string token = await m_localStorage.GetItemAsStringAsync("token");
if (String.IsNullOrEmpty(token))
{
token = await m_sessionStorage.GetItemAsStringAsync("token");
}
return token;
}
public async Task ClearToken()
{
await m_localStorage.ClearAsync();
await m_sessionStorage.ClearAsync();
}
public async Task<bool> IsTokenSet()
{
return await m_localStorage.ContainKeyAsync("token") || await m_sessionStorage.ContainKeyAsync("token");
}
}
}

Loading…
Cancel
Save