import React, { useEffect, useState, memo } from 'react'; import { View, Text, ScrollView, Pressable, StatusBar, ActivityIndicator, RefreshControl, Alert, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { useNavigation } from '@react-navigation/native'; import { NativeStackNavigationProp } from '@react-navigation/native-stack'; import styles from './styles'; import Navbar from '../../Components/Navbar'; import api, { clearToken } from '../../api/api'; import { RootStackParamList } from '../../Constants/RouteParamsList.contants'; import RouteName from '../../Constants/RouteName.constants'; interface IMonitoring { id: number; alat_id: number; nama_alat: string; status: string; waktu_aktif: string | null; waktu_nonaktif: string | null; durasi_menit: number; sumber: string; } interface IAlat { id: number; nama_alat: string; status: string; } interface ISensor { id: number; jarak_cm: number; status: string; waktu: string; } interface IRPM { id: number; alat_id: number; rpm: number; status: string; waktu: string; } interface IRealtimePress { status: string; // 'ON' | 'OFF' phase: string; // 'TURUN'|'TAHAN'|'NAIK'|'MUNDUR'|'IDLE' sisa: number; updatedAt: string | null; } interface IRealtimeGiling { status: string; // 'ON' | 'OFF' rpm: number; sisa: number; updatedAt: string | null; } interface IRealtimeSensor { jarak_cm: number; status: string; updatedAt: string | null; } interface IRealtimeState { press: IRealtimePress; giling: IRealtimeGiling; estop: { aktif: boolean; updatedAt: string | null }; device: { status: string; updatedAt: string | null }; sensor: IRealtimeSensor; } type DashboardNavProp = NativeStackNavigationProp; // Komponen terisolasi untuk timer durasi โ€” hanya komponen ini yang re-render setiap detik, // bukan seluruh Dashboard. interface LiveTimerProps { waktuAktif: string; pad: (n: number) => string; timerStyle: any; unitStyle: any; } const LiveTimer = memo(({ waktuAktif, pad, timerStyle, unitStyle }: LiveTimerProps) => { const [now, setNow] = useState(Date.now()); useEffect(() => { const id = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(id); }, []); const totalMenit = Math.floor((now - new Date(waktuAktif).getTime()) / 60000); return ( {pad(Math.floor(totalMenit / 60))} jam {pad(totalMenit % 60)} mnt ); }); const Dashboard = () => { const navigation = useNavigation(); const [alatList, setAlatList] = useState([]); const [monitoring, setMonitoring] = useState([]); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [namaUser, setNamaUser] = useState(''); const [sensor, setSensor] = useState(null); const [rpmMap, setRpmMap] = useState>({}); const [realtime, setRealtime] = useState(null); useEffect(() => { const loadUser = async () => { const savedUser = await AsyncStorage.getItem('user'); if (savedUser) { const user = JSON.parse(savedUser); setNamaUser(user.nama || ''); } }; loadUser(); }, []); const fetchData = async () => { try { // 1 request ke backend menggantikan 5 request terpisah const [dashRes, realtimeRes] = await Promise.all([ api.get('/device/dashboard'), api.get('/sensor/realtime'), ]); if (dashRes.data.success) { const { alat, monitoring, rpmMap: rpm, sensor: sensorData } = dashRes.data.data; setAlatList(alat); setMonitoring(monitoring); setRpmMap(rpm); setSensor(sensorData); } if (realtimeRes.data.success) setRealtime(realtimeRes.data.data); } catch (err) { console.log('Dashboard fetch error:', err); } finally { setLoading(false); setRefreshing(false); } }; // Fetch sensor HC-SR04 dari realtime state (polling lebih sering untuk update cepat) const fetchSensor = async () => { try { const res = await api.get('/sensor/realtime'); if (res.data.success) setRealtime(res.data.data); } catch (_) {} }; useEffect(() => { fetchData(); const fetchInterval = setInterval(() => { fetchData(); }, 10000); const sensorInterval = setInterval(() => { fetchSensor(); }, 1000); return () => { clearInterval(fetchInterval); clearInterval(sensorInterval); }; }, []); const handleLogout = () => { Alert.alert('Logout', 'Yakin ingin keluar?', [ { text: 'Batal', style: 'cancel' }, { text: 'Keluar', style: 'destructive', onPress: async () => { await AsyncStorage.removeItem('token'); await AsyncStorage.removeItem('user'); clearToken(); navigation.reset({ index: 0, routes: [{ name: RouteName.LoginNavigation }], }); }, }, ]); }; const pad = (n: number) => String(n).padStart(2, '0'); const formatWaktu = (waktu: string) => { if (!waktu) return '-'; const d = new Date(waktu); return `${pad(d.getHours())}:${pad(d.getMinutes())}`; }; const formatDurasi = (menit: number) => { const j = Math.floor(menit / 60); const m = menit % 60; if (j > 0) return `${j}j ${pad(m)}m`; return `${m} menit`; }; const hitungTotalMenit = (alat_id: number): number => { const dataAlat = monitoring.filter(m => m.alat_id === alat_id); const alat = alatList.find(a => a.id === alat_id); const isPengepres = alat?.nama_alat.toLowerCase().includes('pres') ?? false; const rtAlat = isPengepres ? realtime?.press : realtime?.giling; const mesinNyala = rtAlat?.status === 'ON'; if (!mesinNyala) return 0; const sesiAktif = dataAlat .filter(m => m.status === 'aktif' && m.waktu_aktif) .sort((a, b) => new Date(b.waktu_aktif!).getTime() - new Date(a.waktu_aktif!).getTime())[0]; if (!sesiAktif) return 0; // Kembalikan waktuAktif string supaya LiveTimer yang hitung detiknya return new Date(sesiAktif.waktu_aktif!).getTime(); }; // Hitung total menit statis (tidak real-time) โ€” untuk progress bar & stats const hitungTotalMenitStatis = (alat_id: number): number => { const dataAlat = monitoring.filter(m => m.alat_id === alat_id); const alat = alatList.find(a => a.id === alat_id); const isPengepres = alat?.nama_alat.toLowerCase().includes('pres') ?? false; const rtAlat = isPengepres ? realtime?.press : realtime?.giling; const mesinNyala = rtAlat?.status === 'ON'; if (!mesinNyala) return 0; const sesiAktif = dataAlat .filter(m => m.status === 'aktif' && m.waktu_aktif) .sort((a, b) => new Date(b.waktu_aktif!).getTime() - new Date(a.waktu_aktif!).getTime())[0]; if (!sesiAktif) return 0; return Math.floor((Date.now() - new Date(sesiAktif.waktu_aktif!).getTime()) / 60000); }; const getWaktuAktif = (alat_id: number): string | null => { const dataAlat = monitoring.filter(m => m.alat_id === alat_id); const alat = alatList.find(a => a.id === alat_id); const isPengepres = alat?.nama_alat.toLowerCase().includes('pres') ?? false; const rtAlat = isPengepres ? realtime?.press : realtime?.giling; if (rtAlat?.status !== 'ON') return null; const sesiAktif = dataAlat .filter(m => m.status === 'aktif' && m.waktu_aktif) .sort((a, b) => new Date(b.waktu_aktif!).getTime() - new Date(a.waktu_aktif!).getTime())[0]; return sesiAktif?.waktu_aktif ?? null; }; const sensorColor = (status: string) => { if (status === 'peringatan') return '#ef9f27'; if (status === 'bahaya') return '#e24b4a'; return '#1d9e75'; }; const sensorBgColor = (status: string) => { if (status === 'peringatan') return '#2a1f10'; if (status === 'bahaya') return '#2a1010'; return '#1a2a22'; }; const sensorLabel = (status: string) => { if (status === 'peringatan') return 'Peringatan'; if (status === 'bahaya') return 'Bahaya'; return 'Normal'; }; const rpmColor = (status: string) => { if (status === 'peringatan') return '#ef9f27'; if (status === 'bahaya') return '#e24b4a'; return '#ffffff'; }; if (loading) { return ( EdaSmart {namaUser ? `Halo, ${namaUser} ๐Ÿ‘‹` : 'Sistem Manajemen Edamame'} ); } return ( {/* Topbar */} EdaSmart {namaUser ? `Halo, ${namaUser} ๐Ÿ‘‹` : 'Sistem Manajemen Edamame'} {/* Status device dari MQTT realtime */} {realtime?.device.status === 'ONLINE' ? 'Online' : 'Offline'} {/* Badge E-Stop jika aktif */} {realtime?.estop.aktif && ( ๐Ÿšจ E-STOP AKTIF )} Logout { setRefreshing(true); fetchData(); }} tintColor="#1d9e75" /> }> {/* Status Alat */} STATUS ALAT {alatList.length === 0 && ( Belum ada alat )} {alatList.map((alat, index) => { const isPengepres = alat.nama_alat.toLowerCase().includes('pres'); const rtAlat = isPengepres ? realtime?.press : realtime?.giling; const alatNyala = rtAlat?.status === 'ON'; return ( {isPengepres ? '๐Ÿ”ง' : 'โš™๏ธ'} {alat.nama_alat} {alatNyala ? 'โ— Sedang berjalan' : 'โ— Mati'} {alatNyala ? 'Aktif' : 'Nonaktif'} {index < alatList.length - 1 && ( )} ); })} {/* Monitoring Durasi */} MONITORING DURASI {alatList.length === 0 && ( Belum ada data monitoring )} {alatList.map(alat => { const dataAlat = monitoring.filter(m => m.alat_id === alat.id); const totalMenit = hitungTotalMenitStatis(alat.id); const waktuAktif = getWaktuAktif(alat.id); const sesiHariIni = dataAlat.length; const progress = Math.min((totalMenit / 480) * 100, 100); const rpmData = rpmMap[alat.id]; const isPengepres = alat.nama_alat.toLowerCase().includes('pres'); // Data realtime dari MQTT โ€” jadikan sumber kebenaran status const rt = isPengepres ? realtime?.press : realtime?.giling; const mesinNyala = rt?.status === 'ON'; // pakai MQTT, bukan DB const rtRpm = realtime?.giling.rpm; const rtPhase = realtime?.press.phase; const rtSisa = rt?.sisa ?? 0; return ( {isPengepres ? '๐Ÿ”ง' : 'โš™๏ธ'} {alat.nama_alat} {mesinNyala ? 'Aktif' : 'Tidak aktif'} {/* Banner realtime โ€” hanya tampil saat mesin benar-benar ON */} {isPengepres && mesinNyala && rtPhase && rtPhase !== 'IDLE' && ( โšก Fase: {rtPhase} {rtSisa > 0 && ( ยท Sisa {rtSisa}s )} )} {!isPengepres && mesinNyala && ( โšก RPM: {rtRpm ?? 'โ€”'} {rtSisa > 0 && ( ยท Sisa {rtSisa}s )} )} Total durasi hari ini {waktuAktif && mesinNyala ? ( ) : ( 00 jam 00 mnt )} Sesi hari ini {sesiHariIni}x Total durasi {formatDurasi(totalMenit)} {/* RPM hanya tampil untuk alat penggiling (bukan pengepres) */} {!isPengepres && ( Kecepatan {rpmData ? `${rpmData.rpm} RPM` : 'โ€” RPM'} )} Status {alat.status === 'aktif' ? '๐ŸŸข On' : 'โšซ Off'} ); })} {/* Sensor Jarak HC-SR04 */} SENSOR JARAK HC-SR04 {(() => { // Prioritas: realtime.sensor (MQTT langsung) โ†’ sensor (DB fallback) const rtSensor = realtime?.sensor; const hasRt = rtSensor && rtSensor.updatedAt !== null; const jarakCm = hasRt ? rtSensor!.jarak_cm : sensor?.jarak_cm ?? null; const status = hasRt ? rtSensor!.status : sensor?.status ?? 'normal'; const waktu = hasRt ? rtSensor!.updatedAt : sensor?.waktu ?? null; if (jarakCm === null) return ( Belum ada data sensor ); return ( <> Jarak terdeteksi {sensorLabel(status)} {jarakCm} cm Diperbarui: {waktu ? formatWaktu(waktu) : '-'} ); })()} ); }; export default Dashboard;