feat: Nur die Waehrungen zeigen, die man braucht – und auf dem Telefon lesbar
Drei Sachen, die beim Benutzen aufgefallen sind. ANGEBOTENE WAEHRUNGEN. In den Einstellungen steht jetzt, welche Waehrungen in den Auswahlfeldern erscheinen (Vorgabe EUR, CHF, USD). Dass dreissig unterstuetzt werden, ist gut; dreissig Zeilen in einem Feld zu sehen, wenn man drei davon braucht, ist es nicht. Die Einstellung beschraenkt nur die ANZEIGE. Gueltig bleibt alles mit EZB-Kurs, und die gerade gesetzte Waehrung steht IMMER im Feld – sonst stuende ein Konto in Dollar vor einem Feld ohne Dollar und wechselte beim naechsten Speichern stillschweigend die Waehrung. Der Saldo waere danach still falsch. Die Hauptwaehrung laesst sich nicht abwaehlen. ZEICHEN STATT KUERZEL, wo es hilft: "€ EUR", "$ USD", "£ GBP". CHF steht bewusst ohne: "Fr." ist weniger gelaeufig als CHF selbst. Mehrfach belegte Zeichen bleiben ebenfalls draussen – "US$" von "CA$" zu unterscheiden ist Raetselraten. AUF DEM TELEFON. Die Vertragszeile quetschte vier Spalten in 375 Pixel; vom Namen blieb "Sal..." uebrig und von der Kategorie ein Punkt. Sie ist jetzt ein Gitter: Unter 620 Pixel rutscht der Betrag unter den Namen, die Knoepfe bleiben rechts. Der Name bekommt die volle Breite – auf dem Telefon ist er das Einzige, woran man die Zeile erkennt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f99be47587
commit
0a790fd45a
@@ -20,6 +20,11 @@ BUDGET_START_DAY_KEY = "budget_start_day"
|
|||||||
LOCALE_KEY = "locale"
|
LOCALE_KEY = "locale"
|
||||||
THEME_KEY = "theme"
|
THEME_KEY = "theme"
|
||||||
DATE_FORMAT_KEY = "date_format"
|
DATE_FORMAT_KEY = "date_format"
|
||||||
|
# Welche Waehrungen in Auswahlfeldern erscheinen (Komma-getrennt).
|
||||||
|
# Beschraenkt nur die ANZEIGE: Gueltig bleibt alles, was einen
|
||||||
|
# EZB-Referenzkurs hat – sonst verloere ein Konto seine Waehrung,
|
||||||
|
# nur weil sie hier gerade nicht angehakt ist.
|
||||||
|
ACTIVE_CURRENCIES_KEY = "active_currencies"
|
||||||
|
|
||||||
|
|
||||||
def known_defaults() -> dict[str, str]:
|
def known_defaults() -> dict[str, str]:
|
||||||
@@ -29,6 +34,7 @@ def known_defaults() -> dict[str, str]:
|
|||||||
LOCALE_KEY: "de",
|
LOCALE_KEY: "de",
|
||||||
THEME_KEY: "system",
|
THEME_KEY: "system",
|
||||||
DATE_FORMAT_KEY: "de",
|
DATE_FORMAT_KEY: "de",
|
||||||
|
ACTIVE_CURRENCIES_KEY: "EUR,CHF,USD",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -161,3 +161,20 @@ def test_fremdes_konto_wird_nicht_angenommen(admin):
|
|||||||
"name": "Fremdzugriff", "amount_cents": 100, "currency": "EUR",
|
"name": "Fremdzugriff", "amount_cents": 100, "currency": "EUR",
|
||||||
"interval_months": 1, "account_id": fremd["id"]})
|
"interval_months": 1, "account_id": fremd["id"]})
|
||||||
assert r.status_code == 404, r.text
|
assert r.status_code == 404, r.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_angebotene_waehrungen_sind_eine_einstellung(admin):
|
||||||
|
"""Die Liste beschraenkt nur die ANZEIGE. Gueltig bleibt alles mit Kurs –
|
||||||
|
sonst verloere ein Konto seine Waehrung, weil sie gerade nicht angehakt
|
||||||
|
ist."""
|
||||||
|
rows = {r["key"]: r["value"] for r in admin.get("/api/settings").json()}
|
||||||
|
assert rows["active_currencies"] == "EUR,CHF,USD", "Vorgabe passt nicht"
|
||||||
|
|
||||||
|
r = admin.put("/api/settings/active_currencies", params={"value": "EUR,CHF"})
|
||||||
|
assert r.status_code == 200, r.text
|
||||||
|
try:
|
||||||
|
# Trotz Abwahl weiter anlegbar: Die Einstellung ist keine Sperre.
|
||||||
|
abo = _abo(admin, currency="USD", amount_cents=500)
|
||||||
|
assert abo["currency"] == "USD"
|
||||||
|
finally:
|
||||||
|
admin.put("/api/settings/active_currencies", params={"value": "EUR,CHF,USD"})
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { useSettings } from "../settings";
|
||||||
|
import { angeboteneWaehrungen, waehrungLabel } from "../waehrungen";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Währungs-Auswahl. Zeigt die eingestellten Währungen – und immer die gerade
|
||||||
|
* gesetzte dazu.
|
||||||
|
*
|
||||||
|
* Das Letzte ist der Punkt: Ein Konto in Dollar vor einem Feld ohne Dollar
|
||||||
|
* wechselte beim nächsten Speichern stillschweigend die Währung, und der
|
||||||
|
* Saldo wäre danach still falsch.
|
||||||
|
*/
|
||||||
|
export default function CurrencySelect({ value, onChange, disabled = false, ...rest }) {
|
||||||
|
const { activeCurrencies } = useSettings();
|
||||||
|
const liste = angeboteneWaehrungen(activeCurrencies, value);
|
||||||
|
return (
|
||||||
|
<select
|
||||||
|
value={value}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={(e) => onChange(e.target.value)}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{liste.map((w) => (
|
||||||
|
<option key={w} value={w}>
|
||||||
|
{waehrungLabel(w)}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
);
|
||||||
|
}
|
||||||
+65
-4
@@ -1646,27 +1646,38 @@ tr.row-dup {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
/* Als Gitter und nicht als Reihe: Auf dem Telefon rutscht der Betrag unter den
|
||||||
|
Namen, statt vier Spalten in 375 Pixel zu quetschen. Mit Reihe blieb vom
|
||||||
|
Namen "Sal..." uebrig und von der Kategorie ein Punkt. */
|
||||||
.abo-zeile {
|
.abo-zeile {
|
||||||
display: flex;
|
display: grid;
|
||||||
|
grid-template-columns: auto 1fr auto auto;
|
||||||
|
grid-template-areas: "symbol mitte betrag knoepfe";
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--sp-3);
|
gap: var(--sp-3);
|
||||||
padding: var(--sp-2) 0;
|
padding: var(--sp-2) 0;
|
||||||
border-bottom: 1px solid var(--border);
|
border-bottom: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
.abo-zeile > :nth-child(1) { grid-area: symbol; }
|
||||||
|
.abo-mitte { grid-area: mitte; }
|
||||||
|
.abo-betrag { grid-area: betrag; }
|
||||||
|
.abo-knoepfe { grid-area: knoepfe; }
|
||||||
.abo-zeile:last-child { border-bottom: none; }
|
.abo-zeile:last-child { border-bottom: none; }
|
||||||
/* Gekündigtes bleibt lesbar, tritt aber zurück – es zählt nicht mehr mit. */
|
/* Gekündigtes bleibt lesbar, tritt aber zurück – es zählt nicht mehr mit. */
|
||||||
.abo-beendet { opacity: 0.55; }
|
.abo-beendet { opacity: 0.55; }
|
||||||
|
|
||||||
.abo-mitte { flex: 1; min-width: 0; }
|
.abo-mitte { min-width: 0; }
|
||||||
.abo-kopf {
|
.abo-kopf {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
gap: var(--sp-2);
|
flex-wrap: wrap;
|
||||||
|
gap: 2px var(--sp-2);
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
}
|
}
|
||||||
.abo-unten {
|
.abo-unten {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 5px;
|
flex-wrap: wrap;
|
||||||
|
gap: 0 5px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
}
|
}
|
||||||
@@ -1688,3 +1699,53 @@ tr.row-dup {
|
|||||||
padding-left: var(--sp-3);
|
padding-left: var(--sp-3);
|
||||||
margin-bottom: var(--sp-3);
|
margin-bottom: var(--sp-3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 620px) {
|
||||||
|
/* Betrag unter den Namen, Knoepfe bleiben rechts oben. So bekommt der Name
|
||||||
|
die volle Breite – auf dem Telefon ist er das Einzige, woran man die Zeile
|
||||||
|
erkennt. */
|
||||||
|
.abo-zeile {
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
grid-template-areas:
|
||||||
|
"symbol mitte knoepfe"
|
||||||
|
"symbol betrag knoepfe";
|
||||||
|
align-items: start;
|
||||||
|
row-gap: 2px;
|
||||||
|
padding: var(--sp-3) 0;
|
||||||
|
}
|
||||||
|
.abo-zeile > :nth-child(1),
|
||||||
|
.abo-knoepfe { align-self: center; }
|
||||||
|
/* Waagrecht statt untereinander: Unter dem Namen ist Breite da, Hoehe nicht. */
|
||||||
|
.abo-betrag {
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0 var(--sp-2);
|
||||||
|
}
|
||||||
|
.abo-summe { gap: var(--sp-3) var(--sp-5); }
|
||||||
|
.abo-summe-zahl { font-size: 1.2rem; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Waehrungs-Auswahl in den Einstellungen: ein Gitter aus Kacheln statt einer
|
||||||
|
Liste mit dreissig Zeilen. */
|
||||||
|
.waehrung-gitter {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: var(--sp-2);
|
||||||
|
}
|
||||||
|
.waehrung-kachel {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius-sm, 6px);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.waehrung-kachel.an { border-color: var(--accent, var(--border-strong)); }
|
||||||
|
.waehrung-kachel input { width: auto; margin: 0; }
|
||||||
|
.waehrung-kachel:has(input:disabled) { opacity: 0.6; cursor: default; }
|
||||||
|
|||||||
@@ -77,6 +77,8 @@
|
|||||||
"general": "Darstellung & Sprache",
|
"general": "Darstellung & Sprache",
|
||||||
"baseCurrency": "Hauptwährung",
|
"baseCurrency": "Hauptwährung",
|
||||||
"baseCurrencyHint": "Währung für Budget-Summen und Dashboard-Gesamtwerte.",
|
"baseCurrencyHint": "Währung für Budget-Summen und Dashboard-Gesamtwerte.",
|
||||||
|
"activeCurrencies": "Angebotene Währungen",
|
||||||
|
"activeCurrenciesHint": "Nur diese stehen in den Auswahlfeldern für Konten und Verträge. Alle übrigen bleiben gültig – wer eine davon schon benutzt, sieht sie dort weiterhin.",
|
||||||
"locale": "Sprache",
|
"locale": "Sprache",
|
||||||
"theme": "Darstellung",
|
"theme": "Darstellung",
|
||||||
"themeSystem": "Automatisch (System)",
|
"themeSystem": "Automatisch (System)",
|
||||||
|
|||||||
@@ -77,6 +77,8 @@
|
|||||||
"general": "Appearance & language",
|
"general": "Appearance & language",
|
||||||
"baseCurrency": "Base currency",
|
"baseCurrency": "Base currency",
|
||||||
"baseCurrencyHint": "Currency for budget totals and dashboard aggregates.",
|
"baseCurrencyHint": "Currency for budget totals and dashboard aggregates.",
|
||||||
|
"activeCurrencies": "Offered currencies",
|
||||||
|
"activeCurrenciesHint": "Only these appear in the currency pickers for accounts and contracts. All others stay valid – if one is already in use, it still shows up there.",
|
||||||
"locale": "Language",
|
"locale": "Language",
|
||||||
"theme": "Appearance",
|
"theme": "Appearance",
|
||||||
"themeSystem": "Automatic (system)",
|
"themeSystem": "Automatic (system)",
|
||||||
|
|||||||
+4
-10
@@ -11,7 +11,7 @@ import IconPicker, { CATEGORY_ICONS } from "../components/IconPicker";
|
|||||||
import CategorySelect from "../components/CategorySelect";
|
import CategorySelect from "../components/CategorySelect";
|
||||||
import AccountSelect from "../components/AccountSelect";
|
import AccountSelect from "../components/AccountSelect";
|
||||||
import { gruppenFuer } from "../kategorien";
|
import { gruppenFuer } from "../kategorien";
|
||||||
import { WAEHRUNGEN } from "../waehrungen";
|
import CurrencySelect from "../components/CurrencySelect";
|
||||||
import { centsToInput, formatDate, formatMoney, parseAmountToCents } from "../format";
|
import { centsToInput, formatDate, formatMoney, parseAmountToCents } from "../format";
|
||||||
|
|
||||||
// Muss zu INTERVALL_MONATE im Server passen.
|
// Muss zu INTERVALL_MONATE im Server passen.
|
||||||
@@ -334,16 +334,10 @@ export default function Abos() {
|
|||||||
</label>
|
</label>
|
||||||
<label style={{ minWidth: 110 }}>
|
<label style={{ minWidth: 110 }}>
|
||||||
<span>{t("abos.currency")}</span>
|
<span>{t("abos.currency")}</span>
|
||||||
<select
|
<CurrencySelect
|
||||||
value={form.currency}
|
value={form.currency}
|
||||||
onChange={(e) => setForm({ ...form, currency: e.target.value })}
|
onChange={(w) => setForm({ ...form, currency: w })}
|
||||||
>
|
/>
|
||||||
{WAEHRUNGEN.map((w) => (
|
|
||||||
<option key={w} value={w}>
|
|
||||||
{w}
|
|
||||||
</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import Modal from "../components/Modal";
|
|||||||
import IconPicker, { ACCOUNT_ICONS } from "../components/IconPicker";
|
import IconPicker, { ACCOUNT_ICONS } from "../components/IconPicker";
|
||||||
import CategorySelect from "../components/CategorySelect";
|
import CategorySelect from "../components/CategorySelect";
|
||||||
import { gruppenFuer } from "../kategorien";
|
import { gruppenFuer } from "../kategorien";
|
||||||
import { WAEHRUNGEN } from "../waehrungen";
|
import CurrencySelect from "../components/CurrencySelect";
|
||||||
import { centsToInput, formatDate, formatMoney, parseAmountToCents } from "../format";
|
import { centsToInput, formatDate, formatMoney, parseAmountToCents } from "../format";
|
||||||
|
|
||||||
const ACCOUNT_TYPES = ["girokonto", "sparkonto", "bargeld", "kreditkarte", "depot", "sonstiges"];
|
const ACCOUNT_TYPES = ["girokonto", "sparkonto", "bargeld", "kreditkarte", "depot", "sonstiges"];
|
||||||
@@ -470,9 +470,8 @@ export default function Accounts() {
|
|||||||
</label>
|
</label>
|
||||||
<label style={{ minWidth: 120 }}>
|
<label style={{ minWidth: 120 }}>
|
||||||
<span>{t("accounts.currency")}</span>
|
<span>{t("accounts.currency")}</span>
|
||||||
<select value={form.currency} onChange={(e) => setForm({ ...form, currency: e.target.value })}>
|
<CurrencySelect value={form.currency}
|
||||||
{WAEHRUNGEN.map((w) => <option key={w} value={w}>{w}</option>)}
|
onChange={(w) => setForm({ ...form, currency: w })} />
|
||||||
</select>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { useAuth } from "../auth";
|
|||||||
import { useToast } from "../toast";
|
import { useToast } from "../toast";
|
||||||
import { api } from "../api";
|
import { api } from "../api";
|
||||||
import Icon from "../components/Icon";
|
import Icon from "../components/Icon";
|
||||||
|
import CurrencySelect from "../components/CurrencySelect";
|
||||||
|
import { WAEHRUNGEN, waehrungLabel } from "../waehrungen";
|
||||||
import PolicyCard from "./settings/PolicyCard";
|
import PolicyCard from "./settings/PolicyCard";
|
||||||
import Bankanbindung from "./settings/Bankanbindung";
|
import Bankanbindung from "./settings/Bankanbindung";
|
||||||
import Postfaecher from "./settings/Postfaecher";
|
import Postfaecher from "./settings/Postfaecher";
|
||||||
@@ -168,13 +170,36 @@ export default function Settings() {
|
|||||||
<h2 style={{ marginTop: 0 }}>{t("settings.general")}</h2>
|
<h2 style={{ marginTop: 0 }}>{t("settings.general")}</h2>
|
||||||
<label>
|
<label>
|
||||||
<span>{t("settings.baseCurrency")}</span>
|
<span>{t("settings.baseCurrency")}</span>
|
||||||
<select value={s.base_currency} onChange={(e) => set("base_currency", e.target.value)}>
|
<CurrencySelect value={s.base_currency}
|
||||||
<option value="EUR">Euro (EUR)</option>
|
onChange={(w) => set("base_currency", w)} />
|
||||||
<option value="CHF">Schweizer Franken (CHF)</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
</label>
|
||||||
<p className="muted small" style={{ marginTop: -8 }}>{t("settings.baseCurrencyHint")}</p>
|
<p className="muted small" style={{ marginTop: -8 }}>{t("settings.baseCurrencyHint")}</p>
|
||||||
|
|
||||||
|
<label style={{ marginBottom: 6 }}>
|
||||||
|
<span>{t("settings.activeCurrencies")}</span>
|
||||||
|
</label>
|
||||||
|
<div className="waehrung-gitter">
|
||||||
|
{WAEHRUNGEN.map((w) => {
|
||||||
|
const an = s.activeCurrencies.includes(w);
|
||||||
|
// Die Hauptwährung lässt sich nicht abwählen – ohne sie
|
||||||
|
// stünde jede Summe vor einem Feld ohne ihre eigene Einheit.
|
||||||
|
const fest = w === s.base_currency;
|
||||||
|
return (
|
||||||
|
<label key={w} className={`waehrung-kachel${an ? " an" : ""}`}>
|
||||||
|
<input type="checkbox" checked={an} disabled={fest}
|
||||||
|
onChange={(e) => {
|
||||||
|
const naechste = e.target.checked
|
||||||
|
? [...s.activeCurrencies, w]
|
||||||
|
: s.activeCurrencies.filter((x) => x !== w);
|
||||||
|
set("active_currencies", naechste.join(","));
|
||||||
|
}} />
|
||||||
|
<span>{waehrungLabel(w)}</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
<p className="muted small">{t("settings.activeCurrenciesHint")}</p>
|
||||||
|
|
||||||
<label>
|
<label>
|
||||||
<span>{t("settings.locale")}</span>
|
<span>{t("settings.locale")}</span>
|
||||||
<select value={s.locale} onChange={(e) => set("locale", e.target.value)}>
|
<select value={s.locale} onChange={(e) => set("locale", e.target.value)}>
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { api } from "./api";
|
|||||||
import { useAuth } from "./auth";
|
import { useAuth } from "./auth";
|
||||||
import { applyTheme } from "./theme";
|
import { applyTheme } from "./theme";
|
||||||
import { formatMoney as fmtMoney, formatDate as fmtDate } from "./format";
|
import { formatMoney as fmtMoney, formatDate as fmtDate } from "./format";
|
||||||
|
import { ausEinstellung, STANDARD_AKTIV } from "./waehrungen";
|
||||||
|
|
||||||
const DEFAULTS = {
|
const DEFAULTS = {
|
||||||
base_currency: "EUR",
|
base_currency: "EUR",
|
||||||
@@ -11,6 +12,10 @@ const DEFAULTS = {
|
|||||||
theme: "system",
|
theme: "system",
|
||||||
date_format: "de",
|
date_format: "de",
|
||||||
budget_start_day: "1",
|
budget_start_day: "1",
|
||||||
|
// Welche Währungen in Auswahlfeldern erscheinen. Der Rest bleibt erlaubt,
|
||||||
|
// steht aber nicht im Weg: Dreissig Zeilen sind kein Gewinn, wenn man drei
|
||||||
|
// davon braucht.
|
||||||
|
active_currencies: STANDARD_AKTIV.join(","),
|
||||||
};
|
};
|
||||||
|
|
||||||
const SettingsContext = createContext({
|
const SettingsContext = createContext({
|
||||||
@@ -61,6 +66,7 @@ export function SettingsProvider({ children }) {
|
|||||||
const value = {
|
const value = {
|
||||||
...values,
|
...values,
|
||||||
baseCurrency: values.base_currency,
|
baseCurrency: values.base_currency,
|
||||||
|
activeCurrencies: ausEinstellung(values.active_currencies),
|
||||||
formatMoney: (cents, currency) => fmtMoney(cents, currency ?? values.base_currency),
|
formatMoney: (cents, currency) => fmtMoney(cents, currency ?? values.base_currency),
|
||||||
formatDate: (iso) => fmtDate(iso, values.locale),
|
formatDate: (iso) => fmtDate(iso, values.locale),
|
||||||
update,
|
update,
|
||||||
|
|||||||
+66
-1
@@ -1,4 +1,4 @@
|
|||||||
// Welche Währungen die Oberfläche anbietet.
|
// Welche Währungen die Oberfläche kennt – und wie sie geschrieben werden.
|
||||||
//
|
//
|
||||||
// Muss zur Erlaubnisliste des Servers passen (backend/app/waehrungen.py):
|
// Muss zur Erlaubnisliste des Servers passen (backend/app/waehrungen.py):
|
||||||
// genau die Währungen, für die es einen EZB-Referenzkurs gibt. Ohne Kurs
|
// genau die Währungen, für die es einen EZB-Referenzkurs gibt. Ohne Kurs
|
||||||
@@ -15,3 +15,68 @@ const REST = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const WAEHRUNGEN = [...VORNE, ...REST];
|
export const WAEHRUNGEN = [...VORNE, ...REST];
|
||||||
|
|
||||||
|
// Zeichen, wo es eines gibt, das kürzer und eindeutiger ist als das Kürzel.
|
||||||
|
// CHF steht bewusst NICHT drin: "Fr." ist weniger geläufig als CHF selbst,
|
||||||
|
// und ein Zeichen, das niemand liest, hilft niemandem. Mehrfach belegte
|
||||||
|
// Zeichen ($, kr) bleiben ebenfalls draussen – dort ist das Kürzel die
|
||||||
|
// klarere Angabe, und "US$" neben "CA$" zu unterscheiden ist Rätselraten.
|
||||||
|
const ZEICHEN = {
|
||||||
|
EUR: "€",
|
||||||
|
USD: "$",
|
||||||
|
GBP: "£",
|
||||||
|
JPY: "¥",
|
||||||
|
CNY: "¥",
|
||||||
|
ILS: "₪",
|
||||||
|
INR: "₹",
|
||||||
|
KRW: "₩",
|
||||||
|
PHP: "₱",
|
||||||
|
THB: "฿",
|
||||||
|
TRY: "₺",
|
||||||
|
PLN: "zł",
|
||||||
|
BRL: "R$",
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Wie eine Währung in Listen und Auswahlfeldern steht. */
|
||||||
|
export function waehrungLabel(code) {
|
||||||
|
const zeichen = ZEICHEN[code];
|
||||||
|
return zeichen ? `${zeichen} ${code}` : code;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Nur das Zeichen, wo es eines gibt – sonst das Kürzel. */
|
||||||
|
export function waehrungZeichen(code) {
|
||||||
|
return ZEICHEN[code] || code;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Was ohne eigene Einstellung angeboten wird. Der Rest bleibt erreichbar,
|
||||||
|
// muss aber erst eingeschaltet werden: Dreissig Zeilen in einem Auswahlfeld
|
||||||
|
// sind kein Gewinn, wenn man drei davon braucht.
|
||||||
|
export const STANDARD_AKTIV = ["EUR", "CHF", "USD"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Die Währungen, die ein Auswahlfeld zeigen soll.
|
||||||
|
*
|
||||||
|
* ``aktiv`` ist die Einstellung des Nutzers. ``dazu`` sind Werte, die trotzdem
|
||||||
|
* dabei sein müssen – allen voran der gerade gesetzte: Sonst stünde ein Konto
|
||||||
|
* in USD vor einem Feld ohne USD und wechselte beim nächsten Speichern
|
||||||
|
* stillschweigend die Währung.
|
||||||
|
*/
|
||||||
|
export function angeboteneWaehrungen(aktiv, ...dazu) {
|
||||||
|
const erlaubt = new Set(WAEHRUNGEN);
|
||||||
|
const gewaehlt = (aktiv && aktiv.length ? aktiv : STANDARD_AKTIV).filter((w) =>
|
||||||
|
erlaubt.has(w),
|
||||||
|
);
|
||||||
|
const menge = new Set(gewaehlt);
|
||||||
|
for (const w of dazu) if (w && erlaubt.has(w)) menge.add(w);
|
||||||
|
return WAEHRUNGEN.filter((w) => menge.has(w));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Die Einstellung als Liste – gespeichert wird sie als "EUR,CHF,USD". */
|
||||||
|
export function ausEinstellung(wert) {
|
||||||
|
if (!wert) return [...STANDARD_AKTIV];
|
||||||
|
const liste = wert
|
||||||
|
.split(",")
|
||||||
|
.map((s) => s.trim().toUpperCase())
|
||||||
|
.filter((s) => WAEHRUNGEN.includes(s));
|
||||||
|
return liste.length ? liste : [...STANDARD_AKTIV];
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user