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:
Guido
2026-08-20 16:58:32 +02:00
co-authored by Claude Opus 4.8
parent d1cab19264
commit 8d0fbdfe2b
9 changed files with 148 additions and 41 deletions
+32 -2
View File
@@ -43,12 +43,19 @@ export default function Dashboard() {
async function persist(next) {
setItems(next);
try {
await api.saveDashboardLayout(next.map((it) => ({ i: it.i, type: it.type, x: it.x, y: it.y, w: it.w, h: it.h })));
await api.saveDashboardLayout(next.map((it) => ({
i: it.i, type: it.type, x: it.x, y: it.y, w: it.w, h: it.h, props: it.props,
})));
} catch (e) {
toast(e.message, "error");
}
}
// Eine Karten-Option (z. B. Aufschlüsselung Gruppen/Kategorien) setzen + sichern.
function setCardProp(i, key, value) {
persist(items.map((it) => (it.i === i ? { ...it, props: { ...(it.props || {}), [key]: value } } : it)));
}
function addCard(type) {
const def = KARTEN[type];
const maxY = items.reduce((m, it) => Math.max(m, it.y + it.h), 0);
@@ -140,7 +147,30 @@ export default function Dashboard() {
</button>
)}
</div>
<div className="dash-card-body">{def ? createElement(def.Comp) : null}</div>
<div className="dash-card-body">
{edit && def?.options?.length > 0 && (
<div className="dash-config-bar">
{def.options.map((opt) => {
const cur = it.props?.[opt.key] ?? opt.def;
return (
<div key={opt.key} className="dash-config-opt">
<span className="muted small">{t(opt.labelKey)}</span>
<div className="seg sm">
{opt.choices.map((ch) => (
<button key={ch.v} type="button"
className={`seg-btn${cur === ch.v ? " active" : ""}`}
onClick={() => setCardProp(it.i, opt.key, ch.v)}>
{t(ch.labelKey)}
</button>
))}
</div>
</div>
);
})}
</div>
)}
{def ? createElement(def.Comp, { key: JSON.stringify(it.props || {}), props: it.props }) : null}
</div>
</div>
);
})}