Compare commits
20
Commits
main
..
7e0cc92c1d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7e0cc92c1d | ||
|
|
aa392815e5 | ||
|
|
71eed68e21 | ||
|
|
f284b85771 | ||
|
|
be3eb6b674 | ||
|
|
9f8c4b7af1 | ||
|
|
45fe327840 | ||
|
|
4d0d5c8d88 | ||
|
|
1686f5769c | ||
|
|
2d36ab0b88 | ||
|
|
62e2b7dee8 | ||
|
|
86a73b862a | ||
|
|
22e509d982 | ||
|
|
76ba79b63e | ||
|
|
33a8abf5cc | ||
|
|
9c41856b5b | ||
|
|
122d4de809 | ||
|
|
5e0ad197f0 | ||
|
|
a3c1bee09e | ||
|
|
317c696e70 |
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
"type": "process",
|
"type": "process",
|
||||||
"args": [
|
"args": [
|
||||||
"build",
|
"build",
|
||||||
"${workspaceFolder}/src/Duempelkas.Desktop/Duempelkas.Desktop.csproj"
|
"${workspaceFolder}/Duempelkas.slnx"
|
||||||
],
|
],
|
||||||
"problemMatcher": "$msCompile",
|
"problemMatcher": "$msCompile",
|
||||||
"group": {
|
"group": {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<Solution>
|
||||||
|
<Folder Name="/src/">
|
||||||
|
<Project Path="src/Duempelkas.App/Duempelkas.App.csproj" />
|
||||||
|
<Project Path="src/Duempelkas.Desktop/Duempelkas.Desktop.csproj" />
|
||||||
|
<Project Path="src/Duempelkas.Domain/Duempelkas.Domain.csproj" />
|
||||||
|
<Project Path="src/Duempelkas.Infrastructure/Duempelkas.Infrastructure.csproj" />
|
||||||
|
</Folder>
|
||||||
|
<Folder Name="/tests/">
|
||||||
|
<Project Path="tests/Duempelkas.Tests/Duempelkas.Tests.csproj" />
|
||||||
|
</Folder>
|
||||||
|
</Solution>
|
||||||
@@ -121,25 +121,3 @@ Validierung: 60/60 Tests in Debug und Release; Desktop Debug/Release ohne Warnun
|
|||||||
Das vorhandene Kassen-Icon bleibt in der Windows-EXE eingebettet und wird als natives Fenstersymbol gesetzt. Die ungenutzte Photino-Registrierung für Windows-Systembenachrichtigungen ist deaktiviert: Sie kann die explizite AppUserModelID überschreiben und Verknüpfungen zum generischen dotnet-Host anlegen. Damit bleibt die eigene Windows-Zuordnung für die Taskleiste erhalten; die in der Anwendung angezeigten Toasts funktionieren unabhängig davon. Bestehende Windows-Verknüpfungen werden nicht verändert.
|
Das vorhandene Kassen-Icon bleibt in der Windows-EXE eingebettet und wird als natives Fenstersymbol gesetzt. Die ungenutzte Photino-Registrierung für Windows-Systembenachrichtigungen ist deaktiviert: Sie kann die explizite AppUserModelID überschreiben und Verknüpfungen zum generischen dotnet-Host anlegen. Damit bleibt die eigene Windows-Zuordnung für die Taskleiste erhalten; die in der Anwendung angezeigten Toasts funktionieren unabhängig davon. Bestehende Windows-Verknüpfungen werden nicht verändert.
|
||||||
|
|
||||||
Release als eigenständige Windows-Einzeldatei veröffentlicht. Das aus der veröffentlichten EXE extrahierte Anwendungssymbol entspricht dem vorhandenen Kassen-Icon; auch das native Testfenster zeigt es in der Titelleiste. Die Taskleistendarstellung wurde nicht separat visuell erfasst. Build und Veröffentlichung ohne Warnungen oder Fehler.
|
Release als eigenständige Windows-Einzeldatei veröffentlicht. Das aus der veröffentlichten EXE extrahierte Anwendungssymbol entspricht dem vorhandenen Kassen-Icon; auch das native Testfenster zeigt es in der Titelleiste. Die Taskleistendarstellung wurde nicht separat visuell erfasst. Build und Veröffentlichung ohne Warnungen oder Fehler.
|
||||||
|
|
||||||
## Zentrierte Symbole, Tabellenkontrast und konkrete Laufmeldungen
|
|
||||||
|
|
||||||
Stift und Papierkorb sind in beiden Achsen zentriert; die Symbolschrift verwendet im Button eine Blockbox ohne Baseline-Versatz. Tabellenköpfe verwenden die Text- und Oberflächenfarbe des aktuellen Designs statt der festen Bootstrap-Klasse für helle Tabellen. Die sechs generischen Laufmeldungen benennen nun Sicherung erstellen, Sicherung wiederherstellen, Kontenübersicht als PDF erstellen/speichern, Kontoauszug als PDF erstellen/speichern oder gespeicherte PDF öffnen.
|
|
||||||
|
|
||||||
Debug-Tests: 60/60 bestanden. Zusätzliche Browserprüfung mit lokalem Produktions-CSS: Symbole bei 100 und 200 % in beiden Designs innerhalb von 0,6 Pixeln zentriert, Buttons mindestens 44 × 44 Pixel. Kontrast der sichtbaren Tabellenköpfe 17,85:1 (hell) und 12,89:1 (dunkel). Alle 16 bisherigen Offline-Layoutkombinationen bestehen weiterhin; dunkler Tabellenkopf und Symbole zusätzlich visuell geprüft.
|
|
||||||
|
|
||||||
Auch die Release-Suite besteht mit 60/60 Tests. Desktop Debug und Release bauen ohne Warnungen oder Fehler; `git diff --check` ist sauber.
|
|
||||||
|
|
||||||
## Dauerhafte Anzeige gelöschter Buchungen
|
|
||||||
|
|
||||||
Die Historienansicht wird als benutzerweite Einstellung in settings.json gespeichert und beim Öffnen jeder Kontoliste geladen. Sie bleibt über Kontowechsel und Neustarts aktiv, bis der Switch ausgeschaltet wird. „Filter zurücksetzen“ ändert nur Suche und Sortierung. Bestehende Einstellungsdateien ohne diesen Wert starten weiterhin mit ausgeblendeten gelöschten Buchungen.
|
|
||||||
|
|
||||||
62/62 Tests in Debug und Release bestanden. Neue Regressionen prüfen Kontowechsel, Filterreset, Ausschalten, erneutes Laden durch eine neue SettingsService-Instanz und Erhalt der übrigen Einstellungen. Navigationstest wartet auf die tatsächlich geladene Kontokarte. Debug-Desktop-Build ohne Warnungen/Fehler. Der vorhandene Publish-Task wird anschließend mit seinen unveränderten Release-/win-x64-/Einzeldatei-Argumenten ausgeführt. Die bereits vorhandene lokale Anpassung an AccountCard.razor bleibt separat erhalten und fließt in die lokale Veröffentlichung ein.
|
|
||||||
|
|
||||||
Publish-Task erfolgreich abgeschlossen: Publish/Duempelkas-v26.306.exe (69.325.134 Bytes). Die ältere veröffentlichte Version bleibt erhalten. Die veröffentlichte Release-Anwendung wurde nicht gegen die Vereinsdaten gestartet.
|
|
||||||
|
|
||||||
## Builddatum im Über-Dialog
|
|
||||||
|
|
||||||
Neben der Programmversion steht jetzt das Builddatum im Format TT.MM.JJJJ. Der Desktop-Build bettet seinen UTC-Zeitstempel als Assembly-Metadatum ein; die Anzeige benötigt weder einen Dateipfad noch das Änderungsdatum der EXE und bleibt dadurch auch in der veröffentlichten Einzeldatei verfügbar. Debug-Build ohne Warnungen/Fehler; eingebettetes Datum 02.11.2026 aus der erzeugten Desktop-Assembly ausgelesen und geprüft. Debug-Suite: 62/62 Tests bestanden.
|
|
||||||
|
|
||||||
Auch Release: 62/62 Tests bestanden; vorhandener Publish-Task erfolgreich erneut ausgeführt und Publish/Duempelkas-v26.306.exe aktualisiert.
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
@inject NavigationManager Navigation
|
@inject NavigationManager Navigation
|
||||||
|
|
||||||
<a class="card account-card h-100" href="@($"/accounts/{Account.Id}")" @onclick="Navigate" @onclick:preventDefault="true">
|
<a class="card account-card h-100" href="@($"/accounts/{Account.Id}")" @onclick="Navigate" @onclick:preventDefault="true">
|
||||||
<div class="card-body d-flex flex-column justify-content-between pb-0 pt-1 px-2">
|
<div class="card-body d-flex flex-column justify-content-between">
|
||||||
<h4 class="card-title mb-3 mt-0">@Account.Name</h4>
|
<h3 class="card-title mb-3 mt-0">@Account.Name</h3>
|
||||||
<div class="@(Account.TotalBalance >= 0 ? "amount-positive" : "amount-negative") fs-5">
|
<div class="@(Account.TotalBalance >= 0 ? "amount-positive" : "amount-negative") fs-4">
|
||||||
@($"{Account.TotalBalance:N2} €")
|
@($"{Account.TotalBalance:N2} €")
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
@inject ISettingsService Settings
|
|
||||||
|
|
||||||
<div class="entry-filters">
|
<div class="entry-filters">
|
||||||
<div><label for="entry-search">Buchungen suchen</label>
|
<div><label for="entry-search">Buchungen suchen</label>
|
||||||
<input id="entry-search" type="search" class="form-control" @bind="searchText" @bind:event="oninput" placeholder="Beschreibung, Nummer, Datum oder Betrag" /></div>
|
<input id="entry-search" type="search" class="form-control" @bind="searchText" @bind:event="oninput" placeholder="Beschreibung, Nummer, Datum oder Betrag" /></div>
|
||||||
@@ -19,13 +17,13 @@
|
|||||||
@if (Entries.Any(e => e.IsDeleted))
|
@if (Entries.Any(e => e.IsDeleted))
|
||||||
{
|
{
|
||||||
<div class="form-check form-switch history-toggle">
|
<div class="form-check form-switch history-toggle">
|
||||||
<input id="show-deleted" class="form-check-input" type="checkbox" role="switch" @bind="showDeleted" @bind:after="SaveHistoryPreference" />
|
<input id="show-deleted" class="form-check-input" type="checkbox" role="switch" @bind="showDeleted" />
|
||||||
<label class="form-check-label" for="show-deleted">Gelöschte Buchungen anzeigen (@Entries.Count(e => e.IsDeleted))</label>
|
<label class="form-check-label" for="show-deleted">Gelöschte Buchungen anzeigen (@Entries.Count(e => e.IsDeleted))</label>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<div class="table-responsive entry-list">
|
<div class="table-responsive entry-list">
|
||||||
<table class="table table-hover table-sm align-middle">
|
<table class="table table-hover table-sm align-middle">
|
||||||
<thead>
|
<thead class="table-light">
|
||||||
<tr>
|
<tr>
|
||||||
<th style="width: 100px;">LfdNr</th>
|
<th style="width: 100px;">LfdNr</th>
|
||||||
<th style="width: 120px;">Datum</th>
|
<th style="width: 120px;">Datum</th>
|
||||||
|
|||||||
@@ -6,13 +6,11 @@ namespace Duempelkas.App.Components.Accounts;
|
|||||||
public partial class EntryTable
|
public partial class EntryTable
|
||||||
{
|
{
|
||||||
private bool showDeleted;
|
private bool showDeleted;
|
||||||
protected override async Task OnInitializedAsync() => showDeleted = await Settings.GetShowDeletedEntriesAsync();
|
|
||||||
private Task SaveHistoryPreference() => Settings.SetShowDeletedEntriesAsync(showDeleted);
|
|
||||||
private string searchText = string.Empty;
|
private string searchText = string.Empty;
|
||||||
private string sort = "date-asc";
|
private string sort = "date-asc";
|
||||||
private List<EntryDto> VisibleEntries => Duempelkas.App.Services.EntryListQuery.Apply(Entries, searchText, showDeleted, sort);
|
private List<EntryDto> VisibleEntries => Duempelkas.App.Services.EntryListQuery.Apply(Entries, searchText, showDeleted, sort);
|
||||||
private decimal ResultSum => VisibleEntries.Where(e => !e.IsDeleted).Sum(Duempelkas.App.Services.EntryListQuery.SignedAmount);
|
private decimal ResultSum => VisibleEntries.Where(e => !e.IsDeleted).Sum(Duempelkas.App.Services.EntryListQuery.SignedAmount);
|
||||||
private void ResetFilters() { searchText = string.Empty; sort = "date-asc"; }
|
private void ResetFilters() { searchText = string.Empty; sort = "date-asc"; showDeleted = false; }
|
||||||
[Parameter] public int? HighlightedEntryId { get; set; }
|
[Parameter] public int? HighlightedEntryId { get; set; }
|
||||||
[Parameter] public string EmptyMessage { get; set; } = "Keine Buchungen vorhanden.";
|
[Parameter] public string EmptyMessage { get; set; } = "Keine Buchungen vorhanden.";
|
||||||
#region Parameters
|
#region Parameters
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<a class="btn btn-outline-secondary" href="hilfe">Hilfe</a>
|
<a class="btn btn-outline-secondary" href="hilfe">Hilfe</a>
|
||||||
<label for="text-scale">Textgröße</label>
|
<label for="text-scale">Textgröße</label>
|
||||||
<select id="text-scale" @bind="fontScale" @bind:after="ApplyTextScale">
|
<select id="text-scale" @bind="fontScale" @bind:after="ApplyTextScale">
|
||||||
<option value="75">Klein (75 %)</option><option value="100">Normal</option><option value="125">Groß (125 %)</option>
|
<option value="100">Normal</option><option value="125">Groß (125 %)</option>
|
||||||
<option value="150">Sehr groß (150 %)</option><option value="200">200 %</option>
|
<option value="150">Sehr groß (150 %)</option><option value="200">200 %</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -32,8 +32,7 @@
|
|||||||
{
|
{
|
||||||
<ModalDialog Title="Über Dümpelkas" OnCancel="CloseAboutDialog">
|
<ModalDialog Title="Über Dümpelkas" OnCancel="CloseAboutDialog">
|
||||||
<h5>Über Dümpelkas · Version @AppVersion</h5>
|
<h5>Über Dümpelkas · Version @AppVersion</h5>
|
||||||
<p class="mb-1 mt-3">Erstellt: @AppBuildDate</p>
|
<p class="mb-2">Entwickler: Andre Beging</p>
|
||||||
<p class="mb-1">Entwickler: Andre Beging</p>
|
|
||||||
<p class="mb-3">E-Mail: <a href="mailto:mail@beging.de" style="color: var(--color-text);">mail@beging.de</a></p>
|
<p class="mb-3">E-Mail: <a href="mailto:mail@beging.de" style="color: var(--color-text);">mail@beging.de</a></p>
|
||||||
<div class="d-flex justify-content-end">
|
<div class="d-flex justify-content-end">
|
||||||
<button type="button" class="btn btn-outline-secondary" @onclick="CloseAboutDialog">
|
<button type="button" class="btn btn-outline-secondary" @onclick="CloseAboutDialog">
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public partial class MainLayout
|
|||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
var value = await JS.InvokeAsync<int>("duempelkasReading.get");
|
var value = await JS.InvokeAsync<int>("duempelkasReading.get");
|
||||||
fontScale = value is 75 or 125 or 150 or 200 ? value : 100;
|
fontScale = value is 125 or 150 or 200 ? value : 100;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,19 +24,6 @@ public partial class MainLayout
|
|||||||
|
|
||||||
#region Properties
|
#region Properties
|
||||||
|
|
||||||
private static string AppBuildDate
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var value = Assembly.GetEntryAssembly()?.GetCustomAttributes<AssemblyMetadataAttribute>()
|
|
||||||
.FirstOrDefault(attribute => attribute.Key == "BuildDate")?.Value;
|
|
||||||
return DateTimeOffset.TryParse(value, System.Globalization.CultureInfo.InvariantCulture,
|
|
||||||
System.Globalization.DateTimeStyles.AssumeUniversal, out var built)
|
|
||||||
? built.UtcDateTime.ToString("dd.MM.yyyy", System.Globalization.CultureInfo.InvariantCulture)
|
|
||||||
: "unbekannt";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string AppVersion
|
private static string AppVersion
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ public partial class AccountDetail
|
|||||||
private async Task HandleExport()
|
private async Task HandleExport()
|
||||||
{
|
{
|
||||||
if (fileBusy) return;
|
if (fileBusy) return;
|
||||||
var busyToast = Toasts.Show("Kontoauszug wird als PDF erstellt und gespeichert … Bitte warten.", persistent: true);
|
var busyToast = Toasts.Show("Dateiaktion läuft … Bitte warten.", persistent: true);
|
||||||
fileBusy = true;
|
fileBusy = true;
|
||||||
fileResult = null;
|
fileResult = null;
|
||||||
try
|
try
|
||||||
@@ -277,7 +277,7 @@ public partial class AccountDetail
|
|||||||
private async Task HandleOpenSavedPdf()
|
private async Task HandleOpenSavedPdf()
|
||||||
{
|
{
|
||||||
if (fileBusy) return;
|
if (fileBusy) return;
|
||||||
var busyToast = Toasts.Show("Gespeicherte PDF-Datei wird geöffnet … Bitte warten.", persistent: true);
|
var busyToast = Toasts.Show("Dateiaktion läuft … Bitte warten.", persistent: true);
|
||||||
fileBusy = true;
|
fileBusy = true;
|
||||||
fileResult = null;
|
fileResult = null;
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ public partial class Dashboard
|
|||||||
private async Task HandleBackupAsync()
|
private async Task HandleBackupAsync()
|
||||||
{
|
{
|
||||||
if (fileBusy) return;
|
if (fileBusy) return;
|
||||||
var busyToast = Toasts.Show("Sicherung wird erstellt … Bitte warten.", persistent: true);
|
var busyToast = Toasts.Show("Dateiaktion läuft … Bitte warten.", persistent: true);
|
||||||
fileBusy = true;
|
fileBusy = true;
|
||||||
fileResult = null;
|
fileResult = null;
|
||||||
try
|
try
|
||||||
@@ -127,7 +127,7 @@ public partial class Dashboard
|
|||||||
private async Task HandleConfirmRestore()
|
private async Task HandleConfirmRestore()
|
||||||
{
|
{
|
||||||
if (fileBusy) return;
|
if (fileBusy) return;
|
||||||
var busyToast = Toasts.Show("Sicherung wird wiederhergestellt … Bitte warten.", persistent: true);
|
var busyToast = Toasts.Show("Dateiaktion läuft … Bitte warten.", persistent: true);
|
||||||
fileBusy = true;
|
fileBusy = true;
|
||||||
fileResult = null;
|
fileResult = null;
|
||||||
try
|
try
|
||||||
@@ -144,7 +144,7 @@ public partial class Dashboard
|
|||||||
private async Task HandleDashboardExportAsync()
|
private async Task HandleDashboardExportAsync()
|
||||||
{
|
{
|
||||||
if (fileBusy) return;
|
if (fileBusy) return;
|
||||||
var busyToast = Toasts.Show("Kontenübersicht wird als PDF erstellt und gespeichert … Bitte warten.", persistent: true);
|
var busyToast = Toasts.Show("Dateiaktion läuft … Bitte warten.", persistent: true);
|
||||||
fileBusy = true;
|
fileBusy = true;
|
||||||
fileResult = null;
|
fileResult = null;
|
||||||
try
|
try
|
||||||
@@ -180,7 +180,7 @@ public partial class Dashboard
|
|||||||
private async Task HandleOpenSavedPdf()
|
private async Task HandleOpenSavedPdf()
|
||||||
{
|
{
|
||||||
if (fileBusy) return;
|
if (fileBusy) return;
|
||||||
var busyToast = Toasts.Show("Gespeicherte PDF-Datei wird geöffnet … Bitte warten.", persistent: true);
|
var busyToast = Toasts.Show("Dateiaktion läuft … Bitte warten.", persistent: true);
|
||||||
fileBusy = true;
|
fileBusy = true;
|
||||||
fileResult = null;
|
fileResult = null;
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ namespace Duempelkas.App.Services;
|
|||||||
|
|
||||||
public interface ISettingsService
|
public interface ISettingsService
|
||||||
{
|
{
|
||||||
Task<bool> GetShowDeletedEntriesAsync();
|
|
||||||
Task SetShowDeletedEntriesAsync(bool show);
|
|
||||||
Task<int> GetSetupStepAsync() => Task.FromResult(0);
|
Task<int> GetSetupStepAsync() => Task.FromResult(0);
|
||||||
Task SetSetupStepAsync(int step) => Task.CompletedTask;
|
Task SetSetupStepAsync(int step) => Task.CompletedTask;
|
||||||
Task<string?> GetClubNameAsync();
|
Task<string?> GetClubNameAsync();
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AssemblyMetadata Include="BuildDate" Value="$([System.DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ'))" />
|
|
||||||
<Content Include="wwwroot\**">
|
<Content Include="wwwroot\**">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
|||||||
@@ -350,9 +350,8 @@ html[data-ui-theme="light"] .btn-dark {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.table thead th {
|
.table thead th {
|
||||||
background-color: var(--color-surface);
|
|
||||||
border-bottom-color: var(--color-border);
|
border-bottom-color: var(--color-border);
|
||||||
color: var(--color-text);
|
color: var(--color-text-muted);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -644,9 +643,6 @@ body:has(dialog[open]) .toast-stack:not(.modal-toasts) { display: none; }
|
|||||||
|
|
||||||
.entry-actions button { min-height: 44px; min-width: 44px; padding: .6rem; }
|
.entry-actions button { min-height: 44px; min-width: 44px; padding: .6rem; }
|
||||||
.entry-action-buttons { display: flex; gap: .5rem; }
|
.entry-action-buttons { display: flex; gap: .5rem; }
|
||||||
.entry-action-buttons .btn { display: inline-flex; align-items: center; justify-content: center; }
|
|
||||||
.entry-action-buttons .btn i { margin: 0; line-height: 1; }
|
|
||||||
.entry-action-buttons .btn i::before { display: block; }
|
|
||||||
.entry-description { overflow-wrap: anywhere; }
|
.entry-description { overflow-wrap: anywhere; }
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.entry-list thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
|
.entry-list thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0,0,0,0); }
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ window.duempelkasDialog = {
|
|||||||
window.duempelkasReading = {
|
window.duempelkasReading = {
|
||||||
get() { let value = 100; try { value = Number(localStorage.getItem('duempelkas-text-scale')) || 100; } catch (_) {} return this.set(value, false); },
|
get() { let value = 100; try { value = Number(localStorage.getItem('duempelkas-text-scale')) || 100; } catch (_) {} return this.set(value, false); },
|
||||||
set(value, persist = true) {
|
set(value, persist = true) {
|
||||||
const scale = [75,100,125,150,200].includes(Number(value)) ? Number(value) : 100;
|
const scale = [100,125,150,200].includes(Number(value)) ? Number(value) : 100;
|
||||||
document.documentElement.style.setProperty('--app-font-size', (16 * scale / 100) + 'px');
|
document.documentElement.style.setProperty('--app-font-size', (16 * scale / 100) + 'px');
|
||||||
if (persist) { try { localStorage.setItem('duempelkas-text-scale', String(scale)); } catch (_) {} }
|
if (persist) { try { localStorage.setItem('duempelkas-text-scale', String(scale)); } catch (_) {} }
|
||||||
return scale;
|
return scale;
|
||||||
|
|||||||
@@ -12,15 +12,6 @@ public class SettingsService : ISettingsService
|
|||||||
|
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
||||||
|
|
||||||
public async Task<bool> GetShowDeletedEntriesAsync() => (await LoadAsync()).ShowDeletedEntries;
|
|
||||||
|
|
||||||
public async Task SetShowDeletedEntriesAsync(bool show)
|
|
||||||
{
|
|
||||||
var settings = await LoadAsync();
|
|
||||||
settings.ShowDeletedEntries = show;
|
|
||||||
await SaveAsync(settings);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<string?> GetClubNameAsync()
|
public async Task<string?> GetClubNameAsync()
|
||||||
{
|
{
|
||||||
var settings = await LoadAsync();
|
var settings = await LoadAsync();
|
||||||
@@ -68,7 +59,6 @@ public class SettingsService : ISettingsService
|
|||||||
|
|
||||||
private class AppSettings
|
private class AppSettings
|
||||||
{
|
{
|
||||||
public bool ShowDeletedEntries { get; set; }
|
|
||||||
public string? ClubName { get; set; }
|
public string? ClubName { get; set; }
|
||||||
public int SetupStep { get; set; }
|
public int SetupStep { get; set; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -174,8 +174,6 @@ public class DashboardPdfStatementServiceTests : IDisposable
|
|||||||
|
|
||||||
private sealed class FixedSettingsService : ISettingsService
|
private sealed class FixedSettingsService : ISettingsService
|
||||||
{
|
{
|
||||||
public Task<bool> GetShowDeletedEntriesAsync() => Task.FromResult(false);
|
|
||||||
public Task SetShowDeletedEntriesAsync(bool show) => Task.CompletedTask;
|
|
||||||
private readonly string? _clubName;
|
private readonly string? _clubName;
|
||||||
|
|
||||||
public FixedSettingsService(string? clubName)
|
public FixedSettingsService(string? clubName)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public class DesktopResourceTests
|
|||||||
public void StartupStylesAndFonts_AreDeliveredLocally()
|
public void StartupStylesAndFonts_AreDeliveredLocally()
|
||||||
{
|
{
|
||||||
var root = new DirectoryInfo(AppContext.BaseDirectory);
|
var root = new DirectoryInfo(AppContext.BaseDirectory);
|
||||||
while (root != null && !File.Exists(Path.Combine(root.FullName, "src", "Duempelkas.Desktop", "Duempelkas.Desktop.csproj"))) root = root.Parent;
|
while (root != null && !File.Exists(Path.Combine(root.FullName, "Duempelkas.slnx"))) root = root.Parent;
|
||||||
Assert.NotNull(root);
|
Assert.NotNull(root);
|
||||||
var web = Path.Combine(root.FullName, "src", "Duempelkas.Desktop", "wwwroot");
|
var web = Path.Combine(root.FullName, "src", "Duempelkas.Desktop", "wwwroot");
|
||||||
var html = File.ReadAllText(Path.Combine(web, "index.html"));
|
var html = File.ReadAllText(Path.Combine(web, "index.html"));
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ public class EntryPresentationTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void Actions_AreDirectIconButtonsWithTitlesAndBookingSpecificNames()
|
public void Actions_AreDirectIconButtonsWithTitlesAndBookingSpecificNames()
|
||||||
{
|
{
|
||||||
using var fixture = new LedgerUiFixture();
|
using var context = new BunitContext();
|
||||||
var context = fixture.Context;
|
|
||||||
var entry = new EntryDto(1, 1, "2026-001", EntryType.Expense, DateTime.Today, "Porto", 4.20m, false, false, null, null, null);
|
var entry = new EntryDto(1, 1, "2026-001", EntryType.Expense, DateTime.Today, "Porto", 4.20m, false, false, null, null, null);
|
||||||
var edited = 0;
|
var edited = 0;
|
||||||
var table = context.Render<EntryTable>(p => p.Add(x => x.Entries, [entry]).Add(x => x.OnEditEntry, id => edited = id));
|
var table = context.Render<EntryTable>(p => p.Add(x => x.Entries, [entry]).Add(x => x.OnEditEntry, id => edited = id));
|
||||||
|
|||||||
@@ -12,8 +12,7 @@ public class EntrySearchTests
|
|||||||
[Fact]
|
[Fact]
|
||||||
public void Search_FindsKnownBookingInLargeList_AndResetsWithoutChangingAmounts()
|
public void Search_FindsKnownBookingInLargeList_AndResetsWithoutChangingAmounts()
|
||||||
{
|
{
|
||||||
using var fixture = new LedgerUiFixture();
|
using var context = new BunitContext();
|
||||||
var context = fixture.Context;
|
|
||||||
var entries = Enumerable.Range(1, 400).Select(i => new EntryDto(i, 1, $"2026-{i:D3}", EntryType.Income,
|
var entries = Enumerable.Range(1, 400).Select(i => new EntryDto(i, 1, $"2026-{i:D3}", EntryType.Income,
|
||||||
new(2026, 1, 1), i == 321 ? "Älteres Jubiläum" : $"Beitrag {i}", i == 321 ? 12.50m : i, false, false, null, null, null)).ToList();
|
new(2026, 1, 1), i == 321 ? "Älteres Jubiläum" : $"Beitrag {i}", i == 321 ? 12.50m : i, false, false, null, null, null)).ToList();
|
||||||
var original = entries.Sum(e => e.Amount);
|
var original = entries.Sum(e => e.Amount);
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
using Bunit;
|
|
||||||
using Duempelkas.App.Components.Accounts;
|
|
||||||
using Duempelkas.App.Services;
|
|
||||||
using Duempelkas.App.Services.Models;
|
|
||||||
using Duempelkas.Domain.Enums;
|
|
||||||
using Duempelkas.Infrastructure.Services;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Duempelkas.Tests;
|
|
||||||
|
|
||||||
public class HistoryPreferenceTests
|
|
||||||
{
|
|
||||||
[Fact]
|
|
||||||
public async Task PreferenceSurvivesRestart_AndPreservesOtherSettings()
|
|
||||||
{
|
|
||||||
var folder = Path.Combine(Path.GetTempPath(), "duempelkas-history-" + Guid.NewGuid().ToString("N"));
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var settings = new SettingsService(folder);
|
|
||||||
Assert.False(await settings.GetShowDeletedEntriesAsync());
|
|
||||||
await settings.SetClubNameAsync("Testverein");
|
|
||||||
await settings.SetShowDeletedEntriesAsync(true);
|
|
||||||
var restarted = new SettingsService(folder);
|
|
||||||
Assert.True(await restarted.GetShowDeletedEntriesAsync());
|
|
||||||
Assert.Equal("Testverein", await restarted.GetClubNameAsync());
|
|
||||||
await restarted.SetSetupStepAsync(2);
|
|
||||||
Assert.True(await new SettingsService(folder).GetShowDeletedEntriesAsync());
|
|
||||||
await restarted.SetShowDeletedEntriesAsync(false);
|
|
||||||
Assert.False(await new SettingsService(folder).GetShowDeletedEntriesAsync());
|
|
||||||
}
|
|
||||||
finally { if (Directory.Exists(folder)) Directory.Delete(folder, true); }
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public async Task HistoryStaysEnabledAcrossAccountsAndFilterReset_UntilSwitchedOff()
|
|
||||||
{
|
|
||||||
using var f = new LedgerUiFixture();
|
|
||||||
List<EntryDto> Rows(int account) => [new(account, account, "2026-001", EntryType.Income,
|
|
||||||
DateTime.Today, "Deleted test", 10m, true, false, null, null, null)];
|
|
||||||
var first = f.Context.Render<EntryTable>(p => p.Add(x => x.Entries, Rows(1)));
|
|
||||||
Assert.Empty(first.FindAll("tbody tr"));
|
|
||||||
await first.Find("#show-deleted").ChangeAsync(new() { Value = true });
|
|
||||||
Assert.True(await f.Service<ISettingsService>().GetShowDeletedEntriesAsync());
|
|
||||||
first.Find(".filter-reset").Click();
|
|
||||||
Assert.Single(first.FindAll("tbody tr"));
|
|
||||||
var second = f.Context.Render<EntryTable>(p => p.Add(x => x.Entries, Rows(2)));
|
|
||||||
second.WaitForAssertion(() => Assert.Single(second.FindAll("tbody tr")));
|
|
||||||
await second.Find("#show-deleted").ChangeAsync(new() { Value = false });
|
|
||||||
Assert.False(await f.Service<ISettingsService>().GetShowDeletedEntriesAsync());
|
|
||||||
var returning = f.Context.Render<EntryTable>(p => p.Add(x => x.Entries, Rows(1)));
|
|
||||||
returning.WaitForAssertion(() => Assert.Empty(returning.FindAll("tbody tr")));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -32,7 +32,7 @@ public class LayoutControlTests
|
|||||||
if (output == null) return;
|
if (output == null) return;
|
||||||
Directory.CreateDirectory(output);
|
Directory.CreateDirectory(output);
|
||||||
var root = new DirectoryInfo(AppContext.BaseDirectory);
|
var root = new DirectoryInfo(AppContext.BaseDirectory);
|
||||||
while (root != null && !File.Exists(Path.Combine(root.FullName, "src", "Duempelkas.Desktop", "Duempelkas.Desktop.csproj"))) root = root.Parent;
|
while (root != null && !File.Exists(Path.Combine(root.FullName, "Duempelkas.slnx"))) root = root.Parent;
|
||||||
var web = new Uri(Path.Combine(root!.FullName, "src", "Duempelkas.Desktop", "wwwroot") + Path.DirectorySeparatorChar).AbsoluteUri;
|
var web = new Uri(Path.Combine(root!.FullName, "src", "Duempelkas.Desktop", "wwwroot") + Path.DirectorySeparatorChar).AbsoluteUri;
|
||||||
File.WriteAllText(Path.Combine(output, name + ".html"), "<!doctype html><html lang='de'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1'>" +
|
File.WriteAllText(Path.Combine(output, name + ".html"), "<!doctype html><html lang='de'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width, initial-scale=1'>" +
|
||||||
$"<link rel='stylesheet' href='{web}vendor/bootstrap/bootstrap.min.css'><link rel='stylesheet' href='{web}vendor/bootstrap-icons/bootstrap-icons.min.css'><link rel='stylesheet' href='{web}css/app.css'>" +
|
$"<link rel='stylesheet' href='{web}vendor/bootstrap/bootstrap.min.css'><link rel='stylesheet' href='{web}vendor/bootstrap-icons/bootstrap-icons.min.css'><link rel='stylesheet' href='{web}css/app.css'>" +
|
||||||
|
|||||||
@@ -34,11 +34,11 @@ public class YearNavigationTests
|
|||||||
context.Services.GetRequiredService<ReportingPeriod>().Year = 2025;
|
context.Services.GetRequiredService<ReportingPeriod>().Year = 2025;
|
||||||
var page = context.Render<Routes>();
|
var page = context.Render<Routes>();
|
||||||
page.WaitForAssertion(() => Assert.Contains("Jahr: 2025", page.Markup));
|
page.WaitForAssertion(() => Assert.Contains("Jahr: 2025", page.Markup));
|
||||||
page.WaitForElement(".account-card").Click();
|
page.Find(".account-card").Click();
|
||||||
page.WaitForAssertion(() => Assert.Contains("Vorjahr", page.Markup));
|
page.WaitForAssertion(() => Assert.Contains("Vorjahr", page.Markup));
|
||||||
page.FindAll("button").Single(b => b.TextContent.Contains("Zurück")).Click();
|
page.FindAll("button").Single(b => b.TextContent.Contains("Zurück")).Click();
|
||||||
page.WaitForAssertion(() => Assert.Contains("Jahr: 2025", page.Markup));
|
page.WaitForAssertion(() => Assert.Contains("Jahr: 2025", page.Markup));
|
||||||
page.WaitForElement(".account-card").Click();
|
page.Find(".account-card").Click();
|
||||||
page.WaitForElement(".summary-section");
|
page.WaitForElement(".summary-section");
|
||||||
page.FindAll("button").Single(b => b.TextContent.Contains("Einnahme oder Ausgabe erfassen")).Click();
|
page.FindAll("button").Single(b => b.TextContent.Contains("Einnahme oder Ausgabe erfassen")).Click();
|
||||||
var dialog = page.FindComponent<AddEntryDialog>();
|
var dialog = page.FindComponent<AddEntryDialog>();
|
||||||
@@ -60,7 +60,7 @@ public class YearNavigationTests
|
|||||||
page.FindAll("button").Single(b => b.TextContent.Trim() == "Alle Buchungen").Click();
|
page.FindAll("button").Single(b => b.TextContent.Trim() == "Alle Buchungen").Click();
|
||||||
page.FindAll("button").Single(b => b.TextContent.Contains("Zurück")).Click();
|
page.FindAll("button").Single(b => b.TextContent.Contains("Zurück")).Click();
|
||||||
page.WaitForAssertion(() => Assert.Contains("Alle Jahre", page.Markup));
|
page.WaitForAssertion(() => Assert.Contains("Alle Jahre", page.Markup));
|
||||||
page.WaitForElement(".account-card").Click();
|
page.Find(".account-card").Click();
|
||||||
page.WaitForAssertion(() => Assert.Contains("Vorjahr", page.Markup));
|
page.WaitForAssertion(() => Assert.Contains("Vorjahr", page.Markup));
|
||||||
Assert.Null(context.Services.GetRequiredService<ReportingPeriod>().Year);
|
Assert.Null(context.Services.GetRequiredService<ReportingPeriod>().Year);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user