"use client"; import { useEffect, useState } from "react"; import { AppShell } from "../../components/app-shell"; import { apiFetch } from "@/lib/api"; type RuleRow = { id: string; name: string; priority: number; isActive: boolean; conditions: Record; actions: Record; }; type Suggestion = { id: string; name: string; conditions: Record; actions: Record; confidence: number; reason?: string; matchCount?: number; type?: string; }; export default function RulesPage() { const [rules, setRules] = useState([]); const [suggestions, setSuggestions] = useState([]); const [status, setStatus] = useState("Loading rules..."); const [showNew, setShowNew] = useState(false); const [builderMode, setBuilderMode] = useState<"simple" | "advanced">("simple"); const [advancedConditions, setAdvancedConditions] = useState(`{ "all": [ { "field": "description", "operator": "contains", "value": "coffee" }, { "any": [ { "field": "amount", "operator": ">", "value": 5 }, { "field": "source", "operator": "equals", "value": "csv" } ] } ] }`); const [form, setForm] = useState({ name: "", priority: "", textContains: "", textNotContains: "", textRegex: "", amountGreater: "", amountLess: "", amountEquals: "", sourceEquals: "", categoryEquals: "", dateAfter: "", dateBefore: "", setCategory: "", clearCategory: false, setNote: "", appendNote: "", clearNote: false, hiddenAction: "none", isActive: true }); const load = async () => { try { const [rulesPayload, suggestionsPayload] = await Promise.all([ apiFetch("/api/rules"), apiFetch("/api/rules/suggestions") ]); if (rulesPayload.error) { setStatus(rulesPayload.error?.message ?? "Unable to load rules."); return; } setRules(rulesPayload.data); setSuggestions(suggestionsPayload.data ?? []); setStatus(rulesPayload.data.length ? "" : "No rules yet."); } catch { setStatus("Unable to load rules."); } }; useEffect(() => { load(); }, []); const onCreate = async () => { let conditions: Record; if (builderMode === "advanced") { try { conditions = JSON.parse(advancedConditions) as Record; } catch { setStatus("Advanced rule DSL must be valid JSON."); return; } } else { conditions = { textContains: form.textContains || undefined, textNotContains: form.textNotContains || undefined, textRegex: form.textRegex || undefined, amountGreaterThan: form.amountGreater ? Number(form.amountGreater) : undefined, amountLessThan: form.amountLess ? Number(form.amountLess) : undefined, amountEquals: form.amountEquals ? Number(form.amountEquals) : undefined, sourceEquals: form.sourceEquals || undefined, categoryEquals: form.categoryEquals || undefined, dateAfter: form.dateAfter || undefined, dateBefore: form.dateBefore || undefined }; } const payload = { name: form.name || "Untitled rule", priority: form.priority ? Number(form.priority) : undefined, isActive: form.isActive, conditions, actions: { setCategory: form.setCategory || undefined, clearCategory: form.clearCategory || undefined, setNote: form.setNote || undefined, appendNote: form.appendNote || undefined, clearNote: form.clearNote || undefined, setHidden: form.hiddenAction === "hide" ? true : form.hiddenAction === "unhide" ? false : undefined } }; try { const data = await apiFetch("/api/rules", { method: "POST", body: JSON.stringify(payload) }); if (data.error) { setStatus(data.error?.message ?? "Unable to create rule."); return; } setShowNew(false); setForm({ name: "", priority: "", textContains: "", textNotContains: "", textRegex: "", amountGreater: "", amountLess: "", amountEquals: "", sourceEquals: "", categoryEquals: "", dateAfter: "", dateBefore: "", setCategory: "", clearCategory: false, setNote: "", appendNote: "", clearNote: false, hiddenAction: "none", isActive: true }); setRules((prev) => [data.data, ...prev]); setStatus(""); } catch { setStatus("Unable to create rule."); } }; const onExecute = async (ruleId: string) => { setStatus("Executing rule..."); const result = await apiFetch<{ applied: number; status: string }>(`/api/rules/${ruleId}/execute`, { method: "POST", }); if (result.error) { setStatus(result.error.message ?? "Unable to execute rule."); return; } setStatus(`Rule ${result.data.status}. Applied to ${result.data.applied ?? 0} transaction(s).`); }; const onAcceptSuggestion = async (item: Suggestion) => { setStatus("Saving suggested rule..."); const result = await apiFetch("/api/rules", { method: "POST", body: JSON.stringify({ name: item.name, conditions: item.conditions, actions: item.actions, isActive: true, }), }); if (result.error) { setStatus(result.error.message ?? "Unable to save suggested rule."); return; } setRules((prev) => [result.data, ...prev]); setSuggestions((prev) => prev.filter((suggestion) => suggestion.id !== item.id)); setStatus("Suggested rule saved."); }; return (
Active rules Priority ordered Auto applied
{showNew ? (

Create a rule

{builderMode === "advanced" ? (