diff --git a/src/App.css b/src/App.css index 5ff7858..289ce63 100644 --- a/src/App.css +++ b/src/App.css @@ -1,7 +1,14 @@ +html, body, #root { + height: 100%; + margin: 0; +} + .App { - display: flex; - align-items: end; - justify-content: center; + height: 100%; + display: flex; + flex-direction: column; + background: #fff; + align-items: center; } .App-logo { diff --git a/src/ChatBot.js b/src/ChatBot.js index b9c6164..33d17a3 100644 --- a/src/ChatBot.js +++ b/src/ChatBot.js @@ -1,220 +1,203 @@ - import React, { useState, useEffect } from 'react'; - import styles from './ChatBot.module.css'; +import React, { useState, useEffect } from 'react'; +import styles from './ChatBot.module.css'; - const ChatBot = ({ existingConversation, readOnly, hh }) => { - const [messages, setMessages] = useState([ - { - sender: 'bot', - text: 'Hai Dermalovers! 👋 Saya siap membantu anda tampil lebih percaya diri. Ada pertanyaan seputar perawatan kulit atau kecantikan hari ini?', - time: getTime(), - quickReplies: [ - 'List harga layanan Dermalounge', - 'Beri saya info jadwal dokter', - 'Apa saja layanan disini', - ], - }, - ]); +const ChatBot = ({ existingConversation, readOnly, hh }) => { + const [messages, setMessages] = useState([ + { + sender: 'bot', + text: 'Hai Dermalovers! 👋 Saya siap membantu anda tampil lebih percaya diri. Ada pertanyaan seputar perawatan kulit atau kecantikan hari ini?', + time: getTime(), + quickReplies: [ + 'Info layanan Dermalounge', + 'Apa perawatan wajah recommended', + 'Saya ingin konsultasi masalah kulit', + 'Info lokasi & cara booking', + ], + }, + ]); - const [input, setInput] = useState(''); - const [isLoading, setIsLoading] = useState(false); - useEffect(() => { + const [input, setInput] = useState(''); + const [isLoading, setIsLoading] = useState(false); + useEffect(() => { - if (existingConversation && existingConversation.length > 0) { - setMessages(existingConversation); - } - }, [existingConversation]) - useEffect(() => { - if (!localStorage.getItem('session')) { - function generateUUID() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - const r = Math.random() * 16 | 0; - const v = c === 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); - }); - } - - const sessionId = generateUUID(); - const dateNow = new Date().toISOString(); - - localStorage.setItem('session', JSON.stringify({ sessionId: sessionId, lastSeen: dateNow })) - } - }, []); - - const sendMessage = async (textOverride = null) => { - const message = textOverride || input.trim(); - if (message === '') return; - - // Show user's message immediately - const newMessages = [ - ...messages, - { sender: 'user', text: message, time: getTime() }, - ]; - - setMessages(newMessages); - setInput(''); - - setIsLoading(true); - try { - // Send to backend - 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() }), + if (existingConversation && existingConversation.length > 0) { + setMessages(existingConversation); + } + }, [existingConversation]) + useEffect(() => { + if (!localStorage.getItem('session')) { + function generateUUID() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + const r = Math.random() * 16 | 0; + const v = c === 'x' ? r : (r & 0x3 | 0x8); + return v.toString(16); }); + } - 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[0].text || data[0].output || 'Maaf, saya tidak mengerti.'; + const sessionId = generateUUID(); + const dateNow = new Date().toISOString(); - // Add bot's reply + localStorage.setItem('session', JSON.stringify({ sessionId: sessionId, lastSeen: dateNow })) + } + }, []); + + const sendMessage = async (textOverride = null, tryCount = 0) => { + const message = textOverride || input.trim(); + if (message === '') return; + + // Show user's message immediately + const newMessages = [ + ...messages, + { sender: 'user', text: message, time: getTime() }, + ]; + + setMessages(newMessages); + setInput(''); + + setIsLoading(true); + try { + // Send to backend + const response = await fetch('https://bot.kediritechnopark.com/webhook/master-agent/ask/dev', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ pertanyaan: message, sessionId: JSON.parse(localStorage.getItem('session')).sessionId, lastSeen: new Date().toISOString() }), + }); + + 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[0].text || data[0].output || 'Maaf, saya tidak mengerti.'; + + // Add bot's reply + setMessages(prev => [ + ...prev, + { sender: 'bot', text: botAnswer, time: getTime() }, + ]); + + setIsLoading(false); + } catch (error) { + console.log(tryCount) + if (tryCount > 3) { + // Add bot's error reply setMessages(prev => [ ...prev, - { sender: 'bot', text: botAnswer, time: getTime() }, + { sender: 'bot', text: 'Maaf saya sedang tidak tersedia sekarang, coba lagi nanti', time: getTime() }, ]); - - setIsLoading(false); - } catch (error) { - sendMessage('gimana') - console.error('Fetch error:', error); - } finally { setIsLoading(false); + return; } - }; + setTimeout(() => sendMessage(message, tryCount + 1), 3000); - return ( -
-
- Bot Avatar - DERMALOUNGE -
- -
- - {isLoading && ( -
-
- Mengetik... -
-
- )} - {messages.slice().reverse().map((msg, index) => ( -
-
- {msg.sender !== 'bot' - ? msg.text - : (() => { - try {let cleanText = msg.text.replace(/`/g, ''); // Remove backticks -cleanText = cleanText.substring(4); // Remove first 4 characters -let parsedObj = JSON.parse(cleanText); - -return parsedObj.jawaban; -} catch (e) { - return msg.text; // Return an empty string if there is an error -} - - })()} - {msg.quickReplies && ( -
- {msg.quickReplies.map((reply, i) => ( -
sendMessage(reply)} - > - {reply} -
- ))} -
- )} -
{msg.time}
-
-
- ))} -
- -
- setInput(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && sendMessage()} - disabled={isLoading} - /> - -
-
-
-
sendMessage('Dapatkah bopeng dihilangkan?')} - > - Dapatkah bopeng dihilangkan? -
-
sendMessage('Bisa booking treatment untuk besok?')} - > - Bisa booking treatment untuk besok? -
-
sendMessage('Bisa booking treatment untuk besok?')} - > - Ada treatment untuk jerawat? -
-
- -
-
- ); + console.error('Fetch error:', error); + } }; - function getTime() { - const now = new Date(); - return now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); - } + return ( +
+
+ Bot Avatar + DERMALOUNGE +
- export default ChatBot; +
+ + {isLoading && ( +
+
+ Mengetik... +
+
+ )} + {messages.slice().reverse().map((msg, index) => ( +
+
+ {msg.sender !== 'bot' + ? msg.text + : (() => { + try { + let cleanText = msg.text.replace(/`/g, ''); // Remove backticks + cleanText = cleanText.substring(4); // Remove first 4 characters + let parsedObj = JSON.parse(cleanText); + + return parsedObj.jawaban; + } catch (e) { + return msg.text; // Return an empty string if there is an error + } + + })()} + {msg.quickReplies && ( +
+ {msg.quickReplies.map((reply, i) => ( +
sendMessage(reply)} + > + {reply} +
+ ))} +
+ )} +
{msg.time}
+
+
+ ))} +
+ +
+ setInput(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && sendMessage()} + disabled={isLoading} + /> + +
+
+ +
+ Untuk bisa membantu Anda lebih jauh, boleh saya tahu nama dan nomor telepon Anda? + Informasi ini juga membantu tim admin kami jika perlu melakukan follow-up nantinya 😊 +
+ console.log('Nama focused')} + /> +
+ +62 + console.log('Telepon focused')} + style={{border: 0, width: '100%'}} + /> +
+ +
+ Lanjut +
+
+
+
+
+ ); +}; + +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 index c90f962..eba772f 100644 --- a/src/ChatBot.module.css +++ b/src/ChatBot.module.css @@ -1,14 +1,23 @@ .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: 100vh; - overflow: auto; + height: 100%; + background: #fff; + border-radius: 10px; + box-shadow: 0 5px 15px #0003; + display: flex; + flex-direction: column; + max-width: 500px; + overflow: auto; + position: relative; } +.PopUp{ + background-color: #555454ab; + position: absolute; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} .chatHeader { background: #075e54; color: #fff; @@ -117,6 +126,22 @@ gap: 8px; margin: 10px 0 0; } +.inputGroup { + display: flex; + align-items: center; + border: 1px solid #ccc; + border-radius: 20px; + overflow: hidden; + width: 100%; +} + +.prefix { + background-color: #f0f0f0; + padding: 0.5rem 0.75rem; + border-right: 1px solid #ccc; + font-size: 12px; + color: #555; +} .quickReply { background: #fff; diff --git a/src/Dashboard.js b/src/Dashboard.js index 87d7ca5..fac9731 100644 --- a/src/Dashboard.js +++ b/src/Dashboard.js @@ -9,6 +9,8 @@ import DiscussedTopics from './DiscussedTopics'; import Chart from 'chart.js/auto'; const Dashboard = () => { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const chartRef = useRef(null); const chartInstanceRef = useRef(null); const [conversations, setConversations] = useState([]); @@ -46,6 +48,22 @@ const Dashboard = () => { 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'); @@ -98,7 +116,7 @@ const Dashboard = () => { userMessages, botMessages, }); - + setLoading(false); // ⬅️ Setelah berhasil, hilangkan loading } catch (error) { console.error('Error:', error); @@ -206,14 +224,33 @@ const Dashboard = () => {
{isLoggedIn ? ( - + +
+ + + {isMenuOpen && ( +
+ + +
+ )} +
) : ( Login )} Bot Avatar

