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 { .App {
height: 100%;
display: flex; display: flex;
align-items: end; flex-direction: column;
justify-content: center; background: #fff;
align-items: center;
} }
.App-logo { .App-logo {

View File

@@ -8,9 +8,10 @@
text: 'Hai Dermalovers! 👋 Saya siap membantu anda tampil lebih percaya diri. Ada pertanyaan seputar perawatan kulit atau kecantikan hari ini?', text: 'Hai Dermalovers! 👋 Saya siap membantu anda tampil lebih percaya diri. Ada pertanyaan seputar perawatan kulit atau kecantikan hari ini?',
time: getTime(), time: getTime(),
quickReplies: [ quickReplies: [
'List harga layanan Dermalounge', 'Info layanan Dermalounge',
'Beri saya info jadwal dokter', 'Apa perawatan wajah recommended',
'Apa saja layanan disini', '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(); const message = textOverride || input.trim();
if (message === '') return; if (message === '') return;
@@ -56,7 +57,7 @@
setIsLoading(true); setIsLoading(true);
try { try {
// Send to backend // 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', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ pertanyaan: message, sessionId: JSON.parse(localStorage.getItem('session')).sessionId, lastSeen: new Date().toISOString() }), body: JSON.stringify({ pertanyaan: message, sessionId: JSON.parse(localStorage.getItem('session')).sessionId, lastSeen: new Date().toISOString() }),
@@ -76,15 +77,24 @@
setIsLoading(false); setIsLoading(false);
} catch (error) { } catch (error) {
sendMessage('gimana') console.log(tryCount)
console.error('Fetch error:', error); if (tryCount > 3) {
} finally { // Add bot's error reply
setMessages(prev => [
...prev,
{ sender: 'bot', text: 'Maaf saya sedang tidak tersedia sekarang, coba lagi nanti', time: getTime() },
]);
setIsLoading(false); setIsLoading(false);
return;
}
setTimeout(() => sendMessage(message, tryCount + 1), 3000);
console.error('Fetch error:', error);
} }
}; };
return ( return (
<div className={styles.chatContainer} style={{ height: hh || '100vh' }}> <div className={styles.chatContainer} >
<div className={styles.chatHeader}> <div className={styles.chatHeader}>
<img src="/dermalounge.jpg" alt="Bot Avatar" /> <img src="/dermalounge.jpg" alt="Bot Avatar" />
<strong>DERMALOUNGE</strong> <strong>DERMALOUNGE</strong>
@@ -108,7 +118,8 @@
{msg.sender !== 'bot' {msg.sender !== 'bot'
? msg.text ? msg.text
: (() => { : (() => {
try {let cleanText = msg.text.replace(/`/g, ''); // Remove backticks try {
let cleanText = msg.text.replace(/`/g, ''); // Remove backticks
cleanText = cleanText.substring(4); // Remove first 4 characters cleanText = cleanText.substring(4); // Remove first 4 characters
let parsedObj = JSON.parse(cleanText); let parsedObj = JSON.parse(cleanText);
@@ -137,7 +148,7 @@ return parsedObj.jawaban;
))} ))}
</div> </div>
<div className={styles.chatInput} style={{ visibility: readOnly ? 'hidden' : 'visible', marginTop: readOnly ? '-59px' : '0px' }}> <div className={styles.chatInput} /*/style={{ visibility: readOnly ? 'hidden' : 'visible'}}/*/>
<input <input
type="text" type="text"
placeholder="Ketik pesan..." placeholder="Ketik pesan..."
@@ -150,63 +161,35 @@ return parsedObj.jawaban;
Kirim Kirim
</button> </button>
</div> </div>
<div style={{width: '96.6%'}}> <div className={styles.PopUp}>
<div style={{
backgroundColor: '#f0f0f0', <div className={`${styles.message} ${styles['bot']}`}>
width: '100%', Untuk bisa membantu Anda lebih jauh, boleh saya tahu nama dan nomor telepon Anda?
display: 'flex', Informasi ini juga membantu tim admin kami jika perlu melakukan follow-up nantinya 😊
flexDirection: 'row', <div className={styles.quickReplies} style={{ flexDirection: 'column' }}>
overflowX: 'auto', <input
padding: '8px', className={styles.quickReply}
scrollbarWidth: 'none' placeholder="Nama Lengkapmu"
}}> onFocus={() => console.log('Nama focused')}
<div />
style={{ <div className={styles.inputGroup}>
flexShrink: 0, <span className={styles.prefix}>+62</span>
background: '#fff', <input
border: '1px solid #ccc', type="tel"
padding: '8px 12px', className={styles.quickReply}
borderRadius: '20px', placeholder="Nomor HP"
fontSize: '13px', onFocus={() => console.log('Telepon focused')}
cursor: 'pointer', style={{border: 0, width: '100%'}}
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> </div>
<div
className={styles.quickReply}
>
Lanjut
</div>
</div>
</div>
</div> </div>
</div> </div>
); );

View File

@@ -1,14 +1,23 @@
.chatContainer { .chatContainer {
max-width: 500px; height: 100%;
background: #fff; background: #fff;
border-radius: 10px; border-radius: 10px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); box-shadow: 0 5px 15px #0003;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100vh; max-width: 500px;
overflow: auto; overflow: auto;
position: relative;
} }
.PopUp{
background-color: #555454ab;
position: absolute;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.chatHeader { .chatHeader {
background: #075e54; background: #075e54;
color: #fff; color: #fff;
@@ -117,6 +126,22 @@
gap: 8px; gap: 8px;
margin: 10px 0 0; 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 { .quickReply {
background: #fff; background: #fff;

View File

@@ -9,6 +9,8 @@ import DiscussedTopics from './DiscussedTopics';
import Chart from 'chart.js/auto'; import Chart from 'chart.js/auto';
const Dashboard = () => { const Dashboard = () => {
const [isMenuOpen, setIsMenuOpen] = useState(false);
const chartRef = useRef(null); const chartRef = useRef(null);
const chartInstanceRef = useRef(null); const chartInstanceRef = useRef(null);
const [conversations, setConversations] = useState([]); const [conversations, setConversations] = useState([]);
@@ -46,6 +48,22 @@ const Dashboard = () => {
window.location.reload(); 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(() => { useEffect(() => {
const fetchData = async () => { const fetchData = async () => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
@@ -206,14 +224,33 @@ const Dashboard = () => {
<div className={styles.dashboardContainer}> <div className={styles.dashboardContainer}>
<div className={styles.dashboardHeader}> <div className={styles.dashboardHeader}>
{isLoggedIn ? ( {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> <a href="/login" className={styles.loginButton}>Login</a>
)} )}
<img src="/dermalounge.jpg" alt="Bot Avatar" /> <img src="/dermalounge.jpg" alt="Bot Avatar" />
<div> <div>
<h1 className={styles.h1}>Dermalounge AI Admin Dashboard</h1> <h1 className={styles.h1}>Dermalounge AI Admin Dashboard</h1>
<p>Statistik penggunaan chatbot secara real-time</p>
</div> </div>
</div> </div>

View File

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