import React, { useState, useEffect, useCallback } from 'react'; import { View, Text, StyleSheet, FlatList, TextInput, TouchableOpacity, RefreshControl, Alert, Modal, ScrollView, KeyboardAvoidingView, Platform, Image, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { colors, spacing, fontSize, borderRadius } from '../constants/theme'; import { useAuth } from '../contexts/AuthContext'; import api from '../services/api'; import { Product, Category } from '../types'; interface ProductFormData { name: string; description: string; sku: string; barcode: string; price: string; cost: string; currentStock: string; minStock: string; categoryId: string; } const initialFormData: ProductFormData = { name: '', description: '', sku: '', barcode: '', price: '', cost: '', currentStock: '0', minStock: '5', categoryId: '', }; export default function ProductsScreen({ navigation }: any) { const { user } = useAuth(); const [products, setProducts] = useState([]); const [categories, setCategories] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [selectedCategory, setSelectedCategory] = useState(null); const [modalVisible, setModalVisible] = useState(false); const [editingProduct, setEditingProduct] = useState(null); const [formData, setFormData] = useState(initialFormData); const [saving, setSaving] = useState(false); const fetchProducts = useCallback(async () => { try { const params: any = {}; if (searchQuery) params.search = searchQuery; if (selectedCategory) params.categoryId = selectedCategory; const response = await api.get('/products', { params }); setProducts(response.data.data || response.data); } catch (error) { console.error('Error fetching products:', error); } }, [searchQuery, selectedCategory]); const fetchCategories = async () => { try { const response = await api.get('/categories'); setCategories(response.data.data || response.data); } catch (error) { console.error('Error fetching categories:', error); } }; useEffect(() => { const loadData = async () => { setLoading(true); await Promise.all([fetchProducts(), fetchCategories()]); setLoading(false); }; loadData(); }, []); useEffect(() => { const delayedSearch = setTimeout(() => { fetchProducts(); }, 300); return () => clearTimeout(delayedSearch); }, [searchQuery, selectedCategory]); const onRefresh = async () => { setRefreshing(true); await fetchProducts(); setRefreshing(false); }; const openCreateModal = () => { setEditingProduct(null); setFormData(initialFormData); setModalVisible(true); }; const openEditModal = (product: Product) => { setEditingProduct(product); setFormData({ name: product.name, description: product.description || '', sku: product.sku || '', barcode: product.barcode || '', price: product.price.toString(), cost: product.cost?.toString() || '', currentStock: product.currentStock?.toString() || '0', minStock: product.minStock?.toString() || '5', categoryId: product.categoryId || '', }); setModalVisible(true); }; const handleSave = async () => { if (!formData.name.trim()) { Alert.alert('Error', 'El nombre del producto es requerido'); return; } if (!formData.price || parseFloat(formData.price) <= 0) { Alert.alert('Error', 'El precio debe ser mayor a 0'); return; } setSaving(true); try { const payload = { name: formData.name.trim(), description: formData.description.trim() || null, sku: formData.sku.trim() || null, barcode: formData.barcode.trim() || null, price: parseFloat(formData.price), cost: formData.cost ? parseFloat(formData.cost) : null, currentStock: parseInt(formData.currentStock) || 0, minStock: parseInt(formData.minStock) || 5, categoryId: formData.categoryId || null, }; if (editingProduct) { await api.put(`/products/${editingProduct.id}`, payload); Alert.alert('Exito', 'Producto actualizado correctamente'); } else { await api.post('/products', payload); Alert.alert('Exito', 'Producto creado correctamente'); } setModalVisible(false); fetchProducts(); } catch (error: any) { Alert.alert('Error', error.response?.data?.message || 'No se pudo guardar el producto'); } finally { setSaving(false); } }; const handleDelete = (product: Product) => { Alert.alert( 'Eliminar Producto', `¿Estas seguro de eliminar "${product.name}"?`, [ { text: 'Cancelar', style: 'cancel' }, { text: 'Eliminar', style: 'destructive', onPress: async () => { try { await api.delete(`/products/${product.id}`); fetchProducts(); } catch (error: any) { Alert.alert('Error', 'No se pudo eliminar el producto'); } }, }, ] ); }; const toggleFavorite = async (product: Product) => { try { await api.patch(`/products/${product.id}/toggle-favorite`); fetchProducts(); } catch (error) { console.error('Error toggling favorite:', error); } }; const renderProduct = ({ item }: { item: Product }) => { const isLowStock = item.currentStock <= (item.minStock || 5); return ( openEditModal(item)} onLongPress={() => handleDelete(item)} > {item.imageUrl ? ( ) : ( {item.name.charAt(0).toUpperCase()} )} {item.isFavorite && ( )} {item.name} {item.sku || 'Sin SKU'} ${item.price.toFixed(2)} {item.currentStock} uds toggleFavorite(item)} > {item.isFavorite ? '★' : '☆'} ); }; const renderCategoryFilter = () => ( setSelectedCategory(null)} > Todos {categories.map((cat) => ( setSelectedCategory(cat.id)} > {cat.name} ))} ); const renderFormModal = () => ( setModalVisible(false)} > setModalVisible(false)}> Cancelar {editingProduct ? 'Editar Producto' : 'Nuevo Producto'} {saving ? 'Guardando...' : 'Guardar'} Nombre * setFormData({ ...formData, name: text })} placeholder="Nombre del producto" placeholderTextColor={colors.textMuted} /> Descripcion setFormData({ ...formData, description: text })} placeholder="Descripcion del producto" placeholderTextColor={colors.textMuted} multiline numberOfLines={3} /> SKU setFormData({ ...formData, sku: text })} placeholder="SKU-001" placeholderTextColor={colors.textMuted} /> Codigo de Barras setFormData({ ...formData, barcode: text })} placeholder="7501234567890" placeholderTextColor={colors.textMuted} keyboardType="numeric" /> Precio de Venta * setFormData({ ...formData, price: text })} placeholder="0.00" placeholderTextColor={colors.textMuted} keyboardType="decimal-pad" /> Costo setFormData({ ...formData, cost: text })} placeholder="0.00" placeholderTextColor={colors.textMuted} keyboardType="decimal-pad" /> Stock Actual setFormData({ ...formData, currentStock: text })} placeholder="0" placeholderTextColor={colors.textMuted} keyboardType="number-pad" /> Stock Minimo setFormData({ ...formData, minStock: text })} placeholder="5" placeholderTextColor={colors.textMuted} keyboardType="number-pad" /> Categoria setFormData({ ...formData, categoryId: '' })} > Sin categoria {categories.map((cat) => ( setFormData({ ...formData, categoryId: cat.id })} > {cat.name} ))} ); return ( {/* Header */} Productos + Nuevo {/* Search */} {/* Category Filter */} {renderCategoryFilter()} {/* Products List */} {loading ? ( Cargando productos... ) : products.length === 0 ? ( 📦 No hay productos Crear primer producto ) : ( item.id} renderItem={renderProduct} contentContainerStyle={styles.listContent} numColumns={2} columnWrapperStyle={styles.columnWrapper} refreshControl={ } /> )} {renderFormModal()} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: colors.background, }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: spacing.lg, paddingVertical: spacing.md, }, title: { fontSize: fontSize.xxl, fontWeight: 'bold', color: colors.text, }, addButton: { backgroundColor: colors.primary, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, borderRadius: borderRadius.md, }, addButtonText: { color: '#fff', fontWeight: '600', fontSize: fontSize.sm, }, searchContainer: { paddingHorizontal: spacing.lg, marginBottom: spacing.sm, }, searchInput: { backgroundColor: colors.surface, borderRadius: borderRadius.md, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, fontSize: fontSize.md, color: colors.text, borderWidth: 1, borderColor: colors.border, }, categoryFilter: { maxHeight: 50, marginBottom: spacing.sm, }, categoryFilterContent: { paddingHorizontal: spacing.lg, gap: spacing.sm, }, categoryChip: { backgroundColor: colors.surface, paddingHorizontal: spacing.md, paddingVertical: spacing.xs, borderRadius: borderRadius.full, borderWidth: 1, borderColor: colors.border, marginRight: spacing.sm, }, categoryChipActive: { backgroundColor: colors.primary, borderColor: colors.primary, }, categoryChipText: { fontSize: fontSize.sm, color: colors.textSecondary, }, categoryChipTextActive: { color: '#fff', }, listContent: { paddingHorizontal: spacing.md, paddingBottom: spacing.xl, }, columnWrapper: { justifyContent: 'space-between', }, productCard: { width: '48%', backgroundColor: colors.surface, borderRadius: borderRadius.lg, marginBottom: spacing.md, overflow: 'hidden', borderWidth: 1, borderColor: colors.border, }, productImageContainer: { position: 'relative', height: 120, backgroundColor: colors.background, }, productImage: { width: '100%', height: '100%', resizeMode: 'cover', }, productImagePlaceholder: { width: '100%', height: '100%', justifyContent: 'center', alignItems: 'center', backgroundColor: colors.primaryLight, }, productImageText: { fontSize: 40, fontWeight: 'bold', color: colors.primary, }, favoriteIndicator: { position: 'absolute', top: spacing.xs, left: spacing.xs, backgroundColor: colors.warning, borderRadius: borderRadius.full, padding: 4, }, productInfo: { padding: spacing.sm, }, productName: { fontSize: fontSize.md, fontWeight: '600', color: colors.text, marginBottom: spacing.xs, }, productSku: { fontSize: fontSize.xs, color: colors.textMuted, marginBottom: spacing.sm, }, productFooter: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, productPrice: { fontSize: fontSize.lg, fontWeight: 'bold', color: colors.primary, }, stockBadge: { paddingHorizontal: spacing.sm, paddingVertical: 2, borderRadius: borderRadius.sm, }, stockBadgeOk: { backgroundColor: colors.successLight, }, stockBadgeLow: { backgroundColor: colors.errorLight, }, stockText: { fontSize: fontSize.xs, fontWeight: '600', }, stockTextOk: { color: colors.success, }, stockTextLow: { color: colors.error, }, favoriteButton: { position: 'absolute', top: spacing.xs, right: spacing.xs, backgroundColor: 'rgba(255,255,255,0.9)', borderRadius: borderRadius.full, padding: spacing.xs, }, centerContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: spacing.xl, }, loadingText: { fontSize: fontSize.md, color: colors.textMuted, }, emptyIcon: { fontSize: 60, marginBottom: spacing.md, }, emptyText: { fontSize: fontSize.lg, color: colors.textSecondary, marginBottom: spacing.lg, }, emptyButton: { backgroundColor: colors.primary, paddingHorizontal: spacing.lg, paddingVertical: spacing.md, borderRadius: borderRadius.md, }, emptyButtonText: { color: '#fff', fontWeight: '600', }, // Modal styles modalContainer: { flex: 1, backgroundColor: colors.background, }, modalHeader: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', paddingHorizontal: spacing.lg, paddingVertical: spacing.md, borderBottomWidth: 1, borderBottomColor: colors.border, }, modalCancel: { fontSize: fontSize.md, color: colors.textSecondary, }, modalTitle: { fontSize: fontSize.lg, fontWeight: 'bold', color: colors.text, }, modalSave: { fontSize: fontSize.md, color: colors.primary, fontWeight: '600', }, modalSaveDisabled: { opacity: 0.5, }, formContainer: { flex: 1, padding: spacing.lg, }, formGroup: { marginBottom: spacing.md, }, formRow: { flexDirection: 'row', }, formLabel: { fontSize: fontSize.sm, fontWeight: '600', color: colors.textSecondary, marginBottom: spacing.xs, }, formInput: { backgroundColor: colors.surface, borderRadius: borderRadius.md, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, fontSize: fontSize.md, color: colors.text, borderWidth: 1, borderColor: colors.border, }, formTextArea: { minHeight: 80, textAlignVertical: 'top', }, categorySelector: { flexDirection: 'row', }, categorySelectorItem: { backgroundColor: colors.surface, paddingHorizontal: spacing.md, paddingVertical: spacing.sm, borderRadius: borderRadius.md, borderWidth: 1, borderColor: colors.border, marginRight: spacing.sm, }, categorySelectorItemActive: { backgroundColor: colors.primary, borderColor: colors.primary, }, categorySelectorText: { fontSize: fontSize.sm, color: colors.textSecondary, }, categorySelectorTextActive: { color: '#fff', }, });