From bc9cc3b7f7add8a634596a5301dd26f72637feb4 Mon Sep 17 00:00:00 2001 From: Esteban Date: Thu, 18 Dec 2025 23:29:15 -0600 Subject: [PATCH] Projects api improve --- src/api/projects.ts | 253 ++++++++++++++++++ src/pages/concentrators/ConcentratorsPage.tsx | 18 +- src/pages/concentrators/concentrators.api.ts | 103 ------- src/pages/projects/ProjectsPage.tsx | 218 +-------------- 4 files changed, 277 insertions(+), 315 deletions(-) create mode 100644 src/api/projects.ts diff --git a/src/api/projects.ts b/src/api/projects.ts new file mode 100644 index 0000000..831b6d4 --- /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 ["GRH (PADRE)", "CESPT", "Proyecto A", "Proyecto B"]; + } +}; + +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 c76e839..3a7eec8 100644 --- a/src/pages/concentrators/ConcentratorsPage.tsx +++ b/src/pages/concentrators/ConcentratorsPage.tsx @@ -1,7 +1,8 @@ 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 { createConcentrator } from "./concentrators.api"; /* ================= TYPES ================= */ interface Concentrator { @@ -37,7 +38,7 @@ export default function ConcentratorsPage() { useEffect(() => { const loadProjects = async () => { try { - const projects = await fetchProjects(); + const projects = await fetchProjectNames(); setAllProjects(projects); } catch (error) { console.error('Error loading projects:', error); @@ -180,10 +181,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) => (