feat: Die Abo-Auswahl zeigt das Markenlogo – und steht alphabetisch

Das native Dropdown fiel neben der Kategorie-Auswahl auseinander: anderer
Stil, kein Suchfeld, kein Symbol, und die Vertraege standen in
Anlagereihenfolge. Bei zwanzig Abos sucht man einen bestimmten, und die
Reihenfolge der Eingabe kennt niemand.

Jetzt dieselbe Auswahl wie bei der Kategorie. CategorySelect bekommt dafuer
zwei kleine Zugaenge statt eines zweiten Dropdowns: ein eigenes Symbol je
Eintrag (renderMarke) und eine Gruppe ohne Namen, die ohne Kopfzeile
auskommt – die Vertraege haben keine Gruppen. Alles andere, von der
Suche bis zum Schliessen beim Scrollen, bleibt eine Mechanik.

Das Logo schlaegt dabei das Symbol: Netflix erkennt man am roten N, nicht
am Wort. Die Fallunterscheidung dafuer stand schon in der Abo-Liste und
ist jetzt eine Komponente, die sich beide teilen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-10-15 10:25:17 +02:00
co-authored by Claude Opus 5
parent 0ea2ab1322
commit 6fc2a924d2
7 changed files with 70 additions and 24 deletions
+2 -6
View File
@@ -6,6 +6,7 @@ import { useConfirm } from "../confirm";
import { useToast } from "../toast";
import Icon from "../components/Icon";
import IconChip from "../components/IconChip";
import AboMarke from "../components/AboMarke";
import Modal from "../components/Modal";
import IconPicker, { CATEGORY_ICONS } from "../components/IconPicker";
import CategorySelect from "../components/CategorySelect";
@@ -213,12 +214,7 @@ export default function Abos() {
<div className={"abo-zeile" + beendet}>
{/* Das Logo schlaegt das Symbol: Netflix erkennt man am roten N,
nicht am Wort – und erst recht nicht an einem Zahnrad. */}
{s.has_logo ? (
<img className="abo-logo" alt=""
src={api.subscriptionLogoUrl(s.id, s.logo_version)} />
) : (
<IconChip name={s.icon} color={s.color} size={30} />
)}
<AboMarke abo={s} size={30} />
<div className="abo-mitte">
<div className="abo-kopf">
<strong className="ellipsis">{s.name}</strong>
+21 -5
View File
@@ -11,6 +11,7 @@ import Modal from "../components/Modal";
import BudgetZeitraum from "../components/BudgetZeitraum";
import AccountSelect from "../components/AccountSelect";
import CategorySelect from "../components/CategorySelect";
import AboMarke from "../components/AboMarke";
import UebertragDialog from "../components/UebertragDialog";
import DataTable from "../components/DataTable";
import { gruppenFuer } from "../kategorien";
@@ -183,6 +184,14 @@ export default function Transactions() {
&& (toAcc.savings_category_id != null || SAVINGS_TYPES.includes(toAcc.type));
// Standard-Kategorie für budgetierte Sparen-Transfers (falls vorhanden).
// Eine namenlose Gruppe: CategorySelect zeigt dann keine Kopfzeile.
// Alphabetisch, nicht in Anlagereihenfolge – bei zwanzig Vertraegen sucht
// man einen bestimmten, und die Reihenfolge der Eingabe kennt niemand.
const aboGruppe = useMemo(() => [{
id: 0, name: null,
categories: [...abos].sort((a, b) => a.name.localeCompare(b.name, i18n.language)),
}], [abos, i18n.language]);
const expenseGroups = useMemo(() => gruppenFuer(groups, "expense"), [groups]);
const incomeGroups = useMemo(() => gruppenFuer(groups, "income"), [groups]);
const sparenCatId = useMemo(() => {
@@ -481,11 +490,18 @@ export default function Transactions() {
tx.type === "transfer" || links[String(tx.id)] ? (
<span className="muted">—</span>
) : (
<select value={tx.subscription_id || ""}
onChange={(e) => aboSetzen(tx, e.target.value ? Number(e.target.value) : null)}>
<option value="">{t("abos.noneAssigned")}</option>
{abos.map((a) => <option key={a.id} value={a.id}>{a.name}</option>)}
</select>
// Dieselbe Auswahl wie bei der Kategorie – nur flach (die Vertraege
// haben keine Gruppen) und mit dem Markenlogo statt eines Symbols.
<CategorySelect
groups={aboGruppe}
value={tx.subscription_id}
onChange={(id) => aboSetzen(tx, id)}
renderMarke={(a, size) => <AboMarke abo={a} size={size} />}
searchPlaceholder={t("abos.searchPlaceholder")}
allowNone
noneLabel={t("abos.noneAssigned")}
placeholder={t("abos.noneAssigned")}
/>
)
),
},