Files
boc/iom/quixzoom-app/src/components/MissionCard.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

261 lines
6.2 KiB
JavaScript

/**
* MissionCard Component
* Displays available missions with reward and progress
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Animated,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
export default function MissionCard({ mission, onPress, isActive }) {
const scaleAnim = new Animated.Value(1);
const handlePressIn = () => {
Animated.spring(scaleAnim, {
toValue: 0.97,
useNativeDriver: true,
}).start();
};
const handlePressOut = () => {
Animated.spring(scaleAnim, {
toValue: 1,
useNativeDriver: true,
}).start();
};
// Calculate progress
const progress = mission.completed_observations / mission.total_observations;
const progressPercent = Math.round(progress * 100);
// Get category icon
const getCategoryIcon = (category) => {
const icons = {
infrastructure: 'construction',
building: 'business',
road: 'directions-car',
bridge: 'terrain',
environment: 'nature',
safety: 'security',
default: 'assignment',
};
return icons[category] || icons.default;
};
// Get difficulty color
const getDifficultyColor = (difficulty) => {
const colors = {
easy: '#34C759',
medium: '#FF9500',
hard: '#FF3B30',
};
return colors[difficulty] || colors.medium;
};
return (
<Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
<TouchableOpacity
style={[styles.card, isActive && styles.activeCard]}
onPress={() => onPress(mission)}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
activeOpacity={0.9}
>
{/* Header */}
<View style={styles.header}>
<View style={styles.iconContainer}>
<Icon
name={getCategoryIcon(mission.category)}
size={24}
color="#007AFF"
/>
</View>
<View style={styles.headerText}>
<Text style={styles.title} numberOfLines={1}>
{mission.title}
</Text>
<Text style={styles.category}>{mission.category}</Text>
</View>
<View style={[styles.difficulty, { backgroundColor: getDifficultyColor(mission.difficulty) }]}>
<Text style={styles.difficultyText}>{mission.difficulty}</Text>
</View>
</View>
{/* Description */}
<Text style={styles.description} numberOfLines={2}>
{mission.description}
</Text>
{/* Progress Bar */}
<View style={styles.progressContainer}>
<View style={styles.progressBar}>
<View style={[styles.progressFill, { width: `${progressPercent}%` }]} />
</View>
<Text style={styles.progressText}>{progressPercent}%</Text>
</View>
{/* Footer */}
<View style={styles.footer}>
<View style={styles.footerItem}>
<Icon name="attach-money" size={16} color="#34C759" />
<Text style={styles.reward}>${mission.reward_usd}</Text>
</View>
<View style={styles.footerItem}>
<Icon name="location-on" size={16} color="#8E8E93" />
<Text style={styles.distance}>
{mission.distance ? `${mission.distance.toFixed(1)} km` : 'Remote'}
</Text>
</View>
<View style={styles.footerItem}>
<Icon name="schedule" size={16} color="#8E8E93" />
<Text style={styles.deadline}>
{new Date(mission.deadline).toLocaleDateString()}
</Text>
</View>
</View>
{/* Active Indicator */}
{isActive && (
<View style={styles.activeIndicator}>
<Icon name="check-circle" size={20} color="#34C759" />
<Text style={styles.activeText}>Active</Text>
</View>
)}
</TouchableOpacity>
</Animated.View>
);
}
const styles = StyleSheet.create({
card: {
backgroundColor: '#1C1C1E',
borderRadius: 16,
padding: 16,
marginBottom: 12,
borderWidth: 1,
borderColor: '#2C2C2E',
},
activeCard: {
borderColor: '#34C759',
borderWidth: 2,
},
header: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
iconContainer: {
width: 44,
height: 44,
borderRadius: 12,
backgroundColor: 'rgba(0,122,255,0.1)',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
headerText: {
flex: 1,
},
title: {
fontSize: 16,
fontWeight: '600',
color: '#fff',
},
category: {
fontSize: 12,
color: '#8E8E93',
marginTop: 2,
textTransform: 'capitalize',
},
difficulty: {
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
},
difficultyText: {
color: '#fff',
fontSize: 10,
fontWeight: '700',
textTransform: 'uppercase',
},
description: {
fontSize: 14,
color: '#8E8E93',
marginBottom: 12,
lineHeight: 20,
},
progressContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
progressBar: {
flex: 1,
height: 6,
backgroundColor: '#2C2C2E',
borderRadius: 3,
marginRight: 8,
},
progressFill: {
height: '100%',
backgroundColor: '#34C759',
borderRadius: 3,
},
progressText: {
fontSize: 12,
color: '#8E8E93',
fontWeight: '600',
minWidth: 35,
},
footer: {
flexDirection: 'row',
justifyContent: 'space-between',
borderTopWidth: 1,
borderTopColor: '#2C2C2E',
paddingTop: 12,
},
footerItem: {
flexDirection: 'row',
alignItems: 'center',
},
reward: {
fontSize: 14,
fontWeight: '700',
color: '#34C759',
marginLeft: 4,
},
distance: {
fontSize: 12,
color: '#8E8E93',
marginLeft: 4,
},
deadline: {
fontSize: 12,
color: '#8E8E93',
marginLeft: 4,
},
activeIndicator: {
position: 'absolute',
top: 12,
right: 12,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(52,199,89,0.2)',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
},
activeText: {
color: '#34C759',
fontSize: 10,
fontWeight: '700',
marginLeft: 4,
},
});