Compare commits
2 Commits
feature/bl
...
layout
| Author | SHA1 | Date | |
|---|---|---|---|
| 4623750255 | |||
| bde55cf8d5 |
@@ -11,6 +11,7 @@ namespace Pos.Models
|
|||||||
public string Name { get; set; } = string.Empty;
|
public string Name { get; set; } = string.Empty;
|
||||||
public int NumberProducts { get; set; }
|
public int NumberProducts { get; set; }
|
||||||
public decimal Price { get; set; }
|
public decimal Price { get; set; }
|
||||||
|
public decimal VatRate { get; set; }
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public bool IsProductGroup { get; set; }
|
public bool IsProductGroup { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,7 +48,8 @@ namespace Pos.Service
|
|||||||
string name,
|
string name,
|
||||||
int numberOfProducts,
|
int numberOfProducts,
|
||||||
decimal price,
|
decimal price,
|
||||||
bool isProductGroup)
|
bool isProductGroup,
|
||||||
|
decimal vatRate = 25m)
|
||||||
{
|
{
|
||||||
if (id <= 0)
|
if (id <= 0)
|
||||||
throw new ArgumentOutOfRangeException(
|
throw new ArgumentOutOfRangeException(
|
||||||
@@ -62,12 +63,18 @@ namespace Pos.Service
|
|||||||
|
|
||||||
ValidatePriceAndQuantity(price, numberOfProducts);
|
ValidatePriceAndQuantity(price, numberOfProducts);
|
||||||
|
|
||||||
|
if (vatRate < 0 || vatRate > 100)
|
||||||
|
throw new ArgumentOutOfRangeException(
|
||||||
|
nameof(vatRate),
|
||||||
|
"Momsen skal være mellem 0 og 100 procent.");
|
||||||
|
|
||||||
PriceLineModel saleLine = new PriceLineModel
|
PriceLineModel saleLine = new PriceLineModel
|
||||||
{
|
{
|
||||||
Id = id,
|
Id = id,
|
||||||
Name = name,
|
Name = name,
|
||||||
NumberProducts = numberOfProducts,
|
NumberProducts = numberOfProducts,
|
||||||
Price = price,
|
Price = price,
|
||||||
|
VatRate = vatRate,
|
||||||
IsProductGroup = isProductGroup
|
IsProductGroup = isProductGroup
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<div class="home-container">
|
<div class="home-container">
|
||||||
<FluentButton Class="menu-button" Size="ButtonSize.Large">
|
<FluentButton Class="menu-button" Size="ButtonSize.Large" onclick="@(() => Nav.NavigateTo("/sale"))">
|
||||||
Kassesalg
|
Kassesalg
|
||||||
</FluentButton>
|
</FluentButton>
|
||||||
<FluentButton Class="menu-button" Size="ButtonSize.Large" onclick="@(() => Nav.NavigateTo("/todaySales"))">
|
<FluentButton Class="menu-button" Size="ButtonSize.Large" onclick="@(() => Nav.NavigateTo("/todaySales"))">
|
||||||
|
|||||||
144
PointOfSale/Pos.Web/Components/Pages/Sale/Sale.razor
Normal file
144
PointOfSale/Pos.Web/Components/Pages/Sale/Sale.razor
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
@page "/sale"
|
||||||
|
@rendermode InteractiveServer
|
||||||
|
@using Database.Models
|
||||||
|
@using Pos.Models
|
||||||
|
@using Pos.Service
|
||||||
|
@inject EmployeeService EmployeeService
|
||||||
|
@inject ProductGroupService ProductGroupService
|
||||||
|
@inject SalesTransactionService TransactionService
|
||||||
|
@inject SaleService SaleService
|
||||||
|
@inject NavigationManager Nav
|
||||||
|
|
||||||
|
<main class="sale-page">
|
||||||
|
<section class="current-sale">
|
||||||
|
<div class="sale-title">
|
||||||
|
<button class="back-button"
|
||||||
|
type="button"
|
||||||
|
title="Tilbage til forsiden"
|
||||||
|
aria-label="Tilbage til forsiden"
|
||||||
|
@onclick="@(() => Nav.NavigateTo("/"))">←</button>
|
||||||
|
<h1>Aktuelt salg</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sale-lines-header sale-line-grid">
|
||||||
|
<span>Vare</span><span>Antal</span><span>Pris</span><span>Moms</span><span>I alt</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sale-lines">
|
||||||
|
@foreach (PriceLineModel line in TransactionService.SaleLines)
|
||||||
|
{
|
||||||
|
<div class="sale-line sale-line-grid">
|
||||||
|
<span>@line.Name</span>
|
||||||
|
<span>@line.NumberProducts</span>
|
||||||
|
<span>@Currency(line.Price)</span>
|
||||||
|
<span>@line.VatRate.ToString("0.##") %</span>
|
||||||
|
<span>@Currency(line.Price * line.NumberProducts)</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
@if (TransactionService.SaleLines.Count == 0)
|
||||||
|
{
|
||||||
|
<p class="empty-sale">Vælg en varegruppe og tilføj den til salget.</p>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="sale-summary">
|
||||||
|
<span>Total</span>
|
||||||
|
<strong>@Currency(TransactionService.TotalPrice)</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="registered-payments">
|
||||||
|
<h2>Registrerede betalinger</h2>
|
||||||
|
@if (_payments.Count == 0)
|
||||||
|
{
|
||||||
|
<p class="empty-payments">Der er endnu ikke registreret betalinger.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@foreach (RegisteredPayment payment in _payments)
|
||||||
|
{
|
||||||
|
<div class="payment-row">
|
||||||
|
<span class="payment-check">✓</span>
|
||||||
|
<span>@payment.Method</span>
|
||||||
|
<span>@Currency(payment.Amount)</span>
|
||||||
|
<time>@payment.Time.ToString("HH:mm")</time>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="finish-button" type="button"
|
||||||
|
disabled="@(!TransactionService.IsFullyPaid || _selectedEmployeeId <= 0)"
|
||||||
|
@onclick="CompleteSale">
|
||||||
|
Afslut salg
|
||||||
|
</button>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="sale-controls">
|
||||||
|
<div class="selection-panel">
|
||||||
|
<label class="employee-field">
|
||||||
|
<span>Medarbejder</span>
|
||||||
|
<select @bind="_selectedEmployeeId">
|
||||||
|
<option value="0">Vælg medarbejder</option>
|
||||||
|
@foreach (EmployeeEntity employee in _employees)
|
||||||
|
{
|
||||||
|
<option value="@employee.Id">@employee.Name</option>
|
||||||
|
}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<h2>Varegruppe</h2>
|
||||||
|
<div class="product-groups">
|
||||||
|
@foreach (ProductGroupEntity productGroup in _productGroups)
|
||||||
|
{
|
||||||
|
<button type="button"
|
||||||
|
class="product-group-button @(productGroup.Id == _selectedProductGroupId ? "selected" : null)"
|
||||||
|
disabled="@(_selectedEmployeeId <= 0)"
|
||||||
|
@onclick="@(() => SelectProductGroup(productGroup.Id))">
|
||||||
|
@productGroup.Name
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="manual-product">
|
||||||
|
<h2>Tilføj vare manuelt</h2>
|
||||||
|
<div class="manual-grid">
|
||||||
|
<label><span>Antal</span>
|
||||||
|
<input type="number" min="1" step="1" @bind="_quantity" />
|
||||||
|
</label>
|
||||||
|
<label><span>Pris</span>
|
||||||
|
<div class="price-input"><input type="number" min="0" step="0.01" @bind="_price" /><span>kr.</span></div>
|
||||||
|
</label>
|
||||||
|
<label><span>Linjetotal</span>
|
||||||
|
<output>@Currency(LineTotal)</output>
|
||||||
|
</label>
|
||||||
|
<button class="add-button" type="button" disabled="@(!CanAddLine)" @onclick="AddSaleLine">Tilføj</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="payment-panel">
|
||||||
|
<h2>Betaling</h2>
|
||||||
|
<label class="paid-amount"><span>Betalt beløb</span>
|
||||||
|
<div><input type="number" min="0" step="0.01" placeholder="@RemainingAmountText" @bind="_paymentAmount" /><span>kr.</span></div>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="payment-buttons">
|
||||||
|
<button type="button" disabled="@(TransactionService.TotalPrice <= 0)" @onclick="@(() => AddPayment(PaymentMethod.Cash))">▣ <span>Kontant</span></button>
|
||||||
|
<button type="button" disabled="@(TransactionService.TotalPrice <= 0)" @onclick="@(() => AddPayment(PaymentMethod.Card))">▤ <span>Kort</span></button>
|
||||||
|
<button type="button" disabled="@(TransactionService.TotalPrice <= 0)" @onclick="@(() => AddPayment(PaymentMethod.MobilePay))">▯ <span>MobilePay</span></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="change-row @(TransactionService.Change > 0 ? "has-change" : null)">
|
||||||
|
<span>Byttepenge</span><strong>@Currency(TransactionService.Change)</strong>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="print-button" type="button" @onclick="PrintReceipt">▣ <span>Print bon</span></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_message))
|
||||||
|
{
|
||||||
|
<p class="sale-message @(_isError ? "error" : "success")" role="status">@_message</p>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
148
PointOfSale/Pos.Web/Components/Pages/Sale/Sale.razor.cs
Normal file
148
PointOfSale/Pos.Web/Components/Pages/Sale/Sale.razor.cs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Database.Models;
|
||||||
|
using Pos.Models;
|
||||||
|
|
||||||
|
namespace Pos.Web.Components.Pages.Sale
|
||||||
|
{
|
||||||
|
public partial class Sale
|
||||||
|
{
|
||||||
|
private static readonly CultureInfo Danish = CultureInfo.GetCultureInfo("da-DK");
|
||||||
|
private List<EmployeeEntity> _employees = [];
|
||||||
|
private List<ProductGroupEntity> _productGroups = [];
|
||||||
|
private readonly List<RegisteredPayment> _payments = [];
|
||||||
|
private int _selectedEmployeeId;
|
||||||
|
private int _selectedProductGroupId;
|
||||||
|
private int _quantity = 1;
|
||||||
|
private decimal? _price;
|
||||||
|
private decimal? _paymentAmount;
|
||||||
|
private string _message = string.Empty;
|
||||||
|
private bool _isError;
|
||||||
|
|
||||||
|
private decimal LineTotal => (_price ?? 0) * _quantity;
|
||||||
|
private bool CanAddLine => _selectedProductGroupId > 0 && _quantity > 0 && _price >= 0;
|
||||||
|
private string RemainingAmountText => TransactionService.RemainingAmount.ToString("0.00", Danish);
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
_employees = await EmployeeService.GetEmployeesAsync();
|
||||||
|
_productGroups = ProductGroupService.GetProductGroups();
|
||||||
|
_selectedEmployeeId = _employees.FirstOrDefault()?.Id ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SelectProductGroup(int productGroupId)
|
||||||
|
{
|
||||||
|
_selectedProductGroupId = productGroupId;
|
||||||
|
ClearMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddSaleLine()
|
||||||
|
{
|
||||||
|
ProductGroupEntity? productGroup = _productGroups
|
||||||
|
.FirstOrDefault(item => item.Id == _selectedProductGroupId);
|
||||||
|
|
||||||
|
if (productGroup == null || !CanAddLine)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TransactionService.AddSaleLine(
|
||||||
|
productGroup.Id,
|
||||||
|
productGroup.Name,
|
||||||
|
_quantity,
|
||||||
|
_price ?? 0,
|
||||||
|
true,
|
||||||
|
productGroup.VatRate);
|
||||||
|
|
||||||
|
_quantity = 1;
|
||||||
|
_price = null;
|
||||||
|
_selectedProductGroupId = 0;
|
||||||
|
ClearMessage();
|
||||||
|
}
|
||||||
|
catch (ArgumentException exception)
|
||||||
|
{
|
||||||
|
ShowError(exception.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddPayment(PaymentMethod paymentMethod)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AmountGridModel payment = TransactionService.AddPayment(paymentMethod, _paymentAmount);
|
||||||
|
_payments.Add(new RegisteredPayment(payment.PaymentMethodText, payment.Amount, DateTime.Now));
|
||||||
|
_paymentAmount = null;
|
||||||
|
ClearMessage();
|
||||||
|
}
|
||||||
|
catch (Exception exception) when (exception is ArgumentException or InvalidOperationException)
|
||||||
|
{
|
||||||
|
ShowError(exception.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CompleteSale()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
TransactionService.EnsureCanComplete();
|
||||||
|
SaleService.Save(
|
||||||
|
TransactionService.SaleLines.Select(ToSaleGridModel).ToList(),
|
||||||
|
TransactionService.Payments.ToList(),
|
||||||
|
_selectedEmployeeId);
|
||||||
|
|
||||||
|
TransactionService.Reset();
|
||||||
|
_payments.Clear();
|
||||||
|
_selectedProductGroupId = 0;
|
||||||
|
_paymentAmount = null;
|
||||||
|
_message = "Salget er afsluttet og gemt.";
|
||||||
|
_isError = false;
|
||||||
|
}
|
||||||
|
catch (Exception exception) when (exception is ArgumentException or InvalidOperationException)
|
||||||
|
{
|
||||||
|
ShowError(exception.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PrintReceipt()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
SaleService.PrintReceipt();
|
||||||
|
_message = "Bonen er sendt til printeren.";
|
||||||
|
_isError = false;
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
ShowError(exception.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SaleGridModel ToSaleGridModel(PriceLineModel line)
|
||||||
|
{
|
||||||
|
return new SaleGridModel
|
||||||
|
{
|
||||||
|
Navn = line.Name,
|
||||||
|
Stk = line.NumberProducts.ToString(Danish),
|
||||||
|
Pris = Currency(line.Price),
|
||||||
|
Total = Currency(line.Price * line.NumberProducts),
|
||||||
|
SaleGridInternalModel = new SaleGridInternalModel
|
||||||
|
{
|
||||||
|
InternPrice = line.Price,
|
||||||
|
InternPieces = line.NumberProducts
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string Currency(decimal amount) =>
|
||||||
|
$"{amount.ToString("N2", Danish)} kr.";
|
||||||
|
|
||||||
|
private void ClearMessage() => _message = string.Empty;
|
||||||
|
|
||||||
|
private void ShowError(string message)
|
||||||
|
{
|
||||||
|
_message = message;
|
||||||
|
_isError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed record RegisteredPayment(string Method, decimal Amount, DateTime Time);
|
||||||
|
}
|
||||||
|
}
|
||||||
569
PointOfSale/Pos.Web/Components/Pages/Sale/Sale.razor.css
Normal file
569
PointOfSale/Pos.Web/Components/Pages/Sale/Sale.razor.css
Normal file
@@ -0,0 +1,569 @@
|
|||||||
|
.sale-page {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 100;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(420px, .92fr) minmax(560px, 1.28fr);
|
||||||
|
box-sizing: border-box;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100dvh;
|
||||||
|
min-height: 720px;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border-top: 1px solid var(--colorNeutralStroke2);
|
||||||
|
background: var(--colorNeutralBackground1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-sale,
|
||||||
|
.sale-controls {
|
||||||
|
box-sizing: border-box;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-sale {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 24px;
|
||||||
|
border-right: 1px solid var(--colorNeutralStroke2);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
flex: 0 0 44px;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid var(--colorNeutralStroke1);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button:hover {
|
||||||
|
background: var(--colorNeutralBackground1Hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-button:focus-visible {
|
||||||
|
outline: 2px solid var(--colorStrokeFocus2);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-line-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 50px 82px 58px 90px;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-lines-header {
|
||||||
|
padding: 0 8px 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-lines-header span:not(:first-child),
|
||||||
|
.sale-line span:not(:first-child) {
|
||||||
|
text-align: right;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-line-grid > span {
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-line > span:first-child {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-lines {
|
||||||
|
min-height: 280px;
|
||||||
|
max-height: 42dvh;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
border-top: 1px solid var(--colorNeutralStroke2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-line {
|
||||||
|
min-height: 50px;
|
||||||
|
padding: 0 8px;
|
||||||
|
border-bottom: 1px solid var(--colorNeutralStroke2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-sale,
|
||||||
|
.empty-payments {
|
||||||
|
color: var(--colorNeutralForeground3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-sale {
|
||||||
|
margin: 30px 8px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 20px 8px;
|
||||||
|
border-top: 1px solid var(--colorNeutralStroke2);
|
||||||
|
border-bottom: 1px solid var(--colorNeutralStroke2);
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-summary strong {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registered-payments {
|
||||||
|
margin-top: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 24px 1fr 120px 60px;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 52px;
|
||||||
|
padding: 0 12px;
|
||||||
|
border: 1px solid var(--colorNeutralStroke2);
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-row:last-child {
|
||||||
|
border-bottom: 1px solid var(--colorNeutralStroke2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-row > :nth-child(3),
|
||||||
|
.payment-row time {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-check {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border: 1px solid #16a34a;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #16a34a;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finish-button {
|
||||||
|
width: 100%;
|
||||||
|
min-height: 64px;
|
||||||
|
margin-top: auto;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: #0f5bd3;
|
||||||
|
color: white;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
font-size: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finish-button:disabled {
|
||||||
|
background: #b7c9e8;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-controls {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: minmax(0, 1fr) auto auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-panel {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-panel,
|
||||||
|
.manual-product,
|
||||||
|
.payment-panel {
|
||||||
|
padding: 20px 24px;
|
||||||
|
border-bottom: 1px solid var(--colorNeutralStroke2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee-field {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 110px 1fr;
|
||||||
|
gap: 14px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee-field select,
|
||||||
|
input,
|
||||||
|
output {
|
||||||
|
box-sizing: border-box;
|
||||||
|
height: 42px;
|
||||||
|
border: 1px solid var(--colorNeutralStroke1);
|
||||||
|
border-radius: 5px;
|
||||||
|
background: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee-field select,
|
||||||
|
input {
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-groups {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
gap: 9px 14px;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
max-height: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-group-button {
|
||||||
|
min-height: 42px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border: 1px solid var(--colorNeutralStroke2);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--colorNeutralBackground1);
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-group-button:hover:not(:disabled) {
|
||||||
|
background: var(--colorNeutralBackground1Hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-group-button.selected {
|
||||||
|
border-color: #0f5bd3;
|
||||||
|
background: #0f5bd3;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-group-button:disabled {
|
||||||
|
opacity: .55;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: .8fr 1fr 1fr .8fr;
|
||||||
|
gap: 24px;
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-grid label,
|
||||||
|
.paid-amount {
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-input,
|
||||||
|
.paid-amount > div {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
min-width: 0;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price-input input,
|
||||||
|
.paid-amount input {
|
||||||
|
min-width: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 12px;
|
||||||
|
background: var(--colorNeutralBackground2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-button,
|
||||||
|
.payment-buttons button,
|
||||||
|
.print-button {
|
||||||
|
min-height: 44px;
|
||||||
|
border: 1px solid #0f5bd3;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: var(--colorNeutralBackground1);
|
||||||
|
color: #0f5bd3;
|
||||||
|
cursor: pointer;
|
||||||
|
font: inherit;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-button:disabled,
|
||||||
|
.payment-buttons button:disabled {
|
||||||
|
border-color: var(--colorNeutralStroke2);
|
||||||
|
color: var(--colorNeutralForegroundDisabled);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paid-amount > div {
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
height: 58px;
|
||||||
|
padding-right: 14px;
|
||||||
|
border: 1px solid var(--colorNeutralStroke1);
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paid-amount input {
|
||||||
|
height: 56px;
|
||||||
|
border: 0;
|
||||||
|
outline: 0;
|
||||||
|
font-size: 32px;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-buttons {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-buttons button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
min-height: 52px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-buttons button span {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 58px;
|
||||||
|
margin-top: 14px;
|
||||||
|
padding: 0 18px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--colorNeutralBackground3);
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-row strong {
|
||||||
|
font-size: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-row.has-change {
|
||||||
|
background: #e30000;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-button {
|
||||||
|
width: 100%;
|
||||||
|
margin-top: 14px;
|
||||||
|
color: var(--colorNeutralForeground1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-button span {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-message {
|
||||||
|
position: absolute;
|
||||||
|
right: 24px;
|
||||||
|
bottom: 76px;
|
||||||
|
left: 24px;
|
||||||
|
z-index: 2;
|
||||||
|
margin: 0;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-message.error {
|
||||||
|
background: #fde7e9;
|
||||||
|
color: #b10e1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-message.success {
|
||||||
|
background: #dff6dd;
|
||||||
|
color: #107c10;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1050px) {
|
||||||
|
.sale-page {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
height: auto;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-sale {
|
||||||
|
min-height: 700px;
|
||||||
|
border-right: 0;
|
||||||
|
border-bottom: 1px solid var(--colorNeutralStroke2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-controls {
|
||||||
|
display: block;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-groups {
|
||||||
|
max-height: 275px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablet i liggende format, f.eks. 1280 x 800. */
|
||||||
|
@media (min-width: 1051px) and (max-width: 1400px) and (max-height: 850px) {
|
||||||
|
.sale-page {
|
||||||
|
grid-template-columns: minmax(440px, .9fr) minmax(600px, 1.25fr);
|
||||||
|
min-height: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-sale {
|
||||||
|
padding: 16px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-title {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-lines {
|
||||||
|
min-height: 210px;
|
||||||
|
max-height: 34dvh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-summary {
|
||||||
|
padding-top: 12px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.registered-payments {
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-row {
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.finish-button {
|
||||||
|
min-height: 54px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selection-panel,
|
||||||
|
.manual-product,
|
||||||
|
.payment-panel {
|
||||||
|
padding: 12px 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee-field {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-groups {
|
||||||
|
gap: 7px 10px;
|
||||||
|
max-height: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-group-button {
|
||||||
|
min-height: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.manual-grid {
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.employee-field select,
|
||||||
|
input,
|
||||||
|
output,
|
||||||
|
.add-button {
|
||||||
|
min-height: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paid-amount > div,
|
||||||
|
.paid-amount input {
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paid-amount input {
|
||||||
|
font-size: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-buttons {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-buttons button {
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-row {
|
||||||
|
min-height: 50px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-row strong {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.print-button {
|
||||||
|
min-height: 46px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 680px) {
|
||||||
|
.sale-page {
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-sale,
|
||||||
|
.selection-panel,
|
||||||
|
.manual-product,
|
||||||
|
.payment-panel {
|
||||||
|
padding: 18px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-groups,
|
||||||
|
.manual-grid,
|
||||||
|
.payment-buttons {
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sale-line-grid {
|
||||||
|
grid-template-columns: minmax(0, 1fr) 40px 70px 50px 74px;
|
||||||
|
gap: 6px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ builder.Services.AddFluentUIComponents();
|
|||||||
builder.Services.AddScoped<EmployeeService>();
|
builder.Services.AddScoped<EmployeeService>();
|
||||||
builder.Services.AddScoped<SaleService>();
|
builder.Services.AddScoped<SaleService>();
|
||||||
builder.Services.AddScoped<ProductGroupService>();
|
builder.Services.AddScoped<ProductGroupService>();
|
||||||
|
builder.Services.AddScoped<ProductService>();
|
||||||
|
builder.Services.AddScoped<SalesTransactionService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user