Yaltopia-Tickets-App/app/payments/[id].tsx
2026-06-05 13:39:37 +03:00

988 lines
34 KiB
TypeScript

import React, { useState, useCallback } from "react";
import {
View,
ScrollView,
TextInput,
ActivityIndicator,
Pressable,
Linking,
Modal,
StyleSheet,
Alert,
Platform,
PermissionsAndroid,
} from "react-native";
import { useSirouRouter } from "@sirou/react-native";
import { AppRoutes } from "@/lib/routes";
import { Stack, useLocalSearchParams, useFocusEffect } from "expo-router";
import { Text } from "@/components/ui/text";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { EmptyState } from "@/components/EmptyState";
import { FormFlow } from "@/components/FormFlow";
import {
Wallet,
Link2,
Clock,
AlertTriangle,
User,
Building2,
Hash,
Eye,
Trash2,
Info,
Search,
ChevronDown,
X,
Scan,
} from "@/lib/icons";
import { ScreenWrapper } from "@/components/ScreenWrapper";
import { StandardHeader } from "@/components/StandardHeader";
import { api, BASE_URL } from "@/lib/api";
import { useColorScheme } from "nativewind";
import { toast } from "@/lib/toast-store";
import { ActionModal } from "@/components/ActionModal";
import { PickerModal, SelectOption } from "@/components/PickerModal";
import { CalendarGrid } from "@/components/CalendarGrid";
import { getPlaceholderColor } from "@/lib/colors";
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("[PaymentDetail] SMS module unavailable");
}
}
const S = StyleSheet.create({
input: {
paddingHorizontal: 12,
paddingVertical: 12,
fontSize: 12,
fontWeight: "500",
borderRadius: 6,
borderWidth: 1,
textAlignVertical: "center",
},
});
const CURRENCIES = ["ETB", "USD", "EUR", "GBP", "KES", "ZAR"];
const PAYMENT_METHODS = [
"Telebirr",
"CBE",
"Dashen",
"DECSI",
"Bank Transfer",
"Cash",
"Other",
];
export default function PaymentDetailScreen() {
const nav = useSirouRouter<AppRoutes>();
const { id } = useLocalSearchParams();
const { colorScheme } = useColorScheme();
const isDark = colorScheme === "dark";
const [payment, setPayment] = useState<any>(null);
const [loading, setLoading] = useState(true);
const [deleting, setDeleting] = useState(false);
const [matching, setMatching] = useState(false);
const [scanningSms, setScanningSms] = useState(false);
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [showEditModal, setShowEditModal] = useState(false);
const [showInvoicePicker, setShowInvoicePicker] = useState(false);
const [editSaving, setEditSaving] = useState(false);
// Edit form state
const [editTxnId, setEditTxnId] = useState("");
const [editAmount, setEditAmount] = useState("");
const [editCurrency, setEditCurrency] = useState("ETB");
const [editPaymentMethod, setEditPaymentMethod] = useState("Telebirr");
const [editPaymentDate, setEditPaymentDate] = useState("");
const [editNotes, setEditNotes] = useState("");
const [editStep, setEditStep] = useState(0);
const [showEditCurrency, setShowEditCurrency] = useState(false);
const [showEditMethod, setShowEditMethod] = useState(false);
const [showEditDate, setShowEditDate] = useState(false);
// Invoice list for matching
const [invoices, setInvoices] = useState<any[]>([]);
const [invoiceSearch, setInvoiceSearch] = useState("");
const paymentId = Array.isArray(id) ? id[0] : id;
function useInputColors() {
const dark = isDark;
return {
bg: dark ? "rgba(30,30,30,0.8)" : "rgba(241,245,249,0.2)",
border: dark ? "rgba(255,255,255,0.08)" : "rgba(203,213,225,0.6)",
text: dark ? "#f1f5f9" : "#0f172a",
placeholder: "rgba(100,116,139,0.45)",
};
}
const c = useInputColors();
const fetchPayment = useCallback(async () => {
try {
setLoading(true);
if (!paymentId) throw new Error("No ID provided");
const response = await api.payments.getById({
params: { id: paymentId },
});
setPayment(response);
} catch (error) {
console.error("[PaymentDetail] Error fetching payment:", error);
toast.error("Error", "Failed to fetch payment details.");
} finally {
setLoading(false);
}
}, [paymentId]);
useFocusEffect(
useCallback(() => {
fetchPayment();
}, [fetchPayment]),
);
const handleDelete = () => setShowDeleteModal(true);
const confirmDelete = async () => {
setDeleting(true);
try {
if (!paymentId) return;
await api.payments.delete({ params: { id: paymentId } });
toast.success("Deleted", "Payment record has been removed.");
setShowDeleteModal(false);
nav.back();
} catch (err: any) {
toast.error("Error", err.message || "Failed to delete payment.");
setShowDeleteModal(false);
} finally {
setDeleting(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",
"SMS access is required to scan payment messages.",
);
setScanningSms(false);
return;
}
if (!SmsAndroid) {
toast.error("Not Available", "SMS scanning module is not available.");
setScanningSms(false);
return;
}
const fiveMinsAgo = Date.now() - 5 * 60 * 1000;
const filter = { box: "inbox", minDate: fiveMinsAgo, maxCount: 30 };
SmsAndroid.list(
JSON.stringify(filter),
(fail: string) => {
toast.error("Scan Failed", fail);
setScanningSms(false);
},
(_count: number, smsList: string) => {
const messages = JSON.parse(smsList);
const match = messages.find((m: any) => {
const addr = (m.address || "").toUpperCase();
const body = (m.body || "").toUpperCase();
return (
addr === "127" ||
addr === "CBE" ||
body.includes("TELEBIRR") ||
body.includes("CBE")
);
});
if (!match) {
toast.error(
"No Match",
"No CBE or Telebirr SMS found in the last 5 minutes.",
);
setScanningSms(false);
return;
}
const text = match.body;
const lines = [
`From: ${match.address}`,
`Date: ${new Date(match.date).toLocaleString()}`,
"",
`"${text}"`,
"",
"Send this SMS to verify the payment?",
];
Alert.alert("SMS Found", lines.filter(Boolean).join("\n"), [
{
text: "Cancel",
style: "cancel",
onPress: () => setScanningSms(false),
},
{
text: "Verify",
onPress: async () => {
try {
await api.payments.verifySms({
params: { id: paymentId },
body: { smsContent: text },
});
toast.success(
"Verified",
"SMS has been sent for verification.",
);
fetchPayment();
} catch (err: any) {
toast.error("Error", err.message || "Verification failed.");
} finally {
setScanningSms(false);
}
},
},
]);
},
);
} catch (err) {
toast.error("Error", "Something went wrong during SMS scan.");
setScanningSms(false);
}
};
const openEdit = () => {
const amt = Number(
typeof payment.amount === "object"
? payment.amount.value
: payment.amount,
);
setEditTxnId(payment.transactionId || "");
setEditAmount(String(amt));
setEditCurrency(payment.currency || "ETB");
setEditPaymentMethod(payment.paymentMethod || "Telebirr");
setEditPaymentDate(
payment.paymentDate
? new Date(payment.paymentDate).toISOString().split("T")[0]
: new Date().toISOString().split("T")[0],
);
setEditNotes(payment.notes || "");
setEditStep(0);
setShowEditModal(true);
};
const handleEditSave = async () => {
if (!editAmount || parseFloat(editAmount) <= 0) {
toast.error("Validation Error", "Amount must be greater than 0");
return;
}
setEditSaving(true);
try {
await api.payments.update({
params: { id: paymentId },
body: {
transactionId: editTxnId,
amount: parseFloat(editAmount),
currency: editCurrency,
paymentDate: new Date(editPaymentDate).toISOString(),
paymentMethod: editPaymentMethod,
notes: editNotes,
},
});
toast.success("Success", "Payment updated successfully.");
setShowEditModal(false);
fetchPayment();
} catch (err: any) {
const msg =
err?.response?.data?.message ||
err?.data?.message ||
err?.message ||
"Failed to update payment";
toast.error("Error", msg);
} finally {
setEditSaving(false);
}
};
const openMatchPicker = async () => {
if (!payment || matching || !paymentId) return;
setMatching(true);
try {
const response = await api.invoices.getAll();
const list = Array.isArray(response)
? response
: (response as any).data || [];
setInvoices(list);
setInvoiceSearch("");
setShowInvoicePicker(true);
} catch (err: any) {
toast.error("Error", "Failed to fetch invoices.");
} finally {
setMatching(false);
}
};
const handleInvoiceSelect = async (inv: any) => {
setShowInvoicePicker(false);
try {
await api.payments.associate({
params: { id: paymentId },
body: { invoiceId: inv.id },
});
toast.success(
"Success",
`Payment associated with invoice #${inv.invoiceNumber || inv.id}.`,
);
fetchPayment();
} catch (err: any) {
toast.error("Error", err.message || "Failed to associate.");
}
};
if (loading) {
return (
<ScreenWrapper className="bg-background">
<Stack.Screen options={{ headerShown: false }} />
<StandardHeader title="Payment Details" showBack />
<View className="flex-1 justify-center items-center">
<ActivityIndicator color="#ea580c" size="large" />
<Text
variant="muted"
className="mt-4 font-sans-bold text-[10px] uppercase tracking-widest"
>
Retrieving Transaction...
</Text>
</View>
</ScreenWrapper>
);
}
if (!payment) {
return (
<ScreenWrapper className="bg-background">
<Stack.Screen options={{ headerShown: false }} />
<StandardHeader title="Payment Details" showBack />
<View className="flex-1 justify-center items-center p-6">
<AlertTriangle color="#ef4444" size={48} strokeWidth={1.5} />
<Text variant="h4" className="mt-4 text-foreground font-sans-black">
Transaction Not Found
</Text>
<Text variant="muted" className="mt-2 text-center">
The requested payment record could not be retrieved.
</Text>
</View>
</ScreenWrapper>
);
}
const amountValue = Number(
typeof payment.amount === "object" ? payment.amount.value : payment.amount,
);
const paymentDate = payment.paymentDate
? new Date(payment.paymentDate)
: new Date(payment.createdAt);
const isFlagged = payment.isFlagged === true;
const filteredInvoices = invoices.filter((inv) => {
if (!invoiceSearch) return true;
const q = invoiceSearch.toLowerCase();
return (
(inv.invoiceNumber || "").toLowerCase().includes(q) ||
(inv.customerName || "").toLowerCase().includes(q)
);
});
return (
<ScreenWrapper className="bg-background">
<Stack.Screen options={{ headerShown: false }} />
<StandardHeader
title="Payment Details"
showBack
rightAction="edit"
onRightActionPress={openEdit}
/>
<ScrollView
className="flex-1"
contentContainerStyle={{ paddingBottom: 10, paddingHorizontal: 16 }}
showsVerticalScrollIndicator={false}
>
{/* Flagged Alert */}
{isFlagged && (
<View className="mt-5 mb-4 bg-red-500/10 border border-red-500/20 rounded-[6px] p-4 flex-row items-start">
<View className="bg-red-500/20 p-2 rounded-full mr-3">
<AlertTriangle color="#ef4444" size={18} />
</View>
<View className="flex-1">
<Text className="text-red-600 font-sans-black text-[10px] uppercase tracking-[2px] mb-1">
Security Flag ({payment.flagReason || "Audit Needed"})
</Text>
<Text className="text-foreground/80 font-sans-medium text-xs leading-5">
{payment.flagNotes || "System flagged this for manual review."}
</Text>
</View>
</View>
)}
{/* Hero Section */}
<View className="pt-6">
<Text className="text-[10px] font-sans-black uppercase tracking-[3px] text-muted-foreground mb-2">
Total Amount
</Text>
<View className="flex-row items-end gap-3 mb-6">
<Text className="text-4xl font-sans-black text-foreground tracking-tighter">
{amountValue.toLocaleString()}
</Text>
<Text className="text-2xl font-sans-black text-primary mb-1">
{payment.currency || "USD"}
</Text>
</View>
<View className="flex-row gap-3 mb-6">
<Card
className="rounded-[6px] overflow-hidden border border-border"
style={{ flex: 1 }}
>
<CardContent className="p-4">
<Building2 size={18} color="#ea580c" />
<Text className="text-[9px] uppercase font-sans-black tracking-widest text-muted-foreground mt-3 mb-1">
Sender
</Text>
<Text
className="text-foreground font-sans-black text-sm"
numberOfLines={1}
>
{payment.senderName || "Unknown"}
</Text>
</CardContent>
</Card>
<Card
className="rounded-[6px] overflow-hidden border border-border"
style={{ flex: 1 }}
>
<CardContent className="p-4">
<Wallet size={18} color="#ea580c" />
<Text className="text-[9px] uppercase font-sans-black tracking-widest text-muted-foreground mt-3 mb-1">
Method
</Text>
<Text
className="text-foreground font-sans-black text-sm"
numberOfLines={1}
>
{payment.paymentMethod || "Direct"}
</Text>
</CardContent>
</Card>
</View>
</View>
{/* Details Section */}
<Card className="rounded-[6px] overflow-hidden mb-6 border border-border">
<CardContent className="p-4">
<Text className="font-sans-bold text-xs uppercase tracking-widest text-muted-foreground mb-4">
Transaction Details
</Text>
<View className="gap-4">
<View className="flex-row items-center gap-3">
<Hash size={15} color="#64748b" />
<View className="flex-1">
<Text className="text-[10px] uppercase tracking-wider text-muted-foreground font-sans-semibold">
Transaction ID
</Text>
<Text className="text-foreground font-sans-bold text-sm">
{payment.transactionId || "—"}
</Text>
</View>
</View>
<View className="flex-row items-center gap-3">
<User size={15} color="#64748b" />
<View className="flex-1">
<Text className="text-[10px] uppercase tracking-wider text-muted-foreground font-sans-semibold">
Receiver
</Text>
<Text className="text-foreground font-sans-bold text-sm">
{payment.receiverName || "—"}
</Text>
</View>
</View>
<View className="flex-row items-center gap-3">
<Clock size={15} color="#64748b" />
<View className="flex-1">
<Text className="text-[10px] uppercase tracking-wider text-muted-foreground font-sans-semibold">
Payment Date
</Text>
<Text className="text-foreground font-sans-bold text-sm">
{paymentDate.toLocaleString()}
</Text>
</View>
</View>
{payment.invoiceId && (
<Pressable
onPress={() =>
nav.go("invoices/[id]", { id: payment.invoiceId })
}
className="flex-row items-center gap-3 active:opacity-60"
>
<Link2 size={15} color="#64748b" />
<View className="flex-1">
<Text className="text-[10px] uppercase tracking-wider text-muted-foreground font-sans-semibold">
Linked Invoice
</Text>
<Text className="text-foreground font-sans-bold text-sm underline">
{payment.invoiceId}
</Text>
</View>
</Pressable>
)}
<View className="flex-row items-center gap-3">
<Info size={15} color="#64748b" />
<View className="flex-1">
<Text className="text-[10px] uppercase tracking-wider text-muted-foreground font-sans-semibold">
Created
</Text>
<Text className="text-foreground font-sans-bold text-sm">
{new Date(payment.createdAt).toLocaleString()}
</Text>
</View>
</View>
</View>
</CardContent>
</Card>
{/* Notes */}
{payment.notes && (
<View className="mb-6">
<Text className="font-sans-bold text-xs uppercase tracking-widest text-muted-foreground mb-3">
Notes
</Text>
<Card className="rounded-[6px] overflow-hidden border border-border">
<CardContent className="p-4">
<Text className="text-foreground font-sans-medium italic opacity-70 leading-6">
"{payment.notes}"
</Text>
</CardContent>
</Card>
</View>
)}
{/* Actions */}
<View className="gap-3 mb-10">
<View className="flex-row gap-3">
{payment.receiptPath && (
<Button
className="flex-1 h-10 rounded-[6px] border border-border bg-card"
onPress={() =>
Linking.openURL(
`${BASE_URL}${payment.receiptPath.replace(/^\//, "")}`,
)
}
>
<Eye
color={isDark ? "#f1f5f9" : "#0f172a"}
size={18}
strokeWidth={2.5}
/>
<Text className="ml-2 text-foreground font-sans-black text-xs uppercase tracking-widest">
View Receipt
</Text>
</Button>
)}
<Button
className="flex-1 h-10 rounded-[6px] border border-border bg-card"
onPress={handleScanSms}
disabled={scanningSms}
>
{scanningSms ? (
<ActivityIndicator color="#ea580c" />
) : (
<>
<Scan color="#ea580c" size={18} strokeWidth={2.5} />
<Text className="ml-2 text-foreground font-sans-black text-xs uppercase tracking-widest">
Scan SMS
</Text>
</>
)}
</Button>
</View>
<View className="flex-row gap-3">
{!payment.invoiceId && (
<Button
className="flex-1 h-10 rounded-[6px] bg-primary shadow-lg shadow-primary/20"
onPress={openMatchPicker}
disabled={matching}
>
{matching ? (
<ActivityIndicator color="white" />
) : (
<>
<Link2 size={18} color="white" strokeWidth={2.5} />
<Text className="ml-2 text-white font-sans-black text-xs uppercase tracking-widest">
Link Invoice
</Text>
</>
)}
</Button>
)}
</View>
<Button
variant="ghost"
className="h-10 rounded-[6px] bg-red-500"
onPress={handleDelete}
disabled={deleting}
>
{deleting ? (
<ActivityIndicator color="#ef4444" />
) : (
<>
<Trash2 color="#fff" size={18} />
<Text className="ml-2 text-white font-sans-bold text-xs uppercase tracking-widest">
Delete Payment
</Text>
</>
)}
</Button>
</View>
</ScrollView>
{/* Edit Modal */}
<Modal
visible={showEditModal}
animationType="slide"
presentationStyle="pageSheet"
onRequestClose={() => setShowEditModal(false)}
>
<ScreenWrapper className="bg-background">
<View className="flex-1 flex-col">
<View className="flex-row items-center justify-between px-4 pt-4 pb-2">
<Text variant="h4" className="text-foreground font-sans-semibold">
Edit Payment
</Text>
<Pressable
onPress={() => setShowEditModal(false)}
className="h-10 w-10 rounded-[10px] bg-card items-center justify-center border border-border"
>
<X color={isDark ? "#f1f5f9" : "#0f172a"} size={18} />
</Pressable>
</View>
<FormFlow
steps={[
{ key: "details", label: "Details" },
{ key: "schedule", label: "Schedule" },
{ key: "notes", label: "Notes" },
]}
currentStep={editStep}
onNext={() => setEditStep(editStep + 1)}
onBack={() => setEditStep(editStep - 1)}
onComplete={handleEditSave}
loading={editSaving}
completeLabel="Save Changes"
hideHeader
>
{editStep === 0 && (
<View className="gap-5">
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
Payment Details
</Text>
<View className="bg-card rounded-[6px] gap-4">
<View>
<Text className="font-sans-semibold text-[10px] uppercase tracking-wider mb-1.5 ml-1">
Transaction ID
</Text>
<TextInput
style={[
S.input,
{
backgroundColor: c.bg,
borderColor: c.border,
color: c.text,
},
]}
placeholder="TXN-2024-001"
placeholderTextColor={c.placeholder}
value={editTxnId}
onChangeText={setEditTxnId}
autoCorrect={false}
/>
</View>
<View className="flex-row gap-4">
<View style={{ flex: 1 }}>
<Text className="font-sans-semibold text-[10px] uppercase tracking-wider mb-1.5 ml-1">
Amount
</Text>
<TextInput
style={[
S.input,
{
backgroundColor: c.bg,
borderColor: c.border,
color: c.text,
},
]}
placeholder="0.00"
placeholderTextColor={c.placeholder}
value={editAmount}
onChangeText={setEditAmount}
keyboardType="numeric"
/>
</View>
<View style={{ flex: 1 }}>
<Text className="font-sans-semibold text-[10px] uppercase tracking-wider mb-1.5 ml-1">
Currency
</Text>
<Pressable
onPress={() => setShowEditCurrency(true)}
className="h-11 px-3 border border-border rounded-[6px] flex-row items-center justify-between"
style={{
backgroundColor: c.bg,
borderColor: c.border,
}}
>
<Text
className="text-xs font-sans-bold"
style={{ color: c.text }}
>
{editCurrency}
</Text>
<ChevronDown
size={14}
color={c.text}
strokeWidth={3}
/>
</Pressable>
</View>
</View>
</View>
</View>
)}
{editStep === 1 && (
<View className="gap-5">
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
Schedule
</Text>
<View className="bg-card rounded-[6px] gap-4">
<View style={{ flex: 1 }}>
<Text className="font-sans-semibold text-[10px] uppercase tracking-wider mb-1.5 ml-1">
Payment Date
</Text>
<Pressable
onPress={() => setShowEditDate(true)}
className="h-11 px-3 border border-border rounded-[6px] flex-row items-center justify-between"
style={{ backgroundColor: c.bg, borderColor: c.border }}
>
<Text
className="text-xs font-sans-medium"
style={{ color: c.text }}
>
{editPaymentDate || "Select Date"}
</Text>
<Clock size={14} color="#ea580c" strokeWidth={2.5} />
</Pressable>
</View>
<View style={{ flex: 1 }}>
<Text className="font-sans-semibold text-[10px] uppercase tracking-wider mb-1.5 ml-1">
Method
</Text>
<Pressable
onPress={() => setShowEditMethod(true)}
className="h-11 px-3 border border-border rounded-[6px] flex-row items-center justify-between"
style={{ backgroundColor: c.bg, borderColor: c.border }}
>
<Text
className="text-xs font-sans-bold"
style={{ color: c.text }}
>
{editPaymentMethod}
</Text>
<ChevronDown size={14} color={c.text} strokeWidth={3} />
</Pressable>
</View>
</View>
</View>
)}
{editStep === 2 && (
<View className="gap-5">
<Text className="text-[18px] font-sans-bold text-foreground tracking-tight">
Notes
</Text>
<View className="bg-card rounded-[6px]">
<Text className="font-sans-semibold text-[10px] uppercase tracking-wider mb-1.5 ml-1">
Notes
</Text>
<TextInput
style={[
S.input,
{
backgroundColor: c.bg,
borderColor: c.border,
color: c.text,
height: 120,
textAlignVertical: "top",
paddingTop: 10,
},
]}
placeholder="Optional notes"
placeholderTextColor={c.placeholder}
value={editNotes}
onChangeText={setEditNotes}
multiline
/>
</View>
</View>
)}
</FormFlow>
</View>
<PickerModal
visible={showEditCurrency}
onClose={() => setShowEditCurrency(false)}
title="Select Currency"
>
{CURRENCIES.map((curr) => (
<SelectOption
key={curr}
label={curr}
value={curr}
selected={editCurrency === curr}
onSelect={(v) => {
setEditCurrency(v);
setShowEditCurrency(false);
}}
/>
))}
</PickerModal>
<PickerModal
visible={showEditMethod}
onClose={() => setShowEditMethod(false)}
title="Select Payment Method"
>
{PAYMENT_METHODS.map((method) => (
<SelectOption
key={method}
label={method}
value={method}
selected={editPaymentMethod === method}
onSelect={(v) => {
setEditPaymentMethod(v);
setShowEditMethod(false);
}}
/>
))}
</PickerModal>
<PickerModal
visible={showEditDate}
onClose={() => setShowEditDate(false)}
title="Select Payment Date"
>
<CalendarGrid
selectedDate={editPaymentDate}
onSelect={(v) => {
setEditPaymentDate(v);
setShowEditDate(false);
}}
/>
</PickerModal>
</ScreenWrapper>
</Modal>
{/* Invoice Picker Modal */}
<PickerModal
visible={showInvoicePicker}
onClose={() => setShowInvoicePicker(false)}
title="Select Invoice"
>
<View className="px-4 pb-3">
<View className="flex-row items-center rounded-xl px-3 border border-border h-10">
<Search size={16} color={isDark ? "#94a3b8" : "#64748b"} />
<TextInput
className="flex-1 ml-2 text-foreground py-0 text-sm"
placeholder="Search by number or customer..."
placeholderTextColor={getPlaceholderColor(isDark)}
value={invoiceSearch}
onChangeText={setInvoiceSearch}
autoCorrect={false}
style={{ textAlignVertical: "center" }}
/>
</View>
</View>
<ScrollView className="max-h-96" keyboardShouldPersistTaps="handled">
{filteredInvoices.length > 0 ? (
filteredInvoices.map((inv) => (
<Pressable
key={inv.id}
onPress={() => handleInvoiceSelect(inv)}
className="px-4 py-3 border-b border-border/40 flex-row items-center"
>
<View className="flex-1">
<Text className="text-foreground font-sans-bold text-sm">
{inv.customerName || "Unknown"}
</Text>
<Text className="text-muted-foreground text-[10px] font-sans-semibold uppercase tracking-wider mt-0.5">
#{inv.invoiceNumber || inv.id} · {inv.currency || "ETB"}{" "}
{Number(inv.amount || 0).toLocaleString()}
</Text>
</View>
<Link2 size={16} color="#ea580c" strokeWidth={2.5} />
</Pressable>
))
) : (
<EmptyState
title={
invoiceSearch
? "No invoices match your search"
: "No invoices available"
}
/>
)}
</ScrollView>
</PickerModal>
<ActionModal
visible={showDeleteModal}
onClose={() => setShowDeleteModal(false)}
onConfirm={confirmDelete}
title="Delete Payment"
description="Are you sure you want to permanently delete this payment record? This action cannot be reversed."
confirmText="Delete"
confirmVariant="destructive"
icon={Trash2}
iconColor="#ef4444"
loading={deleting}
/>
</ScreenWrapper>
);
}