diff --git a/public/no-brand.jpg b/public/no-brand.jpg new file mode 100644 index 0000000..de7feea Binary files /dev/null and b/public/no-brand.jpg differ diff --git a/src/ChatBot.js b/src/ChatBot.js index df93ee1..3af1a87 100644 --- a/src/ChatBot.js +++ b/src/ChatBot.js @@ -103,6 +103,17 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => { console.error('Fetch error:', error); } }; +function formatBoldText(text) { + const parts = text.split(/(\*\*[^\*]+\*\*)/g); + + return parts.map((part, index) => { + if (part.startsWith('**') && part.endsWith('**')) { + return {part.slice(2, -2)}; + } else { + return {part}; + } + }); +} return (
@@ -127,19 +138,15 @@ const ChatBot = ({ existingConversation, readOnly, hh }) => { >
{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); + ? msg.text + : (() => { + try { + return formatBoldText(msg.text); // Apply formatting here + } catch (e) { + return msg.text; + } + })()} - return parsedObj.jawaban; - } catch (e) { - return msg.text; // Return an empty string if there is an error - } - - })()} {msg.quickReplies && (
{msg.quickReplies.map((reply, i) => ( diff --git a/src/Dashboard.js b/src/Dashboard.js index a17db14..c68be67 100644 --- a/src/Dashboard.js +++ b/src/Dashboard.js @@ -145,7 +145,7 @@ const Dashboard = () => { setCheckOnce(true); if (subscription === null) { // Not subscribed yet — show modal asking user to subscribe - setModalContent(setModalContent('')} />); + setModalContent( setModalContent('')} />); } else { // Already subscribed setModalContent('') @@ -228,17 +228,17 @@ const Dashboard = () => { }; const prefixes = Object.keys(prefixLabelMap); -const parsedHours = rawData.map(d => new Date(d.hour)); -parsedHours.sort((a, b) => a - b); + const parsedHours = rawData.map(d => new Date(d.hour)); + parsedHours.sort((a, b) => a - b); -// Extract only the date (no timezone shifting) -const getDateStr = date => date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, '0') + '-' + date.getDate().toString().padStart(2, '0'); + // Extract only the date (no timezone shifting) + const getDateStr = date => date.getFullYear() + '-' + (date.getMonth() + 1).toString().padStart(2, '0') + '-' + date.getDate().toString().padStart(2, '0'); -const hours = parsedHours.map((date, index) => { - const timeStr = date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0'); - return index === parsedHours.length - 1 ? 'Sekarang' : timeStr; -}); + const hours = parsedHours.map((date, index) => { + const timeStr = date.getHours().toString().padStart(2, '0') + ':' + date.getMinutes().toString().padStart(2, '0'); + return index === parsedHours.length - 1 ? 'Now' : timeStr; + }); const counts = {}; prefixes.forEach(prefix => { @@ -303,25 +303,25 @@ const hours = parsedHours.map((date, index) => { return (
-
- +
+ - {isMenuOpen && ( -
- - -
- )} -
+ {isMenuOpen && ( +
+ + +
+ )} +
Bot Avatar

Dermalounge AI Admin Dashboard

@@ -329,26 +329,26 @@ const hours = parsedHours.map((date, index) => {
-
+

{stats.totalChats}

-

Total Percakapan selama 24 jam

+

TOTAL USER PER DAY

{stats.botMessages}

-

Respons Bot

+

AI RESPONSE

-
setModalContent()}> +
setModalContent()}>

{followUps.length}

-

Follow up

+

BOOKING REQUEST

{discussedTopics[0]?.topic}

-

Paling sering ditanyakan

+

Top topic

-

Grafik Interaksi

+

Interactions

@@ -371,9 +371,9 @@ const hours = parsedHours.map((date, index) => { }} >

- Seret file ke sini, atau Klik untuk unggah + Drop file here, or Click to upload

-

Klik untuk unggah

+

Click to upload

{selectedFile && ( <> diff --git a/src/Dashboard.module.css b/src/Dashboard.module.css index 860f527..7f8b06e 100644 --- a/src/Dashboard.module.css +++ b/src/Dashboard.module.css @@ -55,11 +55,17 @@ margin-top: 20px; } +.statsGrid p { + text-transform: uppercase; +} .statCard { background: #ece5dd; border-radius: 10px; padding: 20px; box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.05); + display: flex; + flex-direction: column; + justify-content: space-around; } .statCard h2 { @@ -200,8 +206,8 @@ position: absolute; position: relative; display: inline-block; position: absolute; - top: 10px; - right: 10px; + bottom: 5px; + right: 5px; } .dropdownToggle { diff --git a/src/DiscussedTopics.js b/src/DiscussedTopics.js index 7cdb9d5..723b10d 100644 --- a/src/DiscussedTopics.js +++ b/src/DiscussedTopics.js @@ -5,10 +5,10 @@ import React from 'react'; const DiscussedTopics = ({ topics }) => { return (
-

Topik yang Sering Ditanyakan

+

Top Topic

    {topics.map((topic, idx) => ( -
  • {topic.topic} - {topic.count} kali
  • +
  • {topic.topic} - {topic.count} x
  • ))}
diff --git a/src/FollowUps.js b/src/FollowUps.js index 4fe0020..16d177a 100644 --- a/src/FollowUps.js +++ b/src/FollowUps.js @@ -4,7 +4,6 @@ import styles from './FollowUps.module.css'; const FollowUps = ({ data }) => { return (
-

User yang tertarik

{data.map(user => (
diff --git a/src/FollowUps.module.css b/src/FollowUps.module.css index 348a108..8b88130 100644 --- a/src/FollowUps.module.css +++ b/src/FollowUps.module.css @@ -1,5 +1,4 @@ .container { - padding: 24px; background-color: #f7f9fa; font-family: 'Amazon Ember', sans-serif; } diff --git a/src/ProfileTab.js b/src/ProfileTab.js index bfcfec2..1ba46fc 100644 --- a/src/ProfileTab.js +++ b/src/ProfileTab.js @@ -1,43 +1,41 @@ import React, { useState, useRef, useEffect } from 'react'; -import { FaPen } from 'react-icons/fa'; -import styles from './ProfileTab.module.css'; import { useNavigate } from 'react-router-dom'; +import styles from './ProfileTab.module.css'; const ProfileTab = () => { const menuRef = useRef(null); const navigate = useNavigate(); const [isEditing, setIsEditing] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false); + const [profile, setProfile] = useState({}); + const [profileTemp, setProfileTemp] = useState({}); + + const licenses = [ + { id: 1, type: "Current Subscription", number: "DRML-2025-AI001", validUntil: "June 30 2025" }, + ]; - // 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); - }; + return () => document.removeEventListener('mousedown', handleClickOutside); }, []); const handleLogout = () => { localStorage.removeItem('token'); localStorage.removeItem('user'); - navigator.serviceWorker.ready.then(function (registration) { - registration.pushManager.getSubscription().then(function (subscription) { + navigator.serviceWorker.ready.then((registration) => { + registration.pushManager.getSubscription().then((subscription) => { if (subscription) { - subscription.unsubscribe().then(function (successful) { - console.log('Push subscription unsubscribed on logout:', successful); - // Optional: also notify backend to clear the token + subscription.unsubscribe().then((successful) => { + console.log('Unsubscribed from push notifications:', successful); fetch('/api/clear-subscription', { method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ endpoint: subscription.endpoint }), }); }); @@ -48,73 +46,79 @@ const ProfileTab = () => { window.location.reload(); }; - useEffect(() => { - const fetchData = async () => { - const token = localStorage.getItem('token'); + useEffect(() => { + const fetchData = async () => { + const token = localStorage.getItem('token'); - try { - const response = await fetch('https://bot.kediritechnopark.com/webhook/dashboard?profileOnly=true', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${token}`, - }, - }); + try { + const response = await fetch('https://bot.kediritechnopark.com/webhook/dashboard?profileOnly=true', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}`, + }, + }); - if (response.status === 401 || response.status === 403) { - handleLogout(); - return; - } + if (response.status === 401 || response.status === 403) { + handleLogout(); + return; + } - if (!response.ok) { - throw new Error('Fetch gagal dengan status: ' + response.status); - } + if (!response.ok) { + throw new Error('Fetch gagal dengan status: ' + response.status); + } - const data = await response.json(); - console.log(data); + const data = await response.json(); + setProfile(data.profile_data); + setProfileTemp(data.profile_data); + } catch (error) { + console.error('Fetch error:', error); + navigate('/login'); + } + }; - setProfile(data.profile_data); - } catch (error) { - console.error('Error:', error); - navigate('/login'); - } - }; - - fetchData(); // Jalankan langsung saat komponen di-mount - - }, [navigate]); - - const [profile, setProfile] = useState({}); - - const licenses = [ - { id: 1, type: "AI Bot License", number: "DL-2025-AI001", validUntil: "2026-12-31" }, - { id: 2, type: "Clinic Data Access", number: "DL-2025-CL002", validUntil: "2026-06-30" } - ]; + fetchData(); + }, [navigate]); const handleChange = (e) => { const { name, value } = e.target; - setProfile((prev) => ({ ...prev, [name]: value })); + setProfile(prev => ({ ...prev, [name]: value })); }; + const handleSave = async () => { try { const token = localStorage.getItem('token'); + if (profile.newPassword && profile.newPassword !== profile.confirmPassword) { + alert('Password dan konfirmasi tidak sama.'); + return; + } + + const payload = { ...profile }; + if (!payload.newPassword) { + delete payload.newPassword; + delete payload.confirmPassword; + } else { + payload.password = payload.newPassword; + delete payload.newPassword; + delete payload.confirmPassword; + } + const response = await fetch('https://bot.kediritechnopark.com/webhook/profile', { method: 'PUT', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}`, }, - body: JSON.stringify(profile), + body: JSON.stringify(payload), }); if (!response.ok) throw new Error('Gagal menyimpan profil'); const result = await response.json(); - console.log('Profil berhasil diperbarui:', result); setIsEditing(false); - alert('Profil berhasil disimpan!'); + alert('Profile saved!'); } catch (error) { console.error('Error saat menyimpan profil:', error); alert('Terjadi kesalahan saat menyimpan profil.'); @@ -123,8 +127,7 @@ const ProfileTab = () => { return (
-
-

Profil Perusahaan

+
- - - - -
)}
+ Bot Avatar +
+

Dermalounge AI Admin Profile

+
- Company Logo
{["name", "company", "address", "email", "phone"].map((field) => (
@@ -164,35 +161,75 @@ const ProfileTab = () => { -
))} - {isEditing && -
+
+ + +
+
+ + +
+ + )} + + {!isEditing && +
setIsEditing(true)} > - Simpan + Edit +
+ } + {isEditing && + +
+ {/*
{ + setIsEditing(false); + setProfile(profileTemp); + }}> + Batal +
*/} +
+ Save +
}
-

License

{licenses.map((item) => (

{item.type}

-

No: {item.number}

-

Berlaku sampai: {item.validUntil}

+

{item.number}

+

Free License Valid until: {item.validUntil}

))}
+ +
+ © 2025 Kediri Technopark +
); }; diff --git a/src/ProfileTab.module.css b/src/ProfileTab.module.css index b14a068..75c14f2 100644 --- a/src/ProfileTab.module.css +++ b/src/ProfileTab.module.css @@ -1,40 +1,31 @@ -/* Container */ +/* Container Utama */ .dashboardContainer { - width: 100%; + max-width: 900px; + margin: 30px auto; + background: #fff; + border-radius: 10px; + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2); padding: 20px; - max-width: 1200px; - margin: 0 auto; - box-sizing: border-box; } -/* Header */ -.profileHeader { +.dashboardHeader { display: flex; - justify-content: space-between; align-items: center; - flex-wrap: wrap; - margin-bottom: 20px; - gap: 12px; -} - -.editButton { - background-color: #075e54; + gap: 15px; + background: #075e54; color: white; - padding: 6px 12px; - font-size: 12px; - border: none; - border-radius: 8px; - display: flex; - align-items: center; - gap: 6px; - cursor: pointer; - transition: background-color 0.3s ease; + padding: 20px; + border-radius: 10px 10px 0 0; + position: relative; } -.editButton:hover { - background-color: #0f9b8a; +.dashboardHeader img { + width: 75px; + height: 75px; + border-radius: 50%; } + /* Profile Section */ .profileSection { display: flex; @@ -43,6 +34,7 @@ gap: 20px; flex-wrap: wrap; margin-bottom: 30px; + margin-top: 20px; } .companyImage { @@ -80,64 +72,42 @@ color: white; padding: 16px; border-radius: 12px; - box-shadow: 0 4px 8px rgba(0,0,0,0.1); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); font-size: 14px; } -/* ==================== - Responsive Breakpoints -==================== */ - -/* Tablet (768px – 1023px) */ -@media screen and (max-width: 1023px) { - .profileSection { - flex-direction: row; - justify-content: flex-start; - } - - .profileDetails { - min-width: 100%; - } - - .licenseCards { - grid-template-columns: repeat(2, 1fr); - } +.profileHeader { + flex-direction: column; + align-items: flex-start; } -/* Mobile (≤ 767px) */ -@media screen and (max-width: 767px) { - .profileHeader { - flex-direction: column; - align-items: flex-start; - } - - .editButton { - align-self: flex-end; - } - - .profileSection { - flex-direction: column; - align-items: center; - text-align: center; - } - - .companyImage { - margin-bottom: 12px; - } - - .profileDetails { - text-align: left; - width: 100%; - } - - .licenseCards { - grid-template-columns: 1fr; - } - - .licenseCard { - font-size: 13px; - } +.editButton { + align-self: flex-end; } + +.profileSection { + flex-direction: column; + align-items: center; + text-align: center; +} + +.companyImage { + margin-bottom: 12px; +} + +.profileDetails { + text-align: left; + width: 100%; +} + +.licenseCards { + grid-template-columns: 1fr; +} + +.licenseCard { + font-size: 13px; +} + .profileInputGroup { display: flex; align-items: center; @@ -147,7 +117,8 @@ } .profileInputGroup label { - min-width: 100px; /* atau sesuai label terpanjang */ + min-width: 110px; + /* atau sesuai label terpanjang */ font-size: 14px; } @@ -162,7 +133,8 @@ width: auto; max-width: 100%; - flex: 1; /* biar input bisa melar jika ruang tersedia */ + flex: 1; + /* biar input bisa melar jika ruang tersedia */ } /* Saat tidak dalam mode editing */ @@ -173,18 +145,17 @@ color: #000; } - .dropdownContainer { position: relative; display: inline-block; position: absolute; - top: 37px; - right: 10px; + bottom: 5px; + right: 5px; } .dropdownToggle { - color: #ffff; - background-color: #255e54; + background-color: #ffff; + color: #255e54; padding: 8px 12px; border: none; border-radius: 4px; @@ -216,3 +187,62 @@ background-color: #f0f0f0; } + +/* Input Profil */ +.profileForm { + display: flex; + flex-direction: column; + gap: 16px; + padding: 20px 0; +} + +.editableInput { + font-size: 14px; + padding: 8px 12px; + border: 1px solid #ccc; + border-radius: 8px; + background-color: #f9f9f9; + color: #333; + transition: border-color 0.3s; + flex: 1; +} + +.readOnly { + border-color: transparent; + background-color: transparent; + pointer-events: none; + color: #000; +} + +/* Footer (optional) */ +.footer { + text-align: center; + margin-top: 30px; + font-size: 13px; + color: #777; +} + + +/* Mobile styles */ +@media (max-width: 768px) { +.h1 { + color: white; + font-size: 23px; +} +.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