import React, { useEffect, useState, useCallback } from 'react'; import { View, Text, StyleSheet, ActivityIndicator, ScrollView, RefreshControl, Alert, TouchableOpacity, Modal, TextInput, ToastAndroid } from 'react-native'; import { useRoute, RouteProp } from '@react-navigation/native'; import { Ionicons } from "@react-native-vector-icons/ionicons"; import colors from '../../components/colors'; import AppView from '../../components/AppView'; import { apiCall } from '../../utils/ApiHelper'; import { BASE_URL } from '../../utils/ApiUrls'; import { responsiveFontSize } from 'react-native-responsive-dimensions'; // --- TYPE DEFINITIONS --- interface ApiResponse { status: 'success' | 'error'; data: T; message?: string; statusCode: number; } interface Participant { userId: string; firstName: string; lastName: string; mobile: string; contributionAmount: number; paidAmount: number; payAmount?: number; status: 'SUCCESS' | 'PENDING' | 'FAILED' | 'UNPAID'; lateFee: number; lateFeeWaived: boolean; totalPaidAmount: string; expectedAmount: number; waivedAmount:number } interface PaymentDetails { groupId: string; groupName: string; currentCycle: number; contributionAmount: number; totalDueWithLateFee: number; participants: Participant[]; nextCycleStart: string; cycleCompleted: boolean; } type PaymentStackParamList = { Payments: { groupId: string } }; type PaymentsRouteProp = RouteProp; // The component is a valid React Functional Component, which is required for using hooks. const Payments: React.FC = () => { // --- HOOKS --- // All hooks are called at the top level, which is correct. const route = useRoute(); const { groupId } = route.params; const [paymentDetails, setPaymentDetails] = useState(null); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [isGroupActive, setIsGroupActive] = useState(true); const [isModalVisible, setModalVisible] = useState(false); const [currentParticipant, setCurrentParticipant] = useState(null); const [waiverAmount, setWaiverAmount] = useState(''); const [cycleData, setCycleData] = useState(); // --- FUNCTIONS --- // These are regular functions and do not call hooks inside them, which is correct. const getCycleData = async () => { try { const response = await apiCall(`${BASE_URL}/payments/groups/${groupId}/cycle-dashboard`, 'GET') as ApiResponse; if (response && response.statusCode === 200) { setCycleData(response.data); } } catch (error: any) { console.error("Failed to fetch cycle data:", error.message); } }; const fetchPaymentDetails = useCallback(async () => { if (!refreshing) setLoading(true); setIsGroupActive(true); try { const response = await apiCall(`${BASE_URL}payments/current/payment/details/${groupId}`, 'GET') as ApiResponse; // console.log("fchffryjtc", response) if (response && response.statusCode === 200) { setPaymentDetails(response.data); } else if (response && response.statusCode === 400) { setIsGroupActive(false); setPaymentDetails(null); } else { setIsGroupActive(false); } } catch (error: any) { console.log("payment error: ", error); setIsGroupActive(false); } finally { setLoading(false); setRefreshing(false); } }, [groupId, refreshing]); // --- EFFECT HOOK --- // useEffect is correctly used to call functions when the component mounts or dependencies change. useEffect(() => { getCycleData(); fetchPaymentDetails(); }, [fetchPaymentDetails]); const onRefresh = useCallback(() => { setRefreshing(true); getCycleData(); fetchPaymentDetails(); }, [fetchPaymentDetails]); const openWaiverDialog = (participant: Participant) => { setCurrentParticipant(participant); setWaiverAmount(participant.lateFee.toString()); setModalVisible(true); }; const handleWaiveFee = async () => { console.log("first approch ") // if (!currentParticipant || !paymentDetails) console.log("second approch ") ; const amountToWaive = parseFloat(waiverAmount); if (isNaN(amountToWaive) || amountToWaive < 0) { ToastAndroid.show("Please enter a valid waiver amount.", ToastAndroid.SHORT); return; } if (amountToWaive > currentParticipant.lateFee) { ToastAndroid.show(`Waiver cannot exceed the current late fee of ₹${currentParticipant.lateFee}.`, ToastAndroid.SHORT); return; } try { const url = `${BASE_URL}payments/groups/${groupId}/cycles/${paymentDetails.currentCycle}/waive/${currentParticipant.userId}?amountToWaive=${amountToWaive}`; const response = await apiCall(url, 'PUT', {}) as any; console.log("vtitttit hbfbf sjbj j b", response) if (response && response.statusCode === 200) { ToastAndroid.show(`₹${amountToWaive} has been waived successfully.`, ToastAndroid.SHORT); setModalVisible(false); onRefresh(); } else { throw new Error(response?.message || "Failed to apply waiver."); } } catch (error: any) { ToastAndroid.show(error.message || "Failed to apply waiver.", ToastAndroid.SHORT); console.error("Waive Fee Error:", error); } }; // --- RENDER LOGIC --- const renderParticipant = (participant: Participant) => { const isPending = participant.status === 'UNPAID'; const canWaive = participant.lateFee > 0 && isPending return ( {participant.firstName} {participant.lastName} {participant.mobile} {participant.status} {participant.status === "UNPAID" && {`₹${participant?.expectedAmount} `} } {participant.status === "SUCCESS" && {`₹${participant?.totalPaidAmount}`} } {/* {isPending ? `₹${participant.payAmount || 0}` : `₹${participant.totalPaidAmount}`} */} {participant.lateFee > 0 && ( Late Fee: ₹{participant.lateFee} )} {/* {participant.lateFeeWaived && Fee Waived} */} { participant?.lateFeeWaived && Waived Amount : {participant?.waivedAmount} } {canWaive && ( openWaiverDialog(participant)}> )} ); }; return ( setModalVisible(false)} > Waive Late Fee For {currentParticipant?.firstName}. Current late fee is ₹{currentParticipant?.lateFee}. setModalVisible(false)}> Cancel Apply Waiver {loading && !refreshing ? ( ) : !isGroupActive ? ( This group is not currently active. Tap to Refresh ) : ( } > {paymentDetails && ( <> {paymentDetails.groupName} Cycle {paymentDetails.currentCycle} Contribution ₹ {paymentDetails.contributionAmount} Total Amount ₹{paymentDetails.totalDueWithLateFee} Participants {paymentDetails?.cycleCompleted && Next Cycle from : {paymentDetails?.nextCycleStart} }{paymentDetails.participants.map(renderParticipant)} )} )} ); }; const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#f4f6f8' }, header: { padding: 20, paddingBottom: 15 }, headerTitle: { color: '#000000', fontSize: 24, fontWeight: 'bold' }, summaryContainer: { flexDirection: 'row', justifyContent: 'space-around', padding: 20, backgroundColor: '#fff', marginHorizontal: 15, borderRadius: 12, elevation: 2 }, summaryBox: { alignItems: 'center' }, summaryLabel: { fontSize: 14, color: '#6c757d', marginBottom: 4 }, summaryValue: { fontSize: 18, fontWeight: '700', color: colors.black }, sectionTitle: { fontSize: 20, fontWeight: 'bold', marginLeft: 20, marginTop: 20, marginBottom: 10 }, participantCard: { backgroundColor: '#fff', marginHorizontal: 15, marginBottom: 10, borderRadius: 10, padding: 15, flexDirection: 'row', alignItems: 'center', elevation: 1 }, participantInfo: { flex: 1 }, participantName: { fontSize: 16, fontWeight: '600' }, participantMobile: { fontSize: 14, color: '#6c757d', marginTop: 2 }, paymentStatus: { alignItems: 'flex-end', marginHorizontal: 10 }, statusText: { fontSize: 14, fontWeight: 'bold', textTransform: 'uppercase' }, amountText: { fontSize: 14, color: '#495057', marginTop: 2 }, lateFeeText: { fontSize: 13, color: colors.danger, fontWeight: '600', marginTop: 4 }, waivedText: { fontSize: 13, color: colors.success, fontWeight: '600', marginTop: 4 }, menuContainer: { justifyContent: 'center', alignItems: 'center', minWidth: 25 }, modalOverlay: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.5)', justifyContent: 'center', alignItems: 'center' }, modalContainer: { width: '85%', backgroundColor: 'white', borderRadius: 15, padding: 20, elevation: 10 }, modalTitle: { fontSize: 20, fontWeight: 'bold', color: '#333', marginBottom: 5 }, modalSubtitle: { fontSize: 14, color: '#666', marginBottom: 20 }, input: { width: '100%', borderWidth: 1, borderColor: '#ddd', borderRadius: 8, paddingHorizontal: 15, paddingVertical: 12, fontSize: 16, marginBottom: 20 }, modalButtonContainer: { flexDirection: 'row', justifyContent: 'flex-end' }, modalButton: { paddingVertical: 10, paddingHorizontal: 20, borderRadius: 8, marginLeft: 10 }, cancelButton: { backgroundColor: '#f0f0f0' }, cancelButtonText: { color: '#333', fontWeight: 'bold' }, saveButton: { backgroundColor: colors.primary }, saveButtonText: { color: 'white', fontWeight: 'bold' }, fullScreenMessage: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#f4f6f8', padding: 20, }, fullScreenMessageText: { fontSize: responsiveFontSize(1.8), color: '#333', textAlign: 'center', marginTop: 15, fontWeight: 'bold', }, refreshButton: { marginTop: 20, paddingHorizontal: 20, paddingVertical: 10, borderRadius: 8, }, refreshButtonText: { color: colors.black, fontWeight: 'bold', }, }); export default Payments;