Files
circle-collect/src/screens/transaction/TransactionList.tsx
T

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" },
});