393 lines
9.8 KiB
TypeScript
393 lines
9.8 KiB
TypeScript
import { ScrollView, View, Text, StyleSheet, Image } from "react-native";
|
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
import { Ionicons } from "@expo/vector-icons";
|
|
import { Redirect } from "expo-router";
|
|
|
|
import { COLORS } from "../constants/colors";
|
|
import { useApp } from "../context/AppContext";
|
|
import type { NotificationType } from "../services/tracking.service";
|
|
|
|
type Severity = "info" | "attention" | "important" | "critical";
|
|
|
|
const meta: Record<
|
|
NotificationType,
|
|
{
|
|
severity: Severity;
|
|
icon: keyof typeof Ionicons.glyphMap;
|
|
color: string;
|
|
bg: string;
|
|
badge: string;
|
|
}
|
|
> = {
|
|
ROUTE_START: {
|
|
severity: "info",
|
|
icon: "navigate",
|
|
color: "#3B82F6",
|
|
bg: "#EFF6FF",
|
|
badge: "Informativo",
|
|
},
|
|
TRUCK_PROXIMITY: {
|
|
severity: "attention",
|
|
icon: "time-outline",
|
|
color: "#F59E0B",
|
|
bg: "#FEF3C7",
|
|
badge: "Atención",
|
|
},
|
|
TRUCK_ARRIVED: {
|
|
severity: "important",
|
|
icon: "notifications",
|
|
color: "#22C55E",
|
|
bg: "#ECFDF5",
|
|
badge: "Importante",
|
|
},
|
|
ROUTE_COMPLETED: {
|
|
severity: "info",
|
|
icon: "checkmark-done",
|
|
color: "#6B7280",
|
|
bg: "#F3F4F6",
|
|
badge: "Informativo",
|
|
},
|
|
DELAY: {
|
|
severity: "critical",
|
|
icon: "warning-outline",
|
|
color: "#EF4444",
|
|
bg: "#FEF2F2",
|
|
badge: "Crítico",
|
|
},
|
|
MECHANICAL_FAILURE: {
|
|
severity: "critical",
|
|
icon: "warning",
|
|
color: "#EF4444",
|
|
bg: "#FEF2F2",
|
|
badge: "Crítico",
|
|
},
|
|
};
|
|
|
|
const STATUS_LABEL: Record<string, { label: string; color: string }> = {
|
|
EN_RUTA: { label: "ACTIVA", color: "#22C55E" },
|
|
DETENIDO: { label: "DETENIDO", color: "#F59E0B" },
|
|
FALLA: { label: "FALLA", color: "#EF4444" },
|
|
FINALIZADO: { label: "FINALIZADO", color: "#6B7280" },
|
|
ESPERA: { label: "EN ESPERA", color: "#6B7280" },
|
|
};
|
|
|
|
export default function AlertsScreen() {
|
|
const { user, notifications, route } = useApp();
|
|
|
|
if (!user) return <Redirect href="/login" />;
|
|
|
|
const statusInfo =
|
|
STATUS_LABEL[route?.status ?? "ESPERA"] ?? STATUS_LABEL.ESPERA;
|
|
|
|
return (
|
|
<SafeAreaView
|
|
style={{ flex: 1, backgroundColor: COLORS.background }}
|
|
edges={["top"]}
|
|
>
|
|
<ScrollView
|
|
showsVerticalScrollIndicator={false}
|
|
contentContainerStyle={{ paddingBottom: 130 }}
|
|
>
|
|
{/* Hero con título + count + camion image en banner azul */}
|
|
<View style={styles.heroSection}>
|
|
<View style={styles.heroTextWrap}>
|
|
<Text style={styles.headerTitle}>Alertas</Text>
|
|
<Text style={styles.headerCount}>
|
|
{notifications.length}{" "}
|
|
{notifications.length === 1
|
|
? "notificación"
|
|
: "notificaciones"}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.heroBanner}>
|
|
<Image
|
|
source={require("../../assets/illustrations/truck-city.png")}
|
|
style={styles.truckImage}
|
|
resizeMode="cover"
|
|
/>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Estado de ruta */}
|
|
<View
|
|
style={[
|
|
styles.statusBanner,
|
|
{ backgroundColor: `${statusInfo.color}15` },
|
|
]}
|
|
>
|
|
<View
|
|
style={[
|
|
styles.statusIcon,
|
|
{ backgroundColor: statusInfo.color },
|
|
]}
|
|
>
|
|
<Ionicons name="bus-outline" size={20} color="#FFFFFF" />
|
|
</View>
|
|
<View style={{ flex: 1 }}>
|
|
<Text style={[styles.statusTitle, { color: statusInfo.color }]}>
|
|
ESTADO DE RUTA: {statusInfo.label}
|
|
</Text>
|
|
<Text style={styles.statusBody}>
|
|
{statusInfo.label === "ACTIVA"
|
|
? "El servicio de recolección sigue en curso."
|
|
: "Sin servicio activo en este momento."}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
|
|
{/* Timeline de notificaciones */}
|
|
{notifications.length === 0 ? (
|
|
<View style={styles.emptyBox}>
|
|
<Ionicons name="leaf-outline" size={40} color="#9CA3AF" />
|
|
<Text style={styles.emptyText}>
|
|
Aún no hay alertas. Las recibirás cuando el camión avance en
|
|
tu ruta.
|
|
</Text>
|
|
</View>
|
|
) : (
|
|
<View style={{ paddingHorizontal: 20, marginTop: 12 }}>
|
|
{notifications.map((n, idx) => {
|
|
const m = meta[n.type];
|
|
const isLast = idx === notifications.length - 1;
|
|
const timeStr = new Date(n.createdAt).toLocaleTimeString(
|
|
"es-MX",
|
|
{ hour: "2-digit", minute: "2-digit" },
|
|
);
|
|
return (
|
|
<View key={n.id} style={styles.timelineRow}>
|
|
{/* Columna izquierda: dot + línea */}
|
|
<View style={styles.timelineLeft}>
|
|
<View
|
|
style={[
|
|
styles.timelineDot,
|
|
{ backgroundColor: m.color },
|
|
]}
|
|
>
|
|
<Ionicons name={m.icon} size={14} color="#FFFFFF" />
|
|
</View>
|
|
{!isLast && (
|
|
<View
|
|
style={[
|
|
styles.timelineLine,
|
|
{ backgroundColor: `${m.color}50` },
|
|
]}
|
|
/>
|
|
)}
|
|
<Text style={styles.timelineTime}>{timeStr}</Text>
|
|
</View>
|
|
|
|
{/* Card */}
|
|
<View
|
|
style={[
|
|
styles.notifCard,
|
|
{ backgroundColor: m.bg },
|
|
]}
|
|
>
|
|
<View style={styles.notifHeader}>
|
|
<Text style={styles.notifTitle}>{n.title}</Text>
|
|
<View
|
|
style={[
|
|
styles.badge,
|
|
{ backgroundColor: `${m.color}25` },
|
|
]}
|
|
>
|
|
<Text style={[styles.badgeText, { color: m.color }]}>
|
|
{m.badge}
|
|
</Text>
|
|
</View>
|
|
</View>
|
|
<Text style={styles.notifBody}>{n.body}</Text>
|
|
</View>
|
|
</View>
|
|
);
|
|
})}
|
|
</View>
|
|
)}
|
|
|
|
{/* Consejo del día */}
|
|
<View style={styles.tipCard}>
|
|
<View style={styles.tipIcon}>
|
|
<Ionicons name="leaf" size={20} color="#0E8A61" />
|
|
</View>
|
|
<View style={{ flex: 1 }}>
|
|
<Text style={styles.tipTitle}>Consejo del día</Text>
|
|
<Text style={styles.tipBody}>
|
|
Separa correctamente tus residuos. Tu acción hace la diferencia.
|
|
</Text>
|
|
</View>
|
|
<Image
|
|
source={require("../../assets/illustrations/bins-cute.png")}
|
|
style={styles.tipImage}
|
|
resizeMode="contain"
|
|
/>
|
|
</View>
|
|
</ScrollView>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
heroSection: {
|
|
backgroundColor: "#DBEAFE",
|
|
paddingBottom: 0,
|
|
marginBottom: 20,
|
|
},
|
|
heroTextWrap: {
|
|
paddingHorizontal: 20,
|
|
paddingTop: 12,
|
|
paddingBottom: 8,
|
|
},
|
|
heroBanner: {
|
|
height: 150,
|
|
width: "100%",
|
|
backgroundColor: "#DBEAFE",
|
|
overflow: "hidden",
|
|
},
|
|
headerTitle: {
|
|
fontSize: 32,
|
|
fontWeight: "800",
|
|
color: "#0F172A",
|
|
},
|
|
headerCount: {
|
|
fontSize: 14,
|
|
color: "#6B7280",
|
|
marginTop: 2,
|
|
},
|
|
truckImage: {
|
|
width: "100%",
|
|
height: "100%",
|
|
},
|
|
statusBanner: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
marginHorizontal: 20,
|
|
padding: 14,
|
|
borderRadius: 14,
|
|
marginBottom: 4,
|
|
},
|
|
statusIcon: {
|
|
width: 38,
|
|
height: 38,
|
|
borderRadius: 19,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
marginRight: 12,
|
|
},
|
|
statusTitle: {
|
|
fontSize: 13,
|
|
fontWeight: "800",
|
|
},
|
|
statusBody: {
|
|
fontSize: 12,
|
|
color: "#374151",
|
|
marginTop: 2,
|
|
},
|
|
emptyBox: {
|
|
alignItems: "center",
|
|
padding: 40,
|
|
marginHorizontal: 20,
|
|
marginTop: 16,
|
|
backgroundColor: "#FFFFFF",
|
|
borderRadius: 14,
|
|
},
|
|
emptyText: {
|
|
fontSize: 13,
|
|
color: "#6B7280",
|
|
textAlign: "center",
|
|
marginTop: 10,
|
|
},
|
|
timelineRow: {
|
|
flexDirection: "row",
|
|
marginBottom: 14,
|
|
},
|
|
timelineLeft: {
|
|
width: 56,
|
|
alignItems: "center",
|
|
},
|
|
timelineDot: {
|
|
width: 32,
|
|
height: 32,
|
|
borderRadius: 16,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
},
|
|
timelineLine: {
|
|
flex: 1,
|
|
width: 2,
|
|
marginTop: 2,
|
|
marginBottom: 2,
|
|
},
|
|
timelineTime: {
|
|
fontSize: 10,
|
|
color: "#9CA3AF",
|
|
marginTop: 4,
|
|
},
|
|
notifCard: {
|
|
flex: 1,
|
|
borderRadius: 14,
|
|
padding: 14,
|
|
marginLeft: 4,
|
|
},
|
|
notifHeader: {
|
|
flexDirection: "row",
|
|
justifyContent: "space-between",
|
|
alignItems: "flex-start",
|
|
marginBottom: 6,
|
|
},
|
|
notifTitle: {
|
|
fontSize: 14,
|
|
fontWeight: "800",
|
|
color: "#0F172A",
|
|
flex: 1,
|
|
paddingRight: 8,
|
|
},
|
|
badge: {
|
|
paddingHorizontal: 8,
|
|
paddingVertical: 3,
|
|
borderRadius: 8,
|
|
},
|
|
badgeText: {
|
|
fontSize: 10,
|
|
fontWeight: "700",
|
|
},
|
|
notifBody: {
|
|
fontSize: 13,
|
|
color: "#374151",
|
|
lineHeight: 18,
|
|
},
|
|
tipCard: {
|
|
flexDirection: "row",
|
|
alignItems: "center",
|
|
backgroundColor: "#ECFDF5",
|
|
borderRadius: 16,
|
|
padding: 14,
|
|
marginHorizontal: 20,
|
|
marginTop: 16,
|
|
},
|
|
tipIcon: {
|
|
width: 42,
|
|
height: 42,
|
|
borderRadius: 21,
|
|
backgroundColor: "#FFFFFF",
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
marginRight: 12,
|
|
},
|
|
tipTitle: {
|
|
fontSize: 14,
|
|
fontWeight: "800",
|
|
color: "#065F46",
|
|
marginBottom: 4,
|
|
},
|
|
tipBody: {
|
|
fontSize: 12,
|
|
color: "#065F46",
|
|
lineHeight: 16,
|
|
},
|
|
tipImage: {
|
|
width: 70,
|
|
height: 70,
|
|
marginLeft: 8,
|
|
},
|
|
});
|