Browse Source

Merge pull request 'Dashboard Commit 3' (#8) from BISK2023-21-develop-the-frontend-for-the-dashboard into dev

Reviewed-on: #8
BISK2023-4-item-search-feature
Benjamin Arhen 2 years ago
parent
commit
c8b95cef41
  1. 1
      Client/Biskilog Accounting.Client.csproj
  2. 57
      Client/Pages/Dashboard/Dashboard.razor
  3. 34
      Client/Pages/Dashboard/Dashboard.razor.cs
  4. 9
      Client/Pages/Dashboard/Elements/AnalyticsItemSmall.razor
  5. 58
      Client/Pages/Dashboard/Elements/ChartElement.razor
  6. 46
      Client/Pages/Dashboard/Elements/ChartElement.razor.cs
  7. 1
      Client/_Imports.razor
  8. 2
      Client/wwwroot/index.html
  9. 27
      Server/Controllers/AnalyticsController.cs
  10. 67
      Server/Services/AnalyticalService.cs
  11. 15
      Shared/CustomModels/WeeklySaleItem.cs
  12. 17
      Shared/Interfaces/IAnalytics.cs
  13. 2
      Shared/ServiceRepo/CalculatorService.cs

1
Client/Biskilog Accounting.Client.csproj

@ -19,6 +19,7 @@
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="7.0.5" /> <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.AspNetCore.Components.WebAssembly.DevServer" Version="7.0.5" PrivateAssets="all" />
<PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="7.0.0" />
<PackageReference Include="Blazor-ApexCharts" Version="0.9.21-beta" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

57
Client/Pages/Dashboard/Dashboard.razor

@ -12,7 +12,7 @@
<div class="col-lg-4 col-md-4 order-1"> <div class="col-lg-4 col-md-4 order-1">
<div class="row"> <div class="row">
<div class="col-lg-6 col-md-12 col-6 mb-4"> <div class="col-lg-6 col-md-12 col-6 mb-4">
<AnalyticsItemSmall Icon="../assets/img/icons/unicons/chart-success.png" Title="Transactions" Value="2000" Percentage="70"/> <AnalyticsItemSmall Icon="../assets/img/icons/unicons/chart-success.png" Title="Transactions" Value="@m_tradeSummary.CurrentTradeSales" Percentage="@(((m_tradeSummary.CurrentTradeSales-m_tradeSummary.LastTradeSales)/m_tradeSummary.LastTradeSales)*100)" />
</div> </div>
<div class="col-lg-6 col-md-12 col-6 mb-4"> <div class="col-lg-6 col-md-12 col-6 mb-4">
<AnalyticsItemSmall Icon="../assets/img/icons/unicons/wallet-info.png" Title="Cancelled Sales" Value="4679" Percentage="28.42" /> <AnalyticsItemSmall Icon="../assets/img/icons/unicons/wallet-info.png" Title="Cancelled Sales" Value="4679" Percentage="28.42" />
@ -21,64 +21,13 @@
</div> </div>
<!-- Total Revenue --> <!-- Total Revenue -->
<div class="col-12 col-lg-8 order-2 order-md-3 order-lg-2 mb-4"> <div class="col-12 col-lg-8 order-2 order-md-3 order-lg-2 mb-4">
<div class="card"> <ChartElement Series="@m_weeklySaleItems" Title="Weekly Revenue" SubTitle="Performance Trend" IsLoading="@loadWeeklySales" />
<div class="row row-bordered g-0">
<div class="col-md-8">
<h5 class="card-header m-0 me-2 pb-3">Total Revenue</h5>
<div id="totalRevenueChart" class="px-2"></div>
</div>
<div class="col-md-4">
<div class="card-body">
<div class="text-center">
<div class="dropdown">
<button class="btn btn-sm btn-outline-primary dropdown-toggle"
type="button"
id="growthReportId"
data-bs-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false">
2022
</button>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="growthReportId">
<a class="dropdown-item" href="javascript:void(0);">2021</a>
<a class="dropdown-item" href="javascript:void(0);">2020</a>
<a class="dropdown-item" href="javascript:void(0);">2019</a>
</div>
</div>
</div>
</div>
<div id="growthChart"></div>
<div class="text-center fw-semibold pt-3 mb-2">62% Company Growth</div>
<div class="d-flex px-xxl-4 px-lg-2 p-4 gap-xxl-3 gap-lg-1 gap-3 justify-content-between">
<div class="d-flex">
<div class="me-2">
<span class="badge bg-label-primary p-2"><i class="bx bx-dollar text-primary"></i></span>
</div>
<div class="d-flex flex-column">
<small>2022</small>
<h6 class="mb-0">$32.5k</h6>
</div>
</div>
<div class="d-flex">
<div class="me-2">
<span class="badge bg-label-info p-2"><i class="bx bx-wallet text-info"></i></span>
</div>
<div class="d-flex flex-column">
<small>2021</small>
<h6 class="mb-0">$41.2k</h6>
</div>
</div>
</div>
</div>
</div>
</div>
</div> </div>
<!--/ Total Revenue --> <!--/ Total Revenue -->
<div class="col-12 col-md-8 col-lg-4 order-3 order-md-2"> <div class="col-12 col-md-8 col-lg-4 order-3 order-md-2">
<div class="row"> <div class="row">
<div class="col-6 mb-4"> <div class="col-6 mb-4">
<AnalyticsItemSmall Icon="../assets/img/icons/unicons/paypal.png" Title="Out Of Stock" Value="2456" Percentage="-22" /> <AnalyticsItemSmall Icon="../assets/img/icons/unicons/paypal.png" Title="Credit Sales" Value="2456" Percentage="-22" />
</div> </div>
<div class="col-6 mb-4"> <div class="col-6 mb-4">
<AnalyticsItemSmall Icon="../assets/img/icons/unicons/cc-primary.png" Title="Transactions" Value="2456" Percentage="-22" /> <AnalyticsItemSmall Icon="../assets/img/icons/unicons/cc-primary.png" Title="Transactions" Value="2456" Percentage="-22" />

34
Client/Pages/Dashboard/Dashboard.razor.cs

@ -7,11 +7,14 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard
public partial class Dashboard public partial class Dashboard
{ {
private TradeSummary m_tradeSummary { get; set; } = new TradeSummary(); private TradeSummary m_tradeSummary { get; set; } = new TradeSummary();
private List<WeeklySaleItem> m_weeklySaleItems { get; set; } = new List<WeeklySaleItem> { };
private string m_username { get; set; } = string.Empty; private string m_username { get; set; } = string.Empty;
private bool loadWeeklySales = true;
protected override async Task OnInitializedAsync() protected override async Task OnInitializedAsync()
{ {
m_username = m_tokenService.GetUserNameFromToken(await m_tokenService.GetToken())!; m_username = m_tokenService.GetUserNameFromToken(await m_tokenService.GetToken())!;
await GetSummary(); await GetTradeSummary();
await GetWeeklySales();
return; return;
} }
@ -19,7 +22,7 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard
/// Gets the tade summary /// Gets the tade summary
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
async Task GetSummary() async Task GetTradeSummary()
{ {
try try
{ {
@ -32,7 +35,32 @@ namespace Biskilog_Accounting.Client.Pages.Dashboard
m_tradeSummary = tradeSummary; m_tradeSummary = tradeSummary;
StateHasChanged(); StateHasChanged();
} }
}catch (Exception ex) }
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
/// <summary>
/// Gets the tade summary
/// </summary>
/// <returns></returns>
async Task GetWeeklySales()
{
try
{
var response = await m_http.GetAsync("api/analytics/sales/weekly");
if (response.IsSuccessStatusCode)
{
var jsonContent = await response.Content.ReadAsStringAsync();
var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true };
var sales = JsonSerializer.Deserialize<List<WeeklySaleItem>>(jsonContent, options);
m_weeklySaleItems = sales;
loadWeeklySales = false;
StateHasChanged();
}
}
catch (Exception ex)
{ {
Console.WriteLine(ex.Message); Console.WriteLine(ex.Message);
} }

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

@ -1,4 +1,7 @@
<div class="card"> @using Biskilog_Accounting.Shared.Interfaces
@inject ICalculator m_calculator
<div class="card">
<div class="card-body"> <div class="card-body">
<div class="card-title d-flex align-items-start justify-content-between"> <div class="card-title d-flex align-items-start justify-content-between">
<div class="avatar flex-shrink-0"> <div class="avatar flex-shrink-0">
@ -22,7 +25,7 @@
</div> </div>
</div> </div>
<span class="fw-semibold d-block mb-1">@Title</span> <span class="fw-semibold d-block mb-1">@Title</span>
<h3 class="card-title mb-2">@Value</h3> <h6 class="card-title mb-2">@(m_calculator.FormatMoneyWithCurrency(Value))</h6>
<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> <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.ToString("0.00")) %</small>
</div> </div>
</div> </div>

58
Client/Pages/Dashboard/Elements/ChartElement.razor

@ -0,0 +1,58 @@
@using Biskilog_Accounting.Shared.CustomModels;
@using Biskilog_Accounting.Shared.Interfaces;
@inject ICalculator m_calculator
@if (!IsLoading)
{
<div class="card">
<div class="row row-bordered g-0">
<div class="col-md-8">
<h5 class="card-header m-0 me-2 pb-3">@Title</h5>
<ApexChart TItem="WeeklySaleItem" Options=m_options>
<ApexPointSeries TItem="WeeklySaleItem"
Items="@Series"
Name="Sales"
YValue="@(e => e.Total)"
XValue="@(e => e.Date.ToShortDateString())"
SeriesType="SeriesType.Bar" />
</ApexChart>
</div>
<div class="col-md-4">
<div class="text-center fw-semibold pt-3 mb-2">@SubTitle</div>
<ApexChart TItem="WeeklySaleItem">
<ApexPointSeries TItem="WeeklySaleItem"
Items="@Series"
Name="Sales"
YValue="@(e => e.Total)"
XValue="@(e => e.Date.Day)"
SeriesType="SeriesType.Area" />
</ApexChart>
<div class="d-flex px-xxl-4 px-lg-2 p-4 gap-xxl-3 gap-lg-1 gap-3 justify-content-between">
<div class="d-flex">
<div class="me-2">
<span class="badge bg-label-primary p-2"><i class="bx bx-dollar text-primary"></i></span>
</div>
<div class="d-flex flex-column">
<small>Best Day</small>
<small class="mb-0">@m_bestDay</small>
<small class="mb-0">@m_bestSales</small>
</div>
</div>
<div class="d-flex">
<div class="me-2">
<span class="badge bg-label-info p-2"><i class="bx bx-wallet text-info"></i></span>
</div>
<div class="d-flex flex-column">
<small>Worst Day</small>
<small class="mb-0">@m_worstDay</small>
<small class="mb-0">@m_worstSales</small>
</div>
</div>
</div>
</div>
</div>
</div>
}else{
}

46
Client/Pages/Dashboard/Elements/ChartElement.razor.cs

@ -0,0 +1,46 @@
using ApexCharts;
using Biskilog_Accounting.Shared.CustomModels;
using Microsoft.AspNetCore.Components;
namespace Biskilog_Accounting.Client.Pages.Dashboard.Elements
{
public partial class ChartElement
{
[Parameter]
public bool IsLoading { get; set; }
[Parameter]
public string Title { get; set; } = string.Empty;
[Parameter]
public string SubTitle { get; set; } = string.Empty;
[Parameter]
public List<WeeklySaleItem> Series { get; set; } = new List<WeeklySaleItem>();
private ApexChartOptions<WeeklySaleItem> m_options;
private string m_bestDay { get; set; } = string.Empty;
private string m_worstDay { get; set; } = string.Empty;
private string m_bestSales { get; set; } = string.Empty;
private string m_worstSales { get; set; } = string.Empty;
protected override void OnParametersSet()
{
m_options = new ApexChartOptions<WeeklySaleItem>
{
PlotOptions = new PlotOptions
{
Bar = new PlotOptionsBar
{
ColumnWidth = "7",
}
}
};
if (Series.Count > 1)
{
m_bestDay = Series.OrderByDescending(t => t.Total).First().Date.ToShortDateString();
m_bestSales = m_calculator.FormatMoneyWithCurrency((double)Series.OrderByDescending(t => t.Total).First().Total);
m_worstDay = Series.OrderByDescending(t => t.Total).Last().Date.ToShortDateString();
m_worstSales = m_calculator.FormatMoneyWithCurrency((double)Series.OrderByDescending(t => t.Total).Last().Total);
}
base.OnParametersSet();
}
}
}

1
Client/_Imports.razor

@ -5,3 +5,4 @@
@using Microsoft.AspNetCore.Components.WebAssembly.Http @using Microsoft.AspNetCore.Components.WebAssembly.Http
@using Microsoft.JSInterop @using Microsoft.JSInterop
@using Biskilog_Accounting.Client @using Biskilog_Accounting.Client
@using ApexCharts;

2
Client/wwwroot/index.html

@ -47,6 +47,8 @@
<a class="dismiss">🗙</a> <a class="dismiss">🗙</a>
</div> </div>
<script src="_framework/blazor.webassembly.js"></script> <script src="_framework/blazor.webassembly.js"></script>
<script src="_content/Blazor-ApexCharts/js/apex-charts.min.js"></script>
<script src="_content/Blazor-ApexCharts/js/blazor-apex-charts.js"></script>
<script>navigator.serviceWorker.register('service-worker.js');</script> <script>navigator.serviceWorker.register('service-worker.js');</script>
</body> </body>

27
Server/Controllers/AnalyticsController.cs

@ -41,6 +41,15 @@ namespace Biskilog_Accounting.Server.Controllers
return m_analyticService.GetSalesTransaction(a_start, a_end); return m_analyticService.GetSalesTransaction(a_start, a_end);
} }
/// <summary> /// <summary>
/// Endpoint to return analysis on Sales within a specified period
/// </summary>
[Authorize]
[HttpGet, Route("sales/weekly")]
public IEnumerable<WeeklySaleItem> GetWeeklySalesAsync()
{
return m_analyticService.GetWeeklySalesTransaction().OrderBy(a => a.Date);
}
/// <summary>
/// Endpoint to return analysis on in-debt customers /// Endpoint to return analysis on in-debt customers
/// </summary> /// </summary>
/// <param name="a_start"></param> /// <param name="a_start"></param>
@ -106,5 +115,23 @@ namespace Biskilog_Accounting.Server.Controllers
{ {
return m_analyticService.GetTradeSummary(); return m_analyticService.GetTradeSummary();
} }
/// <summary>
/// Endpoint to return analysis on recent transactions
/// </summary>
[Authorize]
[HttpGet, Route("sales/recent/{a_limit}")]
public IEnumerable<Tblcart> GetRecentTransactions(int a_limit)
{
return m_analyticService.GetRecentSales(a_limit);
}
/// <summary>
/// Endpoint to return analysis on recent price changes
/// </summary>
[Authorize]
[HttpGet, Route("pricechanges/recent/{a_limit}")]
public IEnumerable<ProductPriceChange> GetRecentPricing(int a_limit)
{
return m_analyticService.GetRecentPriceChanges(a_limit);
}
} }
} }

