import { Cpu, Settings, BarChart3, Bell } from "lucide-react"; import { useEffect, useMemo, useState } from "react"; import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid, } from "recharts"; import { fetchMeters, type Meter } from "../api/meters"; import { getAuditLogs, type AuditLog } from "../api/audit"; import { fetchNotifications, type Notification } from "../api/notifications"; import { getAllUsers, type User } from "../api/users"; import { fetchProjects, type Project } from "../api/projects"; import { getCurrentUserRole, getCurrentUserProjectId } from "../api/auth"; import type { Page } from "../App"; import grhWatermark from "../assets/images/grhWatermark.png"; /* ================= TYPES ================= */ type OrganismStatus = "ACTIVO" | "INACTIVO"; type Organism = { id: string; name: string; region: string; projects: number; meters: number; activeAlerts: number; lastSync: string; contact: string; status: OrganismStatus; projectId: string | null; }; type AlertItem = { company: string; type: string; time: string }; type HistoryItem = { user: string; action: string; target: string; time: string; }; /* ================= COMPONENT ================= */ export default function Home({ setPage, navigateToMetersWithProject, }: { setPage: (page: Page) => void; navigateToMetersWithProject: (projectName: string) => void; }) { const userRole = useMemo(() => getCurrentUserRole(), []); const userProjectId = useMemo(() => getCurrentUserProjectId(), []); const isOperator = userRole?.toUpperCase() === 'OPERATOR'; const isAdmin = userRole?.toUpperCase() === 'ADMIN'; /* ================= METERS ================= */ const [meters, setMeters] = useState([]); const loadMeters = async () => { try { const data = await fetchMeters(); setMeters(data); } catch (err) { console.error("Error loading meters:", err); setMeters([]); } }; useEffect(() => { loadMeters(); }, []); const [projects, setProjects] = useState([]); const loadProjects = async () => { try { const data = await fetchProjects(); setProjects(data); } catch (err) { console.error("Error loading projects:", err); setProjects([]); } }; useEffect(() => { loadProjects(); }, []); const [users, setUsers] = useState([]); const [loadingUsers, setLoadingUsers] = useState(false); const [selectedOrganism, setSelectedOrganism] = useState("Todos"); const [showOrganisms, setShowOrganisms] = useState(false); const [organismQuery, setOrganismQuery] = useState(""); const loadUsers = async () => { setLoadingUsers(true); try { const response = await getAllUsers({ is_active: true }); setUsers(response.data); } catch (err) { console.error("Error loading users:", err); setUsers([]); } finally { setLoadingUsers(false); } }; useEffect(() => { if (!isOperator) { loadUsers(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const organismsData: Organism[] = useMemo(() => { return users.map(user => { const userMeters = user.project_id ? meters.filter(m => m.projectId === user.project_id).length : 0; const userProjects = user.project_id ? 1 : 0; return { id: user.id, name: user.name, region: user.email, projects: userProjects, meters: userMeters, activeAlerts: 0, lastSync: user.last_login ? `Último acceso: ${new Date(user.last_login).toLocaleDateString()}` : "Nunca", contact: user.role?.name || "N/A", status: user.is_active ? "ACTIVO" : "INACTIVO", projectId: user.project_id, }; }); }, [users, meters]); const [auditLogs, setAuditLogs] = useState([]); const [loadingAuditLogs, setLoadingAuditLogs] = useState(false); const loadAuditLogs = async () => { setLoadingAuditLogs(true); try { const response = await getAuditLogs({ limit: 10, page: 1 }); setAuditLogs(response.data); } catch (err) { console.error("Error loading audit logs:", err); setAuditLogs([]); } finally { setLoadingAuditLogs(false); } }; useEffect(() => { if (!isOperator) { loadAuditLogs(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const filteredMeters = useMemo(() => { // If user is OPERATOR, always filter by their assigned project if (isOperator && userProjectId) { return meters.filter((m) => m.projectId === userProjectId); } // For ADMIN users with organism selector if (selectedOrganism === "Todos") { return meters; } const selectedUser = users.find(u => u.id === selectedOrganism); if (!selectedUser || !selectedUser.project_id) { return []; } return meters.filter((m) => m.projectId === selectedUser.project_id); }, [meters, selectedOrganism, users, isOperator, userProjectId]); const filteredProjects = useMemo( () => [...new Set(filteredMeters.map((m) => m.projectName))].filter(Boolean) as string[], [filteredMeters] ); const selectedUserProjectName = useMemo(() => { // If user is OPERATOR, get their project name if (isOperator && userProjectId) { const project = projects.find(p => p.id === userProjectId); return project?.name || null; } // For ADMIN users with organism selector if (selectedOrganism === "Todos") return null; const selectedUser = users.find(u => u.id === selectedOrganism); if (!selectedUser || !selectedUser.project_id) return null; const project = projects.find(p => p.id === selectedUser.project_id); return project?.name || null; }, [selectedOrganism, users, projects, isOperator, userProjectId]); const chartData = useMemo(() => { // If user is OPERATOR, show only their project if (isOperator && selectedUserProjectName) { return [{ name: selectedUserProjectName, meterCount: filteredMeters.length, }]; } // For ADMIN users if (selectedOrganism === "Todos") { return filteredProjects.map((projectName) => ({ name: projectName, meterCount: filteredMeters.filter((m) => m.projectName === projectName).length, })); } if (selectedUserProjectName) { const meterCount = filteredMeters.length; return [{ name: selectedUserProjectName, meterCount: meterCount, }]; } return []; }, [selectedOrganism, filteredProjects, filteredMeters, selectedUserProjectName, isOperator]); // eslint-disable-next-line @typescript-eslint/no-explicit-any const handleBarClick = (data: any) => { if (data?.activeLabel) { navigateToMetersWithProject(data.activeLabel); } }; /* ================= ORGANISM FILTER (DRAWER) ================= */ const filteredOrganisms = useMemo(() => { const q = organismQuery.trim().toLowerCase(); if (!q) return organismsData; return organismsData.filter((o) => o.name.toLowerCase().includes(q)); }, [organismQuery, organismsData]); const [notifications, setNotifications] = useState([]); const [loadingNotifications, setLoadingNotifications] = useState(false); const loadNotifications = async () => { setLoadingNotifications(true); try { const response = await fetchNotifications({ limit: 10, page: 1 }); setNotifications(response.data); } catch (err) { console.error("Error loading notifications:", err); setNotifications([]); } finally { setLoadingNotifications(false); } }; useEffect(() => { if (!isOperator) { loadNotifications(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const formatNotificationType = (type: string): string => { const typeMap: Record = { NEGATIVE_FLOW: "Flujo Negativo", SYSTEM_ALERT: "Alerta del Sistema", MAINTENANCE: "Mantenimiento", }; return typeMap[type] || type; }; const formatNotificationTime = (dateString: string): string => { const date = new Date(dateString); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return "Hace un momento"; if (diffMins < 60) return `Hace ${diffMins} minuto${diffMins > 1 ? "s" : ""}`; if (diffHours < 24) return `Hace ${diffHours} hora${diffHours > 1 ? "s" : ""}`; if (diffDays < 7) return `Hace ${diffDays} día${diffDays > 1 ? "s" : ""}`; return date.toLocaleDateString(); }; const alerts: AlertItem[] = useMemo( () => notifications.map((n) => ({ company: n.meter_serial_number || "Sistema", type: formatNotificationType(n.notification_type), time: formatNotificationTime(n.created_at), })), [notifications] ); const formatAuditAction = (action: string): string => { const actionMap: Record = { CREATE: "creó", UPDATE: "actualizó", DELETE: "eliminó", READ: "consultó", LOGIN: "inició sesión", LOGOUT: "cerró sesión", EXPORT: "exportó", BULK_UPLOAD: "cargó masivamente", STATUS_CHANGE: "cambió estado de", PERMISSION_CHANGE: "cambió permisos de", }; return actionMap[action] || action.toLowerCase(); }; const formatTableName = (tableName: string): string => { const tableMap: Record = { meters: "medidor", concentrators: "concentrador", projects: "proyecto", users: "usuario", roles: "rol", gateways: "gateway", devices: "dispositivo", readings: "lectura", webhooks: "webhook", }; return tableMap[tableName] || tableName; }; const formatRelativeTime = (dateString: string): string => { const date = new Date(dateString); const now = new Date(); const diffMs = now.getTime() - date.getTime(); const diffMins = Math.floor(diffMs / 60000); const diffHours = Math.floor(diffMs / 3600000); const diffDays = Math.floor(diffMs / 86400000); if (diffMins < 1) return "Hace un momento"; if (diffMins < 60) return `Hace ${diffMins} minuto${diffMins > 1 ? "s" : ""}`; if (diffHours < 24) return `Hace ${diffHours} hora${diffHours > 1 ? "s" : ""}`; if (diffDays < 7) return `Hace ${diffDays} día${diffDays > 1 ? "s" : ""}`; return date.toLocaleDateString(); }; const formatAuditLog = (log: AuditLog): HistoryItem => { const action = formatAuditAction(log.action); const target = formatTableName(log.table_name); const recordInfo = log.description || log.record_id || target; return { user: log.user_name || log.user_email || "Sistema", action: action, target: recordInfo, time: formatRelativeTime(log.created_at), }; }; const history: HistoryItem[] = useMemo( () => auditLogs.map(formatAuditLog), // eslint-disable-next-line react-hooks/exhaustive-deps [auditLogs] ); /* ================= KPIs (Optional) ================= */ // eslint-disable-next-line @typescript-eslint/no-unused-vars const totalMeters = filteredMeters.length; // eslint-disable-next-line @typescript-eslint/no-unused-vars const totalProjects = filteredProjects.length; // eslint-disable-next-line @typescript-eslint/no-unused-vars const totalActiveAlerts = 0; // eslint-disable-next-line @typescript-eslint/no-unused-vars const avgMetersPerProject = totalProjects > 0 ? totalMeters / totalProjects : 0; return (
{/* Título + Selector */}
{/* ✅ Título + logo a la derecha */}

Sistema de Tomas de Agua

Monitorea, administra y controla tus operaciones en un solo lugar.

{/* ✅ Logo con z-index bajo para NO tapar menús */} Gestión de Recursos Hídricos
{/* Cards de Secciones */}
setPage("meters")} > Tomas
setPage("auditoria")} > Alertas
setPage("projects")} > Proyectos
Reportes
{isAdmin && (

Organismos Operadores

Seleccionado:{" "} {selectedOrganism === "Todos" ? "Todos" : organismsData.find(o => o.id === selectedOrganism)?.name || "Ninguno"}

{showOrganisms && (
{/* Overlay */}
{ setShowOrganisms(false); setOrganismQuery(""); }} /> {/* Panel */}
{/* Header */}

Organismos Operadores

Selecciona un organismo para filtrar la información del dashboard

{/* Search */}
setOrganismQuery(e.target.value)} placeholder="Buscar organismo…" className="w-full rounded-lg border border-gray-300 dark:border-zinc-700 dark:bg-zinc-800 dark:text-white px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-blue-200 dark:focus:ring-blue-500 dark:placeholder-gray-400" />
{/* List */}
{loadingUsers ? (
) : ( <>

Todos los Organismos

Ver todos los datos del sistema

TODOS
{filteredOrganisms.map((o) => { const active = o.id === selectedOrganism; return (

{o.name}

{o.region}

{o.status}
Rol {o.contact}
Email {o.region}
Proyectos {o.projects}
Medidores {o.meters}
Último acceso {o.lastSync}
); })} )} {!loadingUsers && filteredOrganisms.length === 0 && (
No se encontraron organismos.
)}
{/* Footer */}
Mostrando {filteredOrganisms.length} organismo{filteredOrganisms.length !== 1 ? 's' : ''} de {users.length} total{users.length !== 1 ? 'es' : ''}
)}
)}
{/* Gráfica */}

Numero de Medidores por Proyecto

Click en barra para ver tomas
{chartData.length === 0 && selectedOrganism !== "Todos" ? (

{selectedUserProjectName ? "Este organismo no tiene medidores registrados" : "Este organismo no tiene un proyecto asignado"}

{selectedUserProjectName && (

Proyecto asignado: {selectedUserProjectName}

)}
) : chartData.length === 0 ? (

No hay datos disponibles

) : ( <>
{selectedOrganism !== "Todos" && selectedUserProjectName && (
Proyecto del organismo: {selectedUserProjectName}
Total de medidores: {filteredMeters.length}
)} )}
{!isOperator && (

Historial Reciente de Auditoria

{loadingAuditLogs ? (
) : history.length === 0 ? (

No hay registros de auditoría disponibles

) : (
    {history.map((h, i) => (
  • {h.user} {h.action}{" "} {h.target}

    {h.time}

  • ))}
)}
)} {!isOperator && (

Ultimas Alertas

{loadingNotifications ? (
) : alerts.length === 0 ? (

No hay alertas disponibles

) : (
    {alerts.map((a, i) => (
  • {a.company} - {a.type}
    {a.time}
  • ))}
)}
)}
); }