FOUNDRY
C8 Platform
← Tasks

[E2E-v2] CampaignPro Team

completedcode_genP1

Description

Generate a React Native / Expo screen component for CampaignPro Mexico. SCREEN: Team Screen — Member List + Roles FRAMEWORK: Expo + React Native + TypeScript THEME: Dark (#0f172a bg, #1e293b surface, #e11d48 accent, white text) LANGUAGE: Spanish UI labels Requirements: - Header: 'Equipo de Campaña' with member count badge - Section headers by role: Coordinadores, Brigadistas, Voluntarios - Member cards: avatar circle with initials, name, phone, zona assigned, active/inactive badge - Search bar
Assignee
-
Claimed By
G3 Flash
Created
22d ago
Completed
-

Quality

Overall
75%
Pass
PASS
Graded By
-
Graded At
-

Cost

Input Tokens
-
Output Tokens
-
Est. USD
-

Result

{
  "arm": "G3 Flash",
  "code": "import React, { useState, useMemo } from 'react';\nimport {\n  StyleSheet,\n  View,\n  Text,\n  TextInput,\n  SectionList,\n  TouchableOpacity,\n  StatusBar,\n  Linking,\n  Platform,\n} from 'react-native';\nimport { \n  Search, \n  UserPlus, \n  Phone, \n  MapPin, \n  ChevronRight, \n  MoreVertical \n} from 'lucide-react-native';\nimport { GestureHandlerRootView, Swipeable } from 'react-native-gesture-handler';\n\n// --- Types ---\ninterface TeamMember {\n  id: string;\n  name: string;\n  phone: string;\n  zone: string;\n  active: boolean;\n  role: 'Coordinador' | 'Brigadista' | 'Voluntario';\n}\n\ninterface SectionData {\n  title: string;\n  data: TeamMember[];\n}\n\n// --- Mock Data ---\nconst MOCK_TEAM: TeamMember[] = [\n  { id: '1', name: 'Alejandro Rivera', phone: '555-0102', zone: 'Centro', active: true, role: 'Coordinador' },\n  { id: '2', name: 'Beatriz Domínguez', phone: '555-0199', zone: 'Norte', active: true, role: 'Coordinador' },\n  { id: '3', name: 'Carlos Slim Helú', phone: '555-0123', zone: 'Sur', active: false, role: 'Brigadista' },\n  { id: '4', name: 'Diana Salazar', phone: '555-0144', zone: 'Poniente', active: true, role: 'Brigadista' },\n  { id: '5', name: 'Eduardo Galeano', phone: '555-0155', zone: 'Centro', active: true, role: 'Voluntario' },\n  { id: '6', name: 'Fernanda Familiar', phone: '555-0166', zone: 'Oriente', active: true, role: 'Voluntario' },\n];\n\nconst COLORS = {\n  bg: '#0f172a',\n  surface: '#1e293b',\n  accent: '#e11d48',\n  text: '#ffffff',\n  textMuted: '#94a3b8',\n  success: '#22c55e',\n  warning: '#f59e0b',\n};\n\nexport default function TeamScreen() {\n  const [searchQuery, setSearchQuery] = useState('');\n\n  const sections = useMemo(() => {\n    const filtered = MOCK_TEAM.filter((m) =>\n      m.name.toLowerCase().includes(searchQuery.toLowerCase())\n    );\n\n    const grouped: SectionData[] = [\n      { title: 'Coordinadores', data: filtered.filter((m) => m.role === 'Coordinador') },\n      { title: 'Brigadistas', data: filtered.filter((m) => m.role === 'Brigadista') },\n      { title: 'Voluntarios', data: filtered.filter((m) => m.role === 'Voluntario') },\n    ];\n\n    return grouped;\n  }, [searchQuery]);\n\n  const totalMembers = MOCK_TEAM.length;\n\n  const handleCall = (phone: string) => {\n    Linking.openURL(`tel:${phone}`);\n  };\n\n  const renderRightActions = (phone: string) => (\n    <TouchableOpacity \n      style={styles.callAction} \n      onPress={() => handleCall(phone)}\n    >\n      <Phone color=\"#fff\" size={24} />\n      <Text style={styles.callActionText}>Llamar</Text>\n    </TouchableOpacity>\n  );\n\n  const MemberCard = ({ item }: { item: TeamMember }) => {\n    const initials = item.name\n      .split(' ')\n      .map((n) => n[0])\n      .join('')\n      .slice(0, 2);\n\n    return (\n      <Swipeable renderRightActions={() => renderRightActions(item.phone)}>\n        <View style={styles.card}>\n          <View style={styles.avatarContainer}>\n            <Text style={styles.avatarText}>{initials}</Text>\n          </View>\n          \n          <View style={styles.cardContent}>\n            <View style={styles.cardHeader}>\n              <Text style={styles.memberName}>{item.name}</Text>\n              <View style={[styles.statusBadge, { backgroundColor: item.active ? COLORS.success + '20' : COLORS.warning + '20' }]}>\n                <View style={[styles.statusDot, { backgroundColor: item.active ? COLORS.success : COLORS.warning }]} />\n                <Text style={[styles.statusText, { color: item.active ? COLORS.success : COLORS.warning }]}>\n                  {item.active ? 'Activo' : 'Inactivo'}\n                </Text>\n              </View>\n            </View>\n\n            <View style={styles.cardFooter}>\n              <View style={styles.infoRow}>\n                <Phone size={14} color={COLORS.textMuted} />\n                <Text style={styles.infoText}>{item.phone}</Text>\n              </View>\n              <View style={[styles.infoRow, { marginLeft: 16 }]}>\n                <MapPin size={14} color={COLORS.textMuted} />\n                <Text style={styles.infoText}>{item.zone}</Text>\n              </View>\n            </View>\n          </View>\n          \n          <ChevronRight size={20} color={COLORS.textMuted} />\n        </View>\n      </Swipeable>\n    );\n  };\n\n  return (\n    <GestureHandlerRootView style={{ flex: 1 }}>\n      <View style={styles.container}>\n        <StatusBar barStyle=\"light-content\" />\n        \n        {/* Header */}\n        <View style={styles.header}>\n          <View>\n            <Text style={styles.headerTitle}>Equipo de Campaña</Text>\n            <View style={styles.badgeContainer}>\n              <Text style={styles.badgeText}>{totalMembers} Miembros totales</Text>\n            </View>\n          </View>\n          <TouchableOpacity style={styles.inviteButton}>\n            <UserPlus size={20} color=\"#fff\" />\n            <Text style={styles.inviteButtonText}>Invitar</Text>\n          </TouchableOpacity>\n        </View>\n\n        {/* Search Bar */}\n        <View style={styles.searchContainer}>\n          <Search size={20} color={COLORS.textMuted} style={styles.searchIcon} />\n          <TextInput\n            style={styles.searchInput}\n            placeholder=\"Buscar miembro...\"\n            placeholderTextColor={COLORS.textMuted}\n            value={searchQuery}\n            onChangeText={setSearchQuery}\n          />\n        </View>\n\n        {/* List */}\n        <SectionList\n          sections={sections}\n          keyExtractor={(item) => item.id}\n          stickySectionHeadersEnabled={false}\n          contentContainerStyle={styles.listPadding}\n          renderSectionHeader={({ section: { title, data } }) => (\n            <View style={styles.sectionHeader}>\n              <Text style={styles.sectionTitle}>{title}</Text>\n              {data.length === 0 && (\n                <Text style={styles.emptyText}>Sin miembros en esta categoría</Text>\n              )}\n            </View>\n          )}\n          renderItem={({ item }) => <MemberCard item={item} />}\n          ItemSeparatorComponent={() => <View style={styles.separator} />}\n        />\n      </View>\n    </GestureHandlerRootView>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: COLORS.bg,\n    paddingTop: Platform.OS === 'ios' ? 60 : 20,\n  },\n  header: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n    alignItems: 'center',\n    paddingHorizontal: 20,\n    marginBottom: 20,\n  },\n  headerTitle: {\n    fontSize: 24,\n    fontWeight: 'bold',\n    color: COLORS.text,\n  },\n  badgeContainer: {\n    backgroundColor: COLORS.surface,\n    paddingHorizontal: 8,\n    paddingVertical: 4,\n    borderRadius: 6,\n    marginTop: 4,\n    alignSelf: 'flex-start',\n  },\n  badgeText: {\n    color: COLORS.textMuted,\n    fontSize: 12,\n    fontWeight: '600',\n  },\n  inviteButton: {\n    backgroundColor: COLORS.accent,\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingHorizontal: 16,\n    paddingVertical: 10,\n    borderRadius: 12,\n  },\n  inviteButtonText: {\n    color: '#fff',\n    fontWeight: 'bold',\n    marginLeft: 8,\n  },\n  searchContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    backgroundColor: COLORS.surface,\n    marginHorizontal: 20,\n    paddingHorizontal: 15,\n    borderRadius: 12,\n    height: 45,\n    marginBottom: 20,\n  },\n  searchIcon: {\n    marginRight: 10,\n  },\n  searchInput: {\n    flex: 1,\n    color: COLORS.text,\n    fontSize: 16,\n  },\n  listPadding: {\n    paddingHorizontal: 20,\n    paddingBottom: 40,\n  },\n  sectionHeader: {\n    marginTop: 24,\n    marginBottom: 12,\n  },\n  sectionTitle: {\n    fontSize: 18,\n    fontWeight: 'bold',\n    color: COLORS.accent,\n    textTransform: 'uppercase',\n    letterSpacing: 1,\n  },\n  emptyText: {\n    color: COLORS.textMuted,\n    fontSize: 14,\n    marginTop: 8,\n    fontStyle: 'italic',\n  },\n  card: {\n    backgroundColor: COLORS.surface,\n    borderRadius: 16,\n    padding: 16,\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  avatarContainer: {\n    width: 48,\n    height: 48,\n    borderRadius: 24,\n    backgroundColor: COLORS.accent,\n    justifyContent: 'center',\n    alignItems: 'center',\n    marginRight: 16,\n  },\n  avatarText: {\n    color: '#fff',\n    fontWeight: 'bold',\n    fontSize: 16,\n  },\n  cardContent: {\n    flex: 1,\n  },\n  cardHeader: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'space-between',\n    marginBottom: 6,\n  },\n  memberName: {\n    fontSize: 16,\n    fontWeight: '600',\n    color: COLORS.text,\n  },\n  statusBadge: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingHorizontal: 8,\n    paddingVertical: 2,\n    borderRadius: 10,\n  },\n  statusDot: {\n    width: 6,\n    height: 6,\n    borderRadius: 3,\n    marginRight: 4,\n  },\n  statusText: {\n    fontSize: 10,\n    fontWeight: 'bold',\n    textTransform: 'uppercase',\n  },\n  cardFooter: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  infoRow: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  infoText: {\n    color: COLORS.textMuted,\n    fontSize: 13,\n    marginLeft: 4,\n  },\n  separator: {\n    height: 12,\n  },\n  callAction: {\n    backgroundColor: COLORS.success,\n    justifyContent: 'center',\n    alignItems: 'center',\n    width: 80,\n    height: '100%',\n    borderRadius: 16,\n    marginLeft: 10,\n  },\n  callActionText: {\n    color: '#fff',\n    fontSize: 12,\n    fontWeight: 'bold',\n    marginTop: 4,\n  },\n});",
  "content": "import React, { useState, useMemo } from 'react';\nimport {\n  StyleSheet,\n  View,\n  Text,\n  TextInput,\n  SectionList,\n  TouchableOpacity,\n  StatusBar,\n  Linking,\n  Platform,\n} from 'react-native';\nimport { \n  Search, \n  UserPlus, \n  Phone, \n  MapPin, \n  ChevronRight, \n  MoreVertical \n} from 'lucide-react-native';\nimport { GestureHandlerRootView, Swipeable } from 'react-native-gesture-handler';\n\n// --- Types ---\ninterface TeamMember {\n  id: string;\n  name: string;\n  phone: string;\n  zone: string;\n  active: boolean;\n  role: 'Coordinador' | 'Brigadista' | 'Voluntario';\n}\n\ninterface SectionData {\n  title: string;\n  data: TeamMember[];\n}\n\n// --- Mock Data ---\nconst MOCK_TEAM: TeamMember[] = [\n  { id: '1', name: 'Alejandro Rivera', phone: '555-0102', zone: 'Centro', active: true, role: 'Coordinador' },\n  { id: '2', name: 'Beatriz Domínguez', phone: '555-0199', zone: 'Norte', active: true, role: 'Coordinador' },\n  { id: '3', name: 'Carlos Slim Helú', phone: '555-0123', zone: 'Sur', active: false, role: 'Brigadista' },\n  { id: '4', name: 'Diana Salazar', phone: '555-0144', zone: 'Poniente', active: true, role: 'Brigadista' },\n  { id: '5', name: 'Eduardo Galeano', phone: '555-0155', zone: 'Centro', active: true, role: 'Voluntario' },\n  { id: '6', name: 'Fernanda Familiar', phone: '555-0166', zone: 'Oriente', active: true, role: 'Voluntario' },\n];\n\nconst COLORS = {\n  bg: '#0f172a',\n  surface: '#1e293b',\n  accent: '#e11d48',\n  text: '#ffffff',\n  textMuted: '#94a3b8',\n  success: '#22c55e',\n  warning: '#f59e0b',\n};\n\nexport default function TeamScreen() {\n  const [searchQuery, setSearchQuery] = useState('');\n\n  const sections = useMemo(() => {\n    const filtered = MOCK_TEAM.filter((m) =>\n      m.name.toLowerCase().includes(searchQuery.toLowerCase())\n    );\n\n    const grouped: SectionData[] = [\n      { title: 'Coordinadores', data: filtered.filter((m) => m.role === 'Coordinador') },\n      { title: 'Brigadistas', data: filtered.filter((m) => m.role === 'Brigadista') },\n      { title: 'Voluntarios', data: filtered.filter((m) => m.role === 'Voluntario') },\n    ];\n\n    return grouped;\n  }, [searchQuery]);\n\n  const totalMembers = MOCK_TEAM.length;\n\n  const handleCall = (phone: string) => {\n    Linking.openURL(`tel:${phone}`);\n  };\n\n  const renderRightActions = (phone: string) => (\n    <TouchableOpacity \n      style={styles.callAction} \n      onPress={() => handleCall(phone)}\n    >\n      <Phone color=\"#fff\" size={24} />\n      <Text style={styles.callActionText}>Llamar</Text>\n    </TouchableOpacity>\n  );\n\n  const MemberCard = ({ item }: { item: TeamMember }) => {\n    const initials = item.name\n      .split(' ')\n      .map((n) => n[0])\n      .join('')\n      .slice(0, 2);\n\n    return (\n      <Swipeable renderRightActions={() => renderRightActions(item.phone)}>\n        <View style={styles.card}>\n          <View style={styles.avatarContainer}>\n            <Text style={styles.avatarText}>{initials}</Text>\n          </View>\n          \n          <View style={styles.cardContent}>\n            <View style={styles.cardHeader}>\n              <Text style={styles.memberName}>{item.name}</Text>\n              <View style={[styles.statusBadge, { backgroundColor: item.active ? COLORS.success + '20' : COLORS.warning + '20' }]}>\n                <View style={[styles.statusDot, { backgroundColor: item.active ? COLORS.success : COLORS.warning }]} />\n                <Text style={[styles.statusText, { color: item.active ? COLORS.success : COLORS.warning }]}>\n                  {item.active ? 'Activo' : 'Inactivo'}\n                </Text>\n              </View>\n            </View>\n\n            <View style={styles.cardFooter}>\n              <View style={styles.infoRow}>\n                <Phone size={14} color={COLORS.textMuted} />\n                <Text style={styles.infoText}>{item.phone}</Text>\n              </View>\n              <View style={[styles.infoRow, { marginLeft: 16 }]}>\n                <MapPin size={14} color={COLORS.textMuted} />\n                <Text style={styles.infoText}>{item.zone}</Text>\n              </View>\n            </View>\n          </View>\n          \n          <ChevronRight size={20} color={COLORS.textMuted} />\n        </View>\n      </Swipeable>\n    );\n  };\n\n  return (\n    <GestureHandlerRootView style={{ flex: 1 }}>\n      <View style={styles.container}>\n        <StatusBar barStyle=\"light-content\" />\n        \n        {/* Header */}\n        <View style={styles.header}>\n          <View>\n            <Text style={styles.headerTitle}>Equipo de Campaña</Text>\n            <View style={styles.badgeContainer}>\n              <Text style={styles.badgeText}>{totalMembers} Miembros totales</Text>\n            </View>\n          </View>\n          <TouchableOpacity style={styles.inviteButton}>\n            <UserPlus size={20} color=\"#fff\" />\n            <Text style={styles.inviteButtonText}>Invitar</Text>\n          </TouchableOpacity>\n        </View>\n\n        {/* Search Bar */}\n        <View style={styles.searchContainer}>\n          <Search size={20} color={COLORS.textMuted} style={styles.searchIcon} />\n          <TextInput\n            style={styles.searchInput}\n            placeholder=\"Buscar miembro...\"\n            placeholderTextColor={COLORS.textMuted}\n            value={searchQuery}\n            onChangeText={setSearchQuery}\n          />\n        </View>\n\n        {/* List */}\n        <SectionList\n          sections={sections}\n          keyExtractor={(item) => item.id}\n          stickySectionHeadersEnabled={false}\n          contentContainerStyle={styles.listPadding}\n          renderSectionHeader={({ section: { title, data } }) => (\n            <View style={styles.sectionHeader}>\n              <Text style={styles.sectionTitle}>{title}</Text>\n              {data.length === 0 && (\n                <Text style={styles.emptyText}>Sin miembros en esta categoría</Text>\n              )}\n            </View>\n          )}\n          renderItem={({ item }) => <MemberCard item={item} />}\n          ItemSeparatorComponent={() => <View style={styles.separator} />}\n        />\n      </View>\n    </GestureHandlerRootView>\n  );\n}\n\nconst styles = StyleSheet.create({\n  container: {\n    flex: 1,\n    backgroundColor: COLORS.bg,\n    paddingTop: Platform.OS === 'ios' ? 60 : 20,\n  },\n  header: {\n    flexDirection: 'row',\n    justifyContent: 'space-between',\n    alignItems: 'center',\n    paddingHorizontal: 20,\n    marginBottom: 20,\n  },\n  headerTitle: {\n    fontSize: 24,\n    fontWeight: 'bold',\n    color: COLORS.text,\n  },\n  badgeContainer: {\n    backgroundColor: COLORS.surface,\n    paddingHorizontal: 8,\n    paddingVertical: 4,\n    borderRadius: 6,\n    marginTop: 4,\n    alignSelf: 'flex-start',\n  },\n  badgeText: {\n    color: COLORS.textMuted,\n    fontSize: 12,\n    fontWeight: '600',\n  },\n  inviteButton: {\n    backgroundColor: COLORS.accent,\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingHorizontal: 16,\n    paddingVertical: 10,\n    borderRadius: 12,\n  },\n  inviteButtonText: {\n    color: '#fff',\n    fontWeight: 'bold',\n    marginLeft: 8,\n  },\n  searchContainer: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    backgroundColor: COLORS.surface,\n    marginHorizontal: 20,\n    paddingHorizontal: 15,\n    borderRadius: 12,\n    height: 45,\n    marginBottom: 20,\n  },\n  searchIcon: {\n    marginRight: 10,\n  },\n  searchInput: {\n    flex: 1,\n    color: COLORS.text,\n    fontSize: 16,\n  },\n  listPadding: {\n    paddingHorizontal: 20,\n    paddingBottom: 40,\n  },\n  sectionHeader: {\n    marginTop: 24,\n    marginBottom: 12,\n  },\n  sectionTitle: {\n    fontSize: 18,\n    fontWeight: 'bold',\n    color: COLORS.accent,\n    textTransform: 'uppercase',\n    letterSpacing: 1,\n  },\n  emptyText: {\n    color: COLORS.textMuted,\n    fontSize: 14,\n    marginTop: 8,\n    fontStyle: 'italic',\n  },\n  card: {\n    backgroundColor: COLORS.surface,\n    borderRadius: 16,\n    padding: 16,\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  avatarContainer: {\n    width: 48,\n    height: 48,\n    borderRadius: 24,\n    backgroundColor: COLORS.accent,\n    justifyContent: 'center',\n    alignItems: 'center',\n    marginRight: 16,\n  },\n  avatarText: {\n    color: '#fff',\n    fontWeight: 'bold',\n    fontSize: 16,\n  },\n  cardContent: {\n    flex: 1,\n  },\n  cardHeader: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    justifyContent: 'space-between',\n    marginBottom: 6,\n  },\n  memberName: {\n    fontSize: 16,\n    fontWeight: '600',\n    color: COLORS.text,\n  },\n  statusBadge: {\n    flexDirection: 'row',\n    alignItems: 'center',\n    paddingHorizontal: 8,\n    paddingVertical: 2,\n    borderRadius: 10,\n  },\n  statusDot: {\n    width: 6,\n    height: 6,\n    borderRadius: 3,\n    marginRight: 4,\n  },\n  statusText: {\n    fontSize: 10,\n    fontWeight: 'bold',\n    textTransform: 'uppercase',\n  },\n  cardFooter: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  infoRow: {\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  infoText: {\n    color: COLORS.textMuted,\n    fontSize: 13,\n    marginLeft: 4,\n  },\n  separator: {\n    height: 12,\n  },\n  callAction: {\n    backgroundColor: COLORS.success,\n    justifyContent: 'center',\n    alignItems: 'center',\n    width: 80,\n    height: '100%',\n    borderRadius: 16,\n    marginLeft: 10,\n  },\n  callActionText: {\n    color: '#fff',\n    fontSize: 12,\n    fontWeight: 'bold',\n    marginTop: 4,\n  },\n});"
}

Audit Trail (1)

22d agotask_createde2e_test_v2
Task ID: e8129045-9876-4797-b32a-aa4ad09cee0a