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

View File

@@ -9,6 +9,7 @@ import {
updateProject as apiUpdateProject,
deleteProject as apiDeleteProject,
} from "../../api/projects";
import { fetchMeterTypes, type MeterType } from "../../api/meterTypes";
export default function ProjectsPage() {
const [projects, setProjects] = useState<Project[]>([]);
@@ -18,6 +19,8 @@ export default function ProjectsPage() {
const [showModal, setShowModal] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null);
const [meterTypes, setMeterTypes] = useState<MeterType[]>([]);
const emptyForm: ProjectInput = {
name: "",
@@ -25,6 +28,7 @@ export default function ProjectsPage() {
areaName: "",
location: "",
status: "ACTIVE",
meterTypeId: null,
};
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(() => {
loadProjects();
loadMeterTypesData();
}, []);
const handleSave = async () => {
@@ -104,6 +119,7 @@ export default function ProjectsPage() {
areaName: activeProject.areaName,
location: activeProject.location ?? "",
status: activeProject.status,
meterTypeId: activeProject.meterTypeId ?? null,
});
setShowModal(true);
};
@@ -183,6 +199,19 @@ export default function ProjectsPage() {
columns={[
{ title: "Nombre", field: "name" },
{ 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: "Ubicación", field: "location", render: (rowData: Project) => rowData.location || "-" },
{
@@ -278,6 +307,27 @@ export default function ProjectsPage() {
/>
</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>
<label className="block text-sm text-gray-600 mb-1">Estado</label>
<select