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;
"Communication Time": 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;
"Communication Time": 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[]> => {
@@ -77,11 +67,6 @@ export const fetchConcentrators = async (): Promise<Concentrator[]> => {
"Installed Time": r.fields["Installed Time"] || "",
"Communication Time": r.fields["Communication Time"] || "",
"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) {
console.error("Error fetching concentrators:", error);
@@ -107,11 +92,6 @@ export const createConcentrator = async (
"Installed Time": concentratorData["Installed Time"],
"Communication Time": concentratorData["Communication Time"],
"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"],
"Communication Time": createdRecord.fields["Communication Time"] || concentratorData["Communication Time"],
"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) {
console.error("Error creating concentrator:", error);
@@ -170,11 +145,6 @@ export const updateConcentrator = async (
"Installed Time": concentratorData["Installed Time"],
"Communication Time": concentratorData["Communication Time"],
"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"],
"Communication Time": updatedRecord.fields["Communication Time"] || concentratorData["Communication Time"],
"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) {
console.error("Error updating concentrator:", error);

View File

@@ -18,6 +18,15 @@ interface User {
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 ================= */
export default function ConcentratorsPage() {
// Simulación de usuario actual
@@ -99,6 +108,9 @@ export default function ConcentratorsPage() {
"Installed Time": new Date().toISOString().slice(0, 10),
"Communication Time": new Date().toISOString(),
"Instruction Manual": "",
});
const getEmptyGatewayData = (): GatewayData => ({
"Gateway ID": 0,
"Gateway EUI": "",
"Gateway Name": "",
@@ -107,10 +119,51 @@ export default function ConcentratorsPage() {
});
const [form, setForm] = useState<Omit<Concentrator, "id">>(getEmptyConcentrator());
const [gatewayForm, setGatewayForm] = useState<GatewayData>(getEmptyGatewayData());
const [errors, setErrors] = useState<{ [key: string]: boolean }>({});
/* ================= 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 () => {
if (!validateForm()) {
return;
}
try {
let savedConcentrator: Concentrator;
if (editingSerial) {
const concentratorToUpdate = concentrators.find(c => c["Device S/N"] === editingSerial);
if (!concentratorToUpdate) {
@@ -123,13 +176,30 @@ export default function ConcentratorsPage() {
c.id === concentratorToUpdate.id ? updatedConcentrator : c
)
);
savedConcentrator = updatedConcentrator;
} else {
const newConcentrator = await createConcentrator(form);
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);
setEditingSerial(null);
setForm({ ...getEmptyConcentrator(), "Area Name": selectedProject });
setGatewayForm(getEmptyGatewayData());
setErrors({});
setActiveConcentrator(null);
} catch (error) {
console.error('Error saving concentrator:', error);
@@ -223,6 +293,8 @@ export default function ConcentratorsPage() {
<button
onClick={() => {
setForm({ ...getEmptyConcentrator(), "Area Name": selectedProject });
setGatewayForm(getEmptyGatewayData());
setErrors({});
setEditingSerial(null);
setShowModal(true);
}}
@@ -246,12 +318,9 @@ export default function ConcentratorsPage() {
"Installed Time": activeConcentrator["Installed Time"],
"Communication Time": activeConcentrator["Communication Time"],
"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);
}}
disabled={!activeConcentrator}
@@ -337,48 +406,96 @@ export default function ConcentratorsPage() {
{/* MODAL */}
{showModal && (
<div className="fixed inset-0 bg-black/40 flex items-center justify-center">
<div className="bg-white rounded-xl p-6 w-96 space-y-3">
<div className="fixed inset-0 bg-black/40 flex items-center justify-center z-50">
<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">
{editingSerial ? "Edit Concentrator" : "Add Concentrator"}
</h2>
<div className="space-y-3">
<h3 className="text-sm font-semibold text-gray-700 border-b pb-2">
Concentrator Information
</h3>
<div>
<input
className="w-full border px-3 py-2 rounded"
placeholder="Device Name"
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 })
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>
<div>
<input
className="w-full border px-3 py-2 rounded"
placeholder="Device S/N"
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
errors["Device S/N"] ? "border-red-500" : ""
}`}
placeholder="Device S/N *"
value={form["Device S/N"]}
onChange={(e) =>
setForm({ ...form, "Device S/N": e.target.value })
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>
<div>
<input
className="w-full border px-3 py-2 rounded"
placeholder="Operator"
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
errors["Operator"] ? "border-red-500" : ""
}`}
placeholder="Operator *"
value={form["Operator"]}
onChange={(e) =>
setForm({ ...form, "Operator": e.target.value })
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>
<div>
<input
className="w-full border px-3 py-2 rounded"
placeholder="Instruction Manual"
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
errors["Instruction Manual"] ? "border-red-500" : ""
}`}
placeholder="Instruction Manual *"
value={form["Instruction Manual"]}
onChange={(e) =>
setForm({ ...form, "Instruction Manual": e.target.value })
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
onClick={() =>
@@ -390,98 +507,202 @@ export default function ConcentratorsPage() {
}
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>
<div>
<input
type="date"
className="w-full border px-3 py-2 rounded"
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
errors["Installed Time"] ? "border-red-500" : ""
}`}
placeholder="Installed Time *"
value={form["Installed Time"]}
onChange={(e) =>
setForm({ ...form, "Installed Time": e.target.value })
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>
<div>
<input
type="datetime-local"
className="w-full border px-3 py-2 rounded"
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
errors["Device Time"] ? "border-red-500" : ""
}`}
placeholder="Device Time *"
value={form["Device Time"].slice(0, 16)}
onChange={(e) =>
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>
<div>
<input
type="datetime-local"
className="w-full border px-3 py-2 rounded"
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
errors["Communication Time"] ? "border-red-500" : ""
}`}
placeholder="Communication Time *"
value={form["Communication Time"].slice(0, 16)}
onChange={(e) =>
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>
<div className="space-y-3 pt-4">
<h3 className="text-sm font-semibold text-gray-700 border-b pb-2">
Gateway Information
</h3>
<div>
<input
type="number"
className="w-full border px-3 py-2 rounded"
placeholder="Gateway ID"
value={form["Gateway ID"]}
onChange={(e) =>
setForm({ ...form, "Gateway ID": parseInt(e.target.value) || 0 })
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
errors["Gateway ID"] ? "border-red-500" : ""
}`}
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>
<div>
<input
className="w-full border px-3 py-2 rounded"
placeholder="Gateway EUI"
value={form["Gateway EUI"]}
onChange={(e) =>
setForm({ ...form, "Gateway EUI": e.target.value })
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
errors["Gateway EUI"] ? "border-red-500" : ""
}`}
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>
<div>
<input
className="w-full border px-3 py-2 rounded"
placeholder="Gateway Name"
value={form["Gateway Name"]}
onChange={(e) =>
setForm({ ...form, "Gateway Name": e.target.value })
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
errors["Gateway Name"] ? "border-red-500" : ""
}`}
placeholder="Gateway Name *"
value={gatewayForm["Gateway Name"]}
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>
<textarea
className="w-full border px-3 py-2 rounded"
placeholder="Gateway Description"
value={form["Gateway Description"]}
onChange={(e) =>
setForm({ ...form, "Gateway Description": e.target.value })
<div>
<input
className={`w-full border px-3 py-2 rounded focus:ring-2 focus:ring-blue-500 focus:border-transparent ${
errors["Gateway Description"] ? "border-red-500" : ""
}`}
placeholder="Gateway Description *"
value={gatewayForm["Gateway Description"]}
onChange={(e) => {
setGatewayForm({
...gatewayForm,
"Gateway Description": e.target.value,
});
if (errors["Gateway Description"]) {
setErrors({ ...errors, "Gateway Description": false });
}
rows={3}
}}
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"
value={form["Antenna Placement"]}
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) =>
setForm({ ...form, "Antenna Placement": e.target.value })
setGatewayForm({
...gatewayForm,
"Antenna Placement": e.target.value as "Indoor" | "Outdoor",
})
}
required
>
<option value="Indoor">Indoor</option>
<option value="Outdoor">Outdoor</option>
</select>
</div>
</div>
<div className="flex justify-end gap-2 pt-3">
<button onClick={() => setShowModal(false)}>Cancel</button>
<div className="flex justify-end gap-2 pt-3 border-t">
<button
onClick={() => {
setShowModal(false);
setGatewayForm(getEmptyGatewayData());
setErrors({});
}}
className="px-4 py-2 rounded hover:bg-gray-100"
>
Cancel
</button>
<button
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
</button>