diff --git a/src/App.css b/src/App.css
index 289ce63..666c9fd 100644
--- a/src/App.css
+++ b/src/App.css
@@ -5,6 +5,7 @@ html, body, #root {
.App {
height: 100%;
+ overflow: auto;
display: flex;
flex-direction: column;
background: #fff;
diff --git a/src/ChatBot.js b/src/ChatBot.js
index 890007e..4898304 100644
--- a/src/ChatBot.js
+++ b/src/ChatBot.js
@@ -1,19 +1,20 @@
import React, { useState, useEffect } from 'react';
import styles from './ChatBot.module.css';
-const ChatBot = ({ existingConversation, readOnly, hh }) => {
+const ChatBot = ({ existingConversation }) => {
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?',
+ text: 'Hai! Saya Alle, asisten virtual Dermalounge Clinic. Ada yang bisa Alle bantu hari ini?',
time: getTime(),
quickReplies: [
- 'Konsultasi estetik',
- 'Konsultasi kulit dan kelamin'
+ 'Konsultasi Estetik',
+ 'Konsultasi Kulit & Kelamin'
],
},
]);
+
const [input, setInput] = useState('');
const [isLoading, setIsLoading] = useState(false);
@@ -231,8 +232,7 @@ function formatBoldText(text) {
2 && phoneNumber.length >= 10 ? 'black' : '#ccc' }}
+ className={styles.nextButton}
onClick={() => {
if (name.length > 2 && phoneNumber.length >= 10) {
const sessionData = JSON.parse(localStorage.getItem('session')) || {};
diff --git a/src/ChatBot.module.css b/src/ChatBot.module.css
index 6d08f27..b5d327f 100644
--- a/src/ChatBot.module.css
+++ b/src/ChatBot.module.css
@@ -152,16 +152,30 @@
transition: all 0.2s ease;
}
-.quickReply:hover {
+/* .quickReply:hover {
background: #075e54;
color: white;
border-color: #075e54;
-}
+} */
-.quickReply:hover::placeholder {
+/* .quickReply:hover::placeholder {
+ color: white;
+} */
+
+.nextButton {
+ background: #075e54;
+ border: 1px solid #ccc;
+ padding: 8px 14px;
+ border-radius: 20px;
+ font-size: 13px;
+ cursor: pointer;
+ transition: all 0.2s ease;
color: white;
}
+.nextButton:hover::placeholder {
+ color: #ccc;
+}
.quickReply2 {
width: 100%;
background: #fff;
@@ -174,13 +188,13 @@
transition: all 0.2s ease;
}
-.quickReply2:hover {
+/* .quickReply2:hover {
background: #075e54;
color: white;
border-color: #075e54;
border: 1px solid #075e54;
-}
+} */
-.quickReply2:hover::placeholder {
+/* .quickReply2:hover::placeholder {
color: white;
-}
\ No newline at end of file
+} */
\ No newline at end of file
diff --git a/src/Dashboard.js b/src/Dashboard.js
index 1d94517..a82137c 100644
--- a/src/Dashboard.js
+++ b/src/Dashboard.js
@@ -22,6 +22,9 @@ const Dashboard = () => {
const [modalContent, setModalContent] = useState(null);
const [rawData, setRawData] = useState([]);
const [loading, setLoading] = useState(true); // β¬
οΈ Tambahkan state loading
+ const [fileList, setFileList] = useState([]);
+ const [selectedKeys, setSelectedKeys] = useState([]);
+
const [stats, setStats] = useState({
totalChats: 0,
@@ -29,17 +32,21 @@ const Dashboard = () => {
});
const [isDragging, setIsDragging] = useState(false);
- const [selectedFile, setSelectedFile] = useState(null);
+ const [selectedFiles, setSelectedFiles] = useState([]);
const navigate = useNavigate();
- const handleFile = (file) => {
- if (file) {
- setSelectedFile(file);
- }
+ const handleFiles = (files) => {
+ const newFiles = files.filter(file => {
+ // Hindari duplikat berdasarkan nama (atau bisa pakai hash/md5 jika perlu)
+ return !selectedFiles.some(f => f.name === file.name);
+ });
+
+ setSelectedFiles((prev) => [...prev, ...newFiles]);
};
+
const handleLogout = () => {
localStorage.removeItem('token');
localStorage.removeItem('user');
@@ -90,16 +97,17 @@ const Dashboard = () => {
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);
- setDiscussedTopics(data?.result?.topics)
- setFollowUps(data?.result?.interested_users)
-
- const graphObj = data.result.graph;
+ setDiscussedTopics(data?.graph[0]?.json?.result?.topics)
+ setFollowUps(data?.graph[0]?.json?.result?.interested_users)
+ setFileList(data?.files)
+ const graphObj = data?.graph[0]?.json?.result?.graph;
+ console.log(graphObj)
const rawDataArray = Object.entries(graphObj).map(([hour, sesi]) => ({
hour,
sesi,
@@ -144,51 +152,51 @@ const Dashboard = () => {
}, [navigate]);
-useEffect(() => {
- if ('serviceWorker' in navigator) {
- navigator.serviceWorker.ready.then(async (registration) => {
- const subscription = await registration.pushManager.getSubscription();
- if (!subscription) {
- // Belum subscribe β tampilkan prompt
- setModalContent(
-
setModalContent('')}
- />
- );
- } else {
- // Sudah subscribe β tidak perlu panggil subscribeUser lagi
- console.log('User is already subscribed.');
- setModalContent('');
- subscribeUser();
- }
+ useEffect(() => {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready.then(async (registration) => {
+ const subscription = await registration.pushManager.getSubscription();
+ if (!subscription) {
+ // Belum subscribe β tampilkan prompt
+ setModalContent(
+ setModalContent('')}
+ />
+ );
+ } else {
+ // Sudah subscribe β tidak perlu panggil subscribeUser lagi
+ console.log('User is already subscribed.');
+ setModalContent('');
+ subscribeUser();
+ }
+ });
+ }
+ }, []);
+
+
+ const subscribeUser = async () => {
+ setModalContent('');
+ const registration = await navigator.serviceWorker.ready;
+
+ const subscription = await registration.pushManager.subscribe({
+ userVisibleOnly: true,
+ applicationServerKey: urlBase64ToUint8Array('BPT-ypQB0Z7HndmeFhRR7AMjDujCLSbOQ21VoVHLQg9MOfWhEZ7SKH5cMjLqkXHl2sTuxdY2rjHDOAxhRK2G2K4'),
});
- }
-}, []);
+ const token = localStorage.getItem('token');
-const subscribeUser = async () => {
- setModalContent('');
- const registration = await navigator.serviceWorker.ready;
+ await fetch('https://bot.kediritechnopark.com/webhook/subscribe', {
+ method: 'POST',
+ body: JSON.stringify({ subscription }),
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${token}`,
+ },
+ });
- const subscription = await registration.pushManager.subscribe({
- userVisibleOnly: true,
- applicationServerKey: urlBase64ToUint8Array('BPT-ypQB0Z7HndmeFhRR7AMjDujCLSbOQ21VoVHLQg9MOfWhEZ7SKH5cMjLqkXHl2sTuxdY2rjHDOAxhRK2G2K4'),
- });
-
- const token = localStorage.getItem('token');
-
- await fetch('https://bot.kediritechnopark.com/webhook/subscribe', {
- method: 'POST',
- body: JSON.stringify({ subscription }),
- headers: {
- 'Content-Type': 'application/json',
- 'Authorization': `Bearer ${token}`,
- },
- });
-
- setModalContent('');
-};
+ setModalContent('');
+ };
function urlBase64ToUint8Array(base64String) {
@@ -298,6 +306,109 @@ const subscribeUser = async () => {
});
}, [rawData]);
+ const handleDeleteFile = async (key) => {
+ if (!window.confirm(`Yakin ingin menghapus "${key}"?`)) return;
+ const token = localStorage.getItem('token');
+
+ try {
+ await fetch("https://bot.kediritechnopark.com/webhook/files/delete", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ "Authorization": `Bearer ${token}`,
+ },
+ body: JSON.stringify({ key }),
+ });
+
+ // fetchFiles(); // Refresh list
+ } catch (error) {
+ console.error("Gagal menghapus file:", error);
+ }
+ };
+
+ const handleBatchDownload = async () => {
+ const token = localStorage.getItem('token');
+ for (const key of selectedKeys) {
+ try {
+ const response = await fetch(
+ `https://bot.kediritechnopark.com/webhook/files/download?key=${encodeURIComponent(key)}`,
+ {
+ method: 'GET',
+ headers: {
+ 'Authorization': `Bearer ${token}`
+ }
+ }
+ );
+
+ if (!response.ok) throw new Error('Gagal download');
+
+ const blob = await response.blob();
+
+ // Coba ambil nama file dari header Content-Disposition (jika tersedia)
+ let filename = key;
+ const disposition = response.headers.get('Content-Disposition');
+ if (disposition && disposition.includes('filename=')) {
+ const match = disposition.match(/filename="?(.+?)"?$/);
+ if (match) filename = match[1];
+ }
+
+ // Buat URL dan download
+ const url = window.URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ a.click();
+ window.URL.revokeObjectURL(url);
+ } catch (err) {
+ console.error(`Gagal download ${key}:`, err);
+ }
+ }
+ };
+
+const handleBatchUpload = async () => {
+ const token = localStorage.getItem('token');
+
+ for (const file of selectedFiles) {
+ const formData = new FormData();
+ formData.append('file', file); // Kirim satu per satu file
+
+ const response = await fetch('https://bot.kediritechnopark.com/webhook/files/upload', {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${token}`
+ // βJangan set 'Content-Type' untuk FormData, biarkan browser mengaturnya
+ },
+ body: formData
+ });
+
+ if (!response.ok) {
+ console.error(`Upload gagal untuk file ${file.name}`);
+ }
+ }
+
+ alert('Upload selesai');
+ setSelectedFiles([]); // Kosongkan setelah selesai upload
+};
+
+ const handleBatchDelete = async () => {
+ if (!window.confirm(`Yakin ingin menghapus ${selectedKeys.length} file?`)) return;
+ const token = localStorage.getItem('token');
+
+ for (const key of selectedKeys) {
+ const response = await fetch(
+ `https://bot.kediritechnopark.com/webhook/files/delete?key=${encodeURIComponent(key)}`,
+ {
+ method: 'DELETE',
+ headers: {
+ 'Authorization': `Bearer ${token}`
+ }
+ }
+ );
+ }
+
+ setSelectedKeys([]);
+ };
+
// β¬οΈ Jika masih loading, tampilkan full white screen
if (loading) {
return ;
@@ -354,53 +465,126 @@ const subscribeUser = async () => {
Interactions
-
Update data
+ {/* β
TOMBOL AKSI */}
+ {selectedKeys.length > 0 && (
+
+
+
+
+ )}
+
+ {/* β
AREA UPLOAD */}
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);
+ const files = Array.from(e.dataTransfer.files);
+ handleFiles(files);
}}
>
+
+ {/* β
TABEL FILE */}
+
+
- Drop file here, or Click to upload
+ Drop file here, or document.getElementById("fileInput").click()} className={styles.uploadLink}>Click to upload
-
Click to upload
+
document.getElementById("fileInput").click()}>Click to upload
- {selectedFile && (
- <>
-
- {selectedFile.name}
-
-
setSelectedFile(null)}>
- X
-
- >
- )}
+
+
+ {selectedFiles.length > 0 &&
+ selectedFiles.map((file, index) => (
+
+
+ {file.name}
+
+
+ setSelectedFiles((prev) => prev.filter((_, i) => i !== index))
+ }
+ >
+ X
+
+
+))}
+
+{selectedFiles.length > 0 &&
+
+
handleBatchUpload()} className={styles.fileUpload}>
+ Upload
+
+
+ }
+
{
- const file = e.target.files[0];
- handleFile(file);
+ const files = Array.from(e.target.files);
+ handleFiles(files);
}}
/>
+
+
+
+
© 2025 Kediri Technopark
diff --git a/src/Dashboard.module.css b/src/Dashboard.module.css
index 7f8b06e..e0f1c9e 100644
--- a/src/Dashboard.module.css
+++ b/src/Dashboard.module.css
@@ -4,13 +4,14 @@
}
.uploadContainer {
+ overflow: hidden;
margin-top: 16px;
border: 2px dashed #ccc;
border-radius: 12px;
- padding: 32px;
text-align: center;
cursor: pointer;
transition: border-color 0.3s;
+ padding-bottom: 30px;
}
.uploadContainer:hover {
@@ -96,6 +97,7 @@
.desktopText {
display: block;
+ /* padding: 32px; */
}
.mobileText {
@@ -115,10 +117,12 @@
padding: 8px 12px;
border-radius: 8px 0 0 8px;
display: inline-block;
+ margin-bottom: 10px;
}
.fileInfoClose {
margin-left: 2px;
+ margin-right: 6px;
margin-top: 16px;
font-size: 14px;
color: #ff0000;
@@ -130,6 +134,17 @@
}
+.fileUpload {
+ margin-top: 16px;
+ font-size: 14px;
+ color: white;
+ background: #2bb438;
+ padding: 8px 12px;
+ border-radius: 8px;
+ display: inline-block;
+ margin-bottom: 10px;
+}
+
/* Mobile styles */
@media (max-width: 768px) {
.h1 {
@@ -243,3 +258,41 @@ position: absolute;
.dropdownItem:hover {
background-color: #f0f0f0;
}
+
+.fileTable {
+ width: 100%;
+ border-collapse: collapse;
+ margin-bottom: 40px;
+}
+
+.fileTable th,
+.fileTable td {
+ border-bottom: 1px solid #ccc;
+ padding: 10px;
+ text-align: left;
+}
+
+.fileTable th {
+ background-color: #f2f2f2;
+}
+
+.actionBar {
+ margin-bottom: 12px;
+ display: flex;
+ gap: 10px;
+}
+
+.actionBar button {
+ padding: 6px 12px;
+ background-color: #00adef;
+ color: white;
+ font-weight: bold;
+ border: none;
+ border-radius: 6px;
+ cursor: pointer;
+}
+
+.actionBar button:hover {
+ background-color: #0095cc;
+}
+
diff --git a/src/FileManager.js b/src/FileManager.js
new file mode 100644
index 0000000..07d538e
--- /dev/null
+++ b/src/FileManager.js
@@ -0,0 +1,131 @@
+// import React, { useEffect, useState } from 'react';
+
+// const FileManager = ({ token }) => {
+// const [files, setFiles] = useState([]);
+// const [uploadFile, setUploadFile] = useState(null);
+
+// const API_BASE = 'https://bot.kediritechnopark.com/webhook/files';
+
+// // Ambil daftar file
+// const fetchFiles = async () => {
+// try {
+// const response = await fetch(`${API_BASE}/list`, {
+// method: 'POST',
+// headers: {
+// 'Content-Type': 'application/json',
+// 'Authorization': `Bearer ${token}`,
+// }
+// });
+
+// const data = await response.json();
+// setFiles(data);
+// } catch (error) {
+// console.error('Gagal mengambil daftar file:', error);
+// }
+// };
+
+// // Hapus file
+// const deleteFile = async (key) => {
+// if (!window.confirm(`Yakin ingin hapus "${key}"?`)) return;
+// try {
+// await fetch(`${API_BASE}/delete`, {
+// method: 'POST',
+// headers: {
+// 'Content-Type': 'application/json',
+// 'Authorization': `Bearer ${token}`,
+// },
+// body: JSON.stringify({ key }),
+// });
+
+// fetchFiles();
+// } catch (error) {
+// console.error('Gagal menghapus file:', error);
+// }
+// };
+
+// // Upload file
+// const handleUpload = async () => {
+// if (!uploadFile) return;
+
+// const formData = new FormData();
+// formData.append('file', uploadFile);
+
+// try {
+// await fetch(`${API_BASE}/upload`, {
+// method: 'POST',
+// headers: {
+// 'Authorization': `Bearer ${token}`,
+// },
+// body: formData,
+// });
+
+// setUploadFile(null);
+// fetchFiles();
+// } catch (error) {
+// console.error('Gagal mengunggah file:', error);
+// }
+// };
+
+// // Download file
+// const downloadFile = async (key) => {
+// try {
+// const response = await fetch(`${API_BASE}/download?key=${encodeURIComponent(key)}`, {
+// method: 'GET',
+// headers: {
+// 'Authorization': `Bearer ${token}`,
+// },
+// });
+
+// if (!response.ok) throw new Error('Download gagal');
+
+// const blob = await response.blob();
+// const url = window.URL.createObjectURL(blob);
+
+// const a = document.createElement('a');
+// a.href = url;
+// a.download = key;
+// document.body.appendChild(a);
+// a.click();
+// a.remove();
+
+// window.URL.revokeObjectURL(url);
+// } catch (error) {
+// console.error('Gagal mengunduh file:', error);
+// }
+// };
+
+// useEffect(() => {
+// fetchFiles();
+// }, []);
+
+// return (
+//
+//
π Manajemen File
+
+//
+// {files.map(file => (
+// -
+// {file.Key}{' '}
+// {' '}
+//
+//
+// ))}
+//
+
+//
+
+//
+//
π€ Upload File
+// setUploadFile(e.target.files[0])}
+// />
+//
+//
+//
+// );
+// };
+
+// export default FileManager;