Compare commits

...

13 Commits

Author SHA1 Message Date
8953e917e1 Merge pull request 'feature/blazor' (#3) from feature/blazor into development
Reviewed-on: #3
2026-08-03 11:50:52 +00:00
6e14130a9c Finsih Employyepage 2026-08-03 11:01:37 +02:00
f01f05e126 chance database connection string 2026-08-03 10:02:58 +02:00
Alex Pedersen
c1695b4967 Addet Employye get logic 2026-08-03 09:55:41 +02:00
Alex Pedersen
205d27c6c7 Addet employee frontend and move header to a componet 2026-07-31 14:07:43 +02:00
Alex Pedersen
03594bba3f Made home page and startet the layout for settings home page 2026-07-30 15:21:59 +02:00
Alex Pedersen
2e537337f5 Addet aspire support pos.web and also addet the pictures for layout 2026-07-30 13:22:33 +02:00
Alex Pedersen
9497afe262 Rework so employee dont use repos but servies 2026-07-30 10:23:27 +02:00
Alex Pedersen
6df185d519 Move logic out the the class libs 2026-07-30 10:11:54 +02:00
Alex Pedersen
97688542fe moved logic out of SalesWindows.xaml.cs to SaleTransactionServce 2026-07-30 09:41:47 +02:00
Alex Pedersen
236eb6792c addet pos.models and pos.service 2026-07-30 09:22:48 +02:00
Alex Pedersen
d02414edbb modified sln to run the brach 2026-07-30 08:43:30 +02:00
be0350f458 Fluent Blazor web oprettet 2026-07-28 19:53:59 +02:00
96 changed files with 2095 additions and 313 deletions

View File

@@ -20,4 +20,8 @@
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
</ItemGroup>
</Project>
<ItemGroup>
<ProjectReference Include="..\Pos.ServiceDefaults\Pos.ServiceDefaults.csproj" />
</ItemGroup>
</Project>

View File

@@ -4,6 +4,8 @@ using Microsoft.Extensions.DependencyInjection;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
// Services
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
@@ -13,6 +15,8 @@ builder.Services.AddScoped<EpsonPrintService>();
var app = builder.Build();
app.MapDefaultEndpoints();
// Swagger (altid aktiv vi er på en Pi, ikke i produktion)
app.UseSwagger();
app.UseSwaggerUI();

View File

@@ -19,7 +19,7 @@
},
"EpsonPrinter": {
"commandName": "Project",
"dotnetRunMessages": "true",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:5001;http://localhost:5000",

View File

@@ -10,3 +10,9 @@ Buket..............1 á 50,50 DKK : 50,50 DKK
Total: 50,50 DKK
Moms udgør 25% : 12,63 DKK
Tak for handlen
Dato: 30-07-2026
-----------------------------------
string : .00
-----------------------------------
Total salg: .00
Antal kunder: 0

View File

@@ -1,5 +1,11 @@
var builder = DistributedApplication.CreateBuilder(args);
builder.AddProject<Projects.Database>("database");
var epsonPrinter =
builder.AddProject<Projects.EpsonPrinter>("epsonprinter");
builder
.AddProject<Projects.Pos_Web>("pos-web")
.WithReference(epsonPrinter)
.WaitFor(epsonPrinter);
builder.Build().Run();

View File

@@ -9,7 +9,8 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Pos.Ui\Database\Database.csproj" />
<ProjectReference Include="..\EpsonPrinterLinux\EpsonPrinter.csproj" />
<ProjectReference Include="..\Pos.Web\Pos.Web.csproj" />
</ItemGroup>
</Project>

View File

@@ -8,8 +8,8 @@ namespace Pos.Models
{
public class AmountGridModel
{
public string PaymentMethodText { get; set; }
public string PaymentMethodText { get; set; } = string.Empty;
public decimal Amount { get; set; }
public string AmountText { get; set; }
public string AmountText { get; set; } = string.Empty;
}
}

View File

@@ -6,4 +6,4 @@ public enum PaymentMethod
Card,
GiftCard,
MobilePay
}
}

View File

@@ -8,11 +8,11 @@ namespace Pos.Json
{
public class BodyModel
{
public ProductModel[] products { get; set; }
public ProductModel[] products { get; set; } = Array.Empty<ProductModel>();
public float totalPrice { get; set; }
public float totalVat { get; set; }
public int receiptNumber { get; set; }
public string receiptTime { get; set; }
public string staff { get; set; }
public string receiptTime { get; set; } = string.Empty;
public string staff { get; set; } = string.Empty;
}
}

View File

