Files
boc/iom/quixzoom-app/src/components/StatsView.js
T
Bernt bae705aa97 ARCHITECTURE: NFC roadmap, edge AI, audit logging
- Add NFC ePassport roadmap (ICAO 9303, eIDAS)
- Add TensorFlow.js edge face detection (BlazeFace)
- Add structured audit logger (GDPR-compliant)
- Risk scoring support

Part of KYC Apple Native UX v1.1.0
2026-06-29 16:24:48 +00:00

369 lines
9.8 KiB
JavaScript

/**
* StatsView Component
* Zoomer statistics and earnings dashboard
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
Animated,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
export default function StatsView({ stats }) {
const fadeAnim = new Animated.Value(0);
React.useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 600,
useNativeDriver: true,
}).start();
}, []);
const renderStatCard = (icon, title, value, subtitle, color) => (
<View style={[styles.statCard, { borderLeftColor: color }]}>
<View style={styles.statIconContainer}>
<Icon name={icon} size={24} color={color} />
</View>
<View style={styles.statContent}>
<Text style={styles.statValue}>{value}</Text>
<Text style={styles.statTitle}>{title}</Text>
{subtitle && <Text style={styles.statSubtitle}>{subtitle}</Text>}
</View>
</View>
);
const renderLevelProgress = () => {
const levels = ['Supplementary', 'Active', 'Professional'];
const currentLevelIndex = levels.indexOf(stats.level);
const nextLevel = levels[currentLevelIndex + 1];
const progress = stats.level_progress || 0;
return (
<View style={styles.levelCard}>
<View style={styles.levelHeader}>
<Text style={styles.levelTitle}>Zoomer Level</Text>
<View style={[styles.levelBadge, { backgroundColor: getLevelColor(stats.level) }]}>
<Text style={styles.levelBadgeText}>{stats.level}</Text>
</View>
</View>
<View style={styles.levelProgressContainer}>
<View style={styles.levelProgressBar}>
<View style={[styles.levelProgressFill, { width: `${progress}%` }]} />
</View>
<Text style={styles.levelProgressText}>{progress}% to {nextLevel || 'Max'}</Text>
</View>
<View style={styles.levelRequirements}>
<Text style={styles.levelRequirementsTitle}>Requirements for {nextLevel}:</Text>
{getLevelRequirements(nextLevel).map((req, index) => (
<View key={index} style={styles.requirementRow}>
<Icon
name={req.completed ? 'check-circle' : 'radio-button-unchecked'}
size={16}
color={req.completed ? '#34C759' : '#8E8E93'}
/>
<Text style={[styles.requirementText, req.completed && styles.requirementCompleted]}>
{req.text}
</Text>
</View>
))}
</View>
</View>
);
};
const getLevelColor = (level) => {
const colors = {
'Supplementary': '#8E8E93',
'Active': '#007AFF',
'Professional': '#34C759',
};
return colors[level] || '#8E8E93';
};
const getLevelRequirements = (level) => {
const requirements = {
'Active': [
{ text: 'Complete 10 missions', completed: (stats.completed_missions || 0) >= 10 },
{ text: 'Earn $500 total', completed: (stats.total_earnings || 0) >= 500 },
{ text: '95% approval rate', completed: (stats.approval_rate || 0) >= 95 },
],
'Professional': [
{ text: 'Complete 50 missions', completed: (stats.completed_missions || 0) >= 50 },
{ text: 'Earn $5,000 total', completed: (stats.total_earnings || 0) >= 5000 },
{ text: '98% approval rate', completed: (stats.approval_rate || 0) >= 98 },
{ text: '5+ countries', completed: (stats.countries || 0) >= 5 },
],
};
return requirements[level] || [];
};
return (
<Animated.View style={[styles.container, { opacity: fadeAnim }]}>
<ScrollView style={styles.scrollView}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerTitle}>Your Stats</Text>
<Text style={styles.headerSubtitle}>Last 30 days</Text>
</View>
{/* Earnings Card */}
<View style={styles.earningsCard}>
<Text style={styles.earningsLabel}>Total Earnings</Text>
<Text style={styles.earningsValue}>${(stats.total_earnings || 0).toFixed(2)}</Text>
<View style={styles.earningsTrend}>
<Icon name="trending-up" size={16} color="#34C759" />
<Text style={styles.earningsTrendText}>+{(stats.earnings_growth || 0).toFixed(1)}% this month</Text>
</View>
</View>
{/* Stats Grid */}
<View style={styles.statsGrid}>
{renderStatCard('camera-alt', 'Observations', stats.total_observations || 0, `${stats.approved_observations || 0} approved`, '#007AFF')}
{renderStatCard('check-circle', 'Missions', stats.completed_missions || 0, `${stats.active_missions || 0} active`, '#34C759')}
{renderStatCard('star', 'Rating', `${stats.rating || 0}/5.0`, `${stats.reviews || 0} reviews`, '#FF9500')}
{renderStatCard('schedule', 'Response Time', `${stats.avg_response_time || 0}h`, 'Average', '#5856D6')}
</View>
{/* Level Progress */}
{renderLevelProgress()}
{/* Recent Activity */}
<View style={styles.activityCard}>
<Text style={styles.activityTitle}>Recent Activity</Text>
{(stats.recent_activity || []).map((activity, index) => (
<View key={index} style={styles.activityRow}>
<View style={[styles.activityIcon, { backgroundColor: activity.color || '#007AFF20' }]}>
<Icon name={activity.icon || 'circle'} size={16} color={activity.color || '#007AFF'} />
</View>
<View style={styles.activityContent}>
<Text style={styles.activityText}>{activity.text}</Text>
<Text style={styles.activityTime}>{activity.time}</Text>
</View>
{activity.amount && (
<Text style={[styles.activityAmount, { color: activity.amount > 0 ? '#34C759' : '#FF3B30' }]}>
{activity.amount > 0 ? '+' : ''}${Math.abs(activity.amount).toFixed(2)}
</Text>
)}
</View>
))}
</View>
</ScrollView>
</Animated.View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
scrollView: {
flex: 1,
padding: 16,
},
header: {
marginBottom: 20,
},
headerTitle: {
fontSize: 28,
fontWeight: '700',
color: '#fff',
},
headerSubtitle: {
fontSize: 14,
color: '#8E8E93',
marginTop: 4,
},
earningsCard: {
backgroundColor: '#1C1C1E',
borderRadius: 16,
padding: 20,
marginBottom: 16,
alignItems: 'center',
},
earningsLabel: {
fontSize: 14,
color: '#8E8E93',
marginBottom: 8,
},
earningsValue: {
fontSize: 48,
fontWeight: '700',
color: '#34C759',
},
earningsTrend: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 8,
},
earningsTrendText: {
fontSize: 14,
color: '#34C759',
marginLeft: 4,
},
statsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginBottom: 16,
},
statCard: {
width: '48%',
backgroundColor: '#1C1C1E',
borderRadius: 12,
padding: 16,
marginBottom: 12,
borderLeftWidth: 3,
flexDirection: 'row',
alignItems: 'center',
},
statIconContainer: {
width: 40,
height: 40,
borderRadius: 10,
backgroundColor: 'rgba(255,255,255,0.05)',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
statContent: {
flex: 1,
},
statValue: {
fontSize: 24,
fontWeight: '700',
color: '#fff',
},
statTitle: {
fontSize: 12,
color: '#8E8E93',
marginTop: 2,
},
statSubtitle: {
fontSize: 11,
color: '#34C759',
marginTop: 2,
},
levelCard: {
backgroundColor: '#1C1C1E',
borderRadius: 16,
padding: 20,
marginBottom: 16,
},
levelHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
levelTitle: {
fontSize: 16,
fontWeight: '600',
color: '#fff',
},
levelBadge: {
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 8,
},
levelBadgeText: {
color: '#fff',
fontSize: 12,
fontWeight: '700',
},
levelProgressContainer: {
marginBottom: 16,
},
levelProgressBar: {
height: 8,
backgroundColor: '#2C2C2E',
borderRadius: 4,
marginBottom: 8,
},
levelProgressFill: {
height: '100%',
backgroundColor: '#34C759',
borderRadius: 4,
},
levelProgressText: {
fontSize: 12,
color: '#8E8E93',
textAlign: 'right',
},
levelRequirements: {
borderTopWidth: 1,
borderTopColor: '#2C2C2E',
paddingTop: 12,
},
levelRequirementsTitle: {
fontSize: 14,
fontWeight: '600',
color: '#fff',
marginBottom: 8,
},
requirementRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
requirementText: {
fontSize: 13,
color: '#8E8E93',
marginLeft: 8,
},
requirementCompleted: {
color: '#34C759',
textDecorationLine: 'line-through',
},
activityCard: {
backgroundColor: '#1C1C1E',
borderRadius: 16,
padding: 20,
marginBottom: 16,
},
activityTitle: {
fontSize: 16,
fontWeight: '600',
color: '#fff',
marginBottom: 12,
},
activityRow: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: '#2C2C2E',
},
activityIcon: {
width: 32,
height: 32,
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
activityContent: {
flex: 1,
},
activityText: {
fontSize: 14,
color: '#fff',
},
activityTime: {
fontSize: 12,
color: '#8E8E93',
marginTop: 2,
},
activityAmount: {
fontSize: 14,
fontWeight: '600',
},
});