67
Server/Services/AnalyticalService.cs

@ -177,6 +177,39 @@ namespace Biskilog_Accounting.Server.Services
}; };
} }
public IEnumerable<ProductPriceChange> GetRecentPriceChanges(int a_limit)
{
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token);
return (from change in m_context.Tblpricechanges
join p in m_context.Tblproducts on change.Pcode equals p.Pcode
where accessiblebranches.Contains(change.BranchId)
orderby change.ChangeDate descending
select new ProductPriceChange
{
BranchId = change.BranchId,
ChangeDate = change.ChangeDate,
Pcode = change.Pcode,
CountId = change.CountId,
CurrentPrice = change.CurrentPrice,
PreviousPrice = change.PreviousPrice,
ProductName = p.ProductName
}).Take(a_limit);
}
public IEnumerable<Tblcart> GetRecentSales(int a_limit)
{
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
if (AuthEnums.Valid == m_tokenService.ValidateToken(token))
{
IEnumerable<string> accessiblebranches = m_tokenService.BranchIds(token);
return m_context.Tblcarts.Where(t => accessiblebranches.Contains(t.BranchId)).OrderByDescending(t => t.Date).Take(a_limit);
}
return new List<Tblcart>();
}
public IEnumerable<Tblcart> GetSalesTransaction(DateTime a_start, DateTime a_end) public IEnumerable<Tblcart> GetSalesTransaction(DateTime a_start, DateTime a_end)
{ {
string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!; string token = m_httpContext.Request.Headers[HeaderNames.Authorization]!;
@ -196,8 +229,9 @@ namespace Biskilog_Accounting.Server.Services
using (var command = m_context.Database.GetDbConnection().CreateCommand()) using (var command = m_context.Database.GetDbConnection().CreateCommand())
{ {
command.CommandText = "CALL GetTradeSummary(@p0)"; command.CommandText = "CALL GetTradeSummary(@p0,@p1)";
command.Parameters.Add(new MySqlParameter("@p0", string.Join(", ", accessiblebranches.ToArray()))); command.Parameters.Add(new MySqlParameter("@p0", string.Join(", ", accessiblebranches.ToArray())));
command.Parameters.Add(new MySqlParameter("@p1", 2));
m_context.Database.OpenConnection(); m_context.Database.OpenConnection();
@ -226,5 +260,36 @@ namespace Biskilog_Accounting.Server.Services
} }
return new TradeSummary(); return new TradeSummary();
} }
public IEnumerable<WeeklySaleItem> GetWeeklySalesTransaction()
{
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,@p1)";
command.Parameters.Add(new MySqlParameter("@p0", string.Join(", ", accessiblebranches.ToArray())));
command.Parameters.Add(new MySqlParameter("@p1", 7));
m_context.Database.OpenConnection();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return new WeeklySaleItem()
{
Date = reader.GetDateTime(0),
Total = reader.GetDecimal(1)
};
}
}
}
}
}
} }
} }

