import { useLocalSearchParams, router } from "expo-router"; import { useState } from "react"; import { Alert, ScrollView, StyleSheet, Text, View } from "react-native"; import { AppBackground } from "@/components/AppBackground"; import { ExpenseFormFields, type ExpenseFormState, } from "@/components/expenses/ExpenseFormFields"; import { ReceiptItemSelector } from "@/components/expenses/ReceiptItemSelector"; import { LoadingScreen } from "@/components/LoadingScreen"; import { PageHeader } from "@/components/PageHeader"; import { SwipeableRow } from "@/components/SwipeableRow"; import { TabPage } from "@/components/TabPage"; import { Button } from "@/components/ui/Button"; import { fonts, spacing } from "@/constants/theme"; import { useAppTheme } from "@/contexts/ThemeContext"; import { formatCurrency, formatDate } from "@/lib/format"; import { scanReceiptImage, type ReceiptScanResult } from "@/lib/receipt-scan"; import { api } from "@/lib/trpc"; type ReceiptSplitDraft = Pick< ReceiptScanResult, "items" | "subtotal" | "tax" | "total" >; export default function ExpenseDetailScreen() { const { id } = useLocalSearchParams<{ id: string }>(); const { colors } = useAppTheme(); const utils = api.useUtils(); const [scanning, setScanning] = useState(false); const [editing, setEditing] = useState(false); const [receiptSplit, setReceiptSplit] = useState( null, ); const [form, setForm] = useState({ description: "", amountText: "", date: new Date(), category: "", businessId: "", clientId: "", billable: false, reimbursable: false, taxDeductible: false, notes: "", }); const expenseQuery = api.expenses.getById.useQuery( { id: id ?? "" }, { enabled: Boolean(id) }, ); const businessesQuery = api.businesses.getAll.useQuery(); const clientsQuery = api.clients.getAll.useQuery(); const uploadReceipt = api.expenses.uploadReceipt.useMutation({ onSuccess: () => void expenseQuery.refetch(), }); const deleteReceipt = api.expenses.deleteReceipt.useMutation({ onSuccess: () => void expenseQuery.refetch(), }); const updateExpense = api.expenses.update.useMutation({ onSuccess: async () => { await utils.expenses.getAll.invalidate(); await expenseQuery.refetch(); setEditing(false); }, }); const suggest = api.expenses.suggestFromReceiptText.useMutation(); const expense = expenseQuery.data; const businesses = businessesQuery.data ?? []; const clients = clientsQuery.data ?? []; async function attachAndScan(fromCamera: boolean) { if (!id || !expense) return; setScanning(true); try { const result = await scanReceiptImage( fromCamera, { description: expense.description, amountText: String(expense.amount), date: new Date(expense.date), }, (input) => suggest.mutateAsync(input), ); if (!result) return; await uploadReceipt.mutateAsync({ expenseId: id, filename: result.image.filename, mimeType: result.image.mimeType, data: result.image.base64, }); setForm( expenseToForm(expense, { description: result.description, amountText: result.amountText, date: result.date, notes: result.ocrText, }), ); setReceiptSplit( result.items.length > 0 ? { items: result.items, subtotal: result.subtotal, tax: result.tax, total: result.total, } : null, ); setEditing(true); Alert.alert( "Receipt attached", result.items.length > 0 ? "Select the owed items, apply the split amount, then save the expense." : "OCR filled the fields below. Save to update this expense.", ); } finally { setScanning(false); } } function handleSaveEdits() { if (!id) return; const amount = Number(form.amountText); if (!form.description.trim() || !Number.isFinite(amount) || amount <= 0) { Alert.alert("Invalid fields", "Description and amount are required."); return; } updateExpense.mutate({ id, description: form.description.trim(), amount, date: form.date, category: form.category || undefined, businessId: form.businessId || undefined, clientId: form.clientId || undefined, billable: form.billable, reimbursable: form.reimbursable, taxDeductible: form.taxDeductible, notes: form.notes.trim() || undefined, }); } function startEditing() { if (!expense) return; setForm(expenseToForm(expense)); setReceiptSplit(null); setEditing(true); } if (expenseQuery.isLoading) { return ; } if (!expense) { return ( Expense not found ); } return ( {editing ? ( <> {receiptSplit ? ( { setForm((current) => ({ ...current, amountText: selection.owedTotal.toFixed(2), notes: mergeNotes(selection.notes, current.notes), })); }} /> ) : null}