193 lines
5.5 KiB
TypeScript
193 lines
5.5 KiB
TypeScript
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" },
|
|
}); |