feat: add UI screens design
BIN
frontend/assets/illustrations/avatar-ana.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
frontend/assets/illustrations/battery-meds.png
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
frontend/assets/illustrations/bins-cute.png
Normal file
|
After Width: | Height: | Size: 598 KiB |
BIN
frontend/assets/illustrations/eco-bins.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
frontend/assets/illustrations/garbage-truck.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
frontend/assets/illustrations/impact-leaf.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
frontend/assets/illustrations/organic-food.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
frontend/assets/illustrations/pet-bottles.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
frontend/assets/illustrations/recycling.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
frontend/assets/illustrations/sanitary.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
frontend/assets/illustrations/trees-banner.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
frontend/assets/illustrations/truck-banner.png
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
frontend/assets/illustrations/truck-city.png
Normal file
|
After Width: | Height: | Size: 964 KiB |
@@ -1,64 +1,392 @@
|
|||||||
import { ScrollView, Text } from "react-native";
|
import { ScrollView, View, Text, StyleSheet, Image } from "react-native";
|
||||||
import { SafeAreaView } from "react-native-safe-area-context";
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { Redirect } from "expo-router";
|
import { Redirect } from "expo-router";
|
||||||
|
|
||||||
import { COLORS } from "../constants/colors";
|
import { COLORS } from "../constants/colors";
|
||||||
|
|
||||||
import SectionTitle from "../components/SectionTitle";
|
|
||||||
import AlertItem from "@/components/Alertltem";
|
|
||||||
|
|
||||||
import { useApp } from "../context/AppContext";
|
import { useApp } from "../context/AppContext";
|
||||||
import { notificationTypeToAlertType } from "../lib/notification-mapper";
|
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() {
|
export default function AlertsScreen() {
|
||||||
const { user, notifications } = useApp();
|
const { user, notifications, route } = useApp();
|
||||||
|
|
||||||
if (!user) {
|
if (!user) return <Redirect href="/login" />;
|
||||||
return <Redirect href="/login" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
const statusInfo =
|
||||||
<SafeAreaView
|
STATUS_LABEL[route?.status ?? "ESPERA"] ?? STATUS_LABEL.ESPERA;
|
||||||
style={{
|
|
||||||
flex: 1,
|
return (
|
||||||
backgroundColor: COLORS.background,
|
<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` },
|
||||||
|
]}
|
||||||
>
|
>
|
||||||
<ScrollView
|
<View
|
||||||
showsVerticalScrollIndicator={false}
|
style={[
|
||||||
contentContainerStyle={{
|
styles.statusIcon,
|
||||||
padding: 20,
|
{ backgroundColor: statusInfo.color },
|
||||||
paddingBottom: 120,
|
]}
|
||||||
}}
|
>
|
||||||
>
|
<Ionicons name="bus-outline" size={20} color="#FFFFFF" />
|
||||||
<SectionTitle title="Alertas" />
|
</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>
|
||||||
|
|
||||||
<Text
|
{/* Timeline de notificaciones */}
|
||||||
style={{
|
{notifications.length === 0 ? (
|
||||||
color: "#6B7280",
|
<View style={styles.emptyBox}>
|
||||||
fontSize: 14,
|
<Ionicons name="leaf-outline" size={40} color="#9CA3AF" />
|
||||||
marginBottom: 24,
|
<Text style={styles.emptyText}>
|
||||||
marginLeft: 12,
|
Aún no hay alertas. Las recibirás cuando el camión avance en
|
||||||
}}
|
tu ruta.
|
||||||
>
|
</Text>
|
||||||
{notifications.length > 0
|
</View>
|
||||||
? `${notifications.length} notificaciones`
|
) : (
|
||||||
: "Aún no hay alertas. Vuelve al inicio y desliza para actualizar."}
|
<View style={{ paddingHorizontal: 20, marginTop: 12 }}>
|
||||||
</Text>
|
{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>
|
||||||
|
|
||||||
{notifications.map((n) => (
|
{/* Card */}
|
||||||
<AlertItem
|
<View
|
||||||
key={n.id}
|
style={[
|
||||||
title={n.title}
|
styles.notifCard,
|
||||||
description={n.body}
|
{ backgroundColor: m.bg },
|
||||||
time={new Date(n.createdAt).toLocaleTimeString("es-MX", {
|
]}
|
||||||
hour: "2-digit",
|
>
|
||||||
minute: "2-digit",
|
<View style={styles.notifHeader}>
|
||||||
})}
|
<Text style={styles.notifTitle}>{n.title}</Text>
|
||||||
type={notificationTypeToAlertType(n.type)}
|
<View
|
||||||
/>
|
style={[
|
||||||
))}
|
styles.badge,
|
||||||
</ScrollView>
|
{ backgroundColor: `${m.color}25` },
|
||||||
</SafeAreaView>
|
]}
|
||||||
);
|
>
|
||||||
|
<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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import { ScrollView, View, Text, StyleSheet } from "react-native";
|
import { ScrollView, View, Text, StyleSheet, Image } from "react-native";
|
||||||
import { SafeAreaView } from "react-native-safe-area-context";
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
import { Ionicons } from "@expo/vector-icons";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
|
||||||
import { COLORS } from "../constants/colors";
|
import { COLORS } from "../constants/colors";
|
||||||
import SectionTitle from "../components/SectionTitle";
|
|
||||||
|
|
||||||
type Category = {
|
type Category = {
|
||||||
key: string;
|
key: string;
|
||||||
title: string;
|
title: string;
|
||||||
color: string;
|
color: string;
|
||||||
|
bg: string;
|
||||||
icon: keyof typeof Ionicons.glyphMap;
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
|
image: any;
|
||||||
examples: string[];
|
examples: string[];
|
||||||
tip: string;
|
tip: string;
|
||||||
};
|
};
|
||||||
@@ -19,7 +20,9 @@ const CATEGORIES: Category[] = [
|
|||||||
key: "organico",
|
key: "organico",
|
||||||
title: "Orgánicos",
|
title: "Orgánicos",
|
||||||
color: "#22C55E",
|
color: "#22C55E",
|
||||||
|
bg: "#ECFDF5",
|
||||||
icon: "leaf",
|
icon: "leaf",
|
||||||
|
image: require("../../assets/illustrations/organic-food.png"),
|
||||||
examples: [
|
examples: [
|
||||||
"Restos de comida (frutas, verduras, cáscaras)",
|
"Restos de comida (frutas, verduras, cáscaras)",
|
||||||
"Bolsitas de té, residuos de café",
|
"Bolsitas de té, residuos de café",
|
||||||
@@ -31,7 +34,9 @@ const CATEGORIES: Category[] = [
|
|||||||
key: "reciclable",
|
key: "reciclable",
|
||||||
title: "Reciclables",
|
title: "Reciclables",
|
||||||
color: "#3B82F6",
|
color: "#3B82F6",
|
||||||
|
bg: "#EFF6FF",
|
||||||
icon: "refresh-circle",
|
icon: "refresh-circle",
|
||||||
|
image: require("../../assets/illustrations/pet-bottles.png"),
|
||||||
examples: [
|
examples: [
|
||||||
"Botellas y envases PET limpios",
|
"Botellas y envases PET limpios",
|
||||||
"Cartón y papel seco",
|
"Cartón y papel seco",
|
||||||
@@ -42,8 +47,10 @@ const CATEGORIES: Category[] = [
|
|||||||
{
|
{
|
||||||
key: "sanitario",
|
key: "sanitario",
|
||||||
title: "Sanitarios",
|
title: "Sanitarios",
|
||||||
color: "#9CA3AF",
|
color: "#6B7280",
|
||||||
|
bg: "#F3F4F6",
|
||||||
icon: "medkit",
|
icon: "medkit",
|
||||||
|
image: require("../../assets/illustrations/sanitary.png"),
|
||||||
examples: [
|
examples: [
|
||||||
"Pañales y toallas femeninas",
|
"Pañales y toallas femeninas",
|
||||||
"Papel higiénico usado",
|
"Papel higiénico usado",
|
||||||
@@ -55,7 +62,9 @@ const CATEGORIES: Category[] = [
|
|||||||
key: "especial",
|
key: "especial",
|
||||||
title: "Especiales",
|
title: "Especiales",
|
||||||
color: "#EF4444",
|
color: "#EF4444",
|
||||||
|
bg: "#FEF2F2",
|
||||||
icon: "warning",
|
icon: "warning",
|
||||||
|
image: require("../../assets/illustrations/battery-meds.png"),
|
||||||
examples: [
|
examples: [
|
||||||
"Pilas y baterías",
|
"Pilas y baterías",
|
||||||
"Electrónicos viejos",
|
"Electrónicos viejos",
|
||||||
@@ -68,39 +77,51 @@ const CATEGORIES: Category[] = [
|
|||||||
|
|
||||||
export default function GuideScreen() {
|
export default function GuideScreen() {
|
||||||
return (
|
return (
|
||||||
<SafeAreaView style={{ flex: 1, backgroundColor: COLORS.background }}>
|
<SafeAreaView
|
||||||
|
style={{ flex: 1, backgroundColor: COLORS.background }}
|
||||||
|
edges={["top"]}
|
||||||
|
>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
contentContainerStyle={{ padding: 20, paddingBottom: 120 }}
|
contentContainerStyle={{ padding: 20, paddingBottom: 130 }}
|
||||||
>
|
>
|
||||||
<SectionTitle title="Guía de separación" />
|
{/* Header con título grande + imagen */}
|
||||||
|
<View style={styles.headerRow}>
|
||||||
<Text
|
<View style={{ flex: 1, paddingRight: 8 }}>
|
||||||
style={{
|
<Text style={styles.headerTitle}>Guía de{"\n"}separación</Text>
|
||||||
color: "#6B7280",
|
<Text style={styles.headerSubtitle}>
|
||||||
fontSize: 14,
|
Separar correctamente reduce contaminación y ayuda a la ruta de
|
||||||
marginBottom: 20,
|
recolección.
|
||||||
marginLeft: 12,
|
</Text>
|
||||||
}}
|
<Text style={styles.headerOffline}>Funciona sin conexión.</Text>
|
||||||
>
|
</View>
|
||||||
Separar correctamente reduce contaminación y ayuda a la ruta de
|
<Image
|
||||||
recolección. Funciona sin conexión.
|
source={require("../../assets/illustrations/recycling.png")}
|
||||||
</Text>
|
style={styles.heroImage}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
{CATEGORIES.map((cat) => (
|
{CATEGORIES.map((cat) => (
|
||||||
<View key={cat.key} style={styles.card}>
|
<View key={cat.key} style={styles.card}>
|
||||||
|
{/* Icono circular */}
|
||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.iconWrap,
|
styles.iconWrap,
|
||||||
{ backgroundColor: `${cat.color}20` },
|
{ backgroundColor: cat.bg },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Ionicons name={cat.icon} size={28} color={cat.color} />
|
<Ionicons name={cat.icon} size={28} color={cat.color} />
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Contenido */}
|
||||||
<View style={styles.cardContent}>
|
<View style={styles.cardContent}>
|
||||||
<Text style={[styles.cardTitle, { color: cat.color }]}>
|
<View style={styles.cardTitleRow}>
|
||||||
{cat.title}
|
<Text style={[styles.cardTitle, { color: cat.color }]}>
|
||||||
</Text>
|
{cat.title}
|
||||||
|
</Text>
|
||||||
|
<Ionicons name="chevron-forward" size={18} color="#9CA3AF" />
|
||||||
|
</View>
|
||||||
{cat.examples.map((ex, i) => (
|
{cat.examples.map((ex, i) => (
|
||||||
<Text key={i} style={styles.example}>
|
<Text key={i} style={styles.example}>
|
||||||
• {ex}
|
• {ex}
|
||||||
@@ -109,24 +130,50 @@ export default function GuideScreen() {
|
|||||||
<View
|
<View
|
||||||
style={[
|
style={[
|
||||||
styles.tipBox,
|
styles.tipBox,
|
||||||
{ backgroundColor: `${cat.color}15` },
|
{ backgroundColor: cat.bg },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
<Ionicons name="bulb" size={14} color={cat.color} />
|
||||||
<Text style={[styles.tipText, { color: cat.color }]}>
|
<Text style={[styles.tipText, { color: cat.color }]}>
|
||||||
💡 {cat.tip}
|
{cat.tip}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
{/* Imagen ilustrativa por categoría */}
|
||||||
|
<Image
|
||||||
|
source={cat.image}
|
||||||
|
style={styles.categoryImage}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
|
{/* Recuerda */}
|
||||||
<View style={styles.preventiveBox}>
|
<View style={styles.preventiveBox}>
|
||||||
<Text style={styles.preventiveTitle}>Recuerda</Text>
|
<View style={{ flex: 1 }}>
|
||||||
<Text style={styles.preventiveBody}>
|
<View style={styles.preventiveHeader}>
|
||||||
• Saca tu basura sólo dentro del horario de recolección.{"\n"}
|
<View style={styles.preventiveIcon}>
|
||||||
• No persigas al camión, es peligroso.{"\n"}
|
<Ionicons name="notifications" size={20} color="#FFFFFF" />
|
||||||
• Si tu camión no pasó, usa el buzón de retroalimentación.
|
</View>
|
||||||
</Text>
|
<Text style={styles.preventiveTitle}>Recuerda</Text>
|
||||||
|
</View>
|
||||||
|
{[
|
||||||
|
"Saca tu basura sólo dentro del horario.",
|
||||||
|
"No persigas al camión, es peligroso.",
|
||||||
|
"Si tu camión no pasó, usa el buzón.",
|
||||||
|
].map((t, i) => (
|
||||||
|
<View key={i} style={styles.preventiveRow}>
|
||||||
|
<Ionicons name="checkmark-circle" size={14} color="#92400E" />
|
||||||
|
<Text style={styles.preventiveBody}>{t}</Text>
|
||||||
|
</View>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<Image
|
||||||
|
source={require("../../assets/illustrations/truck-banner.png")}
|
||||||
|
style={styles.preventiveImage}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
@@ -134,11 +181,40 @@ export default function GuideScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
headerRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 20,
|
||||||
|
paddingHorizontal: 4,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 30,
|
||||||
|
fontWeight: "800",
|
||||||
|
color: "#0F172A",
|
||||||
|
lineHeight: 34,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
headerSubtitle: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: "#1E40AF",
|
||||||
|
lineHeight: 18,
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
headerOffline: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: "#0E8A61",
|
||||||
|
fontWeight: "700",
|
||||||
|
},
|
||||||
|
heroImage: {
|
||||||
|
width: 120,
|
||||||
|
height: 120,
|
||||||
|
},
|
||||||
card: {
|
card: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
|
alignItems: "flex-start",
|
||||||
backgroundColor: "#FFFFFF",
|
backgroundColor: "#FFFFFF",
|
||||||
borderRadius: 18,
|
borderRadius: 18,
|
||||||
padding: 16,
|
padding: 14,
|
||||||
marginBottom: 14,
|
marginBottom: 14,
|
||||||
shadowColor: "#000",
|
shadowColor: "#000",
|
||||||
shadowOffset: { width: 0, height: 4 },
|
shadowOffset: { width: 0, height: 4 },
|
||||||
@@ -152,28 +228,82 @@ const styles = StyleSheet.create({
|
|||||||
borderRadius: 25,
|
borderRadius: 25,
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginRight: 14,
|
marginRight: 12,
|
||||||
},
|
},
|
||||||
cardContent: { flex: 1 },
|
cardContent: {
|
||||||
cardTitle: { fontSize: 17, fontWeight: "800", marginBottom: 6 },
|
flex: 1,
|
||||||
example: { fontSize: 13, color: "#4B5563", marginBottom: 3 },
|
paddingRight: 4,
|
||||||
|
},
|
||||||
|
cardTitleRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 6,
|
||||||
|
},
|
||||||
|
cardTitle: { fontSize: 18, fontWeight: "800" },
|
||||||
|
example: { fontSize: 12, color: "#4B5563", marginBottom: 2 },
|
||||||
tipBox: {
|
tipBox: {
|
||||||
marginTop: 10,
|
flexDirection: "row",
|
||||||
padding: 10,
|
alignItems: "center",
|
||||||
borderRadius: 10,
|
|
||||||
},
|
|
||||||
tipText: { fontSize: 12, fontWeight: "600" },
|
|
||||||
preventiveBox: {
|
|
||||||
marginTop: 8,
|
marginTop: 8,
|
||||||
padding: 16,
|
padding: 8,
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
tipText: {
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: "600",
|
||||||
|
marginLeft: 6,
|
||||||
|
flex: 1,
|
||||||
|
lineHeight: 14,
|
||||||
|
},
|
||||||
|
categoryImage: {
|
||||||
|
width: 64,
|
||||||
|
height: 80,
|
||||||
|
marginLeft: 4,
|
||||||
|
alignSelf: "center",
|
||||||
|
},
|
||||||
|
preventiveBox: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginTop: 8,
|
||||||
|
padding: 14,
|
||||||
backgroundColor: "#FEF3C7",
|
backgroundColor: "#FEF3C7",
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
},
|
},
|
||||||
|
preventiveHeader: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
preventiveIcon: {
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: "#F59E0B",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
marginRight: 10,
|
||||||
|
},
|
||||||
preventiveTitle: {
|
preventiveTitle: {
|
||||||
fontSize: 14,
|
fontSize: 16,
|
||||||
fontWeight: "800",
|
fontWeight: "800",
|
||||||
color: "#92400E",
|
color: "#92400E",
|
||||||
marginBottom: 6,
|
|
||||||
},
|
},
|
||||||
preventiveBody: { fontSize: 13, color: "#92400E", lineHeight: 20 },
|
preventiveRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
preventiveBody: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: "#92400E",
|
||||||
|
marginLeft: 6,
|
||||||
|
flex: 1,
|
||||||
|
lineHeight: 16,
|
||||||
|
},
|
||||||
|
preventiveImage: {
|
||||||
|
width: 90,
|
||||||
|
height: 90,
|
||||||
|
marginLeft: 4,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,127 +1,473 @@
|
|||||||
import { ScrollView, View, Text, RefreshControl } from "react-native";
|
import { useEffect } from "react";
|
||||||
|
import {
|
||||||
|
ScrollView,
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
Image,
|
||||||
|
Pressable,
|
||||||
|
StyleSheet,
|
||||||
|
} from "react-native";
|
||||||
import { SafeAreaView } from "react-native-safe-area-context";
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
import { Redirect, useRouter } from "expo-router";
|
import { Redirect, useRouter } from "expo-router";
|
||||||
|
|
||||||
import { COLORS } from "../constants/colors";
|
import { COLORS } from "../constants/colors";
|
||||||
|
|
||||||
import SectionTitle from "../components/SectionTitle";
|
|
||||||
import EtaCard from "../components/EtaCard";
|
|
||||||
import QuickAction from "../components/QuickAction";
|
|
||||||
|
|
||||||
import { useApp } from "../context/AppContext";
|
import { useApp } from "../context/AppContext";
|
||||||
|
|
||||||
|
type QuickAction = {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
|
color: string;
|
||||||
|
bg: string;
|
||||||
|
onPress: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
export default function HomeScreen() {
|
export default function HomeScreen() {
|
||||||
const { user, eta, route, loading, refreshStatus } = useApp();
|
const { user, eta, route, loading, refreshStatus } = useApp();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
if (!user) {
|
useEffect(() => {
|
||||||
return <Redirect href="/login" />;
|
if (user && !eta) {
|
||||||
}
|
void refreshStatus();
|
||||||
|
}
|
||||||
|
}, [user]);
|
||||||
|
|
||||||
const minutes = eta ? Math.max(0, eta.etaMinutes) : 0;
|
if (!user) return <Redirect href="/login" />;
|
||||||
|
|
||||||
|
const minutes = eta ? Math.max(0, eta.etaMinutes) : null;
|
||||||
const windowText = eta?.arrivalWindow
|
const windowText = eta?.arrivalWindow
|
||||||
? `Ventana: ${eta.arrivalWindow.from} - ${eta.arrivalWindow.to}`
|
? `${eta.arrivalWindow.from} - ${eta.arrivalWindow.to} a.m.`
|
||||||
: "Esperando datos del camión...";
|
: "Calculando...";
|
||||||
|
|
||||||
const subtitle =
|
const statusOK = route?.status === "EN_RUTA" || route?.status === "ESPERA";
|
||||||
eta?.message ?? `Monitoreando ruta ${route?.routeId ?? "..."}`;
|
const statusColor = statusOK ? "#22C55E" : "#EF4444";
|
||||||
|
const statusLabel =
|
||||||
|
route?.status === "EN_RUTA"
|
||||||
|
? "ESTABLE"
|
||||||
|
: route?.status === "FALLA"
|
||||||
|
? "CON FALLA"
|
||||||
|
: route?.status === "DETENIDO"
|
||||||
|
? "DETENIDO"
|
||||||
|
: "EN ESPERA";
|
||||||
|
|
||||||
|
const quickActions: QuickAction[] = [
|
||||||
|
{
|
||||||
|
key: "alerts",
|
||||||
|
title: "Alertas",
|
||||||
|
icon: "notifications-outline",
|
||||||
|
color: "#22C55E",
|
||||||
|
bg: "#ECFDF5",
|
||||||
|
onPress: () => router.push("/alerts"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "guide",
|
||||||
|
title: "Guía",
|
||||||
|
icon: "leaf-outline",
|
||||||
|
color: "#3B82F6",
|
||||||
|
bg: "#EFF6FF",
|
||||||
|
onPress: () => router.push("/guide"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "feedback",
|
||||||
|
title: "Reportar",
|
||||||
|
icon: "chatbubble-outline",
|
||||||
|
color: "#EF4444",
|
||||||
|
bg: "#FEF2F2",
|
||||||
|
onPress: () => router.push("/feedback"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "address",
|
||||||
|
title: "Domicilio",
|
||||||
|
icon: "home-outline",
|
||||||
|
color: "#8B5CF6",
|
||||||
|
bg: "#F5F3FF",
|
||||||
|
onPress: () => router.push("/addresses"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SafeAreaView
|
<SafeAreaView
|
||||||
style={{
|
style={{ flex: 1, backgroundColor: COLORS.background }}
|
||||||
flex: 1,
|
edges={["top"]}
|
||||||
backgroundColor: COLORS.background,
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<ScrollView
|
<ScrollView
|
||||||
showsVerticalScrollIndicator={false}
|
showsVerticalScrollIndicator={false}
|
||||||
contentContainerStyle={{
|
contentContainerStyle={{ paddingBottom: 130 }}
|
||||||
padding: 20,
|
refreshControl={undefined}
|
||||||
paddingBottom: 120,
|
|
||||||
}}
|
|
||||||
refreshControl={
|
|
||||||
<RefreshControl refreshing={loading} onRefresh={refreshStatus} />
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
<SectionTitle title="EcoRuta" />
|
{/* Title */}
|
||||||
|
<View style={styles.titleRow}>
|
||||||
<Text
|
<View>
|
||||||
style={{
|
<Text style={styles.title}>EcoRuta</Text>
|
||||||
color: "#6B7280",
|
<Text style={styles.subtitle}>
|
||||||
fontSize: 14,
|
Ruta {route?.routeId ?? "—"}
|
||||||
marginBottom: 20,
|
|
||||||
marginLeft: 12,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{subtitle}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<EtaCard minutes={minutes} status={windowText} />
|
|
||||||
|
|
||||||
{route?.horarioEstimado && (
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#ECFDF5",
|
|
||||||
padding: 14,
|
|
||||||
borderRadius: 14,
|
|
||||||
marginHorizontal: 15,
|
|
||||||
marginTop: 4,
|
|
||||||
borderLeftWidth: 4,
|
|
||||||
borderLeftColor: "#0E8A61",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={{ color: "#065F46", fontSize: 11, fontWeight: "700" }}>
|
|
||||||
PRÓXIMA RECOLECCIÓN
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={{
|
|
||||||
color: "#065F46",
|
|
||||||
fontSize: 14,
|
|
||||||
fontWeight: "600",
|
|
||||||
marginTop: 4,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{route.horarioEstimado}
|
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
|
||||||
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
backgroundColor: "#FEF3C7",
|
|
||||||
padding: 12,
|
|
||||||
borderRadius: 12,
|
|
||||||
marginHorizontal: 15,
|
|
||||||
marginTop: 10,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text style={{ color: "#92400E", fontSize: 12, fontWeight: "600" }}>
|
|
||||||
⚠️ No saques tus residuos fuera del horario y no persigas al camión.
|
|
||||||
</Text>
|
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<SectionTitle title="Acciones rápidas" />
|
{/* Hero del camión */}
|
||||||
|
<View style={styles.heroBox}>
|
||||||
<View
|
<Image
|
||||||
style={{
|
source={require("../../assets/illustrations/truck-city.png")}
|
||||||
flexDirection: "row",
|
style={styles.heroTruck}
|
||||||
justifyContent: "space-between",
|
resizeMode="cover"
|
||||||
marginTop: 10,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<QuickAction
|
|
||||||
title="Alertas"
|
|
||||||
icon="notifications-outline"
|
|
||||||
onPress={() => router.push("/alerts")}
|
|
||||||
/>
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
<QuickAction
|
{/* Card ETA */}
|
||||||
title="Reportar"
|
<View style={styles.etaCard}>
|
||||||
icon="chatbubble-outline"
|
<View style={styles.etaContent}>
|
||||||
onPress={() => router.push("/feedback")}
|
<View style={styles.etaIcon}>
|
||||||
|
<Ionicons name="time-outline" size={22} color="#0E8A61" />
|
||||||
|
</View>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Text style={styles.etaLabel}>LLEGADA ESTIMADA</Text>
|
||||||
|
<Text style={styles.etaMinutes}>
|
||||||
|
{minutes !== null
|
||||||
|
? `${minutes} min`
|
||||||
|
: loading
|
||||||
|
? "..."
|
||||||
|
: "—"}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.etaWindowLabel}>Ventana de recolección</Text>
|
||||||
|
<View style={styles.windowPill}>
|
||||||
|
<Text style={styles.windowText}>{windowText}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={styles.etaCircle}>
|
||||||
|
<View style={styles.etaCircleInner}>
|
||||||
|
<Ionicons name="bus-outline" size={28} color="#0E8A61" />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Estado de ruta */}
|
||||||
|
<View style={styles.statusBanner}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.statusDot,
|
||||||
|
{ backgroundColor: statusColor },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Text style={[styles.statusTitle, { color: statusColor }]}>
|
||||||
|
ESTADO DE RUTA: {statusLabel}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.statusBody}>
|
||||||
|
{statusOK
|
||||||
|
? "Tu recolección sigue en horario normal."
|
||||||
|
: "Hay incidencias en la ruta."}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.statusShield,
|
||||||
|
{ backgroundColor: `${statusColor}15` },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Ionicons
|
||||||
|
name="shield-checkmark-outline"
|
||||||
|
size={20}
|
||||||
|
color={statusColor}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Mensaje preventivo */}
|
||||||
|
<View style={styles.warningBanner}>
|
||||||
|
<View style={styles.warningIcon}>
|
||||||
|
<Ionicons name="alert" size={20} color="#FFFFFF" />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.warningText}>
|
||||||
|
No saques tus residuos fuera del horario{"\n"}y no persigas al camión.
|
||||||
|
</Text>
|
||||||
|
<View style={styles.warningRight}>
|
||||||
|
<Ionicons name="walk-outline" size={20} color="#92400E" />
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Acciones rápidas */}
|
||||||
|
<Text style={styles.sectionTitle}>ACCIONES RÁPIDAS</Text>
|
||||||
|
<View style={styles.actionsGrid}>
|
||||||
|
{quickActions.map((a) => (
|
||||||
|
<Pressable
|
||||||
|
key={a.key}
|
||||||
|
style={styles.actionCard}
|
||||||
|
onPress={a.onPress}
|
||||||
|
>
|
||||||
|
<View style={[styles.actionIcon, { backgroundColor: a.bg }]}>
|
||||||
|
<Ionicons name={a.icon} size={26} color={a.color} />
|
||||||
|
</View>
|
||||||
|
<Text style={styles.actionTitle}>{a.title}</Text>
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Consejo rápido */}
|
||||||
|
<View style={styles.tipCard}>
|
||||||
|
<View style={styles.tipIcon}>
|
||||||
|
<Ionicons name="leaf" size={22} color="#0E8A61" />
|
||||||
|
</View>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Text style={styles.tipTitle}>Consejo rápido</Text>
|
||||||
|
<Text style={styles.tipBody}>
|
||||||
|
Compacta cartón y PET para ahorrar espacio y ayudar al medio
|
||||||
|
ambiente.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Image
|
||||||
|
source={require("../../assets/illustrations/pet-bottles.png")}
|
||||||
|
style={styles.tipImage}
|
||||||
|
resizeMode="contain"
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</SafeAreaView>
|
</SafeAreaView>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
titleRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingTop: 8,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 32,
|
||||||
|
fontWeight: "800",
|
||||||
|
color: "#0E8A61",
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: "#6B7280",
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
heroBox: {
|
||||||
|
height: 180,
|
||||||
|
marginTop: 8,
|
||||||
|
backgroundColor: "#DBEAFE",
|
||||||
|
overflow: "hidden",
|
||||||
|
},
|
||||||
|
heroTruck: {
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
etaCard: {
|
||||||
|
marginHorizontal: 20,
|
||||||
|
marginTop: -30,
|
||||||
|
backgroundColor: "#ECFDF5",
|
||||||
|
borderRadius: 20,
|
||||||
|
padding: 18,
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOffset: { width: 0, height: 6 },
|
||||||
|
shadowOpacity: 0.08,
|
||||||
|
shadowRadius: 10,
|
||||||
|
elevation: 6,
|
||||||
|
},
|
||||||
|
etaContent: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
etaIcon: {
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
borderRadius: 18,
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
etaLabel: {
|
||||||
|
fontSize: 11,
|
||||||
|
fontWeight: "800",
|
||||||
|
color: "#065F46",
|
||||||
|
letterSpacing: 0.5,
|
||||||
|
},
|
||||||
|
etaMinutes: {
|
||||||
|
fontSize: 36,
|
||||||
|
fontWeight: "800",
|
||||||
|
color: "#0F172A",
|
||||||
|
marginTop: 4,
|
||||||
|
lineHeight: 38,
|
||||||
|
},
|
||||||
|
etaWindowLabel: {
|
||||||
|
fontSize: 11,
|
||||||
|
color: "#6B7280",
|
||||||
|
marginTop: 6,
|
||||||
|
},
|
||||||
|
windowPill: {
|
||||||
|
alignSelf: "flex-start",
|
||||||
|
backgroundColor: "#D1FAE5",
|
||||||
|
borderRadius: 10,
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 4,
|
||||||
|
marginTop: 4,
|
||||||
|
},
|
||||||
|
windowText: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: "#065F46",
|
||||||
|
fontWeight: "700",
|
||||||
|
},
|
||||||
|
etaCircle: {
|
||||||
|
width: 88,
|
||||||
|
height: 88,
|
||||||
|
borderRadius: 44,
|
||||||
|
borderWidth: 4,
|
||||||
|
borderColor: "#22C55E",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
etaCircleInner: {
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
borderRadius: 32,
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
statusBanner: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginHorizontal: 20,
|
||||||
|
marginTop: 14,
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 14,
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
statusDot: {
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
borderRadius: 6,
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
statusTitle: {
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: "800",
|
||||||
|
},
|
||||||
|
statusBody: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: "#374151",
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
statusShield: {
|
||||||
|
width: 36,
|
||||||
|
height: 36,
|
||||||
|
borderRadius: 18,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
warningBanner: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginHorizontal: 20,
|
||||||
|
marginTop: 12,
|
||||||
|
backgroundColor: "#FEF3C7",
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 12,
|
||||||
|
},
|
||||||
|
warningIcon: {
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 16,
|
||||||
|
backgroundColor: "#F59E0B",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
marginRight: 10,
|
||||||
|
},
|
||||||
|
warningText: {
|
||||||
|
flex: 1,
|
||||||
|
fontSize: 12,
|
||||||
|
color: "#92400E",
|
||||||
|
fontWeight: "600",
|
||||||
|
lineHeight: 16,
|
||||||
|
},
|
||||||
|
warningRight: {
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: "800",
|
||||||
|
color: "#6B7280",
|
||||||
|
letterSpacing: 1,
|
||||||
|
marginTop: 22,
|
||||||
|
marginBottom: 10,
|
||||||
|
marginLeft: 20,
|
||||||
|
},
|
||||||
|
actionsGrid: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
},
|
||||||
|
actionCard: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
borderRadius: 14,
|
||||||
|
paddingVertical: 14,
|
||||||
|
alignItems: "center",
|
||||||
|
marginHorizontal: 4,
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
actionIcon: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 25,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
actionTitle: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: "700",
|
||||||
|
color: "#0F172A",
|
||||||
|
textAlign: "center",
|
||||||
|
},
|
||||||
|
tipCard: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: "#ECFDF5",
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 14,
|
||||||
|
marginHorizontal: 20,
|
||||||
|
marginTop: 16,
|
||||||
|
},
|
||||||
|
tipIcon: {
|
||||||
|
width: 44,
|
||||||
|
height: 44,
|
||||||
|
borderRadius: 22,
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
tipTitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: "800",
|
||||||
|
color: "#065F46",
|
||||||
|
marginBottom: 2,
|
||||||
|
},
|
||||||
|
tipBody: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: "#065F46",
|
||||||
|
lineHeight: 16,
|
||||||
|
},
|
||||||
|
tipImage: {
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,104 +1,382 @@
|
|||||||
import { View, Text, StyleSheet, Alert } from "react-native";
|
import { useEffect, useState } from "react";
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
ScrollView,
|
||||||
|
Image,
|
||||||
|
Pressable,
|
||||||
|
Alert,
|
||||||
|
} from "react-native";
|
||||||
import { SafeAreaView } from "react-native-safe-area-context";
|
import { SafeAreaView } from "react-native-safe-area-context";
|
||||||
import { useRouter } from "expo-router";
|
import { Ionicons } from "@expo/vector-icons";
|
||||||
|
import { Redirect, useRouter } from "expo-router";
|
||||||
|
|
||||||
import { COLORS } from "../constants/colors";
|
import { COLORS } from "../constants/colors";
|
||||||
import PrimaryButton from "../components/PrimaryButton";
|
|
||||||
|
|
||||||
import { useApp } from "../context/AppContext";
|
import { useApp } from "../context/AppContext";
|
||||||
import { resetDemo } from "../services/tracking.service";
|
import { getMyAddress, type MyAddress } from "../services/addresses.service";
|
||||||
|
import { apiFetch } from "../lib/api";
|
||||||
|
|
||||||
|
type Row = {
|
||||||
|
key: string;
|
||||||
|
title: string;
|
||||||
|
subtitle: string;
|
||||||
|
icon: keyof typeof Ionicons.glyphMap;
|
||||||
|
color: string;
|
||||||
|
onPress: () => void;
|
||||||
|
danger?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export default function ProfileScreen() {
|
export default function ProfileScreen() {
|
||||||
const { user, logout, refreshStatus } = useApp();
|
const { user, logout } = useApp();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [myAddress, setMyAddress] = useState<MyAddress | null>(null);
|
||||||
|
|
||||||
const handleResetDemo = async () => {
|
useEffect(() => {
|
||||||
try {
|
if (!user) return;
|
||||||
await resetDemo();
|
void getMyAddress().then(setMyAddress).catch(() => {});
|
||||||
await refreshStatus();
|
}, [user]);
|
||||||
Alert.alert("Demo reiniciado", "Estado y notificaciones borradas.");
|
|
||||||
} catch (err) {
|
|
||||||
Alert.alert(
|
|
||||||
"Error",
|
|
||||||
err instanceof Error ? err.message : "No se pudo reiniciar",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!user) {
|
if (!user) return <Redirect href="/login" />;
|
||||||
return (
|
|
||||||
<SafeAreaView style={styles.container}>
|
const handleLogout = () => {
|
||||||
<View style={styles.content}>
|
logout();
|
||||||
<Text style={styles.title}>No has iniciado sesión</Text>
|
router.replace("/login");
|
||||||
<PrimaryButton
|
};
|
||||||
title="Iniciar sesión"
|
|
||||||
onPress={() => router.push("/login")}
|
const handleReset = async () => {
|
||||||
/>
|
try {
|
||||||
</View>
|
await apiFetch<{ message: string }>("/api/tracking/reset-demo", {
|
||||||
</SafeAreaView>
|
method: "POST",
|
||||||
);
|
});
|
||||||
|
Alert.alert("Demo reiniciada", "Las notificaciones se borraron.");
|
||||||
|
} catch (err) {
|
||||||
|
Alert.alert(
|
||||||
|
"No se pudo reiniciar",
|
||||||
|
err instanceof Error ? err.message : "Error",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleHelp = () => {
|
||||||
logout();
|
Alert.alert(
|
||||||
router.replace("/login");
|
"Ayuda",
|
||||||
};
|
"EcoRuta te avisa cuando el camión recolector está cerca.\n\n" +
|
||||||
|
"• Tu dirección define la ruta que verás.\n" +
|
||||||
return (
|
"• Las alertas se actualizan cada 30 segundos.\n" +
|
||||||
<SafeAreaView style={styles.container}>
|
"• Usa el buzón si el camión no pasó.",
|
||||||
<View style={styles.content}>
|
|
||||||
<Text style={styles.title}>Hola, {user.name}</Text>
|
|
||||||
<Text style={styles.email}>{user.email}</Text>
|
|
||||||
|
|
||||||
<View style={{ height: 24 }} />
|
|
||||||
|
|
||||||
<PrimaryButton
|
|
||||||
title="Mi domicilio"
|
|
||||||
onPress={() => router.push("/addresses")}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<View style={{ height: 12 }} />
|
|
||||||
|
|
||||||
<PrimaryButton
|
|
||||||
title="Buzón de retroalimentación"
|
|
||||||
onPress={() => router.push("/feedback")}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<View style={{ height: 12 }} />
|
|
||||||
|
|
||||||
<PrimaryButton
|
|
||||||
title="Reiniciar demo"
|
|
||||||
onPress={handleResetDemo}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<View style={{ height: 12 }} />
|
|
||||||
|
|
||||||
<PrimaryButton title="Cerrar sesión" onPress={handleLogout} />
|
|
||||||
</View>
|
|
||||||
</SafeAreaView>
|
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const rows: Row[] = [
|
||||||
|
{
|
||||||
|
key: "address",
|
||||||
|
title: "Mi domicilio",
|
||||||
|
subtitle: "Ver y administrar tus domicilios registrados",
|
||||||
|
icon: "home-outline",
|
||||||
|
color: "#0E8A61",
|
||||||
|
onPress: () => router.push("/addresses"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "feedback",
|
||||||
|
title: "Buzón de retroalimentación",
|
||||||
|
subtitle: "Sugerencias, comentarios y reportes",
|
||||||
|
icon: "chatbubble-outline",
|
||||||
|
color: "#0E8A61",
|
||||||
|
onPress: () => router.push("/feedback"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "reset",
|
||||||
|
title: "Reiniciar demo",
|
||||||
|
subtitle: "Restablecer la simulación y los datos",
|
||||||
|
icon: "refresh-outline",
|
||||||
|
color: "#0E8A61",
|
||||||
|
onPress: handleReset,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "help",
|
||||||
|
title: "Ayuda",
|
||||||
|
subtitle: "Preguntas frecuentes y soporte",
|
||||||
|
icon: "help-circle-outline",
|
||||||
|
color: "#0E8A61",
|
||||||
|
onPress: handleHelp,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "logout",
|
||||||
|
title: "Cerrar sesión",
|
||||||
|
subtitle: "Salir de tu cuenta",
|
||||||
|
icon: "log-out-outline",
|
||||||
|
color: "#EF4444",
|
||||||
|
onPress: handleLogout,
|
||||||
|
danger: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const initial = (user.name?.[0] ?? user.email[0]).toUpperCase();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView
|
||||||
|
style={{ flex: 1, backgroundColor: COLORS.background }}
|
||||||
|
edges={["bottom"]}
|
||||||
|
>
|
||||||
|
<ScrollView
|
||||||
|
showsVerticalScrollIndicator={false}
|
||||||
|
contentContainerStyle={{ paddingBottom: 130 }}
|
||||||
|
>
|
||||||
|
{/* Header verde con avatar */}
|
||||||
|
<View style={styles.heroWrap}>
|
||||||
|
<View style={styles.heroBackground} />
|
||||||
|
<View style={styles.avatarOuter}>
|
||||||
|
<View style={styles.avatarInner}>
|
||||||
|
<Text style={styles.avatarText}>{initial}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.identityBox}>
|
||||||
|
<Text style={styles.name}>
|
||||||
|
Hola, {user.name} <Text style={{ color: "#22C55E" }}>🌿</Text>
|
||||||
|
</Text>
|
||||||
|
<View style={styles.emailRow}>
|
||||||
|
<Ionicons name="mail-outline" size={14} color="#6B7280" />
|
||||||
|
<Text style={styles.email}>{user.email}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Zona + ruta */}
|
||||||
|
<Pressable
|
||||||
|
style={styles.zoneRow}
|
||||||
|
onPress={() => router.push("/addresses")}
|
||||||
|
>
|
||||||
|
<View style={styles.zoneCol}>
|
||||||
|
<Ionicons name="location-outline" size={22} color="#0E8A61" />
|
||||||
|
<View style={{ marginLeft: 8, flex: 1 }}>
|
||||||
|
<Text style={styles.zoneLabel}>Tu zona</Text>
|
||||||
|
<Text style={styles.zoneValue} numberOfLines={2}>
|
||||||
|
{myAddress?.colonia ?? "Sin asignar"}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={styles.zoneDivider} />
|
||||||
|
<View style={styles.zoneCol}>
|
||||||
|
<Ionicons name="bus-outline" size={22} color="#0E8A61" />
|
||||||
|
<View style={{ marginLeft: 8, flex: 1 }}>
|
||||||
|
<Text style={styles.zoneLabel}>Ruta asignada</Text>
|
||||||
|
<Text style={styles.zoneValue} numberOfLines={1}>
|
||||||
|
{myAddress?.routeId ?? "—"}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<Ionicons
|
||||||
|
name="chevron-forward"
|
||||||
|
size={18}
|
||||||
|
color="#9CA3AF"
|
||||||
|
style={{ marginLeft: 4 }}
|
||||||
|
/>
|
||||||
|
</Pressable>
|
||||||
|
|
||||||
|
{/* Lista de opciones */}
|
||||||
|
<View style={{ paddingHorizontal: 16, marginTop: 4 }}>
|
||||||
|
{rows.map((r) => (
|
||||||
|
<Pressable key={r.key} style={styles.rowCard} onPress={r.onPress}>
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
styles.rowIcon,
|
||||||
|
{ backgroundColor: `${r.color}15` },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Ionicons name={r.icon} size={22} color={r.color} />
|
||||||
|
</View>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.rowTitle,
|
||||||
|
r.danger && { color: "#EF4444" },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{r.title}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.rowSubtitle}>{r.subtitle}</Text>
|
||||||
|
</View>
|
||||||
|
<Ionicons name="chevron-forward" size={18} color="#9CA3AF" />
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Tu impacto cuenta */}
|
||||||
|
<View style={styles.impactCard}>
|
||||||
|
<View style={styles.impactLeaf}>
|
||||||
|
<Image
|
||||||
|
source={require("../../assets/illustrations/impact-leaf.png")}
|
||||||
|
style={{ width: 40, height: 40 }}
|
||||||
|
resizeMode="contain"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={{ flex: 1 }}>
|
||||||
|
<Text style={styles.impactTitle}>Tu impacto cuenta</Text>
|
||||||
|
<Text style={styles.impactBody}>
|
||||||
|
Siguiendo los horarios y separando correctamente, haces tu ciudad
|
||||||
|
más limpia.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</SafeAreaView>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {
|
heroWrap: {
|
||||||
flex: 1,
|
height: 140,
|
||||||
backgroundColor: COLORS.background,
|
backgroundColor: "#D1FAE5",
|
||||||
},
|
overflow: "visible",
|
||||||
content: {
|
marginBottom: 60,
|
||||||
flex: 1,
|
},
|
||||||
padding: 24,
|
heroBackground: {
|
||||||
justifyContent: "center",
|
position: "absolute",
|
||||||
},
|
top: 0,
|
||||||
title: {
|
left: 0,
|
||||||
fontSize: 22,
|
right: 0,
|
||||||
fontWeight: "bold",
|
bottom: 0,
|
||||||
textAlign: "center",
|
backgroundColor: "#D1FAE5",
|
||||||
marginBottom: 6,
|
},
|
||||||
},
|
avatarOuter: {
|
||||||
email: {
|
position: "absolute",
|
||||||
fontSize: 14,
|
bottom: -50,
|
||||||
color: "#6B7280",
|
alignSelf: "center",
|
||||||
textAlign: "center",
|
width: 112,
|
||||||
marginBottom: 16,
|
height: 112,
|
||||||
},
|
borderRadius: 56,
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOffset: { width: 0, height: 4 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 8,
|
||||||
|
elevation: 6,
|
||||||
|
},
|
||||||
|
avatarInner: {
|
||||||
|
width: 100,
|
||||||
|
height: 100,
|
||||||
|
borderRadius: 50,
|
||||||
|
backgroundColor: "#0E8A61",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
},
|
||||||
|
avatarText: {
|
||||||
|
color: "#FFFFFF",
|
||||||
|
fontSize: 38,
|
||||||
|
fontWeight: "800",
|
||||||
|
},
|
||||||
|
identityBox: {
|
||||||
|
alignItems: "center",
|
||||||
|
marginBottom: 18,
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
fontSize: 22,
|
||||||
|
fontWeight: "800",
|
||||||
|
color: "#0F172A",
|
||||||
|
},
|
||||||
|
emailRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
marginTop: 6,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: "#6B7280",
|
||||||
|
marginLeft: 6,
|
||||||
|
},
|
||||||
|
zoneRow: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: "#ECFDF5",
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 14,
|
||||||
|
marginHorizontal: 16,
|
||||||
|
marginBottom: 16,
|
||||||
|
},
|
||||||
|
zoneCol: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
zoneDivider: {
|
||||||
|
width: 1,
|
||||||
|
height: 36,
|
||||||
|
backgroundColor: "#A7F3D0",
|
||||||
|
marginHorizontal: 8,
|
||||||
|
},
|
||||||
|
zoneLabel: {
|
||||||
|
fontSize: 11,
|
||||||
|
color: "#065F46",
|
||||||
|
fontWeight: "700",
|
||||||
|
},
|
||||||
|
zoneValue: {
|
||||||
|
fontSize: 13,
|
||||||
|
color: "#0F172A",
|
||||||
|
fontWeight: "600",
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
rowCard: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
borderRadius: 14,
|
||||||
|
padding: 14,
|
||||||
|
marginBottom: 10,
|
||||||
|
shadowColor: "#000",
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.05,
|
||||||
|
shadowRadius: 6,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
rowIcon: {
|
||||||
|
width: 42,
|
||||||
|
height: 42,
|
||||||
|
borderRadius: 21,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
rowTitle: {
|
||||||
|
fontSize: 15,
|
||||||
|
fontWeight: "700",
|
||||||
|
color: "#0F172A",
|
||||||
|
},
|
||||||
|
rowSubtitle: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: "#6B7280",
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
impactCard: {
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: "#ECFDF5",
|
||||||
|
borderRadius: 16,
|
||||||
|
padding: 14,
|
||||||
|
marginHorizontal: 16,
|
||||||
|
marginTop: 8,
|
||||||
|
},
|
||||||
|
impactLeaf: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 25,
|
||||||
|
backgroundColor: "#FFFFFF",
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
impactTitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
fontWeight: "800",
|
||||||
|
color: "#065F46",
|
||||||
|
marginBottom: 4,
|
||||||
|
},
|
||||||
|
impactBody: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: "#065F46",
|
||||||
|
lineHeight: 16,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
// android
|
// android
|
||||||
// export const API_URL = "http://10.0.2.2:8080";
|
export const API_URL = "http://10.0.2.2:8080";
|
||||||
export const API_URL = "http://192.168.93.148:8080";
|
//export const API_URL = "http://172.20.10.4:8080";
|
||||||