prueba columns
This commit is contained in:
@@ -53,6 +53,21 @@ export interface Meter {
|
|||||||
concentratorSerial?: string;
|
concentratorSerial?: string;
|
||||||
projectId?: string;
|
projectId?: string;
|
||||||
projectName?: 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;
|
type?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
installationDate?: 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -182,6 +182,16 @@ export default function MetersPage({
|
|||||||
type: activeMeter.type,
|
type: activeMeter.type,
|
||||||
status: activeMeter.status,
|
status: activeMeter.status,
|
||||||
installationDate: activeMeter.installationDate ?? "",
|
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({});
|
setErrors({});
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
@@ -311,6 +321,7 @@ export default function MetersPage({
|
|||||||
{showModal && (
|
{showModal && (
|
||||||
<MetersModal
|
<MetersModal
|
||||||
editingId={editingId}
|
editingId={editingId}
|
||||||
|
selectedProject={m.selectedProject}
|
||||||
form={form}
|
form={form}
|
||||||
setForm={setForm}
|
setForm={setForm}
|
||||||
errors={errors}
|
errors={errors}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { fetchConcentrators, type Concentrator } from "../../api/concentrators";
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
editingId: string | null;
|
editingId: string | null;
|
||||||
|
selectedProject?: string;
|
||||||
|
|
||||||
form: MeterInput;
|
form: MeterInput;
|
||||||
setForm: React.Dispatch<React.SetStateAction<MeterInput>>;
|
setForm: React.Dispatch<React.SetStateAction<MeterInput>>;
|
||||||
@@ -18,6 +19,7 @@ type Props = {
|
|||||||
|
|
||||||
export default function MetersModal({
|
export default function MetersModal({
|
||||||
editingId,
|
editingId,
|
||||||
|
selectedProject,
|
||||||
form,
|
form,
|
||||||
setForm,
|
setForm,
|
||||||
errors,
|
errors,
|
||||||
@@ -28,6 +30,7 @@ export default function MetersModal({
|
|||||||
const title = editingId ? "Editar Medidor" : "Agregar Medidor";
|
const title = editingId ? "Editar Medidor" : "Agregar Medidor";
|
||||||
const [concentrators, setConcentrators] = useState<Concentrator[]>([]);
|
const [concentrators, setConcentrators] = useState<Concentrator[]>([]);
|
||||||
const [loadingConcentrators, setLoadingConcentrators] = useState(true);
|
const [loadingConcentrators, setLoadingConcentrators] = useState(true);
|
||||||
|
const isPruebaProject = selectedProject === "PRUEBA";
|
||||||
|
|
||||||
// Load concentrators for the dropdown
|
// Load concentrators for the dropdown
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -187,6 +190,145 @@ export default function MetersModal({
|
|||||||
</div>
|
</div>
|
||||||
</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 */}
|
{/* ACTIONS */}
|
||||||
<div className="flex justify-end gap-2 pt-3 border-t">
|
<div className="flex justify-end gap-2 pt-3 border-t">
|
||||||
<button onClick={onClose} className="px-4 py-2 rounded hover:bg-gray-100">
|
<button onClick={onClose} className="px-4 py-2 rounded hover:bg-gray-100">
|
||||||
|
|||||||
@@ -30,12 +30,9 @@ export default function MetersTable({
|
|||||||
GRANDES: "Grandes consumidores",
|
GRANDES: "Grandes consumidores",
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const isPruebaProject = selectedProject === "PRUEBA";
|
||||||
<div className={disabled ? "opacity-60 pointer-events-none" : ""}>
|
|
||||||
<MaterialTable
|
const defaultColumns = [
|
||||||
title="Meters"
|
|
||||||
isLoading={isLoading}
|
|
||||||
columns={[
|
|
||||||
{ title: "Serial", field: "serialNumber", render: (r: Meter) => r.serialNumber || "-" },
|
{ title: "Serial", field: "serialNumber", render: (r: Meter) => r.serialNumber || "-" },
|
||||||
{ title: "Meter ID", field: "meterId", render: (r: Meter) => r.meterId || "-" },
|
{ title: "Meter ID", field: "meterId", render: (r: Meter) => r.meterId || "-" },
|
||||||
{ title: "Nombre", field: "name", render: (r: Meter) => r.name || "-" },
|
{ title: "Nombre", field: "name", render: (r: Meter) => r.name || "-" },
|
||||||
@@ -81,7 +78,55 @@ export default function MetersTable({
|
|||||||
},
|
},
|
||||||
{ title: "Concentrador", field: "concentratorName", render: (r: Meter) => r.concentratorName || "-" },
|
{ 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) : "-" },
|
{ 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={columns}
|
||||||
data={disabled ? [] : data}
|
data={disabled ? [] : data}
|
||||||
onRowClick={(_, rowData) => onRowClick(rowData as Meter)}
|
onRowClick={(_, rowData) => onRowClick(rowData as Meter)}
|
||||||
options={{
|
options={{
|
||||||
|
|||||||
@@ -292,7 +292,9 @@ export async function getAll(
|
|||||||
m.status, m.last_reading_value, m.last_reading_at, m.installation_date,
|
m.status, m.last_reading_value, m.last_reading_at, m.installation_date,
|
||||||
m.created_at, m.updated_at,
|
m.created_at, m.updated_at,
|
||||||
c.name as concentrator_name, c.serial_number as concentrator_serial,
|
c.name as concentrator_name, c.serial_number as concentrator_serial,
|
||||||
c.project_id, p.name as project_name
|
c.project_id, p.name as project_name,
|
||||||
|
m.protocol, m.voltage, m.signal, m.leakage_status, m.burst_status,
|
||||||
|
m.current_flow, m.total_flow_reverse, m.manufacturer, m.latitude, m.longitude
|
||||||
FROM meters m
|
FROM meters m
|
||||||
JOIN concentrators c ON m.concentrator_id = c.id
|
JOIN concentrators c ON m.concentrator_id = c.id
|
||||||
JOIN projects p ON c.project_id = p.id
|
JOIN projects p ON c.project_id = p.id
|
||||||
|
|||||||
Reference in New Issue
Block a user