import { useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { api } from "@/lib/api"; import { useFinanceData } from "@/context/FinanceDataContext"; import { useMoney } from "@/hooks/useMoney"; import { formatMoney, toDateString } from "@/lib/format"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { ToolCard, QueryError, useToolAction } from "./shared"; export const CURRENCIES = ["USD", "INR", "EUR", "GBP", "CAD", "AUD", "JPY", "CHF", "SGD", "NZD", "AED", "HKD", "CNY", "KRW", "MXN", "BRL", "SEK", "NOK", "DKK", "ZAR"]; export const selectClass = "h-10 rounded-md border border-input bg-background px-3 text-sm"; export function useAccountCurrencies() { return useQuery({ queryKey: ["enhanced", "currencies"], queryFn: () => api.enhanced.get>("currencies") }); } export function CurrencyTools() { const { accounts, currency } = useFinanceData(); const query = useAccountCurrencies(); const action = useToolAction(); const [selection, setSelection] = useState>({}); return

Confirm the unit used by each account. This labels existing amounts without converting them. Confirmed currencies cannot be changed here.

{accounts.map(a =>
{a.nickname || a.name} {query.data?.[a.id] ? {query.data[a.id]} · confirmed : a.id.startsWith("plaid-") ? Refresh bank sync to confirm currency : <> }
)}
; } type Report = { currency: string | null; accountId: string | null; income: number; expenses: number; net: number; categories: Record }; export function CurrencyReport() { useMoney(); // Subscribe to the shared privacy preference. const today = new Date(); const [start, setStart] = useState(toDateString(new Date(today.getFullYear(), today.getMonth(), 1))); const [end, setEnd] = useState(toDateString(today)); const valid = !!start && !!end && start <= end; const report = useQuery({ queryKey: ["enhanced", "currency-report", start, end], enabled: valid, queryFn: () => api.enhanced.get(`reports?start=${start}&end=${end}`) }); return
{!valid &&

Choose a valid date range.

}

Currencies are reported separately. Splits count against their own categories; internal transfers are excluded. Unconfirmed amounts have no currency label.

{report.data?.map(g =>

{g.currency || `Unconfirmed account: ${g.accountId}`}

{!g.currency ?

Confirm account currencies in Settings → Data Sources to include these amounts in monetary totals.

: <>

In {formatMoney(g.income, g.currency)} · Out {formatMoney(g.expenses, g.currency)} · Net {formatMoney(g.net, g.currency)}

{Object.entries(g.categories).map(([c, amount]) =>

{c}{formatMoney(amount, g.currency)}

)} }
)} {!report.isPending && !report.error && report.data?.length === 0 &&

No dated transactions in this period.

}
; } type Result = { currency: string; expected: number; difference: number; transactionCount: number; hasUndatedTransactions: boolean; transactions: { id: string; date: string; description: string; amount: number }[] }; type Saved = { id: string; startDate: string; endDate: string; currency: string; difference: number; stale: boolean }; export function ReconciliationTool({ accountId }: { accountId: string }) { const { hidden } = useMoney(); const currency = useAccountCurrencies(); const [startDate, setStart] = useState(""); const [endDate, setEnd] = useState(""); const [opening, setOpening] = useState(""); const [closing, setClosing] = useState(""); const [preview, setPreview] = useState(null); const saved = useQuery({ queryKey: ["enhanced", "reconciliations", accountId], enabled: !!currency.data?.[accountId], queryFn: () => api.enhanced.get(`accounts/${encodeURIComponent(accountId)}/reconciliations`) }); const action = useToolAction(); const payload = { startDate, endDate, opening: Number(opening), closing: Number(closing) }; const valid = startDate && endDate && startDate <= endDate && opening !== "" && closing !== "" && Number.isFinite(Number(opening)) && Number.isFinite(Number(closing)); const edit = (fn: () => void) => { fn(); setPreview(null); }; return {!currency.data?.[accountId] ?

Confirm this account's currency in Settings → Data Sources first.

: <>

Compare the statement's opening and closing balances with the ledger. Both dates are inclusive. For credit cards and loans, enter positive balances owed. Saving a comparison does not change your account balance.

{preview &&

Expected {formatMoney(preview.expected, preview.currency)} · Difference {formatMoney(preview.difference, preview.currency)} · {preview.transactionCount} transactions

{preview.hasUndatedTransactions &&

Undated transactions exist and are excluded. Review their dates before relying on this comparison.

}
Review included transactions{preview.transactions.map(t =>

{t.date} · {t.description} · {formatMoney(t.amount, preview.currency)}

)}
} {saved.data?.map(s =>

{s.startDate}–{s.endDate}: difference {formatMoney(s.difference, s.currency)}{s.stale ? " · ledger changed; compare again" : ""}

)} }
; }