import { useState, useEffect } from 'react'; import { View, Text, TextInput, TouchableOpacity, ScrollView, Alert, ActivityIndicator, Modal } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { SlideToConfirm } from '../../../components/SlideToConfirm'; import { router, useLocalSearchParams } from 'expo-router'; import { useQuery } from '@tanstack/react-query'; import { api } from '../../../services/api'; const METHODS = [ { id: 'CASH', label: 'Cash' }, { id: 'GCASH', label: 'GCash' }, { id: 'MAYA', label: 'Maya' }, { id: 'BANK', label: 'Bank Transfer' }, ]; export default function RecordPaymentScreen() { // Prefill params when navigated from client detail const params = useLocalSearchParams<{ prefillClientId?: string; prefillName?: string; prefillAccountNumber?: string; }>(); const [search, setSearch] = useState(''); const [client, setClient] = useState(null); const [invoice, setInvoice] = useState(null); const [showInvoicePicker, setShowInvoicePicker] = useState(false); const [amount, setAmount] = useState(''); const [method, setMethod] = useState('CASH'); const [reference, setReference] = useState(''); const [loading, setLoading] = useState(false); const [searching, setSearching] = useState(false); const { data: clientInvoices } = useQuery({ queryKey: ['record-payment-invoices', client?.id], queryFn: async () => { const res = await api.get(`/api/v1/invoices?clientId=${client.id}&limit=50`); const all: any[] = res.data?.data ?? res.data ?? []; return all.filter((inv: any) => ['SENT', 'PARTIAL', 'OVERDUE'].includes(inv.status) && Number(inv.balance) > 0); }, enabled: !!client?.id, }); useEffect(() => { if (params.prefillClientId && params.prefillName) { setClient({ id: params.prefillClientId, firstName: params.prefillName.split(' ')[0] ?? '', lastName: params.prefillName.split(' ').slice(1).join(' ') ?? '', accountNumber: params.prefillAccountNumber ?? '', }); } }, []); // When client changes, reset invoice selection useEffect(() => { setInvoice(null); }, [client?.id]); const searchClient = async () => { if (!search.trim()) return; setSearching(true); try { const res = await api.get(`/api/v1/clients?search=${encodeURIComponent(search.trim())}&limit=5`); const found = res.data?.data ?? res.data ?? []; if (Array.isArray(found) && found.length === 1) { setClient(found[0]); } else if (Array.isArray(found) && found.length > 1) { // Show picker if multiple results Alert.alert( 'Multiple clients found', found.map((c: any, i: number) => `${i + 1}. ${c.firstName} ${c.lastName} (${c.accountNumber})`).join('\n'), [ ...found.slice(0, 5).map((c: any, i: number) => ({ text: `${i + 1}. ${c.firstName} ${c.lastName}`, onPress: () => setClient(c), })), { text: 'Cancel', style: 'cancel' as const }, ] ); } else { Alert.alert('Not Found', 'No client found. Try account number or full name.'); } } catch { Alert.alert('Error', 'Search failed. Please try again.'); } finally { setSearching(false); } }; const submit = async () => { if (!client) return Alert.alert('Required', 'Search and select a client first.'); const amt = Number(amount); if (!amount || isNaN(amt) || amt <= 0) return Alert.alert('Required', 'Enter a valid amount.'); setLoading(true); try { await api.post('/api/v1/payments', { clientId: client.id, invoiceId: invoice?.id ?? undefined, amount: amt, channel: method, referenceNumber: reference.trim() || undefined, paymentDate: new Date().toISOString(), }); Alert.alert('Payment Recorded!', `₱${amt.toLocaleString()} from ${client.firstName} ${client.lastName}`, [ { text: 'Record Another', onPress: () => { setClient(null); setAmount(''); setSearch(''); setReference(''); } }, { text: 'Done', onPress: () => router.back() }, ]); } catch (e: any) { const msg = e?.response?.data?.message; Alert.alert('Error', Array.isArray(msg) ? msg.join('\n') : msg ?? 'Payment failed.'); } finally { setLoading(false); } }; const canSubmit = !!client && !!amount && Number(amount) > 0; return ( {/* Header */} router.back()} style={{ marginBottom: 12 }} activeOpacity={0.7}> ← Back Record Payment Field collection {/* Client section */} Client {client ? ( {client.firstName} {client.lastName} {client.accountNumber} { setClient(null); setSearch(''); }} style={{ marginTop: 10 }} hitSlop={{ top: 8, bottom: 8, left: 0, right: 8 }}> × Change client ) : ( {search.length > 0 && ( setSearch('')} hitSlop={{ top: 10, bottom: 10, left: 10, right: 10 }}> × )} {searching ? : Find } Search by account number, first name, or last name )} {/* Invoice selection (shown after client selected) */} {client && ( <> Invoice (optional) {!clientInvoices ? ( ) : clientInvoices.length === 0 ? ( ✓ No outstanding invoices ) : ( setShowInvoicePicker(true)} activeOpacity={0.7} > {invoice ? invoice.invoiceNumber : 'Select invoice to apply payment'} {invoice && ( Balance: ₱{Number(invoice.balance).toLocaleString()} )} )} )} {/* Amount */} Amount (₱) {/* Payment method */} Payment Method {METHODS.map(m => ( setMethod(m.id)} style={{ flex: 1, borderRadius: 14, paddingVertical: 14, alignItems: 'center', marginHorizontal: 4, backgroundColor: method === m.id ? '#0891B2' : '#FFF', borderWidth: 1.5, borderColor: method === m.id ? '#0891B2' : '#E2E8F0' }} activeOpacity={0.7} > {m.label} ))} {/* Reference */} Reference # (optional) {/* Invoice Picker Modal */} setShowInvoicePicker(false)}> setShowInvoicePicker(false)}> Select Invoice Choose which invoice to apply payment to { setInvoice(null); setShowInvoicePicker(false); }} style={{ padding: 16, borderRadius: 14, marginBottom: 10, backgroundColor: !invoice ? '#ECFEFF' : '#F8FAFC', borderWidth: 1.5, borderColor: !invoice ? '#0891B2' : '#F1F5F9' }} activeOpacity={0.7} > No specific invoice (general payment) {(clientInvoices ?? []).map((inv: any) => ( { setInvoice(inv); setAmount(String(inv.balance)); setShowInvoicePicker(false); }} style={{ padding: 16, borderRadius: 14, marginBottom: 10, backgroundColor: invoice?.id === inv.id ? '#ECFEFF' : '#F8FAFC', borderWidth: 1.5, borderColor: invoice?.id === inv.id ? '#0891B2' : '#F1F5F9' }} activeOpacity={0.7} > {inv.invoiceNumber} ₱{Number(inv.balance).toLocaleString()} {inv.dueDate ? `Due: ${new Date(inv.dueDate).toLocaleDateString('en-PH', { month: 'short', day: 'numeric', year: 'numeric' })}` : 'No due date'} · {inv.status} ))} ); }