feat: Dashboard-Karte „Ausgaben-Anteile"
Zeigt für den laufenden Budget-Monat, wie sich die tatsächlichen Ausgaben prozentual auf die Kategorien verteilen – absteigend sortiert, mit Balken in der Kategoriefarbe, Prozentwert und Betrag. Anders als der Budget-Donut hängt sie nicht am geplanten Budget: Sie zeigt reine Ist-Ausgaben, auch wenn für den Monat nichts geplant wurde. Umschaltbar zwischen Kategorien (Standard) und Gruppen. Die Anteile rechnet die Oberfläche aus Betrag und Summe – gerundete Prozente vom Server addierten sich sonst nicht auf 100 %. Wird die Karte schmal, gibt zuerst der Betrag nach; ohne Kategorienamen sagt die Zeile nichts mehr aus. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -149,6 +149,40 @@ function RecentTransactions() {
|
||||
);
|
||||
}
|
||||
|
||||
function ExpenseShare({ props }) {
|
||||
const { t } = useTranslation();
|
||||
const groupBy = props?.group_by || "category";
|
||||
const data = useData(() => api.cardExpenseShare(groupBy));
|
||||
if (!data) return <Loading />;
|
||||
if (!data.rows?.length) return <div className="dash-empty muted">{t("cards.noExpenses")}</div>;
|
||||
return (
|
||||
<div className="dash-list">
|
||||
{data.rows.map((r) => {
|
||||
// Anteil aus den Rohwerten – gerundete Prozente ergäben in der Summe
|
||||
// sonst nicht 100 %.
|
||||
const pct = data.total > 0 ? (r.amount / data.total) * 100 : 0;
|
||||
return (
|
||||
<div className="dash-progress" key={r.name}>
|
||||
<div className="dash-progress-top share-row">
|
||||
<span style={{ color: r.color }}>●</span>
|
||||
<span className="grow ellipsis share-name">{r.name}</span>
|
||||
<strong className="share-pct">{pct.toFixed(1).replace(".", ",")} %</strong>
|
||||
<span className="muted small share-amount">{formatMoney(r.amount, data.currency)}</span>
|
||||
</div>
|
||||
<div className="budget-bar">
|
||||
<div className="budget-bar-fill" style={{ width: `${pct}%`, background: r.color }} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
<div className="dash-total">
|
||||
<span className="grow">{t("cards.total")}</span>
|
||||
<strong>{formatMoney(data.total, data.currency)}</strong>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Aufschlüsselungs-Option (Gruppen vs. Kategorien) – von Donut und Fortschritt geteilt.
|
||||
const AUFSCHLUESSELUNG = {
|
||||
key: "group_by", labelKey: "cards.breakdown", def: "group",
|
||||
@@ -165,6 +199,8 @@ export const KARTEN = {
|
||||
"budget-donut": { titleKey: "cards.budgetDonut", Comp: BudgetDonut, w: 4, h: 5, minW: 3, minH: 4, options: [AUFSCHLUESSELUNG] },
|
||||
"net-worth": { titleKey: "cards.netWorth", Comp: NetWorth, w: 8, h: 4, minW: 4, minH: 3 },
|
||||
"budget-progress": { titleKey: "cards.budgetProgress", Comp: BudgetProgress, w: 4, h: 3, minW: 3, minH: 2, options: [AUFSCHLUESSELUNG] },
|
||||
"expense-share": { titleKey: "cards.expenseShare", Comp: ExpenseShare, w: 4, h: 5, minW: 3, minH: 3,
|
||||
options: [{ ...AUFSCHLUESSELUNG, def: "category" }] },
|
||||
"income-expense": { titleKey: "cards.incomeExpense", Comp: IncomeExpense, w: 4, h: 2, minW: 2, minH: 2 },
|
||||
"recent-transactions": { titleKey: "cards.recent", Comp: RecentTransactions, w: 4, h: 4, minW: 3, minH: 3 },
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user