diff --git a/frontend/src/pages/transactions/TransactionFormModal.tsx b/frontend/src/pages/transactions/TransactionFormModal.tsx index 14b6700..7382318 100644 --- a/frontend/src/pages/transactions/TransactionFormModal.tsx +++ b/frontend/src/pages/transactions/TransactionFormModal.tsx @@ -2,7 +2,7 @@ import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { format } from "date-fns"; -import { X, Loader2, Sparkles } from "lucide-react"; +import { X, Loader2, Sparkles, RotateCcw } from "lucide-react"; import type { Account } from "@/api/accounts"; const schema = z.object({ @@ -40,9 +40,11 @@ interface Props { initialValues?: TransactionInitialValues; parsedFromReceipt?: boolean; showAiDebug?: boolean; + onRescan?: () => void; + rescanLoading?: boolean; } -export default function TransactionFormModal({ accounts, categories, onClose, onSubmit, isLoading, initialValues, parsedFromReceipt, showAiDebug }: Props) { +export default function TransactionFormModal({ accounts, categories, onClose, onSubmit, isLoading, initialValues, parsedFromReceipt, showAiDebug, onRescan, rescanLoading }: Props) { const { register, handleSubmit, watch, formState: { errors } } = useForm
({ resolver: zodResolver(schema), defaultValues: { @@ -89,14 +91,40 @@ export default function TransactionFormModal({ accounts, categories, onClose, on {parsedFromReceipt && !showAiDebug && (
- Fields pre-filled from receipt — review before saving + Fields pre-filled from receipt — review before saving + {onRescan && ( + + )}
)} {parsedFromReceipt && showAiDebug && (
-

- AI scan result — review before saving -

+
+

+ AI scan result — review before saving +

+ {onRescan && ( + + )} +
Merchant{initialValues?.merchant ?? not detected} Amount{initialValues?.amount != null ? initialValues.amount : not detected} diff --git a/frontend/src/pages/transactions/TransactionList.tsx b/frontend/src/pages/transactions/TransactionList.tsx index f2d044b..3be9dce 100644 --- a/frontend/src/pages/transactions/TransactionList.tsx +++ b/frontend/src/pages/transactions/TransactionList.tsx @@ -55,6 +55,8 @@ export default function TransactionList() { const receiptFileRef = useRef(null); const [scanError, setScanError] = useState(null); const [scanning, setScanning] = useState(false); + const [rescanLoading, setRescanLoading] = useState(false); + const [rescanKey, setRescanKey] = useState(0); const receiptInputRef = useRef(null); const [search, setSearch] = useState(""); const [filterType, setFilterType] = useState(""); @@ -113,6 +115,27 @@ export default function TransactionList() { } } + async function handleRescan() { + if (!receiptFileRef.current) return; + setRescanLoading(true); + setScanError(null); + try { + const parsed = await parseReceiptFile(receiptFileRef.current); + setReceiptParsed(parsed); + setRescanKey((k) => k + 1); + } catch (e: any) { + const detail = e?.response?.data?.detail; + const status = e?.response?.status; + if (detail) { + setScanError(typeof detail === "string" ? detail : `HTTP ${status}: ${JSON.stringify(detail)}`); + } else { + setScanError(`Rescan failed — ${e?.message ?? "unknown error"}`); + } + } finally { + setRescanLoading(false); + } + } + async function handleReceiptFile(file: File) { setScanning(true); setScanError(null); @@ -368,6 +391,7 @@ export default function TransactionList() { {showForm && ( { setShowForm(false); setReceiptParsed(null); receiptFileRef.current = null; }} @@ -384,6 +408,8 @@ export default function TransactionList() { } : undefined} parsedFromReceipt={!!receiptParsed} showAiDebug={aiSettings?.debug ?? false} + onRescan={handleRescan} + rescanLoading={rescanLoading} /> )}