import React, { useState, useEffect, useCallback } from "react"; import { View, ScrollView, Pressable, ActivityIndicator, FlatList, ListRenderItem, } from "react-native"; import { useSirouRouter } from "@sirou/react-native"; 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 { ScanLine, CheckCircle2, Wallet, ChevronRight, AlertTriangle, Plus, } from "@/lib/icons"; import { ScreenWrapper } from "@/components/ScreenWrapper"; import { StandardHeader } from "@/components/StandardHeader"; import { toast } from "@/lib/toast-store"; import { useAuthStore } from "@/lib/auth-store"; import { EmptyState } from "@/components/EmptyState"; import { PERMISSION_MAP, hasPermission } from "@/lib/permissions"; const PRIMARY = "#ea580c"; interface Payment { id: string; transactionId: string; amount: | { value: number; currency: string; } | number; currency: string; paymentDate: string; paymentMethod: string; notes: string; isFlagged: boolean; flagReason: string; flagNotes: string; receiptPath: string; userId: string; invoiceId: string; createdAt: string; updatedAt: string; } export default function PaymentsScreen() { const nav = useSirouRouter(); const permissions = useAuthStore((s) => s.permissions); const [payments, setPayments] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(true); const [loadingMore, setLoadingMore] = useState(false); // Check permissions const canCreatePayments = hasPermission(permissions, PERMISSION_MAP["payments:create"]); const fetchPayments = useCallback( async (pageNum: number, isRefresh = false) => { const { isAuthenticated } = useAuthStore.getState(); if (!isAuthenticated) return; try { if (!isRefresh) { pageNum === 1 ? setLoading(true) : setLoadingMore(true); } const response = await api.payments.getAll({ query: { page: pageNum, limit: 10 }, }); const newPayments = response.data; if (isRefresh) { setPayments(newPayments); } else { 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); setRefreshing(false); setLoadingMore(false); } }, [], ); useEffect(() => { fetchPayments(1); }, [fetchPayments]); const onRefresh = () => { setRefreshing(true); fetchPayments(1, true); }; const loadMore = () => { if (hasMore && !loadingMore && !loading) { fetchPayments(page + 1); } }; const categorized = { flagged: payments.filter((p) => p.isFlagged), pending: payments.filter((p) => !p.invoiceId && !p.isFlagged), reconciled: payments.filter((p) => p.invoiceId && !p.isFlagged), }; const renderPaymentItem = ( pay: Payment, type: "reconciled" | "pending" | "flagged", ) => { const isReconciled = type === "reconciled"; const isFlagged = type === "flagged"; // Support both object and direct number amount from API const amountValue = typeof pay.amount === "object" ? pay.amount.value : pay.amount; const dateStr = new Date(pay.paymentDate).toLocaleDateString(); return ( nav.go("payments/[id]", { id: pay.id })} className="mb-2" > {isFlagged ? ( ) : isReconciled ? ( ) : ( )} {pay.currency || "$"} {amountValue?.toLocaleString()} {pay.paymentMethod} ยท {dateStr} {isFlagged ? ( Flagged ) : !isReconciled ? ( Match ) : ( )} ); }; if (loading && page === 1) { return ( ); } return ( { const isCloseToBottom = nativeEvent.layoutMeasurement.height + nativeEvent.contentOffset.y >= nativeEvent.contentSize.height - 20; if (isCloseToBottom) loadMore(); }} scrollEventThrottle={400} > {/* Flagged Section */} {categorized.flagged.length > 0 && ( <> Flagged Payments {categorized.flagged.map((p) => renderPaymentItem(p, "flagged"))} )} {/* Pending Section */} Pending Match {categorized.pending.length > 0 ? ( categorized.pending.map((p) => renderPaymentItem(p, "pending")) ) : ( )} {/* Reconciled Section */} Reconciled {categorized.reconciled.length > 0 ? ( categorized.reconciled.map((p) => renderPaymentItem(p, "reconciled"), ) ) : ( )} {loadingMore && ( )} ); }