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/projects.ts b/src/api/projects.ts index 831b6d4..755cbec 100644 --- a/src/api/projects.ts +++ b/src/api/projects.ts @@ -72,7 +72,7 @@ export const fetchProjectNames = async (): Promise => { return projectNames; } catch (error) { console.error("Error fetching project names:", error); - return ["GRH (PADRE)", "CESPT", "Proyecto A", "Proyecto B"]; + return []; } }; diff --git a/src/pages/concentrators/ConcentratorsPage.tsx b/src/pages/concentrators/ConcentratorsPage.tsx index 3a7eec8..875f020 100644 --- a/src/pages/concentrators/ConcentratorsPage.tsx +++ b/src/pages/concentrators/ConcentratorsPage.tsx @@ -2,20 +2,15 @@ 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 { createConcentrator } from "./concentrators.api"; +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; @@ -34,6 +29,7 @@ export default function ConcentratorsPage() { const [allProjects, setAllProjects] = useState([]); const [loadingProjects, setLoadingProjects] = useState(true); + const [loadingConcentrators, setLoadingConcentrators] = useState(true); useEffect(() => { const loadProjects = async () => { @@ -42,7 +38,7 @@ export default function ConcentratorsPage() { setAllProjects(projects); } catch (error) { console.error('Error loading projects:', error); - setAllProjects(["GRH (PADRE)", "CESPT", "Proyecto A", "Proyecto B"]); + setAllProjects([]); } finally { setLoadingProjects(false); } @@ -62,6 +58,7 @@ export default function ConcentratorsPage() { ); const [selectedProject, setSelectedProject] = useState(""); + const [concentrators, setConcentrators] = useState([]); useEffect(() => { if (visibleProjects.length > 0 && !selectedProject) { @@ -69,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(""); @@ -129,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); @@ -144,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 ================= */ @@ -232,7 +231,17 @@ export default function ConcentratorsPage() { onClick={() => { if (!activeConcentrator) return; setEditingSerial(activeConcentrator["Device S/N"]); - setForm(activeConcentrator); + setForm({ + "Area Name": activeConcentrator["Area Name"], + "Device S/N": activeConcentrator["Device S/N"], + "Device Name": activeConcentrator["Device Name"], + "Device Time": activeConcentrator["Device Time"], + "Device Status": activeConcentrator["Device Status"], + "Operator": activeConcentrator["Operator"], + "Installed Time": activeConcentrator["Installed Time"], + "Communication Time": activeConcentrator["Communication Time"], + "Instruction Manual": activeConcentrator["Instruction Manual"], + }); setShowModal(true); }} disabled={!activeConcentrator} @@ -250,7 +259,7 @@ export default function ConcentratorsPage() {