diff --git a/src/ChatBot.js b/src/ChatBot.js index 03af59b..ec095bc 100644 --- a/src/ChatBot.js +++ b/src/ChatBot.js @@ -34,7 +34,7 @@ const isOpenCamera = searchParams.get('camera') === 'open'; }, [existingConversation]); useEffect(() => { - if (!localStorage.getItem('session')) { + if (!sessionStorage.getItem('session')) { function generateUUID() { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { const r = Math.random() * 16 | 0; @@ -46,7 +46,7 @@ const isOpenCamera = searchParams.get('camera') === 'open'; const sessionId = generateUUID(); const dateNow = new Date().toISOString(); - localStorage.setItem('session', JSON.stringify({ sessionId: sessionId, lastSeen: dateNow })) + sessionStorage.setItem('session', JSON.stringify({ sessionId: sessionId, lastSeen: dateNow })) } }, []); @@ -65,7 +65,7 @@ const isOpenCamera = searchParams.get('camera') === 'open'; } const askToBot = async ({ type = 'text', content, tryCount = 0 }) => { - const session = JSON.parse(localStorage.getItem('session')); + const session = JSON.parse(sessionStorage.getItem('session')); if (!session || !session.sessionId) return; let body; @@ -143,7 +143,7 @@ const isOpenCamera = searchParams.get('camera') === 'open'; const message = textOverride || input.trim(); if (message === '') return; - const session = JSON.parse(localStorage.getItem('session')); + const session = JSON.parse(sessionStorage.getItem('session')); if ((!session || !session.name || !session.phoneNumber) && messages.length > 2) { setIsPoppedUp(message); setInput(''); @@ -331,10 +331,10 @@ const isOpenCamera = searchParams.get('camera') === 'open'; className={styles.nextButton} onClick={() => { if (name.length > 2 && phoneNumber.length >= 10) { - const sessionData = JSON.parse(localStorage.getItem('session')) || {}; + const sessionData = JSON.parse(sessionStorage.getItem('session')) || {}; sessionData.name = name; sessionData.phoneNumber = phoneNumber; - localStorage.setItem('session', JSON.stringify(sessionData)); + sessionStorage.setItem('session', JSON.stringify(sessionData)); setIsPoppedUp(''); sendMessage(isPoppedUp); } diff --git a/src/Dashboard.js b/src/Dashboard.js index 3c7eabf..31f931b 100644 --- a/src/Dashboard.js +++ b/src/Dashboard.js @@ -15,13 +15,18 @@ import NotificationPrompt from './NotificationPrompt'; const Dashboard = () => { const [isMenuOpen, setIsMenuOpen] = useState(false); - const chartRef = useRef(null); - const chartInstanceRef = useRef(null); + const weeklyChartRef = useRef(null); + const allTimeChartRef = useRef(null); + const weeklyChartInstanceRef = useRef(null); + const allTimeChartInstanceRef = useRef(null); + + const [weeklyData, setWeeklyData] = useState([]); + const [allTimeData, setAllTimeData] = useState([]); + const [conversations, setConversations] = useState([]); const [followUps, setFollowUps] = useState([]); const [discussedTopics, setDiscussedTopics] = useState([]); 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([]); @@ -177,6 +182,115 @@ const Dashboard = () => { }; }, []); + // Helper parse data function +function parseGraphData(graph) { + const prefixLabelMap = { + WEB: 'Web App', + TGG: 'Telegram', + WGG: 'Whatsapp', + IGG: 'Instagram', + }; + const prefixColors = { + WEB: { border: '#e2b834', background: 'rgba(226,184,52,0.6)' }, + TGG: { border: '#24A1DE', background: 'rgba(36,161,222,0.6)' }, + WGG: { border: '#25d366', background: 'rgba(37,211,102,0.6)' }, + IGG: { border: '#d62976', background: 'rgba(214,41,118,0.6)' }, + }; + + const rawDataArray = Object.entries(graph).map(([date, sesi]) => ({ + hour: date, + sesi, + })); + + rawDataArray.sort((a, b) => new Date(a.hour) - new Date(b.hour)); + + const prefixes = Object.keys(prefixLabelMap); + + // Format label: tanggal + nama bulan singkat Indonesia + const bulanIndoSingkat = ['Jan', 'Feb', 'Mar', 'Apr', 'Mei', 'Jun', 'Jul', 'Agu', 'Sep', 'Okt', 'Nov', 'Des']; + const labels = rawDataArray.map(d => { + const date = new Date(d.hour); + const tgl = date.getDate().toString().padStart(2, '0'); + const bln = bulanIndoSingkat[date.getMonth()]; + return `${tgl} ${bln}`; + }); + + const counts = {}; + prefixes.forEach(prefix => { + counts[prefix] = labels.map(() => 0); + }); + + rawDataArray.forEach(({ sesi }, index) => { + prefixes.forEach(prefix => { + if (typeof sesi[prefix] === 'number') { + counts[prefix][index] = sesi[prefix]; + } + }); + }); + + const datasets = prefixes.map(prefix => ({ + label: prefixLabelMap[prefix], + data: counts[prefix], + borderColor: prefixColors[prefix].border, + backgroundColor: prefixColors[prefix].background, + fill: true, + tension: 0.3, + })); + + return { labels, datasets }; +} + + + // Effect buat render weekly chart + useEffect(() => { + if (!weeklyData.labels || !weeklyChartRef.current) return; + + if (weeklyChartInstanceRef.current) { + weeklyChartInstanceRef.current.destroy(); + } + + const ctx = weeklyChartRef.current.getContext('2d'); + weeklyChartInstanceRef.current = new Chart(ctx, { + type: 'line', + data: { + labels: weeklyData.labels, + datasets: weeklyData.datasets, + }, + options: { + responsive: true, + plugins: { + legend: { position: 'bottom' }, + }, + scales: { y: { beginAtZero: true } }, + }, + }); + }, [weeklyData]); + + // Effect buat render all-time chart + useEffect(() => { + if (!allTimeData.labels || !allTimeChartRef.current) return; + + if (allTimeChartInstanceRef.current) { + allTimeChartInstanceRef.current.destroy(); + } + + const ctx = allTimeChartRef.current.getContext('2d'); + allTimeChartInstanceRef.current = new Chart(ctx, { + type: 'line', + data: { + labels: allTimeData.labels, + datasets: allTimeData.datasets, + }, + options: { + responsive: true, + plugins: { + legend: { position: 'bottom' }, + }, + scales: { y: { beginAtZero: true } }, + }, + }); + }, [allTimeData]); + useEffect(() => { const fetchData = async () => { const token = localStorage.getItem('token'); @@ -207,13 +321,19 @@ const Dashboard = () => { setFollowUps(data[0]?.graph[0]?.json?.result?.interested_users) setFileList(data[0]?.files) setUpdateDetected(data[1]?.updateDetected) + + + const result = data[0].graph[0].json.result; + + setWeeklyData(parseGraphData(result.weekly_graph || {})); + setAllTimeData(parseGraphData(result.all_time_graph || {})); + const graphObj = data[0]?.graph[0]?.json?.result?.graph; console.log(graphObj) const rawDataArray = Object.entries(graphObj).map(([hour, sesi]) => ({ hour, sesi, })); - setRawData(rawDataArray); let totalSessions = new Set(); let botMessages = 0; @@ -320,98 +440,6 @@ const Dashboard = () => { setModalContent(); }; - useEffect(() => { - if (!rawData.length) return; - - const ctx = chartRef.current?.getContext('2d'); - if (!ctx) return; - - if (chartInstanceRef.current) { - chartInstanceRef.current.destroy(); - } - - const prefixLabelMap = { - WEB: 'Web App', - TGG: 'Telegram', - WGG: 'Whatsapp', - IGG: 'Instagram', - }; - - const prefixColors = { - WEB: { border: '#e2b834', background: 'rgba(226,184,52,0.6)' }, - TGG: { border: '#24A1DE', background: 'rgba(36,161,222,0.6)' }, - WGG: { border: '#25d366', background: 'rgba(37,211,102,0.6)' }, - IGG: { border: '#d62976', background: 'rgba(214,41,118,0.6)' }, - }; - - const prefixes = Object.keys(prefixLabelMap); - 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'); - - - 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 => { - counts[prefix] = hours.map(() => 0); - }); - - rawData.forEach(({ sesi }, index) => { - prefixes.forEach(prefix => { - if (Array.isArray(sesi[prefix])) { - counts[prefix][index] = sesi[prefix].length; - } - }); - }); - - const datasets = prefixes.map(prefix => ({ - label: prefixLabelMap[prefix], - data: counts[prefix], - borderColor: prefixColors[prefix].border, - backgroundColor: prefixColors[prefix].background, - fill: true, - tension: 0.3, - })); - - chartInstanceRef.current = new Chart(ctx, { - type: 'line', - data: { - labels: hours, - datasets, - }, - options: { - responsive: true, - plugins: { - legend: { - display: true, - position: 'bottom', - labels: { - boxWidth: 15 - } - }, - }, - scales: { - y: { - beginAtZero: true, - }, - x: { - ticks: { - font: { - size: 10, // 👈 set your desired font size here - }, - }, - }, - }, - }, - }); - }, [rawData]); - const handleDeleteFile = async (key) => { if (!window.confirm(`Yakin ingin menghapus "${key}"?`)) return; const token = localStorage.getItem('token'); @@ -437,7 +465,7 @@ const Dashboard = () => { for (const key of selectedKeys) { try { const response = await fetch( - `https://bot.kediritechnopark.com/webhook/dermalounge/files/download?key=${encodeURIComponent(key)}`, + `https://bot.kediritechnopark.com/webhook/files/download?key=${encodeURIComponent(key)}`, { method: 'GET', headers: { @@ -479,7 +507,7 @@ const Dashboard = () => { const formData = new FormData(); formData.append('file', file, file.name); - const response = await fetch('https://bot.kediritechnopark.com/webhook/dermalounge/files/upload', { + const response = await fetch('https://bot.kediritechnopark.com/webhook/files/upload', { method: 'POST', headers: { 'Authorization': `Bearer ${token}` @@ -527,7 +555,7 @@ const Dashboard = () => { for (const key of selectedKeys) { try { const response = await fetch( - `https://bot.kediritechnopark.com/webhook/dermalounge/files/delete?key=${encodeURIComponent(key)}`, + `https://bot.kediritechnopark.com/webhook/files/delete?key=${encodeURIComponent(key)}`, { method: 'DELETE', headers: { @@ -641,7 +669,16 @@ const Dashboard = () => {

Interactions

- + +
+

Weekly Graph

+ +
+ +
+

All Time Graph

+ +

Update AI data

diff --git a/src/FollowUps.js b/src/FollowUps.js index 365d4dd..1f6fd1c 100644 --- a/src/FollowUps.js +++ b/src/FollowUps.js @@ -45,7 +45,7 @@ const FollowUps = ({ data: initialData }) => { } }; - // Gabungkan data berdasarkan contact_info + // Gabungkan data berdasarkan contact_info dan hilangkan note yang sama secara berurutan const mergedDataMap = new Map(); data.forEach(user => { @@ -61,10 +61,13 @@ const FollowUps = ({ data: initialData }) => { }); } else { const existing = mergedDataMap.get(key); - existing.notesList.push({ - note: user.notes, - created_at: user.created_at - }); + const lastNote = existing.notesList[existing.notesList.length - 1]; + if (!lastNote || lastNote.note !== user.notes) { + existing.notesList.push({ + note: user.notes, + created_at: user.created_at + }); + } // Prioritaskan status tertinggi existing.issuccess = existing.issuccess || user.issuccess; diff --git a/src/ProfileTab.js b/src/ProfileTab.js index 3a0b36c..4832624 100644 --- a/src/ProfileTab.js +++ b/src/ProfileTab.js @@ -11,7 +11,7 @@ const ProfileTab = () => { const [profileTemp, setProfileTemp] = useState({}); const licenses = [ - { id: 1, type: "Monthly Subscription", number: "DRML-2025-AI001", validUntil: "July 31 2025" }, + { id: 1, type: "MONTHLY SUBSCRIPTION", number: "DRML-2025-AI001", validUntil: "Aug 31 2025" }, ]; useEffect(() => { @@ -218,9 +218,9 @@ const ProfileTab = () => {
{licenses.map((item) => (
-

{item.type}

+

CURRENT PLAN

{item.number}

-

Free License Valid until: {item.validUntil}

+

{item.type} Valid until: {item.validUntil}

))}