fix: 5 bugs - tab height, collect pay modal, invoice picker in record payment, comment body field, dashboard tickets label

This commit is contained in:
Nemo
2026-03-24 11:44:14 +08:00
parent defacbcc77
commit f8c5402b02
5 changed files with 211 additions and 41 deletions

View File

@@ -1,12 +1,95 @@
import { View, Text, ScrollView, TouchableOpacity, RefreshControl, ActivityIndicator, Linking, Alert, TextInput } from 'react-native';
import { View, Text, ScrollView, TouchableOpacity, RefreshControl, ActivityIndicator, Linking, Alert, TextInput, Modal } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { router } from 'expo-router';
import { useQuery } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { useState } from 'react';
import { api } from '../../../services/api';
// ── Pay Invoice Modal ──────────────────────────────────────────────────────────
function PayInvoiceModal({ invoice, onClose, onSuccess }: { invoice: any; onClose: () => void; onSuccess: () => void }) {
const [amount, setAmount] = useState('');
const [method, setMethod] = useState('CASH');
const [ref, setRef] = useState('');
const [loading, setLoading] = useState(false);
const METHODS = ['CASH', 'GCASH', 'MAYA', 'BANK'];
const submit = async () => {
const amt = Number(amount);
if (!amt || amt <= 0) return Alert.alert('Required', 'Enter a valid amount.');
if (amt > Number(invoice.balance)) {
Alert.alert('Over Payment', `Amount exceeds balance of ₱${Number(invoice.balance).toLocaleString()}`);
return;
}
setLoading(true);
try {
await api.post('/api/v1/payments', {
clientId: invoice.clientId,
invoiceId: invoice.id,
amount: amt,
channel: method,
referenceNumber: ref.trim() || undefined,
paymentDate: new Date().toISOString(),
});
Alert.alert('Payment Recorded! ✓', `${amt.toLocaleString()} applied to ${invoice.invoiceNumber}`);
onSuccess();
} catch (e: any) {
const msg = e?.response?.data?.message;
Alert.alert('Error', Array.isArray(msg) ? msg.join('\n') : msg ?? 'Payment failed.');
} finally { setLoading(false); }
};
return (
<Modal visible animationType="slide" transparent onRequestClose={onClose}>
<TouchableOpacity style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.5)', justifyContent: 'flex-end' }} activeOpacity={1} onPress={onClose}>
<TouchableOpacity activeOpacity={1} style={{ backgroundColor: '#FFF', borderTopLeftRadius: 28, borderTopRightRadius: 28, padding: 24 }}>
<Text style={{ fontSize: 20, fontWeight: '800', color: '#0F172A', marginBottom: 2 }}>Record Payment</Text>
<Text style={{ fontSize: 14, color: '#64748B', marginBottom: 4 }}>{invoice.client?.firstName} {invoice.client?.lastName} · {invoice.client?.accountNumber}</Text>
<Text style={{ fontSize: 15, color: '#64748B', marginBottom: 20 }}>
{invoice.invoiceNumber} · Balance: <Text style={{ fontWeight: '700', color: '#991B1B' }}>{Number(invoice.balance).toLocaleString()}</Text>
</Text>
<Text style={{ fontSize: 15, fontWeight: '700', color: '#0F172A', marginBottom: 8 }}>Amount ()</Text>
<TextInput
style={{ backgroundColor: '#F8FAFC', borderWidth: 1.5, borderColor: '#E2E8F0', borderRadius: 14, paddingHorizontal: 16, paddingVertical: 14, fontSize: 28, fontWeight: '800', color: '#0F172A', textAlign: 'center', marginBottom: 16 }}
placeholder="0.00" placeholderTextColor="#CBD5E1"
keyboardType="decimal-pad" value={amount} onChangeText={setAmount}
/>
<Text style={{ fontSize: 15, fontWeight: '700', color: '#0F172A', marginBottom: 10 }}>Method</Text>
<View style={{ flexDirection: 'row', marginBottom: 16 }}>
{METHODS.map(m => (
<TouchableOpacity key={m} onPress={() => setMethod(m)}
style={{ flex: 1, borderRadius: 12, paddingVertical: 12, alignItems: 'center', marginHorizontal: 3, backgroundColor: method === m ? '#0891B2' : '#F1F5F9' }}
activeOpacity={0.7}
>
<Text style={{ fontSize: 13, fontWeight: '700', color: method === m ? '#FFF' : '#64748B' }}>{m}</Text>
</TouchableOpacity>
))}
</View>
<Text style={{ fontSize: 15, fontWeight: '700', color: '#0F172A', marginBottom: 8 }}>Reference # <Text style={{ fontWeight: '400', color: '#94A3B8' }}>(optional)</Text></Text>
<TextInput
style={{ backgroundColor: '#F8FAFC', borderWidth: 1.5, borderColor: '#E2E8F0', borderRadius: 14, paddingHorizontal: 16, paddingVertical: 12, fontSize: 16, color: '#0F172A', marginBottom: 20 }}
placeholder="GCash ref, OR number..." placeholderTextColor="#94A3B8"
value={ref} onChangeText={setRef}
/>
<TouchableOpacity
style={{ backgroundColor: amount && Number(amount) > 0 ? '#059669' : '#CBD5E1', borderRadius: 14, paddingVertical: 18, alignItems: 'center' }}
onPress={submit} disabled={loading} activeOpacity={0.8}
>
{loading ? <ActivityIndicator color="#FFF" /> : <Text style={{ color: '#FFF', fontSize: 17, fontWeight: '700' }}>Confirm Payment</Text>}
</TouchableOpacity>
</TouchableOpacity>
</TouchableOpacity>
</Modal>
);
}
export default function CollectScreen() {
const [search, setSearch] = useState('');
const [payingInvoice, setPayingInvoice] = useState<any>(null);
const qc = useQueryClient();
const { data: invoices, isLoading, isRefetching, refetch } = useQuery({
queryKey: ['unpaid-invoices'],
@@ -148,7 +231,7 @@ export default function CollectScreen() {
return (
<TouchableOpacity
key={inv.id}
onPress={() => router.push({ pathname: '/(app)/clients/[id]', params: { id: inv.clientId, tab: 'invoices' } })}
onPress={() => setPayingInvoice(inv)}
activeOpacity={0.8}
style={{
backgroundColor: '#FFF',
@@ -194,7 +277,7 @@ export default function CollectScreen() {
</View>
<TouchableOpacity
onPress={(e) => { e.stopPropagation?.(); navigate(inv); }}
onPress={() => navigate(inv)}
style={{
flexDirection: 'row', alignItems: 'center',
backgroundColor: hasLocation ? '#ECFEFF' : '#F1F5F9',
@@ -214,6 +297,17 @@ export default function CollectScreen() {
</ScrollView>
)}
</View>
{payingInvoice && (
<PayInvoiceModal
invoice={payingInvoice}
onClose={() => setPayingInvoice(null)}
onSuccess={() => {
setPayingInvoice(null);
qc.invalidateQueries({ queryKey: ['unpaid-invoices'] });
}}
/>
)}
</SafeAreaView>
);
}