Basdat/web/js/components/NotificationDropdown.js
2025-12-20 00:01:08 +07:00

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