From ab65327e59b0ea8d966a491dce89e2fdb6050828 Mon Sep 17 00:00:00 2001 From: Vassshhh Date: Thu, 12 Jun 2025 17:56:33 +0700 Subject: [PATCH] ok --- src/ChatBot.js | 2 +- src/Dashboard.js | 233 +++++++++++++++++++++++++++------------ src/Dashboard.module.css | 77 +++++++++++++ 3 files changed, 241 insertions(+), 71 deletions(-) diff --git a/src/ChatBot.js b/src/ChatBot.js index 35b7322..287f445 100644 --- a/src/ChatBot.js +++ b/src/ChatBot.js @@ -55,7 +55,7 @@ try { // Send to backend - const response = await fetch('https://botdev.kediritechnopark.com/webhook/master-agent/ask', { + const response = await fetch('https://bot.kediritechnopark.com/webhook/master-agent/ask', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ pertanyaan: message, sessionId: JSON.parse(localStorage.getItem('session')).sessionId, lastSeen: new Date().toISOString() }), diff --git a/src/Dashboard.js b/src/Dashboard.js index 9f6484e..55ec4ad 100644 --- a/src/Dashboard.js +++ b/src/Dashboard.js @@ -14,21 +14,56 @@ const Dashboard = () => { const [discussedTopics, setDiscussedTopics] = useState([]); const [modalContent, setModalContent] = useState(null); const [rawData, setRawData] = useState([]); + + const [stats, setStats] = useState({ + totalChats: 0, + userMessages: 0, + botMessages: 0, + mostDiscussedTopics: '-', +}); - const stats = { - totalChats: 0, - userMessages: 0, - botMessages: 0, - activeNow: 0, - mostDiscussedTopics: '-', + + const [isDragging, setIsDragging] = useState(false); + const [selectedFile, setSelectedFile] = useState(null); + + const handleFile = (file) => { + if (file) { + setSelectedFile(file); + } }; + useEffect(() => { async function fetchStats() { try { - const response = await fetch('https://botdev.kediritechnopark.com/webhook/master-agent/dashboard'); + const response = await fetch('https://bot.kediritechnopark.com/webhook/master-agent/dashboard'); const data = await response.json(); setRawData(data) + + setRawData(data); + + // Hitung statistik + let totalSessions = new Set(); // unik session_id + let userMessages = 0; + let botMessages = 0; + + data.forEach(({ sesi }) => { + Object.entries(sesi).forEach(([channel, 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, + mostDiscussedTopics: '-', // placeholder, bisa ditambah nanti + }); + } catch (error) { console.error('Failed to fetch dashboard data:', error); } @@ -46,75 +81,90 @@ const Dashboard = () => { }; - useEffect(() => { - const ctx = chartRef.current?.getContext('2d'); - if (!ctx) return; +useEffect(() => { + if (!rawData.length) return; - // Cleanup old chart if it exists - if (chartInstanceRef.current) { - chartInstanceRef.current.destroy(); - } + const ctx = chartRef.current?.getContext('2d'); + if (!ctx) return; - 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)' }, - }; + if (chartInstanceRef.current) { + chartInstanceRef.current.destroy(); + } - const hours = [...new Set(rawData.map(d => d.hour_group))].sort(); + const prefixLabelMap = { + WEB: 'Web App', + WAP: 'WhatsApp', + DME: 'Instagram', + }; - // Initialize zero-filled data structure - const counts = { - WEB: hours.map(() => 0), - WAP: hours.map(() => 0), - DME: hours.map(() => 0) - }; + 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)' }, + }; - 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 prefixes = Object.keys(prefixLabelMap); + + // Ambil semua grub (jam) + const hours = rawData.map(d => d.grub).sort((a, b) => parseFloat(a) - parseFloat(b)); + + // Inisialisasi data per platform + const counts = {}; + prefixes.forEach(prefix => { + counts[prefix] = hours.map(() => 0); + }); + + // Hitung jumlah pesan berdasarkan panjang array per grub & prefix + rawData.forEach((entry, index) => { + const sesi = entry.sesi || {}; + 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 - })); + 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' - } + 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]); + }, + scales: { + y: { + beginAtZero: true, + title: { + display: true, + text: 'Jumlah Pesan', + }, + }, + x: { + title: { + display: true, + text: 'Jam', + }, + }, + }, + }, + }); +}, [rawData]); return (
@@ -150,13 +200,56 @@ const Dashboard = () => {
-
-

Grafik request booking

- + +
+

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 Kloowear AI - Admin Panel + © 2025 Kediri Technopark
{modalContent && setModalContent(null)}>{modalContent}} diff --git a/src/Dashboard.module.css b/src/Dashboard.module.css index 6fb7ebf..98d6651 100644 --- a/src/Dashboard.module.css +++ b/src/Dashboard.module.css @@ -3,6 +3,26 @@ color: white; } +.uploadContainer { + margin-top: 16px; + border: 2px dashed #ccc; + border-radius: 12px; + padding: 32px; + text-align: center; + cursor: pointer; + transition: border-color 0.3s; +} + +.uploadContainer:hover { + border-color: #888; +} + +.uploadLink { + color: #007bff; + text-decoration: underline; + cursor: pointer; +} + .dashboardContainer { max-width: 900px; margin: 30px auto; @@ -67,3 +87,60 @@ font-size: 13px; color: #777; } + +.desktopText { + display: block; +} + +.mobileText { + display: none; +} + +.dragActive { + background-color: #e0f7fa; /* light blue or whatever color you prefer */ + border-color: #00acc1; /* optional: change border on drag too */ +} + +.fileInfo { + margin-top: 16px; + font-size: 14px; + color: #333; + background: #f1f1f1; + padding: 8px 12px; + border-radius: 8px 0 0 8px; + display: inline-block; +} + +.fileInfoClose { + margin-left: 2px; + margin-top: 16px; + font-size: 14px; + color: #ff0000; + font-weight: 700; + background: #f1f1f1; + padding: 8px 12px; + border-radius: 0 8px 8px 0; + display: inline-block; + +} + +/* Mobile styles */ +@media (max-width: 768px) { + +.dashboardContainer { + max-width: 900px; + margin: 30px auto; + background: #fff; + border-radius: 10px; + box-shadow: none; + padding: 20px; +} + + .desktopText { + display: none; + } + + .mobileText { + display: block; + } +} \ No newline at end of file