import React, { useState, useEffect } from "react"; import { View, ScrollView, Pressable, TextInput, StyleSheet, ActivityIndicator, } from "react-native"; import { useColorScheme } from "nativewind"; import { Text } from "@/components/ui/text"; import { Button } from "@/components/ui/button"; import { ArrowLeft, ArrowRight, Trash2, Send, Plus, Calendar, ChevronDown, CalendarSearch, FileText, } from "@/lib/icons"; import { ScreenWrapper } from "@/components/ScreenWrapper"; import { useSirouRouter } from "@sirou/react-native"; import { AppRoutes } from "@/lib/routes"; import { Stack, useLocalSearchParams } from "expo-router"; import { useRouter } from "expo-router"; import { ShadowWrapper } from "@/components/ShadowWrapper"; import { api } from "@/lib/api"; import { toast } from "@/lib/toast-store"; import { PickerModal, SelectOption } from "@/components/PickerModal"; import { CalendarGrid } from "@/components/CalendarGrid"; import { StandardHeader } from "@/components/StandardHeader"; type Item = { id: number; description: string; qty: string; price: string }; const S = StyleSheet.create({ input: { height: 44, paddingHorizontal: 12, fontSize: 12, fontWeight: "500", borderRadius: 6, borderWidth: 1, }, inputCenter: { height: 44, paddingHorizontal: 12, fontSize: 14, fontWeight: "500", borderRadius: 6, borderWidth: 1, textAlign: "center", }, }); function useInputColors() { const { colorScheme } = useColorScheme(); const isDark = colorScheme === "dark"; return { bg: isDark ? "rgba(30,30,30,0.8)" : "rgba(241,245,249,0.2)", border: isDark ? "rgba(255,255,255,0.08)" : "rgba(203,213,225,0.6)", text: isDark ? "#f1f5f9" : "#0f172a", placeholder: "rgba(100,116,139,0.45)", }; } function Field({ label, value, onChangeText, placeholder, numeric = false, center = false, flex, }: { label: string; value: string; onChangeText: (v: string) => void; placeholder: string; numeric?: boolean; center?: boolean; flex?: number; }) { const c = useInputColors(); return ( {label} ); } export default function EditInvoiceScreen() { const nav = useSirouRouter(); const router = useRouter(); const { id } = useLocalSearchParams(); const isEdit = !!id; const [loading, setLoading] = useState(isEdit); const [submitting, setSubmitting] = useState(false); // Form fields const [invoiceNumber, setInvoiceNumber] = useState(""); const [customerName, setCustomerName] = useState(""); const [customerEmail, setCustomerEmail] = useState(""); const [customerPhone, setCustomerPhone] = useState(""); const [currency, setCurrency] = useState("ETB"); const [type, setType] = useState("SALES"); const [notes, setNotes] = useState(""); const [taxAmount, setTaxAmount] = useState(""); const [discountAmount, setDiscountAmount] = useState(""); // Dates const [issueDate, setIssueDate] = useState(new Date()); const [dueDate, setDueDate] = useState(new Date()); // Items const [items, setItems] = useState([ { id: 1, description: "", qty: "", price: "" }, ]); // Modals const [currencyModal, setCurrencyModal] = useState(false); const [typeModal, setTypeModal] = useState(false); const [issueModal, setIssueModal] = useState(false); const [dueModal, setDueModal] = useState(false); // Fetch existing data for edit useEffect(() => { if (isEdit) { fetchInvoice(); } }, [id]); const fetchInvoice = async () => { try { setLoading(true); const data = await api.invoices.getById({ params: { id: id as string } }); // Robust fallbacks for scanned invoices const original = data.scannedData?.originalData || {}; setInvoiceNumber(data.invoiceNumber || original.invoiceNumber || ""); // Clean up common OCR artifacts let name = data.customerName || original.customerName || ""; name = name .replace(/^Customer Name:\s*/i, "") .replace(/^Bill To:\s*/i, ""); setCustomerName(name); setCustomerEmail(data.customerEmail || original.customerEmail || ""); setCustomerPhone(data.customerPhone || original.customerPhone || ""); setCurrency(data.currency || original.currency || "ETB"); setType(data.type || "SALES"); setNotes(data.notes || ""); const taxVal = typeof data.taxAmount === "object" ? data.taxAmount?.value : data.taxAmount || original.taxAmount || "0"; setTaxAmount(String(taxVal)); const discVal = typeof data.discountAmount === "object" ? data.discountAmount?.value : data.discountAmount || original.discountAmount || "0"; setDiscountAmount(String(discVal)); setIssueDate( new Date( data.createdAt || data.issueDate || original.issueDate || Date.now(), ), ); setDueDate(new Date(data.dueDate || original.dueDate || Date.now())); // Populate items with fallback to original scanned data const apiItems = data.items || []; const sourceItems = apiItems.length > 0 ? apiItems : original.items || []; if (sourceItems.length > 0) { setItems( sourceItems.map((item: any, idx: number) => ({ id: idx + 1, description: item.description || "", qty: String(item.quantity || "1"), price: String(item.unitPrice?.value || item.unitPrice || "0"), })), ); } else { setItems([{ id: 1, description: "", qty: "", price: "" }]); } } catch (error) { console.error("[EditInvoice] Error:", error); toast.error("Error", "Failed to load invoice details"); } finally { setLoading(false); } }; const addItem = () => { const newId = items.length > 0 ? Math.max(...items.map((i) => i.id)) + 1 : 1; setItems([...items, { id: newId, description: "", qty: "", price: "" }]); }; const removeItem = (id: number) => { if (items.length > 1) { setItems(items.filter((i) => i.id !== id)); } }; const updateItem = (id: number, field: keyof Item, value: string) => { setItems(items.map((i) => (i.id === id ? { ...i, [field]: value } : i))); }; const calculateSubtotal = () => { return items.reduce((acc, item) => { const qty = parseFloat(item.qty) || 0; const price = parseFloat(item.price) || 0; return acc + qty * price; }, 0); }; const calculateTotal = () => { const subtotal = calculateSubtotal(); const tax = parseFloat(taxAmount) || 0; const discount = parseFloat(discountAmount) || 0; return subtotal + tax - discount; }; const handleSubmit = async () => { // Validation if (!invoiceNumber || !customerName) { toast.error("Error", "Please fill required fields"); return; } setSubmitting(true); try { const payload = { invoiceNumber, customerName, customerEmail, customerPhone, amount: calculateTotal(), currency, type, issueDate: issueDate.toISOString(), dueDate: dueDate.toISOString(), notes, taxAmount: parseFloat(taxAmount) || 0, discountAmount: parseFloat(discountAmount) || 0, items: items.map((item) => ({ description: item.description, quantity: parseFloat(item.qty) || 0, unitPrice: parseFloat(item.price) || 0, total: (parseFloat(item.qty) || 0) * (parseFloat(item.price) || 0), })), status: "PENDING", }; if (isEdit) { await api.invoices.update({ params: { id: id as string }, body: payload, }); toast.success("Success", "Invoice updated successfully"); } else { await api.invoices.create({ body: payload }); toast.success("Success", "Invoice created successfully"); } nav.back(); } catch (error: any) { toast.error("Error", error.message || "Failed to save invoice"); } finally { setSubmitting(false); } }; if (loading) { return ( ); } const currencies = ["ETB", "USD", "EUR", "GBP"]; const invoiceTypes = ["SALES", "PURCHASE", "SERVICE"]; return ( {/* Invoice Details */} Invoice Details {/* Customer Details */} Customer Details {/* Dates */} Dates setIssueModal(true)} > Issue Date: {issueDate.toLocaleDateString()} setDueModal(true)} > Due Date: {dueDate.toLocaleDateString()} {/* Items */} Items {items.map((item) => ( updateItem(item.id, "description", v)} placeholder="Item description" /> updateItem(item.id, "qty", v)} placeholder="0" numeric center /> updateItem(item.id, "price", v)} placeholder="0.00" numeric center /> removeItem(item.id)} > ))} {/* Totals */} Totals Subtotal {currency} {calculateSubtotal().toFixed(2)} Total {currency} {calculateTotal().toFixed(2)} {/* Configuration */} Configuration Currency setCurrencyModal(true)} > {currency} Invoice Type setTypeModal(true)} > {type} {/* Bottom Action */} {/* Modals */} setCurrencyModal(false)} > {currencies.map((curr) => ( { setCurrency(v); setCurrencyModal(false); }} /> ))} setTypeModal(false)} > {invoiceTypes.map((t) => ( { setType(v); setTypeModal(false); }} /> ))} setIssueModal(false)} > { setIssueDate(new Date(dateStr)); setIssueModal(false); }} selectedDate={issueDate.toISOString().substring(0, 10)} /> setDueModal(false)} > { setDueDate(new Date(dateStr)); setDueModal(false); }} selectedDate={dueDate.toISOString().substring(0, 10)} /> ); }