From 1cf7a02ba709a373e3fee755ae10c9ce3a10a88e Mon Sep 17 00:00:00 2001 From: "[Valentino Heman Budiarto]" <[hemanvalentino@gmail.com]> Date: Mon, 15 Jun 2026 17:40:51 +0700 Subject: [PATCH] admin monitoring --- frontend/app/admin/monitoring/page.tsx | 372 +++++++++++++------------ 1 file changed, 195 insertions(+), 177 deletions(-) diff --git a/frontend/app/admin/monitoring/page.tsx b/frontend/app/admin/monitoring/page.tsx index dd36ed2..09c6aed 100644 --- a/frontend/app/admin/monitoring/page.tsx +++ b/frontend/app/admin/monitoring/page.tsx @@ -1,48 +1,47 @@ "use client"; import { useState, useEffect } from "react"; -import { Activity, Power, AlertTriangle, Lightbulb, Wind, Projector, Zap } from "lucide-react"; +import { Activity, Power, ZapOff, AlertTriangle, Lightbulb, Wind, Projector } from "lucide-react"; export default function PowerMonitoringPage() { // ========================================================================= - // 1. DEKLARASI STATE UNTUK KELAS D101 + // 1. DEKLARASI STATE // ========================================================================= - const [powerData, setPowerData] = useState({ umum: 0, ac1: 0, ac2: 0 }); - const [deviceStatus, setDeviceStatus] = useState({ - "Lampu 1": false, - "Lampu 2": false, - "AC": false, - "Proyektor": false, - }); - const [isRelayOn, setIsRelayOn] = useState(true); // Status apakah ruangan terkunci/terbuka - const [lastUpdate, setLastUpdate] = useState("Menunggu data..."); + const [rooms, setRooms] = useState([]); + const [roomDeviceStatus, setRoomDeviceStatus] = useState<{ [roomId: string]: { [deviceName: string]: boolean } }>({}); + + // State khusus untuk menampung rincian 3 MCB di D101 + const [powerDataD101, setPowerDataD101] = useState({ umum: 0, ac1: 0, ac2: 0 }); // ========================================================================= - // 2. FUNGSI FETCH DATA DARI BACKEND GOLANG + // 2. FUNGSI FETCH DATA DARI BACKEND // ========================================================================= - // A. Tarik Data Daya dari 3 MCB (Umum, AC1, AC2) + // A. Tarik Data Daya (Power / Watt) const fetchPowerStatus = async () => { try { - // Pastikan route ini sesuai dengan router.GET di main.go Golang kamu const response = await fetch("http://172.17.172.17:8080/api/hardware/power-status"); if (response.ok) { const data = await response.json(); - setPowerData({ - umum: parseFloat(data.umum) || 0, - ac1: parseFloat(data.ac1) || 0, - ac2: parseFloat(data.ac2) || 0, - }); - const now = new Date(); - setLastUpdate(`${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`); + const umum = parseFloat(data.umum) || 0; + const ac1 = parseFloat(data.ac1) || 0; + const ac2 = parseFloat(data.ac2) || 0; + const totalD101 = umum + ac1 + ac2; + + setPowerDataD101({ umum, ac1, ac2 }); + + // Update nilai power D101 di dalam array rooms + setRooms(prev => prev.map(room => + room.name === "Kelas D101" ? { ...room, power: totalD101, lastUpdate: "Real-time" } : room + )); } } catch (err) { console.error("Gagal mengambil data daya:", err); } }; - // B. Tarik Data Status Perangkat IoT + // B. Tarik Data Status Perangkat (Sinkronisasi Real-time) const fetchDeviceStatus = async () => { try { const response = await fetch("http://172.17.172.17:8080/api/hardware/status"); @@ -50,14 +49,17 @@ export default function PowerMonitoringPage() { if (result.status === "success") { const backendData = result.data; - setDeviceStatus({ - "Lampu 1": backendData.lampu1 === "on", - "Lampu 2": backendData.lampu2 === "on", - "AC": backendData.ac === "on", - "Proyektor": backendData.projector === "on", - }); - // Jika semua mati dan gemboknya aktif, bisa kita asumsikan ruangan off - // Tergantung bagaimana logika "auth" kamu di frontend + + setRoomDeviceStatus(prev => ({ + ...prev, + "room_1": { // room_1 diasumsikan sebagai D101 + ...prev["room_1"], + "Lampu 1": backendData.lampu1 === "on", + "Lampu 2": backendData.lampu2 === "on", + "AC": backendData.ac === "on", + "Proyektor": backendData.projector === "on", + } + })); } } catch (err) { console.error("Gagal sinkronisasi status perangkat:", err); @@ -65,16 +67,37 @@ export default function PowerMonitoringPage() { }; // ========================================================================= - // 3. INISIALISASI & POLLING (AUTO-REFRESH 3 DETIK) + // 3. INISIALISASI & POLLING (AUTO-REFRESH) // ========================================================================= useEffect(() => { + // Simulasi Data Ruangan + const dummyRooms = [ + { id: 1, name: "Kelas D101", power: 0, isRelayOn: true, lastUpdate: "Menunggu..." }, + { id: 2, name: "Kelas D102", power: 0, isRelayOn: false, lastUpdate: "2 mnt lalu" }, + { id: 3, name: "Kelas D103", power: 45, isRelayOn: true, lastUpdate: "Baru saja" }, + { id: 4, name: "Kelas D104", power: 0, isRelayOn: false, lastUpdate: "10 mnt lalu" }, + ]; + setRooms(dummyRooms); + + // Inisialisasi status default semua device OFF + const initialStatus: { [roomId: string]: { [deviceName: string]: boolean } } = {}; + dummyRooms.forEach(room => { + initialStatus[`room_${room.id}`] = { + "Lampu 1": false, + "Lampu 2": false, + "AC": false, + "Proyektor": false, + }; + }); + setRoomDeviceStatus(initialStatus); + fetchPowerStatus(); fetchDeviceStatus(); const interval = setInterval(() => { fetchPowerStatus(); fetchDeviceStatus(); - }, 3000); + }, 3000); // Polling 3 detik return () => clearInterval(interval); }, []); @@ -82,24 +105,30 @@ export default function PowerMonitoringPage() { // ========================================================================= // 4. FUNGSI KONTROL DEVICE & CUT OFF // ========================================================================= - const handleDeviceToggle = async (deviceName: string) => { - // Tentukan kunci array state yang benar - const key = deviceName as keyof typeof deviceStatus; - const currentStatus = deviceStatus[key]; + const handleDeviceToggle = async (roomId: number, roomName: string, deviceName: string) => { + const roomIdKey = `room_${roomId}`; + const currentStatus = roomDeviceStatus[roomIdKey]?.[deviceName] || false; const actionType = currentStatus ? "off" : "on"; if (currentStatus) { - if (!window.confirm(`Apakah Anda yakin ingin mematikan ${deviceName}?`)) return; + const confirmMsg = `Apakah Anda yakin ingin mematikan ${deviceName} di ${roomName}?`; + if (!window.confirm(confirmMsg)) return; } + // Jika yang ditekan bukan D101, ubah UI saja (simulasi) + if (roomName !== "Kelas D101") { + setRoomDeviceStatus(prev => ({ ...prev, [roomIdKey]: { ...prev[roomIdKey], [deviceName]: !currentStatus } })); + return; + } + + // Logika khusus D101 (Tembak ke Golang) let backendDevice = ""; if (deviceName === 'AC') backendDevice = "ac"; else if (deviceName === 'Proyektor') backendDevice = "projector"; else if (deviceName === 'Lampu 1') backendDevice = "lampu1"; else if (deviceName === 'Lampu 2') backendDevice = "lampu2"; - // Optimistic UI (Berubah cepat di layar) - setDeviceStatus(prev => ({ ...prev, [key]: !currentStatus })); + setRoomDeviceStatus(prev => ({ ...prev, [roomIdKey]: { ...prev[roomIdKey], [deviceName]: !currentStatus } })); try { const response = await fetch("http://172.17.172.17:8080/api/hardware/control", { @@ -109,167 +138,156 @@ export default function PowerMonitoringPage() { }); if (!response.ok) { - setDeviceStatus(prev => ({ ...prev, [key]: currentStatus })); - const errorData = await response.json(); - alert(`GAGAL: ${errorData.error || "Server menolak perintah"}`); + setRoomDeviceStatus(prev => ({ ...prev, [roomIdKey]: { ...prev[roomIdKey], [deviceName]: currentStatus } })); + alert("Gagal mengontrol perangkat."); } } catch (error) { - console.error("Error API:", error); - setDeviceStatus(prev => ({ ...prev, [key]: currentStatus })); + setRoomDeviceStatus(prev => ({ ...prev, [roomIdKey]: { ...prev[roomIdKey], [deviceName]: currentStatus } })); alert("GAGAL: Tidak dapat terhubung ke Server Golang."); } }; - // FUNGSI CUT OFF 3 MCB SEKALIGUS - const handleCutOff = async () => { - if (!window.confirm(`PERINGATAN FATAL: Anda yakin ingin memutus 3 MCB Daya di Kelas D101 secara paksa?`)) return; - - try { - const response = await fetch("http://172.17.172.17:8080/api/cutoff", { - method: "POST", - }); - - if (response.ok) { - alert(`Daya di Kelas D101 berhasil diputus!`); - setIsRelayOn(false); - fetchPowerStatus(); // Paksa update layar agar jadi 0 Watt - fetchDeviceStatus(); // Paksa update icon perangkat mati - } else { - alert("Gagal memutus daya. Cek koneksi server."); + // FUNGSI CUT OFF DAYA KHUSUS D101 + const handleCutOff = async (roomName: string, roomId: number) => { + if (roomName === "Kelas D101") { + if (!window.confirm(`PERINGATAN FATAL: Anda yakin ingin memutus 3 MCB Daya di ${roomName} secara paksa?`)) return; + + try { + const response = await fetch("http://172.17.172.17:8080/api/cutoff", { method: "POST" }); + if (response.ok) { + alert(`Daya di ${roomName} berhasil diputus!`); + setRooms(prev => prev.map(r => r.name === roomName ? { ...r, isRelayOn: false, power: 0 } : r)); + fetchPowerStatus(); + } else { + alert("Gagal memutus daya. Cek koneksi server."); + } + } catch (error) { + alert("Terjadi kesalahan jaringan saat memutus daya."); + } + } else { + // Dummy untuk kelas lain + if (window.confirm(`Simulasi mematikan daya di ${roomName}?`)) { + setRooms(prev => prev.map(r => r.name === roomName ? { ...r, isRelayOn: false, power: 0 } : r)); } - } catch (error) { - console.error(error); - alert("Terjadi kesalahan jaringan saat memutus daya."); } }; - // Hitung total daya - const totalPower = powerData.umum + powerData.ac1 + powerData.ac2; - // ========================================================================= // 5. TAMPILAN UI (RENDER) // ========================================================================= return ( -
- {/* HEADER SECTION */} -
-
-
- -
-
-

S-CLASS Power Monitoring

-

Pantau beban 3 MCB utama & kendalikan perangkat Kelas D101.

-
+
+
+
+
- - -
- - {/* TOTAL POWER SUMMARY */} -
-
-
- -
-
-

Total Konsumsi Daya (D101)

-

Update terakhir: {lastUpdate}

-
-
-
- 3000 ? 'text-red-400' : 'text-yellow-400'}`}> - {totalPower.toFixed(1)} - - Watts +
+

Power Monitoring & Control

+

Pantau konsumsi daya kWh meter dan kendalikan relay sirkuit ruangan.

- {/* 3 MCB MONITORING CARDS */} -
- {/* MCB UMUM */} -
-
MCB 1
-

Jalur Umum

-

Lampu, Proyektor & Stop Kontak

-
- {powerData.umum.toFixed(1)} - W -
-
- - {/* MCB AC 1 */} -
-
MCB 2
-

Pendingin 1

-

Air Conditioner Unit 1

-
- {powerData.ac1.toFixed(1)} - W -
-
- - {/* MCB AC 2 */} -
-
MCB 3
-

Pendingin 2

-

Air Conditioner Unit 2

-
- {powerData.ac2.toFixed(1)} - W -
-
-
- - {totalPower > 3500 && ( -
- PERINGATAN: Beban Kelas D101 melebihi batas aman (3500W)! -
- )} - - {/* IOT DEVICE CONTROLS */} -
-

Kontrol Perangkat Kelas D101

-
+
+ {[...rooms].sort((a, b) => a.name.localeCompare(b.name)).map((room) => { - + const roomIdKey = `room_${room.id}`; + const currentRoomStatus = roomDeviceStatus[roomIdKey] || {}; - + return ( +
+ +
+ {room.isRelayOn ? 'Sirkuit Aktif' : 'Sirkuit Terputus'} +
- +

{room.name}

+ {room.name === "Kelas D101" &&

(Live Connected API)

} + {room.name !== "Kelas D101" &&

(Simulasi / Offline)

} + +
+ 3000 ? 'text-red-500' : room.power > 1000 ? 'text-orange-500' : 'text-gray-800'}`}> + {typeof room.power === 'number' ? room.power.toFixed(1) : room.power} + + Watts +
- + {/* RINCIAN KHUSUS D101 */} + {room.name === "Kelas D101" && ( +
+ Umum: {powerDataD101.umum.toFixed(0)}W + AC1: {powerDataD101.ac1.toFixed(0)}W + AC2: {powerDataD101.ac2.toFixed(0)}W +
+ )} + {room.name !== "Kelas D101" &&
} -
+ {room.power > 3000 && ( +
+ Batas Daya Terlampaui! +
+ )} + +
+

IoT Device Control

+
+ + + + + + + + + +
+
+ +
+ Update: {room.lastUpdate} + +
+
+ ); + })}
);