diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8ae4c16 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=domain_url +VITE_API_TOKEN=api_token diff --git a/.gitignore b/.gitignore index a547bf3..d305d04 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,13 @@ dist dist-ssr *.local +# Environment variables +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + # Editor directories and files .vscode/* !.vscode/extensions.json diff --git a/src/api/concentrators.ts b/src/api/concentrators.ts new file mode 100644 index 0000000..5f8034a --- /dev/null +++ b/src/api/concentrators.ts @@ -0,0 +1,206 @@ +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; +export const CONCENTRATORS_API_URL = `${API_BASE_URL}/api/v3/data/ppfu31vhv5gf6i0/mqqvi3woqdw5ziq/records`; +const API_TOKEN = import.meta.env.VITE_API_TOKEN; + +const getAuthHeaders = () => ({ + Authorization: `Bearer ${API_TOKEN}`, + "Content-Type": "application/json", +}); + +export interface ConcentratorRecord { + id: string; + fields: { + "Area Name": string; + "Device S/N": string; + "Device Name": string; + "Device Time": string; + "Device Status": string; + "Operator": string; + "Installed Time": string; + "Communication Time": string; + "Instruction Manual": string; + }; +} + +export interface ConcentratorsResponse { + records: ConcentratorRecord[]; + next?: string; + prev?: string; + nestedNext?: string; + nestedPrev?: string; +} + +export interface Concentrator { + id: string; + "Area Name": string; + "Device S/N": string; + "Device Name": string; + "Device Time": string; + "Device Status": string; + "Operator": string; + "Installed Time": string; + "Communication Time": string; + "Instruction Manual": string; +} + +export const fetchConcentrators = async (): Promise => { + try { + const response = await fetch(CONCENTRATORS_API_URL, { + method: "GET", + headers: getAuthHeaders(), + }); + + if (!response.ok) { + throw new Error("Failed to fetch concentrators"); + } + + const data: ConcentratorsResponse = await response.json(); + + return data.records.map((r: ConcentratorRecord) => ({ + id: r.id, + "Area Name": r.fields["Area Name"] || "", + "Device S/N": r.fields["Device S/N"] || "", + "Device Name": r.fields["Device Name"] || "", + "Device Time": r.fields["Device Time"] || "", + "Device Status": r.fields["Device Status"] || "", + "Operator": r.fields["Operator"] || "", + "Installed Time": r.fields["Installed Time"] || "", + "Communication Time": r.fields["Communication Time"] || "", + "Instruction Manual": r.fields["Instruction Manual"] || "", + })); + } catch (error) { + console.error("Error fetching concentrators:", error); + throw error; + } +}; + +export const createConcentrator = async ( + concentratorData: Omit +): Promise => { + try { + const response = await fetch(CONCENTRATORS_API_URL, { + method: "POST", + headers: getAuthHeaders(), + body: JSON.stringify({ + fields: { + "Area Name": concentratorData["Area Name"], + "Device S/N": concentratorData["Device S/N"], + "Device Name": concentratorData["Device Name"], + "Device Time": concentratorData["Device Time"], + "Device Status": concentratorData["Device Status"], + "Operator": concentratorData["Operator"], + "Installed Time": concentratorData["Installed Time"], + "Communication Time": concentratorData["Communication Time"], + "Instruction Manual": concentratorData["Instruction Manual"], + }, + }), + }); + + if (!response.ok) { + throw new Error(`Failed to create concentrator: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + const createdRecord = data.records?.[0]; + + if (!createdRecord) { + throw new Error("Invalid response format: no record returned"); + } + + return { + id: createdRecord.id, + "Area Name": createdRecord.fields["Area Name"] || concentratorData["Area Name"], + "Device S/N": createdRecord.fields["Device S/N"] || concentratorData["Device S/N"], + "Device Name": createdRecord.fields["Device Name"] || concentratorData["Device Name"], + "Device Time": createdRecord.fields["Device Time"] || concentratorData["Device Time"], + "Device Status": createdRecord.fields["Device Status"] || concentratorData["Device Status"], + "Operator": createdRecord.fields["Operator"] || concentratorData["Operator"], + "Installed Time": createdRecord.fields["Installed Time"] || concentratorData["Installed Time"], + "Communication Time": createdRecord.fields["Communication Time"] || concentratorData["Communication Time"], + "Instruction Manual": createdRecord.fields["Instruction Manual"] || concentratorData["Instruction Manual"], + }; + } catch (error) { + console.error("Error creating concentrator:", error); + throw error; + } +}; + +export const updateConcentrator = async ( + id: string, + concentratorData: Omit +): Promise => { + try { + const response = await fetch(CONCENTRATORS_API_URL, { + method: "PATCH", + headers: getAuthHeaders(), + body: JSON.stringify({ + id: id, + fields: { + "Area Name": concentratorData["Area Name"], + "Device S/N": concentratorData["Device S/N"], + "Device Name": concentratorData["Device Name"], + "Device Time": concentratorData["Device Time"], + "Device Status": concentratorData["Device Status"], + "Operator": concentratorData["Operator"], + "Installed Time": concentratorData["Installed Time"], + "Communication Time": concentratorData["Communication Time"], + "Instruction Manual": concentratorData["Instruction Manual"], + }, + }), + }); + + 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(`Failed to update concentrator: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + const updatedRecord = data.records?.[0]; + + if (!updatedRecord) { + throw new Error("Invalid response format: no record returned"); + } + + return { + id: updatedRecord.id, + "Area Name": updatedRecord.fields["Area Name"] || concentratorData["Area Name"], + "Device S/N": updatedRecord.fields["Device S/N"] || concentratorData["Device S/N"], + "Device Name": updatedRecord.fields["Device Name"] || concentratorData["Device Name"], + "Device Time": updatedRecord.fields["Device Time"] || concentratorData["Device Time"], + "Device Status": updatedRecord.fields["Device Status"] || concentratorData["Device Status"], + "Operator": updatedRecord.fields["Operator"] || concentratorData["Operator"], + "Installed Time": updatedRecord.fields["Installed Time"] || concentratorData["Installed Time"], + "Communication Time": updatedRecord.fields["Communication Time"] || concentratorData["Communication Time"], + "Instruction Manual": updatedRecord.fields["Instruction Manual"] || concentratorData["Instruction Manual"], + }; + } catch (error) { + console.error("Error updating concentrator:", error); + throw error; + } +}; + +export const deleteConcentrator = async (id: string): Promise => { + try { + const response = await fetch(CONCENTRATORS_API_URL, { + method: "DELETE", + headers: getAuthHeaders(), + body: JSON.stringify({ + id: id, + }), + }); + + 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(`Failed to delete concentrator: ${response.status} ${response.statusText}`); + } + } catch (error) { + console.error("Error deleting concentrator:", error); + throw error; + } +}; diff --git a/src/api/meters.ts b/src/api/meters.ts new file mode 100644 index 0000000..29b3468 --- /dev/null +++ b/src/api/meters.ts @@ -0,0 +1,278 @@ +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; +export const METERS_API_URL = `${API_BASE_URL}/api/v3/data/ppfu31vhv5gf6i0/mp1izvcpok5rk6s/records`; +const API_TOKEN = import.meta.env.VITE_API_TOKEN; + +const getAuthHeaders = () => ({ + Authorization: `Bearer ${API_TOKEN}`, + "Content-Type": "application/json", +}); + +export interface MeterRecord { + id: string; + fields: { + CreatedAt: string; + UpdatedAt: string; + "Area Name": string; + "Account Number": string | null; + "User Name": string | null; + "User Address": string | null; + "Meter S/N": string; + "Meter Name": string; + "Meter Status": string; + "Protocol Type": string; + "Price No.": string | null; + "Price Name": string | null; + "DMA Partition": string | null; + "Supply Types": string; + "Device ID": string; + "Device Name": string; + "Device Type": string; + "Usage Analysis Type": string; + "Installed Time": string; + }; +} + +export interface MetersResponse { + records: MeterRecord[]; + next?: string; + prev?: string; + nestedNext?: string; + nestedPrev?: string; +} + +export interface Meter { + id: string; + createdAt: string; + updatedAt: string; + areaName: string; + accountNumber: string | null; + userName: string | null; + userAddress: string | null; + meterSerialNumber: string; + meterName: string; + meterStatus: string; + protocolType: string; + priceNo: string | null; + priceName: string | null; + dmaPartition: string | null; + supplyTypes: string; + deviceId: string; + deviceName: string; + deviceType: string; + usageAnalysisType: string; + installedTime: string; +} + +export const fetchMeters = async (): Promise => { + try { + const response = await fetch(METERS_API_URL, { + method: "GET", + headers: getAuthHeaders(), + }); + + if (!response.ok) { + throw new Error("Failed to fetch meters"); + } + + const data: MetersResponse = await response.json(); + const ans = data.records.map((r: MeterRecord) => ({ + id: r.id, + createdAt: r.fields.CreatedAt || "", + updatedAt: r.fields.UpdatedAt || "", + areaName: r.fields["Area Name"] || "", + accountNumber: r.fields["Account Number"] || null, + userName: r.fields["User Name"] || null, + userAddress: r.fields["User Address"] || null, + meterSerialNumber: r.fields["Meter S/N"] || "", + meterName: r.fields["Meter Name"] || "", + meterStatus: r.fields["Meter Status"] || "", + protocolType: r.fields["Protocol Type"] || "", + priceNo: r.fields["Price No."] || null, + priceName: r.fields["Price Name"] || null, + dmaPartition: r.fields["DMA Partition"] || null, + supplyTypes: r.fields["Supply Types"] || "", + deviceId: r.fields["Device ID"] || "", + deviceName: r.fields["Device Name"] || "", + deviceType: r.fields["Device Type"] || "", + usageAnalysisType: r.fields["Usage Analysis Type"] || "", + installedTime: r.fields["Installed Time"] || "", + })); + console.log ("ans", ans); + + return ans; + } catch (error) { + console.error("Error fetching meters:", error); + throw error; + } +}; + +export const createMeter = async ( + meterData: Omit +): Promise => { + try { + const response = await fetch(METERS_API_URL, { + method: "POST", + headers: getAuthHeaders(), + body: JSON.stringify({ + fields: { + CreatedAt: meterData.createdAt, + UpdatedAt: meterData.updatedAt, + "Area Name": meterData.areaName, + "Account Number": meterData.accountNumber, + "User Name": meterData.userName, + "User Address": meterData.userAddress, + "Meter S/N": meterData.meterSerialNumber, + "Meter Name": meterData.meterName, + "Meter Status": meterData.meterStatus, + "Protocol Type": meterData.protocolType, + "Price No.": meterData.priceNo, + "Price Name": meterData.priceName, + "DMA Partition": meterData.dmaPartition, + "Supply Types": meterData.supplyTypes, + "Device ID": meterData.deviceId, + "Device Name": meterData.deviceName, + "Device Type": meterData.deviceType, + "Usage Analysis Type": meterData.usageAnalysisType, + "Installed Time": meterData.installedTime, + }, + }), + }); + + if (!response.ok) { + throw new Error(`Failed to create meter: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + const createdRecord = data.records?.[0]; + + if (!createdRecord) { + throw new Error("Invalid response format: no record returned"); + } + + return { + id: createdRecord.id, + 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, + userName: createdRecord.fields["User Name"] || meterData.userName, + 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, + 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, + 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, + }; + } catch (error) { + console.error("Error creating meter:", error); + throw error; + } +}; + +export const updateMeter = async ( + id: string, + meterData: Omit +): Promise => { + try { + const response = await fetch(METERS_API_URL, { + method: "PATCH", + headers: getAuthHeaders(), + body: JSON.stringify({ + id: id, + fields: { + CreatedAt: meterData.createdAt, + UpdatedAt: meterData.updatedAt, + "Area Name": meterData.areaName, + "Account Number": meterData.accountNumber, + "User Name": meterData.userName, + "User Address": meterData.userAddress, + "Meter S/N": meterData.meterSerialNumber, + "Meter Name": meterData.meterName, + "Meter Status": meterData.meterStatus, + "Protocol Type": meterData.protocolType, + "Price No.": meterData.priceNo, + "Price Name": meterData.priceName, + "DMA Partition": meterData.dmaPartition, + "Supply Types": meterData.supplyTypes, + "Device ID": meterData.deviceId, + "Device Name": meterData.deviceName, + "Device Type": meterData.deviceType, + "Usage Analysis Type": meterData.usageAnalysisType, + "Installed Time": meterData.installedTime, + }, + }), + }); + + 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(`Failed to update meter: ${response.status} ${response.statusText}`); + } + + const data = await response.json(); + const updatedRecord = data.records?.[0]; + + if (!updatedRecord) { + throw new Error("Invalid response format: no record returned"); + } + + return { + id: updatedRecord.id, + 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, + userName: updatedRecord.fields["User Name"] || meterData.userName, + 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, + 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, + 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, + }; + } catch (error) { + console.error("Error updating meter:", error); + throw error; + } +}; + +export const deleteMeter = async (id: string): Promise => { + try { + const response = await fetch(METERS_API_URL, { + method: "DELETE", + headers: getAuthHeaders(), + body: JSON.stringify({ + id: id, + }), + }); + + 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(`Failed to delete meter: ${response.status} ${response.statusText}`); + } + } catch (error) { + console.error("Error deleting meter:", error); + throw error; + } +}; diff --git a/src/api/projects.ts b/src/api/projects.ts new file mode 100644 index 0000000..755cbec --- /dev/null +++ b/src/api/projects.ts @@ -0,0 +1,253 @@ +const API_BASE_URL = import.meta.env.VITE_API_BASE_URL; +export const PROJECTS_API_URL = `${API_BASE_URL}/api/v3/data/ppfu31vhv5gf6i0/m05u6wpquvdbv3c/records`; +const API_TOKEN = import.meta.env.VITE_API_TOKEN; + +export const getAuthHeaders = () => ({ + Authorization: `Bearer ${API_TOKEN}`, + "Content-Type": "application/json", +}); + +export interface ProjectRecord { + id: number; + fields: { + "Area name"?: string; + "Device S/N"?: string; + "Device Name"?: string; + "Device Type"?: string; + "Device Status"?: string; + Operator?: string; + "Installed Time"?: string; + "Communication Time"?: string; + "Instruction Manual"?: string | null; + }; +} + +export interface ProjectsResponse { + records: ProjectRecord[]; + next?: string; + prev?: string; + nestedNext?: string; + nestedPrev?: string; +} + +export interface Project { + id: string; + areaName: string; + deviceSN: string; + deviceName: string; + deviceType: string; + deviceStatus: "ACTIVE" | "INACTIVE"; + operator: string; + installedTime: string; + communicationTime: string; + instructionManual: string; +} + +export const fetchProjectNames = async (): Promise => { + try { + const response = await fetch(PROJECTS_API_URL, { + method: "GET", + headers: getAuthHeaders(), + }); + + if (!response.ok) { + throw new Error("Failed to fetch projects"); + } + + const data: ProjectsResponse = await response.json(); + + if (!data.records || data.records.length === 0) { + console.warn("No project records found from API"); + return []; + } + + const projectNames = [ + ...new Set( + data.records + .map((record) => record.fields["Area name"] || "") + .filter((name) => name) + ), + ]; + + return projectNames; + } catch (error) { + console.error("Error fetching project names:", error); + return []; + } +}; + +export const fetchProjects = async (): Promise => { + try { + const response = await fetch(PROJECTS_API_URL, { + method: "GET", + headers: getAuthHeaders(), + }); + + if (!response.ok) { + throw new Error("Failed to fetch projects"); + } + + const data: ProjectsResponse = await response.json(); + + return data.records.map((r: ProjectRecord) => ({ + id: r.id.toString(), + 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"] === "Installed" ? "ACTIVE" : "INACTIVE", + operator: r.fields["Operator"] ?? "", + installedTime: r.fields["Installed Time"] ?? "", + communicationTime: r.fields["Communication Time"] ?? "", + instructionManual: r.fields["Instruction Manual"] ?? "", + })); + } catch (error) { + console.error("Error fetching projects:", error); + throw error; + } +}; + +export const createProject = async ( + projectData: Omit +): Promise => { + const response = await fetch(PROJECTS_API_URL, { + method: "POST", + headers: getAuthHeaders(), + body: JSON.stringify({ + fields: { + "Area name": projectData.areaName, + "Device S/N": projectData.deviceSN, + "Device Name": projectData.deviceName, + "Device Type": projectData.deviceType, + "Device Status": + projectData.deviceStatus === "ACTIVE" ? "Installed" : "Inactive", + Operator: projectData.operator, + "Installed Time": projectData.installedTime, + "Communication Time": projectData.communicationTime, + "Instruction Manual": projectData.instructionManual, + }, + }), + }); + + if (!response.ok) { + throw new Error( + `Failed to create project: ${response.status} ${response.statusText}` + ); + } + + const data = await response.json(); + + const createdRecord = data.records?.[0]; + if (!createdRecord) { + throw new Error("Invalid response format: no record returned"); + } + + return { + id: createdRecord.id.toString(), + areaName: createdRecord.fields["Area name"] ?? projectData.areaName, + deviceSN: createdRecord.fields["Device S/N"] ?? projectData.deviceSN, + deviceName: createdRecord.fields["Device Name"] ?? projectData.deviceName, + deviceType: createdRecord.fields["Device Type"] ?? projectData.deviceType, + deviceStatus: + createdRecord.fields["Device Status"] === "Installed" + ? "ACTIVE" + : "INACTIVE", + operator: createdRecord.fields["Operator"] ?? projectData.operator, + installedTime: + createdRecord.fields["Installed Time"] ?? projectData.installedTime, + communicationTime: + createdRecord.fields["Communication Time"] ?? + projectData.communicationTime, + instructionManual: + createdRecord.fields["Instruction Manual"] ?? + projectData.instructionManual, + }; +}; + +export const updateProject = async ( + id: string, + projectData: Omit +): Promise => { + const response = await fetch(PROJECTS_API_URL, { + method: "PATCH", + headers: getAuthHeaders(), + body: JSON.stringify({ + id: parseInt(id), + fields: { + "Area name": projectData.areaName, + "Device S/N": projectData.deviceSN, + "Device Name": projectData.deviceName, + "Device Type": projectData.deviceType, + "Device Status": + projectData.deviceStatus === "ACTIVE" ? "Installed" : "Inactive", + Operator: projectData.operator, + "Installed Time": projectData.installedTime, + "Communication Time": projectData.communicationTime, + "Instruction Manual": projectData.instructionManual, + }, + }), + }); + + 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( + `Failed to update project: ${response.status} ${response.statusText}` + ); + } + + const data = await response.json(); + + const updatedRecord = data.records?.[0]; + if (!updatedRecord) { + throw new Error("Invalid response format: no record returned"); + } + + return { + id: updatedRecord.id.toString(), + areaName: updatedRecord.fields["Area name"] ?? projectData.areaName, + deviceSN: updatedRecord.fields["Device S/N"] ?? projectData.deviceSN, + deviceName: updatedRecord.fields["Device Name"] ?? projectData.deviceName, + deviceType: updatedRecord.fields["Device Type"] ?? projectData.deviceType, + deviceStatus: + updatedRecord.fields["Device Status"] === "Installed" + ? "ACTIVE" + : "INACTIVE", + operator: updatedRecord.fields["Operator"] ?? projectData.operator, + installedTime: + updatedRecord.fields["Installed Time"] ?? projectData.installedTime, + communicationTime: + updatedRecord.fields["Communication Time"] ?? + projectData.communicationTime, + instructionManual: + updatedRecord.fields["Instruction Manual"] ?? + projectData.instructionManual, + }; +}; + +export const deleteProject = async (id: string): Promise => { + const response = await fetch(PROJECTS_API_URL, { + method: "DELETE", + headers: getAuthHeaders(), + body: JSON.stringify({ + id: id, + }), + }); + + 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( + `Failed to delete project: ${response.status} ${response.statusText}` + ); + } +}; diff --git a/src/pages/concentrators/ConcentratorsPage.tsx b/src/pages/concentrators/ConcentratorsPage.tsx index bd9bfd5..1755a8d 100644 --- a/src/pages/concentrators/ConcentratorsPage.tsx +++ b/src/pages/concentrators/ConcentratorsPage.tsx @@ -1,20 +1,16 @@ import { useState, useEffect, useMemo } from "react"; import { Plus, Trash2, Pencil, RefreshCcw } from "lucide-react"; import MaterialTable from "@material-table/core"; -import { fetchProjects, createConcentrator } from "./concentrators.api"; +import { fetchProjectNames } from "../../api/projects"; +import { + fetchConcentrators, + createConcentrator, + updateConcentrator, + deleteConcentrator, + type Concentrator, +} from "../../api/concentrators"; /* ================= TYPES ================= */ -interface Concentrator { - "Area Name": string; - "Device S/N": string; - "Device Name": string; - "Device Time": string; - "Device Status": string; - "Operator": string; - "Installed Time": string; - "Communication Time": string; - "Instruction Manual": string; -} interface User { name: string; @@ -33,15 +29,16 @@ export default function ConcentratorsPage() { const [allProjects, setAllProjects] = useState([]); const [loadingProjects, setLoadingProjects] = useState(true); + const [loadingConcentrators, setLoadingConcentrators] = useState(true); useEffect(() => { const loadProjects = async () => { try { - const projects = await fetchProjects(); + const projects = await fetchProjectNames(); setAllProjects(projects); } catch (error) { console.error('Error loading projects:', error); - setAllProjects(["GRH (PADRE)", "CESPT", "Proyecto A", "Proyecto B"]); + setAllProjects([]); } finally { setLoadingProjects(false); } @@ -61,6 +58,7 @@ export default function ConcentratorsPage() { ); const [selectedProject, setSelectedProject] = useState(""); + const [concentrators, setConcentrators] = useState([]); useEffect(() => { if (visibleProjects.length > 0 && !selectedProject) { @@ -68,41 +66,22 @@ export default function ConcentratorsPage() { } }, [visibleProjects, selectedProject]); - const [concentrators, setConcentrators] = useState([ - { - "Area Name": "GRH (PADRE)", - "Device S/N": "SN001", - "Device Name": "Concentrador A", - "Device Time": "2025-12-17T10:00:00Z", - "Device Status": "ACTIVE", - "Operator": "Operador 1", - "Installed Time": "2025-12-17", - "Communication Time": "2025-12-17T10:30:00Z", - "Instruction Manual": "Manual A", - }, - { - "Area Name": "CESPT", - "Device S/N": "SN002", - "Device Name": "Concentrador B", - "Device Time": "2025-12-16T11:00:00Z", - "Device Status": "INACTIVE", - "Operator": "Operador 2", - "Installed Time": "2025-12-16", - "Communication Time": "2025-12-16T11:30:00Z", - "Instruction Manual": "Manual B", - }, - { - "Area Name": "Proyecto A", - "Device S/N": "SN003", - "Device Name": "Concentrador C", - "Device Time": "2025-12-15T12:00:00Z", - "Device Status": "ACTIVE", - "Operator": "Operador 3", - "Installed Time": "2025-12-15", - "Communication Time": "2025-12-15T12:30:00Z", - "Instruction Manual": "Manual C", - }, - ]); + const loadConcentrators = async () => { + setLoadingConcentrators(true); + try { + const data = await fetchConcentrators(); + setConcentrators(data); + } catch (error) { + console.error("Error loading concentrators:", error); + setConcentrators([]); + } finally { + setLoadingConcentrators(false); + } + }; + + useEffect(() => { + loadConcentrators(); + }, []); const [activeConcentrator, setActiveConcentrator] = useState(null); const [search, setSearch] = useState(""); @@ -128,14 +107,20 @@ export default function ConcentratorsPage() { const handleSave = async () => { try { if (editingSerial) { + const concentratorToUpdate = concentrators.find(c => c["Device S/N"] === editingSerial); + if (!concentratorToUpdate) { + throw new Error("Concentrator to update not found"); + } + + const updatedConcentrator = await updateConcentrator(concentratorToUpdate.id, form); setConcentrators((prev) => prev.map((c) => - c["Device S/N"] === editingSerial ? { ...form } : c + c.id === concentratorToUpdate.id ? updatedConcentrator : c ) ); } else { - await createConcentrator(form); - setConcentrators((prev) => [...prev, { ...form }]); + const newConcentrator = await createConcentrator(form); + setConcentrators((prev) => [...prev, newConcentrator]); } setShowModal(false); setEditingSerial(null); @@ -143,20 +128,35 @@ export default function ConcentratorsPage() { setActiveConcentrator(null); } catch (error) { console.error('Error saving concentrator:', error); - setConcentrators((prev) => [...prev, { ...form }]); - setShowModal(false); - setEditingSerial(null); - setForm({ ...getEmptyConcentrator(), "Area Name": selectedProject }); - setActiveConcentrator(null); + alert( + `Error saving concentrator: ${ + error instanceof Error ? error.message : "Please try again." + }` + ); } }; - const handleDelete = () => { + const handleDelete = async () => { if (!activeConcentrator) return; - setConcentrators((prev) => - prev.filter((c) => c["Device S/N"] !== activeConcentrator["Device S/N"]) + + const confirmDelete = window.confirm( + `Are you sure you want to delete the concentrator "${activeConcentrator["Device Name"]}"?` ); - setActiveConcentrator(null); + + if (!confirmDelete) return; + + try { + await deleteConcentrator(activeConcentrator.id); + setConcentrators((prev) => prev.filter((c) => c.id !== activeConcentrator.id)); + setActiveConcentrator(null); + } catch (error) { + console.error("Error deleting concentrator:", error); + alert( + `Error deleting concentrator: ${ + error instanceof Error ? error.message : "Please try again." + }` + ); + } }; /* ================= FILTER ================= */ @@ -180,10 +180,12 @@ export default function ConcentratorsPage() { value={selectedProject} onChange={(e) => setSelectedProject(e.target.value)} className="w-full border px-3 py-2 rounded" - disabled={loadingProjects} + disabled={loadingProjects || visibleProjects.length === 0} > {loadingProjects ? ( + ) : visibleProjects.length === 0 ? ( + ) : ( visibleProjects.map((proj) => (