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;
|
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);
|
||||||
|
|||||||
@@ -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[]>([]);
|
||||||
@@ -18,6 +19,8 @@ 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: "",
|
||||||
@@ -25,6 +28,7 @@ export default function ProjectsPage() {
|
|||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user