erp-core/mobile/app/(tabs)/partners.tsx
rckrdmrd 0086695b4c
Some checks failed
ERP Core CI / Backend Lint (push) Has been cancelled
ERP Core CI / Backend Unit Tests (push) Has been cancelled
ERP Core CI / Backend Integration Tests (push) Has been cancelled
ERP Core CI / Frontend Lint (push) Has been cancelled
ERP Core CI / Frontend Unit Tests (push) Has been cancelled
ERP Core CI / Frontend E2E Tests (push) Has been cancelled
ERP Core CI / Database DDL Validation (push) Has been cancelled
ERP Core CI / Backend Build (push) Has been cancelled
ERP Core CI / Frontend Build (push) Has been cancelled
ERP Core CI / CI Success (push) Has been cancelled
Performance Tests / Lighthouse CI (push) Has been cancelled
Performance Tests / Bundle Size Analysis (push) Has been cancelled
Performance Tests / k6 Load Tests (push) Has been cancelled
Performance Tests / Performance Summary (push) Has been cancelled
[SIMCO-V38] feat: Actualizar a SIMCO v3.8.0 + cambios backend
- HERENCIA-SIMCO.md actualizado con directivas v3.7 y v3.8
- Actualizaciones en modulos CRM y OpenAPI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 08:53:05 -06:00

300 lines
7.8 KiB
TypeScript

