import React, { useState, useEffect } from "react"; import { View, ScrollView, ActivityIndicator, Alert, Linking, useColorScheme, Pressable, Platform, PermissionsAndroid, } from "react-native"; import { useSirouRouter } from "@sirou/react-native"; import { AppRoutes } from "@/lib/routes"; import { Stack, useLocalSearchParams } from "expo-router"; import { Text } from "@/components/ui/text"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { FileText, Calendar, Share2, Download, Trash2, Package, Clock, ExternalLink, ChevronRight, User, CreditCard, Hash, AlertCircle, MessageSquare, } from "@/lib/icons"; import { ScreenWrapper } from "@/components/ScreenWrapper"; import { ShadowWrapper } from "@/components/ShadowWrapper"; import { StandardHeader } from "@/components/StandardHeader"; import { api, BASE_URL } from "@/lib/api"; import { toast } from "@/lib/toast-store"; import { useAuthStore } from "@/lib/auth-store"; // Android only SMS module let SmsAndroid: any = null; if (Platform.OS === "android") { try { const smsModule = require("react-native-get-sms-android"); SmsAndroid = smsModule.default || smsModule; } catch (e) { console.log("[InvoiceDetail] SMS module unavailable"); } } export default function InvoiceDetailScreen() { const nav = useSirouRouter(); const { id } = useLocalSearchParams(); const colorScheme = useColorScheme(); const isDark = colorScheme === "dark"; const [loading, setLoading] = useState(true); const [invoice, setInvoice] = useState(null); const [scanningSms, setScanningSms] = useState(false); useEffect(() => { fetchInvoice(); }, [id]); const fetchInvoice = async () => { try { setLoading(true); const invoiceId = Array.isArray(id) ? id[0] : id; if (!invoiceId) throw new Error("No ID provided"); const data = await api.invoices.getById({ params: { id: invoiceId } }); setInvoice(data); } catch (error: any) { console.error("[InvoiceDetail] Error:", error); toast.error("Error", "Failed to load invoice details"); } finally { setLoading(false); } }; const handleScanSms = async () => { if (Platform.OS !== "android") { toast.error("Not Supported", "SMS scanning is only available on Android."); return; } setScanningSms(true); try { const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.READ_SMS ); if (granted !== PermissionsAndroid.RESULTS.GRANTED) { toast.error("Permission Denied", "We need SMS access to verify payments."); setScanningSms(false); return; } toast.info("Scanning SMS", "Searching for bank messages from the last 30 minutes..."); // Simulate logic if native module is missing (Expo Go) if (!SmsAndroid) { setTimeout(() => { toast.error("No Match", "No matching banking SMS found in the last 30 minutes."); setScanningSms(false); }, 2000); return; } const thirtyMinsAgo = Date.now() - 30 * 60 * 1000; const filter = { box: "inbox", minDate: thirtyMinsAgo, maxCount: 20, }; SmsAndroid.list( JSON.stringify(filter), (fail: string) => { toast.error("Scan Failed", fail); setScanningSms(false); }, (count: number, smsList: string) => { const messages = JSON.parse(smsList); const amountStr = amountValue.toString(); const custName = (invoice.customerName || "").toUpperCase(); // Search for amount or customer name in SMS body const match = messages.find((m: any) => { const body = m.body.toUpperCase(); return body.includes(amountStr) || (custName && body.includes(custName)); }); if (match) { Alert.alert( "Payment Found!", `We found a matching SMS proof for ${amountValue} ${invoice.currency}. Would you like to attach this to the invoice?`, [ { text: "No", style: "cancel" }, { text: "Attach SMS", onPress: () => toast.success("Attached", "SMS proof linked to invoice successfully.") } ] ); } else { toast.error("No Match", "Could not find any matching banking SMS in the last 30 minutes."); } setScanningSms(false); } ); } catch (err) { toast.error("Error", "Something went wrong during SMS scan."); setScanningSms(false); } }; const handleGetPdf = async () => { try { const { token } = useAuthStore.getState(); const pdfUrl = `${BASE_URL}invoices/${id}/pdf?token=${token}`; await Linking.openURL(pdfUrl); } catch (error) { console.error("[InvoiceDetail] PDF Error:", error); toast.error("Error", "Failed to open PDF"); } }; const handleDelete = async () => { Alert.alert( "Delete Invoice", "Are you sure you want to delete this invoice? This action cannot be undone.", [ { text: "Cancel", style: "cancel" }, { text: "Delete", style: "destructive", onPress: async () => { try { setLoading(true); const invoiceId = Array.isArray(id) ? id[0] : id; await api.invoices.delete({ params: { id: invoiceId as string }, }); toast.success("Success", "Invoice deleted successfully"); nav.back(); } catch (error) { console.error("[InvoiceDetail] Delete Error:", error); toast.error("Error", "Failed to delete invoice"); setLoading(false); } }, }, ], ); }; if (loading) { return ( ); } if (!invoice) { return ( Invoice Not Found The requested document could not be retrieved. ); } // Robust data extraction const originalData = invoice.scannedData?.originalData || {}; const items = (invoice.items?.length > 0 ? invoice.items : originalData.items) || []; const taxAmountValue = Number( typeof invoice.taxAmount === "object" ? invoice.taxAmount?.value : invoice.taxAmount || originalData.taxAmount || 0, ); const discountValue = Number( typeof invoice.discountAmount === "object" ? invoice.discountAmount?.value : invoice.discountAmount || originalData.discountAmount || 0, ); let amountValue = Number( typeof invoice.amount === "object" ? invoice.amount.value : invoice.amount, ); if (items.length > 0) { const itemsTotal = items.reduce( (acc: number, item: any) => acc + (Number(item.total?.value || item.total) || 0), 0, ); if (itemsTotal > 0 && (amountValue === taxAmountValue || amountValue < itemsTotal)) { amountValue = itemsTotal + taxAmountValue - discountValue; } } const subtotalValue = amountValue - taxAmountValue + discountValue; const statusColors = { PAID: { bg: "bg-emerald-500/10", text: "text-emerald-500", dot: "bg-emerald-500" }, PENDING: { bg: "bg-amber-500/10", text: "text-amber-500", dot: "bg-amber-500" }, DRAFT: { bg: "bg-blue-500/10", text: "text-blue-500", dot: "bg-blue-500" }, DEFAULT: { bg: "bg-slate-500/10", text: "text-slate-500", dot: "bg-slate-500" }, }; const status = (invoice.status || "PENDING").toUpperCase(); const colors = statusColors[status as keyof typeof statusColors] || statusColors.DEFAULT; return ( nav.go("invoices/edit", { id: invoice.id })} /> {status} Total Amount {Number(amountValue).toLocaleString(undefined, { minimumFractionDigits: 2 })} {invoice.currency || "ETB"} Date {new Date(invoice.issueDate || invoice.createdAt).toLocaleDateString()} Due {new Date(invoice.dueDate).toLocaleDateString()} Client {invoice.customerName?.replace("Customer Name: ", "") || "Walking Client"} Items {items.map((item: any, idx: number) => ( {item.description} {Number(item.total?.value || item.total || 0).toLocaleString()} {item.quantity} x {Number(item.unitPrice?.value || item.unitPrice || 0).toLocaleString()} {invoice.currency} ))} Subtotal {subtotalValue.toLocaleString()} {invoice.currency} Grand Total {amountValue.toLocaleString()} {invoice.currency} ); }