← Tasks
[E2E-v2] CampaignPro Dashboard
completedcode_genP1
Description
Generate a React Native / Expo screen component for CampaignPro Mexico.
SCREEN: Dashboard — Campaign Overview
FRAMEWORK: Expo + React Native + TypeScript
THEME: Dark (#0f172a bg, #1e293b surface, #e11d48 accent, white text)
LANGUAGE: Spanish UI labels
Requirements:
- Welcome header with campaign name and days remaining badge
- Stats grid (2x2): Total Votantes, Visitas Hoy, % A Favor, Colonias Cubiertas
- Each stat card with icon, value, label, and trend arrow
- Quick actions row: 'Capturar', '
Assignee
-
Claimed By
Claude Sonnet
Created
22d ago
Completed
-
Quality
Overall
74%
Pass
PASS
Graded By
-
Graded At
-
Cost
Input Tokens
-
Output Tokens
-
Est. USD
-
Result
{
"arm": "Claude Sonnet",
"code": "import React, { useState, useCallback } from 'react';\nimport {\n View,\n Text,\n StyleSheet,\n ScrollView,\n RefreshControl,\n TouchableOpacity,\n SafeAreaView,\n} from 'react-native';\nimport { Ionicons } from '@expo/vector-icons';\n\ninterface StatCardProps {\n icon: keyof typeof Ionicons.glyphMap;\n value: string;\n label: string;\n trend: 'up' | 'down' | 'neutral';\n trendValue: string;\n}\n\ninterface ActivityItem {\n id: string;\n voterName: string;\n colonia: string;\n timestamp: string;\n status: 'favor' | 'contra' | 'indeciso';\n}\n\nconst StatCard: React.FC<StatCardProps> = ({ icon, value, label, trend, trendValue }) => {\n const trendColor = trend === 'up' ? '#10b981' : trend === 'down' ? '#ef4444' : '#6b7280';\n const trendIcon = trend === 'up' ? 'trending-up' : trend === 'down' ? 'trending-down' : 'remove';\n\n return (\n <View style={styles.statCard}>\n <View style={styles.statHeader}>\n <Ionicons name={icon} size={24} color=\"#e11d48\" />\n <View style={styles.trendContainer}>\n <Ionicons name={trendIcon} size={16} color={trendColor} />\n <Text style={[styles.trendText, { color: trendColor }]}>{trendValue}</Text>\n </View>\n </View>\n <Text style={styles.statValue}>{value}</Text>\n <Text style={styles.statLabel}>{label}</Text>\n </View>\n );\n};\n\nconst QuickAction: React.FC<{ icon: keyof typeof Ionicons.glyphMap; label: string; onPress: () => void }> = ({\n icon,\n label,\n onPress,\n}) => (\n <TouchableOpacity style={styles.quickAction} onPress={onPress}>\n <View style={styles.quickActionIcon}>\n <Ionicons name={icon} size={24} color=\"#e11d48\" />\n </View>\n <Text style={styles.quickActionLabel}>{label}</Text>\n </TouchableOpacity>\n);\n\nconst ActivityFeedItem: React.FC<{ item: ActivityItem }> = ({ item }) => {\n const statusColor = item.status === 'favor' ? '#10b981' : item.status === 'contra' ? '#ef4444' : '#f59e0b';\n const statusIcon = item.status === 'favor' ? 'thumbs-up' : item.status === 'contra' ? 'thumbs-down' : 'help';\n\n return (\n <View style={styles.activityItem}>\n <View style={styles.activityIcon}>\n <Ionicons name={statusIcon} size={16} color={statusColor} />\n </View>\n <View style={styles.activityContent}>\n <Text style={styles.activityVoter}>{item.voterName}</Text>\n <Text style={styles.activityColonia}>{item.colonia}</Text>\n </View>\n <Text style={styles.activityTime}>{item.timestamp}</Text>\n </View>\n );\n};\n\nexport default function DashboardScreen() {\n const [refreshing, setRefreshing] = useState(false);\n \n const campaignName = \"Campaña Municipal 2024\";\n const daysRemaining = 45;\n const progressPercentage = 68;\n\n const stats = [\n {\n icon: 'people' as const,\n value: '2,847',\n label: 'Total Votantes',\n trend: 'up' as const,\n trendValue: '+12%',\n },\n {\n icon: 'calendar' as const,\n value: '156',\n label: 'Visitas Hoy',\n trend: 'up' as const,\n trendValue: '+8%',\n },\n {\n icon: 'heart' as const,\n value: '64%',\n label: '% A Favor',\n trend: 'up' as const,\n trendValue: '+3%',\n },\n {\n icon: 'location' as const,\n value: '89',\n label: 'Colonias Cubiertas',\n trend: 'neutral' as const,\n trendValue: '0%',\n },\n ];\n\n const recentActivity: ActivityItem[] = [\n {\n id: '1',\n voterName: 'María González',\n colonia: 'Centro',\n timestamp: '10:30',\n status: 'favor',\n },\n {\n id: '2',\n voterName: 'Carlos Mendoza',\n colonia: 'Las Flores',\n timestamp: '10:15',\n status: 'indeciso',\n },\n {\n id: '3',\n voterName: 'Ana Rodríguez',\n colonia: 'San José',\n timestamp: '09:45',\n status: 'favor',\n },\n {\n id: '4',\n voterName: 'Roberto Silva',\n colonia: 'El Mirador',\n timestamp: '09:30',\n status: 'contra',\n },\n {\n id: '5',\n voterName: 'Lucia Morales',\n colonia: 'Jardines',\n timestamp: '09:15',\n status: 'favor',\n },\n ];\n\n const onRefresh = useCallback(() => {\n setRefreshing(true);\n setTimeout(() => {\n setRefreshing(false);\n }, 2000);\n }, []);\n\n const handleQuickAction = (action: string) => {\n console.log(`Quick action: ${action}`);\n };\n\n return (\n <SafeAreaView style={styles.container}>\n <ScrollView\n style={styles.scrollView}\n contentContainerStyle={styles.scrollContent}\n refreshControl={\n <RefreshControl\n refreshing={refreshing}\n onRefresh={onRefresh}\n tintColor=\"#e11d48\"\n colors={['#e11d48']}\n />\n }\n >\n {/* Welcome Header */}\n <View style={styles.header}>\n <View>\n <Text style={styles.welcomeText}>¡Bienvenido!</Text>\n <Text style={styles.campaignName}>{campaignName}</Text>\n </View>\n <View style={styles.daysBadge}>\n <Text style={styles.daysText}>{daysRemaining}</Text>\n <Text style={styles.daysLabel}>días</Text>\n </View>\n </View>\n\n {/* Stats Grid */}\n <View style={styles.statsGrid}>\n {stats.map((stat, index) => (\n <StatCard key={index} {...stat} />\n ))}\n </View>\n\n {/* Quick Actions */}\n <View style={styles.quickActions}>\n <Text style={styles.sectionTitle}>Acciones Rápidas</Text>\n <View style={styles.quickActionsRow}>\n <QuickAction\n icon=\"add-circle\"\n label=\"Capturar\"\n onPress={() => handleQuickAction('capture')}\n />\n <QuickAction\n icon=\"map\"\n label=\"Mapa\"\n onPress={() => handleQuickAction('map')}\n />\n <QuickAction\n icon=\"download\"\n label=\"Exportar\"\n onPress={() => handleQuickAction('export')}\n />\n </View>\n </View>\n\n {/* Recent Activity */}\n <View style={styles.activitySection}>\n <Text style={styles.sectionTitle}>Actividad Reciente</Text>\n <View style={styles.activityFeed}>\n {recentActivity.map((item) => (\n <ActivityFeedItem key={item.id} item={item} />\n ))}\n </View>\n </View>\n\n {/* Campaign Progress */}\n <View style={styles.progressSection}>\n <View style={styles.progressHeader}>\n <Text style={styles.progressTitle}>Progreso de Campaña</Text>\n <Text style={styles.progressPercent}>{progressPercentage}%</Text>\n </View>\n <View style={styles.progressBarContainer}>\n <View style={[styles.progressBar, { width: `${progressPercentage}%` }]} />\n </View>\n </View>\n </ScrollView>\n </SafeAreaView>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flex: 1,\n backgroundColor: '#0f172a',\n },\n scrollView: {\n flex: 1,\n },\n scrollContent: {\n padding: 16,\n },\n header: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: 24,\n },\n welcomeText: {\n fontSize: 16,\n color: '#94a3b8',\n marginBottom: 4,\n },\n campaignName: {\n fontSize: 24,\n fontWeight: 'bold',\n color: '#ffffff',\n },\n daysBadge: {\n backgroundColor: '#e11d48',\n borderRadius: 12,\n paddingHorizontal: 16,\n paddingVertical: 8,\n alignItems: 'center',\n },\n daysText: {\n fontSize: 20,\n fontWeight: 'bold',\n color: '#ffffff',\n },\n daysLabel: {\n fontSize: 12,\n color: '#ffffff',\n },\n statsGrid: {\n flexDirection: 'row',\n flexWrap: 'wrap',\n marginHorizontal: -8,\n marginBottom: 24,\n },\n statCard: {\n width: '50%',\n paddingHorizontal: 8,\n marginBottom: 16,\n },\n statHeader: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n backgroundColor: '#1e293b',\n borderRadius: 12,\n padding: 16,\n },\n trendContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n },\n trendText: {\n fontSize: 12,\n fontWeight: '600',\n marginLeft: 4,\n },\n statValue: {\n fontSize: 24,\n fontWeight: 'bold',\n color: '#ffffff',\n marginTop: 8,\n textAlign: 'center',\n },\n statLabel: {\n fontSize: 14,\n color: '#94a3b8',\n textAlign: 'center',\n marginTop: 4,\n },\n quickActions: {\n marginBottom: 24,\n },\n sectionTitle: {\n fontSize: 18,\n fontWeight: 'bold',\n color: '#ffffff',\n marginBottom: 16,\n },\n quickActionsRow: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n },\n quickAction: {\n flex: 1,\n alignItems: 'center',\n marginHorizontal: 8,\n },\n quickActionIcon: {\n width: 56,\n height: 56,\n backgroundColor: '#1e293b',\n borderRadius: 28,\n justifyContent: 'center',\n alignItems: 'center',\n marginBottom: 8,\n },\n quickActionLabel: {\n fontSize: 14,\n color: '#ffffff',\n textAlign: 'center',\n },\n activitySection: {\n marginBottom: 24,\n },\n activityFeed: {\n backgroundColor: '#1e293b',\n borderRadius: 12,\n padding: 16,\n },\n activityItem: {\n flexDirection: 'row',\n alignItems: 'center',\n marginBottom: 12,\n },\n activityIcon: {\n width: 32,\n height: 32,\n borderRadius: 16,\n backgroundColor: '#0f172a',\n justifyContent: 'center',\n alignItems: 'center',\n marginRight: 12,\n },\n activityContent: {\n flex: 1,\n },\n activityVoter: {\n fontSize: 16,\n color: '#ffffff',\n fontWeight: '500',\n },\n activityColonia: {\n fontSize: 14,\n color: '#94a3b8',\n },\n activityTime: {\n fontSize: 12,\n color: '#64748b',\n },\n progressSection: {\n marginBottom: 24,\n },\n progressHeader: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: 12,\n },\n progressTitle: {\n fontSize: 16,\n color: '#ffffff',\n fontWeight: '500',\n },\n progressPercent: {\n fontSize: 16,\n color: '#e11d48',\n fontWeight: 'bold',\n },\n progressBarContainer: {\n height: 8,\n backgroundColor: '#1e293b',\n borderRadius: 4,\n overflow: 'hidden',\n },\n progressBar: {\n height: '100%',\n backgroundColor: '#e11d48',\n borderRadius: 4,\n },\n});",
"content": "import React, { useState, useCallback } from 'react';\nimport {\n View,\n Text,\n StyleSheet,\n ScrollView,\n RefreshControl,\n TouchableOpacity,\n SafeAreaView,\n} from 'react-native';\nimport { Ionicons } from '@expo/vector-icons';\n\ninterface StatCardProps {\n icon: keyof typeof Ionicons.glyphMap;\n value: string;\n label: string;\n trend: 'up' | 'down' | 'neutral';\n trendValue: string;\n}\n\ninterface ActivityItem {\n id: string;\n voterName: string;\n colonia: string;\n timestamp: string;\n status: 'favor' | 'contra' | 'indeciso';\n}\n\nconst StatCard: React.FC<StatCardProps> = ({ icon, value, label, trend, trendValue }) => {\n const trendColor = trend === 'up' ? '#10b981' : trend === 'down' ? '#ef4444' : '#6b7280';\n const trendIcon = trend === 'up' ? 'trending-up' : trend === 'down' ? 'trending-down' : 'remove';\n\n return (\n <View style={styles.statCard}>\n <View style={styles.statHeader}>\n <Ionicons name={icon} size={24} color=\"#e11d48\" />\n <View style={styles.trendContainer}>\n <Ionicons name={trendIcon} size={16} color={trendColor} />\n <Text style={[styles.trendText, { color: trendColor }]}>{trendValue}</Text>\n </View>\n </View>\n <Text style={styles.statValue}>{value}</Text>\n <Text style={styles.statLabel}>{label}</Text>\n </View>\n );\n};\n\nconst QuickAction: React.FC<{ icon: keyof typeof Ionicons.glyphMap; label: string; onPress: () => void }> = ({\n icon,\n label,\n onPress,\n}) => (\n <TouchableOpacity style={styles.quickAction} onPress={onPress}>\n <View style={styles.quickActionIcon}>\n <Ionicons name={icon} size={24} color=\"#e11d48\" />\n </View>\n <Text style={styles.quickActionLabel}>{label}</Text>\n </TouchableOpacity>\n);\n\nconst ActivityFeedItem: React.FC<{ item: ActivityItem }> = ({ item }) => {\n const statusColor = item.status === 'favor' ? '#10b981' : item.status === 'contra' ? '#ef4444' : '#f59e0b';\n const statusIcon = item.status === 'favor' ? 'thumbs-up' : item.status === 'contra' ? 'thumbs-down' : 'help';\n\n return (\n <View style={styles.activityItem}>\n <View style={styles.activityIcon}>\n <Ionicons name={statusIcon} size={16} color={statusColor} />\n </View>\n <View style={styles.activityContent}>\n <Text style={styles.activityVoter}>{item.voterName}</Text>\n <Text style={styles.activityColonia}>{item.colonia}</Text>\n </View>\n <Text style={styles.activityTime}>{item.timestamp}</Text>\n </View>\n );\n};\n\nexport default function DashboardScreen() {\n const [refreshing, setRefreshing] = useState(false);\n \n const campaignName = \"Campaña Municipal 2024\";\n const daysRemaining = 45;\n const progressPercentage = 68;\n\n const stats = [\n {\n icon: 'people' as const,\n value: '2,847',\n label: 'Total Votantes',\n trend: 'up' as const,\n trendValue: '+12%',\n },\n {\n icon: 'calendar' as const,\n value: '156',\n label: 'Visitas Hoy',\n trend: 'up' as const,\n trendValue: '+8%',\n },\n {\n icon: 'heart' as const,\n value: '64%',\n label: '% A Favor',\n trend: 'up' as const,\n trendValue: '+3%',\n },\n {\n icon: 'location' as const,\n value: '89',\n label: 'Colonias Cubiertas',\n trend: 'neutral' as const,\n trendValue: '0%',\n },\n ];\n\n const recentActivity: ActivityItem[] = [\n {\n id: '1',\n voterName: 'María González',\n colonia: 'Centro',\n timestamp: '10:30',\n status: 'favor',\n },\n {\n id: '2',\n voterName: 'Carlos Mendoza',\n colonia: 'Las Flores',\n timestamp: '10:15',\n status: 'indeciso',\n },\n {\n id: '3',\n voterName: 'Ana Rodríguez',\n colonia: 'San José',\n timestamp: '09:45',\n status: 'favor',\n },\n {\n id: '4',\n voterName: 'Roberto Silva',\n colonia: 'El Mirador',\n timestamp: '09:30',\n status: 'contra',\n },\n {\n id: '5',\n voterName: 'Lucia Morales',\n colonia: 'Jardines',\n timestamp: '09:15',\n status: 'favor',\n },\n ];\n\n const onRefresh = useCallback(() => {\n setRefreshing(true);\n setTimeout(() => {\n setRefreshing(false);\n }, 2000);\n }, []);\n\n const handleQuickAction = (action: string) => {\n console.log(`Quick action: ${action}`);\n };\n\n return (\n <SafeAreaView style={styles.container}>\n <ScrollView\n style={styles.scrollView}\n contentContainerStyle={styles.scrollContent}\n refreshControl={\n <RefreshControl\n refreshing={refreshing}\n onRefresh={onRefresh}\n tintColor=\"#e11d48\"\n colors={['#e11d48']}\n />\n }\n >\n {/* Welcome Header */}\n <View style={styles.header}>\n <View>\n <Text style={styles.welcomeText}>¡Bienvenido!</Text>\n <Text style={styles.campaignName}>{campaignName}</Text>\n </View>\n <View style={styles.daysBadge}>\n <Text style={styles.daysText}>{daysRemaining}</Text>\n <Text style={styles.daysLabel}>días</Text>\n </View>\n </View>\n\n {/* Stats Grid */}\n <View style={styles.statsGrid}>\n {stats.map((stat, index) => (\n <StatCard key={index} {...stat} />\n ))}\n </View>\n\n {/* Quick Actions */}\n <View style={styles.quickActions}>\n <Text style={styles.sectionTitle}>Acciones Rápidas</Text>\n <View style={styles.quickActionsRow}>\n <QuickAction\n icon=\"add-circle\"\n label=\"Capturar\"\n onPress={() => handleQuickAction('capture')}\n />\n <QuickAction\n icon=\"map\"\n label=\"Mapa\"\n onPress={() => handleQuickAction('map')}\n />\n <QuickAction\n icon=\"download\"\n label=\"Exportar\"\n onPress={() => handleQuickAction('export')}\n />\n </View>\n </View>\n\n {/* Recent Activity */}\n <View style={styles.activitySection}>\n <Text style={styles.sectionTitle}>Actividad Reciente</Text>\n <View style={styles.activityFeed}>\n {recentActivity.map((item) => (\n <ActivityFeedItem key={item.id} item={item} />\n ))}\n </View>\n </View>\n\n {/* Campaign Progress */}\n <View style={styles.progressSection}>\n <View style={styles.progressHeader}>\n <Text style={styles.progressTitle}>Progreso de Campaña</Text>\n <Text style={styles.progressPercent}>{progressPercentage}%</Text>\n </View>\n <View style={styles.progressBarContainer}>\n <View style={[styles.progressBar, { width: `${progressPercentage}%` }]} />\n </View>\n </View>\n </ScrollView>\n </SafeAreaView>\n );\n}\n\nconst styles = StyleSheet.create({\n container: {\n flex: 1,\n backgroundColor: '#0f172a',\n },\n scrollView: {\n flex: 1,\n },\n scrollContent: {\n padding: 16,\n },\n header: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: 24,\n },\n welcomeText: {\n fontSize: 16,\n color: '#94a3b8',\n marginBottom: 4,\n },\n campaignName: {\n fontSize: 24,\n fontWeight: 'bold',\n color: '#ffffff',\n },\n daysBadge: {\n backgroundColor: '#e11d48',\n borderRadius: 12,\n paddingHorizontal: 16,\n paddingVertical: 8,\n alignItems: 'center',\n },\n daysText: {\n fontSize: 20,\n fontWeight: 'bold',\n color: '#ffffff',\n },\n daysLabel: {\n fontSize: 12,\n color: '#ffffff',\n },\n statsGrid: {\n flexDirection: 'row',\n flexWrap: 'wrap',\n marginHorizontal: -8,\n marginBottom: 24,\n },\n statCard: {\n width: '50%',\n paddingHorizontal: 8,\n marginBottom: 16,\n },\n statHeader: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n backgroundColor: '#1e293b',\n borderRadius: 12,\n padding: 16,\n },\n trendContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n },\n trendText: {\n fontSize: 12,\n fontWeight: '600',\n marginLeft: 4,\n },\n statValue: {\n fontSize: 24,\n fontWeight: 'bold',\n color: '#ffffff',\n marginTop: 8,\n textAlign: 'center',\n },\n statLabel: {\n fontSize: 14,\n color: '#94a3b8',\n textAlign: 'center',\n marginTop: 4,\n },\n quickActions: {\n marginBottom: 24,\n },\n sectionTitle: {\n fontSize: 18,\n fontWeight: 'bold',\n color: '#ffffff',\n marginBottom: 16,\n },\n quickActionsRow: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n },\n quickAction: {\n flex: 1,\n alignItems: 'center',\n marginHorizontal: 8,\n },\n quickActionIcon: {\n width: 56,\n height: 56,\n backgroundColor: '#1e293b',\n borderRadius: 28,\n justifyContent: 'center',\n alignItems: 'center',\n marginBottom: 8,\n },\n quickActionLabel: {\n fontSize: 14,\n color: '#ffffff',\n textAlign: 'center',\n },\n activitySection: {\n marginBottom: 24,\n },\n activityFeed: {\n backgroundColor: '#1e293b',\n borderRadius: 12,\n padding: 16,\n },\n activityItem: {\n flexDirection: 'row',\n alignItems: 'center',\n marginBottom: 12,\n },\n activityIcon: {\n width: 32,\n height: 32,\n borderRadius: 16,\n backgroundColor: '#0f172a',\n justifyContent: 'center',\n alignItems: 'center',\n marginRight: 12,\n },\n activityContent: {\n flex: 1,\n },\n activityVoter: {\n fontSize: 16,\n color: '#ffffff',\n fontWeight: '500',\n },\n activityColonia: {\n fontSize: 14,\n color: '#94a3b8',\n },\n activityTime: {\n fontSize: 12,\n color: '#64748b',\n },\n progressSection: {\n marginBottom: 24,\n },\n progressHeader: {\n flexDirection: 'row',\n justifyContent: 'space-between',\n alignItems: 'center',\n marginBottom: 12,\n },\n progressTitle: {\n fontSize: 16,\n color: '#ffffff',\n fontWeight: '500',\n },\n progressPercent: {\n fontSize: 16,\n color: '#e11d48',\n fontWeight: 'bold',\n },\n progressBarContainer: {\n height: 8,\n backgroundColor: '#1e293b',\n borderRadius: 4,\n overflow: 'hidden',\n },\n progressBar: {\n height: '100%',\n backgroundColor: '#e11d48',\n borderRadius: 4,\n },\n});"
}Audit Trail (1)
22d agotask_createde2e_test_v2
Task ID: 05533beb-122b-4a28-8b33-20b35dbe17b7