feat: Kontenreihenfolge selbst bestimmen

Die Liste stand in der Anlegereihenfolge, die selten die gewuenschte ist.
Neuer Endpunkt PUT /accounts/order nimmt die IDs in der gewuenschten Folge
und schreibt die Position; er muss vor /{account_id} stehen, sonst faengt
die ID-Route den Pfad ab. Fremde IDs werden abgewiesen.

In der Kontenliste zwei Pfeile je Zeile, optimistisch sortiert, damit die
Liste beim Klicken nicht springt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-21 20:36:59 +02:00
co-authored by Claude Opus 5
parent ff897c196b
commit 9642901e4e
8 changed files with 79 additions and 5 deletions
+25 -1
View File
@@ -193,6 +193,22 @@ export default function Accounts() {
}
}
// Reihenfolge per Pfeil: die Liste stand sonst in der Anlegereihenfolge.
// Optimistisch umsortieren, damit die Liste nicht bei jedem Klick springt.
async function verschiebe(index, richtung) {
const ziel = index + richtung;
if (ziel < 0 || ziel >= accounts.length) return;
const neu = [...accounts];
[neu[index], neu[ziel]] = [neu[ziel], neu[index]];
setAccounts(neu);
try {
await api.setAccountOrder(neu.map((a) => a.id));
} catch (e) {
toast(e.message, "error");
laden();
}
}
async function loeschen(acc) {
if (!(await confirm({ title: t("common.delete"), message: t("accounts.deleteConfirm"), danger: true, confirmLabel: t("common.delete") }))) return;
try {
@@ -223,7 +239,7 @@ export default function Accounts() {
<strong>{formatMoney(total, baseCurrency)}</strong>
</div>
<div className="account-list">
{accounts.map((a) => (
{accounts.map((a, i) => (
<div className="account-row" key={a.id}>
<IconChip name={a.icon} color={a.color} />
<div className="named-text grow">
@@ -236,6 +252,14 @@ export default function Accounts() {
<Money cents={a.balance_cents} currency={a.currency}
baseCents={a.balance_base_cents} baseCurrency={baseCurrency} />
<div className="row-actions">
<button className="btn sm ghost" title={t("accounts.moveUp")}
disabled={i === 0} onClick={() => verschiebe(i, -1)}>
<Icon name="chevron-up" size={15} />
</button>
<button className="btn sm ghost" title={t("accounts.moveDown")}
disabled={i === accounts.length - 1} onClick={() => verschiebe(i, 1)}>
<Icon name="chevron-down" size={15} />
</button>
<button className="btn sm ghost" title={t("common.edit")} onClick={() => openEdit(a)}>
<Icon name="edit" size={15} />
</button>