.
This commit is contained in:
parent
679461d3a3
commit
89c68f340f
@ -153,17 +153,19 @@ func ControlHardware(c *gin.Context) {
|
|||||||
haToken := os.Getenv("HA_TOKEN")
|
haToken := os.Getenv("HA_TOKEN")
|
||||||
var entityID string
|
var entityID string
|
||||||
|
|
||||||
switch req.Device {
|
switch req.Device {
|
||||||
case "ac":
|
case "ac":
|
||||||
if req.Action == "on" {
|
switch req.Action {
|
||||||
|
case "on":
|
||||||
entityID = "scene.ac_d101_on"
|
entityID = "scene.ac_d101_on"
|
||||||
} else {
|
default:
|
||||||
entityID = "scene.ac_d101_off"
|
entityID = "scene.ac_d101_off"
|
||||||
}
|
}
|
||||||
case "projector":
|
case "projector":
|
||||||
if req.Action == "on" {
|
switch req.Action {
|
||||||
|
case "on":
|
||||||
entityID = "scene.projector_d101_on"
|
entityID = "scene.projector_d101_on"
|
||||||
} else {
|
case "off":
|
||||||
entityID = "scene.projector_d101_off"
|
entityID = "scene.projector_d101_off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -38,7 +38,6 @@ export default function PowerMonitoringPage() {
|
|||||||
const backendData = result.data;
|
const backendData = result.data;
|
||||||
|
|
||||||
// Kita terjemahkan data Golang ("on"/"off") menjadi boolean (true/false)
|
// Kita terjemahkan data Golang ("on"/"off") menjadi boolean (true/false)
|
||||||
// dan masukkan khusus untuk Kelas D101 (id: 1)
|
|
||||||
setRoomDeviceStatus(prev => ({
|
setRoomDeviceStatus(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
"room_1": {
|
"room_1": {
|
||||||
@ -59,7 +58,7 @@ export default function PowerMonitoringPage() {
|
|||||||
// 3. INISIALISASI & POLLING (AUTO-REFRESH)
|
// 3. INISIALISASI & POLLING (AUTO-REFRESH)
|
||||||
// =========================================================================
|
// =========================================================================
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// A. Simulasi Data Ruangan dari DB/IoT
|
// Simulasi Data Ruangan
|
||||||
const dummyRooms = [
|
const dummyRooms = [
|
||||||
{ id: 1, name: "Kelas D101", power: 0, isRelayOn: true, lastUpdate: "Real-time" },
|
{ id: 1, name: "Kelas D101", power: 0, isRelayOn: true, lastUpdate: "Real-time" },
|
||||||
{ id: 2, name: "Kelas D102", power: 0, isRelayOn: false, lastUpdate: "2 mnt lalu" },
|
{ id: 2, name: "Kelas D102", power: 0, isRelayOn: false, lastUpdate: "2 mnt lalu" },
|
||||||
@ -68,7 +67,7 @@ export default function PowerMonitoringPage() {
|
|||||||
];
|
];
|
||||||
setRooms(dummyRooms);
|
setRooms(dummyRooms);
|
||||||
|
|
||||||
// B. Inisialisasi status default semua device OFF
|
// Inisialisasi status default semua device OFF
|
||||||
const initialStatus: { [roomId: string]: { [deviceName: string]: boolean } } = {};
|
const initialStatus: { [roomId: string]: { [deviceName: string]: boolean } } = {};
|
||||||
dummyRooms.forEach(room => {
|
dummyRooms.forEach(room => {
|
||||||
initialStatus[`room_${room.id}`] = {
|
initialStatus[`room_${room.id}`] = {
|
||||||
@ -80,17 +79,15 @@ export default function PowerMonitoringPage() {
|
|||||||
});
|
});
|
||||||
setRoomDeviceStatus(initialStatus);
|
setRoomDeviceStatus(initialStatus);
|
||||||
|
|
||||||
// C. Tarik data pertama kali saat halaman dibuka
|
// Tarik data pertama kali & set Interval 2 detik
|
||||||
fetchPowerStatus();
|
fetchPowerStatus();
|
||||||
fetchDeviceStatus();
|
fetchDeviceStatus();
|
||||||
|
|
||||||
// D. Jalankan Polling setiap 2 detik (2000 ms)
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
fetchPowerStatus();
|
fetchPowerStatus();
|
||||||
fetchDeviceStatus();
|
fetchDeviceStatus();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
|
|
||||||
// E. Bersihkan interval jika admin pindah halaman
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -100,18 +97,24 @@ export default function PowerMonitoringPage() {
|
|||||||
const handleDeviceToggle = async (roomId: number, roomName: string, deviceName: string) => {
|
const handleDeviceToggle = async (roomId: number, roomName: string, deviceName: string) => {
|
||||||
const roomIdKey = `room_${roomId}`;
|
const roomIdKey = `room_${roomId}`;
|
||||||
const currentStatus = roomDeviceStatus[roomIdKey]?.[deviceName] || false;
|
const currentStatus = roomDeviceStatus[roomIdKey]?.[deviceName] || false;
|
||||||
|
|
||||||
|
// Logika Pintar: Jika sedang ON, maka klik selanjutnya adalah OFF. Sebaliknya.
|
||||||
const actionType = currentStatus ? "off" : "on";
|
const actionType = currentStatus ? "off" : "on";
|
||||||
|
|
||||||
const confirmMsg = `Apakah Anda yakin ingin mematikan ${deviceName} di ${roomName}?`;
|
// Konfirmasi mematikan (Biar admin gak salah pencet)
|
||||||
if (currentStatus && !confirm(confirmMsg)) return;
|
if (currentStatus) {
|
||||||
|
const confirmMsg = `Apakah Anda yakin ingin mematikan ${deviceName} di ${roomName}?`;
|
||||||
|
if (!window.confirm(confirmMsg)) return; // Jika di-cancel, berhenti di sini
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mapping nama device untuk backend
|
||||||
let backendDevice = "";
|
let backendDevice = "";
|
||||||
if (deviceName === 'AC 1') backendDevice = "ac";
|
if (deviceName === 'AC 1') backendDevice = "ac";
|
||||||
else if (deviceName === 'Proyektor') backendDevice = "projector";
|
else if (deviceName === 'Proyektor') backendDevice = "projector";
|
||||||
else if (deviceName === 'Lampu 1') backendDevice = "lampu1";
|
else if (deviceName === 'Lampu 1') backendDevice = "lampu1";
|
||||||
else if (deviceName === 'Lampu 2') backendDevice = "lampu2";
|
else if (deviceName === 'Lampu 2') backendDevice = "lampu2";
|
||||||
|
|
||||||
// Update UI seketika agar terasa responsif bagi admin yang menekan tombol
|
// 1. UBAH UI SEKETIKA (Optimistic UI) agar layar merespons tanpa delay
|
||||||
setRoomDeviceStatus(prev => ({
|
setRoomDeviceStatus(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[roomIdKey]: {
|
[roomIdKey]: {
|
||||||
@ -120,6 +123,7 @@ export default function PowerMonitoringPage() {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
// 2. KIRIM KE GOLANG
|
||||||
try {
|
try {
|
||||||
const response = await fetch("http://172.17.172.17:8080/api/hardware/control", {
|
const response = await fetch("http://172.17.172.17:8080/api/hardware/control", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -127,8 +131,8 @@ export default function PowerMonitoringPage() {
|
|||||||
body: JSON.stringify({ device: backendDevice, action: actionType }),
|
body: JSON.stringify({ device: backendDevice, action: actionType }),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 3. JIKA GAGAL, KEMBALIKAN UI KE POSISI SEMULA
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
// Jika gagal, kembalikan posisi tombol ke keadaan semula
|
|
||||||
setRoomDeviceStatus(prev => ({
|
setRoomDeviceStatus(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[roomIdKey]: {
|
[roomIdKey]: {
|
||||||
@ -137,11 +141,11 @@ export default function PowerMonitoringPage() {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
const errorData = await response.json();
|
const errorData = await response.json();
|
||||||
alert(`GAGAL: ${errorData.error || response.statusText}`);
|
alert(`GAGAL: ${errorData.error || "Server menolak perintah"}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error API:", error);
|
console.error("Error API:", error);
|
||||||
// Jika server mati, kembalikan posisi tombol
|
// Rollback UI jika koneksi mati
|
||||||
setRoomDeviceStatus(prev => ({
|
setRoomDeviceStatus(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[roomIdKey]: {
|
[roomIdKey]: {
|
||||||
@ -154,7 +158,7 @@ export default function PowerMonitoringPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCutOff = (roomName: string) => {
|
const handleCutOff = (roomName: string) => {
|
||||||
if (confirm(`PERINGATAN: Anda yakin ingin mematikan daya secara paksa di ${roomName}?`)) {
|
if (window.confirm(`PERINGATAN: Anda yakin ingin mematikan daya secara paksa di ${roomName}?`)) {
|
||||||
alert(`Sinyal pemutusan daya dikirim ke Relay Master ${roomName}.`);
|
alert(`Sinyal pemutusan daya dikirim ke Relay Master ${roomName}.`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -164,7 +168,6 @@ export default function PowerMonitoringPage() {
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Header section */}
|
|
||||||
<div className="flex items-center gap-3 mb-6">
|
<div className="flex items-center gap-3 mb-6">
|
||||||
<div className="bg-blue-100 p-3 rounded-lg text-blue-600">
|
<div className="bg-blue-100 p-3 rounded-lg text-blue-600">
|
||||||
<Activity size={28} />
|
<Activity size={28} />
|
||||||
@ -184,7 +187,6 @@ export default function PowerMonitoringPage() {
|
|||||||
return (
|
return (
|
||||||
<div key={room.id} className="bg-white p-6 rounded-xl border border-gray-100 shadow-sm relative overflow-hidden flex flex-col">
|
<div key={room.id} className="bg-white p-6 rounded-xl border border-gray-100 shadow-sm relative overflow-hidden flex flex-col">
|
||||||
|
|
||||||
{/* Status Badge */}
|
|
||||||
<div className={`absolute top-0 right-0 px-4 py-1.5 text-[10px] font-black uppercase tracking-wider rounded-bl-xl
|
<div className={`absolute top-0 right-0 px-4 py-1.5 text-[10px] font-black uppercase tracking-wider rounded-bl-xl
|
||||||
${room.isRelayOn ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
|
${room.isRelayOn ? 'bg-green-100 text-green-700' : 'bg-red-100 text-red-700'}`}>
|
||||||
{room.isRelayOn ? 'Sirkuit Aktif' : 'Sirkuit Terputus'}
|
{room.isRelayOn ? 'Sirkuit Aktif' : 'Sirkuit Terputus'}
|
||||||
@ -192,7 +194,6 @@ export default function PowerMonitoringPage() {
|
|||||||
|
|
||||||
<h3 className="text-lg font-bold text-gray-800 mb-4">{room.name}</h3>
|
<h3 className="text-lg font-bold text-gray-800 mb-4">{room.name}</h3>
|
||||||
|
|
||||||
{/* Power display */}
|
|
||||||
<div className="flex items-end gap-2 mb-6">
|
<div className="flex items-end gap-2 mb-6">
|
||||||
<span className={`text-5xl font-black tracking-tight ${room.power > 1000 ? 'text-orange-500' : 'text-gray-800'}`}>
|
<span className={`text-5xl font-black tracking-tight ${room.power > 1000 ? 'text-orange-500' : 'text-gray-800'}`}>
|
||||||
{room.power}
|
{room.power}
|
||||||
@ -206,7 +207,6 @@ export default function PowerMonitoringPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* PANEL KONTROL IoT */}
|
|
||||||
<div className="mt-2 mb-6 pt-4 border-t border-gray-100">
|
<div className="mt-2 mb-6 pt-4 border-t border-gray-100">
|
||||||
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-wider mb-3">IoT Device Control</p>
|
<p className="text-[10px] font-bold text-gray-400 uppercase tracking-wider mb-3">IoT Device Control</p>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
<div className="grid grid-cols-2 gap-2">
|
||||||
@ -262,7 +262,6 @@ export default function PowerMonitoringPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
|
||||||
<div className="flex items-center justify-between mt-auto pt-4 border-t border-gray-100">
|
<div className="flex items-center justify-between mt-auto pt-4 border-t border-gray-100">
|
||||||
<span className="text-xs text-gray-400 font-medium">Update: {room.lastUpdate}</span>
|
<span className="text-xs text-gray-400 font-medium">Update: {room.lastUpdate}</span>
|
||||||
<button
|
<button
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user