Concentrators create and edit form logic with TTS mock data

This commit is contained in:
2025-12-20 19:45:27 -06:00
parent 014ac19a4b
commit a5fa0cfa64
2 changed files with 350 additions and 164 deletions

View File

@@ -19,11 +19,6 @@ export interface ConcentratorRecord {
"Installed Time": string; "Installed Time": string;
"Communication Time": string; "Communication Time": string;
"Instruction Manual": string; "Instruction Manual": string;
"Gateway ID": number;
"Gateway EUI": string;
"Gateway Name": string;
"Gateway Description": string;
"Antenna Placement": string;
}; };
} }
@@ -46,11 +41,6 @@ export interface Concentrator {
"Installed Time": string; "Installed Time": string;
"Communication Time": string; "Communication Time": string;
"Instruction Manual": string; "Instruction Manual": string;
"Gateway ID": number;
"Gateway EUI": string;
"Gateway Name": string;
"Gateway Description": string;
"Antenna Placement": string;
} }
export const fetchConcentrators = async (): Promise<Concentrator[]> => { export const fetchConcentrators = async (): Promise<Concentrator[]> => {
@@ -77,11 +67,6 @@ export const fetchConcentrators = async (): Promise<Concentrator[]> => {
"Installed Time": r.fields["Installed Time"] || "", "Installed Time": r.fields["Installed Time"] || "",
"Communication Time": r.fields["Communication Time"] || "", "Communication Time": r.fields["Communication Time"] || "",
"Instruction Manual": r.fields["Instruction Manual"] || "", "Instruction Manual": r.fields["Instruction Manual"] || "",
"Gateway ID": r.fields["Gateway ID"] || 0,
"Gateway EUI": r.fields["Gateway EUI"] || "",
"Gateway Name": r.fields["Gateway Name"] || "",
"Gateway Description": r.fields["Gateway Description"] || "",
"Antenna Placement": r.fields["Antenna Placement"] || "Indoor",
})); }));
} catch (error) { } catch (error) {
console.error("Error fetching concentrators:", error); console.error("Error fetching concentrators:", error);
@@ -107,11 +92,6 @@ export const createConcentrator = async (
"Installed Time": concentratorData["Installed Time"], "Installed Time": concentratorData["Installed Time"],
"Communication Time": concentratorData["Communication Time"], "Communication Time": concentratorData["Communication Time"],
"Instruction Manual": concentratorData["Instruction Manual"], "Instruction Manual": concentratorData["Instruction Manual"],
"Gateway ID": concentratorData["Gateway ID"],
"Gateway EUI": concentratorData["Gateway EUI"],
"Gateway Name": concentratorData["Gateway Name"],
"Gateway Description": concentratorData["Gateway Description"],
"Antenna Placement": concentratorData["Antenna Placement"],
}, },
}), }),
}); });
@@ -138,11 +118,6 @@ export const createConcentrator = async (
"Installed Time": createdRecord.fields["Installed Time"] || concentratorData["Installed Time"], "Installed Time": createdRecord.fields["Installed Time"] || concentratorData["Installed Time"],
"Communication Time": createdRecord.fields["Communication Time"] || concentratorData["Communication Time"], "Communication Time": createdRecord.fields["Communication Time"] || concentratorData["Communication Time"],
"Instruction Manual": createdRecord.fields["Instruction Manual"] || concentratorData["Instruction Manual"], "Instruction Manual": createdRecord.fields["Instruction Manual"] || concentratorData["Instruction Manual"],
"Gateway ID": createdRecord.fields["Gateway ID"] || concentratorData["Gateway ID"],
"Gateway EUI": createdRecord.fields["Gateway EUI"] || concentratorData["Gateway EUI"],
"Gateway Name": createdRecord.fields["Gateway Name"] || concentratorData["Gateway Name"],
"Gateway Description": createdRecord.fields["Gateway Description"] || concentratorData["Gateway Description"],
"Antenna Placement": createdRecord.fields["Antenna Placement"] || concentratorData["Antenna Placement"],
}; };
} catch (error) { } catch (error) {
console.error("Error creating concentrator:", error); console.error("Error creating concentrator:", error);
@@ -170,11 +145,6 @@ export const updateConcentrator = async (
"Installed Time": concentratorData["Installed Time"], "Installed Time": concentratorData["Installed Time"],
"Communication Time": concentratorData["Communication Time"], "Communication Time": concentratorData["Communication Time"],
"Instruction Manual": concentratorData["Instruction Manual"], "Instruction Manual": concentratorData["Instruction Manual"],
"Gateway ID": concentratorData["Gateway ID"],
"Gateway EUI": concentratorData["Gateway EUI"],
"Gateway Name": concentratorData["Gateway Name"],
"Gateway Description": concentratorData["Gateway Description"],
"Antenna Placement": concentratorData["Antenna Placement"],
}, },
}), }),
}); });
@@ -205,11 +175,6 @@ export const updateConcentrator = async (
"Installed Time": updatedRecord.fields["Installed Time"] || concentratorData["Installed Time"], "Installed Time": updatedRecord.fields["Installed Time"] || concentratorData["Installed Time"],
"Communication Time": updatedRecord.fields["Communication Time"] || concentratorData["Communication Time"], "Communication Time": updatedRecord.fields["Communication Time"] || concentratorData["Communication Time"],
"Instruction Manual": updatedRecord.fields["Instruction Manual"] || concentratorData["Instruction Manual"], "Instruction Manual": updatedRecord.fields["Instruction Manual"] || concentratorData["Instruction Manual"],
"Gateway ID": updatedRecord.fields["Gateway ID"] || concentratorData["Gateway ID"],
"Gateway EUI": updatedRecord.fields["Gateway EUI"] || concentratorData["Gateway EUI"],
"Gateway Name": updatedRecord.fields["Gateway Name"] || concentratorData["Gateway Name"],
"Gateway Description": updatedRecord.fields["Gateway Description"] || concentratorData["Gateway Description"],
"Antenna Placement": updatedRecord.fields["Antenna Placement"] || concentratorData["Antenna Placement"],
}; };
} catch (error) { } catch (error) {
console.error("Error updating concentrator:", error); console.error("Error updating concentrator:", error);

View File

@@ -18,6 +18,15 @@ interface User {
project?: string; // asignado si no es superadmin project?: string; // asignado si no es superadmin
} }
interface GatewayData {
"Gateway ID": number;
"Gateway EUI": string;
"Gateway Name": string;
"Gateway Description": string;
"Antenna Placement": "Indoor" | "Outdoor";
concentratorId?: string;
}
/* ================= COMPONENT ================= */ /* ================= COMPONENT ================= */
export default function ConcentratorsPage() { export default function ConcentratorsPage() {
// Simulación de usuario actual // Simulación de usuario actual
@@ -99,6 +108,9 @@ export default function ConcentratorsPage() {
"Installed Time": new Date().toISOString().slice(0, 10), "Installed Time": new Date().toISOString().slice(0, 10),
"Communication Time": new Date().toISOString(), "Communication Time": new Date().toISOString(),
"Instruction Manual": "", "Instruction Manual": "",
});
const getEmptyGatewayData = (): GatewayData => ({
"Gateway ID": 0, "Gateway ID": 0,
"Gateway EUI": "", "Gateway EUI": "",
"Gateway Name": "", "Gateway Name": "",
@@ -107,10 +119,51 @@ export default function ConcentratorsPage() {
}); });
const [form, setForm] = useState<Omit<Concentrator, "id">>(getEmptyConcentrator()); const [form, setForm] = useState<Omit<Concentrator, "id">>(getEmptyConcentrator());
const [gatewayForm, setGatewayForm] = useState<GatewayData>(getEmptyGatewayData());
const [errors, setErrors] = useState<{ [key: string]: boolean }>({});
/* ================= CRUD ================= */ /* ================= CRUD ================= */
const createOrUpdateGateway = async (gatewayData: GatewayData): Promise<void> => {
//await fetch('/api/gateways', { method: 'POST', body: JSON.stringify(gatewayData) })
return new Promise((resolve) => {
setTimeout(() => {
console.log('Gateway data that would be sent to API:', gatewayData);
resolve();
}, 500);
});
};
const validateForm = (): boolean => {
const newErrors: { [key: string]: boolean } = {};
if (!form["Device Name"].trim()) newErrors["Device Name"] = true;
if (!form["Device S/N"].trim()) newErrors["Device S/N"] = true;
if (!form["Operator"].trim()) newErrors["Operator"] = true;
if (!form["Instruction Manual"].trim()) newErrors["Instruction Manual"] = true;
if (!form["Installed Time"]) newErrors["Installed Time"] = true;
if (!form["Device Time"]) newErrors["Device Time"] = true;
if (!form["Communication Time"]) newErrors["Communication Time"] = true;
if (!gatewayForm["Gateway ID"] || gatewayForm["Gateway ID"] === 0) {
newErrors["Gateway ID"] = true;
}
if (!gatewayForm["Gateway EUI"].trim()) newErrors["Gateway EUI"] = true;
if (!gatewayForm["Gateway Name"].trim()) newErrors["Gateway Name"] = true;
if (!gatewayForm["Gateway Description"].trim()) newErrors["Gateway Description"] = true;
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSave = async () => { const handleSave = async () => {
if (!validateForm()) {
return;
}
try { try {
let savedConcentrator: Concentrator;
if (editingSerial) { if (editingSerial) {
const concentratorToUpdate = concentrators.find(c => c["Device S/N"] === editingSerial); const concentratorToUpdate = concentrators.find(c => c["Device S/N"] === editingSerial);
if (!concentratorToUpdate) { if (!concentratorToUpdate) {
@@ -123,13 +176,30 @@ export default function ConcentratorsPage() {
c.id === concentratorToUpdate.id ? updatedConcentrator : c c.id === concentratorToUpdate.id ? updatedConcentrator : c
) )
); );
savedConcentrator = updatedConcentrator;
} else { } else {
const newConcentrator = await createConcentrator(form); const newConcentrator = await createConcentrator(form);
setConcentrators((prev) => [...prev, newConcentrator]); setConcentrators((prev) => [...prev, newConcentrator]);
savedConcentrator = newConcentrator;
} }
try {
const gatewayDataWithRef = {
...gatewayForm,
concentratorId: savedConcentrator.id,
};
await createOrUpdateGateway(gatewayDataWithRef);
console.log('Gateway data saved successfully');
} catch (gatewayError) {
console.error('Error saving gateway data:', gatewayError);
alert('Concentrator saved, but there was an error saving gateway data.');
}
setShowModal(false); setShowModal(false);
setEditingSerial(null); setEditingSerial(null);
setForm({ ...getEmptyConcentrator(), "Area Name": selectedProject }); setForm({ ...getEmptyConcentrator(), "Area Name": selectedProject });
setGatewayForm(getEmptyGatewayData());
setErrors({});
setActiveConcentrator(null); setActiveConcentrator(null);
} catch (error) { } catch (error) {
console.error('Error saving concentrator:', error); console.error('Error saving concentrator:', error);
@@ -223,6 +293,8 @@ export default function ConcentratorsPage() {
<button <button
onClick={() => { onClick={() => {
setForm({ ...getEmptyConcentrator(), "Area Name": selectedProject }); setForm({ ...getEmptyConcentrator(), "Area Name": selectedProject });
setGatewayForm(getEmptyGatewayData());
setErrors({});
setEditingSerial(null); setEditingSerial(null);
setShowModal(true); setShowModal(true);
}} }}
@@ -246,12 +318,9 @@ export default function ConcentratorsPage() {
"Installed Time": activeConcentrator["Installed Time"], "Installed Time": activeConcentrator["Installed Time"],
"Communication Time": activeConcentrator["Communication Time"], "Communication Time": activeConcentrator["Communication Time"],
"Instruction Manual": activeConcentrator["Instruction Manual"], "Instruction Manual": activeConcentrator["Instruction Manual"],
"Gateway ID": activeConcentrator["Gateway ID"],
"Gateway EUI": activeConcentrator["Gateway EUI"],
"Gateway Name": activeConcentrator["Gateway Name"],
"Gateway Description": activeConcentrator["Gateway Description"],
"Antenna Placement": activeConcentrator["Antenna Placement"],
}); });
setGatewayForm(getEmptyGatewayData());
setErrors({});
setShowModal(true); setShowModal(true);
}} }}
disabled={!activeConcentrator} disabled={!activeConcentrator}
@@ -337,151 +406,303 @@ export default function ConcentratorsPage() {
{/* 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 space-y-3"> <div className="bg-white rounded-xl p-6 w-[600px] max-h-[90vh] overflow-y-auto space-y-4">
<h2 className="text-lg font-semibold"> <h2 className="text-lg font-semibold">
{editingSerial ? "Edit Concentrator" : "Add Concentrator"} {editingSerial ? "Edit Concentrator" : "Add Concentrator"}
</h2> </h2>
<div className="space-y-3"> <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" Concentrator Information
placeholder="Device Name" </h3>
value={form["Device Name"]}
onChange={(e) => <div>
setForm({ ...form, "Device Name": e.target.value }) <input
} className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
/> errors["Device Name"] ? "border-red-500" : ""
}`}
placeholder="Device Name *"
value={form["Device Name"]}
onChange={(e) => {
setForm({ ...form, "Device Name": e.target.value });
if (errors["Device Name"]) {
setErrors({ ...errors, "Device Name": false });
}
}}
required
/>
{errors["Device Name"] && (
<p className="text-red-500 text-xs mt-1">This field is required</p>
)}
</div>
<input <div>
className="w-full border px-3 py-2 rounded" <input
placeholder="Device S/N" className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
value={form["Device S/N"]} errors["Device S/N"] ? "border-red-500" : ""
onChange={(e) => }`}
setForm({ ...form, "Device S/N": e.target.value }) placeholder="Device S/N *"
} value={form["Device S/N"]}
/> onChange={(e) => {
setForm({ ...form, "Device S/N": e.target.value });
if (errors["Device S/N"]) {
setErrors({ ...errors, "Device S/N": false });
}
}}
required
/>
{errors["Device S/N"] && (
<p className="text-red-500 text-xs mt-1">This field is required</p>
)}
</div>
<input <div>
className="w-full border px-3 py-2 rounded" <input
placeholder="Operator" className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
value={form["Operator"]} errors["Operator"] ? "border-red-500" : ""
onChange={(e) => }`}
setForm({ ...form, "Operator": e.target.value }) placeholder="Operator *"
} value={form["Operator"]}
/> onChange={(e) => {
setForm({ ...form, "Operator": e.target.value });
if (errors["Operator"]) {
setErrors({ ...errors, "Operator": false });
}
}}
required
/>
{errors["Operator"] && (
<p className="text-red-500 text-xs mt-1">This field is required</p>
)}
</div>
<input <div>
className="w-full border px-3 py-2 rounded" <input
placeholder="Instruction Manual" className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
value={form["Instruction Manual"]} errors["Instruction Manual"] ? "border-red-500" : ""
onChange={(e) => }`}
setForm({ ...form, "Instruction Manual": e.target.value }) placeholder="Instruction Manual *"
} value={form["Instruction Manual"]}
/> onChange={(e) => {
setForm({ ...form, "Instruction Manual": e.target.value });
if (errors["Instruction Manual"]) {
setErrors({ ...errors, "Instruction Manual": false });
}
}}
required
/>
{errors["Instruction Manual"] && (
<p className="text-red-500 text-xs mt-1">This field is required</p>
)}
</div>
<button <button
onClick={() => onClick={() =>
setForm({ setForm({
...form, ...form,
"Device Status": "Device Status":
form["Device Status"] === "ACTIVE" ? "INACTIVE" : "ACTIVE", form["Device Status"] === "ACTIVE" ? "INACTIVE" : "ACTIVE",
}) })
} }
className="w-full border rounded px-3 py-2 hover:bg-gray-50 text-left" className="w-full border rounded px-3 py-2 hover:bg-gray-50 text-left"
> >
Device Status: {form["Device Status"]} Device Status: {form["Device Status"]} *
</button> </button>
<input <div>
type="date" <input
className="w-full border px-3 py-2 rounded" type="date"
value={form["Installed Time"]} className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
onChange={(e) => errors["Installed Time"] ? "border-red-500" : ""
setForm({ ...form, "Installed Time": e.target.value }) }`}
} placeholder="Installed Time *"
/> value={form["Installed Time"]}
onChange={(e) => {
setForm({ ...form, "Installed Time": e.target.value });
if (errors["Installed Time"]) {
setErrors({ ...errors, "Installed Time": false });
}
}}
required
/>
{errors["Installed Time"] && (
<p className="text-red-500 text-xs mt-1">This field is required</p>
)}
</div>
<input <div>
type="datetime-local" <input
className="w-full border px-3 py-2 rounded" type="datetime-local"
value={form["Device Time"].slice(0, 16)} className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
onChange={(e) => errors["Device Time"] ? "border-red-500" : ""
setForm({ }`}
...form, placeholder="Device Time *"
"Device Time": new Date(e.target.value).toISOString(), value={form["Device Time"].slice(0, 16)}
}) onChange={(e) => {
} setForm({
/> ...form,
"Device Time": new Date(e.target.value).toISOString(),
});
if (errors["Device Time"]) {
setErrors({ ...errors, "Device Time": false });
}
}}
required
/>
{errors["Device Time"] && (
<p className="text-red-500 text-xs mt-1">This field is required</p>
)}
</div>
<input <div>
type="datetime-local" <input
className="w-full border px-3 py-2 rounded" type="datetime-local"
value={form["Communication Time"].slice(0, 16)} className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
onChange={(e) => errors["Communication Time"] ? "border-red-500" : ""
setForm({ }`}
...form, placeholder="Communication Time *"
"Communication Time": new Date(e.target.value).toISOString(), value={form["Communication Time"].slice(0, 16)}
}) onChange={(e) => {
} setForm({
/> ...form,
"Communication Time": new Date(e.target.value).toISOString(),
});
if (errors["Communication Time"]) {
setErrors({ ...errors, "Communication Time": false });
}
}}
required
/>
{errors["Communication Time"] && (
<p className="text-red-500 text-xs mt-1">This field is required</p>
)}
</div>
</div>
<input <div className="space-y-3 pt-4">
type="number" <h3 className="text-sm font-semibold text-gray-700 border-b pb-2">
className="w-full border px-3 py-2 rounded" Gateway Information
placeholder="Gateway ID" </h3>
value={form["Gateway ID"]}
onChange={(e) =>
setForm({ ...form, "Gateway ID": parseInt(e.target.value) || 0 })
}
/>
<input <div>
className="w-full border px-3 py-2 rounded" <input
placeholder="Gateway EUI" type="number"
value={form["Gateway EUI"]} className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
onChange={(e) => errors["Gateway ID"] ? "border-red-500" : ""
setForm({ ...form, "Gateway EUI": e.target.value }) }`}
} placeholder="Gateway ID *"
/> value={gatewayForm["Gateway ID"] || ""}
onChange={(e) => {
setGatewayForm({
...gatewayForm,
"Gateway ID": parseInt(e.target.value) || 0,
});
if (errors["Gateway ID"]) {
setErrors({ ...errors, "Gateway ID": false });
}
}}
required
min="1"
/>
{errors["Gateway ID"] && (
<p className="text-red-500 text-xs mt-1">This field is required</p>
)}
</div>
<input <div>
className="w-full border px-3 py-2 rounded" <input
placeholder="Gateway Name" className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
value={form["Gateway Name"]} errors["Gateway EUI"] ? "border-red-500" : ""
onChange={(e) => }`}
setForm({ ...form, "Gateway Name": e.target.value }) placeholder="Gateway EUI *"
} value={gatewayForm["Gateway EUI"]}
/> onChange={(e) => {
setGatewayForm({ ...gatewayForm, "Gateway EUI": e.target.value });
if (errors["Gateway EUI"]) {
setErrors({ ...errors, "Gateway EUI": false });
}
}}
required
/>
{errors["Gateway EUI"] && (
<p className="text-red-500 text-xs mt-1">This field is required</p>
)}
</div>
<textarea <div>
className="w-full border px-3 py-2 rounded" <input
placeholder="Gateway Description" className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
value={form["Gateway Description"]} errors["Gateway Name"] ? "border-red-500" : ""
onChange={(e) => }`}
setForm({ ...form, "Gateway Description": e.target.value }) placeholder="Gateway Name *"
} value={gatewayForm["Gateway Name"]}
rows={3} onChange={(e) => {
/> setGatewayForm({ ...gatewayForm, "Gateway Name": e.target.value });
if (errors["Gateway Name"]) {
setErrors({ ...errors, "Gateway Name": false });
}
}}
required
/>
{errors["Gateway Name"] && (
<p className="text-red-500 text-xs mt-1">This field is required</p>
)}
</div>
<select <div>
className="w-full border px-3 py-2 rounded" <input
value={form["Antenna Placement"]} className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
onChange={(e) => errors["Gateway Description"] ? "border-red-500" : ""
setForm({ ...form, "Antenna Placement": e.target.value }) }`}
} placeholder="Gateway Description *"
> value={gatewayForm["Gateway Description"]}
<option value="Indoor">Indoor</option> onChange={(e) => {
<option value="Outdoor">Outdoor</option> setGatewayForm({
</select> ...gatewayForm,
</div> "Gateway Description": e.target.value,
});
if (errors["Gateway Description"]) {
setErrors({ ...errors, "Gateway Description": false });
}
}}
required
/>
{errors["Gateway Description"] && (
<p className="text-red-500 text-xs mt-1">This field is required</p>
)}
</div>
<select
className="w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent"
value={gatewayForm["Antenna Placement"]}
onChange={(e) =>
setGatewayForm({
...gatewayForm,
"Antenna Placement": e.target.value as "Indoor" | "Outdoor",
})
}
required
>
<option value="Indoor">Indoor</option>
<option value="Outdoor">Outdoor</option>
</select>
</div>
<div className="flex justify-end gap-2 pt-3"> <div className="flex justify-end gap-2 pt-3 border-t">
<button onClick={() => setShowModal(false)}>Cancel</button> <button
onClick={() => {
setShowModal(false);
setGatewayForm(getEmptyGatewayData());
setErrors({});
}}
className="px-4 py-2 rounded hover:bg-gray-100"
>
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>