meter types

This commit is contained in:
2026-02-02 17:54:01 -06:00
parent 6c5323906d
commit 6cc4ee0901
3 changed files with 131 additions and 0 deletions

77
src/api/meterTypes.ts Normal file
View File

@@ -0,0 +1,77 @@
/**
* Meter Types API
* Handles all meter type-related API operations
*/
import { apiClient } from './client';
/**
* Meter Type entity
*/
export interface MeterType {
id: string;
name: string;
code: string;
description: string | null;
isActive: boolean;
createdAt: string;
updatedAt: string;
}
/**
* Fetch all active meter types
* @returns Promise resolving to an array of meter types
*/
export async function fetchMeterTypes(): Promise<MeterType[]> {
// apiClient automatically unwraps the response and returns only the data array
const data = await apiClient.get<any[]>('/api/meter-types');
// Transform snake_case to camelCase
return data.map((item: any) => ({
id: item.id,
name: item.name,
code: item.code,
description: item.description,
isActive: item.is_active,
createdAt: item.created_at,
updatedAt: item.updated_at,
}));
}
/**
* Fetch a meter type by ID
* @param id - Meter type ID
* @returns Promise resolving to a meter type
*/
export async function fetchMeterTypeById(id: string): Promise<MeterType> {
const item = await apiClient.get<any>(`/api/meter-types/${id}`);
return {
id: item.id,
name: item.name,
code: item.code,
description: item.description,
isActive: item.is_active,
createdAt: item.created_at,
updatedAt: item.updated_at,
};
}
/**
* Fetch a meter type by code
* @param code - Meter type code (LORA, LORAWAN, GRANDES)
* @returns Promise resolving to a meter type
*/
export async function fetchMeterTypeByCode(code: string): Promise<MeterType> {
const item = await apiClient.get<any>(`/api/meter-types/code/${code}`);
return {
id: item.id,
name: item.name,
code: item.code,
description: item.description,
isActive: item.is_active,
createdAt: item.created_at,
updatedAt: item.updated_at,
};
}

View File

