This commit is contained in:
[Valentino Heman Budiarto] 2026-06-05 20:46:51 +07:00
parent 679461d3a3
commit 89c68f340f
2 changed files with 24 additions and 23 deletions

View File

@ -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"
} }
} }

View File

@ -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";
// Konfirmasi mematikan (Biar admin gak salah pencet)
if (currentStatus) {
const confirmMsg = `Apakah Anda yakin ingin mematikan ${deviceName} di ${roomName}?`; const confirmMsg = `Apakah Anda yakin ingin mematikan ${deviceName} di ${roomName}?`;
if (currentStatus && !confirm(confirmMsg)) return; 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