diff --git a/src/App.tsx b/src/App.tsx index 697b492..183cf0d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,22 +5,33 @@ import TopMenu from "./components/layout/TopMenu"; import Home from "./pages/Home"; import MetersPage from "./pages/meters/MeterPage"; import ConcentratorsPage from "./pages/concentrators/ConcentratorsPage"; -import UsersPage from "./pages/UsersPage"; // nueva página -import RolesPage from "./pages/RolesPage"; // nueva página +import ProjectsPage from "./pages/projects/ProjectsPage"; +import UsersPage from "./pages/UsersPage"; +import RolesPage from "./pages/RolesPage"; + +export type Page = + | "home" + | "projects" + | "meters" + | "concentrators" + | "users" + | "roles"; export default function App() { - const [page, setPage] = useState("home"); + const [page, setPage] = useState("home"); const renderPage = () => { switch (page) { + case "projects": + return ; case "meters": return ; case "concentrators": return ; case "users": - return ; // nueva + return ; case "roles": - return ; // nueva + return ; case "home": default: return ; @@ -32,7 +43,9 @@ export default function App() {
-
{renderPage()}
+
+ {renderPage()} +
); diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 5a3ac72..5e61852 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -7,17 +7,16 @@ import { ExpandLess, Menu, People, - Key } from "@mui/icons-material"; +import { Page } from "../../App"; interface SidebarProps { - setPage: (page: string) => void; + setPage: (page: Page) => void; } export default function Sidebar({ setPage }: SidebarProps) { const [systemOpen, setSystemOpen] = useState(true); - const [waterOpen, setWaterOpen] = useState(true); - const [usersOpen, setUsersOpen] = useState(true); // Nuevo + const [usersOpen, setUsersOpen] = useState(true); const [pinned, setPinned] = useState(false); const [hovered, setHovered] = useState(false); @@ -51,7 +50,6 @@ export default function Sidebar({ setPage }: SidebarProps) { {/* MENU */}
    - {/* DASHBOARD */}
  • +
  • +
  • +
)} - {/* WATER METER SYSTEM + {/* USERS MANAGEMENT */}
  • - {isExpanded && waterOpen && ( + {isExpanded && usersOpen && (
      - {[ - ["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]) => ( -
    • - -
    • - ))} +
    • + +
    • +
    • + +
    )}
  • - *} - - {/* SYSTEM USERS */} -
  • - - - {isExpanded && usersOpen && ( -
      -
    • - -
    • -
    • - -
    • -
    - )} -
  • - -
    diff --git a/src/pages/projects/ProjectsPage.tsx b/src/pages/projects/ProjectsPage.tsx new file mode 100644 index 0000000..e362b72 --- /dev/null +++ b/src/pages/projects/ProjectsPage.tsx @@ -0,0 +1,366 @@ +import { useEffect, useState } from "react"; +import { Plus, Trash2, Pencil, RefreshCcw } from "lucide-react"; +import MaterialTable from "@material-table/core"; + +/* ================= TYPES ================= */ +interface Project { + id: string; + areaName: string; + deviceSN: string; + deviceName: string; + deviceType: string; + deviceStatus: "ACTIVE" | "INACTIVE"; + operator: string; + installedTime: string; + communicationTime: string; + instructionManual: string; +} + +/* ================= MOCK DATA ================= */ +const mockProjects: Project[] = [ + { + id: "1", + areaName: "Zona Norte", + deviceSN: "SN-001", + deviceName: "Sensor Alpha", + deviceType: "Flow Meter", + deviceStatus: "ACTIVE", + operator: "Juan Pérez", + installedTime: "2024-01-10", + communicationTime: "2024-01-11", + instructionManual: "Manual Alpha", + }, + { + id: "2", + areaName: "Zona Centro", + deviceSN: "SN-002", + deviceName: "Sensor Beta", + deviceType: "Pressure Meter", + deviceStatus: "INACTIVE", + operator: "María López", + installedTime: "2024-02-05", + communicationTime: "2024-02-06", + instructionManual: "Manual Beta", + }, + { + id: "3", + areaName: "Zona Sur", + deviceSN: "SN-003", + deviceName: "Sensor Gamma", + deviceType: "Flow Meter", + deviceStatus: "ACTIVE", + operator: "Carlos Ruiz", + installedTime: "2024-03-01", + communicationTime: "2024-03-02", + instructionManual: "Manual Gamma", + }, +]; + +/* ================= API ================= */ +const API_URL = "/api/v2/tables/m05u6wpquvdbv3c/records"; + +const fetchProjects = async (): Promise => { + const res = await fetch(API_URL); + const data = await res.json(); + + return data.records.map((r: any) => ({ + id: r.id, + areaName: r.fields["Area Name"] ?? "", + deviceSN: r.fields["Device S/N"] ?? "", + deviceName: r.fields["Device Name"] ?? "", + deviceType: r.fields["Device Type"] ?? "", + deviceStatus: + r.fields["Device Status"] === "INACTIVE" + ? "INACTIVE" + : "ACTIVE", + operator: r.fields["Operator"] ?? "", + installedTime: r.fields["Installed Time"] ?? "", + communicationTime: r.fields["Communication Time"] ?? "", + instructionManual: r.fields["Instruction Manual"] ?? "", + })); +}; + +/* ================= COMPONENT ================= */ +export default function ProjectsPage() { + const [projects, setProjects] = useState([]); + const [activeProject, setActiveProject] = + useState(null); + const [search, setSearch] = useState(""); + + const [showModal, setShowModal] = useState(false); + const [editingId, setEditingId] = + useState(null); + + const emptyProject: Omit = { + areaName: "", + deviceSN: "", + deviceName: "", + deviceType: "", + deviceStatus: "ACTIVE", + operator: "", + installedTime: "", + communicationTime: "", + instructionManual: "", + }; + + const [form, setForm] = + useState>(emptyProject); + + /* ================= LOAD ================= */ + const loadProjects = async () => { + try { + const data = await fetchProjects(); + if (data.length === 0) { + setProjects(mockProjects); + } else { + setProjects(data); + } + } catch { + setProjects(mockProjects); + } + }; + + useEffect(() => { + loadProjects(); + }, []); + + /* ================= CRUD ================= */ + const handleSave = () => { + if (editingId) { + setProjects((prev) => + prev.map((p) => + p.id === editingId + ? { ...p, ...form } + : p + ) + ); + } else { + setProjects((prev) => [ + ...prev, + { id: Date.now().toString(), ...form }, + ]); + } + + setShowModal(false); + setEditingId(null); + setForm(emptyProject); + setActiveProject(null); + }; + + const handleDelete = () => { + if (!activeProject) return; + setProjects((prev) => + prev.filter( + (p) => p.id !== activeProject.id + ) + ); + setActiveProject(null); + }; + + /* ================= FILTER ================= */ + const filtered = projects.filter((p) => + `${p.areaName} ${p.deviceName} ${p.deviceSN}` + .toLowerCase() + .includes(search.toLowerCase()) + ); + + /* ================= UI ================= */ + return ( +
    +
    + {/* HEADER */} +
    +
    +

    + Project Management +

    +

    + Projects registered +

    +
    + +
    + + + + + + + +
    +
    + + {/* SEARCH */} + setSearch(e.target.value)} + /> + + {/* TABLE */} + ( + + {rowData.deviceStatus} + + ), + }, + { title: "Operator", field: "operator" }, + { title: "Installed Time", field: "installedTime" }, + { title: "Communication Time", field: "communicationTime" }, + { title: "Instruction Manual", field: "instructionManual" }, + ]} + data={filtered} + onRowClick={(_, rowData) => + setActiveProject(rowData as Project) + } + options={{ + search: false, + paging: true, + sorting: true, + rowStyle: (rowData) => ({ + backgroundColor: + activeProject?.id === + (rowData as Project).id + ? "#EEF2FF" + : "#FFFFFF", + }), + }} + /> +
    + + {/* MODAL */} + {showModal && ( +
    +
    +

    + {editingId ? "Edit Project" : "Add Project"} +

    + + setForm({ ...form, areaName: e.target.value })} /> + + setForm({ ...form, deviceSN: e.target.value })} /> + + setForm({ ...form, deviceName: e.target.value })} /> + + setForm({ ...form, deviceType: e.target.value })} /> + + setForm({ ...form, operator: e.target.value })} /> + + setForm({ ...form, installedTime: e.target.value })} /> + + setForm({ ...form, communicationTime: e.target.value })} /> + + setForm({ ...form, instructionManual: e.target.value })} /> + + + +
    + + +
    +
    +
    + )} +
    + ); +} diff --git a/vite.config.ts b/vite.config.ts index a1c3892..437d9be 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,4 +5,11 @@ import tailwindcss from "@tailwindcss/vite" // https://vitejs.dev/config/ export default defineConfig({ plugins: [react(),tailwindcss()], + + server: { + host: '0.0.0.0', // Esto permite que el servidor escuche en todas las IP disponibles + port: 5173, // Puerto por defecto de Vite (puedes cambiarlo si lo deseas) + }, + }) +