249 lines
11 KiB
JavaScript
249 lines
11 KiB
JavaScript
import { StatusBar } from 'expo-status-bar';
|
||
import { StyleSheet, Text, View, TextInput, TouchableOpacity, ActivityIndicator, ScrollView, Alert } from 'react-native';
|
||
import { useState } from 'react';
|
||
|
||
const API_URL = 'http://10.137.112.65:8000';
|
||
|
||
export default function App() {
|
||
const [screen, setScreen] = useState('login');
|
||
const [email, setEmail] = useState('');
|
||
const [password, setPassword] = useState('');
|
||
const [token, setToken] = useState(null);
|
||
const [domicilioId, setDomicilioId] = useState(null);
|
||
const [eta, setEta] = useState(null);
|
||
const [loading, setLoading] = useState(false);
|
||
const [colonia, setColonia] = useState('');
|
||
const [direccion, setDireccion] = useState('');
|
||
|
||
const register = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await fetch(`${API_URL}/auth/register`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ email, password }),
|
||
});
|
||
const data = await res.json();
|
||
if (data.access_token) {
|
||
setToken(data.access_token);
|
||
setScreen('domicilio');
|
||
} else {
|
||
Alert.alert('Error', data.detail || 'Error al registrar');
|
||
}
|
||
} catch (e) {
|
||
Alert.alert('Error', 'No se pudo conectar al servidor');
|
||
}
|
||
setLoading(false);
|
||
};
|
||
|
||
const login = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await fetch(`${API_URL}/auth/login`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ email, password }),
|
||
});
|
||
const data = await res.json();
|
||
if (data.access_token) {
|
||
setToken(data.access_token);
|
||
setScreen('domicilio');
|
||
} else {
|
||
Alert.alert('Error', 'Credenciales incorrectas');
|
||
}
|
||
} catch (e) {
|
||
Alert.alert('Error', 'No se pudo conectar al servidor');
|
||
}
|
||
setLoading(false);
|
||
};
|
||
|
||
const guardarDomicilio = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await fetch(`${API_URL}/domicilios`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${token}`,
|
||
},
|
||
body: JSON.stringify({
|
||
direccion,
|
||
colonia,
|
||
lat: 20.5185,
|
||
lng: -100.8450,
|
||
}),
|
||
});
|
||
const data = await res.json();
|
||
if (data.id) {
|
||
setDomicilioId(data.id);
|
||
setScreen('eta');
|
||
consultarETA(data.id);
|
||
} else {
|
||
Alert.alert('Error', data.detail || 'Colonia no encontrada');
|
||
}
|
||
} catch (e) {
|
||
Alert.alert('Error', 'No se pudo guardar el domicilio');
|
||
}
|
||
setLoading(false);
|
||
};
|
||
|
||
const consultarETA = async (id) => {
|
||
setLoading(true);
|
||
try {
|
||
const res = await fetch(`${API_URL}/eta/${id}`, {
|
||
headers: { 'Authorization': `Bearer ${token}` },
|
||
});
|
||
const data = await res.json();
|
||
setEta(data);
|
||
} catch (e) {
|
||
Alert.alert('Error', 'No se pudo obtener el ETA');
|
||
}
|
||
setLoading(false);
|
||
};
|
||
|
||
if (screen === 'login') return (
|
||
<ScrollView contentContainerStyle={styles.container}>
|
||
<StatusBar style="auto" />
|
||
<Text style={styles.title}>🚛 BasuraApp</Text>
|
||
<Text style={styles.subtitle}>Ingresa a tu cuenta</Text>
|
||
<TextInput style={styles.input} placeholder="Email" value={email}
|
||
onChangeText={setEmail} keyboardType="email-address" autoCapitalize="none" />
|
||
<TextInput style={styles.input} placeholder="Contraseña" value={password}
|
||
onChangeText={setPassword} secureTextEntry />
|
||
{loading ? <ActivityIndicator color="#1a7a4a" /> : <>
|
||
<TouchableOpacity style={styles.btn} onPress={login}>
|
||
<Text style={styles.btnText}>Iniciar sesión</Text>
|
||
</TouchableOpacity>
|
||
<TouchableOpacity style={styles.btnSecondary} onPress={register}>
|
||
<Text style={styles.btnSecondaryText}>Registrarme</Text>
|
||
</TouchableOpacity>
|
||
</>}
|
||
</ScrollView>
|
||
);
|
||
|
||
if (screen === 'domicilio') return (
|
||
<ScrollView contentContainerStyle={styles.container}>
|
||
<Text style={styles.title}>📍 Mi domicilio</Text>
|
||
<Text style={styles.subtitle}>¿En qué colonia vives?</Text>
|
||
<TextInput style={styles.input} placeholder="Dirección (ej: Calle Morelos 123)"
|
||
value={direccion} onChangeText={setDireccion} />
|
||
<TextInput style={styles.input} placeholder="Colonia (ej: Zona Centro)"
|
||
value={colonia} onChangeText={setColonia} />
|
||
<Text style={styles.hint}>Colonias disponibles: Zona Centro, Las Arboledas, Trojes, San Juanico, Los Olivos, Rancho Seco, Las Insurgentes</Text>
|
||
{loading ? <ActivityIndicator color="#1a7a4a" /> :
|
||
<TouchableOpacity style={styles.btn} onPress={guardarDomicilio}>
|
||
<Text style={styles.btnText}>Guardar y ver horario</Text>
|
||
</TouchableOpacity>}
|
||
</ScrollView>
|
||
);
|
||
|
||
if (screen === 'eta') return (
|
||
<ScrollView contentContainerStyle={styles.container}>
|
||
<Text style={styles.title}>🕐 Horario de recolección</Text>
|
||
{loading ? <ActivityIndicator size="large" color="#1a7a4a" /> : eta ? <>
|
||
<View style={styles.etaCard}>
|
||
<Text style={styles.etaEvento}>{eta.evento === 'TRUCK_PROXIMITY' ? '🚨 ¡Camión cercano!' :
|
||
eta.evento === 'ROUTE_START' ? '🟢 Ruta iniciada' :
|
||
eta.evento === 'ROUTE_COMPLETED' ? '✅ Servicio finalizado' : '🚛 En camino'}</Text>
|
||
<Text style={styles.etaMensaje}>{eta.mensaje}</Text>
|
||
<View style={styles.ventanaBox}>
|
||
<Text style={styles.ventanaLabel}>Ventana de llegada</Text>
|
||
<Text style={styles.ventanaHora}>{eta.ventana_inicio} – {eta.ventana_fin}</Text>
|
||
</View>
|
||
<Text style={styles.coloniaText}>📍 {eta.colonia} · {eta.route_id}</Text>
|
||
</View>
|
||
<View style={styles.privacyBox}>
|
||
<Text style={styles.privacyText}>🔒 Solo ves la información de tu zona. No se muestra la ruta completa del camión.</Text>
|
||
</View>
|
||
<TouchableOpacity style={styles.btn} onPress={() => consultarETA(domicilioId)}>
|
||
<Text style={styles.btnText}>Actualizar</Text>
|
||
</TouchableOpacity>
|
||
<TouchableOpacity style={styles.btnSecondary} onPress={() => setScreen('separacion')}>
|
||
<Text style={styles.btnSecondaryText}>📚 Guía de separación</Text>
|
||
</TouchableOpacity>
|
||
<TouchableOpacity style={styles.btnSecondary} onPress={() => setScreen('reporte')}>
|
||
<Text style={styles.btnSecondaryText}>📋 Reportar incidencia</Text>
|
||
</TouchableOpacity>
|
||
</> : <Text>Sin datos</Text>}
|
||
</ScrollView>
|
||
);
|
||
|
||
if (screen === 'separacion') return (
|
||
<ScrollView contentContainerStyle={styles.container}>
|
||
<Text style={styles.title}>♻️ Guía de separación</Text>
|
||
{[
|
||
{ emoji: '🟢', tipo: 'Orgánicos', ejemplos: 'Cáscaras, restos de comida, café, frutas' },
|
||
{ emoji: '🔵', tipo: 'Reciclables', ejemplos: 'Papel, cartón, plástico, vidrio, metal' },
|
||
{ emoji: '🔴', tipo: 'Sanitarios', ejemplos: 'Pañales, papel higiénico, gasas, algodón' },
|
||
{ emoji: '⚠️', tipo: 'Especiales', ejemplos: 'Pilas, medicamentos, electrónicos, pinturas' },
|
||
].map(item => (
|
||
<View key={item.tipo} style={styles.separacionCard}>
|
||
<Text style={styles.separacionEmoji}>{item.emoji}</Text>
|
||
<View>
|
||
<Text style={styles.separacionTipo}>{item.tipo}</Text>
|
||
<Text style={styles.separacionEjemplos}>{item.ejemplos}</Text>
|
||
</View>
|
||
</View>
|
||
))}
|
||
<TouchableOpacity style={styles.btnSecondary} onPress={() => setScreen('eta')}>
|
||
<Text style={styles.btnSecondaryText}>← Volver al horario</Text>
|
||
</TouchableOpacity>
|
||
</ScrollView>
|
||
);
|
||
|
||
if (screen === 'reporte') return (
|
||
<ScrollView contentContainerStyle={styles.container}>
|
||
<Text style={styles.title}>📋 Reportar incidencia</Text>
|
||
<Text style={styles.subtitle}>¿Qué problema tuviste?</Text>
|
||
{['El camión no pasó', 'Pasó fuera de horario', 'No recogió mis residuos', 'Otro'].map(tipo => (
|
||
<TouchableOpacity key={tipo} style={styles.btnSecondary}
|
||
onPress={async () => {
|
||
await fetch(`${API_URL}/reportes?domicilio_id=${domicilioId}&tipo=${tipo}&descripcion=${tipo}`, {
|
||
method: 'POST',
|
||
headers: { 'Authorization': `Bearer ${token}` }
|
||
});
|
||
Alert.alert('¡Gracias!', 'Tu reporte fue enviado correctamente.');
|
||
setScreen('eta');
|
||
}}>
|
||
<Text style={styles.btnSecondaryText}>{tipo}</Text>
|
||
</TouchableOpacity>
|
||
))}
|
||
<TouchableOpacity style={styles.btnSecondary} onPress={() => setScreen('eta')}>
|
||
<Text style={styles.btnSecondaryText}>← Cancelar</Text>
|
||
</TouchableOpacity>
|
||
</ScrollView>
|
||
);
|
||
}
|
||
|
||
const styles = StyleSheet.create({
|
||
container: { flexGrow: 1, backgroundColor: '#f0f4f8', alignItems: 'center',
|
||
justifyContent: 'center', padding: 24 },
|
||
title: { fontSize: 28, fontWeight: 'bold', color: '#1a7a4a', marginBottom: 6, textAlign: 'center' },
|
||
subtitle: { fontSize: 15, color: '#555', marginBottom: 24, textAlign: 'center' },
|
||
input: { width: '100%', backgroundColor: '#fff', borderRadius: 10, padding: 14,
|
||
fontSize: 15, marginBottom: 12, borderWidth: 1, borderColor: '#ddd' },
|
||
btn: { width: '100%', backgroundColor: '#1a7a4a', borderRadius: 10,
|
||
padding: 16, alignItems: 'center', marginTop: 8 },
|
||
btnText: { color: '#fff', fontWeight: 'bold', fontSize: 16 },
|
||
btnSecondary: { width: '100%', borderRadius: 10, padding: 16,
|
||
alignItems: 'center', marginTop: 8, borderWidth: 1, borderColor: '#1a7a4a' },
|
||
btnSecondaryText: { color: '#1a7a4a', fontWeight: 'bold', fontSize: 15 },
|
||
hint: { fontSize: 11, color: '#888', marginBottom: 16, textAlign: 'center' },
|
||
etaCard: { width: '100%', backgroundColor: '#fff', borderRadius: 16,
|
||
padding: 20, marginBottom: 16, borderWidth: 1, borderColor: '#d0e8d8' },
|
||
etaEvento: { fontSize: 18, fontWeight: 'bold', color: '#1a7a4a', marginBottom: 8 },
|
||
etaMensaje: { fontSize: 15, color: '#333', marginBottom: 16, lineHeight: 22 },
|
||
ventanaBox: { backgroundColor: '#e8f5ee', borderRadius: 10, padding: 14, marginBottom: 12 },
|
||
ventanaLabel: { fontSize: 12, color: '#555', marginBottom: 4 },
|
||
ventanaHora: { fontSize: 24, fontWeight: 'bold', color: '#1a7a4a' },
|
||
coloniaText: { fontSize: 12, color: '#888' },
|
||
privacyBox: { width: '100%', backgroundColor: '#fff8e1', borderRadius: 10,
|
||
padding: 14, marginBottom: 16, borderWidth: 1, borderColor: '#ffe082' },
|
||
privacyText: { fontSize: 12, color: '#795548', textAlign: 'center' },
|
||
separacionCard: { width: '100%', backgroundColor: '#fff', borderRadius: 12,
|
||
padding: 16, marginBottom: 12, flexDirection: 'row', alignItems: 'center',
|
||
gap: 14, borderWidth: 1, borderColor: '#ddd' },
|
||
separacionEmoji: { fontSize: 32 },
|
||
separacionTipo: { fontSize: 16, fontWeight: 'bold', color: '#333' },
|
||
separacionEjemplos: { fontSize: 12, color: '#666', marginTop: 2 },
|
||
}); |