import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { registerCustomCategories } from "@/lib/categories"; import { api } from "@/lib/api"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { ToolCard, QueryError, useToolAction } from "./shared"; export type CustomCategory = { id: string; name: string; archived: boolean }; export function CategoryTools() { const [name, setName] = useState(""); const query = useQuery({ queryKey: ["enhanced", "categories"], queryFn: async () => { const rows = await api.enhanced.get("categories"); registerCustomCategories(rows.map(r => r.name)); return rows; } }); const action = useToolAction(); return
{ e.preventDefault(); action.mutate(async () => { await api.enhanced.post("categories", { name: name.trim() }); setName(""); }); }}> setName(e.target.value)} placeholder="e.g. Community garden" required />
{query.data?.map(c =>
{c.name}{c.archived ? " (archived)" : ""}
)}

Archiving keeps historical transactions intact and removes the category from new-entry pickers.

; } export function RulePreview({ id, onRun, busy }: { id: string; onRun: () => void; busy: boolean }) { const [open, setOpen] = useState(false); const preview = useQuery({ queryKey: ["enhanced", "rule-preview", id], queryFn: () => api.enhanced.get<{ matched: number; changes: { id: string; description: string; before: string; after: string }[] }>(`rules/${id}/preview`), enabled: open, staleTime: 0 }); return
{open &&
{preview.isPending ?

Loading preview…

: preview.data && <>

{preview.data.matched} transactions match. Preview shows up to 100; running applies to all matches.

{preview.data.changes.map(c =>

{c.description}: {c.before} → {c.after}

)} }
}
; }