miinventario-mobile-v2/src/app/validation/items.tsx
rckrdmrd eb718a95aa Sincronización desde miinventario/apps/mobile - Estándar multi-repo v2
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-16 08:29:24 -06:00

302 lines
7.1 KiB
TypeScript

import React, { useEffect } from 'react';
import {
View,
Text,
ScrollView,
TouchableOpacity,
StyleSheet,
ActivityIndicator,
Alert,
} from 'react-native';
import { useRouter } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
import { useValidationsStore } from '../../stores/validations.store';
import { ValidationItemCard } from '../../components/validation/ValidationItemCard';
import { ValidationProgressBar } from '../../components/validation/ValidationProgressBar';
import { ValidationItemResponse } from '../../services/api/validations.service';
export default function ValidationItemsScreen() {
const router = useRouter();
const {
pendingRequest,
items,
responses,
currentItemIndex,
isLoading,
error,
addResponse,
nextItem,
previousItem,
submitValidation,
skipValidation,
} = useValidationsStore();
useEffect(() => {
if (!pendingRequest || items.length === 0) {
router.replace('/');
}
}, [pendingRequest, items]);
const handleResponse = (response: Omit<ValidationItemResponse, 'inventoryItemId'>) => {
if (!items[currentItemIndex]) return;
addResponse({
...response,
inventoryItemId: items[currentItemIndex].id,
});
// Auto-advance to next item
if (currentItemIndex < items.length - 1) {
setTimeout(() => nextItem(), 300);
}
};
const handleSubmit = async () => {
if (responses.length < items.length) {
Alert.alert(
'Faltan items',
'Por favor valida todos los productos antes de continuar.',
);
return;
}
try {
await submitValidation();
router.replace('/validation/complete');
} catch {
Alert.alert('Error', 'No se pudo enviar la validacion. Intenta de nuevo.');
}
};
const handleSkip = () => {
Alert.alert(
'Omitir validacion',
'Estas seguro? No recibiras el credito de recompensa.',
[
{ text: 'Cancelar', style: 'cancel' },
{
text: 'Omitir',
style: 'destructive',
onPress: async () => {
await skipValidation();
router.replace('/');
},
},
],
);
};
if (!pendingRequest || items.length === 0) {
return (
<View style={styles.loading}>
<ActivityIndicator size="large" color="#007AFF" />
</View>
);
}
const currentItem = items[currentItemIndex];
const currentResponse = responses.find(
(r) => r.inventoryItemId === currentItem?.id,
);
return (
<View style={styles.container}>
<ValidationProgressBar
current={currentItemIndex}
total={items.length}
validated={responses.length}
/>
<ScrollView style={styles.content} contentContainerStyle={styles.scrollContent}>
{currentItem && (
<ValidationItemCard
item={currentItem}
onResponse={handleResponse}
existingResponse={currentResponse}
/>
)}
{error && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>{error}</Text>
</View>
)}
</ScrollView>
<View style={styles.footer}>
<View style={styles.navigation}>
<TouchableOpacity
style={[styles.navButton, currentItemIndex === 0 && styles.navButtonDisabled]}
onPress={previousItem}
disabled={currentItemIndex === 0}
>
<Ionicons
name="chevron-back"
size={24}
color={currentItemIndex === 0 ? '#ccc' : '#007AFF'}
/>
<Text
style={[
styles.navText,
currentItemIndex === 0 && styles.navTextDisabled,
]}
>
Anterior
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.navButton,
currentItemIndex === items.length - 1 && styles.navButtonDisabled,
]}
onPress={nextItem}
disabled={currentItemIndex === items.length - 1}
>
<Text
style={[
styles.navText,
currentItemIndex === items.length - 1 && styles.navTextDisabled,
]}
>
Siguiente
</Text>
<Ionicons
name="chevron-forward"
size={24}
color={currentItemIndex === items.length - 1 ? '#ccc' : '#007AFF'}
/>
</TouchableOpacity>
</View>
<View style={styles.actions}>
<TouchableOpacity style={styles.skipButton} onPress={handleSkip}>
<Text style={styles.skipText}>Omitir</Text>
</TouchableOpacity>
<TouchableOpacity
style={[
styles.submitButton,
responses.length < items.length && styles.submitButtonDisabled,
]}
onPress={handleSubmit}
disabled={isLoading || responses.length < items.length}
>
{isLoading ? (
<ActivityIndicator color="#fff" />
) : (
<>
<Text style={styles.submitText}>Enviar</Text>
<View style={styles.badge}>
<Text style={styles.badgeText}>
{responses.length}/{items.length}
</Text>
</View>
</>
)}
</TouchableOpacity>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
loading: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
content: {
flex: 1,
},
scrollContent: {
padding: 16,
},
errorContainer: {
backgroundColor: '#ffebee',
padding: 12,
borderRadius: 8,
marginTop: 12,
},
errorText: {
color: '#dc3545',
textAlign: 'center',
},
footer: {
backgroundColor: '#fff',
borderTopWidth: 1,
borderTopColor: '#eee',
padding: 16,
},
navigation: {
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: 16,
},
navButton: {
flexDirection: 'row',
alignItems: 'center',
padding: 8,
gap: 4,
},
navButtonDisabled: {
opacity: 0.5,
},
navText: {
color: '#007AFF',
fontSize: 16,
},
navTextDisabled: {
color: '#ccc',
},
actions: {
flexDirection: 'row',
gap: 12,
},
skipButton: {
flex: 1,
padding: 14,
borderRadius: 8,
backgroundColor: '#f5f5f5',
alignItems: 'center',
},
skipText: {
color: '#666',
fontWeight: '600',
},
submitButton: {
flex: 2,
padding: 14,
borderRadius: 8,
backgroundColor: '#007AFF',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
gap: 8,
},
submitButtonDisabled: {
backgroundColor: '#ccc',
},
submitText: {
color: '#fff',
fontWeight: '600',
fontSize: 16,
},
badge: {
backgroundColor: 'rgba(255,255,255,0.3)',
paddingHorizontal: 8,
paddingVertical: 2,
borderRadius: 10,
},
badgeText: {
color: '#fff',
fontSize: 12,
fontWeight: '600',
},
});