Meter edit and create logic form & concentrators project select option
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect, useMemo } from "react";
|
||||||
import { Plus, Trash2, Pencil, RefreshCcw } from "lucide-react";
|
import { Plus, Trash2, Pencil, RefreshCcw } from "lucide-react";
|
||||||
import MaterialTable from "@material-table/core";
|
import MaterialTable from "@material-table/core";
|
||||||
import { fetchProjectNames } from "../../api/projects";
|
|
||||||
import {
|
import {
|
||||||
fetchConcentrators,
|
fetchConcentrators,
|
||||||
createConcentrator,
|
createConcentrator,
|
||||||
@@ -40,22 +39,6 @@ export default function ConcentratorsPage() {
|
|||||||
const [loadingProjects, setLoadingProjects] = useState(true);
|
const [loadingProjects, setLoadingProjects] = useState(true);
|
||||||
const [loadingConcentrators, setLoadingConcentrators] = useState(true);
|
const [loadingConcentrators, setLoadingConcentrators] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const loadProjects = async () => {
|
|
||||||
try {
|
|
||||||
const projects = await fetchProjectNames();
|
|
||||||
setAllProjects(projects);
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading projects:', error);
|
|
||||||
setAllProjects([]);
|
|
||||||
} finally {
|
|
||||||
setLoadingProjects(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
loadProjects();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Proyectos visibles según el usuario
|
// Proyectos visibles según el usuario
|
||||||
const visibleProjects = useMemo(() =>
|
const visibleProjects = useMemo(() =>
|
||||||
currentUser.role === "SUPER_ADMIN"
|
currentUser.role === "SUPER_ADMIN"
|
||||||
@@ -79,12 +62,16 @@ export default function ConcentratorsPage() {
|
|||||||
setLoadingConcentrators(true);
|
setLoadingConcentrators(true);
|
||||||
try {
|
try {
|
||||||
const data = await fetchConcentrators();
|
const data = await fetchConcentrators();
|
||||||
|
const projectsArray = data.map((record) => record["Area Name"]);
|
||||||
|
setAllProjects(projectsArray);
|
||||||
setConcentrators(data);
|
setConcentrators(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error loading concentrators:", error);
|
console.error("Error loading concentrators:", error);
|
||||||
|
setAllProjects([]);
|
||||||
setConcentrators([]);
|
setConcentrators([]);
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingConcentrators(false);
|
setLoadingConcentrators(false);
|
||||||
|
setLoadingProjects(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ interface User {
|
|||||||
project?: string; // asignado si no es superadmin
|
project?: string; // asignado si no es superadmin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface DeviceData {
|
||||||
|
"Device ID": number;
|
||||||
|
"Device EUI": string;
|
||||||
|
"Join EUI": string;
|
||||||
|
"AppKey": string;
|
||||||
|
meterId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
/* ================= COMPONENT ================= */
|
/* ================= COMPONENT ================= */
|
||||||
export default function MeterManagement() {
|
export default function MeterManagement() {
|
||||||
// Simulación de usuario actual
|
// Simulación de usuario actual
|
||||||
@@ -94,7 +102,16 @@ export default function MeterManagement() {
|
|||||||
installedTime: new Date().toISOString(),
|
installedTime: new Date().toISOString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const emptyDeviceData: DeviceData = {
|
||||||
|
"Device ID": 0,
|
||||||
|
"Device EUI": "",
|
||||||
|
"Join EUI": "",
|
||||||
|
"AppKey": "",
|
||||||
|
};
|
||||||
|
|
||||||
const [form, setForm] = useState<Omit<Meter, "id">>(emptyMeter);
|
const [form, setForm] = useState<Omit<Meter, "id">>(emptyMeter);
|
||||||
|
const [deviceForm, setDeviceForm] = useState<DeviceData>(emptyDeviceData);
|
||||||
|
const [errors, setErrors] = useState<{ [key: string]: boolean }>({});
|
||||||
|
|
||||||
const loadMeters = async () => {
|
const loadMeters = async () => {
|
||||||
setLoadingMeters(true);
|
setLoadingMeters(true);
|
||||||
@@ -113,8 +130,49 @@ export default function MeterManagement() {
|
|||||||
loadMeters();
|
loadMeters();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const createOrUpdateDevice = async (deviceData: DeviceData): Promise<void> => {
|
||||||
|
//await fetch('/api/devices', { method: 'POST', body: JSON.stringify(deviceData) })
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('Device data that would be sent to API:', deviceData);
|
||||||
|
resolve();
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateForm = (): boolean => {
|
||||||
|
const newErrors: { [key: string]: boolean } = {};
|
||||||
|
|
||||||
|
if (!form.meterName.trim()) newErrors["meterName"] = true;
|
||||||
|
if (!form.meterSerialNumber.trim()) newErrors["meterSerialNumber"] = true;
|
||||||
|
if (!form.areaName.trim()) newErrors["areaName"] = true;
|
||||||
|
if (!form.deviceName.trim()) newErrors["deviceName"] = true;
|
||||||
|
if (!form.deviceType.trim()) newErrors["deviceType"] = true;
|
||||||
|
if (!form.protocolType.trim()) newErrors["protocolType"] = true;
|
||||||
|
if (!form.supplyTypes.trim()) newErrors["supplyTypes"] = true;
|
||||||
|
if (!form.usageAnalysisType.trim()) newErrors["usageAnalysisType"] = true;
|
||||||
|
if (!form.installedTime) newErrors["installedTime"] = true;
|
||||||
|
|
||||||
|
if (!deviceForm["Device ID"] || deviceForm["Device ID"] === 0) {
|
||||||
|
newErrors["Device ID"] = true;
|
||||||
|
}
|
||||||
|
if (!deviceForm["Device EUI"].trim()) newErrors["Device EUI"] = true;
|
||||||
|
if (!deviceForm["Join EUI"].trim()) newErrors["Join EUI"] = true;
|
||||||
|
if (!deviceForm["AppKey"].trim()) newErrors["AppKey"] = true;
|
||||||
|
|
||||||
|
setErrors(newErrors);
|
||||||
|
return Object.keys(newErrors).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
|
if (!validateForm()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let savedMeter: Meter;
|
||||||
|
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
const meterToUpdate = meters.find(m => m.id === editingId);
|
const meterToUpdate = meters.find(m => m.id === editingId);
|
||||||
if (!meterToUpdate) {
|
if (!meterToUpdate) {
|
||||||
@@ -127,13 +185,30 @@ export default function MeterManagement() {
|
|||||||
m.id === editingId ? updatedMeter : m
|
m.id === editingId ? updatedMeter : m
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
savedMeter = updatedMeter;
|
||||||
} else {
|
} else {
|
||||||
const newMeter = await createMeter(form);
|
const newMeter = await createMeter(form);
|
||||||
setMeters((prev) => [...prev, newMeter]);
|
setMeters((prev) => [...prev, newMeter]);
|
||||||
|
savedMeter = newMeter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const deviceDataWithRef = {
|
||||||
|
...deviceForm,
|
||||||
|
meterId: savedMeter.id,
|
||||||
|
};
|
||||||
|
await createOrUpdateDevice(deviceDataWithRef);
|
||||||
|
console.log('Device data saved successfully');
|
||||||
|
} catch (deviceError) {
|
||||||
|
console.error('Error saving device data:', deviceError);
|
||||||
|
alert('Meter saved, but there was an error saving device data.');
|
||||||
|
}
|
||||||
|
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
setEditingId(null);
|
setEditingId(null);
|
||||||
setForm(emptyMeter);
|
setForm(emptyMeter);
|
||||||
|
setDeviceForm(emptyDeviceData);
|
||||||
|
setErrors({});
|
||||||
setActiveMeter(null);
|
setActiveMeter(null);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error saving meter:', error);
|
console.error('Error saving meter:', error);
|
||||||
@@ -236,6 +311,8 @@ export default function MeterManagement() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setForm(emptyMeter);
|
setForm(emptyMeter);
|
||||||
|
setDeviceForm(emptyDeviceData);
|
||||||
|
setErrors({});
|
||||||
setEditingId(null);
|
setEditingId(null);
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
}}
|
}}
|
||||||
@@ -270,6 +347,8 @@ export default function MeterManagement() {
|
|||||||
usageAnalysisType: activeMeter.usageAnalysisType,
|
usageAnalysisType: activeMeter.usageAnalysisType,
|
||||||
installedTime: activeMeter.installedTime,
|
installedTime: activeMeter.installedTime,
|
||||||
});
|
});
|
||||||
|
setDeviceForm(emptyDeviceData);
|
||||||
|
setErrors({});
|
||||||
setShowModal(true);
|
setShowModal(true);
|
||||||
}}
|
}}
|
||||||
disabled={!activeMeter}
|
disabled={!activeMeter}
|
||||||
@@ -359,193 +438,388 @@ export default function MeterManagement() {
|
|||||||
|
|
||||||
{/* MODAL */}
|
{/* MODAL */}
|
||||||
{showModal && (
|
{showModal && (
|
||||||
<div className="fixed inset-0 bg-black/40 flex items-center justify-center">
|
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
|
||||||
<div className="bg-white rounded-xl p-6 w-96 max-h-[80vh] overflow-y-auto space-y-3">
|
<div className="bg-white rounded-xl p-6 w-[700px] max-h-[90vh] overflow-y-auto space-y-4">
|
||||||
<h2 className="text-lg font-semibold">
|
<h2 className="text-lg font-semibold">
|
||||||
{editingId ? "Edit Meter" : "Add Meter"}
|
{editingId ? "Edit Meter" : "Add Meter"}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-3">
|
||||||
<input
|
<h3 className="text-sm font-semibold text-gray-700 border-b pb-2">
|
||||||
className="w-full border px-3 py-2 rounded"
|
Meter Information
|
||||||
placeholder="Meter Name"
|
</h3>
|
||||||
value={form.meterName}
|
|
||||||
onChange={(e) => setForm({ ...form, meterName: e.target.value })}
|
<div className="grid grid-cols-2 gap-3">
|
||||||
/>
|
<div>
|
||||||
|
<input
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["meterName"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Meter Name *"
|
||||||
|
value={form.meterName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setForm({ ...form, meterName: e.target.value });
|
||||||
|
if (errors["meterName"]) {
|
||||||
|
setErrors({ ...errors, "meterName": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["meterName"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["meterSerialNumber"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Meter Serial Number *"
|
||||||
|
value={form.meterSerialNumber}
|
||||||
|
onChange={(e) => {
|
||||||
|
setForm({ ...form, meterSerialNumber: e.target.value });
|
||||||
|
if (errors["meterSerialNumber"]) {
|
||||||
|
setErrors({ ...errors, "meterSerialNumber": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["meterSerialNumber"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["areaName"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Area Name *"
|
||||||
|
value={form.areaName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setForm({ ...form, areaName: e.target.value });
|
||||||
|
if (errors["areaName"]) {
|
||||||
|
setErrors({ ...errors, "areaName": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["areaName"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["deviceName"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Device Name *"
|
||||||
|
value={form.deviceName}
|
||||||
|
onChange={(e) => {
|
||||||
|
setForm({ ...form, deviceName: e.target.value });
|
||||||
|
if (errors["deviceName"]) {
|
||||||
|
setErrors({ ...errors, "deviceName": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["deviceName"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["deviceType"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Device Type *"
|
||||||
|
value={form.deviceType}
|
||||||
|
onChange={(e) => {
|
||||||
|
setForm({ ...form, deviceType: e.target.value });
|
||||||
|
if (errors["deviceType"]) {
|
||||||
|
setErrors({ ...errors, "deviceType": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["deviceType"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<select
|
||||||
|
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
value={form.meterStatus}
|
||||||
|
onChange={(e) => setForm({ ...form, meterStatus: e.target.value })}
|
||||||
|
>
|
||||||
|
<option value="Installed">Installed</option>
|
||||||
|
<option value="Uninstalled">Uninstalled</option>
|
||||||
|
<option value="Maintenance">Maintenance</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["protocolType"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Protocol Type *"
|
||||||
|
value={form.protocolType}
|
||||||
|
onChange={(e) => {
|
||||||
|
setForm({ ...form, protocolType: e.target.value });
|
||||||
|
if (errors["protocolType"]) {
|
||||||
|
setErrors({ ...errors, "protocolType": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["protocolType"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["supplyTypes"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Supply Types *"
|
||||||
|
value={form.supplyTypes}
|
||||||
|
onChange={(e) => {
|
||||||
|
setForm({ ...form, supplyTypes: e.target.value });
|
||||||
|
if (errors["supplyTypes"]) {
|
||||||
|
setErrors({ ...errors, "supplyTypes": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["supplyTypes"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["usageAnalysisType"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Usage Analysis Type *"
|
||||||
|
value={form.usageAnalysisType}
|
||||||
|
onChange={(e) => {
|
||||||
|
setForm({ ...form, usageAnalysisType: e.target.value });
|
||||||
|
if (errors["usageAnalysisType"]) {
|
||||||
|
setErrors({ ...errors, "usageAnalysisType": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["usageAnalysisType"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
type="datetime-local"
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["installedTime"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Installed Time *"
|
||||||
|
value={
|
||||||
|
form.installedTime
|
||||||
|
? new Date(form.installedTime).toISOString().slice(0, 16)
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
onChange={(e) => {
|
||||||
|
setForm({ ...form, installedTime: new Date(e.target.value).toISOString() });
|
||||||
|
if (errors["installedTime"]) {
|
||||||
|
setErrors({ ...errors, "installedTime": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["installedTime"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="pt-2 border-t">
|
||||||
|
<p className="text-xs text-gray-500 mb-2">Optional Fields</p>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-3">
|
||||||
|
<input
|
||||||
|
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="Account Number (optional)"
|
||||||
|
value={form.accountNumber ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setForm({ ...form, accountNumber: e.target.value || null })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="User Name (optional)"
|
||||||
|
value={form.userName ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setForm({ ...form, userName: e.target.value || null })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent mt-3"
|
||||||
|
placeholder="User Address (optional)"
|
||||||
|
value={form.userAddress ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setForm({ ...form, userAddress: e.target.value || null })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-3 gap-3 mt-3">
|
||||||
|
<input
|
||||||
|
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="Price No. (optional)"
|
||||||
|
value={form.priceNo ?? ""}
|
||||||
|
onChange={(e) => setForm({ ...form, priceNo: e.target.value || null })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="Price Name (optional)"
|
||||||
|
value={form.priceName ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setForm({ ...form, priceName: e.target.value || null })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<input
|
||||||
|
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
placeholder="DMA Partition (optional)"
|
||||||
|
value={form.dmaPartition ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
setForm({ ...form, dmaPartition: e.target.value || null })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<div className="space-y-3 pt-4">
|
||||||
<input
|
<h3 className="text-sm font-semibold text-gray-700 border-b pb-2">
|
||||||
className="w-full border px-3 py-2 rounded"
|
Device Configuration
|
||||||
placeholder="Meter Serial Number"
|
</h3>
|
||||||
value={form.meterSerialNumber}
|
|
||||||
onChange={(e) =>
|
<div className="grid grid-cols-2 gap-3">
|
||||||
setForm({ ...form, meterSerialNumber: e.target.value })
|
<div>
|
||||||
}
|
<input
|
||||||
/>
|
type="number"
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["Device ID"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Device ID *"
|
||||||
|
value={deviceForm["Device ID"] || ""}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDeviceForm({
|
||||||
|
...deviceForm,
|
||||||
|
"Device ID": parseInt(e.target.value) || 0,
|
||||||
|
});
|
||||||
|
if (errors["Device ID"]) {
|
||||||
|
setErrors({ ...errors, "Device ID": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
min="1"
|
||||||
|
/>
|
||||||
|
{errors["Device ID"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["Device EUI"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Device EUI *"
|
||||||
|
value={deviceForm["Device EUI"]}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDeviceForm({ ...deviceForm, "Device EUI": e.target.value });
|
||||||
|
if (errors["Device EUI"]) {
|
||||||
|
setErrors({ ...errors, "Device EUI": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["Device EUI"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["Join EUI"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="Join EUI *"
|
||||||
|
value={deviceForm["Join EUI"]}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDeviceForm({ ...deviceForm, "Join EUI": e.target.value });
|
||||||
|
if (errors["Join EUI"]) {
|
||||||
|
setErrors({ ...errors, "Join EUI": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["Join EUI"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<input
|
||||||
|
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
|
||||||
|
errors["AppKey"] ? "border-red-500" : ""
|
||||||
|
}`}
|
||||||
|
placeholder="AppKey *"
|
||||||
|
value={deviceForm["AppKey"]}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDeviceForm({ ...deviceForm, "AppKey": e.target.value });
|
||||||
|
if (errors["AppKey"]) {
|
||||||
|
setErrors({ ...errors, "AppKey": false });
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
{errors["AppKey"] && (
|
||||||
|
<p className="text-red-500 text-xs mt-1">This field is required</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-1">
|
<div className="flex justify-end gap-2 pt-3 border-t">
|
||||||
<input
|
<button
|
||||||
className="w-full border px-3 py-2 rounded"
|
onClick={() => {
|
||||||
placeholder="Area Name"
|
setShowModal(false);
|
||||||
value={form.areaName}
|
setDeviceForm(emptyDeviceData);
|
||||||
onChange={(e) => setForm({ ...form, areaName: e.target.value })}
|
setErrors({});
|
||||||
/>
|
}}
|
||||||
</div>
|
className="px-4 py-2 rounded hover:bg-gray-100"
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="Device ID"
|
|
||||||
value={form.deviceId}
|
|
||||||
onChange={(e) => setForm({ ...form, deviceId: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="Device Name"
|
|
||||||
value={form.deviceName}
|
|
||||||
onChange={(e) => setForm({ ...form, deviceName: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="Device Type"
|
|
||||||
value={form.deviceType}
|
|
||||||
onChange={(e) => setForm({ ...form, deviceType: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<select
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
value={form.meterStatus}
|
|
||||||
onChange={(e) => setForm({ ...form, meterStatus: e.target.value })}
|
|
||||||
>
|
>
|
||||||
<option value="Installed">Meter Status: Installed</option>
|
Cancel
|
||||||
<option value="Uninstalled">Meter Status: Uninstalled</option>
|
</button>
|
||||||
<option value="Maintenance">Meter Status: Maintenance</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="Protocol Type"
|
|
||||||
value={form.protocolType}
|
|
||||||
onChange={(e) => setForm({ ...form, protocolType: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="Supply Types"
|
|
||||||
value={form.supplyTypes}
|
|
||||||
onChange={(e) => setForm({ ...form, supplyTypes: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="Usage Analysis Type"
|
|
||||||
value={form.usageAnalysisType}
|
|
||||||
onChange={(e) =>
|
|
||||||
setForm({ ...form, usageAnalysisType: e.target.value })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="Account Number (optional)"
|
|
||||||
value={form.accountNumber ?? ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
setForm({ ...form, accountNumber: e.target.value || null })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="User Name (optional)"
|
|
||||||
value={form.userName ?? ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
setForm({ ...form, userName: e.target.value || null })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="User Address (optional)"
|
|
||||||
value={form.userAddress ?? ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
setForm({ ...form, userAddress: e.target.value || null })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="Price No. (optional)"
|
|
||||||
value={form.priceNo ?? ""}
|
|
||||||
onChange={(e) => setForm({ ...form, priceNo: e.target.value || null })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="Price Name (optional)"
|
|
||||||
value={form.priceName ?? ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
setForm({ ...form, priceName: e.target.value || null })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
placeholder="DMA Partition (optional)"
|
|
||||||
value={form.dmaPartition ?? ""}
|
|
||||||
onChange={(e) =>
|
|
||||||
setForm({ ...form, dmaPartition: e.target.value || null })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-1">
|
|
||||||
<input
|
|
||||||
type="datetime-local"
|
|
||||||
className="w-full border px-3 py-2 rounded"
|
|
||||||
value={
|
|
||||||
form.installedTime
|
|
||||||
? new Date(form.installedTime).toISOString().slice(0, 16)
|
|
||||||
: ""
|
|
||||||
}
|
|
||||||
onChange={(e) =>
|
|
||||||
setForm({ ...form, installedTime: new Date(e.target.value).toISOString() })
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-2 pt-3">
|
|
||||||
<button onClick={() => setShowModal(false)}>Cancel</button>
|
|
||||||
<button
|
<button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
className="bg-[#4c5f9e] text-white px-4 py-2 rounded"
|
className="bg-[#4c5f9e] text-white px-4 py-2 rounded hover:bg-[#3d4d7e]"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user