/**
* Partners Screen
*
* List of customers and suppliers
*/
import { useState, useEffect, useCallback } from 'react';
import {
View,
Text,
StyleSheet,
FlatList,
TextInput,
TouchableOpacity,
RefreshControl,
ActivityIndicator,
} from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import { partnersApi } from '@/services/api';
import { Partner } from '@/types';
type FilterType = 'all' | 'customers' | 'suppliers';
export default function PartnersScreen() {
const [partners, setPartners] = useState<Partner[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [refreshing, setRefreshing] = useState(false);
const [searchQuery, setSearchQuery] = useState('');
const [filter, setFilter] = useState<FilterType>('all');
const fetchPartners = async () => {
try {
const params: any = { limit: 50 };
if (searchQuery) params.search = searchQuery;
if (filter === 'customers') params.isCustomer = true;
if (filter === 'suppliers') params.isSupplier = true;
const response = await partnersApi.list(params);
setPartners(response.data || []);
} catch (error) {
console.error('Error fetching partners:', error);
// Use mock data for demo
setPartners([
{ id: '1', name: 'Empresa ABC', email: 'contacto@abc.com', phone: '+52 55 1234 5678', isCustomer: true, isSupplier: false, active: true },
{ id: '2', name: 'Proveedor XYZ', email: 'ventas@xyz.com', phone: '+52 55 8765 4321', isCustomer: false, isSupplier: true, active: true },
{ id: '3', name: 'Cliente 123', email: 'info@cliente123.com', phone: '+52 55 1111 2222', isCustomer: true, isSupplier: false, active: true },
{ id: '4', name: 'Distribuidor Norte', email: 'norte@dist.com', phone: '+52 81 3333 4444', isCustomer: true, isSupplier: true, active: true },
]);
} finally {
setIsLoading(false);
setRefreshing(false);
}
};
useEffect(() => {
fetchPartners();
}, [filter]);
useEffect(() => {
const timeoutId = setTimeout(() => {
if (!isLoading) fetchPartners();
}, 500);
return () => clearTimeout(timeoutId);
}, [searchQuery]);
const onRefresh = useCallback(() => {
setRefreshing(true);
fetchPartners();
}, [filter, searchQuery]);
const filteredPartners = partners.filter((partner) => {
if (filter === 'customers' && !partner.isCustomer) return false;
if (filter === 'suppliers' && !partner.isSupplier) return false;
return true;
});
const renderPartner = ({ item }: { item: Partner }) => (
<TouchableOpacity style={styles.partnerCard}>
<View style={styles.partnerAvatar}>
<Text style={styles.partnerInitial}>
{item.name.charAt(0).toUpperCase()}
</Text>
</View>
<View style={styles.partnerInfo}>
<Text style={styles.partnerName}>{item.name}</Text>
{item.email && <Text style={styles.partnerEmail}>{item.email}</Text>}
<View style={styles.partnerTags}>
{item.isCustomer && (
<View style={[styles.tag, styles.customerTag]}>
<Text style={styles.tagText}>Cliente</Text>
</View>
)}
{item.isSupplier && (
<View style={[styles.tag, styles.supplierTag]}>
<Text style={styles.tagText}>Proveedor</Text>
</View>
)}
</View>
</View>
<Ionicons name="chevron-forward" size={20} color="#9ca3af" />
</TouchableOpacity>
);
return (
<View style={styles.container}>
{/* Search */}
<View style={styles.searchContainer}>
<Ionicons name="search" size={20} color="#9ca3af" style={styles.searchIcon} />
<TextInput
style={styles.searchInput}
placeholder="Buscar contactos..."
placeholderTextColor="#9ca3af"
value={searchQuery}
onChangeText={setSearchQuery}
/>
</View>
{/* Filters */}
<View style={styles.filters}>
{(['all', 'customers', 'suppliers'] as FilterType[]).map((type) => (
<TouchableOpacity
key={type}
style={[styles.filterButton, filter === type && styles.filterButtonActive]}
onPress={() => setFilter(type)}
>
<Text style={[styles.filterText, filter === type && styles.filterTextActive]}>
{type === 'all' ? 'Todos' : type === 'customers' ? 'Clientes' : 'Proveedores'}
</Text>
</TouchableOpacity>
))}
</View>
{/* List */}
{isLoading ? (
<View style={styles.loadingContainer}>
<ActivityIndicator size="large" color="#1e40af" />
</View>
) : (
<FlatList
data={filteredPartners}
renderItem={renderPartner}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.listContent}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#1e40af" />
}
ListEmptyComponent={
<View style={styles.emptyContainer}>
<Ionicons name="people-outline" size={48} color="#d1d5db" />
<Text style={styles.emptyText}>No se encontraron contactos</Text>
</View>
}
/>
)}
{/* FAB */}
<TouchableOpacity style={styles.fab}>
<Ionicons name="add" size={24} color="#ffffff" />
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8fafc',
},
searchContainer: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#ffffff',
margin: 16,
borderRadius: 10,
paddingHorizontal: 12,
},
searchIcon: {
marginRight: 8,
},
searchInput: {
flex: 1,
paddingVertical: 12,
fontSize: 16,
color: '#1f2937',
},
filters: {
flexDirection: 'row',
paddingHorizontal: 16,
marginBottom: 8,
gap: 8,
},
filterButton: {
paddingHorizontal: 16,
paddingVertical: 8,
borderRadius: 20,
backgroundColor: '#ffffff',
},
filterButtonActive: {
backgroundColor: '#1e40af',
},
filterText: {
fontSize: 14,
color: '#64748b',
},
filterTextActive: {
color: '#ffffff',
fontWeight: '500',
},
listContent: {
padding: 16,
paddingTop: 8,
},
partnerCard: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 16,
marginBottom: 12,
},
partnerAvatar: {
width: 48,
height: 48,
borderRadius: 24,
backgroundColor: '#1e40af',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
partnerInitial: {
fontSize: 20,
fontWeight: '600',
color: '#ffffff',
},
partnerInfo: {
flex: 1,
},
partnerName: {
fontSize: 16,
fontWeight: '600',
color: '#1f2937',
},
partnerEmail: {
fontSize: 14,
color: '#64748b',
marginTop: 2,
},
partnerTags: {
flexDirection: 'row',
marginTop: 6,
gap: 6,
},
tag: {
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: 4,
},
customerTag: {
backgroundColor: '#dbeafe',
},
supplierTag: {
backgroundColor: '#dcfce7',
},
tagText: {
fontSize: 12,
fontWeight: '500',
color: '#374151',
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
emptyContainer: {
alignItems: 'center',
paddingVertical: 48,
},
emptyText: {
fontSize: 16,
color: '#9ca3af',
marginTop: 12,
},
fab: {
position: 'absolute',
bottom: 24,
right: 24,
width: 56,
height: 56,
borderRadius: 28,
backgroundColor: '#1e40af',
justifyContent: 'center',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
},
});