From 1c5227ab9afa017a5a2a5765605f3a7ea54af375 Mon Sep 17 00:00:00 2001 From: Claudio Schaad Date: Tue, 7 Jul 2026 08:15:00 +0200 Subject: [PATCH] PR U-12: report primitives + print CSS Extract ReportHeader, ReportDivider, ReportSectionHeader, and ReportFooter under Components/Pages/Reports/ and adopt them across BalanceReport, BalanceSheetReport, ProfitLossReport, and DetailReport. Row shapes are unchanged; the primitives just fold the divider hr rows (30+ occurrences), section headers (8), and header/footer (4 each). ProfitLossReport / DetailReport also drop leftover `m.Class == 3/4` magic numbers for ClassIds. Print CSS gains `table.report tr { page-break-inside: avoid; }` so section headers don't land at the bottom of a page alone. Net -88 lines across the four report razor files. Co-Authored-By: Claude Opus 4.7 --- .../Pages/Reports/BalanceReport.razor | 48 ++---- .../Pages/Reports/BalanceSheetReport.razor | 39 ++--- .../Pages/Reports/DetailReport.razor | 150 ++++++++---------- .../Pages/Reports/ProfitLossReport.razor | 140 +++++++--------- .../Pages/Reports/ReportDivider.razor | 11 ++ .../Pages/Reports/ReportFooter.razor | 19 +++ .../Pages/Reports/ReportHeader.razor | 14 ++ .../Pages/Reports/ReportSectionHeader.razor | 18 +++ Schaad.Accounting.UI/wwwroot/app.css | 7 + 9 files changed, 210 insertions(+), 236 deletions(-) create mode 100644 Schaad.Accounting.UI/Components/Pages/Reports/ReportDivider.razor create mode 100644 Schaad.Accounting.UI/Components/Pages/Reports/ReportFooter.razor create mode 100644 Schaad.Accounting.UI/Components/Pages/Reports/ReportHeader.razor create mode 100644 Schaad.Accounting.UI/Components/Pages/Reports/ReportSectionHeader.razor diff --git a/Schaad.Accounting.UI/Components/Pages/Reports/BalanceReport.razor b/Schaad.Accounting.UI/Components/Pages/Reports/BalanceReport.razor index 45f767b..f858bd9 100644 --- a/Schaad.Accounting.UI/Components/Pages/Reports/BalanceReport.razor +++ b/Schaad.Accounting.UI/Components/Pages/Reports/BalanceReport.razor @@ -1,4 +1,4 @@ -@page "/BalanceReport" +@page "/BalanceReport" @if (balance is null) { @@ -6,11 +6,8 @@ return; } -

@header

-Print + -
-
@@ -20,16 +17,11 @@ - + - - - - - - + @foreach (var account in balance.ActivaAccountList.OrderBy(m => m.Number)) { @if (account.BalanceCHF == 0) { continue; } @@ -41,23 +33,14 @@ } - - - + - - - - - - - - - + + @foreach (var account in balance.PassivaAccountList.OrderBy(m => m.Number)) { @if (account.BalanceCHF == 0) { continue; } @@ -69,17 +52,13 @@ } - - - + - - - + @@ -95,11 +74,6 @@ - - - - - - + -
Passiven


Aktiven


Total Aktiven CHF @balance.TotalActivaCHF.ToFormattedString()

Passiven

@Math.Abs(account.BalanceCHF).ToFormattedString()

Total Passiven @balance.TotalPassivaCHF.ToFormattedString()

Total @balance.TotalActivaCHF.ToFormattedString()CHF @balance.TotalActivaCHF.ToFormattedString() CHF @((balance.TotalPassivaCHF + win).ToFormattedString())
 
\ No newline at end of file + diff --git a/Schaad.Accounting.UI/Components/Pages/Reports/BalanceSheetReport.razor b/Schaad.Accounting.UI/Components/Pages/Reports/BalanceSheetReport.razor index 09dffb3..9f01251 100644 --- a/Schaad.Accounting.UI/Components/Pages/Reports/BalanceSheetReport.razor +++ b/Schaad.Accounting.UI/Components/Pages/Reports/BalanceSheetReport.razor @@ -1,4 +1,4 @@ -@page "/BalanceSheetReport" +@page "/BalanceSheetReport" @if (balanceSheet is null) { @@ -6,18 +6,11 @@ return; } -

@header

-Print -
-
+ + - - - - - - + @foreach (var account in balanceSheet.ActivaAccountList.OrderBy(m => m.Number)) { @if (account.StartBalanceCHF == 0) { continue; } @@ -38,15 +31,8 @@ } - - - - - - - - - + + @foreach (var account in balanceSheet.ExpensesAccountList.OrderBy(m => m.Number)) { @if (account.BalanceCHF == 0) { continue; } @@ -57,9 +43,7 @@ } - - - + @@ -72,11 +56,6 @@ - - - - - - + -
Einnahmen

@Math.Abs(account.BalanceCHF).ToFormattedString()

Ausgaben

