Merge pull request #1 from luanngel/DevMarlene1
Se agrego la interfaz ProjectsPage
This commit is contained in:
25
src/App.tsx
25
src/App.tsx
@@ -5,22 +5,33 @@ import TopMenu from "./components/layout/TopMenu";
|
|||||||
import Home from "./pages/Home";
|
import Home from "./pages/Home";
|
||||||
import MetersPage from "./pages/meters/MeterPage";
|
import MetersPage from "./pages/meters/MeterPage";
|
||||||
import ConcentratorsPage from "./pages/concentrators/ConcentratorsPage";
|
import ConcentratorsPage from "./pages/concentrators/ConcentratorsPage";
|
||||||
import UsersPage from "./pages/UsersPage"; // nueva página
|
import ProjectsPage from "./pages/projects/ProjectsPage";
|
||||||
import RolesPage from "./pages/RolesPage"; // nueva página
|
import UsersPage from "./pages/UsersPage";
|
||||||
|
import RolesPage from "./pages/RolesPage";
|
||||||
|
|
||||||
|
export type Page =
|
||||||
|
| "home"
|
||||||
|
| "projects"
|
||||||
|
| "meters"
|
||||||
|
| "concentrators"
|
||||||
|
| "users"
|
||||||
|
| "roles";
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [page, setPage] = useState("home");
|
const [page, setPage] = useState<Page>("home");
|
||||||
|
|
||||||
const renderPage = () => {
|
const renderPage = () => {
|
||||||
switch (page) {
|
switch (page) {
|
||||||
|
case "projects":
|
||||||
|
return <ProjectsPage />;
|
||||||
case "meters":
|
case "meters":
|
||||||
return <MetersPage />;
|
return <MetersPage />;
|
||||||
case "concentrators":
|
case "concentrators":
|
||||||
return <ConcentratorsPage />;
|
return <ConcentratorsPage />;
|
||||||
case "users":
|
case "users":
|
||||||
return <UsersPage />; // nueva
|
return <UsersPage />;
|
||||||
case "roles":
|
case "roles":
|
||||||
return <RolesPage />; // nueva
|
return <RolesPage />;
|
||||||
case "home":
|
case "home":
|
||||||
default:
|
default:
|
||||||
return <Home />;
|
return <Home />;
|
||||||
@@ -32,7 +43,9 @@ export default function App() {
|
|||||||
<Sidebar setPage={setPage} />
|
<Sidebar setPage={setPage} />
|
||||||
<div className="flex-1 flex flex-col">
|
<div className="flex-1 flex flex-col">
|
||||||
<TopMenu />
|
<TopMenu />
|
||||||
<main className="flex-1 overflow-auto">{renderPage()}</main>
|
<main className="flex-1 overflow-auto">
|
||||||
|
{renderPage()}
|
||||||
|
</main>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,17 +7,16 @@ import {
|
|||||||
ExpandLess,
|
ExpandLess,
|
||||||
Menu,
|
Menu,
|
||||||
People,
|
People,
|
||||||
Key
|
|
||||||
} from "@mui/icons-material";
|
} from "@mui/icons-material";
|
||||||
|
import { Page } from "../../App";
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
setPage: (page: string) => void;
|
setPage: (page: Page) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Sidebar({ setPage }: SidebarProps) {
|
export default function Sidebar({ setPage }: SidebarProps) {
|
||||||
const [systemOpen, setSystemOpen] = useState(true);
|
const [systemOpen, setSystemOpen] = useState(true);
|
||||||
const [waterOpen, setWaterOpen] = useState(true);
|
const [usersOpen, setUsersOpen] = useState(true);
|
||||||
const [usersOpen, setUsersOpen] = useState(true); // Nuevo
|
|
||||||
const [pinned, setPinned] = useState(false);
|
const [pinned, setPinned] = useState(false);
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
|
|
||||||
@@ -51,7 +50,6 @@ export default function Sidebar({ setPage }: SidebarProps) {
|
|||||||
{/* MENU */}
|
{/* MENU */}
|
||||||
<div className="flex-1 py-4 px-2 overflow-y-auto">
|
<div className="flex-1 py-4 px-2 overflow-y-auto">
|
||||||
<ul className="space-y-1 text-white text-sm">
|
<ul className="space-y-1 text-white text-sm">
|
||||||
|
|
||||||
{/* DASHBOARD */}
|
{/* DASHBOARD */}
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
@@ -72,7 +70,9 @@ export default function Sidebar({ setPage }: SidebarProps) {
|
|||||||
<Settings className="w-5 h-5 shrink-0" />
|
<Settings className="w-5 h-5 shrink-0" />
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<>
|
<>
|
||||||
<span className="ml-3 flex-1 text-left">Project Management</span>
|
<span className="ml-3 flex-1 text-left">
|
||||||
|
Project Management
|
||||||
|
</span>
|
||||||
{systemOpen ? <ExpandLess /> : <ExpandMore />}
|
{systemOpen ? <ExpandLess /> : <ExpandMore />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -80,67 +80,38 @@ export default function Sidebar({ setPage }: SidebarProps) {
|
|||||||
|
|
||||||
{isExpanded && systemOpen && (
|
{isExpanded && systemOpen && (
|
||||||
<ul className="mt-1 space-y-1 text-xs">
|
<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>
|
<li>
|
||||||
<button
|
<button
|
||||||
onClick={() => setPage("concentrators")}
|
onClick={() => setPage("concentrators")}
|
||||||
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
||||||
>
|
>
|
||||||
Concentradores
|
Concentrators
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
onClick={() => setPage("meters")}
|
onClick={() => setPage("meters")}
|
||||||
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
className="pl-10 w-full text-left px-2 py-1.5 rounded-md hover:bg-white/10"
|
||||||
>
|
>
|
||||||
Medidores
|
Meters
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
{/* WATER METER SYSTEM
|
{/* USERS MANAGEMENT */}
|
||||||
<li>
|
<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>
|
|
||||||
*}
|
|
||||||
|
|
||||||
{/* SYSTEM USERS */}
|
|
||||||
<li>
|
|
||||||
<button
|
<button
|
||||||
onClick={() => isExpanded && setUsersOpen(!usersOpen)}
|
onClick={() => isExpanded && setUsersOpen(!usersOpen)}
|
||||||
className="flex items-center w-full px-2 py-2 rounded-md hover:bg-white/10 font-bold"
|
className="flex items-center w-full px-2 py-2 rounded-md hover:bg-white/10 font-bold"
|
||||||
@@ -148,7 +119,9 @@ export default function Sidebar({ setPage }: SidebarProps) {
|
|||||||
<People className="w-5 h-5 shrink-0" />
|
<People className="w-5 h-5 shrink-0" />
|
||||||
{isExpanded && (
|
{isExpanded && (
|
||||||
<>
|
<>
|
||||||
<span className="ml-3 flex-1 text-left">Users Management</span>
|
<span className="ml-3 flex-1 text-left">
|
||||||
|
Users Management
|
||||||
|
</span>
|
||||||
{usersOpen ? <ExpandLess /> : <ExpandMore />}
|
{usersOpen ? <ExpandLess /> : <ExpandMore />}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -174,9 +147,7 @@ export default function Sidebar({ setPage }: SidebarProps) {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
|
|||||||
366
src/pages/projects/ProjectsPage.tsx
Normal file
366
src/pages/projects/ProjectsPage.tsx
Normal file
@@ -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<Project[]> => {
|
||||||
|
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<Project[]>([]);
|
||||||
|
const [activeProject, setActiveProject] =
|
||||||
|
useState<Project | null>(null);
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
|
||||||
|
const [showModal, setShowModal] = useState(false);
|
||||||
|
const [editingId, setEditingId] =
|
||||||
|
useState<string | null>(null);
|
||||||
|
|
||||||
|
const emptyProject: Omit<Project, "id"> = {
|
||||||
|
areaName: "",
|
||||||
|
deviceSN: "",
|
||||||
|
deviceName: "",
|
||||||
|
deviceType: "",
|
||||||
|
deviceStatus: "ACTIVE",
|
||||||
|
operator: "",
|
||||||
|
installedTime: "",
|
||||||
|
communicationTime: "",
|
||||||
|
instructionManual: "",
|
||||||
|
};
|
||||||
|
|
||||||
|
const [form, setForm] =
|
||||||
|
useState<Omit<Project, "id">>(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 (
|
||||||
|
<div className="flex gap-6 p-6 w-full bg-gray-100">
|
||||||
|
<div className="flex-1 flex flex-col gap-6">
|
||||||
|
{/* HEADER */}
|
||||||
|
<div
|
||||||
|
className="rounded-xl shadow p-6 text-white flex justify-between items-center"
|
||||||
|
style={{
|
||||||
|
background:
|
||||||
|
"linear-gradient(135deg, #4c5f9e, #2a355d, #566bb8)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold">
|
||||||
|
Project Management
|
||||||
|
</h1>
|
||||||
|
<p className="text-sm text-blue-100">
|
||||||
|
Projects registered
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setForm(emptyProject);
|
||||||
|
setEditingId(null);
|
||||||
|
setShowModal(true);
|
||||||
|
}}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 bg-white text-[#4c5f9e] rounded-lg"
|
||||||
|
>
|
||||||
|
<Plus size={16} /> Add
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
if (!activeProject) return;
|
||||||
|
setEditingId(activeProject.id);
|
||||||
|
setForm({
|
||||||
|
areaName: activeProject.areaName,
|
||||||
|
deviceSN: activeProject.deviceSN,
|
||||||
|
deviceName: activeProject.deviceName,
|
||||||
|
deviceType: activeProject.deviceType,
|
||||||
|
deviceStatus: activeProject.deviceStatus,
|
||||||
|
operator: activeProject.operator,
|
||||||
|
installedTime: activeProject.installedTime,
|
||||||
|
communicationTime: activeProject.communicationTime,
|
||||||
|
instructionManual: activeProject.instructionManual,
|
||||||
|
});
|
||||||
|
setShowModal(true);
|
||||||
|
}}
|
||||||
|
disabled={!activeProject}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg disabled:opacity-60"
|
||||||
|
>
|
||||||
|
<Pencil size={16} /> Edit
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={handleDelete}
|
||||||
|
disabled={!activeProject}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg disabled:opacity-60"
|
||||||
|
>
|
||||||
|
<Trash2 size={16} /> Delete
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={loadProjects}
|
||||||
|
className="flex items-center gap-2 px-4 py-2 border border-white/40 rounded-lg"
|
||||||
|
>
|
||||||
|
<RefreshCcw size={16} /> Refresh
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* SEARCH */}
|
||||||
|
<input
|
||||||
|
className="bg-white rounded-lg shadow px-4 py-2 text-sm"
|
||||||
|
placeholder="Search project..."
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => setSearch(e.target.value)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* TABLE */}
|
||||||
|
<MaterialTable
|
||||||
|
title="Projects"
|
||||||
|
columns={[
|
||||||
|
{ title: "Area Name", field: "areaName" },
|
||||||
|
{ title: "Device S/N", field: "deviceSN" },
|
||||||
|
{ title: "Device Name", field: "deviceName" },
|
||||||
|
{ title: "Device Type", field: "deviceType" },
|
||||||
|
{
|
||||||
|
title: "Status",
|
||||||
|
field: "deviceStatus",
|
||||||
|
render: (rowData) => (
|
||||||
|
<span
|
||||||
|
className={`px-3 py-1 rounded-full text-xs font-semibold border ${
|
||||||
|
rowData.deviceStatus === "ACTIVE"
|
||||||
|
? "text-blue-600 border-blue-600"
|
||||||
|
: "text-red-600 border-red-600"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{rowData.deviceStatus}
|
||||||
|
</span>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{ 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",
|
||||||
|
}),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* MODAL */}
|
||||||
|
{showModal && (
|
||||||
|
<div className="fixed inset-0 bg-black/40 flex items-center justify-center">
|
||||||
|
<div className="bg-white rounded-xl p-6 w-96 space-y-3">
|
||||||
|
<h2 className="text-lg font-semibold">
|
||||||
|
{editingId ? "Edit Project" : "Add Project"}
|
||||||
|
</h2>
|
||||||
|
|
||||||
|
<input className="w-full border px-3 py-2 rounded" placeholder="Area Name"
|
||||||
|
value={form.areaName}
|
||||||
|
onChange={(e) => setForm({ ...form, areaName: e.target.value })} />
|
||||||
|
|
||||||
|
<input className="w-full border px-3 py-2 rounded" placeholder="Device S/N"
|
||||||
|
value={form.deviceSN}
|
||||||
|
onChange={(e) => setForm({ ...form, deviceSN: e.target.value })} />
|
||||||
|
|
||||||
|
<input className="w-full border px-3 py-2 rounded" placeholder="Device Name"
|
||||||
|
value={form.deviceName}
|
||||||
|
onChange={(e) => setForm({ ...form, deviceName: e.target.value })} />
|
||||||
|
|
||||||
|
<input className="w-full border px-3 py-2 rounded" placeholder="Device Type"
|
||||||
|
value={form.deviceType}
|
||||||
|
onChange={(e) => setForm({ ...form, deviceType: e.target.value })} />
|
||||||
|
|
||||||
|
<input className="w-full border px-3 py-2 rounded" placeholder="Operator"
|
||||||
|
value={form.operator}
|
||||||
|
onChange={(e) => setForm({ ...form, operator: e.target.value })} />
|
||||||
|
|
||||||
|
<input className="w-full border px-3 py-2 rounded" placeholder="Installed Time"
|
||||||
|
value={form.installedTime}
|
||||||
|
onChange={(e) => setForm({ ...form, installedTime: e.target.value })} />
|
||||||
|
|
||||||
|
<input className="w-full border px-3 py-2 rounded" placeholder="Communication Time"
|
||||||
|
value={form.communicationTime}
|
||||||
|
onChange={(e) => setForm({ ...form, communicationTime: e.target.value })} />
|
||||||
|
|
||||||
|
<input className="w-full border px-3 py-2 rounded" placeholder="Instruction Manual"
|
||||||
|
value={form.instructionManual}
|
||||||
|
onChange={(e) => setForm({ ...form, instructionManual: e.target.value })} />
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
setForm({
|
||||||
|
...form,
|
||||||
|
deviceStatus:
|
||||||
|
form.deviceStatus === "ACTIVE"
|
||||||
|
? "INACTIVE"
|
||||||
|
: "ACTIVE",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
className="w-full border rounded px-3 py-2"
|
||||||
|
>
|
||||||
|
Status: {form.deviceStatus}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="flex justify-end gap-2 pt-3">
|
||||||
|
<button onClick={() => setShowModal(false)}>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
onClick={handleSave}
|
||||||
|
className="bg-[#4c5f9e] text-white px-4 py-2 rounded"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -5,4 +5,11 @@ import tailwindcss from "@tailwindcss/vite"
|
|||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(),tailwindcss()],
|
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)
|
||||||
|
},
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user