Add transaction display feature to HomeScreen
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
||||
TouchableOpacity,
|
||||
ActivityIndicator,
|
||||
RefreshControl,
|
||||
FlatList,
|
||||
} from 'react-native';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
@@ -16,6 +17,89 @@ import { COLORS } from '../../constants/colors';
|
||||
import MetricCard from '../../components/MetricCard';
|
||||
import apiService from '../../services/apiService';
|
||||
|
||||
const STATIC_TRANSACTIONS = [
|
||||
{
|
||||
id: 1,
|
||||
type: 'debit',
|
||||
description: 'Canva Design and Publishing',
|
||||
date: '2025-09-07T10:30:00Z',
|
||||
amount: 10.0,
|
||||
currency: 'EUR',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'debit',
|
||||
description: 'Google',
|
||||
date: '2025-09-07T11:00:00Z',
|
||||
amount: 12.99,
|
||||
currency: 'SAR',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'debit',
|
||||
description: 'Canva Design and Publishing',
|
||||
date: '2025-09-05T14:15:00Z',
|
||||
amount: 10.0,
|
||||
currency: 'EUR',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'credit',
|
||||
description: 'Töleg alyňýan hasap',
|
||||
date: '2025-09-03T09:00:00Z',
|
||||
amount: 20.0,
|
||||
currency: 'USD',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'debit',
|
||||
description: 'Комиссия за оплату',
|
||||
date: '2025-09-03T09:01:00Z',
|
||||
amount: 0.12,
|
||||
currency: 'USD',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
type: 'credit',
|
||||
description: 'Hasaby doldurmak',
|
||||
date: '2025-09-02T18:45:00Z',
|
||||
amount: 10.0,
|
||||
currency: 'USD',
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
type: 'debit',
|
||||
description: 'Canva Design and Publishing',
|
||||
date: '2025-09-01T12:00:00Z',
|
||||
amount: 10.0,
|
||||
currency: 'EUR',
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
type: 'debit',
|
||||
description: 'Netflix',
|
||||
date: '2025-08-28T16:00:00Z',
|
||||
amount: 9.99,
|
||||
currency: 'USD',
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
type: 'credit',
|
||||
description: 'Bank Transfer',
|
||||
date: '2025-08-25T08:20:00Z',
|
||||
amount: 500.0,
|
||||
currency: 'TMT',
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
type: 'debit',
|
||||
description: 'Amazon',
|
||||
date: '2025-08-23T19:55:00Z',
|
||||
amount: 45.5,
|
||||
currency: 'USD',
|
||||
},
|
||||
];
|
||||
|
||||
const HomeScreen = () => {
|
||||
const { user, logout } = useAuth();
|
||||
const [metrics, setMetrics] = useState(null);
|
||||
@@ -25,6 +109,7 @@ const HomeScreen = () => {
|
||||
const [cardBalanceError, setCardBalanceError] = useState(null);
|
||||
const [isBalanceVisible, setIsBalanceVisible] = useState(true);
|
||||
const [refreshing, setRefreshing] = useState(false);
|
||||
const [transactions, setTransactions] = useState(STATIC_TRANSACTIONS);
|
||||
|
||||
const showBalanceCard = !loadingCardBalance && cardBalanceError === null && cardBalance !== null;
|
||||
|
||||
@@ -50,16 +135,18 @@ const HomeScreen = () => {
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchCardBalance = async () => {
|
||||
const fetchCardData = async () => {
|
||||
// Ensure user has filled all required card & passport fields
|
||||
if (!user?.passport_serie || !user?.passport_id || !user?.card_number || !user?.card_month || !user?.card_year) {
|
||||
setLoadingCardBalance(false);
|
||||
return;
|
||||
}
|
||||
|
||||
setLoadingCardBalance(true);
|
||||
|
||||
try {
|
||||
const res = await apiService.getCardBalanceQuickCheck();
|
||||
if (res.success) {
|
||||
// Try common balance keys else fallback to raw
|
||||
const raw = res.data;
|
||||
let balanceValue = null;
|
||||
if (raw && typeof raw === 'object') {
|
||||
@@ -73,12 +160,13 @@ const HomeScreen = () => {
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Error fetching card balance:', e);
|
||||
setCardBalanceError('Error fetching balance');
|
||||
} finally {
|
||||
setLoadingCardBalance(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchCardBalance();
|
||||
fetchCardData();
|
||||
}, [user]);
|
||||
|
||||
const handleRefresh = async () => {
|
||||
@@ -124,6 +212,57 @@ const HomeScreen = () => {
|
||||
setRefreshing(false);
|
||||
};
|
||||
|
||||
const groupedTransactions = transactions.reduce((acc, transaction) => {
|
||||
const date = new Date(transaction.date).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
});
|
||||
if (!acc[date]) {
|
||||
acc[date] = [];
|
||||
}
|
||||
acc[date].push(transaction);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const renderTransactionItem = ({ item }) => (
|
||||
<View style={styles.transactionItem}>
|
||||
<View style={styles.transactionIcon}>
|
||||
<Ionicons
|
||||
name={item.type === 'credit' ? 'arrow-down' : 'arrow-up'}
|
||||
size={20}
|
||||
color={item.type === 'credit' ? COLORS.success : COLORS.danger}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.transactionDetails}>
|
||||
<Text style={styles.transactionTitle}>{item.description}</Text>
|
||||
<Text style={styles.transactionDate}>{new Date(item.date).toLocaleTimeString()}</Text>
|
||||
</View>
|
||||
<Text
|
||||
style={[
|
||||
styles.transactionAmount,
|
||||
{ color: item.type === 'credit' ? COLORS.success : COLORS.textPrimary },
|
||||
]}
|
||||
>
|
||||
{item.type === 'credit' ? '+' : '-'}
|
||||
{item.amount} {item.currency}
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
|
||||
const renderTransactionSection = ({ item: date }) => (
|
||||
<View>
|
||||
<Text style={styles.transactionDateHeader}>{date}</Text>
|
||||
<FlatList
|
||||
data={groupedTransactions[date]}
|
||||
renderItem={renderTransactionItem}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
scrollEnabled={false}
|
||||
ItemSeparatorComponent={() => <View style={styles.separator} />}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<StatusBar style="dark" />
|
||||
@@ -149,7 +288,7 @@ const HomeScreen = () => {
|
||||
|
||||
{/* Metrics */}
|
||||
<View style={styles.section}>
|
||||
<Text style={styles.sectionTitle}>Metrics</Text>
|
||||
<Text style={styles.sectionTitle}>Görkezijiler</Text>
|
||||
{loadingMetrics ? (
|
||||
<ActivityIndicator color={COLORS.primary} style={{ marginTop: 16 }} />
|
||||
) : (
|
||||
@@ -181,6 +320,29 @@ const HomeScreen = () => {
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* Card Transactions */}
|
||||
{showBalanceCard && (
|
||||
<View style={styles.section}>
|
||||
<View style={styles.sectionHeader}>
|
||||
<Text style={styles.sectionTitle}>Transactions</Text>
|
||||
<TouchableOpacity>
|
||||
<Text style={styles.seeAllText}>See All</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{transactions.length > 0 ? (
|
||||
<FlatList
|
||||
data={Object.keys(groupedTransactions)}
|
||||
renderItem={renderTransactionSection}
|
||||
keyExtractor={(date) => date}
|
||||
scrollEnabled={false}
|
||||
ItemSeparatorComponent={() => <View style={styles.separator} />}
|
||||
/>
|
||||
) : (
|
||||
<Text style={{ textAlign: 'center', color: COLORS.textSecondary }}>No transactions yet.</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
@@ -311,20 +473,21 @@ const styles = StyleSheet.create({
|
||||
textAlign: 'center',
|
||||
},
|
||||
transactionsList: {
|
||||
gap: 16,
|
||||
marginTop: 16,
|
||||
},
|
||||
transactionItem: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 12,
|
||||
},
|
||||
transactionIcon: {
|
||||
width: 40,
|
||||
height: 40,
|
||||
borderRadius: 20,
|
||||
backgroundColor: COLORS.backgroundSecondary,
|
||||
width: 44,
|
||||
height: 44,
|
||||
borderRadius: 22,
|
||||
backgroundColor: COLORS.background,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
marginRight: 12,
|
||||
marginRight: 16,
|
||||
},
|
||||
transactionDetails: {
|
||||
flex: 1,
|
||||
@@ -333,34 +496,29 @@ const styles = StyleSheet.create({
|
||||
fontSize: 16,
|
||||
fontWeight: '600',
|
||||
color: COLORS.textPrimary,
|
||||
marginBottom: 2,
|
||||
},
|
||||
transactionDate: {
|
||||
fontSize: 14,
|
||||
fontSize: 13,
|
||||
color: COLORS.textSecondary,
|
||||
},
|
||||
transactionAmount: {
|
||||
fontSize: 16,
|
||||
fontWeight: 'bold',
|
||||
color: COLORS.success,
|
||||
fontWeight: '700',
|
||||
},
|
||||
servicesGrid: {
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'space-between',
|
||||
},
|
||||
serviceItem: {
|
||||
width: '48%',
|
||||
padding: 16,
|
||||
backgroundColor: COLORS.backgroundSecondary,
|
||||
borderRadius: 12,
|
||||
alignItems: 'center',
|
||||
marginBottom: 12,
|
||||
},
|
||||
serviceText: {
|
||||
fontSize: 12,
|
||||
transactionDateHeader: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: COLORS.textSecondary,
|
||||
textAlign: 'center',
|
||||
marginTop: 8,
|
||||
backgroundColor: COLORS.white,
|
||||
paddingTop: 16,
|
||||
paddingBottom: 8,
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
separator: {
|
||||
height: 1,
|
||||
backgroundColor: COLORS.gray[200],
|
||||
marginLeft: 60, // Align with transaction details, skipping the icon
|
||||
},
|
||||
metricsGrid: {
|
||||
flexDirection: 'row',
|
||||
|
||||
Reference in New Issue
Block a user