From bd1eccb4e35aa3d4ac0a8ac50d2cd43e11ba0ef2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 6 Oct 2026 10:04:38 +0000 Subject: [PATCH] Finanzen: Vorschau, Steuer und Abos ohne Karten, Container-Query auf Wrapper Co-Authored-By: Claude Sonnet 5.5 --- src/components/finance/AboCard.jsx | 73 ++++++------- src/components/finance/ForecastTable.jsx | 131 ++++++++++++----------- src/components/finance/TaxCard.jsx | 71 ++++++------ src/styles/global.css | 68 +++++------- 4 files changed, 168 insertions(+), 175 deletions(-) diff --git a/src/components/finance/AboCard.jsx b/src/components/finance/AboCard.jsx index fbd4c84..f0649f5 100644 --- a/src/components/finance/AboCard.jsx +++ b/src/components/finance/AboCard.jsx @@ -1,6 +1,6 @@ -import { FaTriangleExclamation } from 'react-icons/fa6' -import { Badge, Card, EmptyState } from '../ui' +import { Badge, Card, EmptyState, Stat } from '../ui' import { aboView } from '../../lib/financeView' +import { isNegText } from '../../lib/financeUi' /** * Block 5: „Abos“. Einnahmen pro Monat (aktiv und Zahlung fehlgeschlagen), zahlende Kunden, gefährdete Einnahmen @@ -12,50 +12,45 @@ export default function AboCard({ abos }) { if (v.error) { return ( - -

-

+ +
+

+ {v.error} +

+
) } return ( - -
- {v.monthlyText} - Abo-Einnahmen pro Monat - {v.payingText} - zahlende Kunden - {v.atRiskText && ( - <> - {v.atRiskText} - - - + +
+
+ + + {v.atRiskText && } +
+ + {v.rows.length === 0 ? ( + + ) : ( +
    + {v.rows.map((row) => ( +
  • +
    + {row.customer} + · {row.product} +
    +
    {row.amountText}
    +
    + {row.statusLabel} + nächste Abbuchung: {row.nextText} +
    +
  • + ))} +
)}
- - {v.rows.length === 0 ? ( - - ) : ( -
    - {v.rows.map((row) => ( -
  • -
    - {row.customer} - · {row.product} -
    -
    {row.amountText}
    -
    - {row.statusLabel} - nächste Abbuchung: {row.nextText} -
    -
  • - ))} -
- )}
) } diff --git a/src/components/finance/ForecastTable.jsx b/src/components/finance/ForecastTable.jsx index 9346d57..cb94063 100644 --- a/src/components/finance/ForecastTable.jsx +++ b/src/components/finance/ForecastTable.jsx @@ -1,12 +1,12 @@ -import { FaTriangleExclamation } from 'react-icons/fa6' import { Badge, Card, EmptyState } from '../ui' import { forecastNotes, forecastRows } from '../../lib/financeView' +import { isNegText } from '../../lib/financeUi' /** * Block 3: „Vorschau 30 Tage“. Je Posten Datum, Bezeichnung, „sicher“/„erwartet“ (als Text), Betrag mit Vorzeichen * und der laufende Saldo beider Linien ab dem Kontostand. Ohne Kontostand entfallen die Saldo-Spalten (mit Hinweis). - * Darunter die Annahme des Servers und Hinweise, wenn Stripe oder die Abos fehlen. Schmal (Karte bis 560 px) - * wird jede Zeile dreizeilig: Datum, Sicherheit und Betrag / Posten / beide Salden; ohne Salden sind es zwei Zeilen. + * Darunter die Annahme des Servers und Hinweise, wenn Stripe oder die Abos fehlen. Schmal (Wrapper .fin-body bis + * 560 px) wird jede Zeile dreizeilig: Datum, Sicherheit und Betrag / Posten / beide Salden; ohne Salden sind es zwei Zeilen. */ export default function ForecastTable({ vorschau, kontostandCents, stripeDown = false, abosFehler = false }) { const v = vorschau && typeof vorschau === 'object' ? vorschau : {} @@ -15,70 +15,77 @@ export default function ForecastTable({ vorschau, kontostandCents, stripeDown = const assumption = typeof v.annahme === 'string' ? v.annahme.trim() : '' return ( - - {rows.length === 0 ? ( - - ) : ( -
- - - - - - - - - {hasBalance && ( - <> - - - - )} - - - - {rows.map((row) => ( - - - - - + +
+ {rows.length === 0 ? ( + + ) : ( +
+
Erwartete Zahlungen der nächsten 30 Tage
DatumPostenSicherheitBetragSaldo sicherSaldo erwartet
{row.dateText}{row.label} - {row.sicherheit} - {row.amountText}
+ + + + + + + {hasBalance && ( <> - - + + )} - ))} - -
Erwartete Zahlungen der nächsten 30 Tage
DatumPostenSicherheitBetrag - {row.saldoSicherText} - - {row.saldoErwartetText} - Saldo sicherSaldo erwartet
-
- )} - {(notes.length > 0 || assumption) && ( -
- {notes.map((note) => ( -

-

- ))} - {assumption &&

{assumption}

} -
- )} + + + {rows.map((row) => ( + + {row.dateText} + {row.label} + + {row.sicherheit} + + + {row.amountText} + + {hasBalance && ( + <> + + {row.saldoSicherText} + + + {row.saldoErwartetText} + + + )} + + ))} + + +
+ )} + {(notes.length > 0 || assumption) && ( +
+ {notes.map((note) => ( +

+ {note.text} +

+ ))} + {assumption &&

{assumption}

} +
+ )} +
) } diff --git a/src/components/finance/TaxCard.jsx b/src/components/finance/TaxCard.jsx index debd7e8..7c1c3f6 100644 --- a/src/components/finance/TaxCard.jsx +++ b/src/components/finance/TaxCard.jsx @@ -1,5 +1,6 @@ import { Card } from '../ui' import { taxView } from '../../lib/financeView' +import { isNegText } from '../../lib/financeUi' /** * Block 4: „Steuer“. Rücklage (Betrag und woraus sie sich ergibt), Kleinunternehmer-Zähler gegen die Grenzen, @@ -10,43 +11,49 @@ import { taxView } from '../../lib/financeView' export default function TaxCard({ steuer }) { const v = taxView(steuer) return ( - -
-
-
- Steuer-Rücklage - {v.reserve.amountText} -
- {v.reserve.basisText &&
{v.reserve.basisText}
} -
- -
-
Kleinunternehmer (§ 19 UStG)
-
{v.small.counterText}
- {v.small.shareText &&
{v.small.shareText}
} -
{v.small.limitText}
-
{v.small.hardText}
- {v.small.previousText &&
{v.small.previousText}
} -
- - {v.gewerbeText && ( -
-
Gewerbesteuer
-
{v.gewerbeText}
-
- )} - - {v.prepayment && ( + +
+
- Nächste ESt-Vorauszahlung - {v.prepayment.amountText} + Steuer-Rücklage + + {v.reserve.amountText} +
-
am {v.prepayment.dateText}
+ {v.reserve.basisText &&
{v.reserve.basisText}
}
- )} -

{v.noteText}

+
+
Kleinunternehmer (§ 19 UStG)
+
{v.small.counterText}
+ {v.small.shareText &&
{v.small.shareText}
} +
{v.small.limitText}
+
{v.small.hardText}
+ {v.small.previousText &&
{v.small.previousText}
} +
+ + {v.gewerbeText && ( +
+
Gewerbesteuer
+
{v.gewerbeText}
+
+ )} + + {v.prepayment && ( +
+
+ Nächste ESt-Vorauszahlung + + {v.prepayment.amountText} + +
+
am {v.prepayment.dateText}
+
+ )} + +

{v.noteText}

+
) diff --git a/src/styles/global.css b/src/styles/global.css index 4b7e389..e78dbd7 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -708,72 +708,56 @@ button.link-danger { background: none; border: 0; padding: 0; font: inherit; fon /* Bloecke 3-5: Vorschau, Steuer, Abos */ -/* Block 3: Vorschau (Tabelle; in einer schmalen Karte wird jede Zeile zweizeilig) */ -.fin-forecast-card { container-type: inline-size; overflow: hidden; } -.fin-forecast th, .fin-forecast td { padding: 9px 10px; vertical-align: baseline; } +/* Block 3: Vorschau. Die Tabelle liegt in .fin-body (Container); schmal wird jede Zeile ein Raster. Zellen: Abstaende wie .table, nur oben und unten 9 px. */ +.fin-forecast th, .fin-forecast td { vertical-align: baseline; } +.fin-forecast td { padding-block: 9px; } .fin-forecast th.fin-amount { text-align: right; } -.fin-forecast th:first-child, .fin-forecast td:first-child { padding-left: var(--sp-4); } -.fin-forecast th:last-child, .fin-forecast td:last-child { padding-right: var(--sp-4); } -.fin-forecast tbody tr:last-child td { border-bottom: none; } .fin-forecast-date { white-space: nowrap; font-variant-numeric: tabular-nums; } .fin-forecast-label { overflow-wrap: anywhere; } -.fin-forecast-foot { display: grid; gap: var(--sp-2); padding: var(--sp-3) var(--sp-4); border-top: 1px solid var(--border); } -.fin-forecast-foot .fin-hint svg { vertical-align: -2px; } +.fin-forecast-foot { display: grid; gap: 6px; margin-top: 12px; } @container (max-width: 560px) { .fin-forecast thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; } .fin-forecast, .fin-forecast tbody { display: block; } .fin-forecast tr { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 4px 0; align-items: baseline; grid-template-areas: "date date kind kind amount amount" "label label label label label label" "sicher sicher sicher erwartet erwartet erwartet"; - padding: 10px var(--sp-4); border-bottom: 1px solid var(--border); + padding: 10px 0; border-bottom: 1px solid var(--line); } .fin-forecast-nobal tr { grid-template-areas: "date date kind kind amount amount" "label label label label label label"; } - .fin-forecast tbody tr:last-child { border-bottom: none; } .fin-forecast td, .fin-forecast td:first-child, .fin-forecast td:last-child { display: block; padding: 0; border: 0; min-width: 0; } - .fin-forecast tbody tr:hover td { background: transparent; } .fin-forecast-date { grid-area: date; } .fin-forecast-kind { grid-area: kind; } - .fin-forecast-amount { grid-area: amount; font-weight: 700; } + .fin-forecast-amount { grid-area: amount; font-weight: 600; } .fin-forecast-label { grid-area: label; } .fin-forecast td.fin-forecast-sicher, .fin-forecast td.fin-forecast-erwartet { - display: flex; flex-wrap: wrap; align-items: baseline; column-gap: var(--sp-2); font-size: 13px; + display: flex; flex-wrap: wrap; align-items: baseline; column-gap: 8px; font-size: 13px; } .fin-forecast-sicher { grid-area: sicher; } .fin-forecast-erwartet { grid-area: erwartet; } - .fin-forecast td.fin-forecast-sicher { padding-right: var(--sp-3); } - .fin-forecast td.fin-forecast-erwartet { padding-left: var(--sp-3); } + .fin-forecast td.fin-forecast-sicher { padding-right: 12px; } + .fin-forecast td.fin-forecast-erwartet { padding-left: 12px; } .fin-saldo { margin-left: auto; } - .fin-forecast td[data-label]::before { content: attr(data-label); color: var(--text-muted); font-size: 12px; font-weight: 500; white-space: nowrap; } + .fin-forecast td[data-label]::before { content: attr(data-label); color: var(--text-2); font-size: 12px; font-weight: 500; white-space: nowrap; } } -/* Block 4: Steuer */ -.fin-facts { display: grid; gap: var(--sp-4); } -.fin-fact { display: grid; gap: 3px; padding-bottom: var(--sp-4); border-bottom: 1px solid var(--border); } -.fin-fact:last-child { padding-bottom: 0; border-bottom: none; } -.fin-fact-row { display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-3); } -.fin-fact-label { color: var(--text-muted); font-size: 13px; } -.fin-fact-big { font-size: 24px; font-weight: 800; } -.fin-fact-strong { font-size: 16px; font-weight: 700; } -.fin-fact-value { font-weight: 700; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; } -.fin-fact-unknown { color: var(--warn); } -.fin-fact-sub { font-size: 13px; color: var(--text-muted); overflow-wrap: anywhere; } +/* Block 4: Steuer (Liste mit Haarlinien; die Fussnote steht nach dem letzten Posten) */ +.fin-facts { display: grid; gap: 16px; } +.fin-fact { display: grid; gap: 3px; padding-bottom: 16px; border-bottom: 1px solid var(--line); } +.fin-fact:last-of-type { padding-bottom: 0; border-bottom: 0; } +.fin-fact-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; } +.fin-fact-label { color: var(--text-2); font-size: 13px; } +.fin-fact-big { font-size: 22px; line-height: 30px; font-weight: 600; } +.fin-fact-strong { font-size: 16px; font-weight: 600; } +.fin-fact-value { font-weight: 600; font-variant-numeric: tabular-nums; overflow-wrap: anywhere; } +.fin-fact-unknown { color: var(--orange-fg); } +.fin-fact-sub { font-size: 13px; color: var(--text-2); overflow-wrap: anywhere; } .fin-fact-sub-strong { color: var(--text); } -/* Block 5: Abos */ -.fin-abo-card { overflow: hidden; } -.fin-stats { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: 6px var(--sp-3); align-items: baseline; padding: var(--sp-4); border-bottom: 1px solid var(--border); } -.fin-stat-value { font-size: 20px; font-weight: 800; } -.fin-stat-label { color: var(--text-muted); } -.fin-stat-label.fin-flag { color: var(--danger); } -.fin-stat-label svg { vertical-align: -2px; } -.fin-abos { list-style: none; margin: 0; padding: 0; } -.fin-abo { - display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px var(--sp-3); align-items: baseline; - padding: 11px var(--sp-4); border-bottom: 1px solid var(--border); -} -.fin-abo:last-child { border-bottom: none; } -.fin-abo-customer { font-weight: 700; overflow-wrap: anywhere; } -.fin-abo-meta { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 4px var(--sp-3); font-size: 13px; } +/* Block 5: Abos (Kennzahlen: .stats.fin-abo-stats ohne eigene Regel) */ +.fin-abos { list-style: none; margin: 0; padding: 0; border-top: 1px solid var(--line); } +.fin-abo { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 4px 12px; align-items: baseline; padding: 12px 0; border-bottom: 1px solid var(--line); } +.fin-abo-customer { font-weight: 600; overflow-wrap: anywhere; } +.fin-abo-meta { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; font-size: 13px; } /* Block 6 und 7: Rechnungen und Buchungen (Tabellen; in einer schmalen Karte wird jede Zeile ein Raster) */ .fin-invoices-card, .fin-tx-card { container-type: inline-size; overflow: hidden; }