import React, { useRef, useState, useEffect } from 'react'; import styles from './Dashboard.module.css'; import { useNavigate } from 'react-router-dom'; import Modal from './Modal'; import Conversations from './Conversations'; import DiscussedTopics from './DiscussedTopics'; import Chart from 'chart.js/auto'; import NotificationPrompt from './NotificationPrompt'; const Dashboard = () => { const [isMenuOpen, setIsMenuOpen] = useState(false); const chartRef = useRef(null); const chartInstanceRef = useRef(null); const [conversations, setConversations] = useState([]); const [discussedTopics, setDiscussedTopics] = useState([]); const [modalContent, setModalContent] = useState(null); const [rawData, setRawData] = useState([]); const [loading, setLoading] = useState(true); // ⬅️ Tambahkan state loading const [checkOnce, setCheckOnce] = useState(false); // ⬅️ Tambahkan state loading const [stats, setStats] = useState({ totalChats: 0, userMessages: 0, botMessages: 0, }); const [isDragging, setIsDragging] = useState(false); const [selectedFile, setSelectedFile] = useState(null); const [isLoggedIn, setIsLoggedIn] = useState(false); const navigate = useNavigate(); const handleFile = (file) => { if (file) { setSelectedFile(file); } }; useEffect(() => { const token = localStorage.getItem('token'); setIsLoggedIn(!!token); }, []); const handleLogout = () => { localStorage.removeItem('token'); localStorage.removeItem('user'); navigator.serviceWorker.ready.then(function (registration) { registration.pushManager.getSubscription().then(function (subscription) { if (subscription) { subscription.unsubscribe().then(function (successful) { console.log('Push subscription unsubscribed on logout:', successful); // Optional: also notify backend to clear the token fetch('/api/clear-subscription', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ endpoint: subscription.endpoint }), }); }); } }); }); window.location.reload(); }; const menuRef = useRef(null); // Close dropdown if click outside useEffect(() => { const handleClickOutside = (event) => { if (menuRef.current && !menuRef.current.contains(event.target)) { setIsMenuOpen(false); } }; document.addEventListener('mousedown', handleClickOutside); return () => { document.removeEventListener('mousedown', handleClickOutside); }; }, []); useEffect(() => { const fetchData = async () => { const token = localStorage.getItem('token'); try { const response = await fetch('https://bot.kediritechnopark.com/webhook/profile', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, }); if (response.status === 401 || response.status === 403) { handleLogout(); return; } if (!response.ok) { throw new Error('Fetch gagal dengan status: ' + response.status); } const data = await response.json(); console.log(data); setDiscussedTopics(data?.result?.topics) const graphObj = data.result.graph; const rawDataArray = Object.entries(graphObj).map(([hour, sesi]) => ({ hour, sesi, })); setRawData(rawDataArray); let totalSessions = new Set(); let userMessages = 0; let botMessages = 0; rawDataArray.forEach(({ sesi }) => { Object.values(sesi).forEach(messages => { messages.forEach(msg => { totalSessions.add(msg.session_id); if (msg.message.type === 'human') userMessages++; if (msg.message.type === 'ai') botMessages++; }); }); }); setStats({ totalChats: totalSessions.size, userMessages, botMessages, }); setLoading(false); // ⬅️ Setelah berhasil, hilangkan loading } catch (error) { console.error('Error:', error); navigate('/login'); } }; if (!checkOnce && 'serviceWorker' in navigator) { navigator.serviceWorker.ready.then(function (registration) { registration.pushManager.getSubscription().then(function (subscription) { setCheckOnce(false); if (subscription === null) { // Not subscribed yet — show modal asking user to subscribe setModalContent(); } else { // Already subscribed setModalContent('') console.log('User is already subscribed.'); } }); }); } fetchData(); // Jalankan langsung saat komponen di-mount const interval = setInterval(fetchData, 30000); // Jalankan setiap 30 detik return () => clearInterval(interval); // Bersihkan interval saat komponen unmount }, [navigate]); const subscribeUser = async () => { const registration = await navigator.serviceWorker.register('/sw.js', { scope: '/', }); const subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: urlBase64ToUint8Array('BPT-ypQB0Z7HndmeFhRR7AMjDujCLSbOQ21VoVHLQg9MOfWhEZ7SKH5cMjLqkXHl2sTuxdY2rjHDOAxhRK2G2K4'), }); const token = localStorage.getItem('token'); await fetch('https://bot.kediritechnopark.com/webhook/subscribe', { method: 'POST', body: JSON.stringify({ subscription, // ← push subscription object }), headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, }); setModalContent('') }; function urlBase64ToUint8Array(base64String) { const padding = '='.repeat((4 - base64String.length % 4) % 4); const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/'); const rawData = atob(base64); return Uint8Array.from([...rawData].map(char => char.charCodeAt(0))); } const openConversationsModal = () => { setModalContent(); }; const openTopicsModal = () => { setModalContent(); }; useEffect(() => { if (!rawData.length) return; const ctx = chartRef.current?.getContext('2d'); if (!ctx) return; if (chartInstanceRef.current) { chartInstanceRef.current.destroy(); } const prefixLabelMap = { WEB: 'Web App', TGG: 'Telegram', WGG: 'Whatsapp', }; const prefixColors = { WEB: { border: '#4285F4', background: 'rgba(66, 133, 244, 0.2)' }, TGG: { border: '#25D366', background: 'rgba(37, 211, 102, 0.2)' }, WGG: { border: '#AA00FF', background: 'rgba(170, 0, 255, 0.2)' }, }; const prefixes = Object.keys(prefixLabelMap); const hours = rawData.map(d => d.hour.split(' ')[1]).sort((a, b) => { // Sort berdasarkan jam dan menit const [h1, m1] = a.split(':').map(Number); const [h2, m2] = b.split(':').map(Number); return h1 !== h2 ? h1 - h2 : m1 - m2; }); const counts = {}; prefixes.forEach(prefix => { counts[prefix] = hours.map(() => 0); }); rawData.forEach(({ sesi }, index) => { prefixes.forEach(prefix => { if (Array.isArray(sesi[prefix])) { counts[prefix][index] = sesi[prefix].length; } }); }); const datasets = prefixes.map(prefix => ({ label: prefixLabelMap[prefix], data: counts[prefix], borderColor: prefixColors[prefix].border, backgroundColor: prefixColors[prefix].background, fill: true, tension: 0.3, })); chartInstanceRef.current = new Chart(ctx, { type: 'line', data: { labels: hours, datasets, }, options: { responsive: true, plugins: { legend: { display: true, position: 'bottom', }, }, scales: { y: { beginAtZero: true, title: { display: true, text: 'Jumlah Pesan', }, }, x: { title: { display: true, text: 'Jam', }, }, }, }, }); }, [rawData]); // ⬇️ Jika masih loading, tampilkan full white screen if (loading) { return
; } return (
{isLoggedIn ? (
{/* ✅ Pindahkan ref ke sini */} {isMenuOpen && (
)}
) : ( Login )} Bot Avatar

Dermalounge AI Admin Dashboard

{stats.totalChats}

Total Percakapan selama 24 jam

{stats.userMessages}

Pesan dari Pengguna

{stats.botMessages}

Respons Bot

{discussedTopics[0]?.topic}

Paling sering ditanyakan

Grafik Interaksi

Update data

selectedFile ? null : document.getElementById("fileInput").click()} onDragOver={(e) => { e.preventDefault(); setIsDragging(true); }} onDragLeave={() => setIsDragging(false)} onDrop={(e) => { e.preventDefault(); setIsDragging(false); const file = e.dataTransfer.files[0]; handleFile(file); }} >

Seret file ke sini, atau Klik untuk unggah

Klik untuk unggah

{selectedFile && ( <>
{selectedFile.name}
setSelectedFile(null)}> X
)} { const file = e.target.files[0]; handleFile(file); }} />
© 2025 Kediri Technopark
{modalContent && setModalContent(null)}>{modalContent}}
); }; export default Dashboard;