feat: Dashboard – Donut ohne Überlauf, Achse lesbar, Karten-Optionen
- Budget-Donut: Mittelbetrag passt sich der Länge an (kein Hineinlaufen in den Ring bei 4-stelligen Beträgen) - Vermögensverlauf: Y-Achse ohne Währungssymbol (formatAmount) + etwas mehr linker Rand → „CHF …" wird nicht mehr abgeschnitten - Karten-Optionen: im Anordnen-Modus je Karte einstellbar; Budget-Donut und Budget-Fortschritt können nach Gruppen ODER Kategorien aufschlüsseln (?group_by=), Auswahl wird im Layout (props) mitgespeichert Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -99,47 +99,69 @@ def _expense_groups(db: Session, user: User) -> list[CategoryGroup]:
|
||||
)
|
||||
|
||||
|
||||
def _units(groups: list[CategoryGroup], by_category: bool):
|
||||
"""Liefert je Anzeige-Einheit (name, farbe, [kategorie-ids]). Bei ``by_category``
|
||||
ist jede Kategorie eine eigene Einheit, sonst jede Gruppe (Summe ihrer Kategorien)."""
|
||||
for g in groups:
|
||||
if by_category:
|
||||
for c in g.categories:
|
||||
if not c.is_archived:
|
||||
yield (c.name, c.color, [c.id])
|
||||
else:
|
||||
yield (g.name, g.color, [c.id for c in g.categories if not c.is_archived])
|
||||
|
||||
|
||||
@router.get("/budget-donut")
|
||||
def budget_donut(db: Session = Depends(get_db), user: User = Depends(get_current_user)) -> dict:
|
||||
def budget_donut(
|
||||
group_by: str = Query(default="group"),
|
||||
db: Session = Depends(get_db),
|
||||
user: User = Depends(get_current_user),
|
||||
) -> dict:
|
||||
base = base_currency(db, user)
|
||||
label = _label(db, user)
|
||||
start, end = budget.period_bounds(label, _start_day(db, user))
|
||||
planned = budget.effective_planned(db, user, label)
|
||||
groups = _expense_groups(db, user)
|
||||
by_category = group_by == "category"
|
||||
|
||||
segments = []
|
||||
total = 0
|
||||
for g in groups:
|
||||
val = sum(planned.get(c.id, 0) for c in g.categories if not c.is_archived)
|
||||
if val > 0:
|
||||
segments.append({"name": g.name, "value": val, "color": g.color})
|
||||
total += val
|
||||
def collect(values: dict[int, int]):
|
||||
segs, tot = [], 0
|
||||
for name, color, ids in _units(groups, by_category):
|
||||
val = sum(values.get(i, 0) for i in ids)
|
||||
if val > 0:
|
||||
segs.append({"name": name, "value": val, "color": color})
|
||||
tot += val
|
||||
return segs, tot
|
||||
|
||||
segments, total = collect(planned)
|
||||
mode = "planned"
|
||||
if total == 0:
|
||||
actual = budget.actual_expense_by_category(db, user, start, end, base)
|
||||
for g in groups:
|
||||
val = sum(actual.get(c.id, 0) for c in g.categories if not c.is_archived)
|
||||
if val > 0:
|
||||
segments.append({"name": g.name, "value": val, "color": g.color})
|
||||
total += val
|
||||
segments, total = collect(budget.actual_expense_by_category(db, user, start, end, base))
|
||||
mode = "actual"
|
||||
return {"segments": segments, "total": total, "currency": base, "mode": mode, "label": label}
|
||||
return {"segments": segments, "total": total, "currency": base, "mode": mode,
|
||||
"label": label, "group_by": "category" if by_category else "group"}
|
||||
|
||||
|
||||
@router.get("/budget-progress")
|
||||
def budget_progress(db: Session = Depends(get_db), user: User = Depends(get_current_user)) -> dict:
|
||||
def budget_progress(
|
||||
group_by: str = Query(default="group"),
|
||||
db: Session = Depends(get_db),
|
||||
user: User = Depends(get_current_user),
|
||||
) -> dict:
|
||||
base = base_currency(db, user)
|
||||
label = _label(db, user)
|
||||
start, end = budget.period_bounds(label, _start_day(db, user))
|
||||
planned = budget.effective_planned(db, user, label)
|
||||
actual = budget.actual_expense_by_category(db, user, start, end, base)
|
||||
by_category = group_by == "category"
|
||||
rows = []
|
||||
for g in _expense_groups(db, user):
|
||||
gp = sum(planned.get(c.id, 0) for c in g.categories if not c.is_archived)
|
||||
ga = sum(actual.get(c.id, 0) for c in g.categories if not c.is_archived)
|
||||
for name, color, ids in _units(_expense_groups(db, user), by_category):
|
||||
gp = sum(planned.get(i, 0) for i in ids)
|
||||
ga = sum(actual.get(i, 0) for i in ids)
|
||||
if gp > 0 or ga > 0:
|
||||
rows.append({"name": g.name, "color": g.color, "planned": gp, "actual": ga, "over": ga > gp})
|
||||
return {"rows": rows, "currency": base, "label": label}
|
||||
rows.append({"name": name, "color": color, "planned": gp, "actual": ga, "over": ga > gp})
|
||||
return {"rows": rows, "currency": base, "label": label,
|
||||
"group_by": "category" if by_category else "group"}
|
||||
|
||||
|
||||
@router.get("/income-expense")
|
||||
|
||||
Reference in New Issue
Block a user