131 lines
4.6 KiB
JavaScript
131 lines
4.6 KiB
JavaScript
// web/js/components/NotificationDropdown.js
|
|
const NotificationDropdown = () => {
|
|
const [notifications, setNotifications] = React.useState([]);
|
|
const [unreadCount, setUnreadCount] = React.useState(0);
|
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
const [loading, setLoading] = React.useState(false);
|
|
|
|
// Load notifications periodically
|
|
React.useEffect(() => {
|
|
loadNotifications();
|
|
const interval = setInterval(loadNotifications, 30000); // Refresh tiap 30 detik
|
|
return () => clearInterval(interval);
|
|
}, []);
|
|
|
|
const loadNotifications = async () => {
|
|
try {
|
|
const response = await fetch(
|
|
`${CONFIG.API_URL}${CONFIG.API_ENDPOINTS.NOTIFICATIONS}?limit=5`,
|
|
{
|
|
headers: { Authorization: `Bearer ${AuthUtils.getToken()}` },
|
|
}
|
|
);
|
|
if (response.ok) {
|
|
const result = await response.json();
|
|
setNotifications(result.data.notifications || []);
|
|
setUnreadCount(result.data.unread_count || 0);
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to load notifications", error);
|
|
}
|
|
};
|
|
|
|
const handleMarkAsRead = async (id) => {
|
|
try {
|
|
await fetch(
|
|
`${CONFIG.API_URL}${CONFIG.API_ENDPOINTS.NOTIFICATIONS}/${id}/read`,
|
|
{
|
|
method: "PATCH",
|
|
headers: { Authorization: `Bearer ${AuthUtils.getToken()}` },
|
|
}
|
|
);
|
|
loadNotifications();
|
|
} catch (error) {
|
|
console.error("Error marking as read", error);
|
|
}
|
|
};
|
|
|
|
const handleItemClick = (notif) => {
|
|
handleMarkAsRead(notif.id);
|
|
// Redirect logic based on type
|
|
if (notif.entity_type === "claim") {
|
|
// Trigger event custom agar UserApp bisa membuka tab Claims
|
|
window.dispatchEvent(new CustomEvent("open-my-claims"));
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="relative">
|
|
{/* Bell Icon */}
|
|
<button
|
|
onClick={() => setIsOpen(!isOpen)}
|
|
className="relative p-2 text-slate-300 hover:text-white transition"
|
|
>
|
|
<span className="text-2xl">🔔</span>
|
|
{unreadCount > 0 && (
|
|
<span className="absolute top-0 right-0 bg-red-500 text-white text-[10px] font-bold px-1.5 py-0.5 rounded-full border-2 border-slate-900 animate-pulse">
|
|
{unreadCount}
|
|
</span>
|
|
)}
|
|
</button>
|
|
|
|
{/* Dropdown Content */}
|
|
{isOpen && (
|
|
<>
|
|
<div
|
|
className="fixed inset-0 z-40"
|
|
onClick={() => setIsOpen(false)}
|
|
></div>
|
|
<div className="absolute right-0 mt-2 w-80 bg-slate-800 border border-slate-700 rounded-xl shadow-2xl z-50 overflow-hidden animate-fade-in">
|
|
<div className="p-3 border-b border-slate-700 flex justify-between items-center bg-slate-900/50">
|
|
<h3 className="font-semibold text-white text-sm">Notifikasi</h3>
|
|
<button
|
|
onClick={loadNotifications}
|
|
className="text-xs text-blue-400 hover:text-blue-300"
|
|
>
|
|
Refresh
|
|
</button>
|
|
</div>
|
|
|
|
<div className="max-h-80 overflow-y-auto">
|
|
{notifications.length === 0 ? (
|
|
<div className="p-4 text-center text-slate-500 text-sm">
|
|
Belum ada notifikasi
|
|
</div>
|
|
) : (
|
|
notifications.map((notif) => (
|
|
<div
|
|
key={notif.id}
|
|
onClick={() => handleItemClick(notif)}
|
|
className={`p-3 border-b border-slate-700 cursor-pointer hover:bg-slate-700 transition ${
|
|
!notif.is_read
|
|
? "bg-slate-700/30 border-l-4 border-l-blue-500"
|
|
: ""
|
|
}`}
|
|
>
|
|
<div className="flex justify-between items-start mb-1">
|
|
<strong
|
|
className={`text-sm ${
|
|
!notif.is_read ? "text-blue-400" : "text-slate-300"
|
|
}`}
|
|
>
|
|
{notif.title}
|
|
</strong>
|
|
<span className="text-[10px] text-slate-500">
|
|
{new Date(notif.created_at).toLocaleDateString()}
|
|
</span>
|
|
</div>
|
|
<p className="text-xs text-slate-400 line-clamp-2">
|
|
{notif.message}
|
|
</p>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|