prueba columns

This commit is contained in:
2026-02-02 01:58:30 -06:00
parent 4f484779d8
commit e06941fd02
5 changed files with 278 additions and 48 deletions

View File

@@ -53,6 +53,21 @@ export interface Meter {
concentratorSerial?: string;
projectId?: string;
projectName?: string;
protocol?: string | null;
mac?: string | null;
gateway?: string | null;
voltage?: number | null;
voltageRtu?: number | null;
voltageStatus?: string | null;
signal?: number | null;
leakageStatus?: string | null;
burstStatus?: string | null;
currentFlow?: number | null;
totalFlowReverse?: number | null;
manufacturer?: string | null;
latitude?: number | null;
longitude?: number | null;
}
/**
@@ -67,6 +82,21 @@ export interface MeterInput {
type?: string;
status?: string;
installationDate?: string;
protocol?: string;
mac?: string;
gateway?: string;
voltage?: number;
voltageRtu?: number;
voltageStatus?: string;
signal?: number;
leakageStatus?: string;
burstStatus?: string;
currentFlow?: number;
totalFlowReverse?: number;
manufacturer?: string;
latitude?: number;
longitude?: number;
}
/**

View File

@@ -182,6 +182,16 @@ export default function MetersPage({
type: activeMeter.type,
status: activeMeter.status,
installationDate: activeMeter.installationDate ?? "",
protocol: activeMeter.protocol ?? undefined,
voltage: activeMeter.voltage ?? undefined,
signal: activeMeter.signal ?? undefined,
leakageStatus: activeMeter.leakageStatus ?? undefined,
burstStatus: activeMeter.burstStatus ?? undefined,
currentFlow: activeMeter.currentFlow ?? undefined,
totalFlowReverse: activeMeter.totalFlowReverse ?? undefined,
manufacturer: activeMeter.manufacturer ?? undefined,
latitude: activeMeter.latitude ?? undefined,
longitude: activeMeter.longitude ?? undefined,
});
setErrors({});
setShowModal(true);
@@ -311,6 +321,7 @@ export default function MetersPage({
{showModal && (
<MetersModal
editingId={editingId}
selectedProject={m.selectedProject}
form={form}
setForm={setForm}
errors={errors}

View File

@@ -5,6 +5,7 @@ import { fetchConcentrators, type Concentrator } from "../../api/concentrators";
type Props = {
editingId: string | null;
selectedProject?: string;
form: MeterInput;
setForm: React.Dispatch<React.SetStateAction<MeterInput>>;
@@ -18,6 +19,7 @@ type Props = {
export default function MetersModal({
editingId,
selectedProject,
form,
setForm,
errors,
@@ -28,6 +30,7 @@ export default function MetersModal({
const title = editingId ? "Editar Medidor" : "Agregar Medidor";
const [concentrators, setConcentrators] = useState<Concentrator[]>([]);
const [loadingConcentrators, setLoadingConcentrators] = useState(true);
const isPruebaProject = selectedProject === "PRUEBA";
// Load concentrators for the dropdown
useEffect(() => {
@@ -187,6 +190,145 @@ export default function MetersModal({
</div>
</div>
{isPruebaProject && (
<div className="space-y-3">
<h3 className="text-sm font-semibold text-gray-700 border-b pb-2">
Información Técnica (Proyecto PRUEBA)
</h3>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm text-gray-600 mb-1">Protocol</label>
<input
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="ej: LoRaWAN"
value={form.protocol ?? ""}
onChange={(e) => setForm({ ...form, protocol: e.target.value || undefined })}
/>
</div>
<div>
<label className="block text-sm text-gray-600 mb-1">Voltage (V)</label>
<input
type="number"
step="0.01"
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="ej: 3.6"
value={form.voltage ?? ""}
onChange={(e) => setForm({ ...form, voltage: e.target.value ? parseFloat(e.target.value) : undefined })}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm text-gray-600 mb-1">Signal (dBm)</label>
<input
type="number"
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="ej: -85"
value={form.signal ?? ""}
onChange={(e) => setForm({ ...form, signal: e.target.value ? parseInt(e.target.value) : undefined })}
/>
</div>
<div>
<label className="block text-sm text-gray-600 mb-1">Current Flow</label>
<input
type="number"
step="0.0001"
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="ej: 12.5"
value={form.currentFlow ?? ""}
onChange={(e) => setForm({ ...form, currentFlow: e.target.value ? parseFloat(e.target.value) : undefined })}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm text-gray-600 mb-1">Total Flow Reverse</label>
<input
type="number"
step="0.0001"
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="ej: 0.0"
value={form.totalFlowReverse ?? ""}
onChange={(e) => setForm({ ...form, totalFlowReverse: e.target.value ? parseFloat(e.target.value) : undefined })}
/>
</div>
<div>
<label className="block text-sm text-gray-600 mb-1">Leakage Status</label>
<select
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
value={form.leakageStatus ?? ""}
onChange={(e) => setForm({ ...form, leakageStatus: e.target.value || undefined })}
>
<option value="">Selecciona...</option>
<option value="OK">OK</option>
<option value="WARNING">Warning</option>
<option value="ALERT">Alert</option>
<option value="CRITICAL">Critical</option>
</select>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm text-gray-600 mb-1">Burst Status</label>
<select
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
value={form.burstStatus ?? ""}
onChange={(e) => setForm({ ...form, burstStatus: e.target.value || undefined })}
>
<option value="">Selecciona...</option>
<option value="OK">OK</option>
<option value="WARNING">Warning</option>
<option value="ALERT">Alert</option>
<option value="CRITICAL">Critical</option>
</select>
</div>
<div>
<label className="block text-sm text-gray-600 mb-1">Manufacturer</label>
<input
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="ej: Kamstrup"
value={form.manufacturer ?? ""}
onChange={(e) => setForm({ ...form, manufacturer: e.target.value || undefined })}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-sm text-gray-600 mb-1">Latitude</label>
<input
type="number"
step="0.00000001"
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="ej: 20.659698"
value={form.latitude ?? ""}
onChange={(e) => setForm({ ...form, latitude: e.target.value ? parseFloat(e.target.value) : undefined })}
/>
</div>
<div>
<label className="block text-sm text-gray-600 mb-1">Longitude</label>
<input
type="number"
step="0.00000001"
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="ej: -103.349609"
value={form.longitude ?? ""}
onChange={(e) => setForm({ ...form, longitude: e.target.value ? parseFloat(e.target.value) : undefined })}
/>
</div>
</div>
</div>
)}
{/* ACTIONS */}
<div className="flex justify-end gap-2 pt-3 border-t">
<button onClick={onClose} className="px-4 py-2 rounded hover:bg-gray-100">

