diff --git a/src/App.tsx b/src/App.tsx index 3c761f4..e709adf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,13 +19,20 @@ export type Page = export default function App() { const [page, setPage] = useState("home"); + const [subPage, setSubPage] = useState("default"); + const [selectedProject, setSelectedProject] = useState(""); + + const navigateToMetersWithProject = (projectName: string) => { + setSelectedProject(projectName); + setPage("meters"); + }; const renderPage = () => { switch (page) { case "projects": return ; case "meters": - return ; + return ; case "concentrators": return ; case "users": @@ -34,7 +41,7 @@ export default function App() { return ; case "home": default: - return ; + return ; } }; @@ -45,13 +52,13 @@ export default function App() {
- + {/* min-w-0: evita que páginas anchas (tablas) empujen el layout */}
- +
- + {/* Scroll solo aquí */}
{renderPage()} @@ -59,4 +66,4 @@ export default function App() {
); -} +} \ No newline at end of file diff --git a/src/api/meters.ts b/src/api/meters.ts index 29b3468..b5f880b 100644 --- a/src/api/meters.ts +++ b/src/api/meters.ts @@ -64,10 +64,11 @@ export interface Meter { } export const fetchMeters = async (): Promise => { + const pageSize = 9999; try { - const response = await fetch(METERS_API_URL, { + const response = await fetch(`${METERS_API_URL}?pageSize=${pageSize}`, { method: "GET", - headers: getAuthHeaders(), + headers: getAuthHeaders() }); if (!response.ok) { @@ -97,7 +98,6 @@ export const fetchMeters = async (): Promise => { usageAnalysisType: r.fields["Usage Analysis Type"] || "", installedTime: r.fields["Installed Time"] || "", })); - console.log ("ans", ans); return ans; } catch (error) { @@ -139,7 +139,9 @@ export const createMeter = async ( }); if (!response.ok) { - throw new Error(`Failed to create meter: ${response.status} ${response.statusText}`); + throw new Error( + `Failed to create meter: ${response.status} ${response.statusText}` + ); } const data = await response.json(); @@ -154,22 +156,32 @@ export const createMeter = async ( createdAt: createdRecord.fields.CreatedAt || meterData.createdAt, updatedAt: createdRecord.fields.UpdatedAt || meterData.updatedAt, areaName: createdRecord.fields["Area Name"] || meterData.areaName, - accountNumber: createdRecord.fields["Account Number"] || meterData.accountNumber, + accountNumber: + createdRecord.fields["Account Number"] || meterData.accountNumber, userName: createdRecord.fields["User Name"] || meterData.userName, - userAddress: createdRecord.fields["User Address"] || meterData.userAddress, - meterSerialNumber: createdRecord.fields["Meter S/N"] || meterData.meterSerialNumber, + userAddress: + createdRecord.fields["User Address"] || meterData.userAddress, + meterSerialNumber: + createdRecord.fields["Meter S/N"] || meterData.meterSerialNumber, meterName: createdRecord.fields["Meter Name"] || meterData.meterName, - meterStatus: createdRecord.fields["Meter Status"] || meterData.meterStatus, - protocolType: createdRecord.fields["Protocol Type"] || meterData.protocolType, + meterStatus: + createdRecord.fields["Meter Status"] || meterData.meterStatus, + protocolType: + createdRecord.fields["Protocol Type"] || meterData.protocolType, priceNo: createdRecord.fields["Price No."] || meterData.priceNo, priceName: createdRecord.fields["Price Name"] || meterData.priceName, - dmaPartition: createdRecord.fields["DMA Partition"] || meterData.dmaPartition, - supplyTypes: createdRecord.fields["Supply Types"] || meterData.supplyTypes, + dmaPartition: + createdRecord.fields["DMA Partition"] || meterData.dmaPartition, + supplyTypes: + createdRecord.fields["Supply Types"] || meterData.supplyTypes, deviceId: createdRecord.fields["Device ID"] || meterData.deviceId, deviceName: createdRecord.fields["Device Name"] || meterData.deviceName, deviceType: createdRecord.fields["Device Type"] || meterData.deviceType, - usageAnalysisType: createdRecord.fields["Usage Analysis Type"] || meterData.usageAnalysisType, - installedTime: createdRecord.fields["Installed Time"] || meterData.installedTime, + usageAnalysisType: + createdRecord.fields["Usage Analysis Type"] || + meterData.usageAnalysisType, + installedTime: + createdRecord.fields["Installed Time"] || meterData.installedTime, }; } catch (error) { console.error("Error creating meter:", error); @@ -214,9 +226,13 @@ export const updateMeter = async ( if (!response.ok) { if (response.status === 400) { const errorData = await response.json(); - throw new Error(`Bad Request: ${errorData.msg || "Invalid data provided"}`); + throw new Error( + `Bad Request: ${errorData.msg || "Invalid data provided"}` + ); } - throw new Error(`Failed to update meter: ${response.status} ${response.statusText}`); + throw new Error( + `Failed to update meter: ${response.status} ${response.statusText}` + ); } const data = await response.json(); @@ -231,22 +247,32 @@ export const updateMeter = async ( createdAt: updatedRecord.fields.CreatedAt || meterData.createdAt, updatedAt: updatedRecord.fields.UpdatedAt || meterData.updatedAt, areaName: updatedRecord.fields["Area Name"] || meterData.areaName, - accountNumber: updatedRecord.fields["Account Number"] || meterData.accountNumber, + accountNumber: + updatedRecord.fields["Account Number"] || meterData.accountNumber, userName: updatedRecord.fields["User Name"] || meterData.userName, - userAddress: updatedRecord.fields["User Address"] || meterData.userAddress, - meterSerialNumber: updatedRecord.fields["Meter S/N"] || meterData.meterSerialNumber, + userAddress: + updatedRecord.fields["User Address"] || meterData.userAddress, + meterSerialNumber: + updatedRecord.fields["Meter S/N"] || meterData.meterSerialNumber, meterName: updatedRecord.fields["Meter Name"] || meterData.meterName, - meterStatus: updatedRecord.fields["Meter Status"] || meterData.meterStatus, - protocolType: updatedRecord.fields["Protocol Type"] || meterData.protocolType, + meterStatus: + updatedRecord.fields["Meter Status"] || meterData.meterStatus, + protocolType: + updatedRecord.fields["Protocol Type"] || meterData.protocolType, priceNo: updatedRecord.fields["Price No."] || meterData.priceNo, priceName: updatedRecord.fields["Price Name"] || meterData.priceName, - dmaPartition: updatedRecord.fields["DMA Partition"] || meterData.dmaPartition, - supplyTypes: updatedRecord.fields["Supply Types"] || meterData.supplyTypes, + dmaPartition: + updatedRecord.fields["DMA Partition"] || meterData.dmaPartition, + supplyTypes: + updatedRecord.fields["Supply Types"] || meterData.supplyTypes, deviceId: updatedRecord.fields["Device ID"] || meterData.deviceId, deviceName: updatedRecord.fields["Device Name"] || meterData.deviceName, deviceType: updatedRecord.fields["Device Type"] || meterData.deviceType, - usageAnalysisType: updatedRecord.fields["Usage Analysis Type"] || meterData.usageAnalysisType, - installedTime: updatedRecord.fields["Installed Time"] || meterData.installedTime, + usageAnalysisType: + updatedRecord.fields["Usage Analysis Type"] || + meterData.usageAnalysisType, + installedTime: + updatedRecord.fields["Installed Time"] || meterData.installedTime, }; } catch (error) { console.error("Error updating meter:", error); @@ -267,9 +293,13 @@ export const deleteMeter = async (id: string): Promise => { if (!response.ok) { if (response.status === 400) { const errorData = await response.json(); - throw new Error(`Bad Request: ${errorData.msg || "Invalid data provided"}`); + throw new Error( + `Bad Request: ${errorData.msg || "Invalid data provided"}` + ); } - throw new Error(`Failed to delete meter: ${response.status} ${response.statusText}`); + throw new Error( + `Failed to delete meter: ${response.status} ${response.statusText}` + ); } } catch (error) { console.error("Error deleting meter:", error); diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 887b3a5..fd5780c 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,7 +1,50 @@ import { Cpu, Settings, BarChart3, Bell } from "lucide-react"; -import { BarChart, Bar, XAxis, YAxis, Tooltip, ResponsiveContainer, CartesianGrid } from "recharts"; +import { useEffect, useState } from "react"; +import { + BarChart, + Bar, + XAxis, + YAxis, + Tooltip, + ResponsiveContainer, + CartesianGrid, +} from "recharts"; +import { fetchMeters, Meter } from "../api/meters"; +import { Page } from "../App"; + +export default function Home({ + setPage, + navigateToMetersWithProject +}: { + setPage: (page: Page) => void; + navigateToMetersWithProject: (projectName: string) => void; +}) { + const [allProjects, setAllProjects] = useState([]); + const [meters, setMeters] = useState([]); + + + const loadMeters = async () => { + const data = await fetchMeters(); + setMeters(data); + const projectsArray = [...new Set(data.map((record: Meter) => record["areaName"]))]; + setAllProjects(projectsArray); + } + useEffect(() => { + loadMeters(); + }, []); + + const chartData = allProjects.map((projectName) => ({ + name: projectName, + meterCount: meters.filter((meter) => meter.areaName === projectName).length, + })); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const handleBarClick = (data: any) => { + if (data.activeLabel) { + navigateToMetersWithProject(data.activeLabel); + } + }; -export default function Home() { // Datos de ejemplo para empresas const companies = [ { name: "Empresa A", tomas: 12, alerts: 2, consumption: 320 }, @@ -18,19 +61,45 @@ export default function Home() { // Historial tipo Google const history = [ - { user: "GRH", action: "Creó un nuevo medidor", target: "SN001", time: "Hace 5 minutos" }, - { user: "CESPT", action: "Actualizó concentrador", target: "Planta 1", time: "Hace 20 minutos" }, - { user: "GRH", action: "Eliminó un usuario", target: "Juan Pérez", time: "Hace 1 hora" }, - { user: "CESPT", action: "Creó un payload", target: "Payload 12", time: "Hace 2 horas" }, - { user: "GRH", action: "Actualizó medidor", target: "SN002", time: "Hace 3 horas" }, + { + user: "GRH", + action: "Creó un nuevo medidor", + target: "SN001", + time: "Hace 5 minutos", + }, + { + user: "CESPT", + action: "Actualizó concentrador", + target: "Planta 1", + time: "Hace 20 minutos", + }, + { + user: "GRH", + action: "Eliminó un usuario", + target: "Juan Pérez", + time: "Hace 1 hora", + }, + { + user: "CESPT", + action: "Creó un payload", + target: "Payload 12", + time: "Hace 2 horas", + }, + { + user: "GRH", + action: "Actualizó medidor", + target: "SN002", + time: "Hace 3 horas", + }, ]; return (
- {/* Título */}
-

Sistema de Tomas de Agua

+

+ Sistema de Tomas de Agua +

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

@@ -38,7 +107,10 @@ export default function Home() { {/* Cards de Secciones */}
-
+
setPage("meters")} + > Tomas
@@ -64,8 +136,14 @@ export default function Home() { className="bg-white rounded-xl shadow p-4 flex flex-col gap-1" > {c.name} - {c.tomas} Tomas - 0 ? "text-red-500" : "text-green-500"}`}> + + {c.tomas} Tomas + + 0 ? "text-red-500" : "text-green-500" + }`} + > {c.alerts} Alertas
@@ -74,15 +152,25 @@ export default function Home() { {/* Gráfica de consumo */}
-

Consumo de Agua por Empresa

+

+ Número de Medidores por Proyecto +

- + - +
@@ -97,7 +185,8 @@ export default function Home() {

- {h.user} {h.action} {h.target} + {h.user} {h.action}{" "} + {h.target}

{h.time}

@@ -112,13 +201,14 @@ export default function Home() {
    {alerts.map((a, i) => (
  • - {a.company} - {a.type} + + {a.company} - {a.type} + {a.time}
  • ))}
-
); } diff --git a/src/pages/concentrators/ConcentratorsPage.tsx b/src/pages/concentrators/ConcentratorsPage.tsx index 1755a8d..4e45f4a 100644 --- a/src/pages/concentrators/ConcentratorsPage.tsx +++ b/src/pages/concentrators/ConcentratorsPage.tsx @@ -1,7 +1,6 @@ import { useState, useEffect, useMemo } from "react"; import { Plus, Trash2, Pencil, RefreshCcw } from "lucide-react"; import MaterialTable from "@material-table/core"; -import { fetchProjectNames } from "../../api/projects"; import { fetchConcentrators, createConcentrator, @@ -18,6 +17,15 @@ interface User { project?: string; // asignado si no es superadmin } +interface GatewayData { + "Gateway ID": number; + "Gateway EUI": string; + "Gateway Name": string; + "Gateway Description": string; + "Antenna Placement": "Indoor" | "Outdoor"; + concentratorId?: string; +} + /* ================= COMPONENT ================= */ export default function ConcentratorsPage() { // Simulación de usuario actual @@ -31,22 +39,6 @@ export default function ConcentratorsPage() { const [loadingProjects, setLoadingProjects] = useState(true); const [loadingConcentrators, setLoadingConcentrators] = useState(true); - useEffect(() => { - const loadProjects = async () => { - try { - const projects = await fetchProjectNames(); - setAllProjects(projects); - } catch (error) { - console.error('Error loading projects:', error); - setAllProjects([]); - } finally { - setLoadingProjects(false); - } - }; - - loadProjects(); - }, []); - // Proyectos visibles según el usuario const visibleProjects = useMemo(() => currentUser.role === "SUPER_ADMIN" @@ -59,23 +51,33 @@ export default function ConcentratorsPage() { const [selectedProject, setSelectedProject] = useState(""); const [concentrators, setConcentrators] = useState([]); + const [filteredConcentrators, setFilteredConcentrators] = useState([]); useEffect(() => { - if (visibleProjects.length > 0 && !selectedProject) { - setSelectedProject(visibleProjects[0]); + if (selectedProject) { + const filtered = concentrators.filter( + (c) => c["Area Name"] === selectedProject + ); + setFilteredConcentrators(filtered); + } else { + setFilteredConcentrators(concentrators); } - }, [visibleProjects, selectedProject]); + }, [selectedProject, concentrators]); const loadConcentrators = async () => { setLoadingConcentrators(true); try { const data = await fetchConcentrators(); + const projectsArray = [...new Set(data.map((record) => record["Area Name"]))]; + setAllProjects(projectsArray); setConcentrators(data); } catch (error) { console.error("Error loading concentrators:", error); + setAllProjects([]); setConcentrators([]); } finally { setLoadingConcentrators(false); + setLoadingProjects(false); } }; @@ -101,11 +103,60 @@ export default function ConcentratorsPage() { "Instruction Manual": "", }); + const getEmptyGatewayData = (): GatewayData => ({ + "Gateway ID": 0, + "Gateway EUI": "", + "Gateway Name": "", + "Gateway Description": "", + "Antenna Placement": "Indoor", + }); + const [form, setForm] = useState>(getEmptyConcentrator()); + const [gatewayForm, setGatewayForm] = useState(getEmptyGatewayData()); + const [errors, setErrors] = useState<{ [key: string]: boolean }>({}); /* ================= CRUD ================= */ + const createOrUpdateGateway = async (gatewayData: GatewayData): Promise => { + //await fetch('/api/gateways', { method: 'POST', body: JSON.stringify(gatewayData) }) + + return new Promise((resolve) => { + setTimeout(() => { + console.log('Gateway data that would be sent to API:', gatewayData); + resolve(); + }, 500); + }); + }; + + const validateForm = (): boolean => { + const newErrors: { [key: string]: boolean } = {}; + + if (!form["Device Name"].trim()) newErrors["Device Name"] = true; + if (!form["Device S/N"].trim()) newErrors["Device S/N"] = true; + if (!form["Operator"].trim()) newErrors["Operator"] = true; + if (!form["Instruction Manual"].trim()) newErrors["Instruction Manual"] = true; + if (!form["Installed Time"]) newErrors["Installed Time"] = true; + if (!form["Device Time"]) newErrors["Device Time"] = true; + if (!form["Communication Time"]) newErrors["Communication Time"] = true; + + if (!gatewayForm["Gateway ID"] || gatewayForm["Gateway ID"] === 0) { + newErrors["Gateway ID"] = true; + } + if (!gatewayForm["Gateway EUI"].trim()) newErrors["Gateway EUI"] = true; + if (!gatewayForm["Gateway Name"].trim()) newErrors["Gateway Name"] = true; + if (!gatewayForm["Gateway Description"].trim()) newErrors["Gateway Description"] = true; + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + const handleSave = async () => { + if (!validateForm()) { + return; + } + try { + let savedConcentrator: Concentrator; + if (editingSerial) { const concentratorToUpdate = concentrators.find(c => c["Device S/N"] === editingSerial); if (!concentratorToUpdate) { @@ -118,13 +169,30 @@ export default function ConcentratorsPage() { c.id === concentratorToUpdate.id ? updatedConcentrator : c ) ); + savedConcentrator = updatedConcentrator; } else { const newConcentrator = await createConcentrator(form); setConcentrators((prev) => [...prev, newConcentrator]); + savedConcentrator = newConcentrator; } + + try { + const gatewayDataWithRef = { + ...gatewayForm, + concentratorId: savedConcentrator.id, + }; + await createOrUpdateGateway(gatewayDataWithRef); + console.log('Gateway data saved successfully'); + } catch (gatewayError) { + console.error('Error saving gateway data:', gatewayError); + alert('Concentrator saved, but there was an error saving gateway data.'); + } + setShowModal(false); setEditingSerial(null); setForm({ ...getEmptyConcentrator(), "Area Name": selectedProject }); + setGatewayForm(getEmptyGatewayData()); + setErrors({}); setActiveConcentrator(null); } catch (error) { console.error('Error saving concentrator:', error); @@ -159,12 +227,10 @@ export default function ConcentratorsPage() { } }; - /* ================= FILTER ================= */ - const filtered = concentrators.filter( + const searchFiltered = filteredConcentrators.filter( (c) => - (c["Device Name"].toLowerCase().includes(search.toLowerCase()) || - c["Device S/N"].toLowerCase().includes(search.toLowerCase())) && - c["Area Name"] === selectedProject + c["Device Name"].toLowerCase().includes(search.toLowerCase()) || + c["Device S/N"].toLowerCase().includes(search.toLowerCase()) ); /* ================= UI ================= */ @@ -187,11 +253,14 @@ export default function ConcentratorsPage() { ) : visibleProjects.length === 0 ? ( ) : ( - visibleProjects.map((proj) => ( - - )) + <> + + {visibleProjects.map((proj) => ( + + ))} + )} @@ -217,7 +286,10 @@ export default function ConcentratorsPage() {
+ - - setForm({ ...form, "Installed Time": e.target.value }) - } - /> +
+ { + setForm({ ...form, "Installed Time": e.target.value }); + if (errors["Installed Time"]) { + setErrors({ ...errors, "Installed Time": false }); + } + }} + required + /> + {errors["Installed Time"] && ( +

This field is required

+ )} +
- - setForm({ - ...form, - "Device Time": new Date(e.target.value).toISOString(), - }) - } - /> +
+ { + setForm({ + ...form, + "Device Time": new Date(e.target.value).toISOString(), + }); + if (errors["Device Time"]) { + setErrors({ ...errors, "Device Time": false }); + } + }} + required + /> + {errors["Device Time"] && ( +

This field is required

+ )} +
- - setForm({ - ...form, - "Communication Time": new Date(e.target.value).toISOString(), - }) - } - /> -
+
+ { + setForm({ + ...form, + "Communication Time": new Date(e.target.value).toISOString(), + }); + if (errors["Communication Time"]) { + setErrors({ ...errors, "Communication Time": false }); + } + }} + required + /> + {errors["Communication Time"] && ( +

This field is required

+ )} +
+
+
+

+ Gateway Information +

-
- +
+ { + setGatewayForm({ + ...gatewayForm, + "Gateway ID": parseInt(e.target.value) || 0, + }); + if (errors["Gateway ID"]) { + setErrors({ ...errors, "Gateway ID": false }); + } + }} + required + min="1" + /> + {errors["Gateway ID"] && ( +

This field is required

+ )} +
+ +
+ { + setGatewayForm({ ...gatewayForm, "Gateway EUI": e.target.value }); + if (errors["Gateway EUI"]) { + setErrors({ ...errors, "Gateway EUI": false }); + } + }} + required + /> + {errors["Gateway EUI"] && ( +

This field is required

+ )} +
+ +
+ { + setGatewayForm({ ...gatewayForm, "Gateway Name": e.target.value }); + if (errors["Gateway Name"]) { + setErrors({ ...errors, "Gateway Name": false }); + } + }} + required + /> + {errors["Gateway Name"] && ( +

This field is required

+ )} +
+ +
+ { + setGatewayForm({ + ...gatewayForm, + "Gateway Description": e.target.value, + }); + if (errors["Gateway Description"]) { + setErrors({ ...errors, "Gateway Description": false }); + } + }} + required + /> + {errors["Gateway Description"] && ( +

This field is required

+ )} +
+ + +
+ +
+ diff --git a/src/pages/meters/MeterPage.tsx b/src/pages/meters/MeterPage.tsx index 3223e31..80c0e54 100644 --- a/src/pages/meters/MeterPage.tsx +++ b/src/pages/meters/MeterPage.tsx @@ -1,7 +1,6 @@ -import { useState, useEffect, useMemo } from "react"; +import { useState, useEffect } from "react"; import { Plus, Trash2, Pencil, RefreshCcw } from "lucide-react"; import MaterialTable from "@material-table/core"; -import { fetchProjectNames } from "../../api/projects"; import { fetchMeters, createMeter, @@ -10,61 +9,24 @@ import { type Meter, } from "../../api/meters"; -/* ================= TYPES ================= */ - -interface User { - name: string; - role: "SUPER_ADMIN" | "USER"; - project?: string; // asignado si no es superadmin +interface DeviceData { + "Device ID": number; + "Device EUI": string; + "Join EUI": string; + "AppKey": string; + meterId?: string; } /* ================= COMPONENT ================= */ -export default function MeterManagement() { - // Simulación de usuario actual - const currentUser: User = { - name: "Admin GRH", - role: "SUPER_ADMIN", // cambiar a USER para probar otro caso - project: "CESPT", - }; - +export default function MeterManagement({ selectedProject: initialProject }: { selectedProject?: string } = {}) { const [allProjects, setAllProjects] = useState([]); const [loadingProjects, setLoadingProjects] = useState(true); - // Proyectos visibles según el usuario - const visibleProjects = useMemo(() => - currentUser.role === "SUPER_ADMIN" - ? allProjects - : currentUser.project - ? [currentUser.project] - : [], - [allProjects, currentUser.role, currentUser.project] - ); - const [selectedProject, setSelectedProject] = useState(""); - - useEffect(() => { - const loadProjects = async () => { - try { - const projects = await fetchProjectNames(); - setAllProjects(projects); - } catch (error) { - console.error('Error loading projects:', error); - setAllProjects([]); - } finally { - setLoadingProjects(false); - } - }; - - loadProjects(); - }, []); - - useEffect(() => { - if (visibleProjects.length > 0 && !selectedProject) { - setSelectedProject(visibleProjects[0]); - } - }, [visibleProjects, selectedProject]); + const [selectedProject, setSelectedProject] = useState(initialProject || ""); const [meters, setMeters] = useState([]); + const [filteredMeters, setFilteredMeters] = useState([]); const [loadingMeters, setLoadingMeters] = useState(true); const [activeMeter, setActiveMeter] = useState(null); const [search, setSearch] = useState(""); @@ -94,18 +56,40 @@ export default function MeterManagement() { installedTime: new Date().toISOString(), }; + const emptyDeviceData: DeviceData = { + "Device ID": 0, + "Device EUI": "", + "Join EUI": "", + "AppKey": "", + }; + + useEffect(() => { + if (selectedProject) { + const filtered = meters.filter((meter) => meter.areaName === selectedProject); + setFilteredMeters(filtered); + } else { + setFilteredMeters(meters); + } + }, [selectedProject, meters]); + const [form, setForm] = useState>(emptyMeter); + const [deviceForm, setDeviceForm] = useState(emptyDeviceData); + const [errors, setErrors] = useState<{ [key: string]: boolean }>({}); const loadMeters = async () => { setLoadingMeters(true); try { const data = await fetchMeters(); + const projectsArray = [...new Set(data.map((record) => record["areaName"]))]; + setAllProjects(projectsArray); setMeters(data); } catch (error) { console.error("Error loading meters:", error); + setAllProjects([]); setMeters([]); } finally { setLoadingMeters(false); + setLoadingProjects(false); } }; @@ -113,8 +97,53 @@ export default function MeterManagement() { loadMeters(); }, []); + useEffect(() => { + if (initialProject) { + setSelectedProject(initialProject); + } + }, [initialProject]); + + const createOrUpdateDevice = async (deviceData: DeviceData): Promise => { + //await fetch('/api/devices', { method: 'POST', body: JSON.stringify(deviceData) }) + + return new Promise((resolve) => { + setTimeout(() => { + console.log('Device data that would be sent to API:', deviceData); + resolve(); + }, 500); + }); + }; + + const validateForm = (): boolean => { + const newErrors: { [key: string]: boolean } = {}; + + // Required fields + if (!form.meterName.trim()) newErrors["meterName"] = true; + if (!form.meterSerialNumber.trim()) newErrors["meterSerialNumber"] = true; + if (!form.areaName.trim()) newErrors["areaName"] = true; + if (!form.deviceName.trim()) newErrors["deviceName"] = true; + if (!form.protocolType.trim()) newErrors["protocolType"] = true; + + // Device Configuration - Required + if (!deviceForm["Device ID"] || deviceForm["Device ID"] === 0) { + newErrors["Device ID"] = true; + } + if (!deviceForm["Device EUI"].trim()) newErrors["Device EUI"] = true; + if (!deviceForm["Join EUI"].trim()) newErrors["Join EUI"] = true; + if (!deviceForm["AppKey"].trim()) newErrors["AppKey"] = true; + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + const handleSave = async () => { + if (!validateForm()) { + return; + } + try { + let savedMeter: Meter; + if (editingId) { const meterToUpdate = meters.find(m => m.id === editingId); if (!meterToUpdate) { @@ -127,13 +156,30 @@ export default function MeterManagement() { m.id === editingId ? updatedMeter : m ) ); + savedMeter = updatedMeter; } else { const newMeter = await createMeter(form); setMeters((prev) => [...prev, newMeter]); + savedMeter = newMeter; } + + try { + const deviceDataWithRef = { + ...deviceForm, + meterId: savedMeter.id, + }; + await createOrUpdateDevice(deviceDataWithRef); + console.log('Device data saved successfully'); + } catch (deviceError) { + console.error('Error saving device data:', deviceError); + alert('Meter saved, but there was an error saving device data.'); + } + setShowModal(false); setEditingId(null); setForm(emptyMeter); + setDeviceForm(emptyDeviceData); + setErrors({}); setActiveMeter(null); } catch (error) { console.error('Error saving meter:', error); @@ -173,15 +219,6 @@ export default function MeterManagement() { setActiveMeter(null); }; - /* ================= FILTER ================= */ - const filtered = meters.filter( - (m) => - (m.meterName.toLowerCase().includes(search.toLowerCase()) || - m.meterSerialNumber.toLowerCase().includes(search.toLowerCase()) || - m.deviceId.toLowerCase().includes(search.toLowerCase()) || - m.areaName.toLowerCase().includes(search.toLowerCase())) - ); - /* ================= UI ================= */ return (
@@ -195,22 +232,25 @@ export default function MeterManagement() { value={selectedProject} onChange={(e) => setSelectedProject(e.target.value)} className="w-full border px-3 py-2 rounded" - disabled={loadingProjects || visibleProjects.length === 0} + disabled={loadingProjects || allProjects.length === 0} > {loadingProjects ? ( - ) : visibleProjects.length === 0 ? ( + ) : meters.length === 0 ? ( ) : ( - visibleProjects.map((proj) => ( - - )) + <> + + {allProjects.map((proj) => ( + + ))} + )} - {visibleProjects.length === 0 && !loadingProjects && ( + {allProjects.length === 0 && !loadingProjects && (

No projects available. Please contact your administrator.

@@ -236,10 +276,12 @@ export default function MeterManagement() { + Cancel +