late fee added
This commit is contained in:
@@ -15,7 +15,7 @@ import colors from "../../../components/colors";
|
||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||
import Header from "../../../components/common/Header";
|
||||
import { apiCall } from "../../../utils/ApiHelper";
|
||||
import { CONTRIBUTE, GROUP_ALL_MEMBER } from "../../../utils/ApiUrls";
|
||||
import { BASE_URL, CONTRIBUTE, GROUP_ALL_MEMBER } from "../../../utils/ApiUrls";
|
||||
import ContributionScreen from "../../ContributionScreen";
|
||||
|
||||
// Define type for a member
|
||||
@@ -108,10 +108,20 @@ const GroupDetails = () => {
|
||||
|
||||
const confirmPayment = async () => {
|
||||
try {
|
||||
const res = await apiCall(`${BASE_URL}payments/contribute/${group?.id}?amount=${group?.collected}`, 'POST') as any
|
||||
console.log("payment response : ", res)
|
||||
if (res.status) {
|
||||
ToastAndroid.show('Payment done!', ToastAndroid.SHORT)
|
||||
}
|
||||
if (res.status === 400) {
|
||||
ToastAndroid.show(res?.data?.message || 'Payment done!', ToastAndroid.SHORT)
|
||||
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
console.log("payment error : ", error)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
const renderMember = ({ item, index }: { item: any; index: number }) => (
|
||||
<View
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
TouchableOpacity,
|
||||
Alert,
|
||||
RefreshControl,
|
||||
ScrollView,
|
||||
Button,
|
||||
} from "react-native";
|
||||
import { useNavigation } from "@react-navigation/native";
|
||||
import type { NativeStackNavigationProp } from "@react-navigation/native-stack";
|
||||
@@ -15,6 +17,7 @@ import colors from "../../../components/colors";
|
||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||
import { GET_JOINED_GROUP_URL } from "../../../utils/ApiUrls";
|
||||
import * as Keychain from "react-native-keychain";
|
||||
import AnimatedCard from "../../../components/common/AnimatedCard";
|
||||
|
||||
type NavigationProp = NativeStackNavigationProp<
|
||||
RootStackParamList,
|
||||
@@ -47,13 +50,12 @@ const UserHome = () => {
|
||||
0
|
||||
);
|
||||
|
||||
|
||||
|
||||
const onRefresh = async () => {
|
||||
setRefreshing(true)
|
||||
await handleGroup()
|
||||
setRefreshing(false)
|
||||
}
|
||||
|
||||
const handleGroup = async () => {
|
||||
try {
|
||||
setLoading(true)
|
||||
@@ -120,9 +122,9 @@ const UserHome = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<View>
|
||||
<>
|
||||
{/* Stats */}
|
||||
<View style={[styles.statsRow, { marginVertical: 24 }]}>
|
||||
<View style={[styles.statsRow, { marginTop:12 }]}>
|
||||
<View style={styles.statBox}>
|
||||
<Text style={styles.statValue}>{totalGroups}</Text>
|
||||
<Text style={styles.statLabel}>Groups Joined</Text>
|
||||
@@ -132,6 +134,10 @@ const UserHome = () => {
|
||||
<Text style={styles.statLabel}>Cycle Contribution</Text>
|
||||
</View>
|
||||
</View>
|
||||
{/* <Button title="Transaction List" onPress={()=>navigation.navigate('TransactionList')}/> */}
|
||||
<AnimatedCard
|
||||
|
||||
onPress={() => navigation.navigate("TransactionList")} title={"transaction list"} iconName="calculator-outline" subtitle="View your transaction history" />
|
||||
|
||||
{/* Groups List */}
|
||||
<Text style={styles.sectionTitle}>Your Groups</Text>
|
||||
@@ -143,7 +149,7 @@ const UserHome = () => {
|
||||
onRefresh={onRefresh}
|
||||
refreshing={refreshing}
|
||||
/>
|
||||
</View>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -23,9 +23,10 @@ import { RequestCard } from "../../components/RequestCard";
|
||||
import { useSelector } from "react-redux";
|
||||
import { UserRequested } from "../../components/UserRequested";
|
||||
import { RootTabParamList } from "../../navigation/BottomTabs"; // Adjust import path
|
||||
import { RootStackParamList } from "../../navigation/AppNavigator";
|
||||
|
||||
// --- TYPE DEFINITIONS ---
|
||||
type GroupManagementNavProp = StackNavigationProp<RootTabParamList, 'Management'>;
|
||||
type GroupManagementNavProp = StackNavigationProp<RootStackParamList, 'ManagementTabs'>;
|
||||
type ManagementRouteProp = RouteProp<RootTabParamList, 'Management'>;
|
||||
type ActiveTab = "My Groups" | "Joined Groups" | "Requests" | "Your Requests";
|
||||
|
||||
|
||||
+171
-122
@@ -1,61 +1,91 @@
|
||||
import React, { useEffect, useState, useCallback } from 'react';
|
||||
import { View, Text, StyleSheet, ActivityIndicator, ScrollView, RefreshControl, Alert, TouchableOpacity } from 'react-native';
|
||||
import { useRoute, useNavigation } from '@react-navigation/native';
|
||||
import * as Keychain from 'react-native-keychain';
|
||||
import { useRoute, useNavigation, RouteProp } from '@react-navigation/native';
|
||||
import { StackNavigationProp } from '@react-navigation/stack';
|
||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||
import colors from '../../components/colors'; // Assuming you have a colors file
|
||||
import colors from '../../components/colors';
|
||||
import AppView from '../../components/AppView';
|
||||
import { apiCall } from '../../utils/ApiHelper';
|
||||
|
||||
// --- TYPE DEFINITIONS ---
|
||||
|
||||
// Generic API Response Wrapper
|
||||
interface ApiResponse<T> {
|
||||
status: 'success' | 'error';
|
||||
data: T;
|
||||
message?: string;
|
||||
statusCode: number;
|
||||
status: 'success' | 'error';
|
||||
data: T;
|
||||
message?: string;
|
||||
statusCode: number;
|
||||
}
|
||||
|
||||
// Define the shape of your payment details data
|
||||
interface PaymentDetails {
|
||||
// Add properties based on your API response, for example:
|
||||
amount: number;
|
||||
currency: string;
|
||||
dueDate: string;
|
||||
paidBy: string[];
|
||||
// Type for an individual participant in the payment details
|
||||
interface Participant {
|
||||
userId: string;
|
||||
userName: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
mobile: string;
|
||||
contributionAmount: number;
|
||||
paidAmount: number;
|
||||
status: 'SUCCESS' | 'PENDING';
|
||||
lateFee: number;
|
||||
daysLate: number;
|
||||
lateFeeWaived: boolean;
|
||||
}
|
||||
const Payments = () => {
|
||||
const route = useRoute<any>();
|
||||
const navigation = useNavigation<any>();
|
||||
|
||||
// Fully typed payment details based on your JSON response
|
||||
interface PaymentDetails {
|
||||
groupId: string;
|
||||
groupName: string;
|
||||
currentCycle: number;
|
||||
cycleType: string;
|
||||
contributionAmount: number;
|
||||
totalContributedAmount: number;
|
||||
lateFeeMode: string;
|
||||
lateFeeValue: number;
|
||||
notReceivedAmount: number;
|
||||
totalLateFee: number;
|
||||
totalDueWithLateFee: number;
|
||||
participants: Participant[];
|
||||
}
|
||||
|
||||
// Assuming your navigation stack params look something like this
|
||||
type PaymentStackParamList = {
|
||||
Payments: { groupId: string };
|
||||
};
|
||||
|
||||
type PaymentsRouteProp = RouteProp<PaymentStackParamList, 'Payments'>;
|
||||
type PaymentsNavigationProp = StackNavigationProp<PaymentStackParamList>;
|
||||
|
||||
const Payments: React.FC = () => {
|
||||
const route = useRoute<PaymentsRouteProp>();
|
||||
const navigation = useNavigation<PaymentsNavigationProp>();
|
||||
const { groupId } = route.params;
|
||||
|
||||
const [paymentDetails, setPaymentDetails] = useState<any>(null);
|
||||
const [paymentDetails, setPaymentDetails] = useState<PaymentDetails | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
|
||||
const fetchPaymentDetails = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
// The apiCall function likely handles tokens internally, so we call it directly.
|
||||
const response = await apiCall(
|
||||
`https://exotic-humpback-mistakenly.ngrok-free.app/api/payments/current/payment/details/${groupId}`,
|
||||
'GET'
|
||||
) as ApiResponse<PaymentDetails>; // Cast the response to the correct type
|
||||
const fetchPaymentDetails = useCallback(async () => {
|
||||
if (!refreshing) setLoading(true);
|
||||
try {
|
||||
const response = await apiCall(
|
||||
`https://exotic-humpback-mistakenly.ngrok-free.app/api/payments/current/payment/details/${groupId}`,
|
||||
'GET'
|
||||
) as ApiResponse<PaymentDetails>;
|
||||
|
||||
console.log("Payment Details Response:", response);
|
||||
|
||||
// Check the response and status property for success
|
||||
if (response && response.statusCode === 200) {
|
||||
setPaymentDetails(response.data);
|
||||
} else {
|
||||
// Use the message from the response object
|
||||
Alert.alert("Error", response?.message || "Failed to fetch payment details.");
|
||||
if (response && response.statusCode === 200) {
|
||||
setPaymentDetails(response.data);
|
||||
} else {
|
||||
Alert.alert("Error", response?.message || "Failed to fetch payment details.");
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Fetch payment details error:", error);
|
||||
Alert.alert("Error", error.message || "An unexpected error occurred.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error("Fetch payment details error:", error);
|
||||
Alert.alert("Error", error.message || "An unexpected error occurred while fetching payment details.");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setRefreshing(false);
|
||||
}
|
||||
}, [groupId]);
|
||||
}, [groupId, refreshing]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPaymentDetails();
|
||||
@@ -63,35 +93,41 @@ const fetchPaymentDetails = useCallback(async () => {
|
||||
|
||||
const onRefresh = () => {
|
||||
setRefreshing(true);
|
||||
fetchPaymentDetails();
|
||||
};
|
||||
|
||||
const renderParticipant = (participant: any) => (
|
||||
<View key={participant.userId} style={styles.participantCard}>
|
||||
<View style={styles.participantInfo}>
|
||||
<Text style={styles.participantName}>{participant.firstName} {participant.lastName}</Text>
|
||||
<Text style={styles.participantMobile}>{participant.mobile}</Text>
|
||||
</View>
|
||||
<View style={styles.paymentStatus}>
|
||||
<Text style={[
|
||||
styles.statusText,
|
||||
{ color: participant.status === 'PENDING' ? colors.warning : colors.success }
|
||||
]}>
|
||||
{participant.status}
|
||||
</Text>
|
||||
<Text style={styles.amountText}>
|
||||
₹{participant.paidAmount} / ₹{participant.contributionAmount}
|
||||
</Text>
|
||||
{/* {participant.status === 'PENDING' && (
|
||||
<TouchableOpacity style={styles.payButton}>
|
||||
<Text style={styles.payButtonText}>Pay Now</Text>
|
||||
</TouchableOpacity>
|
||||
)} */}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
// --- ENHANCED: renderParticipant now shows late fee details ---
|
||||
const renderParticipant = (participant: Participant) => {
|
||||
const isPending = participant.status === 'PENDING';
|
||||
return (
|
||||
<View key={participant.userId} style={styles.participantCard}>
|
||||
<View style={styles.participantInfo}>
|
||||
<Text style={styles.participantName}>{participant.firstName} {participant.lastName}</Text>
|
||||
<Text style={styles.participantMobile}>{participant.mobile}</Text>
|
||||
</View>
|
||||
<View style={styles.paymentStatus}>
|
||||
<Text style={[
|
||||
styles.statusText,
|
||||
{ color: isPending ? '#e0d626ff' : '#18b107ff' }
|
||||
]}>
|
||||
{participant.status}
|
||||
</Text>
|
||||
<Text style={styles.amountText}>
|
||||
{isPending ?`₹${participant.payAmount}`:`₹${participant.paidAmount}`}
|
||||
</Text>
|
||||
|
||||
if (loading) {
|
||||
{/* --- NEW: Conditionally render late payment stats --- */}
|
||||
{isPending && participant.daysLate > 0 && (
|
||||
<View style={styles.lateFeeContainer}>
|
||||
<Text style={styles.lateFeeText}>Late Fee: ₹{participant.lateFee}</Text>
|
||||
<Text style={styles.lateFeeText}>Days Late: {participant.daysLate}</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
if (loading && !refreshing) {
|
||||
return <ActivityIndicator style={styles.centered} size="large" color={colors.primary} />;
|
||||
}
|
||||
|
||||
@@ -107,34 +143,34 @@ const fetchPaymentDetails = useCallback(async () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<AppView>
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
|
||||
>
|
||||
<View style={styles.header}>
|
||||
<Text style={styles.headerTitle}>{paymentDetails.groupName}</Text>
|
||||
</View>
|
||||
<AppView paddingHorizontal={0}>
|
||||
<ScrollView
|
||||
style={styles.container}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} colors={[colors.primary]} tintColor={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}>Current Cycle</Text>
|
||||
<Text style={styles.summaryValue}>{paymentDetails.currentCycle}</Text>
|
||||
<View style={styles.summaryContainer}>
|
||||
<View style={styles.summaryBox}>
|
||||
<Text style={styles.summaryLabel}>Current 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}>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>
|
||||
|
||||
<Text style={styles.sectionTitle}>Participants</Text>
|
||||
{paymentDetails.participants.map(renderParticipant)}
|
||||
</ScrollView>
|
||||
</AppView>
|
||||
<Text style={styles.sectionTitle}>Participants</Text>
|
||||
{paymentDetails.participants.map(renderParticipant)}
|
||||
</ScrollView>
|
||||
</AppView>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -143,25 +179,19 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
backgroundColor: '#f4f6f8',
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
backgroundColor: '#f4f6f8',
|
||||
},
|
||||
header: {
|
||||
|
||||
padding: 20,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
borderBottomLeftRadius: 20,
|
||||
borderBottomRightRadius: 20,
|
||||
},
|
||||
backButton: {
|
||||
marginRight: 15,
|
||||
paddingBottom: 15,
|
||||
},
|
||||
headerTitle: {
|
||||
color: '#000000ff',
|
||||
fontSize: 22,
|
||||
color: '#000000',
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
summaryContainer: {
|
||||
@@ -169,26 +199,33 @@ const styles = StyleSheet.create({
|
||||
justifyContent: 'space-around',
|
||||
padding: 20,
|
||||
backgroundColor: '#fff',
|
||||
margin: 15,
|
||||
borderRadius: 10,
|
||||
marginHorizontal: 15,
|
||||
borderRadius: 12,
|
||||
elevation: 2,
|
||||
shadowColor: '#000',
|
||||
shadowOpacity: 0.05,
|
||||
shadowRadius: 10,
|
||||
},
|
||||
summaryBox: {
|
||||
alignItems: 'center',
|
||||
},
|
||||
summaryLabel: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
color: '#6c757d',
|
||||
marginBottom: 4,
|
||||
},
|
||||
summaryValue: {
|
||||
fontSize: 18,
|
||||
fontWeight: 'bold',
|
||||
fontWeight: '700',
|
||||
color: colors.primary,
|
||||
},
|
||||
sectionTitle: {
|
||||
fontSize: 18,
|
||||
fontSize: 20,
|
||||
fontWeight: 'bold',
|
||||
marginLeft: 20,
|
||||
marginTop: 20,
|
||||
marginBottom: 10,
|
||||
color: '#343a40',
|
||||
},
|
||||
participantCard: {
|
||||
backgroundColor: '#fff',
|
||||
@@ -199,17 +236,20 @@ const styles = StyleSheet.create({
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
elevation: 1,
|
||||
},
|
||||
participantInfo: {
|
||||
flex: 1,
|
||||
},
|
||||
participantName: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
fontWeight: '600',
|
||||
color: '#212529',
|
||||
},
|
||||
participantMobile: {
|
||||
fontSize: 14,
|
||||
color: '#888',
|
||||
color: '#6c757d',
|
||||
marginTop: 2,
|
||||
},
|
||||
paymentStatus: {
|
||||
alignItems: 'flex-end',
|
||||
@@ -217,28 +257,37 @@ const styles = StyleSheet.create({
|
||||
statusText: {
|
||||
fontSize: 14,
|
||||
fontWeight: 'bold',
|
||||
textTransform: 'uppercase',
|
||||
},
|
||||
amountText: {
|
||||
fontSize: 14,
|
||||
color: '#495057',
|
||||
marginTop: 2,
|
||||
},
|
||||
payButton: {
|
||||
marginTop: 5,
|
||||
backgroundColor: colors.primary,
|
||||
paddingVertical: 5,
|
||||
paddingHorizontal: 10,
|
||||
borderRadius: 5,
|
||||
// --- NEW STYLES ---
|
||||
lateFeeContainer: {
|
||||
marginTop: 8,
|
||||
paddingTop: 5,
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#e9ecef',
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
payButtonText: {
|
||||
color: '#fff',
|
||||
fontSize: 12,
|
||||
lateFeeText: {
|
||||
fontSize: 13,
|
||||
color: colors.danger,
|
||||
fontWeight: '600',
|
||||
},
|
||||
retryButton: {
|
||||
marginTop: 10,
|
||||
padding: 10,
|
||||
marginTop: 20,
|
||||
paddingVertical: 10,
|
||||
paddingHorizontal: 20,
|
||||
borderRadius: 8,
|
||||
backgroundColor: colors.primary,
|
||||
},
|
||||
retryButtonText: {
|
||||
color: colors.primary,
|
||||
color: '#fff',
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
StyleSheet,
|
||||
FlatList,
|
||||
RefreshControl,
|
||||
TouchableOpacity,
|
||||
SafeAreaView,
|
||||
} from "react-native";
|
||||
import { apiCall } from "../../utils/ApiHelper";
|
||||
import { TRANSACTION } from "../../utils/ApiUrls";
|
||||
import colors from "../../components/colors";
|
||||
import AppView from "../../components/AppView";
|
||||
import Header from "../../components/common/Header";
|
||||
|
||||
type Transaction = {
|
||||
id: string;
|
||||
fundGroupId: string;
|
||||
fundGroupName: string;
|
||||
amount: number;
|
||||
status: string;
|
||||
payerMobile?: string;
|
||||
transactionDate: number[];
|
||||
};
|
||||
|
||||
type ApiResponse = {
|
||||
message: string;
|
||||
status: string;
|
||||
statusCode: number;
|
||||
data: Transaction[];
|
||||
};
|
||||
|
||||
// Helper: convert the array-style date into a JS Date
|
||||
const parseTransactionDate = (arr: number[] | undefined): Date | null => {
|
||||
if (!arr || arr.length < 6) return null;
|
||||
const [year, month, day, hour = 0, minute = 0, second = 0, sub = 0] = arr;
|
||||
|
||||
let milli = 0;
|
||||
if (sub > 999999999) {
|
||||
milli = Math.floor(sub / 1e6);
|
||||
} else if (sub > 1000) {
|
||||
milli = Math.floor(sub / 1e6);
|
||||
} else {
|
||||
milli = sub;
|
||||
}
|
||||
|
||||
return new Date(year, month - 1, day, hour, minute, second, milli);
|
||||
};
|
||||
|
||||
const formatINR = (amount: number) => {
|
||||
try {
|
||||
return new Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" }).format(amount);
|
||||
} catch (e) {
|
||||
return `₹${amount.toFixed(2)}`;
|
||||
}
|
||||
};
|
||||
|
||||
const StatusBadge: React.FC<{ status?: string }> = ({ status }) => {
|
||||
const text = status ?? "UNKNOWN";
|
||||
const color =
|
||||
text === "SUCCESS" ? styles.statusSuccess : text === "PENDING" ? styles.statusPending : styles.statusFailed;
|
||||
|
||||
return (
|
||||
<View style={[styles.statusBadge, color]}>
|
||||
<Text style={styles.statusText}>{text}</Text>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const TransactionItem: React.FC<{ item: Transaction }> = ({ item }) => {
|
||||
const date = useMemo(() => parseTransactionDate(item.transactionDate), [item.transactionDate]);
|
||||
const dateStr = date ? date.toLocaleString("en-IN") : "-";
|
||||
|
||||
return (
|
||||
<TouchableOpacity activeOpacity={0.8} style={styles.card}>
|
||||
<View style={styles.cardLeft}>
|
||||
<Text style={styles.fundName}>{item.fundGroupName}</Text>
|
||||
<Text style={styles.payer}>Payer: {item.payerMobile ?? "-"}</Text>
|
||||
<Text style={styles.date}>{dateStr}</Text>
|
||||
</View>
|
||||
|
||||
<View style={styles.cardRight}>
|
||||
<Text style={styles.amount}>{formatINR(item.amount)}</Text>
|
||||
<StatusBadge status={item.status} />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
};
|
||||
|
||||
const TransactionsList: React.FC = () => {
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [transactions, setTransactions] = useState<Transaction[]>([]);
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
const handleTransactionList = async () => {
|
||||
try {
|
||||
const res = (await apiCall(TRANSACTION, "GET")) as ApiResponse;
|
||||
console.log("transaction list response : ", res);
|
||||
if (res.statusCode === 200) {
|
||||
setTransactions(res.data);
|
||||
setMessage(res.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error fetching transactions:", error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
handleTransactionList();
|
||||
}, []);
|
||||
|
||||
const handleRefresh = useCallback(async () => {
|
||||
try {
|
||||
setRefreshing(true);
|
||||
await handleTransactionList();
|
||||
} catch (e) {
|
||||
console.warn("Refresh failed", e);
|
||||
} finally {
|
||||
setRefreshing(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderEmpty = () => (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={styles.emptyTitle}>No transactions</Text>
|
||||
<Text style={styles.emptySubtitle}>Transactions will appear here when available.</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<AppView>
|
||||
<Header title="transactions" titleColor={colors.Home} arrowColor={colors.Home}/>
|
||||
|
||||
<Text style={styles.headerTitle}>{message}</Text>
|
||||
|
||||
|
||||
<FlatList
|
||||
data={transactions}
|
||||
keyExtractor={(item) => item.id}
|
||||
renderItem={({ item }) => <TransactionItem item={item} />}
|
||||
ListEmptyComponent={renderEmpty}
|
||||
contentContainerStyle={transactions.length === 0 ? { flex: 1 } : undefined}
|
||||
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={handleRefresh} />}
|
||||
/>
|
||||
</AppView>
|
||||
);
|
||||
};
|
||||
|
||||
export default TransactionsList;
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flex: 1, backgroundColor: "#f7f7fb" },
|
||||
headerTitle: { fontSize: 18, fontWeight: "500", color: colors.primary },
|
||||
headerSubtitle: { marginTop: 4, color: "#6b7280" },
|
||||
|
||||
card: {
|
||||
flexDirection: "row",
|
||||
justifyContent: "space-between",
|
||||
padding: 16,
|
||||
// marginHorizontal: 12,
|
||||
marginVertical: 8,
|
||||
backgroundColor: "#fff",
|
||||
borderRadius: 12,
|
||||
shadowColor: "#000",
|
||||
shadowOpacity: 0.03,
|
||||
shadowRadius: 8,
|
||||
elevation: 2,
|
||||
},
|
||||
|
||||
cardLeft: { flex: 1 },
|
||||
fundName: { fontSize: 16, fontWeight: "600" },
|
||||
payer: { marginTop: 6, color: "#6b7280" },
|
||||
date: { marginTop: 6, color: "#9ca3af" },
|
||||
|
||||
cardRight: { alignItems: "flex-end", justifyContent: "space-between" },
|
||||
amount: { fontSize: 16, fontWeight: "700" },
|
||||
|
||||
statusBadge: {
|
||||
marginTop: 8,
|
||||
paddingHorizontal: 8,
|
||||
paddingVertical: 4,
|
||||
borderRadius: 999,
|
||||
},
|
||||
statusText: { fontSize: 12, fontWeight: "600", color: "#fff" },
|
||||
statusSuccess: { backgroundColor: "#10b981" },
|
||||
statusPending: { backgroundColor: "#f59e0b" },
|
||||
statusFailed: { backgroundColor: "#ef4444" },
|
||||
|
||||
emptyContainer: { alignItems: "center", justifyContent: "center", },
|
||||
emptyTitle: { fontSize: 18, fontWeight: "600", color: "#374151" },
|
||||
emptySubtitle: { marginTop: 8, color: "#6b7280" },
|
||||
});
|
||||
Reference in New Issue
Block a user