Add transaction display feature to HomeScreen
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
RefreshControl,
|
RefreshControl,
|
||||||
|
FlatList,
|
||||||
} from 'react-native';
|
} from 'react-native';
|
||||||
import { StatusBar } from 'expo-status-bar';
|
import { StatusBar } from 'expo-status-bar';
|
||||||
import { Ionicons } from '@expo/vector-icons';
|
import { Ionicons } from '@expo/vector-icons';
|
||||||
@@ -16,6 +17,89 @@ import { COLORS } from '../../constants/colors';
|
|||||||
import MetricCard from '../../components/MetricCard';
|
import MetricCard from '../../components/MetricCard';
|
||||||
import apiService from '../../services/apiService';
|
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 HomeScreen = () => {
|
||||||
const { user, logout } = useAuth();
|
const { user, logout } = useAuth();
|
||||||
const [metrics, setMetrics] = useState(null);
|
const [metrics, setMetrics] = useState(null);
|
||||||
@@ -25,6 +109,7 @@ const HomeScreen = () => {
|
|||||||
const [cardBalanceError, setCardBalanceError] = useState(null);
|
const [cardBalanceError, setCardBalanceError] = useState(null);
|
||||||
const [isBalanceVisible, setIsBalanceVisible] = useState(true);
|
const [isBalanceVisible, setIsBalanceVisible] = useState(true);
|
||||||
const [refreshing, setRefreshing] = useState(false);
|
const [refreshing, setRefreshing] = useState(false);
|
||||||
|
const [transactions, setTransactions] = useState(STATIC_TRANSACTIONS);
|
||||||
|
|
||||||
const showBalanceCard = !loadingCardBalance && cardBalanceError === null && cardBalance !== null;
|
const showBalanceCard = !loadingCardBalance && cardBalanceError === null && cardBalance !== null;
|
||||||
|
|
||||||
@@ -50,16 +135,18 @@ const HomeScreen = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchCardBalance = async () => {
|
const fetchCardData = async () => {
|
||||||
// Ensure user has filled all required card & passport fields
|
// 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) {
|
if (!user?.passport_serie || !user?.passport_id || !user?.card_number || !user?.card_month || !user?.card_year) {
|
||||||
setLoadingCardBalance(false);
|
setLoadingCardBalance(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setLoadingCardBalance(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await apiService.getCardBalanceQuickCheck();
|
const res = await apiService.getCardBalanceQuickCheck();
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
// Try common balance keys else fallback to raw
|
|
||||||
const raw = res.data;
|
const raw = res.data;
|
||||||
let balanceValue = null;
|
let balanceValue = null;
|
||||||
if (raw && typeof raw === 'object') {
|
if (raw && typeof raw === 'object') {
|
||||||
@@ -73,12 +160,13 @@ const HomeScreen = () => {
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('Error fetching card balance:', e);
|
console.warn('Error fetching card balance:', e);
|
||||||
|
setCardBalanceError('Error fetching balance');
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingCardBalance(false);
|
setLoadingCardBalance(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchCardBalance();
|
fetchCardData();
|
||||||
}, [user]);
|
}, [user]);
|
||||||
|
|
||||||
const handleRefresh = async () => {
|
const handleRefresh = async () => {
|
||||||
@@ -124,6 +212,57 @@ const HomeScreen = () => {
|
|||||||
setRefreshing(false);
|
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 (
|
return (
|
||||||
<SafeAreaView style={styles.container}>
|
<SafeAreaView style={styles.container}>
|
||||||
<StatusBar style="dark" />
|
<StatusBar style="dark" />
|
||||||
@@ -149,7 +288,7 @@ const HomeScreen = () => {
|
|||||||
|
|
||||||
{/* Metrics */}
|
{/* Metrics */}
|
||||||
<View style={styles.section}>
|
<View style={styles.section}>
|
||||||
<Text style={styles.sectionTitle}>Metrics</Text>
|
<Text style={styles.sectionTitle}>Görkezijiler</Text>
|
||||||
{loadingMetrics ? (
|
{loadingMetrics ? (
|
||||||
<ActivityIndicator color={COLORS.primary} style={{ marginTop: 16 }} />
|
<ActivityIndicator color={COLORS.primary} style={{ marginTop: 16 }} />
|
||||||
) : (
|
) : (
|
||||||
@@ -181,6 +320,29 @@ const HomeScreen = () => {
|
|||||||
</View>
|
</View>
|
||||||
</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>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
@@ -311,20 +473,21 @@ const styles = StyleSheet.create({
|
|||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
},
|
},
|
||||||
transactionsList: {
|
transactionsList: {
|
||||||
gap: 16,
|
marginTop: 16,
|
||||||
},
|
},
|
||||||
transactionItem: {
|
transactionItem: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
paddingVertical: 12,
|
||||||
},
|
},
|
||||||
transactionIcon: {
|
transactionIcon: {
|
||||||
width: 40,
|
width: 44,
|
||||||
height: 40,
|
height: 44,
|
||||||
borderRadius: 20,
|
borderRadius: 22,
|
||||||
backgroundColor: COLORS.backgroundSecondary,
|
backgroundColor: COLORS.background,
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginRight: 12,
|
marginRight: 16,
|
||||||
},
|
},
|
||||||
transactionDetails: {
|
transactionDetails: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -333,34 +496,29 @@ const styles = StyleSheet.create({
|
|||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: '600',
|
fontWeight: '600',
|
||||||
color: COLORS.textPrimary,
|
color: COLORS.textPrimary,
|
||||||
|
marginBottom: 2,
|
||||||
},
|
},
|
||||||
transactionDate: {
|
transactionDate: {
|
||||||
fontSize: 14,
|
fontSize: 13,
|
||||||
color: COLORS.textSecondary,
|
color: COLORS.textSecondary,
|
||||||
},
|
},
|
||||||
transactionAmount: {
|
transactionAmount: {
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: 'bold',
|
fontWeight: '700',
|
||||||
color: COLORS.success,
|
|
||||||
},
|
},
|
||||||
servicesGrid: {
|
transactionDateHeader: {
|
||||||
flexDirection: 'row',
|
fontSize: 14,
|
||||||
flexWrap: 'wrap',
|
fontWeight: '600',
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
serviceItem: {
|
|
||||||
width: '48%',
|
|
||||||
padding: 16,
|
|
||||||
backgroundColor: COLORS.backgroundSecondary,
|
|
||||||
borderRadius: 12,
|
|
||||||
alignItems: 'center',
|
|
||||||
marginBottom: 12,
|
|
||||||
},
|
|
||||||
serviceText: {
|
|
||||||
fontSize: 12,
|
|
||||||
color: COLORS.textSecondary,
|
color: COLORS.textSecondary,
|
||||||
textAlign: 'center',
|
backgroundColor: COLORS.white,
|
||||||
marginTop: 8,
|
paddingTop: 16,
|
||||||
|
paddingBottom: 8,
|
||||||
|
paddingHorizontal: 4,
|
||||||
|
},
|
||||||
|
separator: {
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: COLORS.gray[200],
|
||||||
|
marginLeft: 60, // Align with transaction details, skipping the icon
|
||||||
},
|
},
|
||||||
metricsGrid: {
|
metricsGrid: {
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
|
|||||||
Reference in New Issue
Block a user