15
Shared/CustomModels/WeeklySaleItem.cs

@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Biskilog_Accounting.Shared.CustomModels
{
public class WeeklySaleItem
{
public DateTime Date { get; set; }
public decimal? Total { get; set; }
public string BranchId { get; set; } = null!;
}
}

17
Shared/Interfaces/IAnalytics.cs

@ -13,6 +13,11 @@ namespace Biskilog_Accounting.Shared.Interfaces
/// <returns></returns> /// <returns></returns>
IEnumerable<Tblcart> GetSalesTransaction(DateTime a_start, DateTime a_end); IEnumerable<Tblcart> GetSalesTransaction(DateTime a_start, DateTime a_end);
/// <summary> /// <summary>
/// Fetches a collection of sales transaction made within a one week period
/// </summary>
/// <returns></returns>
IEnumerable<WeeklySaleItem> GetWeeklySalesTransaction();
/// <summary>
/// Fetches a collection of in-debt customers /// Fetches a collection of in-debt customers
/// <returns></returns> /// <returns></returns>
IEnumerable<InDebtCustomers> GetInDebtCustomers(); IEnumerable<InDebtCustomers> GetInDebtCustomers();
@ -54,5 +59,17 @@ namespace Biskilog_Accounting.Shared.Interfaces
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
TradeSummary GetTradeSummary(); TradeSummary GetTradeSummary();
/// <summary>
/// Fetches the most recent sales transactions
/// </summary>
/// <param name="a_limit">The number of rows to return </param>
/// <returns></returns>
IEnumerable<Tblcart> GetRecentSales(int a_limit);
/// <summary>
/// Fetches a collection of product price changes recently made
/// </summary>
/// <param name="a_limit">the number of rows to return</param>
/// <returns></returns>
IEnumerable<ProductPriceChange> GetRecentPriceChanges(int a_limit);
} }
} }

2
Shared/ServiceRepo/CalculatorService.cs

@ -31,7 +31,7 @@ namespace Biskilog_Accounting.Shared.ServiceRepo
NumberFormatInfo numberFormatInfo = new CultureInfo(locale).NumberFormat; NumberFormatInfo numberFormatInfo = new CultureInfo(locale).NumberFormat;
// Set the currency symbol to Ghanaian cedi // Set the currency symbol to Ghanaian cedi
numberFormatInfo.CurrencySymbol = "GH₵"; numberFormatInfo.CurrencySymbol = "GH₵ ";
return numberFormatInfo; return numberFormatInfo;
} }

Loading…
Cancel
Save