feat: Absender-Zuordnungen bearbeiten und loeschen

Der Loesch-Endpunkt war da, aber ohne Knopf in der Oberflaeche, und
Bearbeiten fehlte ganz – eine falsch eingetippte Zuordnung liess sich also
nicht mehr korrigieren.

Jede Zuordnung ist jetzt eine eigene Zeile mit Erkennungstext und Konto;
der Speichern-Knopf erscheint erst bei einer Aenderung. Dazu ein PUT auf
/mail/senders/{id} im Backend.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Guido
2026-08-22 20:30:24 +02:00
co-authored by Claude Opus 5
parent 615be0f0f5
commit 7b087247a5
7 changed files with 103 additions and 9 deletions
+55 -7
View File
@@ -108,6 +108,19 @@ export default function Postfaecher() {
try { await api.deleteMailSource(quelle.id); laden(); } catch (e) { toast(e.message, "error"); }
}
async function absenderAendern(a, daten) {
try { await api.updateMailSender(a.id, daten); laden(); } catch (e) { toast(e.message, "error"); }
}
async function absenderLoeschen(a) {
if (!(await confirm({
title: t("mailbox.deleteSender"),
message: t("mailbox.deleteSenderConfirm", { text: a.from_contains || "*" }),
danger: true, confirmLabel: t("common.delete"),
}))) return;
try { await api.deleteMailSender(a.id); laden(); } catch (e) { toast(e.message, "error"); }
}
async function absenderAnlegen(quelle, daten) {
try {
await api.createMailSender(quelle.id, daten);
@@ -132,13 +145,16 @@ export default function Postfaecher() {
? `${t("mailbox.lastError")}: ${q.last_error}`
: q.last_poll_at ? t("mailbox.lastPoll") : t("mailbox.neverPolled")}
</div>
<div className="muted small" style={{ marginTop: "var(--sp-2)" }}>
{(absender[q.id] || []).length === 0
? t("mailbox.noSenders")
: (absender[q.id] || []).map((a) => {
const konto = konten.find((k) => k.id === a.account_id);
return `${a.from_contains || "*"} → ${konto ? konto.name : t("mailbox.noAccount")}`;
}).join(" · ")}
<div style={{ marginTop: "var(--sp-2)" }}>
{(absender[q.id] || []).length === 0 ? (
<p className="muted small">{t("mailbox.noSenders")}</p>
) : (
(absender[q.id] || []).map((a) => (
<SenderRow key={a.id} absender={a} konten={konten} t={t}
onSave={(d) => absenderAendern(a, d)}
onDelete={() => absenderLoeschen(a)} />
))
)}
</div>
<SenderForm konten={konten} onAdd={(d) => absenderAnlegen(q, d)} t={t} />
{uebergangen.length > 0 && (
@@ -256,6 +272,38 @@ export default function Postfaecher() {
);
}
/** Eine bestehende Zuordnung: Text und Konto änderbar, dazu löschbar. */
function SenderRow({ absender, konten, t, onSave, onDelete }) {
const [text, setText] = useState(absender.from_contains || "");
const [konto, setKonto] = useState(absender.account_id ? String(absender.account_id) : "");
const geaendert =
text !== (absender.from_contains || "") ||
konto !== (absender.account_id ? String(absender.account_id) : "");
return (
<div className="sender-row">
<input value={text} onChange={(e) => setText(e.target.value)}
placeholder={t("mailbox.fromPlaceholder")} />
<select value={konto} onChange={(e) => setKonto(e.target.value)}>
<option value="">{t("mailbox.noAccount")}</option>
{konten.map((k) => <option key={k.id} value={k.id}>{k.name}</option>)}
</select>
<div className="mail-actions" style={{ marginTop: 4 }}>
{geaendert && (
<button type="button" className="btn sm primary"
onClick={() => onSave({ from_contains: text.trim() || null,
account_id: konto ? Number(konto) : null })}>
{t("common.save")}
</button>
)}
<button type="button" className="btn sm danger" onClick={onDelete}>
<Icon name="trash" size={14} /> {t("common.delete")}
</button>
</div>
</div>
);
}
/** Absender einem Konto zuordnen – ohne das weiss Certain nicht, wohin gebucht wird. */
function SenderForm({ konten, onAdd, t }) {
const [von, setVon] = useState("");