import React, { useState, useCallback } from "react"; import { CommandPalette } from "@/components/CommandPalette"; import { View, ScrollView, Pressable, TextInput, ActivityIndicator, Image, } from "react-native"; import { useSirouRouter } from "@sirou/react-native"; import { useFocusEffect } from "expo-router"; import { AppRoutes } from "@/lib/routes"; import { Text } from "@/components/ui/text"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; import { api } from "@/lib/api"; import { Wallet, ChevronRight, AlertTriangle, Plus, Search, Banknote, FileText, Clock, } from "@/lib/icons"; import { ScreenWrapper } from "@/components/ScreenWrapper"; import { StandardHeader } from "@/components/StandardHeader"; import { EmptyState } from "@/components/EmptyState"; import { toast } from "@/lib/toast-store"; import { useAuthStore } from "@/lib/auth-store"; import { useColorScheme } from "nativewind"; import { getPlaceholderColor } from "@/lib/colors"; import { getProviderLogo, isCash } from "@/lib/payment-providers"; type Tab = "payment" | "request"; interface Payment { id: string; transactionId: string; amount: number; currency: string; paymentDate: string; paymentMethod: string; isFlagged: boolean; senderName?: string; receiverName?: string; userId: string; invoiceId?: string; createdAt: string; updatedAt: string; } interface PaymentRequest { id: string; paymentRequestNumber: string; customerName: string; amount: number; currency: string; issueDate: string; dueDate: string; status: string; openedCount?: number; copiedAccountCount?: number; createdAt: string; } const STATUS_COLORS: Record = { DRAFT: "#6b7280", SENT: "#E46212", OPENED: "#2563eb", PAID: "#16a34a", EXPIRED: "#dc2626", CANCELLED: "#6b7280", }; const STATUS_BG: Record = { DRAFT: "#6b728015", SENT: "#E4621215", OPENED: "#2563eb15", PAID: "#16a34a15", EXPIRED: "#dc262615", CANCELLED: "#dc262615", }; function formatAmountCurrency(amount: any, currency = "ETB") { const val = typeof amount === "object" ? (amount as any)?.value : amount; return `${currency} ${(Number(val) || 0).toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`; } export default function PaymentsScreen() { const nav = useSirouRouter(); const { colorScheme } = useColorScheme(); const isDark = colorScheme === "dark"; const [tab, setTab] = useState("payment"); // Payment state const [payments, setPayments] = useState([]); const [loading, setLoading] = useState(true); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [loadingMore, setLoadingMore] = useState(false); const [search, setSearch] = useState(""); const [searchOpen, setSearchOpen] = useState(false); // Request state const [requests, setRequests] = useState([]); const [requestsLoading, setRequestsLoading] = useState(false); const [reqPage, setReqPage] = useState(1); const [reqHasMore, setReqHasMore] = useState(true); const [reqLoadingMore, setReqLoadingMore] = useState(false); const fetchPayments = useCallback(async (pageNum: number) => { const { isAuthenticated } = useAuthStore.getState(); if (!isAuthenticated) return; try { pageNum === 1 ? setLoading(true) : setLoadingMore(true); const response = await api.payments.getAll({ query: { page: pageNum, limit: 10 }, }); const newPayments = response.data; setPayments((prev) => pageNum === 1 ? newPayments : [...prev, ...newPayments], ); setHasMore(response.meta.hasNextPage); setPage(pageNum); } catch (err: any) { console.error("[Payments] Fetch error:", err); toast.error("Error", "Failed to fetch payments."); } finally { setLoading(false); setLoadingMore(false); } }, []); const fetchRequests = useCallback(async (pageNum: number) => { const { isAuthenticated } = useAuthStore.getState(); if (!isAuthenticated) return; try { pageNum === 1 ? setRequestsLoading(true) : setReqLoadingMore(true); const response = await api.paymentRequests.getAll({ query: { page: pageNum, limit: 10 }, }); const newRequests = response.data; setRequests((prev) => pageNum === 1 ? newRequests : [...prev, ...newRequests], ); setReqHasMore(response.meta.hasNextPage); setReqPage(pageNum); } catch (err: any) { console.error("[PaymentRequests] Fetch error:", err); toast.error("Error", "Failed to fetch payment requests."); } finally { setRequestsLoading(false); setReqLoadingMore(false); } }, []); useFocusEffect( useCallback(() => { if (tab === "payment") fetchPayments(1); else fetchRequests(1); }, [tab, fetchPayments, fetchRequests]), ); const loadMore = () => { if (tab === "payment" && hasMore && !loadingMore && !loading) { fetchPayments(page + 1); } if ( tab === "request" && reqHasMore && !reqLoadingMore && !requestsLoading ) { fetchRequests(reqPage + 1); } }; const formatAmount = (pay: Payment) => { const val = typeof pay.amount === "object" ? (pay.amount as any).value : pay.amount; return `${pay.currency || "ETB"} ${(val || 0).toLocaleString()}`; }; const filteredPayments = useCallback(() => { if (!search.trim()) return payments; const q = search.toLowerCase(); const searchNum = parseFloat(q); return payments.filter((p) => { if (p.senderName?.toLowerCase().includes(q)) return true; if (p.transactionId?.toLowerCase().includes(q)) return true; if (p.paymentMethod?.toLowerCase().includes(q)) return true; const pAmount = typeof p.amount === "object" ? parseFloat(p.amount.value) : parseFloat(p.amount); if (!isNaN(searchNum) && !isNaN(pAmount)) { if (pAmount === searchNum) return true; if (String(pAmount).includes(q)) return true; } return false; }); }, [payments, search]); const filteredRequests = useCallback(() => { if (!search.trim()) return requests; const q = search.toLowerCase(); const searchNum = parseFloat(q); return requests.filter((r) => { if (r.customerName?.toLowerCase().includes(q)) return true; if (r.paymentRequestNumber?.toLowerCase().includes(q)) return true; if (r.status?.toLowerCase().includes(q)) return true; const rAmount = typeof r.amount === "object" ? parseFloat(r.amount.value) : parseFloat(r.amount); if (!isNaN(searchNum) && !isNaN(rAmount)) { if (rAmount === searchNum) return true; if (String(rAmount).includes(q)) return true; } return false; }); }, [requests, search]); const renderPaymentItem = (pay: Payment) => { const dateStr = new Date(pay.paymentDate).toLocaleDateString(); const logo = getProviderLogo(pay.paymentMethod); const cash = isCash(pay.paymentMethod); const hasFlag = pay.isFlagged; return ( nav.go("payments/[id]", { id: pay.id })} > {logo ? ( ) : ( {hasFlag ? ( ) : cash ? ( ) : ( )} )} {formatAmount(pay)} {hasFlag && ( Flagged )} {pay.paymentMethod} · {pay.senderName} · {dateStr} {!hasFlag && ( )} ); }; const renderRequestItem = (req: PaymentRequest) => { const statusColor = STATUS_COLORS[req.status] || "#6b7280"; const statusBg = STATUS_BG[req.status] || "#6b728015"; return ( nav.go("payment-requests/[id]", { id: req.id })} > {formatAmountCurrency(req.amount, req.currency)} {req.status} {req.customerName} · #{req.paymentRequestNumber} ); }; const isLoading = tab === "payment" ? loading && page === 1 : requestsLoading && reqPage === 1; if (isLoading) { return ( ); } const items = tab === "payment" ? filteredPayments() : filteredRequests(); return ( { const isCloseToBottom = nativeEvent.layoutMeasurement.height + nativeEvent.contentOffset.y >= nativeEvent.contentSize.height - 20; if (isCloseToBottom) loadMore(); }} scrollEventThrottle={400} > setSearchOpen(true)} /> {/* Create button */} {/* Tabs */} { if (tab !== "payment") { setTab("payment"); setSearch(""); } }} className={`flex-1 py-2 rounded-[8px] items-center ${ tab === "payment" ? "bg-primary" : "" }`} > Payment { if (tab !== "request") { setTab("request"); setSearch(""); } }} className={`flex-1 py-2 rounded-[8px] items-center ${ tab === "request" ? "bg-primary" : "" }`} > Request {items.length > 0 ? ( items.map((item) => tab === "payment" ? renderPaymentItem(item as Payment) : renderRequestItem(item as PaymentRequest), ) ) : ( )} {(loadingMore || reqLoadingMore) && ( )} setSearchOpen(false)} /> ); }