Dermalounge AI Admin Dashboard

-

Statistik penggunaan chatbot secara real-time

diff --git a/src/Dashboard.module.css b/src/Dashboard.module.css index e677d0a..e742a07 100644 --- a/src/Dashboard.module.css +++ b/src/Dashboard.module.css @@ -43,8 +43,8 @@ } .dashboardHeader img { - width: 50px; - height: 50px; + width: 75px; + height: 75px; border-radius: 50%; } @@ -191,4 +191,46 @@ position: absolute; .logoutButton:hover { background-color: #cb0f0f; -} \ No newline at end of file +} +.dropdownContainer { + position: relative; + display: inline-block; + +position: absolute; + top: 10px; + right: 10px; +} + +.dropdownToggle { + background-color: #007bff; + color: white; + padding: 8px 12px; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.dropdownMenu { + position: absolute; + right: 0; + top: 100%; + background-color: white; + border: 1px solid #ccc; + min-width: 160px; + box-shadow: 0 8px 16px rgba(0,0,0,0.1); + z-index: 1; + border-radius: 4px; +} + +.dropdownItem { + padding: 10px 16px; + text-align: left; + width: 100%; + background: none; + border: none; + cursor: pointer; +} + +.dropdownItem:hover { + background-color: #f0f0f0; +}