notification
This commit is contained in:
@@ -8,7 +8,7 @@ import { apiCall } from '../../utils/ApiHelper';
|
||||
import { BASE_URL } from '../../utils/ApiUrls';
|
||||
|
||||
// --- CONSTANTS ---
|
||||
const API_BASE_URL = 'https://api.godemo.in/FundCollect/api/payments';
|
||||
const API_BASE_URL = 'http://192.168.1.5:9060/api/payments';
|
||||
|
||||
// --- TYPE DEFINITIONS ---
|
||||
interface ApiResponse<T> {
|
||||
@@ -43,54 +43,61 @@ interface PaymentDetails {
|
||||
type PaymentStackParamList = { Payments: { groupId: string } };
|
||||
type PaymentsRouteProp = RouteProp<PaymentStackParamList, 'Payments'>;
|
||||
|
||||
// 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<PaymentsRouteProp>();
|
||||
const { groupId } = route.params;
|
||||
|
||||
const [paymentDetails, setPaymentDetails] = useState<PaymentDetails | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
// State for the waiver modal
|
||||
const [isGroupActive, setIsGroupActive] = useState(true);
|
||||
const [isModalVisible, setModalVisible] = useState(false);
|
||||
const [currentParticipant, setCurrentParticipant] = useState<Participant | null>(null);
|
||||
const [waiverAmount, setWaiverAmount] = useState('');
|
||||
const [cycleData, setCycleData] = useState<any>();
|
||||
|
||||
// --- FUNCTIONS ---
|
||||
// These are regular functions and do not call hooks inside them, which is correct.
|
||||
const getCycleData = async () => {
|
||||
try {
|
||||
|
||||
const response = await apiCall(`${API_BASE_URL}/groups/${groupId}/cycle-dashboard`, 'GET') as ApiResponse<any>;
|
||||
const response = await apiCall(`${API_BASE_URL}/groups/${groupId}/cycle-dashboard`, 'GET') as ApiResponse<any>;
|
||||
if (response && response.statusCode === 200) {
|
||||
setCycleData(response.data);
|
||||
} else {
|
||||
Alert.alert("Error", response?.message || "Failed to fetch cycle data.");
|
||||
}
|
||||
} catch (error: any) {
|
||||
Alert.alert("Error", error.message || "An unexpected error occurred.");
|
||||
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<PaymentDetails>;
|
||||
|
||||
console.log("payment response :", response)
|
||||
if (response && response.statusCode === 200) {
|
||||
setPaymentDetails(response.data);
|
||||
} else if (response && response.statusCode === 400) {
|
||||
setIsGroupActive(false);
|
||||
setPaymentDetails(null);
|
||||
} else {
|
||||
Alert.alert("Error", response?.message || "Failed to fetch details.");
|
||||
setIsGroupActive(false);
|
||||
}
|
||||
} catch (error: any) {
|
||||
// Alert.alert("Error", error.message || "An unexpected error occurred.");
|
||||
console.log("payment error : ", error)
|
||||
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();
|
||||
@@ -104,18 +111,17 @@ const Payments: React.FC = () => {
|
||||
|
||||
const openWaiverDialog = (participant: Participant) => {
|
||||
setCurrentParticipant(participant);
|
||||
setWaiverAmount('');
|
||||
setWaiverAmount(participant.lateFee.toString());
|
||||
setModalVisible(true);
|
||||
};
|
||||
|
||||
// --- UPDATED: handleWaiveFee now uses the correct API endpoint ---
|
||||
const handleWaiveFee = async () => {
|
||||
if (!currentParticipant || !paymentDetails) return;
|
||||
|
||||
const amountToWaive = parseFloat(waiverAmount);
|
||||
|
||||
if (isNaN(amountToWaive) || amountToWaive <= 0) {
|
||||
Alert.alert("Invalid Input", "Please enter a waiver amount greater than zero.");
|
||||
if (isNaN(amountToWaive) || amountToWaive < 0) {
|
||||
Alert.alert("Invalid Input", "Please enter a valid waiver amount.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -125,15 +131,13 @@ const Payments: React.FC = () => {
|
||||
}
|
||||
|
||||
try {
|
||||
// Construct the exact URL as provided
|
||||
const url = `${BASE_URL}groups/${groupId}/cycles/${paymentDetails.currentCycle}/waive/${currentParticipant.userId}?amountToWaive=${amountToWaive}`;
|
||||
|
||||
const response = await apiCall(url, 'PUT', {}) as any; // Body is empty as amount is in URL
|
||||
const response = await apiCall(url, 'PUT', {}) as any;
|
||||
|
||||
if (response && response.statusCode === 200) {
|
||||
Alert.alert("Success", `₹${amountToWaive} has been waived successfully.`);
|
||||
setModalVisible(false);
|
||||
onRefresh(); // Refresh data to reflect the change
|
||||
onRefresh();
|
||||
} else {
|
||||
throw new Error(response?.message || "Failed to apply waiver.");
|
||||
}
|
||||
@@ -142,13 +146,14 @@ const Payments: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// --- RENDER LOGIC ---
|
||||
const renderParticipant = (participant: Participant) => {
|
||||
const isPending = participant.status === 'PENDING';
|
||||
const canWaive = isPending && participant.lateFee > 0;
|
||||
const canWaive = isPending && participant.lateFee > 0 && !participant.lateFeeWaived;
|
||||
|
||||
return (
|
||||
<View key={participant.userId} style={styles.participantCard}>
|
||||
<View style={styles.participantInfo}>
|
||||
<View style={styles.participantInfo}>
|
||||
<Text style={styles.participantName}>{participant.firstName} {participant.lastName}</Text>
|
||||
<Text style={styles.participantMobile}>{participant.mobile}</Text>
|
||||
</View>
|
||||
@@ -163,7 +168,7 @@ const Payments: React.FC = () => {
|
||||
{isPending && participant.lateFee > 0 && (
|
||||
<Text style={styles.lateFeeText}>Late Fee: ₹{participant.lateFee}</Text>
|
||||
)}
|
||||
{participant.lateFee === 0 && participant.lateFeeWaived && <Text style={styles.waivedText}>Fee Waived</Text>}
|
||||
{participant.lateFeeWaived && <Text style={styles.waivedText}>Fee Waived</Text>}
|
||||
</View>
|
||||
|
||||
<View style={styles.menuContainer}>
|
||||
@@ -210,36 +215,48 @@ const Payments: React.FC = () => {
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||
>
|
||||
{loading && !refreshing ? (
|
||||
<ActivityIndicator style={{marginTop: 50}} size="large" color={colors.primary} />
|
||||
) : (
|
||||
<>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.headerTitle}>{paymentDetails?.groupName}</Text>
|
||||
</View>
|
||||
<View style={styles.summaryContainer}>
|
||||
<View style={styles.summaryBox}>
|
||||
<Text style={styles.summaryLabel}>Cycle</Text>
|
||||
<Text style={styles.summaryValue}>{paymentDetails?.currentCycle}</Text>
|
||||
{loading && !refreshing ? (
|
||||
<ActivityIndicator style={{marginTop: 50}} size="large" color={colors.primary} />
|
||||
) : !isGroupActive ? (
|
||||
<View style={styles.fullScreenMessage}>
|
||||
<Ionicons name="warning-outline" size={40} color={colors.black} />
|
||||
<Text style={styles.fullScreenMessageText}>
|
||||
This group is not currently active.
|
||||
</Text>
|
||||
<TouchableOpacity onPress={onRefresh} style={styles.refreshButton}>
|
||||
<Text style={styles.refreshButtonText}>Tap to Refresh</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||
>
|
||||
{paymentDetails && (
|
||||
<>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.headerTitle}>{paymentDetails.groupName}</Text>
|
||||
</View>
|
||||
<View style={styles.summaryBox}>
|
||||
<Text style={styles.summaryLabel}>Contribution</Text>
|
||||
<Text style={styles.summaryValue}>₹{paymentDetails?.contributionAmount}</Text>
|
||||
<View style={styles.summaryContainer}>
|
||||
<View style={styles.summaryBox}>
|
||||
<Text style={styles.summaryLabel}>Cycle</Text>
|
||||
<Text style={styles.summaryValue}>{paymentDetails.currentCycle}</Text>
|
||||
</View>
|
||||
<View style={styles.summaryBox}>
|
||||
<Text style={styles.summaryLabel}>Contribution</Text>
|
||||
<Text style={styles.summaryValue}>₹{paymentDetails.contributionAmount}</Text>
|
||||
</View>
|
||||
<View style={styles.summaryBox}>
|
||||
<Text style={styles.summaryLabel}>Total Due</Text>
|
||||
<Text style={[styles.summaryValue, { color: colors.danger }]}>₹{paymentDetails.totalDueWithLateFee}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<View style={styles.summaryBox}>
|
||||
<Text style={styles.summaryLabel}>Total Due</Text>
|
||||
<Text style={[styles.summaryValue, { color: colors.danger }]}>₹{paymentDetails?.totalDueWithLateFee}</Text>
|
||||
</View>
|
||||
</View>
|
||||
<Text style={styles.sectionTitle}>Participants</Text>
|
||||
{paymentDetails?.participants.map(renderParticipant)}
|
||||
</>
|
||||
)}
|
||||
</ScrollView>
|
||||
<Text style={styles.sectionTitle}>Participants</Text>
|
||||
{paymentDetails.participants.map(renderParticipant)}
|
||||
</>
|
||||
)}
|
||||
</ScrollView>
|
||||
)}
|
||||
</AppView>
|
||||
);
|
||||
};
|
||||
@@ -251,7 +268,7 @@ const styles = StyleSheet.create({
|
||||
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.primary },
|
||||
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 },
|
||||
@@ -274,6 +291,30 @@ const styles = StyleSheet.create({
|
||||
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: 18,
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user