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:
@@ -162,6 +162,7 @@ export const api = {
|
||||
createAccount: (body) => request("/accounts", { method: "POST", body }),
|
||||
updateAccount: (id, body) => request(`/accounts/${id}`, { method: "PATCH", body }),
|
||||
deleteAccount: (id) => request(`/accounts/${id}`, { method: "DELETE" }),
|
||||
setAccountOrder: (ids) => request("/accounts/order", { method: "PUT", body: { ids } }),
|
||||
|
||||
// Kategorien (Gruppen + Kategorien)
|
||||
listCategories: () => request("/categories"),
|
||||
|
||||
@@ -72,6 +72,7 @@ const PATHS = {
|
||||
),
|
||||
"chevron-right": <path d="m9 18 6-6-6-6" />,
|
||||
"chevron-down": <path d="m6 9 6 6 6-6" />,
|
||||
"chevron-up": <path d="m18 15-6-6-6 6" />,
|
||||
tags: (
|
||||
<>
|
||||
<path d="M9 5H2v7l6.29 6.29c.94.94 2.48.94 3.42 0l3.58-3.58c.94-.94.94-2.48 0-3.42L9 5Z" />
|
||||
|
||||
@@ -167,7 +167,9 @@
|
||||
"empty": "Noch keine Konten. Lege dein erstes Konto an.",
|
||||
"deleteConfirm": "Dieses Konto und alle zugehörigen Buchungen löschen?",
|
||||
"openingDateHint": "Stand am Ende dieses Tages. Buchungen bis dahin zählen nicht zum Kontostand, bleiben aber in Liste und Auswertungen. Ohne Stichtag zählt alles.",
|
||||
"shortIdentWarning": "Kurze Erkennungstexte treffen leicht Fremdes: „Euro“ steckt auch in „Europa-Park“ oder „PayPal Europe“. Solche Buchungen werden dann fälschlich zu Überträgen. Nimm lieber etwas Eindeutiges wie die IBAN oder „Revolut**5064*“."
|
||||
"shortIdentWarning": "Kurze Erkennungstexte treffen leicht Fremdes: „Euro“ steckt auch in „Europa-Park“ oder „PayPal Europe“. Solche Buchungen werden dann fälschlich zu Überträgen. Nimm lieber etwas Eindeutiges wie die IBAN oder „Revolut**5064*“.",
|
||||
"moveUp": "Nach oben",
|
||||
"moveDown": "Nach unten"
|
||||
},
|
||||
"accountTypes": {
|
||||
"girokonto": "Girokonto",
|
||||
|
||||
@@ -167,7 +167,9 @@
|
||||
"empty": "No accounts yet. Create your first account.",
|
||||
"deleteConfirm": "Delete this account and all its transactions?",
|
||||
"openingDateHint": "Balance at the end of that day. Bookings up to then don't count toward the balance but stay in the list and reports. Without a date, everything counts.",
|
||||
"shortIdentWarning": "Short match texts hit unrelated bookings: “Euro” also occurs in “Europa-Park” or “PayPal Europe”, turning those into transfers by mistake. Prefer something unique such as the IBAN."
|
||||
"shortIdentWarning": "Short match texts hit unrelated bookings: “Euro” also occurs in “Europa-Park” or “PayPal Europe”, turning those into transfers by mistake. Prefer something unique such as the IBAN.",
|
||||
"moveUp": "Move up",
|
||||
"moveDown": "Move down"
|
||||
},
|
||||
"accountTypes": {
|
||||
"girokonto": "Checking",
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user