Backend (water-api/): - Crear API REST completa con Express + TypeScript - Implementar autenticación JWT con refresh tokens - CRUD completo para: projects, concentrators, meters, gateways, devices, users, roles - Agregar validación con Zod para todas las entidades - Implementar webhooks para The Things Stack (LoRaWAN) - Agregar endpoint de lecturas con filtros y resumen de consumo - Implementar carga masiva de medidores via Excel (.xlsx) Frontend: - Crear cliente HTTP con manejo automático de JWT y refresh - Actualizar todas las APIs para usar nuevo backend - Agregar sistema de autenticación real (login, logout, me) - Agregar selector de tipo (LORA, LoRaWAN, Grandes) en concentradores y medidores - Agregar campo Meter ID en medidores - Crear modal de carga masiva para medidores - Agregar página de consumo con gráficas y filtros - Corregir carga de proyectos independiente de datos existentes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
164 lines
5.0 KiB
TypeScript
164 lines
5.0 KiB
TypeScript
import { useState } from "react";
|
|
import {
|
|
Home,
|
|
Settings,
|
|
ExpandMore,
|
|
ExpandLess,
|
|
Menu,
|
|
People,
|
|
} from "@mui/icons-material";
|
|
import { Page } from "../../App";
|
|
|
|
interface SidebarProps {
|
|
setPage: (page: Page) => void;
|
|
}
|
|
|
|
export default function Sidebar({ setPage }: SidebarProps) {
|
|
const [systemOpen, setSystemOpen] = useState(true);
|
|
const [usersOpen, setUsersOpen] = useState(true);
|
|
const [pinned, setPinned] = useState(false);
|
|
const [hovered, setHovered] = useState(false);
|
|
|
|
const isExpanded = pinned || hovered;
|
|
|
|
return (
|
|
<aside
|
|
onMouseEnter={() => setHovered(true)}
|
|
onMouseLeave={() => setHovered(false)}
|
|
className={`
|
|
h-full bg-[#1f2a48] border-r border-white/10
|
|
transition-all duration-300 flex flex-col overflow-hidden
|
|
${isExpanded ? "w-72" : "w-16"}
|
|
`}
|
|
>
|
|
{/* HEADER */}
|
|
<div className="border-b border-white/10 px-4 py-3 flex items-center gap-3">
|
|
<button
|
|
onClick={() => setPinned(!pinned)}
|
|
className="text-white opacity-90 hover:opacity-100"
|
|
>
|
|
<Menu />
|
|
</button>
|
|
{isExpanded && (
|
|
<span className="text-lg font-bold text-white whitespace-nowrap">
|
|
Water System
|
|
</span>
|
|
)}
|
|
</div>
|
|
|
|
{/* MENU */}
|
|
<div className="flex-1 py-4 px-2 overflow-y-auto">
|
|
<ul className="space-y-1 text-white text-sm">
|
|
{/* DASHBOARD */}
|
|
<li>
|
|
<button
|
|
onClick={() => setPage("home")}
|
|
className="flex items-center w-full px-2 py-2 rounded-md hover:bg-white/10 font-bold"
|
|
>
|
|
<Home className="w-5 h-5 shrink-0" />
|
|
{isExpanded && <span className="ml-3">Dashboard</span>}
|
|
</button>
|
|
</li>
|
|
|
|
{/* PROJECT MANAGEMENT */}
|
|
<li>
|
|
<button
|
|
onClick={() => isExpanded && setSystemOpen(!systemOpen)}
|
|
className="flex items-center w-full px-2 py-2 rounded-md hover:bg-white/10 font-bold"
|
|
>
|
|
<Settings className="w-5 h-5 shrink-0" />
|
|
{isExpanded && (
|
|
<>
|
|
<span className="ml-3 flex-1 text-left">
|
|
Project Management
|
|
</span>
|
|
{systemOpen ? <ExpandLess /> : <ExpandMore />}
|
|
</>
|
|
)}
|
|
</button>
|
|
|
|
{isExpanded && systemOpen && (
|
|
<ul className="mt-1 space-y-1 text-xs">
|
|
<li>
|
|
<button
|
|
onClick={() => setPage("projects")}
|
|
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
|
>
|
|
Projects
|
|
</button>
|
|
</li>
|
|
|
|
<li>
|
|
<button
|
|
onClick={() => setPage("concentrators")}
|
|
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
|
>
|
|
Concentrators
|
|
</button>
|
|
</li>
|
|
|
|
<li>
|
|
<button
|
|
onClick={() => setPage("meters")}
|
|
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
|
>
|
|
Meters
|
|
</button>
|
|
</li>
|
|
|
|
<li>
|
|
<button
|
|
onClick={() => setPage("consumption")}
|
|
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
|
>
|
|
Consumo
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
)}
|
|
</li>
|
|
|
|
{/* USERS MANAGEMENT */}
|
|
<li>
|
|
<button
|
|
onClick={() => isExpanded && setUsersOpen(!usersOpen)}
|
|
className="flex items-center w-full px-2 py-2 rounded-md hover:bg-white/10 font-bold"
|
|
>
|
|
<People className="w-5 h-5 shrink-0" />
|
|
{isExpanded && (
|
|
<>
|
|
<span className="ml-3 flex-1 text-left">
|
|
Users Management
|
|
</span>
|
|
{usersOpen ? <ExpandLess /> : <ExpandMore />}
|
|
</>
|
|
)}
|
|
</button>
|
|
|
|
{isExpanded && usersOpen && (
|
|
<ul className="mt-1 space-y-1 text-xs">
|
|
<li>
|
|
<button
|
|
onClick={() => setPage("users")}
|
|
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
|
>
|
|
Users
|
|
</button>
|
|
</li>
|
|
<li>
|
|
<button
|
|
onClick={() => setPage("roles")}
|
|
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
|
>
|
|
Roles
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
)}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</aside>
|
|
);
|
|
}
|