Addet ToDaySales page
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
<FluentButton Class="menu-button" Size="ButtonSize.Large">
|
||||
Kassesalg
|
||||
</FluentButton>
|
||||
<FluentButton Class="menu-button" Size="ButtonSize.Large">
|
||||
<FluentButton Class="menu-button" Size="ButtonSize.Large" onclick="@(() => Nav.NavigateTo("/todaySales"))">
|
||||
Dagens salg
|
||||
</FluentButton>
|
||||
<FluentButton Class="menu-button" Size="ButtonSize.Large">
|
||||
@@ -18,4 +18,4 @@
|
||||
<FluentButton Class="menu-button" Size="ButtonSize.Large" onclick="@(() => Nav.NavigateTo("/settings"))">
|
||||
Indstillinger
|
||||
</FluentButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
.home-container {
|
||||
width: min(100%, 1100px);
|
||||
margin: 32px auto;
|
||||
@@ -11,4 +12,12 @@
|
||||
min-height: 220px;
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.home-container {
|
||||
grid-template-columns: 1fr;
|
||||
margin: 24px 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,16 +5,14 @@
|
||||
<Header />
|
||||
<div class="settings-page">
|
||||
<h1>Indstillinger</h1>
|
||||
|
||||
<p>Administrer data, der bruges i kassesalget.</p>
|
||||
|
||||
<div class="settings-grid">
|
||||
<FluentButton Class="settings-button" onclick="@(() => Nav.NavigateTo("/settings/employees"))">
|
||||
Medarbejder
|
||||
</FluentButton>
|
||||
|
||||
<FluentButton Class="settings-button">
|
||||
Vare
|
||||
</FluentButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
|
||||
.settings-page {
|
||||
width: min(100%, 1100px);
|
||||
margin: 0 auto;
|
||||
@@ -22,3 +23,14 @@
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.settings-page {
|
||||
padding: 24px 16px;
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
@page "/todaySales"
|
||||
@rendermode InteractiveServer
|
||||
@inject IJSRuntime Js
|
||||
@inject SaleService SaleService
|
||||
|
||||
<Header />
|
||||
<div class="page-container today-sales-page">
|
||||
<div class="filters">
|
||||
<label class="filter-control"><span>Dato</span><span class="filter-box"><svg viewBox="0 0 24 24"><rect x="3.5" y="5.5" width="17" height="15" rx="2"/><path d="M7 3.5v4M17 3.5v4M3.5 9.5h17"/></svg><input type="date" value="@_date.ToString("yyyy-MM-dd")" @onchange="DateChanged"/></span></label>
|
||||
<label class="filter-control short"><span>Periode</span><span class="filter-box"><svg viewBox="0 0 24 24"><rect x="3.5" y="5.5" width="17" height="15" rx="2"/><path d="M7 3.5v4M17 3.5v4M3.5 9.5h17"/></svg><select @onchange="RangeChanged" value="@_range"><option value="1">1 dag</option><option value="7">7 dage</option><option value="30">30 dage</option></select></span></label>
|
||||
</div>
|
||||
<section class="ui-card summary-card">
|
||||
<div class="summary-icon"><svg viewBox="0 0 64 64"><path d="M12 52h40M18 48V35h8v13M29 48V25h8v23M40 48V14h8v34"/></svg></div>
|
||||
<div><p>Dagens salg</p><strong>@Currency(_summary.Total)</strong><small>@_summary.SaleCount salg</small></div>
|
||||
</section>
|
||||
<div class="sales-grid">
|
||||
<UiCard Title="Salg pr. varegruppe" Class="sales-category-card">
|
||||
<MetricList TItem="Metric" Items="@_summary.Categories">
|
||||
<HeaderContent><span>Varegruppe</span><span>Salg</span></HeaderContent>
|
||||
<RowTemplate Context="row"><div class="metric-row"><span>@row.Name</span><span>@Currency(row.Amount)</span></div></RowTemplate>
|
||||
</MetricList>
|
||||
</UiCard>
|
||||
<div class="sales-right">
|
||||
<UiCard Title="Betalingsfordeling">
|
||||
<MetricList TItem="Metric" Items="@_summary.Payments">
|
||||
<RowTemplate Context="row"><div class="metric-row"><span>@row.Name</span><span>@Currency(row.Amount)</span></div></RowTemplate>
|
||||
</MetricList>
|
||||
</UiCard>
|
||||
<button class="print-button" type="button" @onclick="PrintAsync"><svg viewBox="0 0 24 24"><path d="M6 9V3h12v6M6 18H4a2 2 0 0 1-2-2v-5a2 2 0 0 1 2-2h16a2 2 0 0 1 2 2v5a2 2 0 0 1-2 2h-2M6 14h12v7H6z"/></svg>Print salg</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.JSInterop;
|
||||
using Pos.Models;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Pos.Web.Components.Pages.TodaySales;
|
||||
|
||||
public partial class TodaySales
|
||||
{
|
||||
private static readonly CultureInfo Danish = CultureInfo.GetCultureInfo("da-DK");
|
||||
private TotalSaleDetail? _totalSaleDetail;
|
||||
private DateTime _date = DateTime.Now;
|
||||
private string _range = "1";
|
||||
private Summary _summary = new(0m, 0, Array.Empty<Metric>(), Array.Empty<Metric>());
|
||||
|
||||
private async Task DateChanged(ChangeEventArgs e)
|
||||
{
|
||||
if (DateTime.TryParse(e.Value?.ToString(), out var date))
|
||||
{
|
||||
_date = date;
|
||||
await LoadAsync();
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
private async Task RangeChanged(ChangeEventArgs e)
|
||||
{
|
||||
_range = e.Value?.ToString() ?? "1";
|
||||
await LoadAsync();
|
||||
}
|
||||
|
||||
private Task LoadAsync()
|
||||
{
|
||||
var result = SaleService.TotalSale(_date);
|
||||
_totalSaleDetail = result;
|
||||
|
||||
_summary = new Summary(
|
||||
result.TotalSale,
|
||||
result.TotalCustomer,
|
||||
result.TotalSaleCategories
|
||||
.Select(category => new Metric(category.Category, category.Sale))
|
||||
.ToList(),
|
||||
result.PaymentBreakdown
|
||||
.Select(payment => new Metric(payment.PaymentMethod, payment.Amount))
|
||||
.ToList());
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private async Task PrintAsync()
|
||||
{
|
||||
// TODO: Kobl til SaleService.PrintSaleOfDay(_date, _totalSaleDetail)
|
||||
// eller POST /api/sales/print.
|
||||
await Js.InvokeVoidAsync("window.print");
|
||||
}
|
||||
|
||||
private static string Currency(decimal amount) =>
|
||||
$"{amount.ToString("N2", Danish)} kr.";
|
||||
|
||||
private sealed record Metric(string Name, decimal Amount);
|
||||
|
||||
private sealed record Summary(
|
||||
decimal Total,
|
||||
int SaleCount,
|
||||
IReadOnlyList<Metric> Categories,
|
||||
IReadOnlyList<Metric> Payments);
|
||||
}
|
||||
@@ -0,0 +1,307 @@
|
||||
|
||||
.today-sales-page {
|
||||
width: min(100%, 1420px);
|
||||
min-height: calc(100dvh - 78px);
|
||||
margin: 0 auto;
|
||||
padding: 38px 32px 64px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
align-items: end;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.filter-control {
|
||||
display: flex;
|
||||
min-width: 230px;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
color: #3e444d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.filter-control.short {
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.filter-box {
|
||||
display: flex;
|
||||
min-height: 56px;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid #bfc5cd;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.filter-box svg {
|
||||
width: 23px;
|
||||
height: 23px;
|
||||
flex: none;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 1.7;
|
||||
}
|
||||
|
||||
.filter-box input,
|
||||
.filter-box select {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
background: transparent;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
display: flex;
|
||||
min-height: 170px;
|
||||
align-items: center;
|
||||
gap: 32px;
|
||||
padding: 28px 34px;
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
display: grid;
|
||||
width: 114px;
|
||||
height: 114px;
|
||||
flex: none;
|
||||
place-items: center;
|
||||
border-radius: 50%;
|
||||
color: var(--pos-blue);
|
||||
background: var(--pos-blue-soft);
|
||||
}
|
||||
|
||||
.summary-icon svg {
|
||||
width: 58px;
|
||||
height: 58px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.summary-card p {
|
||||
margin: 0 0 4px;
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
.summary-card strong {
|
||||
display: block;
|
||||
font-size: 48px;
|
||||
line-height: 1.05;
|
||||
letter-spacing: -.035em;
|
||||
}
|
||||
|
||||
.summary-card small {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
color: var(--pos-muted);
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.sales-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.25fr) minmax(350px, 1fr);
|
||||
gap: 20px;
|
||||
align-items: start;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.sales-right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
::deep .ui-card {
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--pos-border);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
box-shadow: 0 1px 2px rgba(16, 24, 40, .03);
|
||||
}
|
||||
|
||||
::deep .ui-card__header {
|
||||
padding: 26px 32px 16px;
|
||||
}
|
||||
|
||||
::deep .ui-card__header h2 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
::deep .ui-card__body {
|
||||
padding: 0 32px 28px;
|
||||
}
|
||||
|
||||
::deep .metric-list__header,
|
||||
::deep .metric-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
::deep .metric-list__header {
|
||||
padding-bottom: 12px;
|
||||
color: var(--pos-muted);
|
||||
}
|
||||
|
||||
::deep .metric-row {
|
||||
min-height: 58px;
|
||||
align-items: center;
|
||||
border-top: 1px solid var(--pos-border-soft);
|
||||
font-size: 19px;
|
||||
}
|
||||
|
||||
::deep .metric-row > :last-child,
|
||||
::deep .metric-list__header > :last-child {
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.print-button {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-height: 70px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 14px;
|
||||
border: 1.5px solid var(--pos-blue);
|
||||
border-radius: 9px;
|
||||
color: var(--pos-blue);
|
||||
background: #fff;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.print-button:hover {
|
||||
background: var(--pos-blue-soft);
|
||||
}
|
||||
|
||||
.print-button svg {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
stroke-width: 1.8;
|
||||
}
|
||||
|
||||
@media (min-width: 900px) and (max-height: 850px) {
|
||||
.today-sales-page {
|
||||
padding-top: 22px;
|
||||
padding-bottom: 18px;
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.filter-box {
|
||||
min-height: 48px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
min-height: 130px;
|
||||
gap: 24px;
|
||||
padding: 20px 28px;
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
width: 88px;
|
||||
height: 88px;
|
||||
}
|
||||
|
||||
.summary-icon svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
|
||||
.summary-card strong {
|
||||
font-size: 40px;
|
||||
}
|
||||
|
||||
.summary-card small {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.sales-grid {
|
||||
gap: 20px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
::deep .ui-card__header {
|
||||
padding: 18px 28px 10px;
|
||||
}
|
||||
|
||||
::deep .ui-card__body {
|
||||
padding: 0 28px 16px;
|
||||
}
|
||||
|
||||
::deep .metric-list__header {
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
::deep .metric-row {
|
||||
min-height: 39px;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.print-button {
|
||||
min-height: 54px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.sales-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.today-sales-page {
|
||||
padding: 24px 16px 40px;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
gap: 18px;
|
||||
padding: 22px 20px;
|
||||
}
|
||||
|
||||
.summary-icon {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
}
|
||||
|
||||
.summary-icon svg {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.summary-card strong {
|
||||
font-size: 34px;
|
||||
}
|
||||
|
||||
::deep .ui-card__header {
|
||||
padding: 22px 20px 14px;
|
||||
}
|
||||
|
||||
::deep .ui-card__body {
|
||||
padding: 0 20px 22px;
|
||||
}
|
||||
|
||||
::deep .metric-row {
|
||||
font-size: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,4 +9,4 @@
|
||||
"dddd d. MMMM yyyy · HH:mm",
|
||||
_danishCulture)
|
||||
</time>
|
||||
</header>
|
||||
</header>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Globalization;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Pos.Web.Components.Shared
|
||||
{
|
||||
|
||||
10
PointOfSale/Pos.Web/Components/Shared/MetricList.razor
Normal file
10
PointOfSale/Pos.Web/Components/Shared/MetricList.razor
Normal file
@@ -0,0 +1,10 @@
|
||||
@typeparam TItem
|
||||
<div class="metric-list">
|
||||
@if (HeaderContent is not null) { <div class="metric-list__header">@HeaderContent</div> }
|
||||
@foreach (var item in Items) { @RowTemplate(item) }
|
||||
</div>
|
||||
@code {
|
||||
[Parameter] public IReadOnlyList<TItem> Items { get; set; } = Array.Empty<TItem>();
|
||||
[Parameter] public RenderFragment? HeaderContent { get; set; }
|
||||
[Parameter] public RenderFragment<TItem> RowTemplate { get; set; } = default!;
|
||||
}
|
||||
12
PointOfSale/Pos.Web/Components/Shared/UiCard.razor
Normal file
12
PointOfSale/Pos.Web/Components/Shared/UiCard.razor
Normal file
@@ -0,0 +1,12 @@
|
||||
<section class="ui-card @Class">
|
||||
@if (!string.IsNullOrWhiteSpace(Title))
|
||||
{
|
||||
<div class="ui-card__header"><h2>@Title</h2></div>
|
||||
}
|
||||
<div class="ui-card__body">@ChildContent</div>
|
||||
</section>
|
||||
@code {
|
||||
[Parameter] public string Title { get; set; } = string.Empty;
|
||||
[Parameter] public string Class { get; set; } = string.Empty;
|
||||
[Parameter] public RenderFragment? ChildContent { get; set; }
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
@using System.Net.Http
|
||||
@using System.Net.Http.Json
|
||||
@using Microsoft.AspNetCore.Components
|
||||
@using Microsoft.AspNetCore.Components.Forms
|
||||
@using Microsoft.AspNetCore.Components.Routing
|
||||
@using Microsoft.AspNetCore.Components.Web
|
||||
@@ -11,4 +12,7 @@
|
||||
@using Pos.Web
|
||||
@using Pos.Web.Components
|
||||
@using Pos.Web.Components.Layout
|
||||
@using Pos.Web.Components.Shared
|
||||
@using Pos.Web.Components.Shared
|
||||
@using Pos.Service
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user