import { useEffect, useState } from 'react' import { Plus, Trash2, X } from 'lucide-react' import { api } from '../lib/api' import type { Account, Folder, MatchType, NewRule, Rule, RuleAction, RuleCondition } from '../types' const inputClass = 'rounded-lg border border-zinc-200 bg-white px-3 py-1.5 text-[13px] text-zinc-800 outline-none transition focus:border-indigo-400 focus:ring-2 focus:ring-indigo-100 dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:focus:ring-indigo-500/20' function Field({ label, children }: { label: string; children: React.ReactNode }) { return ( ) } function conditionsSummary(conditions: RuleCondition[], matchType: MatchType) { const parts = conditions.map((c) => `${c.field === 'subject' ? 'Betreff' : 'Absender'} enthält „${c.contains}“`) const joiner = matchType === 'all' ? ' UND ' : ' ODER ' return parts.join(joiner) } function actionLabel(r: Rule, accountFolders: Record) { switch (r.action) { case 'move_to': { const folders = r.account_id ? accountFolders[r.account_id] ?? [] : [] const folder = folders.find((f) => f.name === r.action_value) return `verschieben nach „${folder?.display_name ?? r.action_value}“` } case 'mark_read': return 'als gelesen markieren' case 'flag': return 'markieren' case 'delete': return 'löschen' } } const emptyCondition = (): RuleCondition => ({ field: 'from', contains: '' }) export function RulesModal({ open, onClose, accounts, accountFolders, onToast, }: { open: boolean onClose: () => void accounts: Account[] accountFolders: Record onToast: (msg: string) => void }) { const [rules, setRules] = useState([]) const [loading, setLoading] = useState(false) const [accountId, setAccountId] = useState('') const [conditions, setConditions] = useState([emptyCondition()]) const [matchType, setMatchType] = useState('all') const [action, setAction] = useState('move_to') const [actionValue, setActionValue] = useState('') const [saving, setSaving] = useState(false) const ruleAccounts = accounts.filter((a) => a.protocol === 'imap' || a.protocol === 'pop3') const targetFolders = accountId ? accountFolders[accountId] ?? [] : [] const validConditions = conditions.filter((c) => c.contains.trim()) const isPop3 = accounts.find((a) => a.id === accountId)?.protocol === 'pop3' function refresh() { setLoading(true) api .listRules() .then(setRules) .catch((e) => onToast(`Regeln konnten nicht geladen werden: ${e}`)) .finally(() => setLoading(false)) } useEffect(() => { if (open) refresh() // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]) if (!open) return null function updateCondition(index: number, patch: Partial) { setConditions((prev) => prev.map((c, i) => (i === index ? { ...c, ...patch } : c))) } function addCondition() { setConditions((prev) => [...prev, emptyCondition()]) } function removeCondition(index: number) { setConditions((prev) => (prev.length > 1 ? prev.filter((_, i) => i !== index) : prev)) } async function handleAdd(e: React.FormEvent) { e.preventDefault() if (validConditions.length === 0) return if (action === 'move_to' && !actionValue) return setSaving(true) try { const newRule: NewRule = { account_id: accountId || null, conditions: validConditions.map((c) => ({ field: c.field, contains: c.contains.trim() })), match_type: matchType, action, action_value: action === 'move_to' ? actionValue : undefined, } await api.createRule(newRule) setConditions([emptyCondition()]) setMatchType('all') setActionValue('') refresh() onToast('Regel erstellt') } catch (err) { onToast(`Fehlgeschlagen: ${err}`) } finally { setSaving(false) } } async function handleToggle(r: Rule) { setRules((prev) => prev.map((x) => (x.id === r.id ? { ...x, enabled: !x.enabled } : x))) try { await api.setRuleEnabled(r.id, !r.enabled) } catch (err) { onToast(String(err)) } } async function handleDelete(r: Rule) { setRules((prev) => prev.filter((x) => x.id !== r.id)) try { await api.deleteRule(r.id) } catch (err) { onToast(String(err)) } } return (

Regeln verwalten

Neue Regel

Bedingungen {conditions.length > 1 && (
)}
{conditions.map((c, i) => (
updateCondition(i, { contains: e.target.value })} placeholder="z. B. newsletter@ oder Rechnung" />
))}
{isPop3 && (

POP3 hat keine Server-Ordner, „Verschieben“ ist daher nicht verfügbar.

)}
{action === 'move_to' && ( )}

Aktive Regeln

{loading ? (

Lade…

) : rules.length === 0 ? (

Noch keine Regeln angelegt.

) : (
{rules.map((r) => (
handleToggle(r)} className="rounded border-zinc-300" />
{conditionsSummary(r.conditions, r.match_type)} {' → '} {actionLabel(r, accountFolders)} {r.account_id && ( {' '} ({accounts.find((a) => a.id === r.account_id)?.label ?? '?'}) )}
))}
)}
) }