This commit is contained in:
Vassshhh
2025-06-16 18:13:15 +07:00
parent e7c1f56f91
commit 2ed6ecfe75
5 changed files with 316 additions and 222 deletions

View File

@@ -1,7 +1,14 @@
html, body, #root {
height: 100%;
margin: 0;
}
.App {
height: 100%;
display: flex;
align-items: end;
justify-content: center;
flex-direction: column;
background: #fff;
align-items: center;
}
.App-logo {

View File

@@ -1,16 +1,17 @@
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 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',
'Info layanan Dermalounge',
'Apa perawatan wajah recommended',
'Saya ingin konsultasi masalah kulit',
'Info lokasi & cara booking',
],
},
]);
@@ -40,7 +41,7 @@
}
}, []);
const sendMessage = async (textOverride = null) => {
const sendMessage = async (textOverride = null, tryCount = 0) => {
const message = textOverride || input.trim();
if (message === '') return;
@@ -56,7 +57,7 @@
setIsLoading(true);
try {
// Send to backend
const response = await fetch('https://bot.kediritechnopark.com/webhook/master-agent/ask', {
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() }),
@@ -76,15 +77,24 @@
setIsLoading(false);
} catch (error) {
sendMessage('gimana')
console.error('Fetch error:', error);
} finally {
console.log(tryCount)
if (tryCount > 3) {
// Add bot's error reply
setMessages(prev => [
...prev,
{ sender: 'bot', text: 'Maaf saya sedang tidak tersedia sekarang, coba lagi nanti', time: getTime() },
]);
setIsLoading(false);
return;
}
setTimeout(() => sendMessage(message, tryCount + 1), 3000);
console.error('Fetch error:', error);
}
};
return (
<div className={styles.chatContainer} style={{ height: hh || '100vh' }}>
<div className={styles.chatContainer} >
<div className={styles.chatHeader}>
<img src="/dermalounge.jpg" alt="Bot Avatar" />
<strong>DERMALOUNGE</strong>
@@ -108,14 +118,15 @@
{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);
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 parsedObj.jawaban;
} catch (e) {
return msg.text; // Return an empty string if there is an error
}
}
})()}
{msg.quickReplies && (
@@ -137,7 +148,7 @@ return parsedObj.jawaban;
))}
</div>
<div className={styles.chatInput} style={{ visibility: readOnly ? 'hidden' : 'visible', marginTop: readOnly ? '-59px' : '0px' }}>
<div className={styles.chatInput} /*/style={{ visibility: readOnly ? 'hidden' : 'visible'}}/*/>
<input
type="text"
placeholder="Ketik pesan..."
@@ -150,71 +161,43 @@ return parsedObj.jawaban;
Kirim
</button>
</div>
<div style={{width: '96.6%'}}>
<div style={{
backgroundColor: '#f0f0f0',
width: '100%',
display: 'flex',
flexDirection: 'row',
overflowX: 'auto',
padding: '8px',
scrollbarWidth: 'none'
}}>
<div
style={{
flexShrink: 0,
background: '#fff',
border: '1px solid #ccc',
padding: '8px 12px',
borderRadius: '20px',
fontSize: '13px',
cursor: 'pointer',
margin: '3px'
}}
onClick={() => sendMessage('Dapatkah bopeng dihilangkan?')}
>
Dapatkah bopeng dihilangkan?
</div>
<div
style={{
flexShrink: 0,
background: '#fff',
border: '1px solid #ccc',
padding: '8px 12px',
borderRadius: '20px',
fontSize: '13px',
cursor: 'pointer',
margin: '3px'
}}
onClick={() => sendMessage('Bisa booking treatment untuk besok?')}
>
Bisa booking treatment untuk besok?
</div>
<div
style={{
flexShrink: 0,
background: '#fff',
border: '1px solid #ccc',
padding: '8px 12px',
borderRadius: '20px',
fontSize: '13px',
cursor: 'pointer',
margin: '3px'
}}
onClick={() => sendMessage('Bisa booking treatment untuk besok?')}
>
Ada treatment untuk jerawat?
</div>
<div className={styles.PopUp}>
<div className={`${styles.message} ${styles['bot']}`}>
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 😊
<div className={styles.quickReplies} style={{ flexDirection: 'column' }}>
<input
className={styles.quickReply}
placeholder="Nama Lengkapmu"
onFocus={() => console.log('Nama focused')}
/>
<div className={styles.inputGroup}>
<span className={styles.prefix}>+62</span>
<input
type="tel"
className={styles.quickReply}
placeholder="Nomor HP"
onFocus={() => console.log('Telepon focused')}
style={{border: 0, width: '100%'}}
/>
</div>
<div
className={styles.quickReply}
>
Lanjut
</div>
</div>
</div>
</div>
</div>
);
};
};
function getTime() {
function getTime() {
const now = new Date();
return now.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
}
export default ChatBot;
export default ChatBot;

View File

@@ -1,14 +1,23 @@
.chatContainer {
max-width: 500px;
height: 100%;
background: #fff;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
box-shadow: 0 5px 15px #0003;
display: flex;
flex-direction: column;
height: 100vh;
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;

View File

@@ -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');
@@ -206,14 +224,33 @@ const Dashboard = () => {
<div className={styles.dashboardContainer}>
<div className={styles.dashboardHeader}>
{isLoggedIn ? (
<button onClick={handleLogout} className={styles.logoutButton}>Logout</button>
<div className={styles.dropdownContainer}>
<button
onClick={() => setIsMenuOpen(!isMenuOpen)}
className={styles.dropdownToggle}
ref={menuRef}
>
Menu
</button>
{isMenuOpen && (
<div className={styles.dropdownMenu}>
<button onClick={handleLogout} className={styles.dropdownItem}>
Ganti Password
</button>
<button onClick={handleLogout} className={styles.dropdownItem}>
Logout
</button>
</div>
)}
</div>
) : (
<a href="/login" className={styles.loginButton}>Login</a>
)}
<img src="/dermalounge.jpg" alt="Bot Avatar" />
<div>
<h1 className={styles.h1}>Dermalounge AI Admin Dashboard</h1>
<p>Statistik penggunaan chatbot secara real-time</p>
</div>
</div>

View File

@@ -43,8 +43,8 @@
}
.dashboardHeader img {
width: 50px;
height: 50px;
width: 75px;
height: 75px;
border-radius: 50%;
}
@@ -192,3 +192,45 @@ position: absolute;
.logoutButton:hover {
background-color: #cb0f0f;
}
.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;
}