Compare commits
8 Commits
2c0f6f1cab
...
main
Author | SHA1 | Date | |
---|---|---|---|
ba5766f9d5
|
|||
25876813e5
|
|||
680458b0f3
|
|||
79850c46b5
|
|||
e5478a426b
|
|||
d089065208
|
|||
d64870bf4f
|
|||
00e17497bf
|
@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1.7-labs
|
# syntax=docker/dockerfile:1.7-labs
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build1
|
FROM mcr.microsoft.com/dotnet/sdk:10.0-preview-alpine AS build1
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY ./.config ./
|
COPY ./.config ./
|
||||||
@ -10,7 +10,7 @@ WORKDIR Groceries
|
|||||||
COPY ./Groceries/libman.json ./
|
COPY ./Groceries/libman.json ./
|
||||||
RUN dotnet libman restore
|
RUN dotnet libman restore
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build2
|
FROM mcr.microsoft.com/dotnet/sdk:10.0-preview-alpine AS build2
|
||||||
|
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
COPY ./Groceries.sln ./
|
COPY ./Groceries.sln ./
|
||||||
@ -23,7 +23,7 @@ COPY . ./
|
|||||||
COPY --from=build1 /src ./
|
COPY --from=build1 /src ./
|
||||||
RUN dotnet publish --no-restore --output /out
|
RUN dotnet publish --no-restore --output /out
|
||||||
|
|
||||||
FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine-composite AS base
|
FROM mcr.microsoft.com/dotnet/aspnet:10.0-preview-alpine-composite AS base
|
||||||
WORKDIR /groceries
|
WORKDIR /groceries
|
||||||
|
|
||||||
COPY --from=build2 /out .
|
COPY --from=build2 /out .
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||||
@ -9,10 +9,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="DbUp-PostgreSQL" Version="5.0.40" />
|
<PackageReference Include="DbUp-PostgreSQL" Version="6.0.3" />
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="8.0.3" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
<PackageReference Include="Humanizer.Core" Version="2.14.1" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.8" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.0-preview.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@ -11,6 +11,7 @@
|
|||||||
|
|
||||||
<base href="/" />
|
<base href="/" />
|
||||||
|
|
||||||
|
<link rel="preload" as="font" type="font/woff2" href="lib/inter/files/inter-latin-wght-normal.woff2" crossorigin />
|
||||||
<link rel="stylesheet" type="text/css" href="@Assets["lib/inter/index.css"]" data-turbo-track="reload" />
|
<link rel="stylesheet" type="text/css" href="@Assets["lib/inter/index.css"]" data-turbo-track="reload" />
|
||||||
<link rel="stylesheet" type="text/css" href="@Assets["css/main.css"]" data-turbo-track="reload" />
|
<link rel="stylesheet" type="text/css" href="@Assets["css/main.css"]" data-turbo-track="reload" />
|
||||||
|
|
||||||
|
@ -176,6 +176,12 @@
|
|||||||
{
|
{
|
||||||
string?[] classes = [
|
string?[] classes = [
|
||||||
"table__header",
|
"table__header",
|
||||||
|
column.Align switch
|
||||||
|
{
|
||||||
|
Align.Center => "table__header--align-center",
|
||||||
|
Align.End => "table__header--align-end",
|
||||||
|
_ => null,
|
||||||
|
},
|
||||||
column.Sortable ? "table__header--sortable" : null,
|
column.Sortable ? "table__header--sortable" : null,
|
||||||
HeaderClass,
|
HeaderClass,
|
||||||
];
|
];
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net9.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<WarningsAsErrors>nullable</WarningsAsErrors>
|
<WarningsAsErrors>nullable</WarningsAsErrors>
|
||||||
|
@ -32,10 +32,10 @@ public static class HttpRequestExtensions
|
|||||||
return origin.IsBaseOf(uri);
|
return origin.IsBaseOf(uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri? GetRefererIfSameOrigin(this HttpRequest request)
|
public static Uri? GetReferrerIfSameOrigin(this HttpRequest request)
|
||||||
{
|
{
|
||||||
var referer = request.GetTypedHeaders().Referer;
|
var referrer = request.GetTypedHeaders().Referer;
|
||||||
return referer != null && request.IsSameOrigin(referer) ? referer : null;
|
return referrer != null && request.IsSameOrigin(referrer) ? referrer : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsTurboFrameRequest(this HttpRequest request, string frameId)
|
public static bool IsTurboFrameRequest(this HttpRequest request, string frameId)
|
||||||
|
@ -65,19 +65,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
items = itemsQuery
|
items = itemsQuery
|
||||||
.GroupJoin(
|
.LeftJoin(
|
||||||
dbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase),
|
dbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase),
|
||||||
item => item.Id,
|
item => item.Id,
|
||||||
purchase => purchase.ItemId,
|
purchase => purchase.ItemId,
|
||||||
(item, purchases) => new { item, purchases })
|
(item, lastPurchase) => new ItemModel
|
||||||
.SelectMany(
|
|
||||||
group => group.purchases.DefaultIfEmpty(),
|
|
||||||
(group, lastPurchase) => new ItemModel
|
|
||||||
{
|
{
|
||||||
Id = group.item.Id,
|
Id = item.Id,
|
||||||
Brand = group.item.Brand,
|
Brand = item.Brand,
|
||||||
Name = group.item.Name,
|
Name = item.Name,
|
||||||
HasBarcode = group.item.Barcodes.Count != 0,
|
HasBarcode = item.Barcodes.Count > 0,
|
||||||
LastPurchasedAt = lastPurchase != null ? lastPurchase.CreatedAt : null,
|
LastPurchasedAt = lastPurchase != null ? lastPurchase.CreatedAt : null,
|
||||||
})
|
})
|
||||||
.OrderBy(item => item.Brand)
|
.OrderBy(item => item.Brand)
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using DbUp;
|
using DbUp;
|
||||||
|
using DbUp.Engine.Output;
|
||||||
using Groceries.Data;
|
using Groceries.Data;
|
||||||
using Microsoft.AspNetCore.DataProtection;
|
using Microsoft.AspNetCore.DataProtection;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@ -13,20 +14,6 @@ builder.Configuration
|
|||||||
.AddIniFile(Path.Combine(dataDir, $"config_{env.EnvironmentName}.ini"), optional: true, reloadOnChange: true);
|
.AddIniFile(Path.Combine(dataDir, $"config_{env.EnvironmentName}.ini"), optional: true, reloadOnChange: true);
|
||||||
|
|
||||||
var dbConn = builder.Configuration["Database"]!;
|
var dbConn = builder.Configuration["Database"]!;
|
||||||
EnsureDatabase.For.PostgresqlDatabase(dbConn);
|
|
||||||
|
|
||||||
var dbUpgradeResult = DeployChanges.To
|
|
||||||
.PostgresqlDatabase(dbConn)
|
|
||||||
.JournalToPostgresqlTable("public", "__dbup_migrations")
|
|
||||||
.WithScriptsEmbeddedInAssembly(typeof(AppDbContext).Assembly)
|
|
||||||
.WithTransactionPerScript()
|
|
||||||
.Build()
|
|
||||||
.PerformUpgrade();
|
|
||||||
|
|
||||||
if (!dbUpgradeResult.Successful)
|
|
||||||
{
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dataProtection = builder.Services.AddDataProtection();
|
var dataProtection = builder.Services.AddDataProtection();
|
||||||
if (env.IsProduction())
|
if (env.IsProduction())
|
||||||
@ -58,6 +45,22 @@ app.MapStaticAssets();
|
|||||||
app.MapControllers()
|
app.MapControllers()
|
||||||
.WithStaticAssets();
|
.WithStaticAssets();
|
||||||
|
|
||||||
await app.RunAsync();
|
var dbUpgradeLogger = new MicrosoftUpgradeLog(app.Logger);
|
||||||
|
EnsureDatabase.For.PostgresqlDatabase(dbConn, dbUpgradeLogger);
|
||||||
|
|
||||||
return 0;
|
var dbUpgradeResult = DeployChanges.To
|
||||||
|
.PostgresqlDatabase(dbConn)
|
||||||
|
.JournalToPostgresqlTable("public", "__dbup_migrations")
|
||||||
|
.WithScriptsEmbeddedInAssembly(typeof(AppDbContext).Assembly)
|
||||||
|
.WithTransactionPerScript()
|
||||||
|
.LogTo(dbUpgradeLogger)
|
||||||
|
.Build()
|
||||||
|
.PerformUpgrade();
|
||||||
|
|
||||||
|
if (!dbUpgradeResult.Successful)
|
||||||
|
{
|
||||||
|
Environment.Exit(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.Run();
|
||||||
|
@ -24,9 +24,9 @@
|
|||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
var request = HttpContextAccessor.HttpContext!.Request;
|
var request = HttpContextAccessor.HttpContext!.Request;
|
||||||
if (request.GetRefererIfSameOrigin() is Uri referer && referer != request.GetUri())
|
if (request.GetReferrerIfSameOrigin() is Uri referrer && referrer != request.GetUri())
|
||||||
{
|
{
|
||||||
returnUrl = referer.PathAndQuery;
|
returnUrl = referrer.PathAndQuery;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,9 +19,9 @@
|
|||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
var request = HttpContextAccessor.HttpContext!.Request;
|
var request = HttpContextAccessor.HttpContext!.Request;
|
||||||
if (request.GetRefererIfSameOrigin() is Uri referer && referer != request.GetUri())
|
if (request.GetReferrerIfSameOrigin() is Uri referrer && referrer != request.GetUri())
|
||||||
{
|
{
|
||||||
returnUrl = referer.PathAndQuery;
|
returnUrl = referrer.PathAndQuery;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,10 +33,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card__content card__content--table">
|
<div class="card__content card__content--table">
|
||||||
<Table Items="Transaction.Items.AsQueryable()" CellClass="table__cell--compact">
|
<Table Items="Transaction.Items.AsQueryable()" HeaderClass="table__header--compact" CellClass="table__cell--compact">
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<TemplateTableColumn Title="Name" Fill="true" Context="item">
|
<TemplateTableColumn Title="Name" Fill="true" Context="item">
|
||||||
@itemNames.GetValueOrDefault(item.ItemId)
|
<div class="line-clamp-4">@itemNames.GetValueOrDefault(item.ItemId)</div>
|
||||||
</TemplateTableColumn>
|
</TemplateTableColumn>
|
||||||
<PropertyTableColumn Property="i => i.Price" CompositeFormat='i => i.Unit == null ? "{0:c}" : ("{0:c}/" + i.Unit)' />
|
<PropertyTableColumn Property="i => i.Price" CompositeFormat='i => i.Unit == null ? "{0:c}" : ("{0:c}/" + i.Unit)' />
|
||||||
<PropertyTableColumn Property="i => i.Quantity" CompositeFormat='i => i.Unit == null ? "{0:f0}" : ("{0:f3}" + i.Unit)'>
|
<PropertyTableColumn Property="i => i.Quantity" CompositeFormat='i => i.Unit == null ? "{0:f0}" : ("{0:f3}" + i.Unit)'>
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="card__content card__content--table">
|
<div class="card__content card__content--table">
|
||||||
<Table Items="Transaction.Promotions.AsQueryable()" CellClass="table__cell--compact">
|
<Table Items="Transaction.Promotions.AsQueryable()" HeaderClass="table__header--compact" CellClass="table__cell--compact">
|
||||||
<ChildContent>
|
<ChildContent>
|
||||||
<PropertyTableColumn Property="p => p.Name" Fill="true" />
|
<PropertyTableColumn Property="p => p.Name" Fill="true" />
|
||||||
<TemplateTableColumn Title="Items" Align="Align.End" Context="promotion">
|
<TemplateTableColumn Title="Items" Align="Align.End" Context="promotion">
|
||||||
|
@ -58,7 +58,7 @@
|
|||||||
</label>
|
</label>
|
||||||
<div class="form-field__control input">
|
<div class="form-field__control input">
|
||||||
@{ var step = unit == null ? "1" : "0.001"; }
|
@{ var step = unit == null ? "1" : "0.001"; }
|
||||||
<input class="input__control" id="transactionItemQuantity" name="quantity" value="@quantity" type="number" min="@step" step="@step" required />
|
<input class="input__control" id="transactionItemQuantity" name="quantity" value="@(unit == null ? (int?)quantity : quantity)" type="number" min="@step" step="@step" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -97,17 +97,14 @@
|
|||||||
items = await dbContext.Items
|
items = await dbContext.Items
|
||||||
.OrderBy(item => item.Brand)
|
.OrderBy(item => item.Brand)
|
||||||
.ThenBy(item => item.Name)
|
.ThenBy(item => item.Name)
|
||||||
.GroupJoin(
|
.LeftJoin(
|
||||||
dbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase),
|
dbContext.ItemPurchases.Where(purchase => purchase.IsLastPurchase),
|
||||||
item => item.Id,
|
item => item.Id,
|
||||||
lastPurchase => lastPurchase.ItemId,
|
lastPurchase => lastPurchase.ItemId,
|
||||||
(item, purchases) => new { item, purchases })
|
(item, lastPurchase) => new ItemModel(
|
||||||
.SelectMany(
|
item.Id,
|
||||||
group => group.purchases.DefaultIfEmpty(),
|
item.Brand,
|
||||||
(group, lastPurchase) => new ItemModel(
|
item.Name,
|
||||||
group.item.Id,
|
|
||||||
group.item.Brand,
|
|
||||||
group.item.Name,
|
|
||||||
lastPurchase != null ? lastPurchase.Price : null,
|
lastPurchase != null ? lastPurchase.Price : null,
|
||||||
lastPurchase != null ? lastPurchase.Quantity : null))
|
lastPurchase != null ? lastPurchase.Quantity : null))
|
||||||
.ToArrayAsync();
|
.ToArrayAsync();
|
||||||
|
@ -243,11 +243,13 @@ public class TransactionsController : Controller
|
|||||||
|
|
||||||
using var dbContext = dbContextFactory.CreateDbContext();
|
using var dbContext = dbContextFactory.CreateDbContext();
|
||||||
|
|
||||||
|
foreach (var item in transaction.Items)
|
||||||
|
{
|
||||||
|
item.Item = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Work around EF trying to insert items by explicitly tracking them as unchanged
|
// Work around EF trying to insert items by explicitly tracking them as unchanged
|
||||||
dbContext.Items.AttachRange(
|
dbContext.Items.AttachRange(transaction.Promotions.SelectMany(promotion => promotion.Items));
|
||||||
transaction.Items
|
|
||||||
.Select(item => item.Item!)
|
|
||||||
.Concat(transaction.Promotions.SelectMany(promotion => promotion.Items)));
|
|
||||||
|
|
||||||
dbContext.Transactions.Add(transaction);
|
dbContext.Transactions.Add(transaction);
|
||||||
await dbContext.SaveChangesAsync();
|
await dbContext.SaveChangesAsync();
|
||||||
|
@ -3,11 +3,11 @@
|
|||||||
"defaultProvider": "unpkg",
|
"defaultProvider": "unpkg",
|
||||||
"libraries": [
|
"libraries": [
|
||||||
{
|
{
|
||||||
"library": "@fontsource-variable/inter@5.1.0",
|
"library": "@fontsource-variable/inter@5.2.5",
|
||||||
"destination": "wwwroot/lib/inter/"
|
"destination": "wwwroot/lib/inter/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"library": "@hotwired/turbo@8.0.10",
|
"library": "@hotwired/turbo@8.0.13",
|
||||||
"destination": "wwwroot/lib/hotwired/turbo/"
|
"destination": "wwwroot/lib/hotwired/turbo/"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -69,6 +69,14 @@ h1, h2, h3, h4, h5, h6 {
|
|||||||
flex: 5;
|
flex: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.line-clamp-4 {
|
||||||
|
overflow: hidden;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 4;
|
||||||
|
white-space: initial;
|
||||||
|
}
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
@ -312,6 +320,12 @@ html:has(.modal[open]) {
|
|||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 40rem) {
|
||||||
|
.card__header, .card__footer {
|
||||||
|
padding-inline: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Table */
|
/* Table */
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
@ -374,15 +388,15 @@ html:has(.modal[open]) {
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table__cell--align-center {
|
.table__header--align-center, .table__cell--align-center {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table__cell--align-end {
|
.table__header--align-end, .table__cell--align-end {
|
||||||
text-align: end;
|
text-align: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table__cell--compact {
|
.table__header--compact, .table__cell--compact {
|
||||||
line-height: 1rem;
|
line-height: 1rem;
|
||||||
padding-block: 0.75rem;
|
padding-block: 0.75rem;
|
||||||
}
|
}
|
||||||
@ -408,6 +422,24 @@ html:has(.modal[open]) {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 40rem) {
|
||||||
|
.table__header, .table__cell {
|
||||||
|
padding-inline: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table__header--compact, .table__cell--compact {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table__header--compact:not(:first-child), .table__cell--compact:not(:first-child) {
|
||||||
|
padding-inline-start: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table__header--compact:not(:last-child), .table__cell--compact:not(:last-child) {
|
||||||
|
padding-inline-end: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*@media (prefers-color-scheme: dark) {
|
/*@media (prefers-color-scheme: dark) {
|
||||||
.table__header {
|
.table__header {
|
||||||
background-color: rgb(55, 65, 81);
|
background-color: rgb(55, 65, 81);
|
||||||
|
@ -51,7 +51,7 @@ export default class TransactionItemFormController extends Controller {
|
|||||||
if (!price.value) {
|
if (!price.value) {
|
||||||
price.value = option.getAttribute("data-price");
|
price.value = option.getAttribute("data-price");
|
||||||
}
|
}
|
||||||
if (quantity.value || (!unit.value && quantity.value === "1")) {
|
if (!quantity.value || (!unit.value && quantity.value === "1")) {
|
||||||
quantity.value = option.getAttribute("data-quantity") || (!unit.value ? "1" : "");
|
quantity.value = option.getAttribute("data-quantity") || (!unit.value ? "1" : "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user