@@ -40,6 +40,7 @@ export interface Project {
areaName: string; areaName: string;
location: string | null; location: string | null;
status: string; status: string;
meterTypeId: string | null;
createdBy: string; createdBy: string;
createdAt: string; createdAt: string;
updatedAt: string; updatedAt: string;
@@ -54,6 +55,7 @@ export interface ProjectInput {
areaName: string; areaName: string;
location?: string; location?: string;
status?: string; status?: string;
meterTypeId?: string | null;
} }
/** /**
@@ -94,6 +96,7 @@ export async function createProject(data: ProjectInput): Promise<Project> {
area_name: data.areaName, area_name: data.areaName,
location: data.location, location: data.location,
status: data.status, status: data.status,
meter_type_id: data.meterTypeId,
}; };
const response = await apiClient.post<Record<string, unknown>>('/api/projects', backendData); const response = await apiClient.post<Record<string, unknown>>('/api/projects', backendData);
return transformKeys<Project>(response); return transformKeys<Project>(response);
@@ -112,6 +115,7 @@ export async function updateProject(id: string, data: Partial<ProjectInput>): Pr
if (data.areaName !== undefined) backendData.area_name = data.areaName; if (data.areaName !== undefined) backendData.area_name = data.areaName;
if (data.location !== undefined) backendData.location = data.location; if (data.location !== undefined) backendData.location = data.location;
if (data.status !== undefined) backendData.status = data.status; if (data.status !== undefined) backendData.status = data.status;
if (data.meterTypeId !== undefined) backendData.meter_type_id = data.meterTypeId;
const response = await apiClient.patch<Record<string, unknown>>(`/api/projects/${id}`, backendData); const response = await apiClient.patch<Record<string, unknown>>(`/api/projects/${id}`, backendData);
return transformKeys<Project>(response); return transformKeys<Project>(response);

View File

@@ -9,6 +9,7 @@ import {
updateProject as apiUpdateProject, updateProject as apiUpdateProject,
deleteProject as apiDeleteProject, deleteProject as apiDeleteProject,
} from "../../api/projects"; } from "../../api/projects";
import { fetchMeterTypes, type MeterType } from "../../api/meterTypes";
export default function ProjectsPage() { export default function ProjectsPage() {
const [projects, setProjects] = useState<Project[]>([]); const [projects, setProjects] = useState<Project[]>([]);
@@ -19,12 +20,15 @@ export default function ProjectsPage() {
const [showModal, setShowModal] = useState(false); const [showModal, setShowModal] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null); const [editingId, setEditingId] = useState<string | null>(null);
const [meterTypes, setMeterTypes] = useState<MeterType[]>([]);
const emptyForm: ProjectInput = { const emptyForm: ProjectInput = {
name: "", name: "",
description: "", description: "",
areaName: "", areaName: "",
location: "", location: "",
status: "ACTIVE", status: "ACTIVE",
meterTypeId: null,
}; };
const [form, setForm] = useState<ProjectInput>(emptyForm); const [form, setForm] = useState<ProjectInput>(emptyForm);
@@ -42,8 +46,19 @@ export default function ProjectsPage() {
} }
}; };
const loadMeterTypesData = async () => {
try {
const types = await fetchMeterTypes();
setMeterTypes(types);
} catch (error) {
console.error("Error loading meter types:", error);
setMeterTypes([]);
}
};
useEffect(() => { useEffect(() => {
loadProjects(); loadProjects();
loadMeterTypesData();
}, []); }, []);
const handleSave = async () => { const handleSave = async () => {
@@ -104,6 +119,7 @@ export default function ProjectsPage() {
areaName: activeProject.areaName, areaName: activeProject.areaName,
location: activeProject.location ?? "", location: activeProject.location ?? "",
status: activeProject.status, status: activeProject.status,
meterTypeId: activeProject.meterTypeId ?? null,
}); });
setShowModal(true); setShowModal(true);
}; };
@@ -183,6 +199,19 @@ export default function ProjectsPage() {
columns={[ columns={[
{ title: "Nombre", field: "name" }, { title: "Nombre", field: "name" },
{ title: "Area", field: "areaName" }, { title: "Area", field: "areaName" },
{
title: "Tipo de Toma",
field: "meterTypeId",
render: (rowData: Project) => {
if (!rowData.meterTypeId) return "-";
const meterType = meterTypes.find(mt => mt.id === rowData.meterTypeId);
return meterType ? (
<span className="px-2 py-1 rounded text-xs font-medium bg-indigo-100 text-indigo-700">
{meterType.name}
</span>
) : "-";
}
},
{ title: "Descripción", field: "description", render: (rowData: Project) => rowData.description || "-" }, { title: "Descripción", field: "description", render: (rowData: Project) => rowData.description || "-" },
{ title: "Ubicación", field: "location", render: (rowData: Project) => rowData.location || "-" }, { title: "Ubicación", field: "location", render: (rowData: Project) => rowData.location || "-" },
{ {
@@ -278,6 +307,27 @@ export default function ProjectsPage() {
/> />
</div> </div>
<div>
<label className="block text-sm text-gray-600 mb-1">Tipo de Toma</label>
<select
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
value={form.meterTypeId ?? ""}
onChange={(e) => setForm({ ...form, meterTypeId: e.target.value || null })}
>
<option value="">Selecciona un tipo (opcional)</option>
{meterTypes.map((type) => (
<option key={type.id} value={type.id}>
{type.name}
</option>
))}
</select>
{meterTypes.length === 0 && (
<p className="text-xs text-amber-600 mt-1">
No hay tipos de toma disponibles. Asegúrate de aplicar la migración SQL.
</p>
)}
</div>
<div> <div>
<label className="block text-sm text-gray-600 mb-1">Estado</label> <label className="block text-sm text-gray-600 mb-1">Estado</label>
<select <select