@account.BalanceCHF.ToFormattedString()

Total Einnahmen @balanceSheet.ProfitCHF.ToFormattedString()Übertrag @(balanceSheet.Year + 1) CHF @win.ToFormattedString()
 
\ No newline at end of file + diff --git a/Schaad.Accounting.UI/Components/Pages/Reports/DetailReport.razor b/Schaad.Accounting.UI/Components/Pages/Reports/DetailReport.razor index c85bdea..da5dc5c 100644 --- a/Schaad.Accounting.UI/Components/Pages/Reports/DetailReport.razor +++ b/Schaad.Accounting.UI/Components/Pages/Reports/DetailReport.razor @@ -1,4 +1,4 @@ -@page "/DetailReport" +@page "/DetailReport" @if (accounts is null || transactions is null) { @@ -6,11 +6,8 @@ return; } -

@header

-Print + -
-
@@ -19,100 +16,81 @@ - + - - - - - - -@foreach(var account in accounts.Where(m => m.Class == 3).OrderBy(m => m.Number)) -{ - @if (account.Balance == 0) {continue;} - - - - - - - - @foreach(var trx in transactions.Where(t => t.TargetAccountId == account.Id || t.OriginAccountId == account.Id).OrderBy(t => t.ValueDate)) + + @foreach (var account in accounts.Where(m => m.Class == ClassIds.Income).OrderBy(m => m.Number)) { + @if (account.Balance == 0) { continue; } + - - - + + + + + @foreach (var trx in transactions.Where(t => t.TargetAccountId == account.Id || t.OriginAccountId == account.Id).OrderBy(t => t.ValueDate)) + { + + + + + + } + } - + + -} - - - - - - - - - - - - - -@foreach(var account in accounts.Where(m => m.Class == 4).OrderBy(m => m.Number)) -{ - @if (account.Balance == 0) {continue;} - - - - - - - - @foreach(var trx in transactions.Where(t => t.TargetAccountId == account.Id || t.OriginAccountId == account.Id).OrderBy(t => t.ValueDate)) + + + @foreach (var account in accounts.Where(m => m.Class == ClassIds.Expenses).OrderBy(m => m.Number)) { + @if (account.Balance == 0) { continue; } + - - - + + + + + @foreach (var trx in transactions.Where(t => t.TargetAccountId == account.Id || t.OriginAccountId == account.Id).OrderBy(t => t.ValueDate)) + { + + + + + + } + } - + + -} - - - - - - - - - - + -
Betrag


Einkünfte

@account.Number@account.Name@Math.Abs(account.Balance).ToFormattedString()
@trx.ValueDate.ToString("dd.MM.yyyy")@trx.Text - @if (trx.OriginAccountId == account.Id) - { - @trx.Value.ToFormattedString(); - } - else - { - -@trx.Value.ToFormattedString(); - } - @account.Number@account.Name@Math.Abs(account.Balance).ToFormattedString()
@trx.ValueDate.ToString("dd.MM.yyyy")@trx.Text + @if (trx.OriginAccountId == account.Id) + { + @trx.Value.ToFormattedString(); + } + else + { + - + @trx.Value.ToFormattedString(); + } +

Total Einkünfte@profit.ToFormattedString()
Total Einkünfte@profit.ToFormattedString()

Ausgaben

@account.Number@account.Name@account.Balance.ToFormattedString()
@trx.ValueDate.ToString("dd.MM.yyyy")@trx.Text - @if (trx.TargetAccountId == account.Id) - { - @trx.Value.ToFormattedString(); - } - else - { - -@trx.Value.ToFormattedString(); - } - @account.Number@account.Name@account.Balance.ToFormattedString()
@trx.ValueDate.ToString("dd.MM.yyyy")@trx.Text + @if (trx.TargetAccountId == account.Id) + { + @trx.Value.ToFormattedString(); + } + else + { + - + @trx.Value.ToFormattedString(); + } +

Total Ausgaben@loss.ToFormattedString()
Total Ausgaben@loss.ToFormattedString()
 
\ No newline at end of file + diff --git a/Schaad.Accounting.UI/Components/Pages/Reports/ProfitLossReport.razor b/Schaad.Accounting.UI/Components/Pages/Reports/ProfitLossReport.razor index 145d1f7..c038dbc 100644 --- a/Schaad.Accounting.UI/Components/Pages/Reports/ProfitLossReport.razor +++ b/Schaad.Accounting.UI/Components/Pages/Reports/ProfitLossReport.razor @@ -1,4 +1,4 @@ -@page "/ProfitLossReport" +@page "/ProfitLossReport" @if (accounts is null) { @@ -6,11 +6,8 @@ return; } -

@header

