meter types
This commit is contained in:
77
src/api/meterTypes.ts
Normal file
77
src/api/meterTypes.ts
Normal 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,
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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[]>([]);
|
||||
@@ -19,12 +20,15 @@ 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: "",
|
||||
description: "",
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user