- Updated docs and inventory files - Added new architecture docs Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
284 lines
6.9 KiB
TypeScript
284 lines
6.9 KiB
TypeScript
/**
|
|
* Checklist Screen
|
|
* ERP Transportistas
|
|
* Sprint S8 - TASK-007
|
|
*
|
|
* Pre-trip checklist screen.
|
|
*/
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
ScrollView,
|
|
TouchableOpacity,
|
|
ActivityIndicator,
|
|
Alert,
|
|
} from 'react-native';
|
|
import { useNavigation, useRoute } from '@react-navigation/native';
|
|
import type { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
|
import type { RouteProp } from '@react-navigation/native';
|
|
import { Ionicons } from '@expo/vector-icons';
|
|
import { useViajeStore } from '../store';
|
|
import type { RootStackParamList, ChecklistItem } from '../types';
|
|
|
|
type NavigationProp = NativeStackNavigationProp<RootStackParamList>;
|
|
type ChecklistRouteProp = RouteProp<RootStackParamList, 'Checklist'>;
|
|
|
|
export function ChecklistScreen(): JSX.Element {
|
|
const navigation = useNavigation<NavigationProp>();
|
|
const route = useRoute<ChecklistRouteProp>();
|
|
const {
|
|
checklist,
|
|
cargarChecklist,
|
|
completarItemChecklist,
|
|
guardarChecklist,
|
|
isLoading,
|
|
} = useViajeStore();
|
|
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
|
|
useEffect(() => {
|
|
cargarChecklist(route.params.viajeId);
|
|
}, [route.params.viajeId]);
|
|
|
|
const toggleItem = (itemId: string, currentState: boolean): void => {
|
|
completarItemChecklist(itemId, !currentState);
|
|
};
|
|
|
|
const allRequiredCompleted = (): boolean => {
|
|
return checklist
|
|
.filter((item) => item.requerido)
|
|
.every((item) => item.completado);
|
|
};
|
|
|
|
const handleSave = async (): Promise<void> => {
|
|
if (!allRequiredCompleted()) {
|
|
Alert.alert(
|
|
'Items Pendientes',
|
|
'Debes completar todos los items requeridos antes de continuar'
|
|
);
|
|
return;
|
|
}
|
|
|
|
setIsSaving(true);
|
|
|
|
try {
|
|
await guardarChecklist();
|
|
Alert.alert('Éxito', 'Checklist guardado correctamente', [
|
|
{ text: 'OK', onPress: () => navigation.goBack() },
|
|
]);
|
|
} catch (error) {
|
|
Alert.alert('Error', 'No se pudo guardar el checklist');
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
const renderItem = (item: ChecklistItem): JSX.Element => (
|
|
<TouchableOpacity
|
|
key={item.id}
|
|
style={[styles.itemContainer, item.completado && styles.itemCompleted]}
|
|
onPress={() => toggleItem(item.id, item.completado)}
|
|
>
|
|
<View style={styles.itemCheckbox}>
|
|
{item.completado ? (
|
|
<Ionicons name="checkbox" size={28} color="#22c55e" />
|
|
) : (
|
|
<Ionicons name="square-outline" size={28} color="#94a3b8" />
|
|
)}
|
|
</View>
|
|
<View style={styles.itemContent}>
|
|
<Text style={[styles.itemDescription, item.completado && styles.itemTextCompleted]}>
|
|
{item.descripcion}
|
|
</Text>
|
|
{item.requerido && (
|
|
<Text style={styles.itemRequired}>Requerido</Text>
|
|
)}
|
|
</View>
|
|
{item.foto && (
|
|
<Ionicons name="camera" size={20} color="#3b82f6" />
|
|
)}
|
|
</TouchableOpacity>
|
|
);
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<View style={styles.loadingContainer}>
|
|
<ActivityIndicator size="large" color="#2563eb" />
|
|
<Text style={styles.loadingText}>Cargando checklist...</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const completedCount = checklist.filter((item) => item.completado).length;
|
|
const totalCount = checklist.length;
|
|
const progress = totalCount > 0 ? (completedCount / totalCount) * 100 : 0;
|
|
|
|
return (
|
|
<View style={styles.container}>
|
|
{/* Progress Header */}
|
|
<View style={styles.header}>
|
|
<Text style={styles.headerTitle}>Checklist Pre-viaje</Text>
|
|
<Text style={styles.headerProgress}>
|
|
{completedCount} de {totalCount} completados
|
|
</Text>
|
|
<View style={styles.progressBar}>
|
|
<View style={[styles.progressFill, { width: `${progress}%` }]} />
|
|
</View>
|
|
</View>
|
|
|
|
{/* Checklist Items */}
|
|
<ScrollView style={styles.content}>
|
|
{checklist.length === 0 ? (
|
|
<View style={styles.emptyContainer}>
|
|
<Ionicons name="clipboard-outline" size={48} color="#94a3b8" />
|
|
<Text style={styles.emptyText}>No hay items en el checklist</Text>
|
|
</View>
|
|
) : (
|
|
checklist.map(renderItem)
|
|
)}
|
|
</ScrollView>
|
|
|
|
{/* Save Button */}
|
|
<View style={styles.footer}>
|
|
<TouchableOpacity
|
|
style={[
|
|
styles.saveButton,
|
|
(!allRequiredCompleted() || isSaving) && styles.saveButtonDisabled,
|
|
]}
|
|
onPress={handleSave}
|
|
disabled={!allRequiredCompleted() || isSaving}
|
|
>
|
|
{isSaving ? (
|
|
<ActivityIndicator color="#ffffff" />
|
|
) : (
|
|
<>
|
|
<Ionicons name="checkmark-circle" size={24} color="#ffffff" />
|
|
<Text style={styles.saveButtonText}>Guardar y Continuar</Text>
|
|
</>
|
|
)}
|
|
</TouchableOpacity>
|
|
</View>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: '#f1f5f9',
|
|
},
|
|
loadingContainer: {
|
|
flex: 1,
|
|
justifyContent: 'center',
|
|
alignItems: 'center',
|
|
},
|
|
loadingText: {
|
|
marginTop: 12,
|
|
fontSize: 16,
|
|
color: '#64748b',
|
|
},
|
|
header: {
|
|
backgroundColor: '#ffffff',
|
|
padding: 16,
|
|
borderBottomWidth: 1,
|
|
borderBottomColor: '#e2e8f0',
|
|
},
|
|
headerTitle: {
|
|
fontSize: 20,
|
|
fontWeight: 'bold',
|
|
color: '#1e293b',
|
|
},
|
|
headerProgress: {
|
|
fontSize: 14,
|
|
color: '#64748b',
|
|
marginTop: 4,
|
|
},
|
|
progressBar: {
|
|
height: 6,
|
|
backgroundColor: '#e2e8f0',
|
|
borderRadius: 3,
|
|
marginTop: 12,
|
|
overflow: 'hidden',
|
|
},
|
|
progressFill: {
|
|
height: '100%',
|
|
backgroundColor: '#22c55e',
|
|
borderRadius: 3,
|
|
},
|
|
content: {
|
|
flex: 1,
|
|
padding: 16,
|
|
},
|
|
emptyContainer: {
|
|
alignItems: 'center',
|
|
paddingVertical: 48,
|
|
},
|
|
emptyText: {
|
|
fontSize: 16,
|
|
color: '#64748b',
|
|
marginTop: 12,
|
|
},
|
|
itemContainer: {
|
|
backgroundColor: '#ffffff',
|
|
borderRadius: 12,
|
|
padding: 16,
|
|
marginBottom: 8,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
shadowColor: '#000',
|
|
shadowOffset: { width: 0, height: 1 },
|
|
shadowOpacity: 0.05,
|
|
shadowRadius: 2,
|
|
elevation: 1,
|
|
},
|
|
itemCompleted: {
|
|
backgroundColor: '#f0fdf4',
|
|
borderWidth: 1,
|
|
borderColor: '#bbf7d0',
|
|
},
|
|
itemCheckbox: {
|
|
marginRight: 12,
|
|
},
|
|
itemContent: {
|
|
flex: 1,
|
|
},
|
|
itemDescription: {
|
|
fontSize: 16,
|
|
color: '#1e293b',
|
|
},
|
|
itemTextCompleted: {
|
|
color: '#16a34a',
|
|
},
|
|
itemRequired: {
|
|
fontSize: 12,
|
|
color: '#f59e0b',
|
|
marginTop: 4,
|
|
},
|
|
footer: {
|
|
padding: 16,
|
|
backgroundColor: '#ffffff',
|
|
borderTopWidth: 1,
|
|
borderTopColor: '#e2e8f0',
|
|
},
|
|
saveButton: {
|
|
backgroundColor: '#22c55e',
|
|
borderRadius: 12,
|
|
padding: 16,
|
|
flexDirection: 'row',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
saveButtonDisabled: {
|
|
backgroundColor: '#86efac',
|
|
},
|
|
saveButtonText: {
|
|
color: '#ffffff',
|
|
fontSize: 16,
|
|
fontWeight: '600',
|
|
marginLeft: 8,
|
|
},
|
|
});
|