// App — composition root function App() { const [entity, setEntity] = React.useState("all"); const [screen, setScreen] = React.useState("dashboard"); const [query, setQuery] = React.useState(""); const [sort, setSort] = React.useState("date-desc"); const [transactions, setTransactions] = React.useState(window.AKEFIN_DATA.transactions); const [selectedId, setSelectedId] = React.useState(window.AKEFIN_DATA.transactions[0].id); const [toast, setToast] = React.useState(null); const [paletteOpen, setPaletteOpen] = React.useState(false); const [csvOpen, setCsvOpen] = React.useState(false); const [theme, setTheme] = React.useState("light"); const [filters, setFilters] = React.useState({ dateRange: "all", dateFrom: "", dateTo: "", categories: new Set(), tiers: new Set(), confidenceMin: 0, ccy: new Set(), }); // Set theme on root React.useEffect(() => { document.documentElement.setAttribute("data-theme", theme); }, [theme]); const showToast = (msg) => { setToast(msg); setTimeout(() => setToast(null), 2400); }; // ⌘K to open command palette React.useEffect(() => { const onKey = (e) => { if ((e.metaKey || e.ctrlKey) && e.key === "k") { e.preventDefault(); setPaletteOpen(o => !o); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, []); const approve = (id, account) => { setTransactions(prev => prev.filter(t => t.id !== id)); const next = transactions.find(t => t.id !== id); if (next) setSelectedId(next.id); showToast(`Approved · posted to ${account || "—"}`); }; const skip = (id) => { setTransactions(prev => prev.filter(t => t.id !== id)); const next = transactions.find(t => t.id !== id); if (next) setSelectedId(next.id); showToast(`Skipped · t${id.slice(1)} held for re-review`); }; const runCommand = (cmd) => { setPaletteOpen(false); switch (cmd.id) { case "approve": if (selected) approve(selected.id, selected.suggestedAccount); break; case "skip": if (selected) skip(selected.id); break; case "approve-all-high": const ids = transactions.filter(t => (t.score ?? 0) >= 0.85).map(t => t.id); setTransactions(prev => prev.filter(t => !ids.includes(t.id))); showToast(`Approved ${ids.length} transactions at ≥ 0.85`); break; case "import-csv": setCsvOpen(true); break; case "poll-toss": showToast("Polling Toss · ETA 4 seconds"); break; case "promote-rules": setScreen("rules"); break; case "commit-ledger": showToast("Committed · e4a82c1 on main · pushed"); break; case "switch-personal": setEntity("personal"); break; case "switch-9tfox": setEntity("tfox"); break; case "switch-finacode": setEntity("finacode"); break; case "go-dashboard": setScreen("dashboard"); break; case "go-review": setScreen("review"); break; case "go-rules": setScreen("rules"); break; case "go-ledger": setScreen("ledger"); break; case "go-import": setScreen("import"); break; case "toggle-theme": setTheme(t => t === "light" ? "dark" : "light"); break; default: showToast(`${cmd.label}`); } }; const visibleTx = transactions.filter(t => entity === "all" || t.entity === entity); const selected = visibleTx.find(t => t.id === selectedId) || visibleTx[0] || null; React.useEffect(() => { if (visibleTx.length && !visibleTx.find(t => t.id === selectedId)) { setSelectedId(visibleTx[0].id); } }, [entity, transactions]); const counts = { review: transactions.filter(t => entity === "all" || t.entity === entity).length, rules: window.AKEFIN_DATA.ruleSuggestions.length, import: window.AKEFIN_DATA.importRuns.length, }; const statusLine = `${counts.review} staged · ledger clean · last import 4m ago`; const handleImport = ({ entity: e, file, password }) => { setCsvOpen(false); showToast(`Importing · decrypting with password · ${e} entity`); setTimeout(() => showToast(`Import complete · 247 rows · 35 staged for review`), 1800); }; return (
setPaletteOpen(true)} onImport={() => setCsvOpen(true)} />
{screen === "dashboard" && } {screen === "review" && (
setPaletteOpen(true)} />
)} {screen === "rules" && } {screen === "ledger" && } {screen === "import" && setCsvOpen(true)} />}
setPaletteOpen(false)} onRun={runCommand} /> setCsvOpen(false)} onSubmit={handleImport} /> {toast &&
{toast}
}
); } const root = ReactDOM.createRoot(document.getElementById("root")); root.render();