ok
This commit is contained in:
@@ -7,6 +7,7 @@ import Conversations from './Conversations';
|
|||||||
import DiscussedTopics from './DiscussedTopics';
|
import DiscussedTopics from './DiscussedTopics';
|
||||||
|
|
||||||
import Chart from 'chart.js/auto';
|
import Chart from 'chart.js/auto';
|
||||||
|
import NotificationPrompt from './NotificationPrompt';
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
@@ -46,6 +47,25 @@ const Dashboard = () => {
|
|||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
localStorage.removeItem('token');
|
localStorage.removeItem('token');
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
|
|
||||||
|
navigator.serviceWorker.ready.then(function (registration) {
|
||||||
|
registration.pushManager.getSubscription().then(function (subscription) {
|
||||||
|
if (subscription) {
|
||||||
|
subscription.unsubscribe().then(function (successful) {
|
||||||
|
console.log('Push subscription unsubscribed on logout:', successful);
|
||||||
|
// Optional: also notify backend to clear the token
|
||||||
|
fetch('/api/clear-subscription', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ endpoint: subscription.endpoint }),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -79,8 +99,7 @@ const Dashboard = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response.status === 401 || response.status === 403) {
|
if (response.status === 401 || response.status === 403) {
|
||||||
localStorage.removeItem('token');
|
handleLogout();
|
||||||
navigate('/login');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,8 +145,19 @@ const Dashboard = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (!checkOnce && 'serviceWorker' in navigator) {
|
if (!checkOnce && 'serviceWorker' in navigator) {
|
||||||
subscribeUser();
|
navigator.serviceWorker.ready.then(function (registration) {
|
||||||
setCheckOnce(false);
|
registration.pushManager.getSubscription().then(function (subscription) {
|
||||||
|
setCheckOnce(false);
|
||||||
|
if (subscription === null) {
|
||||||
|
// Not subscribed yet — show modal asking user to subscribe
|
||||||
|
setModalContent(<NotificationPrompt onAllow={subscribeUser} onDismiss={null} />);
|
||||||
|
} else {
|
||||||
|
// Already subscribed
|
||||||
|
setModalContent('')
|
||||||
|
console.log('User is already subscribed.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchData(); // Jalankan langsung saat komponen di-mount
|
fetchData(); // Jalankan langsung saat komponen di-mount
|
||||||
@@ -159,6 +189,7 @@ const Dashboard = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
setModalContent('')
|
||||||
};
|
};
|
||||||
|
|
||||||
function urlBase64ToUint8Array(base64String) {
|
function urlBase64ToUint8Array(base64String) {
|
||||||
@@ -189,17 +220,22 @@ const Dashboard = () => {
|
|||||||
const prefixLabelMap = {
|
const prefixLabelMap = {
|
||||||
WEB: 'Web App',
|
WEB: 'Web App',
|
||||||
TGG: 'Telegram',
|
TGG: 'Telegram',
|
||||||
DME: 'Instagram',
|
WGG: 'Whatsapp',
|
||||||
};
|
};
|
||||||
|
|
||||||
const prefixColors = {
|
const prefixColors = {
|
||||||
WEB: { border: '#4285F4', background: 'rgba(66, 133, 244, 0.2)' },
|
WEB: { border: '#4285F4', background: 'rgba(66, 133, 244, 0.2)' },
|
||||||
TGG: { border: '#25D366', background: 'rgba(37, 211, 102, 0.2)' },
|
TGG: { border: '#25D366', background: 'rgba(37, 211, 102, 0.2)' },
|
||||||
DME: { border: '#AA00FF', background: 'rgba(170, 0, 255, 0.2)' },
|
WGG: { border: '#AA00FF', background: 'rgba(170, 0, 255, 0.2)' },
|
||||||
};
|
};
|
||||||
|
|
||||||
const prefixes = Object.keys(prefixLabelMap);
|
const prefixes = Object.keys(prefixLabelMap);
|
||||||
const hours = rawData.map(d => d.hour).sort((a, b) => parseFloat(a) - parseFloat(b));
|
const hours = rawData.map(d => d.hour.split(' ')[1]).sort((a, b) => {
|
||||||
|
// Sort berdasarkan jam dan menit
|
||||||
|
const [h1, m1] = a.split(':').map(Number);
|
||||||
|
const [h2, m2] = b.split(':').map(Number);
|
||||||
|
return h1 !== h2 ? h1 - h2 : m1 - m2;
|
||||||
|
});
|
||||||
|
|
||||||
const counts = {};
|
const counts = {};
|
||||||
prefixes.forEach(prefix => {
|
prefixes.forEach(prefix => {
|
||||||
@@ -298,7 +334,7 @@ const Dashboard = () => {
|
|||||||
<div className={styles.statsGrid}>
|
<div className={styles.statsGrid}>
|
||||||
<div className={styles.statCard} onClick={openConversationsModal}>
|
<div className={styles.statCard} onClick={openConversationsModal}>
|
||||||
<h2>{stats.totalChats}</h2>
|
<h2>{stats.totalChats}</h2>
|
||||||
<p>Total Percakapan Hari Ini</p>
|
<p>Total Percakapan selama 24 jam</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.statCard}>
|
<div className={styles.statCard}>
|
||||||
<h2>{stats.userMessages}</h2>
|
<h2>{stats.userMessages}</h2>
|
||||||
|
|||||||
23
src/NotificationPrompt.js
Normal file
23
src/NotificationPrompt.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import styles from './NotificationPrompt.module.css';
|
||||||
|
|
||||||
|
export default function NotificationPrompt({ onAllow, onDismiss }) {
|
||||||
|
return (
|
||||||
|
<div className={styles.backdrop}>
|
||||||
|
<div className={styles.modal}>
|
||||||
|
<h2 className={styles.title}>Enable Notifications</h2>
|
||||||
|
<p className={styles.description}>
|
||||||
|
Stay up to date with important updates and alerts. Enable push notifications to never miss a thing.
|
||||||
|
</p>
|
||||||
|
<div className={styles.actions}>
|
||||||
|
<button onClick={onAllow} className={styles.primaryButton}>
|
||||||
|
Enable Notifications
|
||||||
|
</button>
|
||||||
|
<button onClick={onDismiss} className={styles.secondaryButton}>
|
||||||
|
Maybe Later
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
68
src/NotificationPrompt.module.css
Normal file
68
src/NotificationPrompt.module.css
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
.backdrop {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.45);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 9999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 32px;
|
||||||
|
max-width: 420px;
|
||||||
|
width: 90%;
|
||||||
|
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #4b5563;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primaryButton {
|
||||||
|
background-color: #0073bb;
|
||||||
|
color: white;
|
||||||
|
font-weight: 500;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.primaryButton:hover {
|
||||||
|
background-color: #005a99;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondaryButton {
|
||||||
|
background-color: transparent;
|
||||||
|
color: #374151;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondaryButton:hover {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user