From 6afcbd8847f3816aad5ab756f240ae4cf05309fe Mon Sep 17 00:00:00 2001 From: everythingonblack Date: Wed, 28 May 2025 20:29:10 +0700 Subject: [PATCH] ok --- package-lock.json | 106 +++++++++++++++++++++++++++ package.json | 3 + src/App.css | 5 +- src/App.js | 60 +++++++++++----- src/ChatBot.js | 152 +++++++++++++++++++++++++++++++++++++++ src/ChatBot.module.css | 134 ++++++++++++++++++++++++++++++++++ src/Dashboard.js | 91 +++++++++++++++++++++++ src/Dashboard.module.css | 64 +++++++++++++++++ src/TenantDashboard.js | 91 +++++++++++++++++++++++ 9 files changed, 689 insertions(+), 17 deletions(-) create mode 100644 src/ChatBot.js create mode 100644 src/ChatBot.module.css create mode 100644 src/Dashboard.js create mode 100644 src/Dashboard.module.css create mode 100644 src/TenantDashboard.js diff --git a/package-lock.json b/package-lock.json index 534e028..33602d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,8 +12,11 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.9.0", + "chart.js": "^4.4.9", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-router-dom": "^7.6.1", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" } @@ -2957,6 +2960,12 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@kurkle/color": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", + "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==", + "license": "MIT" + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -4894,6 +4903,32 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -5528,6 +5563,18 @@ "node": ">=10" } }, + "node_modules/chart.js": { + "version": "4.4.9", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz", + "integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==", + "license": "MIT", + "dependencies": { + "@kurkle/color": "^0.3.0" + }, + "engines": { + "pnpm": ">=8" + } + }, "node_modules/check-types": { "version": "11.2.3", "resolved": "https://registry.npmjs.org/check-types/-/check-types-11.2.3.tgz", @@ -13626,6 +13673,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -13917,6 +13970,53 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.1.tgz", + "integrity": "sha512-hPJXXxHJZEsPFNVbtATH7+MMX43UDeOauz+EAU4cgqTn7ojdI9qQORqS8Z0qmDlL1TclO/6jLRYUEtbWidtdHQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.1.tgz", + "integrity": "sha512-vxU7ei//UfPYQ3iZvHuO1D/5fX3/JOqhNTbRR+WjSBWxf9bIvpWK+ftjmdfJHzPOuMQKe2fiEdG+dZX6E8uUpA==", + "license": "MIT", + "dependencies": { + "react-router": "7.6.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -14823,6 +14923,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", diff --git a/package.json b/package.json index 2f82861..38d2d30 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,11 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.9.0", + "chart.js": "^4.4.9", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-router-dom": "^7.6.1", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, diff --git a/src/App.css b/src/App.css index 74b5e05..66ad3fb 100644 --- a/src/App.css +++ b/src/App.css @@ -1,5 +1,8 @@ .App { - text-align: center; + height: 100vh; + display: flex; + align-items: end; + justify-content: center; } .App-logo { diff --git a/src/App.js b/src/App.js index 3784575..95c95a7 100644 --- a/src/App.js +++ b/src/App.js @@ -1,23 +1,51 @@ -import logo from './logo.svg'; +import React, { useState, useEffect } from 'react'; +import { BrowserRouter, Routes, Route, useParams } from 'react-router-dom'; +import axios from 'axios'; +import Dashboard from './Dashboard'; +import TenantDashboard from './TenantDashboard'; +import ChatBot from './ChatBot'; + import './App.css'; function App() { + function ChatBotWrapper() { + const { agentId } = useParams(); + const [agentDetails, setAgentDetails] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchAgent = async () => { + try { + // const response = await axios.get( + // `https://n8n.kediritechnopark.my.id/webhook/get-agent?id=${agentId}` + // ); + // if (response.data && response.data.length > 0) { + // setAgentDetails(response.data[0].data); + // } + } catch (error) { + console.error("Error fetching certificate:", error); + } finally { + setLoading(false); + } + }; + + fetchAgent(); + }, [agentId]); + + // if (loading) return
Loading...
; + // if (!agentDetails) return
No agent found
; + + return ; +} return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
+
+ + + } /> + } /> + } /> + +
); } diff --git a/src/ChatBot.js b/src/ChatBot.js new file mode 100644 index 0000000..f70f64e --- /dev/null +++ b/src/ChatBot.js @@ -0,0 +1,152 @@ +import React, { useState } from 'react'; +import styles from './ChatBot.module.css'; + +const ChatBot = () => { + const [messages, setMessages] = useState([ + { + sender: 'bot', + text: 'Halo 👋 Saya Kloowear AI! Ada yang bisa saya bantu?', + time: getTime(), + quickReplies: [ + 'Saya ingin beli gelang custom', + 'Ada katalog produk terbaru?', + 'Gelang cocok untuk hadiah?', + ], + }, + ]); + const [input, setInput] = useState(''); + const [isLoading, setIsLoading] = useState(false); + + const sendMessage = async (textOverride = null) => { + const message = textOverride || input.trim(); + if (message === '') return; + + // Show user's message immediately + const newMessages = [ + { sender: 'user', text: message, time: getTime() }, + ...messages, + ]; + setMessages(newMessages); + setInput(''); + setIsLoading(true); + + try { + // Send to backend + const response = await fetch('https://n8n.kediritechnopark.my.id/webhook/master-agent/ask' , { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ pertanyaan: newMessages }), + }); + + if (!response.ok) throw new Error('Network response was not ok'); + + const data = await response.json(); + console.log(data) + // Assuming your backend sends back something like: { answer: "text" } + // Adjust this according to your actual response shape + const botAnswer = data[0].output || 'Maaf, saya tidak mengerti.'; + + // Add bot's reply + setMessages(prev => [ + { sender: 'bot', text: botAnswer, time: getTime() }, + ...prev, + ]); + } catch (error) { + setMessages(prev => [ + { + sender: 'bot', + text: 'Maaf, terjadi kesalahan pada server. Silakan coba lagi nanti.', + time: getTime(), + }, + ...prev, + ]); + console.error('Fetch error:', error); + } finally { + setIsLoading(false); + } + }; + + return ( +
+
+ Bot Avatar + Kloowear AI Assistant +
+ +
+ + {isLoading && ( +
+ Bot +
+ Mengetik... +
+
+ )} + {messages.map((msg, index) => ( +
+ {msg.sender === 'bot' && ( + Bot + )} +
+ {msg.text} +
{msg.time}
+ {msg.quickReplies && ( +
+ {msg.quickReplies.map((reply, i) => ( +
sendMessage(reply)} + > + {reply} +
+ ))} +
+ )} +
+ {msg.sender === 'user' && ( + User + )} +
+ ))} +
+ +
+ setInput(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && sendMessage()} + disabled={isLoading} + /> + +
+
+ ); +}; + +function getTime() { + const now = new Date(); + return now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); +} + +export default ChatBot; diff --git a/src/ChatBot.module.css b/src/ChatBot.module.css new file mode 100644 index 0000000..8409aaf --- /dev/null +++ b/src/ChatBot.module.css @@ -0,0 +1,134 @@ +.chatContainer { + max-width: 500px; + background: #fff; + border-radius: 10px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); + display: flex; + flex-direction: column; + height: 71vh; + overflow: hidden; +} + +.chatHeader { + background: #075e54; + color: #fff; + padding: 15px; + display: flex; + align-items: center; + gap: 10px; + border-top-left-radius: 10px; + border-top-right-radius: 10px; +} + +.chatHeader img { + width: 45px; + height: 45px; + border-radius: 50%; +} + +.chatBody { + flex: 1; + overflow-y: auto; + padding: 15px; + background: #ece5dd; + display: flex; + flex-direction: column-reverse; +} + +.messageRow { + display: flex; + margin-bottom: 12px; + align-items: flex-end; +} + +.bot { + justify-content: flex-start; +} + +.user { + justify-content: flex-end; +} + +.message { + max-width: 70%; + padding: 12px 16px; + border-radius: 15px; + font-size: 14px; + position: relative; + line-height: 1.4; +} + +.message.bot { + background: #ffffff; + border: 1px solid #ccc; +} + +.message.user { + background: #dcf8c6; +} + +.timestamp { + font-size: 10px; + color: #555; + margin-top: 4px; + text-align: right; +} + +.avatar { + width: 36px; + height: 36px; + border-radius: 50%; + margin: 0 8px; +} + +.chatInput { + display: flex; + padding: 10px; + border-top: 1px solid #ddd; + background: #f0f0f0; +} + +.chatInput input { + flex: 1; + padding: 10px; + border-radius: 20px; + border: 1px solid #ccc; + font-size: 14px; +} + +.chatInput button { + background: #075e54; + color: #fff; + border: none; + border-radius: 20px; + padding: 0 20px; + margin-left: 10px; + cursor: pointer; +} + +.chatInput button:hover { + background: #0b7366; +} + +.quickReplies { + display: flex; + flex-wrap: wrap; + gap: 8px; + margin: 10px 0 0; +} + +.quickReply { + background: #fff; + border: 1px solid #ccc; + padding: 8px 14px; + border-radius: 20px; + font-size: 13px; + cursor: pointer; + transition: all 0.2s ease; +} + +.quickReply:hover { + background: #075e54; + color: white; + border-color: #075e54; +} diff --git a/src/Dashboard.js b/src/Dashboard.js new file mode 100644 index 0000000..48aadd7 --- /dev/null +++ b/src/Dashboard.js @@ -0,0 +1,91 @@ +import React, { useEffect, useRef } from 'react'; +import styles from './Dashboard.module.css'; +import { Chart, LineElement, PointElement, LineController, CategoryScale, LinearScale, Title, Tooltip, Legend } from 'chart.js'; + +Chart.register(LineElement, PointElement, LineController, CategoryScale, LinearScale, Title, Tooltip, Legend); + +const Dashboard = () => { + const chartRef = useRef(null); + const chartInstanceRef = useRef(null); + + useEffect(() => { + const stats = { + totalChats: 120, + userMessages: 210, + botMessages: 220, + activeNow: 5, + }; + + // Inject stats manually + document.getElementById("totalChats").textContent = stats.totalChats; + document.getElementById("userMessages").textContent = stats.userMessages; + document.getElementById("botMessages").textContent = stats.botMessages; + document.getElementById("activeNow").textContent = stats.activeNow; + + // Get canvas from ref + const ctx = chartRef.current.getContext("2d"); + + if (chartInstanceRef.current) { + chartInstanceRef.current.destroy(); + } + + chartInstanceRef.current = new Chart(ctx, { + type: 'line', + data: { + labels: ["08:00", "10:00", "12:00", "14:00", "16:00", "18:00"], + datasets: [{ + label: "Pesan Masuk", + data: [10, 25, 45, 30, 50, 60], + 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 + } + } + } + }); + }, []); + + return ( +
+
+ Bot Avatar +
+

Kloowear AI Admin Dashboard

+

Statistik penggunaan chatbot secara real-time

+
+
+ +
+

0

Total Percakapan Hari Ini

+

0

Pesan dari Pengguna

+

0

Respons Bot

+

0

Pengguna Aktif Sekarang

+
+ +
+

Grafik Interaksi (Simulasi)

+ +
+ +
+ © 2025 Kloowear AI - Admin Panel +
+
+ ); +}; + +export default Dashboard; diff --git a/src/Dashboard.module.css b/src/Dashboard.module.css new file mode 100644 index 0000000..92f45c7 --- /dev/null +++ b/src/Dashboard.module.css @@ -0,0 +1,64 @@ +.dashboardContainer { + max-width: 900px; + margin: 30px auto; + background: #fff; + border-radius: 10px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); + padding: 20px; +} + +.dashboardHeader { + display: flex; + align-items: center; + gap: 15px; + background: #075e54; + color: white; + padding: 20px; + border-radius: 10px 10px 0 0; +} + +.dashboardHeader img { + width: 50px; + height: 50px; + border-radius: 50%; +} + +.statsGrid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin-top: 20px; +} + +.statCard { + background: #ece5dd; + border-radius: 10px; + padding: 20px; + box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05); +} + +.statCard h2 { + margin: 0; + font-size: 28px; + color: #075e54; +} + +.statCard p { + margin: 5px 0 0; + font-size: 14px; +} + +.chartSection { + margin-top: 30px; +} + +.chartTitle { + color: #075e54; +} + +.footer { + text-align: center; + margin-top: 30px; + font-size: 13px; + color: #777; +} diff --git a/src/TenantDashboard.js b/src/TenantDashboard.js new file mode 100644 index 0000000..45f31d6 --- /dev/null +++ b/src/TenantDashboard.js @@ -0,0 +1,91 @@ +import React, { useEffect, useRef } from 'react'; +import styles from './Dashboard.module.css'; +import { Chart, LineElement, PointElement, LineController, CategoryScale, LinearScale, Title, Tooltip, Legend } from 'chart.js'; + +Chart.register(LineElement, PointElement, LineController, CategoryScale, LinearScale, Title, Tooltip, Legend); + +const TenantDashboard = () => { + const chartRef = useRef(null); + const chartInstanceRef = useRef(null); + + useEffect(() => { + const stats = { + totalChats: 120, + userMessages: 210, + botMessages: 220, + activeNow: 5, + }; + + // Inject stats manually + document.getElementById("totalChats").textContent = stats.totalChats; + document.getElementById("userMessages").textContent = stats.userMessages; + document.getElementById("botMessages").textContent = stats.botMessages; + document.getElementById("activeNow").textContent = stats.activeNow; + + // Get canvas from ref + const ctx = chartRef.current.getContext("2d"); + + if (chartInstanceRef.current) { + chartInstanceRef.current.destroy(); + } + + chartInstanceRef.current = new Chart(ctx, { + type: 'line', + data: { + labels: ["08:00", "10:00", "12:00", "14:00", "16:00", "18:00"], + datasets: [{ + label: "Pesan Masuk", + data: [10, 25, 45, 30, 50, 60], + 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 + } + } + } + }); + }, []); + + return ( +
+
+ Bot Avatar +
+

Kloowear AI Admin Dashboard

+

Statistik penggunaan chatbot secara real-time

+
+
+ +
+

0

Total Percakapan Hari Ini

+

0

Pesan dari Pengguna

+

0

Respons Bot

+

0

Pengguna Aktif Sekarang

+
+ +
+

Grafik Interaksi (Simulasi)

+ +
+ +
+ © 2025 Kloowear AI - Admin Panel +
+
+ ); +}; + +export default TenantDashboard;