@@ -8,8 +8,8 @@ namespace Pos.Json
{
public class FooterModel
{
public string value { get; set; }
public PrintStylesModel printStyles { get; set; }
public string value { get; set; } = string.Empty;
public PrintStylesModel printStyles { get; set; } = new();
public int textAlignment { get; set; }
public int feedingLines { get; set; }
}

View File

@@ -8,8 +8,8 @@ namespace Pos.Json
{
public class HeaderModel
{
public string value { get; set; }
public PrintStylesModel printStyles { get; set; }
public string value { get; set; } = string.Empty;
public PrintStylesModel printStyles { get; set; } = new();
public int textAlignment { get; set; }
public int feedingLines { get; set; }
}

View File

@@ -8,9 +8,9 @@ namespace Pos.Json
{
public class PosReceipt
{
public string logoBase64 { get; set; }
public HeaderModel[] header { get; set; }
public string logoBase64 { get; set; } = string.Empty;
public HeaderModel[] header { get; set; } = Array.Empty<HeaderModel>();
public BodyModel bodyModel { get; set; } = new();
public FooterModel[] footer { get; set; }
public FooterModel[] footer { get; set; } = Array.Empty<FooterModel>();
}
}

View File

@@ -8,8 +8,8 @@ namespace Pos.Json
{
public class ProductModel
{
public string product { get; set; }
public string noOfProduct { get; set; }
public string product { get; set; } = string.Empty;
public string noOfProduct { get; set; } = string.Empty;
public float price { get; set; }
public float totalPrice { get; set; }
}

View File

@@ -13,7 +13,7 @@ namespace Pos.Json
public class SaleOfDayDetail
{
public string Category { get; set; }
public string Category { get; set; } = string.Empty;
public decimal TotalSale { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>

View File

@@ -8,7 +8,7 @@ namespace Pos.Models
{
public class PriceLineModel
{
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public int NumberProducts { get; set; }
public decimal Price { get; set; }
public int Id { get; set; }

View File

@@ -0,0 +1,11 @@
using System.Collections.Generic;
namespace Pos.Models
{
public class ReceiptDetailModel
{
public int SaleId { get; set; }
public List<ReceiptLineModel> Lines { get; set; } = new();
public decimal Total { get; set; }
}
}

View File

@@ -0,0 +1,10 @@
namespace Pos.Models
{
public class ReceiptLineModel
{
public string Product { get; set; } = string.Empty;
public int Pieces { get; set; }
public decimal Price { get; set; }
public decimal Total { get; set; }
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace Pos.Models
{
public class ReceiptSummaryModel
{
public int SaleId { get; set; }
public DateTime SaleTime { get; set; }
public decimal Total { get; set; }
}
}

View File

@@ -8,10 +8,10 @@ namespace Pos.Models
{
public class SaleGridModel
{
public string Navn { get; set; }
public string Stk { get; set; }
public string Pris { get; set; }
public string Total { get; set; }
public SaleGridInternalModel SaleGridInternalModel { get; set; }
public string Navn { get; set; } = string.Empty;
public string Stk { get; set; } = string.Empty;
public string Pris { get; set; } = string.Empty;
public string Total { get; set; } = string.Empty;
public SaleGridInternalModel SaleGridInternalModel { get; set; } = new();
}
}

View File

@@ -8,8 +8,8 @@ namespace Pos.Models
{
public class TotalSaleCategory
{
public string Category { get; set; }
public string Category { get; set; } = string.Empty;
public decimal Sale { get; set; }
public string SaleString { get; set; }
public string SaleString { get; set; } = string.Empty;
}
}

View File

@@ -10,8 +10,8 @@ namespace Pos.Service
private static bool _invalidEmployee = false;
private static bool _invalidProductGroup = false;
private static List<EmployeeEntity> _employees;
private static List<ProductGroupEntity> _productGroups;
private static List<EmployeeEntity> _employees = new();
private static List<ProductGroupEntity> _productGroups = new();
public static void Invalidate()

View File

@@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using Database.Models;
using Database.Repository;
namespace Pos.Service
{
public class EmployeeService
{
private readonly EmployeeRepository _employeeRepository =
new EmployeeRepository();
public List<EmployeeEntity> GetEmployees()
{
return _employeeRepository.GetAll();
}
public void AddEmployee(string name)
{
_employeeRepository.Add(ValidateName(name));
CacheService.Invalidate();
}
public void RenameEmployee(int employeeId, string name)
{
ValidateId(employeeId);
_employeeRepository.Edit(employeeId, ValidateName(name));
CacheService.Invalidate();
}
public void ArchiveEmployee(int employeeId)
{
ValidateId(employeeId);
_employeeRepository.Delete(employeeId);
CacheService.Invalidate();
}
private static string ValidateName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(
"Medarbejderens navn skal være udfyldt.",
nameof(name));
return name.Trim();
}
private static void ValidateId(int employeeId)
{
if (employeeId <= 0)
throw new ArgumentOutOfRangeException(nameof(employeeId));
}
public async Task<List<EmployeeEntity>> GetEmployeesAsync()
{
return await _employeeRepository.GetAllAsync();
}
public async Task<EmployeeEntity> AddEmployeeAsync(string name)
{
EmployeeEntity employee =
await _employeeRepository.AddAsync(ValidateName(name));
CacheService.Invalidate();
return employee;
}
}
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.10" />
<PackageReference Include="RestSharp" Version="108.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pos.Models\Pos.Models.csproj" />
<ProjectReference Include="..\Pos.Ui\Database\Database.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,85 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Database.Models;
using Database.Repository;
namespace Pos.Service
{
public class ProductGroupService
{
private readonly ProductGroupRepository _productGroupRepository =
new ProductGroupRepository();
public List<ProductGroupEntity> GetProductGroups()
{
return _productGroupRepository.GetAll();
}
public bool HasProductGroups()
{
return _productGroupRepository.Any();
}
public void AddProductGroup(string name)
{
_productGroupRepository.Add(ValidateName(name));
CacheService.Invalidate();
}
public void RenameProductGroup(int productGroupId, string name)
{
ValidateId(productGroupId);
_productGroupRepository.Edit(
ValidateName(name),
productGroupId);
CacheService.Invalidate();
}
public void ArchiveProductGroup(int productGroupId)
{
ValidateId(productGroupId);
_productGroupRepository.Archive(productGroupId);
CacheService.Invalidate();
}
public void UpdateOrder(IReadOnlyList<int> orderedProductGroupIds)
{
ValidateOrder(orderedProductGroupIds);
_productGroupRepository.SetOrder(orderedProductGroupIds);
CacheService.Invalidate();
}
private static string ValidateName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(
"Produktgruppen skal have et navn.",
nameof(name));
return name.Trim();
}
private static void ValidateId(int productGroupId)
{
if (productGroupId <= 0)
throw new ArgumentOutOfRangeException(nameof(productGroupId));
}
private static void ValidateOrder(IReadOnlyList<int> orderedIds)
{
if (orderedIds == null)
throw new ArgumentNullException(nameof(orderedIds));
if (orderedIds.Any(id => id <= 0))
throw new ArgumentException(
"Rækkefølgen indeholder et ugyldigt id.",
nameof(orderedIds));
if (orderedIds.Distinct().Count() != orderedIds.Count)
throw new ArgumentException(
"Rækkefølgen indeholder dubletter.",
nameof(orderedIds));
}
}
}

View File

@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Database.Models;
using Database.Repository;
namespace Pos.Service
{
public class ProductService
{
private readonly ProductRepository _productRepository =
new ProductRepository();
public List<ProductEntity> GetProducts(int productGroupId)
{
ValidateId(productGroupId, nameof(productGroupId));
return _productRepository.GetByProductGroup(productGroupId);
}
public ProductEntity GetProduct(int productId)
{
ValidateId(productId, nameof(productId));
return _productRepository.GetById(productId);
}
public void AddProduct(string name, int productGroupId)
{
ValidateId(productGroupId, nameof(productGroupId));
_productRepository.Add(ValidateName(name), productGroupId);
CacheService.Invalidate();
}
public void UpdateProduct(
int productId,
int productGroupId,
string name)
{
ValidateId(productId, nameof(productId));
ValidateId(productGroupId, nameof(productGroupId));
_productRepository.Update(
productId,
productGroupId,
ValidateName(name));
CacheService.Invalidate();
}
public void ArchiveProduct(int productId)
{
ValidateId(productId, nameof(productId));
_productRepository.Archive(productId);
CacheService.Invalidate();
}
public void UpdateOrder(
int productGroupId,
IReadOnlyList<int> orderedProductIds)
{
ValidateId(productGroupId, nameof(productGroupId));
if (orderedProductIds == null)
throw new ArgumentNullException(nameof(orderedProductIds));
if (orderedProductIds.Any(id => id <= 0))
throw new ArgumentException(
"Rækkefølgen indeholder et ugyldigt id.",
nameof(orderedProductIds));
if (orderedProductIds.Distinct().Count() != orderedProductIds.Count)
throw new ArgumentException(
"Rækkefølgen indeholder dubletter.",
nameof(orderedProductIds));
_productRepository.SetOrder(
productGroupId,
orderedProductIds);
CacheService.Invalidate();
}
private static string ValidateName(string name)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(
"Produktet skal have et navn.",
nameof(name));
return name.Trim();
}
private static void ValidateId(int id, string parameterName)
{
if (id <= 0)
throw new ArgumentOutOfRangeException(parameterName);
}
}
}

View File

@@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Database.Models;
using Database.Repository;
using Pos.Models;
namespace Pos.Service
{
public class SaleHistoryService
{
private readonly SaleRepository _saleRepository =
new SaleRepository();
public List<ReceiptSummaryModel> Search(
DateTime startDate,
DateTime endDate)
{
if (endDate.Date < startDate.Date)
throw new ArgumentException(
"Slutdatoen må ikke ligge før startdatoen.");
List<SaleEntity> sales =
_saleRepository.GetByDateRange(startDate, endDate);
List<ReceiptSummaryModel> receipts =
new List<ReceiptSummaryModel>();
foreach (SaleEntity sale in sales)
{
decimal total = _saleRepository
.GetSaleLineBySaleId(sale.Id)
.Sum(line => line.Total);
receipts.Add(new ReceiptSummaryModel
{
SaleId = sale.Id,
SaleTime = sale.Time,
Total = total
});
}
return receipts;
}
public ReceiptDetailModel GetReceipt(int saleId)
{
if (saleId <= 0)
throw new ArgumentOutOfRangeException(nameof(saleId));
List<SaleLineEntity> saleLines =
_saleRepository.GetSaleLineBySaleId(saleId);
ReceiptDetailModel receipt = new ReceiptDetailModel
{
SaleId = saleId
};
foreach (SaleLineEntity saleLine in saleLines)
{
receipt.Lines.Add(new ReceiptLineModel
{
Product = saleLine.Product,
Pieces = saleLine.Pieces,
Price = saleLine.Price,
Total = saleLine.Total
});
}
receipt.Total = receipt.Lines.Sum(line => line.Total);
return receipt;
}
}
}

View File

@@ -84,7 +84,9 @@ namespace Pos.Service
LoadConfig loadConfig = new LoadConfig();
IConfiguration config = loadConfig.ByEnvironment();
string url = config["API:URL"];
string url = config["API:URL"]
?? throw new InvalidOperationException(
"API:URL mangler i konfigurationen.");
RestClient restClient = new RestClient($"{url}/api/PosPrinter/SaleOfDay");
RestRequest request = new RestRequest();
@@ -159,7 +161,9 @@ namespace Pos.Service
LoadConfig loadConfig = new LoadConfig();
IConfiguration config = loadConfig.ByEnvironment();
string apiUrl = config["API:URL"];
string apiUrl = config["API:URL"]
?? throw new InvalidOperationException(
"API:URL mangler i konfigurationen.");
string url = $"{apiUrl}/api/PosPrinter/Receipt";
RestClient restClient = new RestClient(url);

View File

@@ -0,0 +1,171 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Pos.Models;
namespace Pos.Service
{
/// <summary>
/// Holder styr på den igangværende handel og dens beregninger.
/// Servicen har ingen afhængighed til WPF eller web.
/// </summary>
public class SalesTransactionService
{
private readonly List<PriceLineModel> _saleLines = new List<PriceLineModel>();
private readonly List<AmountGridModel> _payments = new List<AmountGridModel>();
public IReadOnlyList<PriceLineModel> SaleLines => _saleLines;
public IReadOnlyList<AmountGridModel> Payments => _payments;
public decimal TotalPrice =>
_saleLines.Sum(line =>
CalculateLineTotal(line.Price, line.NumberProducts));
public decimal AmountPaid =>
_payments.Sum(payment => payment.Amount);
public decimal RemainingAmount =>
Math.Max(TotalPrice - AmountPaid, 0);
public decimal Change =>
Math.Max(AmountPaid - TotalPrice, 0);
public bool IsFullyPaid =>
TotalPrice > 0 && AmountPaid >= TotalPrice;
/// <summary>
/// Beregner totalen for en enkelt salgslinje.
/// Parsing og visning af resultatet er fortsat UI-ansvar.
/// </summary>
public decimal CalculateLineTotal(decimal price, int numberOfProducts)
{
ValidatePriceAndQuantity(price, numberOfProducts);
return price * numberOfProducts;
}
public PriceLineModel AddSaleLine(
int id,
string name,
int numberOfProducts,
decimal price,
bool isProductGroup)
{
if (id <= 0)
throw new ArgumentOutOfRangeException(
nameof(id),
"Id skal være større end 0.");
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException(
"Navn skal være udfyldt.",
nameof(name));
ValidatePriceAndQuantity(price, numberOfProducts);
PriceLineModel saleLine = new PriceLineModel
{
Id = id,
Name = name,
NumberProducts = numberOfProducts,
Price = price,
IsProductGroup = isProductGroup
};
_saleLines.Add(saleLine);
return saleLine;
}
public void RemoveSaleLineAt(int index)
{
if (index < 0 || index >= _saleLines.Count)
throw new ArgumentOutOfRangeException(nameof(index));
_saleLines.RemoveAt(index);
}
/// <summary>
/// Registrerer en betaling. Hvis amount er null, betales restbeløbet.
/// </summary>
public AmountGridModel AddPayment(
PaymentMethod paymentMethod,
decimal? amount = null)
{
if (_saleLines.Count == 0)
throw new InvalidOperationException(
"Der kan ikke registreres betaling på et tomt salg.");
decimal paymentAmount = amount ?? RemainingAmount;
if (paymentAmount <= 0)
throw new ArgumentOutOfRangeException(
nameof(amount),
"Betalingsbeløbet skal være større end 0.");
AmountGridModel payment = new AmountGridModel
{
Amount = paymentAmount,
PaymentMethodText = GetPaymentMethodText(paymentMethod)
};
_payments.Add(payment);
return payment;
}
public void RemovePaymentAt(int index)
{
if (index < 0 || index >= _payments.Count)
throw new ArgumentOutOfRangeException(nameof(index));
_payments.RemoveAt(index);
}
public void EnsureCanComplete()
{
if (_saleLines.Count == 0)
throw new InvalidOperationException(
"Et tomt salg kan ikke afsluttes.");
if (!IsFullyPaid)
throw new InvalidOperationException(
"Salget er ikke fuldt betalt.");
}
public void Reset()
{
_saleLines.Clear();
_payments.Clear();
}
private static void ValidatePriceAndQuantity(
decimal price,
int numberOfProducts)
{
if (price < 0)
throw new ArgumentOutOfRangeException(
nameof(price),
"Prisen må ikke være negativ.");
if (numberOfProducts <= 0)
throw new ArgumentOutOfRangeException(
nameof(numberOfProducts),
"Antal skal være større end 0.");
}
private static string GetPaymentMethodText(PaymentMethod paymentMethod)
{
switch (paymentMethod)
{
case PaymentMethod.Card:
return "Kort";
case PaymentMethod.Cash:
return "Kontant";
case PaymentMethod.GiftCard:
return "Gavekort";
case PaymentMethod.MobilePay:
return "MobilePay";
default:
throw new ArgumentOutOfRangeException(nameof(paymentMethod));
}
}
}
}

View File

@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<TargetFrameworks>net8.0;net10.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsAspireSharedProject>true</IsAspireSharedProject>

View File

@@ -17,6 +17,16 @@ namespace Database
public DbSet<SaleLineEntity> SalesLines { get; set; }
public DbSet<PaymentEntity> Payment { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<SaleLineEntity>()
.Property(saleLine => saleLine.Pieces)
.HasConversion<short>()
.HasColumnType("smallint");
base.OnModelCreating(modelBuilder);
}
protected override void OnConfiguring(
DbContextOptionsBuilder optionsBuilder)
{

View File

@@ -1,10 +1,11 @@
using System;
using Database.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Database.Models;
using Microsoft.Extensions.Configuration;
namespace Database.Repository
{
@@ -58,5 +59,32 @@ namespace Database.Repository
public void Dispose()
{
}
public async Task<List<EmployeeEntity>> GetAllAsync()
{
await using PosDbContext context = new PosDbContext();
return await context.Employee
.AsNoTracking()
.Where(employee => !employee.IsArchived)
.OrderBy(employee => employee.Name)
.ToListAsync();
}
public async Task<EmployeeEntity> AddAsync(string name)
{
EmployeeEntity employee = new EmployeeEntity
{
Name = name,
IsModified = true
};
await using PosDbContext context = new PosDbContext();
context.Employee.Add(employee);
await context.SaveChangesAsync();
return employee;
}
}
}

View File

@@ -69,10 +69,30 @@ namespace Database.Repository
context.SaveChanges();
}
public void SetOrder(IReadOnlyList<int> orderedIds)
{
using PosDbContext context = new PosDbContext();
List<ProductGroupEntity> productGroups = context.ProductGroups
.Where(productGroup => orderedIds.Contains(productGroup.Id))
.ToList();
if (productGroups.Count != orderedIds.Count)
throw new InvalidOperationException(
"En eller flere produktgrupper findes ikke.");
Dictionary<int, ProductGroupEntity> productGroupsById =
productGroups.ToDictionary(productGroup => productGroup.Id);
for (int index = 0; index < orderedIds.Count; index++)
productGroupsById[orderedIds[index]].Index = index;
context.SaveChanges();
}
public bool Any()
{
using PosDbContext context = new PosDbContext();
bool any = context.ProductGroups.Any();
bool any = context.ProductGroups.Any(c => !c.IsArchived);
return any;
}
}

View File

@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System;
using System.Linq;
using Database.Models;
@@ -38,6 +39,33 @@ namespace Database.Repository
context.SaveChanges();
}
public void SetOrder(
int productGroupId,
IReadOnlyList<int> orderedIds)
{
using PosDbContext context = new PosDbContext();
List<ProductEntity> products = context.Products
.Where(product => product.ProductGroupId == productGroupId)
.Where(product => orderedIds.Contains(product.Id))
.ToList();
if (products.Count != orderedIds.Count)
throw new InvalidOperationException(
"Et eller flere produkter findes ikke i produktgruppen.");
Dictionary<int, ProductEntity> productsById =
products.ToDictionary(product => product.Id);
for (int index = 0; index < orderedIds.Count; index++)
{
ProductEntity product = productsById[orderedIds[index]];
product.Index = index;
product.IsModified = true;
}
context.SaveChanges();
}
public ProductEntity GetById(int id)
{
using PosDbContext context = new PosDbContext();
@@ -72,7 +100,11 @@ namespace Database.Repository
product.ProductGroupId = productGroupId;
using PosDbContext context = new PosDbContext();
//Get the highest index
ProductEntity highest = context.Products.OrderByDescending(c => c.Index).Take(1).FirstOrDefault();
ProductEntity highest = context.Products
.Where(c => c.ProductGroupId == productGroupId)
.OrderByDescending(c => c.Index)
.Take(1)
.FirstOrDefault();
if (highest == null)
{
highest = new ProductEntity();

View File

@@ -18,12 +18,10 @@
<None Remove="Icons\settings.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="RestSharp" Version="108.0.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Database\Database.csproj" />
<ProjectReference Include="..\..\Pos.Models\Pos.Models.csproj" />
<ProjectReference Include="..\..\Pos.Service\Pos.Service.csproj" />
</ItemGroup>
<ItemGroup>

View File

@@ -11,8 +11,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Models;
using Database.Repository;
using Pos.Models;
using Pos.Service;
namespace Pos
@@ -25,6 +24,9 @@ namespace Pos
private List<ListReceiptModel> _receiptList = new List<ListReceiptModel>();
private bool _init = false;
private int _saleId = 0;
private readonly SaleHistoryService _saleHistoryService =
new SaleHistoryService();
private readonly SaleService _saleService = new SaleService();
public PrintReceiptWindow()
{
@@ -38,28 +40,39 @@ namespace Pos
private void Search_OnClick(object sender, RoutedEventArgs e)
{
DateTime startDate = (DateTime)StartDatePicker.SelectedDate;
DateTime endDate = (DateTime)EndDatePicker.SelectedDate;
SaleRepository saleRepository = new SaleRepository();
List<SaleEntity> saleEntities = saleRepository.GetByDateRange(startDate, endDate);
foreach (SaleEntity entity in saleEntities)
{
List<SaleLineEntity> saleLineBySaleId = saleRepository.GetSaleLineBySaleId(entity.Id);
decimal total = 0;
foreach (SaleLineEntity lineEntity in saleLineBySaleId)
{
total += lineEntity.Total;
}
if (StartDatePicker.SelectedDate is not DateTime startDate ||
EndDatePicker.SelectedDate is not DateTime endDate)
return;
List<ReceiptSummaryModel> receipts;
try
{
receipts = _saleHistoryService.Search(startDate, endDate);
}
catch (ArgumentException exception)
{
MessageBox.Show(
exception.Message,
"Ugyldigt datointerval",
MessageBoxButton.OK,
MessageBoxImage.Warning);
return;
}
_receiptList.Clear();
foreach (ReceiptSummaryModel receipt in receipts)
{
ListReceiptModel receiptModel = new ListReceiptModel
{
Id = entity.Id,
Content = $"{entity.Time:dd-MM-yyyy HH:mm} - Pris: {total:0.00}"
Id = receipt.SaleId,
Content =
$"{receipt.SaleTime:dd-MM-yyyy HH:mm} - Pris: {receipt.Total:0.00}"
};
_receiptList.Add(receiptModel);
}
_init = true;
ListReciept.ItemsSource = null;
ListReciept.ItemsSource = _receiptList;
_init = false;
}
@@ -78,23 +91,21 @@ namespace Pos
private void ListReciept_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_init)
if (_init || e.AddedItems.Count == 0)
return;
ListReceiptModel data = (ListReceiptModel) e.AddedItems[0];
_saleId = data.Id;
SaleRepository saleRepository = new SaleRepository();
List<SaleLineEntity> saleLineBySaleId = saleRepository.GetSaleLineBySaleId(data.Id);
ReceiptDetailModel receipt =
_saleHistoryService.GetReceipt(data.Id);
StringBuilder sb = new StringBuilder();
decimal total = 0;
foreach (SaleLineEntity saleLine in saleLineBySaleId)
foreach (ReceiptLineModel saleLine in receipt.Lines)
{
total += saleLine.Total;
sb.AppendLine(
$"{saleLine.Product} - {saleLine.Pieces} stk. a' {saleLine.Price.ToString("0.00")} = {saleLine.Total.ToString("0.00")}");
$"{saleLine.Product} - {saleLine.Pieces} stk. a' {saleLine.Price:0.00} = {saleLine.Total:0.00}");
}
sb.AppendLine($"Total: {total.ToString("0.00")} kr.");
sb.AppendLine($"Total: {receipt.Total:0.00} kr.");
PrintReceipt.Visibility = Visibility.Visible;
SaleLines.Clear();
SaleLines.Text = sb.ToString();
@@ -102,8 +113,7 @@ namespace Pos
private void PrintReceipt_OnClick(object sender, RoutedEventArgs e)
{
SaleService saleService = new SaleService();
saleService.PrintReceipt(_saleId);
_saleService.PrintReceipt(_saleId);
}
}
}

View File

@@ -13,11 +13,11 @@ namespace Pos
{
private bool _initialLoad = true;
private TotalSaleDetail totalSaleDetail;
private readonly SaleService _saleService = new SaleService();
public SaleOfDay()
{
InitializeComponent();
SaleService saleService = new SaleService();
totalSaleDetail = saleService.TotalSale(DateTime.Now);
totalSaleDetail = _saleService.TotalSale(DateTime.Now);
TotalSale.Content = $"Dagens salg: kr. {totalSaleDetail.TotalSale:0.00}";
StartDatePicker.SelectedDate = DateTime.Now;
GridSaleDetail.ItemsSource = totalSaleDetail.TotalSaleCategories;
@@ -29,18 +29,20 @@ namespace Pos
if (_initialLoad)
return;
_initialLoad = false;
DateTime selectedDate = DateTime.Parse(sender.ToString());
SaleService saleService = new SaleService();
totalSaleDetail = saleService.TotalSale(selectedDate);
if (StartDatePicker.SelectedDate is not DateTime selectedDate)
return;
totalSaleDetail = _saleService.TotalSale(selectedDate);
TotalSale.Content = $"Dagens salg: kr. {totalSaleDetail.TotalSale:0.00}";
GridSaleDetail.ItemsSource = totalSaleDetail.TotalSaleCategories;
}
private void PrintSale_OnClick(object sender, RoutedEventArgs e)
{
SaleService saleService = new SaleService();
saleService.PrintSaleOfDay(StartDatePicker.SelectedDate.Value,totalSaleDetail);
if (StartDatePicker.SelectedDate is not DateTime selectedDate)
return;
_saleService.PrintSaleOfDay(selectedDate, totalSaleDetail);
}
}
}

View File

@@ -29,7 +29,8 @@ namespace Pos
private ObservableCollection<ProductGroupEntity> _productGroups;
private ObservableCollection<SaleGridModel> _saleGridModels;
private ObservableCollection<AmountGridModel> _paymentGridModels;
private List<PriceLineModel> _priceLineModels;
private readonly SalesTransactionService _salesTransactionService =
new SalesTransactionService();
private ProductEntity _selectedProduct;
private ProductGroupEntity _selectedProductGroup;
@@ -37,14 +38,10 @@ namespace Pos
private int _employeeId = -1;
private int _productGroupId = -1;
private int _productId = -1;
private int _numberOfProducts = 1;
private int _selectedSaleGridIndex = -1;
private int _maxCharOfName = 0;
private decimal _price = 0;
private decimal _totalPrice = 0;
private Button _prevEmpButton = new Button();
private Button _prevProductGroupButton = new Button();
private Button _prevProduct = new Button();
@@ -61,7 +58,6 @@ namespace Pos
_employees = CacheService.GetEmployee() as ObservableCollection<EmployeeEntity>;
_productGroups = CacheService.GetProductGroupsIncludeProducts() as ObservableCollection<ProductGroupEntity>;
_saleGridModels = new ObservableCollection<SaleGridModel>();
_priceLineModels = new List<PriceLineModel>();
_paymentGridModels = new ObservableCollection<AmountGridModel>();
SaleGrid.ItemsSource = _saleGridModels;
PaymentGrid.ItemsSource = _paymentGridModels;
@@ -150,11 +146,25 @@ namespace Pos
return;
decimal price = Convert.ToDecimal(Price.Text);
int number = Convert.ToInt32(NumberOfProducts.Text);
int numberOfProducts = Convert.ToInt32(NumberOfProducts.Text);
try
{
BuildPrice(numberOfProducts, price);
}
catch (ArgumentException exception)
{
MessageBox.Show(
exception.Message,
"Ugyldig salgslinje",
MessageBoxButton.OK,
MessageBoxImage.Error);
return;
}
NumberOfProducts.Text = "1";
Price.Text = string.Empty;
TotalPrice.Content = "0";
BuildPrice(false);
_productId = -1;
_productGroupId = -1;
ManipulateBuySection();
@@ -162,16 +172,36 @@ namespace Pos
private void Payment_Click(object sender, RoutedEventArgs e)
{
//bool isValid = ValidateAmountInput();
//if (!isValid)
// return;
decimal amount = -1;
decimal? amount = null;
if (!string.IsNullOrEmpty(Amount.Text))
{
bool isValid = ValidateAmountInput();
if (!isValid)
return;
amount = Convert.ToDecimal(Amount.Text);
}
Amount.Text = string.Empty;
Button button = sender as Button;
BuildAmuntDataGrid(amount,(string)button.Tag);
string paymentMethodName =
((string)button.Tag).Replace("PaymentMethod.", string.Empty);
if (!Enum.TryParse(paymentMethodName, out PaymentMethod paymentMethod))
return;
try
{
BuildAmuntDataGrid(amount, paymentMethod);
}
catch (ArgumentException exception)
{
MessageBox.Show(
exception.Message,
"Ugyldig betaling",
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
private void NumberOfProducts_LostFocus(object sender, RoutedEventArgs e)
@@ -249,61 +279,69 @@ namespace Pos
}
}
private void BuildPrice(bool returnProduct)
private void BuildPrice(int numberOfProducts, decimal price)
{
PriceLineModel pModel = new PriceLineModel();
pModel.NumberProducts = _numberOfProducts;
pModel.Price = _price;
PriceLineModel priceLineModel;
if (_selectedProduct != null)
{
pModel.Id = _selectedProduct.Id;
pModel.Name = _selectedProduct.Name;
pModel.IsProductGroup = false;
priceLineModel = _salesTransactionService.AddSaleLine(
_selectedProduct.Id,
_selectedProduct.Name,
numberOfProducts,
price,
false);
}
else
{
pModel.Id = _selectedProductGroup.Id;
pModel.Name = _selectedProductGroup.Name;
pModel.IsProductGroup = true;
priceLineModel = _salesTransactionService.AddSaleLine(
_selectedProductGroup.Id,
_selectedProductGroup.Name,
numberOfProducts,
price,
true);
}
BuildPriceDataGrid(pModel);
BuildPriceDataGrid(priceLineModel);
//BuildEmployee();
BuildProductGroup();
}
private void CalculateSubPrice()
{
_price = 0;
bool isValid = decimal.TryParse(Price.Text, out _price);
bool isValid = decimal.TryParse(Price.Text, out decimal price);
if (!isValid)
return;
isValid = Int32.TryParse(NumberOfProducts.Text, out _numberOfProducts);
isValid = Int32.TryParse(
NumberOfProducts.Text,
out int numberOfProducts);
if (!isValid)
return;
TotalPrice.Content = _price * _numberOfProducts;
try
{
TotalPrice.Content =
_salesTransactionService.CalculateLineTotal(
price,
numberOfProducts);
}
catch (ArgumentOutOfRangeException)
{
TotalPrice.Content = "0";
}
}
private void CalculateTotalLine()
{
_totalPrice = 0;
foreach (PriceLineModel model in _priceLineModels)
{
_totalPrice += model.Price * model.NumberProducts;
}
Total.Content = $"At betale: Kr. {_totalPrice}";
Total.Content =
$"At betale: Kr. {_salesTransactionService.TotalPrice}";
}
private void BuildPriceDataGrid(PriceLineModel priceLineModel)
{
_selectedProduct = null;
_selectedProductGroup = null;
_numberOfProducts = 1;
_priceLineModels.Add(priceLineModel);
CalculateTotalLine();
SaleGridModel model = new SaleGridModel();
@@ -399,38 +437,14 @@ namespace Pos
TotalPrice.IsEnabled = enabled;
}
private void BuildAmuntDataGrid(decimal amount, string paymentMethod)
private void BuildAmuntDataGrid(
decimal? amount,
PaymentMethod paymentMethod)
{
if (amount == -1)
{
amount = 0;
foreach (PriceLineModel lineModel in _priceLineModels)
{
amount += (lineModel.Price * lineModel.NumberProducts);
}
}
AmountGridModel amountGridModel =
_salesTransactionService.AddPayment(paymentMethod, amount);
AmountGridModel amountGridModel = new AmountGridModel();
amountGridModel.Amount = amount;
amountGridModel.AmountText = $"Kr. {amount}";
switch (paymentMethod)
{
case "PaymentMethod.Card":
amountGridModel.PaymentMethodText = "Kort";
break;
case "PaymentMethod.Cash":
amountGridModel.PaymentMethodText = "Kontant";
break;
case "PaymentMethod.GiftCard":
amountGridModel.PaymentMethodText = "Gavekort";
break;
case "PaymentMethod.MobilePay":
amountGridModel.PaymentMethodText = "MobilePay";
break;
}
amountGridModel.AmountText = $"Kr. {amountGridModel.Amount}";
_paymentGridModels.Add(amountGridModel);
AmountPayed();
@@ -438,28 +452,25 @@ namespace Pos
private void AmountPayed()
{
decimal amountPayed = 0;
foreach (AmountGridModel paymentGridModel in _paymentGridModels)
{
amountPayed += paymentGridModel.Amount;
}
if (amountPayed == _totalPrice)
if (_salesTransactionService.AmountPaid ==
_salesTransactionService.TotalPrice)
{
btnFinish_Click(null,null);
return;
}
if (amountPayed > _totalPrice)
if (_salesTransactionService.Change > 0)
{
lblRecived.Content = $"Byttepenge: Kr. {amountPayed - _totalPrice}";
lblRecived.Content =
$"Byttepenge: Kr. {_salesTransactionService.Change}";
Style style = this.FindResource("BoldLabelWarningNoMargin") as Style;
lblRecived.Style = style;
btnFinish.IsEnabled = true;
}
else
{
lblRecived.Content = $"At betale: Kr. {_totalPrice - amountPayed}";
lblRecived.Content =
$"At betale: Kr. {_salesTransactionService.RemainingAmount}";
Style style = this.FindResource("BoldLabelNoMargin") as Style;
lblRecived.Style = style;
}
@@ -473,21 +484,37 @@ namespace Pos
if (_selectedSaleGridIndex == -1)
return;
int index = _selectedSaleGridIndex;
//_saleGridModels.RemoveAt(_selectedSaleGridIndex);
_priceLineModels.RemoveAt(index);
_salesTransactionService.RemoveSaleLineAt(index);
_saleGridModels.RemoveAt(index);
_selectedSaleGridIndex = -1;
CalculateTotalLine();
if (_salesTransactionService.AmountPaid > 0)
AmountPayed();
}
}
private void btnFinish_Click(object sender, RoutedEventArgs e)
{
try
{
_salesTransactionService.EnsureCanComplete();
}
catch (InvalidOperationException exception)
{
MessageBox.Show(
exception.Message,
"Salget kan ikke afsluttes",
MessageBoxButton.OK,
MessageBoxImage.Error);
return;
}
SaleService saleService = new SaleService();
saleService.Save(_saleGridModels.ToList(),_paymentGridModels.ToList(),_employeeId);
_paymentGridModels.Clear();
_priceLineModels.Clear();
_salesTransactionService.Reset();
UpdateWindowTitle();
_totalPrice = 0;
_saleGridModels.Clear();
lblRecived.Content = string.Empty;
_selectedSaleGridIndex = -1;

View File

@@ -11,7 +11,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Service;
namespace Pos.Setting
{
@@ -20,6 +20,9 @@ namespace Pos.Setting
/// </summary>
public partial class AddEmployeeWindow : Window
{
private readonly EmployeeService _employeeService =
new EmployeeService();
public AddEmployeeWindow()
{
InitializeComponent();
@@ -27,9 +30,19 @@ namespace Pos.Setting
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
EmployeeRepository employeeRepository = new EmployeeRepository();
employeeRepository.Add(txtStaff.Text);
this.Close();
try
{
_employeeService.AddEmployee(txtStaff.Text);
Close();
}
catch (ArgumentException exception)
{
MessageBox.Show(
exception.Message,
"Ugyldig medarbejder",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
}
}
}

View File

@@ -11,7 +11,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Service;
namespace Pos.Setting
{
@@ -21,6 +21,8 @@ namespace Pos.Setting
public partial class DeleteEmployeeWindow : Window
{
private int _employeeId;
private readonly EmployeeService _employeeService =
new EmployeeService();
public DeleteEmployeeWindow(int employeeId, string staffName)
{
@@ -31,9 +33,8 @@ namespace Pos.Setting
private void btnYes_Click(object sender, RoutedEventArgs e)
{
EmployeeRepository employeeRepository = new EmployeeRepository();
employeeRepository.Delete(_employeeId);
this.Close();
_employeeService.ArchiveEmployee(_employeeId);
Close();
}
private void btnNo_Click(object sender, RoutedEventArgs e)

View File

@@ -11,7 +11,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Service;
namespace Pos.Setting
{
@@ -21,6 +21,8 @@ namespace Pos.Setting
public partial class EditEmployeeWindow : Window
{
private int _employeeId;
private readonly EmployeeService _employeeService =
new EmployeeService();
public EditEmployeeWindow(int employeeId, string staffName)
{
@@ -31,9 +33,21 @@ namespace Pos.Setting
private void btnEdit_Click(object sender, RoutedEventArgs e)
{
EmployeeRepository employeeRepository = new EmployeeRepository();
employeeRepository.Edit(_employeeId,txtEmployee.Text);
this.Close();
try
{
_employeeService.RenameEmployee(
_employeeId,
txtEmployee.Text);
Close();
}
catch (ArgumentException exception)
{
MessageBox.Show(
exception.Message,
"Ugyldig medarbejder",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
}
}
}

View File

@@ -13,7 +13,7 @@ using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Models;
using Database.Repository;
using Pos.Service;
namespace Pos.Setting
{
@@ -24,6 +24,8 @@ namespace Pos.Setting
{
private int _selectedEmployee = -1;
private string _employeeName = string.Empty;
private readonly EmployeeService _employeeService =
new EmployeeService();
public EmployeeWindow()
{
InitializeComponent();
@@ -62,15 +64,14 @@ namespace Pos.Setting
deleteEmployeeWindow.Show();
}
private void WindowClosed(object? sender, EventArgs e)
private void WindowClosed(object sender, EventArgs e)
{
LoadData();
}
private void LoadData()
{
using EmployeeRepository employeeRepository = new EmployeeRepository();
List<EmployeeEntity> allStaff = employeeRepository.GetAll();
List<EmployeeEntity> allStaff = _employeeService.GetEmployees();
LstStaff.Items.Clear();
foreach (EmployeeEntity staff in allStaff)
{

View File

@@ -1,8 +1,8 @@
using Database.Repository;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using Pos.Service;
namespace Pos.Setting.ProductGroup
{
@@ -11,6 +11,11 @@ namespace Pos.Setting.ProductGroup
/// </summary>
public partial class AddProductWindow : Window
{
private readonly ProductGroupService _productGroupService =
new ProductGroupService();
private readonly ProductService _productService =
new ProductService();
public AddProductWindow()
{
InitializeComponent();
@@ -19,13 +24,16 @@ namespace Pos.Setting.ProductGroup
private void LoadData(int selectedIndex)
{
ProductGroupRepository productGroupRepository = new ProductGroupRepository();
List<Database.Models.ProductGroupEntity> products = productGroupRepository.GetAll();
foreach (Database.Models.ProductGroupEntity productGroup in products)
List<Database.Models.ProductGroupEntity> productGroups =
_productGroupService.GetProductGroups();
foreach (Database.Models.ProductGroupEntity productGroup in productGroups)
{
ComboBoxItem comboBoxItem = new ComboBoxItem();
comboBoxItem.Tag = productGroup.Id;
comboBoxItem.Content = productGroup.Name;
ComboBoxItem comboBoxItem = new ComboBoxItem
{
Tag = productGroup.Id,
Content = productGroup.Name
};
cmbProductGroup.Items.Add(comboBoxItem);
}
@@ -34,12 +42,23 @@ namespace Pos.Setting.ProductGroup
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
ComboBoxItem item = (ComboBoxItem) cmbProductGroup.SelectedItem;
string name = txtProduct.Text;
int id = Convert.ToInt32(item.Tag);
ProductRepository productRepository = new ProductRepository();
productRepository.Add(name,id);
this.Close();
ComboBoxItem item = (ComboBoxItem)cmbProductGroup.SelectedItem;
try
{
_productService.AddProduct(
txtProduct.Text,
Convert.ToInt32(item.Tag));
Close();
}
catch (ArgumentException exception)
{
MessageBox.Show(
exception.Message,
"Ugyldigt produkt",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
}
}
}

View File

@@ -11,7 +11,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Service;
namespace Pos.Setting.Product
{
@@ -21,20 +21,21 @@ namespace Pos.Setting.Product
public partial class ArchiveProductWindow : Window
{
private int _productId;
private readonly ProductService _productService =
new ProductService();
public ArchiveProductWindow(int productId)
{
InitializeComponent();
_productId = productId;
ProductRepository productRepository = new ProductRepository();
Database.Models.ProductEntity product = productRepository.GetById(_productId);
Database.Models.ProductEntity product =
_productService.GetProduct(_productId);
txtDelProduct.Text = product.Name;
}
private void btnYes_Click(object sender, RoutedEventArgs e)
{
ProductRepository productRepository = new ProductRepository();
productRepository.Archive(_productId);
this.Close();
_productService.ArchiveProduct(_productId);
Close();
}
private void btnNo_Click(object sender, RoutedEventArgs e)

View File

@@ -12,7 +12,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Service;
namespace Pos.Setting.Product
{
@@ -23,6 +23,8 @@ namespace Pos.Setting.Product
{
ObservableCollection<Database.Models.ProductEntity> _productList = new ObservableCollection<Database.Models.ProductEntity>();
private int _productGroupId;
private readonly ProductService _productService =
new ProductService();
public ChangeProductOrderWindow(int productGroupId)
{
InitializeComponent();
@@ -32,8 +34,8 @@ namespace Pos.Setting.Product
private void LoadData()
{
ProductRepository productRepository = new ProductRepository();
List<Database.Models.ProductEntity> products = productRepository.GetByProductGroup(_productGroupId);
List<Database.Models.ProductEntity> products =
_productService.GetProducts(_productGroupId);
foreach (Database.Models.ProductEntity product in products)
{
_productList.Add(product);
@@ -86,13 +88,10 @@ namespace Pos.Setting.Product
private void btnSave_Click(object sender, RoutedEventArgs e)
{
ProductRepository productRepository = new ProductRepository();
for (int i = 0; i < _productList.Count; i++)
{
Database.Models.ProductEntity product = _productList[i];
productRepository.SetIndex(product.Id, i);
}
this.Close();
_productService.UpdateOrder(
_productGroupId,
_productList.Select(product => product.Id).ToList());
Close();
}
}

View File

@@ -11,7 +11,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Service;
namespace Pos.Setting.Product
{
@@ -23,6 +23,10 @@ namespace Pos.Setting.Product
private int _productId;
private int _selectedProductGroupId;
private Database.Models.ProductEntity _product;
private readonly ProductGroupService _productGroupService =
new ProductGroupService();
private readonly ProductService _productService =
new ProductService();
public EditProductWindow(int productId, int selectedProductGroupId)
{
@@ -34,8 +38,8 @@ namespace Pos.Setting.Product
private void Init()
{
ProductGroupRepository categoryRepository = new ProductGroupRepository();
List<Database.Models.ProductGroupEntity> productGroups = categoryRepository.GetAll();
List<Database.Models.ProductGroupEntity> productGroups =
_productGroupService.GetProductGroups();
foreach (Database.Models.ProductGroupEntity productGroup in productGroups)
{
@@ -46,17 +50,29 @@ namespace Pos.Setting.Product
}
cmbProductGroup.SelectedIndex = _selectedProductGroupId;
ProductRepository productRepository = new ProductRepository();
_product = productRepository.GetById(_productId);
_product = _productService.GetProduct(_productId);
txtProduct.Text = _product.Name;
}
private void btnEdit_Click(object sender, RoutedEventArgs e)
{
ComboBoxItem item = (ComboBoxItem)cmbProductGroup.SelectedItem;
ProductRepository productRepository = new ProductRepository();
productRepository.Update(_product.Id,(int)item.Tag,txtProduct.Text);
this.Close();
try
{
_productService.UpdateProduct(
_product.Id,
(int)item.Tag,
txtProduct.Text);
Close();
}
catch (ArgumentException exception)
{
MessageBox.Show(
exception.Message,
"Ugyldigt produkt",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
}
}
}

View File

@@ -3,7 +3,7 @@ using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using Database.Repository;
using Pos.Service;
using Pos.Setting.Product;
namespace Pos.Setting.ProductGroup
@@ -14,7 +14,10 @@ namespace Pos.Setting.ProductGroup
public partial class ProductWindow : Window
{
private List<Database.Models.ProductGroupEntity> _productGroups;
private int _productId;
private readonly ProductGroupService _productGroupService =
new ProductGroupService();
private readonly ProductService _productService =
new ProductService();
public ProductWindow()
{
@@ -26,8 +29,7 @@ namespace Pos.Setting.ProductGroup
private void Init()
{
ProductGroupRepository categoryRepository = new ProductGroupRepository();
_productGroups = categoryRepository.GetAll();
_productGroups = _productGroupService.GetProductGroups();
cmbCat.Items.Clear();
foreach (Database.Models.ProductGroupEntity productGroup in _productGroups)
{
@@ -38,14 +40,19 @@ namespace Pos.Setting.ProductGroup
cmbCat.Items.Add(comboBoxItem);
}
cmbCat.SelectedIndex = 0;
if (_productGroups.Count > 0)
cmbCat.SelectedIndex = 0;
}
private void LoadData()
{
ProductRepository productRepository = new ProductRepository();
List<Database.Models.ProductEntity> products = productRepository.GetByProductGroup(_productGroups[cmbCat.SelectedIndex].Id);
if (cmbCat.SelectedIndex < 0)
return;
List<Database.Models.ProductEntity> products =
_productService.GetProducts(
_productGroups[cmbCat.SelectedIndex].Id);
lstProductGroup.Items.Clear();
foreach (Database.Models.ProductEntity product in products)
{

View File

@@ -11,7 +11,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Service;
namespace Pos.Setting.Category
{
@@ -20,6 +20,9 @@ namespace Pos.Setting.Category
/// </summary>
public partial class AddProductGroupWindow : Window
{
private readonly ProductGroupService _productGroupService =
new ProductGroupService();
public AddProductGroupWindow()
{
InitializeComponent();
@@ -27,9 +30,19 @@ namespace Pos.Setting.Category
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
ProductGroupRepository categoryRepository = new ProductGroupRepository();
categoryRepository.Add(txtProductGroup.Text);
this.Close();
try
{
_productGroupService.AddProductGroup(txtProductGroup.Text);
Close();
}
catch (ArgumentException exception)
{
MessageBox.Show(
exception.Message,
"Ugyldig produktgruppe",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
}
}
}

View File

@@ -11,7 +11,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Service;
namespace Pos.Setting.Category
{
@@ -21,6 +21,8 @@ namespace Pos.Setting.Category
public partial class ArchiveProductGroupWindow : Window
{
private readonly int _id;
private readonly ProductGroupService _productGroupService =
new ProductGroupService();
public ArchiveProductGroupWindow(string name, int id)
{
InitializeComponent();
@@ -36,9 +38,8 @@ namespace Pos.Setting.Category
private void btnYes_Click(object sender, RoutedEventArgs e)
{
ProductGroupRepository categoryRepository = new ProductGroupRepository();
categoryRepository.Archive(_id);
this.Close();
_productGroupService.ArchiveProductGroup(_id);
Close();
}
}
}

View File

@@ -12,7 +12,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Service;
namespace Pos.Setting.Category
{
@@ -22,6 +22,8 @@ namespace Pos.Setting.Category
public partial class ChangeProductGroupOrderWindow : Window
{
ObservableCollection<Database.Models.ProductGroupEntity> _prodGroupList = new ObservableCollection<Database.Models.ProductGroupEntity>();
private readonly ProductGroupService _productGroupService =
new ProductGroupService();
public ChangeProductGroupOrderWindow()
{
InitializeComponent();
@@ -30,8 +32,8 @@ namespace Pos.Setting.Category
private void LoadData()
{
ProductGroupRepository categoryRepository = new ProductGroupRepository();
List<Database.Models.ProductGroupEntity> categories = categoryRepository.GetAll();
List<Database.Models.ProductGroupEntity> categories =
_productGroupService.GetProductGroups();
foreach (Database.Models.ProductGroupEntity category in categories)
{
_prodGroupList.Add(category);
@@ -84,13 +86,9 @@ namespace Pos.Setting.Category
private void btnSave_Click(object sender, RoutedEventArgs e)
{
ProductGroupRepository categoryRepository = new ProductGroupRepository();
for (int i = 0; i < _prodGroupList.Count; i++)
{
Database.Models.ProductGroupEntity productGroup = _prodGroupList[i];
categoryRepository.SetIndex(productGroup.Id,i);
}
this.Close();
_productGroupService.UpdateOrder(
_prodGroupList.Select(productGroup => productGroup.Id).ToList());
Close();
}
}
}

View File

@@ -11,7 +11,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Service;
namespace Pos.Setting.Category
{
@@ -21,6 +21,8 @@ namespace Pos.Setting.Category
public partial class EditProductGroupWindow : Window
{
private readonly int _id;
private readonly ProductGroupService _productGroupService =
new ProductGroupService();
public EditProductGroupWindow(string name, int id)
{
@@ -31,9 +33,21 @@ namespace Pos.Setting.Category
private void btnEdit_Click(object sender, RoutedEventArgs e)
{
ProductGroupRepository categoryRepository = new ProductGroupRepository();
categoryRepository.Edit(txtProductGroup.Text,_id);
this.Close();
try
{
_productGroupService.RenameProductGroup(
_id,
txtProductGroup.Text);
Close();
}
catch (ArgumentException exception)
{
MessageBox.Show(
exception.Message,
"Ugyldig produktgruppe",
MessageBoxButton.OK,
MessageBoxImage.Warning);
}
}
}
}

View File

@@ -11,7 +11,7 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Service;
using Pos.Setting.Category;
namespace Pos.Category
@@ -23,6 +23,8 @@ namespace Pos.Category
{
private int _id = -1;
private string _name = string.Empty;
private readonly ProductGroupService _productGroupService =
new ProductGroupService();
public ProductGroupWindow()
{
@@ -53,8 +55,8 @@ namespace Pos.Category
private void LoadData()
{
ProductGroupRepository categoryRepository = new ProductGroupRepository();
List<Database.Models.ProductGroupEntity> categories = categoryRepository.GetAll();
List<Database.Models.ProductGroupEntity> categories =
_productGroupService.GetProductGroups();
LstCategory.Items.Clear();
foreach (Database.Models.ProductGroupEntity category in categories)
{

View File

@@ -11,8 +11,8 @@ using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using Database.Repository;
using Pos.Category;
using Pos.Service;
using Pos.Setting.ProductGroup;
namespace Pos.Setting
@@ -22,6 +22,9 @@ namespace Pos.Setting
/// </summary>
public partial class SettingWindow : Window
{
private readonly ProductGroupService _productGroupService =
new ProductGroupService();
public SettingWindow()
{
InitializeComponent();
@@ -42,10 +45,8 @@ namespace Pos.Setting
private void btnProductGroups_Click(object sender, RoutedEventArgs e)
{
ProductWindow productWindow = new ProductWindow();
//Check if there is any categories, if not close this window with a message.
ProductGroupRepository categoryRepository = new ProductGroupRepository();
bool anyExist = categoryRepository.Any();
bool anyExist = _productGroupService.HasProductGroups();
if (!anyExist)
{
MessageBox.Show("Der er ingen kategorier, opret dem først", "Kategorier", MessageBoxButton.OK,
@@ -53,6 +54,7 @@ namespace Pos.Setting
}
else
{
ProductWindow productWindow = new ProductWindow();
productWindow.Show();
}
}

View File

@@ -1,60 +0,0 @@
//public class Rootobject
//{
// public string logoBase64 { get; set; }
// public Header[] header { get; set; }
// public Bodymodel bodyModel { get; set; }
// public Footer[] footer { get; set; }
//}
//public class Bodymodel
//{
// public Product[] products { get; set; }
// public int totalPrice { get; set; }
// public int totalVat { get; set; }
// public int receiptNumber { get; set; }
// public string receiptTime { get; set; }
// public string staff { get; set; }
//}
//public class Product
//{
// public string product { get; set; }
// public string noOfProduct { get; set; }
// public float price { get; set; }
// public float totalPrice { get; set; }
//}
//public class Header
//{
// public string value { get; set; }
// public Printstyles printStyles { get; set; }
// public int textAlignment { get; set; }
// public int feedingLines { get; set; }
//}
//public class Printstyles
//{
// public bool fontB { get; set; }
// public bool bold { get; set; }
// public bool doubleHeight { get; set; }
// public bool doubleWidth { get; set; }
// public bool underline { get; set; }
//}
//public class Footer
//{
// public string value { get; set; }
// public Printstyles1 printStyles { get; set; }
// public int textAlignment { get; set; }
// public int feedingLines { get; set; }
//}
//public class Printstyles1
//{
// public bool fontB { get; set; }
// public bool bold { get; set; }
// public bool doubleHeight { get; set; }
// public bool doubleWidth { get; set; }
// public bool underline { get; set; }
//}

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<ResourcePreloader />
<link rel="stylesheet" href="@Assets["app.css"]" />
<link rel="stylesheet" href="@Assets["Pos.Web.styles.css"]" />
<ImportMap />
<link rel="icon" type="image/x-icon" href="favicon.ico" />
<HeadOutlet @rendermode="InteractiveServer" />
</head>
<body>
<Routes @rendermode="InteractiveServer" />
<ReconnectModal />
<script src="@Assets["_framework/blazor.web.js"]"></script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
@inherits LayoutComponentBase
<FluentLayout>
<FluentLayoutItem Area="LayoutArea.Content" Class="main">
<div class="content">
@Body
</div>
</FluentLayoutItem>
</FluentLayout>
<FluentDialogProvider />
<div id="blazor-error-ui" data-nosnippet>
An unhandled error has occurred.
<a href="." class="reload">Reload</a>
<span class="dismiss">🗙</span>
</div>

View File

@@ -0,0 +1,31 @@
<script type="module" src="@Assets["Components/Layout/ReconnectModal.razor.js"]"></script>
<dialog id="components-reconnect-modal" data-nosnippet>
<div class="components-reconnect-container">
<div class="components-rejoining-animation" aria-hidden="true">
<div></div>
<div></div>
</div>
<p class="components-reconnect-first-attempt-visible">
Rejoining the server...
</p>
<p class="components-reconnect-repeated-attempt-visible">
Rejoin failed... trying again in <span id="components-seconds-to-next-attempt"></span> seconds.
</p>
<p class="components-reconnect-failed-visible">
Failed to rejoin.<br />Please retry or reload the page.
</p>
<button id="components-reconnect-button" class="components-reconnect-failed-visible">
Retry
</button>
<p class="components-pause-visible">
The session has been paused by the server.
</p>
<p class="components-resume-failed-visible">
Failed to resume the session.<br />Please retry or reload the page.
</p>
<button id="components-resume-button" class="components-pause-visible components-resume-failed-visible">
Resume
</button>
</div>
</dialog>

View File

@@ -0,0 +1,157 @@
.components-reconnect-first-attempt-visible,
.components-reconnect-repeated-attempt-visible,
.components-reconnect-failed-visible,
.components-pause-visible,
.components-resume-failed-visible,
.components-rejoining-animation {
display: none;
}
#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible,
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-paused .components-pause-visible,
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible,
#components-reconnect-modal.components-reconnect-retrying,
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible,
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation,
#components-reconnect-modal.components-reconnect-failed,
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible {
display: block;
}
#components-reconnect-modal {
background-color: white;
width: 20rem;
margin: 20vh auto;
padding: 2rem;
border: 0;
border-radius: 0.5rem;
box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
opacity: 0;
transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
animation: components-reconnect-modal-fadeOutOpacity 0.5s both;
&[open]
{
animation: components-reconnect-modal-slideUp 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity 0.5s ease-in-out 0.3s;
animation-fill-mode: both;
}
}
#components-reconnect-modal::backdrop {
background-color: rgba(0, 0, 0, 0.4);
animation: components-reconnect-modal-fadeInOpacity 0.5s ease-in-out;
opacity: 1;
}
@keyframes components-reconnect-modal-slideUp {
0% {
transform: translateY(30px) scale(0.95);
}
100% {
transform: translateY(0);
}
}
@keyframes components-reconnect-modal-fadeInOpacity {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes components-reconnect-modal-fadeOutOpacity {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.components-reconnect-container {
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
}
#components-reconnect-modal p {
margin: 0;
text-align: center;
}
#components-reconnect-modal button {
border: 0;
background-color: #6b9ed2;
color: white;
padding: 4px 24px;
border-radius: 4px;
}
#components-reconnect-modal button:hover {
background-color: #3b6ea2;
}
#components-reconnect-modal button:active {
background-color: #6b9ed2;
}
.components-rejoining-animation {
position: relative;
width: 80px;
height: 80px;
}
.components-rejoining-animation div {
position: absolute;
border: 3px solid #0087ff;
opacity: 1;
border-radius: 50%;
animation: components-rejoining-animation 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.components-rejoining-animation div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes components-rejoining-animation {
0% {
top: 40px;
left: 40px;
width: 0;
height: 0;
opacity: 0;
}
4.9% {
top: 40px;
left: 40px;
width: 0;
height: 0;
opacity: 0;
}
5% {
top: 40px;
left: 40px;
width: 0;
height: 0;
opacity: 1;
}
100% {
top: 0px;
left: 0px;
width: 80px;
height: 80px;
opacity: 0;
}
}

View File

@@ -0,0 +1,63 @@
// Set up event handlers
const reconnectModal = document.getElementById("components-reconnect-modal");
reconnectModal.addEventListener("components-reconnect-state-changed", handleReconnectStateChanged);
const retryButton = document.getElementById("components-reconnect-button");
retryButton.addEventListener("click", retry);
const resumeButton = document.getElementById("components-resume-button");
resumeButton.addEventListener("click", resume);
function handleReconnectStateChanged(event) {
if (event.detail.state === "show") {
reconnectModal.showModal();
} else if (event.detail.state === "hide") {
reconnectModal.close();
} else if (event.detail.state === "failed") {
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
} else if (event.detail.state === "rejected") {
location.reload();
}
}
async function retry() {
document.removeEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
try {
// Reconnect will asynchronously return:
// - true to mean success
// - false to mean we reached the server, but it rejected the connection (e.g., unknown circuit ID)
// - exception to mean we didn't reach the server (this can be sync or async)
const successful = await Blazor.reconnect();
if (!successful) {
// We have been able to reach the server, but the circuit is no longer available.
// We'll reload the page so the user can continue using the app as quickly as possible.
const resumeSuccessful = await Blazor.resumeCircuit();
if (!resumeSuccessful) {
location.reload();
} else {
reconnectModal.close();
}
}
} catch (err) {
// We got an exception, server is currently unavailable
document.addEventListener("visibilitychange", retryWhenDocumentBecomesVisible);
}
}
async function resume() {
try {
const successful = await Blazor.resumeCircuit();
if (!successful) {
location.reload();
}
} catch {
reconnectModal.classList.replace("components-reconnect-paused", "components-reconnect-resume-failed");
}
}
async function retryWhenDocumentBecomesVisible() {
if (document.visibilityState === "visible") {
await retry();
}
}

View File

@@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>Development Mode</h3>
<p>
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}

View File

@@ -0,0 +1,21 @@
@rendermode InteractiveServer
@inject NavigationManager Nav
@page "/"
<Header/>
<div class="home-container">
<FluentButton Class="menu-button" Size="ButtonSize.Large">
Kassesalg
</FluentButton>
<FluentButton Class="menu-button" Size="ButtonSize.Large">
Dagens salg
</FluentButton>
<FluentButton Class="menu-button" Size="ButtonSize.Large">
Print Bon
</FluentButton>
<FluentButton Class="menu-button" Size="ButtonSize.Large" onclick="@(() => Nav.NavigateTo("/settings"))">
Indstillinger
</FluentButton>
</div>

View File

@@ -0,0 +1,14 @@
.home-container {
width: min(100%, 1100px);
margin: 32px auto;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px;
}
.home-container ::deep .menu-button {
width: 100%;
min-height: 220px;
font-size: 28px;
font-weight: 600;
}

View File

@@ -0,0 +1,5 @@
@page "/not-found"
@layout MainLayout
<h3>Not Found</h3>
<p>Sorry, the content you are looking for does not exist.</p>

View File

@@ -0,0 +1,44 @@
@using Pos.Service
@inject EmployeeService EmployeeService
@if (IsOpen)
{
<div class="dialog-overlay">
<div class="employee-dialog" role="dialog" aria-modal="true" aria-labelledby="create-employee-title">
<div class="dialog-header">
<h2 id="create-employee-title">Opret medarbejder</h2>
<button class="close-button"
type="button"
aria-label="Luk"
@onclick="CloseAsync">
&times;
</button>
</div>
<div class="dialog-content">
<label for="employee-name">Navn</label>
<input id="employee-name"
class="name-input"
type="text"
placeholder="Indtast navn"
@bind="_name"
@bind:event="oninput" />
<p>Medarbejderen bliver aktiv med det samme.</p>
</div>
<div class="dialog-actions">
<button class="cancel-button"
type="button"
@onclick="CloseAsync">
Annuller
</button>
<button class="create-button"
type="button"
disabled="@string.IsNullOrWhiteSpace(_name)"
@onclick="CreateEmployeeAsync">
Opret medarbejder
</button>
</div>
</div>
</div>
}

View File

@@ -0,0 +1,35 @@
using Database.Models;
using Microsoft.AspNetCore.Components;
using Pos.Service;
namespace Pos.Web.Components.Pages.Settings
{
public partial class CreateEmployeeDialog
{
[Parameter]
public bool IsOpen { get; set; }
[Parameter]
public EventCallback<bool> IsOpenChanged { get; set; }
[Parameter]
public EventCallback<EmployeeEntity> EmployeeCreated { get; set; }
private string _name = string.Empty;
private async Task CreateEmployeeAsync()
{
EmployeeEntity employee =
await EmployeeService.AddEmployeeAsync(_name);
await EmployeeCreated.InvokeAsync(employee);
await CloseAsync();
}
private async Task CloseAsync()
{
_name = string.Empty;
await IsOpenChanged.InvokeAsync(false);
}
}
}

View File

@@ -0,0 +1,132 @@
.dialog-overlay {
position: fixed;
inset: 0;
z-index: 1000;
display: grid;
place-items: center;
padding: 24px;
background: rgb(15 23 42 / 45%);
}
.employee-dialog {
width: min(100%, 650px);
overflow: hidden;
border-radius: 12px;
background: white;
color: #242424;
color-scheme: light;
}
.dialog-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28px 40px 18px;
}
.dialog-header h2 {
margin: 0;
font-size: 28px;
}
.close-button {
border: 0;
background: transparent;
color: #334155;
cursor: pointer;
font-size: 36px;
line-height: 1;
}
.dialog-content {
display: grid;
gap: 10px;
padding: 18px 40px 36px;
}
.dialog-content label {
color: #242424;
font-size: 16px;
font-weight: 500;
}
.name-input {
box-sizing: border-box;
width: 100%;
height: 48px;
padding: 0 14px;
border: 1px solid #8a8a8a;
border-radius: 6px;
outline: none;
background: #ffffff;
color: #242424;
font: inherit;
}
.name-input::placeholder {
color: #707070;
}
.name-input:focus {
border-color: #0f6cbd;
box-shadow: 0 0 0 1px #0f6cbd;
}
.dialog-content p {
margin: 6px 0 0;
color: #64748b;
}
.dialog-actions {
display: flex;
justify-content: flex-end;
gap: 16px;
padding: 24px 40px;
border-top: 1px solid #e2e8f0;
}
.cancel-button,
.create-button {
min-height: 42px;
padding: 0 22px;
border-radius: 6px;
cursor: pointer;
font: inherit;
font-weight: 500;
}
.cancel-button {
border: 1px solid #616161;
background: #ffffff;
color: #242424;
}
.cancel-button:hover {
background: #f5f5f5;
}
.create-button {
border: 1px solid #0f6cbd;
background: #0f6cbd;
color: #ffffff;
}
.create-button:hover:not(:disabled) {
background: #115ea3;
}
.create-button:disabled {
border-color: #d1d1d1;
background: #e0e0e0;
color: #707070;
cursor: not-allowed;
}
@media (max-width: 600px) {
.dialog-header,
.dialog-content,
.dialog-actions {
padding-right: 24px;
padding-left: 24px;
}
}

View File

@@ -0,0 +1,45 @@
@page "/settings/employees"
@inject IDialogService DialogService
@rendermode InteractiveServer
@using Database.Models
@using Pos.Service
@inject EmployeeService EmployeeService
<Header />
<div class="employees-page">
<div class="employees-header">
<h1>Medarbejdere</h1>
<FluentButton Appearance="@ButtonAppearance.Primary"
OnClick="@(() => _showCreateDialog = true)">
Opret medarbejder
</FluentButton>
</div>
@if (_isLoading)
{
<FluentProgressRing />
}
else
{
<FluentDataGrid TGridItem="EmployeeEntity"
Items="@_employees.AsQueryable()"
GridTemplateColumns="1fr 180px">
<PropertyColumn Property="@(item => item.Name)"
Title="Navn"
Sortable="true" />
<TemplateColumn Title="Handling" Context="employee">
<FluentButton Class="deactivate-button"
Appearance="@ButtonAppearance.Outline"
OnClick="@(() => ConfirmDeactivation(employee))">
Deaktivér
</FluentButton>
</TemplateColumn>
</FluentDataGrid>
}
</div>
<CreateEmployeeDialog @bind-IsOpen="_showCreateDialog"
EmployeeCreated="EmployeeCreated" />

View File

@@ -0,0 +1,53 @@
using Database.Models;
using Microsoft.FluentUI.AspNetCore.Components;
namespace Pos.Web.Components.Pages.Settings
{
public partial class Employees
{
private List<EmployeeEntity> _employees = [];
private bool _isLoading = true;
private bool _showCreateDialog;
protected override async Task OnInitializedAsync()
{
_employees = await EmployeeService.GetEmployeesAsync();
_isLoading = false;
}
private void LoadEmployees()
{
_employees = EmployeeService.GetEmployees();
}
private async Task ConfirmDeactivation(EmployeeEntity employee)
{
DialogResult result = await DialogService.ShowConfirmationAsync(
$"Er du sikker på, at du vil deaktivere {employee.Name}?",
"Bekræft deaktivering",
"Annuller",
"Deaktivér");
if (result.Cancelled)
{
DeactivateEmployee(employee);
}
}
private void DeactivateEmployee(EmployeeEntity employee)
{
EmployeeService.ArchiveEmployee(employee.Id);
LoadEmployees();
}
private class EmployeeRow
{
public string Name { get; set; } = string.Empty;
}
private void EmployeeCreated(EmployeeEntity employee)
{
_employees.Add(employee);
}
}
}

View File

@@ -0,0 +1,26 @@
/* CSS for Employees component */
.employees-page {
width: min(100%, 1100px);
margin: 0 auto;
padding: 32px;
}
.employees-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 24px;
}
.employees-header h1 {
margin: 0;
}
.employees-page ::deep fluent-data-grid {
width: 100%;
}
.employees-page ::deep .deactivate-button {
color: #b10e1e;
border-color: #b10e1e;
}

View File

@@ -0,0 +1,20 @@
@rendermode InteractiveServer
@inject NavigationManager Nav
@page "/settings"
<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>

View File

@@ -0,0 +1,24 @@
.settings-page {
width: min(100%, 1100px);
margin: 0 auto;
padding: 32px;
}
.settings-page h1 {
margin-bottom: 12px;
color: var(--colorNeutralForeground1);
}
.settings-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 24px;
margin-top: 32px;
}
.settings-grid ::deep .settings-button {
width: 100%;
min-height: 220px;
font-size: 28px;
font-weight: 600;
}

View File

@@ -0,0 +1,5 @@
<Router AppAssembly="typeof(Program).Assembly" NotFoundPage="typeof(Pages.NotFound)">
<Found Context="routeData">
<RouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)" />
</Found>
</Router>

View File

@@ -0,0 +1,12 @@
@rendermode InteractiveServer
@implements IDisposable
<header class="top-nav">
<div class="brand">Blomster POS</div>
<time datetime="@_currentTime.ToString("O")">
@_currentTime.ToString(
"dddd d. MMMM yyyy · HH:mm",
_danishCulture)
</time>
</header>

View File

@@ -0,0 +1,33 @@
using System.Globalization;
namespace Pos.Web.Components.Shared
{
public partial class Header
{
private readonly CultureInfo _danishCulture =
CultureInfo.GetCultureInfo("da-DK");
private readonly Timer _timer;
private DateTime _currentTime = DateTime.Now;
public Header()
{
_timer = new Timer(
UpdateTime,
null,
TimeSpan.FromMinutes(1),
TimeSpan.FromMinutes(1));
}
private void UpdateTime(object? state)
{
_currentTime = DateTime.Now;
_ = InvokeAsync(StateHasChanged);
}
public void Dispose()
{
_timer.Dispose();
}
}
}

View File

@@ -0,0 +1,24 @@
/* CSS for Header component */
.top-nav {
min-height: 72px;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: space-between;
gap: 24px;
padding: 0 32px;
background: var(--colorNeutralBackground1);
border-bottom: 1px solid var(--colorNeutralStroke2);
}
.brand {
color: var(--colorNeutralForeground1);
font-size: 26px;
font-weight: 600;
}
time {
color: var(--colorNeutralForeground2);
font-size: 18px;
white-space: nowrap;
}

View File

@@ -0,0 +1,14 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.FluentUI.AspNetCore.Components
@using Icons = Microsoft.FluentUI.AspNetCore.Components.Icons
@using Microsoft.JSInterop
@using Pos.Web
@using Pos.Web.Components
@using Pos.Web.Components.Layout
@using Pos.Web.Components.Shared

View File

@@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components" Version="5.0.0-rc.4-26180.1" />
<PackageReference Include="Microsoft.FluentUI.AspNetCore.Components.Icons" Version="4.14.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Pos.Models\Pos.Models.csproj" />
<ProjectReference Include="..\Pos.ServiceDefaults\Pos.ServiceDefaults.csproj" />
<ProjectReference Include="..\Pos.Service\Pos.Service.csproj" />
<ProjectReference Include="..\Pos.Ui\Database\Database.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,35 @@
using Microsoft.FluentUI.AspNetCore.Components;
using Pos.Web.Components;
using Pos.Service;
var builder = WebApplication.CreateBuilder(args);
builder.AddServiceDefaults();
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddFluentUIComponents();
builder.Services.AddScoped<EmployeeService>();
var app = builder.Build();
app.MapDefaultEndpoints();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true);
app.UseHttpsRedirection();
app.UseAntiforgery();
app.MapStaticAssets();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
app.Run();

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "http://localhost:5281",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"https": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"applicationUrl": "https://localhost:7102;http://localhost:5281",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

View File

@@ -0,0 +1,10 @@
{
"SqlServer": "Server=(localdb)\\MSSQLLocalDB;Database=pointofsale;Trusted_Connection=True;TrustServerCertificate=True;",
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}

View File

@@ -0,0 +1,89 @@
@import '/_content/Microsoft.FluentUI.AspNetCore.Components/css/reboot.css';
body {
--body-font: "Segoe UI Variable", "Segoe UI", sans-serif;
margin: 0;
font-family: var(--body-font);
font-size: var(--type-ramp-base-font-size);
line-height: var(--type-ramp-base-line-height);
}
.main {
min-height: 100dvh;
color: var(--neutral-foreground-rest);
align-items: stretch !important;
}
.content {
box-sizing: border-box;
width: 100%;
padding: 0.5rem 1.5rem;
align-self: stretch !important;
}
#blazor-error-ui {
position: fixed;
bottom: 0;
left: 0;
z-index: 1000;
box-sizing: border-box;
display: none;
width: 100%;
padding: 0.6rem 1.25rem 0.7rem;
background: lightyellow;
box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
}
#blazor-error-ui .dismiss {
cursor: pointer;
position: absolute;
right: 0.75rem;
top: 0.5rem;
}
.blazor-error-boundary {
padding: 1rem 1rem 1rem 3.7rem;
color: white;
background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
}
.blazor-error-boundary::before {
content: "An error has occurred. ";
}
.loading-progress {
position: relative;
display: block;
width: 8rem;
height: 8rem;
margin: 20vh auto 1rem;
}
.loading-progress circle {
fill: none;
stroke: #e0e0e0;
stroke-width: 0.6rem;
transform-origin: 50% 50%;
transform: rotate(-90deg);
}
.loading-progress circle:last-child {
stroke: #1b6ec2;
stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%;
transition: stroke-dasharray 0.05s ease-in-out;
}
.loading-progress-text {
position: absolute;
inset: calc(20vh + 3.25rem) 0 auto 0.2rem;
font-weight: bold;
text-align: center;
}
.loading-progress-text::after {
content: var(--blazor-load-percentage-text, "Loading");
}
code {
color: #c02d76;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -13,12 +13,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EpsonPrinter", "EpsonPrinte
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.AppHost", "Pos.AppHost\Pos.AppHost.csproj", "{53EFB4EB-931D-419B-8399-4E389011076C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.ServiceDefaults", "Pos.ServiceDefaults\Pos.ServiceDefaults.csproj", "{EDC882E6-4983-35BD-3357-E433A3E64369}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Pos.Test", "Pos.Test", "{9051E89F-15CC-40A4-8C60-82C98151139E}"
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{9051E89F-15CC-40A4-8C60-82C98151139E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "IntegrationTests\IntegrationTests.csproj", "{04551552-086E-4CF4-AFD3-A4E51CD36182}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.Web", "Pos.Web\Pos.Web.csproj", "{81AEF9BF-15E9-414D-9F18-345631896022}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.Service", "Pos.Service\Pos.Service.csproj", "{563C2EC1-B243-4A66-BE53-88A0BA216706}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.Models", "Pos.Models\Pos.Models.csproj", "{D6EA5236-CEFB-4889-9E67-4983D1A016FA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pos.ServiceDefaults", "Pos.ServiceDefaults\Pos.ServiceDefaults.csproj", "{EDC882E6-4983-35BD-3357-E433A3E64369}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -41,14 +47,26 @@ Global
{53EFB4EB-931D-419B-8399-4E389011076C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53EFB4EB-931D-419B-8399-4E389011076C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{53EFB4EB-931D-419B-8399-4E389011076C}.Release|Any CPU.Build.0 = Release|Any CPU
{EDC882E6-4983-35BD-3357-E433A3E64369}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EDC882E6-4983-35BD-3357-E433A3E64369}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EDC882E6-4983-35BD-3357-E433A3E64369}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EDC882E6-4983-35BD-3357-E433A3E64369}.Release|Any CPU.Build.0 = Release|Any CPU
{04551552-086E-4CF4-AFD3-A4E51CD36182}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{04551552-086E-4CF4-AFD3-A4E51CD36182}.Debug|Any CPU.Build.0 = Debug|Any CPU
{04551552-086E-4CF4-AFD3-A4E51CD36182}.Release|Any CPU.ActiveCfg = Release|Any CPU
{04551552-086E-4CF4-AFD3-A4E51CD36182}.Release|Any CPU.Build.0 = Release|Any CPU
{81AEF9BF-15E9-414D-9F18-345631896022}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{81AEF9BF-15E9-414D-9F18-345631896022}.Debug|Any CPU.Build.0 = Debug|Any CPU
{81AEF9BF-15E9-414D-9F18-345631896022}.Release|Any CPU.ActiveCfg = Release|Any CPU
{81AEF9BF-15E9-414D-9F18-345631896022}.Release|Any CPU.Build.0 = Release|Any CPU
{563C2EC1-B243-4A66-BE53-88A0BA216706}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{563C2EC1-B243-4A66-BE53-88A0BA216706}.Debug|Any CPU.Build.0 = Debug|Any CPU
{563C2EC1-B243-4A66-BE53-88A0BA216706}.Release|Any CPU.ActiveCfg = Release|Any CPU
{563C2EC1-B243-4A66-BE53-88A0BA216706}.Release|Any CPU.Build.0 = Release|Any CPU
{D6EA5236-CEFB-4889-9E67-4983D1A016FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D6EA5236-CEFB-4889-9E67-4983D1A016FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D6EA5236-CEFB-4889-9E67-4983D1A016FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D6EA5236-CEFB-4889-9E67-4983D1A016FA}.Release|Any CPU.Build.0 = Release|Any CPU
{EDC882E6-4983-35BD-3357-E433A3E64369}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EDC882E6-4983-35BD-3357-E433A3E64369}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EDC882E6-4983-35BD-3357-E433A3E64369}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EDC882E6-4983-35BD-3357-E433A3E64369}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

BIN
layout/DagensSalg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 887 KiB

BIN
layout/Forside.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 852 KiB

BIN
layout/Indstillinger.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 864 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1011 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1019 KiB

BIN
layout/PrintBon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 931 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB