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 */} + + + + + + + + + {fileList.map((file, index) => ( + + + + + ))} + +
+ 0 && selectedKeys.length === fileList.length} + onChange={(e) => { + if (e.target.checked) { + setSelectedKeys(fileList.map((f) => f.json.Key)); + } else { + setSelectedKeys([]); + } + }} + /> + select all
+ { + setSelectedKeys((prev) => + prev.includes(file.json.Key) + ? prev.filter((key) => key !== file.json.Key) + : [...prev, file.json.Key] + ); + }} + /> + {file.json.Key}
+

- 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;