Initial commit
This commit is contained in:
196
src/pages/AreaManagement.tsx
Normal file
196
src/pages/AreaManagement.tsx
Normal file
@@ -0,0 +1,196 @@
|
||||
import { useState } from "react";
|
||||
import { DataGrid, GridColDef } from "@mui/x-data-grid";
|
||||
import { Add, Delete, Refresh, Edit } from "@mui/icons-material";
|
||||
import { Button, IconButton, Dialog, DialogTitle, DialogContent, DialogActions, TextField, CircularProgress } from "@mui/material";
|
||||
|
||||
interface Area {
|
||||
id: number;
|
||||
name: string;
|
||||
no: string;
|
||||
code: string;
|
||||
sort: number;
|
||||
pushAddress: string;
|
||||
note: string;
|
||||
time: string;
|
||||
}
|
||||
|
||||
export default function AreaManagement() {
|
||||
const [rows, setRows] = useState<Area[]>([
|
||||
{ id: 1, name: "Operaciones", no: "001", code: "OP01", sort: 1, pushAddress: "Calle 123", note: "Área principal", time: "08:00-17:00" },
|
||||
{ id: 2, name: "Calidad", no: "002", code: "QA02", sort: 2, pushAddress: "Calle 456", note: "Revisión diaria", time: "09:00-18:00" },
|
||||
{ id: 3, name: "Mantenimiento", no: "003", code: "MT03", sort: 3, pushAddress: "Calle 789", note: "Turno A", time: "07:00-15:00" },
|
||||
]);
|
||||
|
||||
const [dialogOpen, setDialogOpen] = useState(false);
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [currentArea, setCurrentArea] = useState<Area>({
|
||||
id: 0,
|
||||
name: "",
|
||||
no: "",
|
||||
code: "",
|
||||
sort: 0,
|
||||
pushAddress: "",
|
||||
note: "",
|
||||
time: "",
|
||||
});
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Columns del DataGrid
|
||||
const columns: GridColDef[] = [
|
||||
{ field: "id", headerName: "ID", width: 70 },
|
||||
{ field: "name", headerName: "Área", width: 150 },
|
||||
{ field: "no", headerName: "Área No.", width: 120 },
|
||||
{ field: "code", headerName: "Código", width: 120 },
|
||||
{ field: "sort", headerName: "Sort", width: 80 },
|
||||
{ field: "pushAddress", headerName: "Push Address", width: 180 },
|
||||
{ field: "note", headerName: "Notas", width: 200 },
|
||||
{ field: "time", headerName: "Time", width: 120 },
|
||||
{
|
||||
field: "operate",
|
||||
headerName: "Operar",
|
||||
width: 150,
|
||||
renderCell: (params) => (
|
||||
<div className="flex gap-2">
|
||||
<IconButton color="primary" size="small" onClick={() => handleEdit(params.row)}>
|
||||
<Edit fontSize="small" />
|
||||
</IconButton>
|
||||
<IconButton color="error" size="small" onClick={() => handleDelete(params.row.id)}>
|
||||
<Delete fontSize="small" />
|
||||
</IconButton>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
// FUNCIONES CRUD
|
||||
const handleDelete = (id: number) => {
|
||||
if (confirm("¿Deseas eliminar esta área?")) {
|
||||
setRows(rows.filter(row => row.id !== id));
|
||||
}
|
||||
};
|
||||
|
||||
const handleEdit = (area: Area) => {
|
||||
setCurrentArea(area);
|
||||
setEditMode(true);
|
||||
setDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleAdd = () => {
|
||||
const newId = rows.length ? Math.max(...rows.map(r => r.id)) + 1 : 1;
|
||||
setRows([...rows, { ...currentArea, id: newId }]);
|
||||
setDialogOpen(false);
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const handleUpdate = () => {
|
||||
setRows(rows.map(r => (r.id === currentArea.id ? currentArea : r)));
|
||||
setDialogOpen(false);
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setCurrentArea({ id: 0, name: "", no: "", code: "", sort: 0, pushAddress: "", note: "", time: "" });
|
||||
setEditMode(false);
|
||||
};
|
||||
|
||||
const handleRefresh = () => {
|
||||
setLoading(true);
|
||||
setTimeout(() => {
|
||||
setRows([...rows]); // podrías reemplazar con fetch real
|
||||
setLoading(false);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 p-6 h-full">
|
||||
|
||||
{/* HEADER */}
|
||||
<div
|
||||
className="flex justify-between items-center p-5 rounded-xl"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #4c5f9e, #2a355d, #566bb8, #3d4e87)",
|
||||
backgroundSize: "350% 350%",
|
||||
animation: "gradientMove 10s ease infinite",
|
||||
color: "white",
|
||||
backdropFilter: "blur(10px)",
|
||||
border: "1px solid rgba(255,255,255,0.25)",
|
||||
boxShadow: "0px 8px 22px rgba(0,0,0,0.25)",
|
||||
}}
|
||||
>
|
||||
<h1 className="text-xl font-bold">Area Management</h1>
|
||||
<div className="flex gap-3">
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<Add />}
|
||||
sx={{
|
||||
color: "white",
|
||||
borderColor: "rgba(255,255,255,0.4)",
|
||||
"&:hover": { borderColor: "white", background: "rgba(255,255,255,0.15)" },
|
||||
}}
|
||||
onClick={() => setDialogOpen(true)}
|
||||
>
|
||||
Agregar
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={loading ? <CircularProgress size={18} color="inherit" /> : <Refresh />}
|
||||
sx={{
|
||||
color: "white",
|
||||
borderColor: "rgba(255,255,255,0.4)",
|
||||
"&:hover": { borderColor: "white", background: "rgba(255,255,255,0.15)" },
|
||||
}}
|
||||
onClick={handleRefresh}
|
||||
>
|
||||
{loading ? "Recargando..." : "Refrescar"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TABLA */}
|
||||
<div className="flex-1 bg-white rounded-xl overflow-hidden shadow-md">
|
||||
<DataGrid
|
||||
rows={rows}
|
||||
columns={columns}
|
||||
pageSize={5}
|
||||
rowsPerPageOptions={[5]}
|
||||
sx={{ border: "none", "& .MuiDataGrid-row:hover": { backgroundColor: "rgba(0,0,0,0.03)" } }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* DIALOG FORM */}
|
||||
<Dialog open={dialogOpen} onClose={() => { setDialogOpen(false); resetForm(); }}>
|
||||
<DialogTitle>{editMode ? "Editar Área" : "Agregar Nueva Área"}</DialogTitle>
|
||||
<DialogContent className="flex flex-col gap-3 min-w-[400px]">
|
||||
{["name","no","code","sort","pushAddress","note","time"].map((field) => (
|
||||
<TextField
|
||||
key={field}
|
||||
label={field.charAt(0).toUpperCase() + field.slice(1)}
|
||||
type={field === "sort" ? "number" : "text"}
|
||||
value={(currentArea as any)[field]}
|
||||
onChange={(e) => setCurrentArea({ ...currentArea, [field]: field === "sort" ? Number(e.target.value) : e.target.value })}
|
||||
fullWidth
|
||||
/>
|
||||
))}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Button onClick={() => { setDialogOpen(false); resetForm(); }}>Cancelar</Button>
|
||||
<Button variant="contained" onClick={editMode ? handleUpdate : handleAdd}>
|
||||
{editMode ? "Actualizar" : "Agregar"}
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
|
||||
<style>
|
||||
{`
|
||||
@keyframes gradientMove {
|
||||
0% {background-position: 0% 50%;}
|
||||
50% {background-position: 100% 50%;}
|
||||
100% {background-position: 0% 50%;}
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
95
src/pages/Home.tsx
Normal file
95
src/pages/Home.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { Cpu, Settings, BarChart3, Bell } from "lucide-react";
|
||||
import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from "recharts";
|
||||
|
||||
export default function Home() {
|
||||
const companies = [
|
||||
{ name: "Empresa A", tomas: 12, alerts: 2, consumption: 320 },
|
||||
{ name: "Empresa B", tomas: 8, alerts: 0, consumption: 210 },
|
||||
{ name: "Empresa C", tomas: 15, alerts: 1, consumption: 450 },
|
||||
];
|
||||
|
||||
const alerts = [
|
||||
{ company: "Empresa A", type: "Fuga", time: "Hace 2 horas" },
|
||||
{ company: "Empresa C", type: "Consumo alto", time: "Hace 5 horas" },
|
||||
{ company: "Empresa B", type: "Inactividad", time: "Hace 8 horas" },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col p-6 gap-8 w-full">
|
||||
|
||||
{/* Título */}
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold text-gray-800">Sistema de Tomas de Agua</h1>
|
||||
<p className="text-gray-600 mt-2">
|
||||
Monitorea, administra y controla tus operaciones en un solo lugar.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Cards de Secciones */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||
<div className="bg-white rounded-xl shadow p-6 flex flex-col items-center justify-center gap-2 hover:bg-blue-50 transition">
|
||||
<Cpu size={40} className="text-blue-600" />
|
||||
<span className="font-semibold text-gray-700">Tomas</span>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow p-6 flex flex-col items-center justify-center gap-2 hover:bg-red-50 transition">
|
||||
<Bell size={40} className="text-red-600" />
|
||||
<span className="font-semibold text-gray-700">Alertas</span>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow p-6 flex flex-col items-center justify-center gap-2 hover:bg-yellow-50 transition">
|
||||
<Settings size={40} className="text-yellow-600" />
|
||||
<span className="font-semibold text-gray-700">Mantenimiento</span>
|
||||
</div>
|
||||
<div className="bg-white rounded-xl shadow p-6 flex flex-col items-center justify-center gap-2 hover:bg-green-50 transition">
|
||||
<BarChart3 size={40} className="text-green-600" />
|
||||
<span className="font-semibold text-gray-700">Reportes</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Resumen de tomas por empresa */}
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{companies.map((c) => (
|
||||
<div
|
||||
key={c.name}
|
||||
className="bg-white rounded-xl shadow p-4 flex flex-col gap-1"
|
||||
>
|
||||
<span className="text-gray-500 text-sm">{c.name}</span>
|
||||
<span className="text-2xl font-bold text-gray-800">{c.tomas} Tomas</span>
|
||||
<span className={`text-sm font-medium ${c.alerts > 0 ? "text-red-500" : "text-green-500"}`}>
|
||||
{c.alerts} Alertas
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Gráfica de consumo */}
|
||||
<div className="bg-white rounded-xl shadow p-6">
|
||||
<h2 className="text-lg font-semibold mb-4">Consumo de Agua por Empresa</h2>
|
||||
<div className="h-60">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<BarChart data={companies} margin={{ top: 5, right: 20, left: 0, bottom: 5 }}>
|
||||
<CartesianGrid strokeDasharray="3 3" />
|
||||
<XAxis dataKey="name" />
|
||||
<YAxis />
|
||||
<Tooltip />
|
||||
<Bar dataKey="consumption" fill="#4c5f9e" />
|
||||
</BarChart>
|
||||
</ResponsiveContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Últimas alertas */}
|
||||
<div className="bg-white rounded-xl shadow p-6">
|
||||
<h2 className="text-lg font-semibold mb-4">Últimas Alertas</h2>
|
||||
<ul className="divide-y divide-gray-200">
|
||||
{alerts.map((a, i) => (
|
||||
<li key={i} className="py-2 flex justify-between">
|
||||
<span>{a.company} - {a.type}</span>
|
||||
<span className="text-red-500 font-medium">{a.time}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
131
src/pages/OperatorManagement.tsx
Normal file
131
src/pages/OperatorManagement.tsx
Normal file
@@ -0,0 +1,131 @@
|
||||
import { useState } from "react";
|
||||
|
||||
interface OperatorManagementProps {
|
||||
subPage: string;
|
||||
}
|
||||
|
||||
export default function OperatorManagement({ subPage }: OperatorManagementProps) {
|
||||
const [search, setSearch] = useState("");
|
||||
|
||||
const users = [
|
||||
{ id: 1, name: "Neil Sims", email: "neil.sims@flowbite.com", role: "React Developer", status: "Online", img: "https://randomuser.me/api/portraits/men/1.jpg" },
|
||||
{ id: 2, name: "Bonnie Green", email: "bonnie@flowbite.com", role: "Designer", status: "Online", img: "https://randomuser.me/api/portraits/women/2.jpg" },
|
||||
{ id: 3, name: "Jese Leos", email: "jese@flowbite.com", role: "Vue JS Developer", status: "Online", img: "https://randomuser.me/api/portraits/men/3.jpg" },
|
||||
{ id: 4, name: "Thomas Lean", email: "thames@flowbite.com", role: "UI/UX Engineer", status: "Online", img: "https://randomuser.me/api/portraits/men/4.jpg" },
|
||||
{ id: 5, name: "Leslie Livingston", email: "leslie@flowbite.com", role: "SEO Specialist", status: "Offline", img: "https://randomuser.me/api/portraits/women/5.jpg" },
|
||||
];
|
||||
|
||||
const filteredUsers = users.filter(
|
||||
(user) =>
|
||||
user.name.toLowerCase().includes(search.toLowerCase()) ||
|
||||
user.email.toLowerCase().includes(search.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6 p-6">
|
||||
{/* HEADER ANIMADO */}
|
||||
<div className="flex flex-col md:flex-row justify-between items-center p-5 rounded-xl relative overflow-hidden"
|
||||
style={{
|
||||
background: "linear-gradient(135deg, #4c5f9e, #2a355d, #566bb8, #3d4e87)",
|
||||
backgroundSize: "350% 350%",
|
||||
animation: "gradientMove 10s ease infinite",
|
||||
color: "white",
|
||||
backdropFilter: "blur(10px)",
|
||||
border: "1px solid rgba(255,255,255,0.25)",
|
||||
boxShadow: "0px 8px 22px rgba(0,0,0,0.25)",
|
||||
}}
|
||||
>
|
||||
<h1 className="text-2xl font-medium mb-3 md:mb-0">Gestión de Usuarios</h1>
|
||||
|
||||
{/* BOTONES ESTILO GHOST */}
|
||||
<div className="flex gap-3">
|
||||
<button className="px-4 py-2 border border-white/40 rounded-lg text-white hover:bg-white/15 hover:border-white transition">
|
||||
Agregar
|
||||
</button>
|
||||
<button className="px-4 py-2 border border-white/40 rounded-lg text-white hover:bg-white/15 hover:border-white transition">
|
||||
Borrar
|
||||
</button>
|
||||
<button className="px-4 py-2 border border-white/40 rounded-lg text-white hover:bg-white/15 hover:border-white transition">
|
||||
Refrescar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* TABLA */}
|
||||
<div className="relative overflow-x-auto shadow-sm rounded-lg border border-gray-300 bg-white">
|
||||
{/* SEARCH */}
|
||||
<div className="flex flex-col md:flex-row items-center justify-between p-4 space-y-4 md:space-y-0">
|
||||
<div className="relative w-full max-w-xs">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Search"
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-full pl-9 pr-3 py-2 border border-gray-300 rounded shadow-sm text-gray-700 text-sm focus:outline-none focus:ring-2 focus:ring-blue-300"
|
||||
/>
|
||||
<div className="absolute inset-y-0 left-2 flex items-center pointer-events-none">
|
||||
<svg className="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-3.5-3.5M17 10a7 7 0 1 1-14 0 7 7 0 0 1 14 0Z" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table className="w-full text-sm text-left text-gray-700">
|
||||
<thead className="bg-gray-200 border-b border-gray-300">
|
||||
<tr>
|
||||
<th className="p-4">
|
||||
<input type="checkbox" className="w-4 h-4 border rounded" />
|
||||
</th>
|
||||
<th className="px-6 py-3 font-medium">Nombre</th>
|
||||
<th className="px-6 py-3 font-medium">Rol</th>
|
||||
<th className="px-6 py-3 font-medium">Estado</th>
|
||||
<th className="px-6 py-3 font-medium">Acción</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{filteredUsers.map((user, index) => (
|
||||
<tr
|
||||
key={user.id}
|
||||
className={`border-b border-gray-300 ${index % 2 === 0 ? "bg-gray-50" : "bg-white"} hover:bg-gray-100`}
|
||||
>
|
||||
<td className="p-4">
|
||||
<input type="checkbox" className="w-4 h-4 border rounded" />
|
||||
</td>
|
||||
<th className="flex items-center px-6 py-4 font-medium whitespace-nowrap">
|
||||
<img className="w-10 h-10 rounded-full" src={user.img} alt={user.name} />
|
||||
<div className="ml-3">
|
||||
<div className="text-base font-semibold">{user.name}</div>
|
||||
<div className="text-gray-500 text-sm">{user.email}</div>
|
||||
</div>
|
||||
</th>
|
||||
<td className="px-6 py-4">{user.role}</td>
|
||||
<td className="px-6 py-4">
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={`h-2.5 w-2.5 rounded-full mr-2 ${user.status === "Online" ? "bg-green-500" : "bg-red-500"}`}
|
||||
></div>
|
||||
{user.status}
|
||||
</div>
|
||||
</td>
|
||||
<td className="px-6 py-4">
|
||||
<button className="text-blue-600 font-medium hover:underline">Editar</button>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
{`
|
||||
@keyframes gradientMove {
|
||||
0% {background-position: 0% 50%;}
|
||||
50% {background-position: 100% 50%;}
|
||||
100% {background-position: 0% 50%;}
|
||||
}
|
||||
`}
|
||||
</style>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
143
src/pages/Sidebar.tsx
Normal file
143
src/pages/Sidebar.tsx
Normal file
@@ -0,0 +1,143 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Home,
|
||||
Settings,
|
||||
WaterDrop,
|
||||
ExpandMore,
|
||||
ExpandLess,
|
||||
Menu,
|
||||
} from "@mui/icons-material";
|
||||
|
||||
export default function Sidebar({ setPage }: any) {
|
||||
const [systemOpen, setSystemOpen] = useState(true);
|
||||
const [waterOpen, setWaterOpen] = 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>
|
||||
|
||||
{/* SYSTEM SETTINGS */}
|
||||
<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">
|
||||
System Settings
|
||||
</span>
|
||||
{systemOpen ? <ExpandLess /> : <ExpandMore />}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{isExpanded && systemOpen && (
|
||||
<ul className="mt-1 space-y-1 text-xs">
|
||||
<li>
|
||||
<button
|
||||
onClick={() => setPage("area")}
|
||||
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
||||
>
|
||||
Area Management
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
onClick={() => setPage("operator")}
|
||||
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
||||
>
|
||||
Operator Management
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
|
||||
{/* WATER METER SYSTEM */}
|
||||
<li>
|
||||
<button
|
||||
onClick={() => isExpanded && setWaterOpen(!waterOpen)}
|
||||
className="flex items-center w-full px-2 py-2 rounded-md hover:bg-white/10 font-bold"
|
||||
>
|
||||
<WaterDrop className="w-5 h-5 shrink-0" />
|
||||
{isExpanded && (
|
||||
<>
|
||||
<span className="ml-3 flex-1 text-left">
|
||||
Water Meter System Management
|
||||
</span>
|
||||
{waterOpen ? <ExpandLess /> : <ExpandMore />}
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
|
||||
{isExpanded && waterOpen && (
|
||||
<ul className="mt-1 space-y-1 text-xs">
|
||||
{[
|
||||
["water-install", "Water Meter Installation"],
|
||||
["device-install", "Device Installation"],
|
||||
["meter-management", "Meter Management"],
|
||||
["device-management", "Device Management"],
|
||||
["data-monitoring", "Data Monitoring"],
|
||||
["data-query", "Data Query"],
|
||||
].map(([key, label]) => (
|
||||
<li key={key}>
|
||||
<button
|
||||
onClick={() => setPage(key)}
|
||||
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</li>
|
||||
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
);
|
||||
}
|
||||
61
src/pages/TopMenu.tsx
Normal file
61
src/pages/TopMenu.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import React from "react";
|
||||
import { Bell, User, Settings } from "lucide-react";
|
||||
|
||||
interface TopMenuProps {
|
||||
page: string;
|
||||
subPage: string;
|
||||
setSubPage: (subPage: string) => void;
|
||||
}
|
||||
|
||||
const TopMenu: React.FC<TopMenuProps> = ({ page, subPage, setSubPage }) => {
|
||||
return (
|
||||
<header
|
||||
className="h-14 shrink-0 flex items-center justify-between px-4 text-white"
|
||||
style={{
|
||||
background:
|
||||
"linear-gradient(135deg, #4c5f9e, #2a355d, #566bb8, #3d4e87)",
|
||||
}}
|
||||
>
|
||||
{/* IZQUIERDA */}
|
||||
<div className="flex items-center gap-2 text-sm font-medium opacity-90">
|
||||
{page !== "home" && (
|
||||
<>
|
||||
<span className="capitalize">{page}</span>
|
||||
{subPage !== "default" && (
|
||||
<>
|
||||
<span className="opacity-60">/</span>
|
||||
<span className="capitalize">{subPage}</span>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* DERECHA */}
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
aria-label="Notificaciones"
|
||||
className="p-2 rounded-full hover:bg-white/10 transition"
|
||||
>
|
||||
<Bell size={20} />
|
||||
</button>
|
||||
|
||||
<button
|
||||
aria-label="Configuración"
|
||||
className="p-2 rounded-full hover:bg-white/10 transition"
|
||||
>
|
||||
<Settings size={20} />
|
||||
</button>
|
||||
|
||||
<div
|
||||
className="w-9 h-9 rounded-full bg-white/15 flex items-center justify-center cursor-pointer hover:bg-white/25 transition"
|
||||
title="Perfil"
|
||||
>
|
||||
<User size={20} />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
};
|
||||
|
||||
export default TopMenu;
|
||||
Reference in New Issue
Block a user