diff --git a/src/App.js b/src/App.js index 95c95a7..57130c9 100644 --- a/src/App.js +++ b/src/App.js @@ -6,6 +6,7 @@ import TenantDashboard from './TenantDashboard'; import ChatBot from './ChatBot'; import './App.css'; +import Login from './Login'; function App() { function ChatBotWrapper() { @@ -41,9 +42,9 @@ function App() {
- } /> - } /> - } /> + } /> + } /> + } />
diff --git a/src/Dashboard.js b/src/Dashboard.js index 2173920..9f6484e 100644 --- a/src/Dashboard.js +++ b/src/Dashboard.js @@ -1,114 +1,34 @@ -import React, { useEffect, useRef, useState } from 'react'; +import React, { useRef, useState, useEffect } from 'react'; import styles from './Dashboard.module.css'; -import { Chart, LineElement, PointElement, LineController, CategoryScale, LinearScale, Title, Tooltip, Legend } from 'chart.js'; import Modal from './Modal'; import Conversations from './Conversations'; import DiscussedTopics from './DiscussedTopics'; -Chart.register(LineElement, PointElement, LineController, CategoryScale, LinearScale, Title, Tooltip, Legend); +import Chart from 'chart.js/auto'; const Dashboard = () => { 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 [stats, setStats] = useState({ + const stats = { totalChats: 0, userMessages: 0, botMessages: 0, activeNow: 0, - mostDiscussedTopics: '', - }); - - const [conversations, setConversations] = useState([]); - const [discussedTopics, setDiscussedTopics] = useState([]); - const [modalContent, setModalContent] = useState(null); + mostDiscussedTopics: '-', + }; useEffect(() => { async function fetchStats() { try { - const response = await fetch('https://n8n.kediritechnopark.my.id/webhook/master-agent/dashboard'); + const response = await fetch('https://botdev.kediritechnopark.com/webhook/master-agent/dashboard'); const data = await response.json(); - - if (data.length === 0) return; - - const parsedResult = JSON.parse(data[0].result); - const conversationsData = parsedResult.conversations || []; - const discussedTopicsData = parsedResult.discussed_topics || []; - - setConversations(conversationsData); - setDiscussedTopics(discussedTopicsData); - - const totalChats = conversationsData.length; - let userMessages = 0; - let botMessages = 0; - - conversationsData.forEach(conv => { - conv.messages.forEach(msg => { - if (msg.sender === 'user') userMessages++; - if (msg.sender === 'bot') botMessages++; - }); - }); - - const activeNow = 5; // Contoh dummy - discussedTopicsData.sort((a, b) => b.question_count - a.question_count); - const mostDiscussedTopics = discussedTopicsData[0]?.topic || '-'; - - setStats({ totalChats, userMessages, botMessages, activeNow, mostDiscussedTopics }); - - const labels = ["08:00", "10:00", "12:00", "14:00", "16:00", "18:00", "20:00", "22:00", "24:00", "02:00", "04:00", "06:00"]; - const hourMap = { 8: 0, 10: 1, 12: 2, 14: 3, 16: 4, 18: 5, 20: 6, 22: 7, 0: 8, 2: 9, 4: 10, 6: 11 }; - const dataChart = new Array(labels.length).fill(0); - - // Hitung berdasarkan jam dari conversation.createdAt - console.log(conversationsData) - conversationsData.forEach(conv => { - console.log(conv) - if (conv.createdAt) { - const date = new Date(conv.createdAt.replace(' ', 'T')); - const hour = date.getHours(); - if (hourMap.hasOwnProperty(hour)) { - const idx = hourMap[hour]; - dataChart[idx]++; - } - } - }); - - - const ctx = chartRef.current.getContext("2d"); - if (chartInstanceRef.current) { - chartInstanceRef.current.destroy(); - } - - chartInstanceRef.current = new Chart(ctx, { - type: 'line', - data: { - labels, - datasets: [{ - label: "Pesan Masuk per Jam", - data: dataChart, - borderColor: "#075e54", - backgroundColor: "rgba(7, 94, 84, 0.2)", - fill: true, - tension: 0.3, - }] - }, - options: { - responsive: true, - plugins: { - legend: { - display: true, - position: 'bottom' - } - }, - scales: { - y: { - beginAtZero: true - } - } - } - }); - + setRawData(data) } catch (error) { console.error('Failed to fetch dashboard data:', error); } @@ -125,12 +45,83 @@ const Dashboard = () => { setModalContent(); }; + + useEffect(() => { + const ctx = chartRef.current?.getContext('2d'); + if (!ctx) return; + + // Cleanup old chart if it exists + if (chartInstanceRef.current) { + chartInstanceRef.current.destroy(); + } + + const prefixes = ['WEB', 'WAP', 'DME']; + const prefixLabelMap = { + WEB: 'Web App', + WAP: 'WhatsApp', + DME: 'Direct Message', + }; + const prefixColors = { + WEB: { border: '#4285F4', background: 'rgba(66, 133, 244, 0.2)' }, + WAP: { border: '#25D366', background: 'rgba(37, 211, 102, 0.2)' }, + DME: { border: '#AA00FF', background: 'rgba(170, 0, 255, 0.2)' }, + }; + + const hours = [...new Set(rawData.map(d => d.hour_group))].sort(); + + // Initialize zero-filled data structure + const counts = { + WEB: hours.map(() => 0), + WAP: hours.map(() => 0), + DME: hours.map(() => 0) + }; + + rawData.forEach(({ hour_group, session_prefix, session_ids }) => { + const hourIndex = hours.indexOf(hour_group); + if (counts[session_prefix] && hourIndex !== -1) { + counts[session_prefix][hourIndex] += session_ids.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 + } + } + } + }); + + }, [rawData]); + return (
Bot Avatar
-

Dermalounge AI Admin Dashboard

+

Dermalounge AI Admin Dashboard

Statistik penggunaan chatbot secara real-time

@@ -155,14 +146,15 @@ const Dashboard = () => {
-

Grafik Interaksi (Simulasi)

+

Grafik Interaksi

-
- UNTUK MENAMBAHKAN LAYANAN, KUNJUNGI LINK INI - dengan username: dermalounge, password: 1234 +
+

Grafik request booking

+
+
© 2025 Kloowear AI - Admin Panel
diff --git a/src/Dashboard.module.css b/src/Dashboard.module.css index 92f45c7..6fb7ebf 100644 --- a/src/Dashboard.module.css +++ b/src/Dashboard.module.css @@ -1,3 +1,8 @@ + +.h1 { + color: white; +} + .dashboardContainer { max-width: 900px; margin: 30px auto; diff --git a/src/Login.js b/src/Login.js new file mode 100644 index 0000000..b68cbf8 --- /dev/null +++ b/src/Login.js @@ -0,0 +1,62 @@ +import React, { useState } from 'react'; +import styles from './Login.module.css'; + +const Login = () => { + const [formData, setFormData] = useState({ + username: '', + password: '' + }); + + const [error, setError] = useState(''); + + const handleChange = (e) => { + setFormData({ ...formData, [e.target.name]: e.target.value }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + + if (formData.username === 'dermalounge' && formData.password === '1234') { + window.location.href = '/dashboard'; // redirect after successful login + } else { + setError('Username atau password salah'); + } + }; + + return ( +
+
+ Logo +

Dermalounge AI Admin Login

+

Silakan masuk untuk melanjutkan ke dashboard

+
+ + + {error &&

{error}

} + +
+
+ © 2025 Kloowear AI - Admin Panel +
+
+
+ ); +}; + +export default Login; diff --git a/src/Login.module.css b/src/Login.module.css new file mode 100644 index 0000000..0b82780 --- /dev/null +++ b/src/Login.module.css @@ -0,0 +1,73 @@ +.loginContainer { + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + background: #f0f2f5; + width: 100vw; +} + +.loginBox { + background: #fff; + padding: 40px; + border-radius: 10px; + box-shadow: 0 4px 25px rgba(0, 0, 0, 0.1); + text-align: center; + width: 100%; + max-width: 400px; +} + +.logo { + width: 80px; + margin-bottom: 20px; +} + +.h1 { + margin-bottom: 10px; + font-size: 24px; + color: #333; +} + +.subtitle { + font-size: 14px; + color: #777; + margin-bottom: 30px; +} + +.form { + display: flex; + flex-direction: column; +} + +.input { + padding: 10px 15px; + margin-bottom: 15px; + border: 1px solid #ccc; + border-radius: 6px; + font-size: 16px; +} + +.button { + background-color: #4285F4; + color: white; + border: none; + padding: 12px; + border-radius: 6px; + font-size: 16px; + cursor: pointer; +} + +.button:hover { + background-color: #3367D6; +} + +.error { + color: red; + margin-bottom: 10px; +} + +.footer { + margin-top: 20px; + font-size: 12px; + color: #aaa; +}