View File

@@ -30,58 +30,103 @@ export default function MetersTable({
GRANDES: "Grandes consumidores",
};
const isPruebaProject = selectedProject === "PRUEBA";
const defaultColumns = [
{ title: "Serial", field: "serialNumber", render: (r: Meter) => r.serialNumber || "-" },
{ title: "Meter ID", field: "meterId", render: (r: Meter) => r.meterId || "-" },
{ title: "Nombre", field: "name", render: (r: Meter) => r.name || "-" },
{ title: "Ubicación", field: "location", render: (r: Meter) => r.location || "-" },
{
title: "Tipo",
field: "type",
render: (r: Meter) => {
const typeLabels: Record<string, string> = {
LORA: "LoRa",
LORAWAN: "LoRaWAN",
GRANDES: "Grandes Consumidores",
};
const typeColors: Record<string, string> = {
LORA: "text-green-600 border-green-600",
LORAWAN: "text-purple-600 border-purple-600",
GRANDES: "text-orange-600 border-orange-600",
};
const type = r.type || "LORA";
return (
<span
className={`px-3 py-1 rounded-full text-xs font-semibold border ${typeColors[type] || "text-gray-600 border-gray-600"}`}
>
{typeLabels[type] || type}
</span>
);
},
},
{
title: "Estado",
field: "status",
render: (r: Meter) => (
<span
className={`px-3 py-1 rounded-full text-xs font-semibold border ${
r.status === "ACTIVE"
? "text-blue-600 border-blue-600"
: "text-red-600 border-red-600"
}`}
>
{r.status || "-"}
</span>
),
},
{ title: "Concentrador", field: "concentratorName", render: (r: Meter) => r.concentratorName || "-" },
{ title: "Última Lectura", field: "lastReadingValue", render: (r: Meter) => r.lastReadingValue != null ? Number(r.lastReadingValue).toFixed(2) : "-" },
];
const pruebaColumns = [
{ title: "Meters No.", field: "meterId", render: (r: Meter) => r.meterId || r.serialNumber || "-" },
{ title: "Name", field: "name", render: (r: Meter) => r.name || "-" },
{ title: "Protocol", field: "protocol", render: (r: Meter) => r.protocol || "-" },
{
title: "Status",
field: "status",
render: (r: Meter) => (
<span
className={`px-3 py-1 rounded-full text-xs font-semibold border ${
r.status === "ACTIVE"
? "text-blue-600 border-blue-600"
: "text-red-600 border-red-600"
}`}
>
{r.status || "-"}
</span>
),
},
{ title: "Total Flow", field: "lastReadingValue", render: (r: Meter) => r.lastReadingValue != null ? Number(r.lastReadingValue).toFixed(2) : "-" },
{
title: "Last Contact",
field: "lastReadingAt",
render: (r: Meter) => r.lastReadingAt ? new Date(r.lastReadingAt).toLocaleString('es-MX', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit'
}) : "-"
},
{ title: "Voltage", field: "voltage", render: (r: Meter) => r.voltage != null ? `${Number(r.voltage).toFixed(2)} V` : "-" },
{ title: "Signal", field: "signal", render: (r: Meter) => r.signal != null ? `${r.signal} dBm` : "-" },
{ title: "Leakage Status", field: "leakageStatus", render: (r: Meter) => r.leakageStatus || "-" },
{ title: "Burst Status", field: "burstStatus", render: (r: Meter) => r.burstStatus || "-" },
{ title: "Current Flow", field: "currentFlow", render: (r: Meter) => r.currentFlow != null ? Number(r.currentFlow).toFixed(4) : "-" },
{ title: "Total Flow Reverse", field: "totalFlowReverse", render: (r: Meter) => r.totalFlowReverse != null ? Number(r.totalFlowReverse).toFixed(4) : "-" },
];
const columns = isPruebaProject ? pruebaColumns : defaultColumns;
return (
<div className={disabled ? "opacity-60 pointer-events-none" : ""}>
<MaterialTable
title="Meters"
isLoading={isLoading}
columns={[
{ title: "Serial", field: "serialNumber", render: (r: Meter) => r.serialNumber || "-" },
{ title: "Meter ID", field: "meterId", render: (r: Meter) => r.meterId || "-" },
{ title: "Nombre", field: "name", render: (r: Meter) => r.name || "-" },
{ title: "Ubicación", field: "location", render: (r: Meter) => r.location || "-" },
{
title: "Tipo",
field: "type",
render: (r: Meter) => {
const typeLabels: Record<string, string> = {
LORA: "LoRa",
LORAWAN: "LoRaWAN",
GRANDES: "Grandes Consumidores",
};
const typeColors: Record<string, string> = {
LORA: "text-green-600 border-green-600",
LORAWAN: "text-purple-600 border-purple-600",
GRANDES: "text-orange-600 border-orange-600",
};
const type = r.type || "LORA";
return (
<span
className={`px-3 py-1 rounded-full text-xs font-semibold border ${typeColors[type] || "text-gray-600 border-gray-600"}`}
>
{typeLabels[type] || type}
</span>
);
},
},
{
title: "Estado",
field: "status",
render: (r: Meter) => (
<span
className={`px-3 py-1 rounded-full text-xs font-semibold border ${
r.status === "ACTIVE"
? "text-blue-600 border-blue-600"
: "text-red-600 border-red-600"
}`}
>
{r.status || "-"}
</span>
),
},
{ title: "Concentrador", field: "concentratorName", render: (r: Meter) => r.concentratorName || "-" },
{ title: "Última Lectura", field: "lastReadingValue", render: (r: Meter) => r.lastReadingValue != null ? Number(r.lastReadingValue).toFixed(2) : "-" },
]}
columns={columns}
data={disabled ? [] : data}
onRowClick={(_, rowData) => onRowClick(rowData as Meter)}
options={{