-Print + -
-
@@ -20,86 +17,63 @@ - + - - - - - - -@foreach(var account in accounts.Where(m => m.Class == 3).OrderBy(m => m.Number)) -{ - @if (account.Balance == 0) {continue;} + + @foreach (var account in accounts.Where(m => m.Class == ClassIds.Income).OrderBy(m => m.Number)) + { + @if (account.Balance == 0) { continue; } - - - - - - -} - - - - - - - - - - - - - - - - - -@foreach(var account in accounts.Where(m => m.Class == 4).OrderBy(m => m.Number)) -{ - @if (account.Balance == 0) {continue;} - - - - - - - -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + } + + + + + + + + + @foreach (var account in accounts.Where(m => m.Class == ClassIds.Expenses).OrderBy(m => m.Number)) + { + @if (account.Balance == 0) { continue; } + + + + + + + + } + + + + + + + + + + + + + + + + + + + + + + + -
Ertrag


Einkünfte

@account.Number@account.Name@Math.Abs(account.Balance).ToFormattedString()

Total Einkünfte@profit.ToFormattedString()

Ausgaben

@account.Number@account.Name@Math.Abs(account.Balance).ToFormattedString()

Total Ausgaben@loss.ToFormattedString()

@loss.ToFormattedString()@profit.ToFormattedString()
Gewinn/Verlust@win.ToFormattedString()
Total@profit.ToFormattedString()@profit.ToFormattedString()
 
@account.Number@account.Name@Math.Abs(account.Balance).ToFormattedString()
Total Einkünfte@profit.ToFormattedString()
@account.Number@account.Name@Math.Abs(account.Balance).ToFormattedString()
Total Ausgaben@loss.ToFormattedString()
@loss.ToFormattedString()@profit.ToFormattedString()
Gewinn/Verlust@win.ToFormattedString()
Total@profit.ToFormattedString()@profit.ToFormattedString()
\ No newline at end of file + diff --git a/Schaad.Accounting.UI/Components/Pages/Reports/ReportDivider.razor b/Schaad.Accounting.UI/Components/Pages/Reports/ReportDivider.razor new file mode 100644 index 0000000..b1b5123 --- /dev/null +++ b/Schaad.Accounting.UI/Components/Pages/Reports/ReportDivider.razor @@ -0,0 +1,11 @@ +@* Full-width table divider —
. + Callers pass their table's column count. *@ + + +
+ + +@code { + [Parameter, EditorRequired] + public int Colspan { get; set; } +} diff --git a/Schaad.Accounting.UI/Components/Pages/Reports/ReportFooter.razor b/Schaad.Accounting.UI/Components/Pages/Reports/ReportFooter.razor new file mode 100644 index 0000000..a43a996 --- /dev/null +++ b/Schaad.Accounting.UI/Components/Pages/Reports/ReportFooter.razor @@ -0,0 +1,19 @@ +@* Report tail — spacer row + italic footer line ("Stand: dd.MM.yyyy"). + Note: the existing code used colspan="4" here even in reports whose + table only has 3 columns; preserved as-is so the visual is unchanged. + Callers can override via the Colspan parameter. *@ + + +   + + + @Footer + + +@code { + [Parameter, EditorRequired] + public string Footer { get; set; } = ""; + + [Parameter] + public int Colspan { get; set; } = 4; +} diff --git a/Schaad.Accounting.UI/Components/Pages/Reports/ReportHeader.razor b/Schaad.Accounting.UI/Components/Pages/Reports/ReportHeader.razor new file mode 100644 index 0000000..62ae6f5 --- /dev/null +++ b/Schaad.Accounting.UI/Components/Pages/Reports/ReportHeader.razor @@ -0,0 +1,14 @@ +@* Shared banner used at the top of every printable report. + H3 + browser print trigger + spacer. The Print link is hidden in the + print stylesheet via .hidePrint. *@ + +

@Title

+Print + +
+
+ +@code { + [Parameter, EditorRequired] + public string Title { get; set; } = ""; +} diff --git a/Schaad.Accounting.UI/Components/Pages/Reports/ReportSectionHeader.razor b/Schaad.Accounting.UI/Components/Pages/Reports/ReportSectionHeader.razor new file mode 100644 index 0000000..5c9b3f5 --- /dev/null +++ b/Schaad.Accounting.UI/Components/Pages/Reports/ReportSectionHeader.razor @@ -0,0 +1,18 @@ +@* Section header + divider pair — "Aktiven", "Passiven", "Einkünfte", + "Ausgaben", "Einnahmen" etc. Wraps two s so the caller drops it + into as a single element. *@ + + + @Title + + +
+ + +@code { + [Parameter, EditorRequired] + public string Title { get; set; } = ""; + + [Parameter, EditorRequired] + public int Colspan { get; set; } +} diff --git a/Schaad.Accounting.UI/wwwroot/app.css b/Schaad.Accounting.UI/wwwroot/app.css index 34853fe..1d2bc9a 100644 --- a/Schaad.Accounting.UI/wwwroot/app.css +++ b/Schaad.Accounting.UI/wwwroot/app.css @@ -63,6 +63,13 @@ h1:focus { .hidePrint { display: none !important; } + + /* Keep report rows intact across page breaks. Without this a row could + be split by the printer between an amount and its label, or between a + section header and its first data row. */ + table.report tr { + page-break-inside: avoid; + } } table.report