diff --git a/src/components/Topbar.js b/src/components/Topbar.js index de559b8f..5c40afa0 100644 --- a/src/components/Topbar.js +++ b/src/components/Topbar.js @@ -1,16 +1,57 @@ -import React from "react"; +import React, { useState, useEffect, useRef } from "react"; +import ReactDOM from "react-dom"; import { useNavigate } from "react-router-dom"; import { logout, getCurrentUser } from "../services/authService"; -export default function Topbar({ onToggleNav }) { - const navigate = useNavigate(); - const currentUser = getCurrentUser(); +function NotificationDropdown() { + const [open, setOpen] = useState(false); + const btnRef = useRef(null); + const menuRef = useRef(null); + const [menuPos, setMenuPos] = useState({ top: 0, right: 0 }); - const handleLogout = () => { - logout(); - navigate("/login"); + useEffect(() => { + const handleClick = (e) => { + if (btnRef.current?.contains(e.target)) return; + if (menuRef.current?.contains(e.target)) return; + setOpen(false); + }; + document.addEventListener("mousedown", handleClick); + return () => document.removeEventListener("mousedown", handleClick); + }, []); + + const handleOpen = () => { + if (btnRef.current) { + const rect = btnRef.current.getBoundingClientRect(); + setMenuPos({ top: rect.bottom + 8, right: window.innerWidth - rect.right }); + } + setOpen(!open); }; + return ( + <> + + {open && ReactDOM.createPortal( +
+
Notifications
+
+ + No new notifications +
+
, + document.body + )} + + ); +} + +function UserDropdown({ currentUser, onLogout, onNavigate }) { + const [open, setOpen] = useState(false); + const btnRef = useRef(null); + const menuRef = useRef(null); + const [menuPos, setMenuPos] = useState({ top: 0, right: 0 }); + const getInitials = (fullname) => { if (!fullname) return "U"; const parts = fullname.split(" "); @@ -20,6 +61,78 @@ export default function Topbar({ onToggleNav }) { return fullname.substring(0, 2).toUpperCase(); }; + useEffect(() => { + const handleClick = (e) => { + if (btnRef.current?.contains(e.target)) return; + if (menuRef.current?.contains(e.target)) return; + setOpen(false); + }; + document.addEventListener("mousedown", handleClick); + return () => document.removeEventListener("mousedown", handleClick); + }, []); + + const handleOpen = () => { + if (btnRef.current) { + const rect = btnRef.current.getBoundingClientRect(); + setMenuPos({ top: rect.bottom + 8, right: window.innerWidth - rect.right }); + } + setOpen(!open); + }; + + const handleNav = (path) => { + setOpen(false); + onNavigate(path); + }; + + return ( + <> + + {open && ReactDOM.createPortal( +
+
+
{getInitials(currentUser?.fullname)}
+
+
{currentUser?.fullname || "User"}
+
{currentUser?.email || ""}
+
+
+
+ + + +
+ +
, + document.body + )} + + ); +} + +export default function Topbar({ onToggleNav }) { + const navigate = useNavigate(); + const currentUser = getCurrentUser(); + + const handleLogout = () => { + logout(); + navigate("/login"); + }; + return (
@@ -34,23 +147,9 @@ export default function Topbar({ onToggleNav }) {
- - +
- -
-
{getInitials(currentUser?.fullname)}
-
- {currentUser?.fullname || "User"} - {currentUser?.userRoles || ""} -
-
- - +
); diff --git a/src/index.css b/src/index.css index 2ad15eb0..5af3533b 100644 --- a/src/index.css +++ b/src/index.css @@ -263,6 +263,145 @@ body { background: rgba(255,100,100,0.1); } +.notifDropdown { + position: fixed; + background: #fff; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 8px 24px rgba(0,0,0,0.15); + z-index: 99999; + width: 300px; + overflow: hidden; +} + +.notifHeader { + padding: 12px 16px; + font-size: 14px; + font-weight: 600; + color: #333; + border-bottom: 1px solid #eee; +} + +.notifEmpty { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + padding: 30px 16px; + color: #999; + font-size: 13px; +} + +.notifEmptyIcon { + font-size: 24px; + color: #ccc; +} + +.topbarUserBtn { + display: flex; + align-items: center; + gap: 8px; + background: none; + border: none; + cursor: pointer; + padding: 4px 8px; + border-radius: 4px; +} + +.topbarUserBtn:hover { + background: rgba(255,255,255,0.1); +} + +.topbarUserCaret { + color: rgba(255,255,255,0.5); + font-size: 12px; + margin-left: 2px; +} + +.userDropdown { + position: fixed; + background: #fff; + border: 1px solid #ddd; + border-radius: 8px; + box-shadow: 0 8px 24px rgba(0,0,0,0.15); + z-index: 99999; + width: 260px; + overflow: hidden; +} + +.userDropdownHeader { + display: flex; + align-items: center; + gap: 12px; + padding: 16px; +} + +.userDropdownAvatar { + width: 40px; + height: 40px; + border-radius: 50%; + background: #1fa8de; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 14px; + color: #fff; + flex-shrink: 0; +} + +.userDropdownName { + font-size: 14px; + font-weight: 600; + color: #333; +} + +.userDropdownEmail { + font-size: 12px; + color: #999; +} + +.userDropdownDivider { + height: 1px; + background: #eee; +} + +.userDropdownItem { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + padding: 10px 16px; + border: none; + background: #fff; + font-size: 13px; + color: #333; + cursor: pointer; + text-align: left; +} + +.userDropdownItem:hover { + background: #f5f5f5; +} + +.userDropdownItem i { + width: 16px; + text-align: center; + color: #999; +} + +.userDropdownLogout { + color: #dc3545; +} + +.userDropdownLogout:hover { + background: #fff5f5; +} + +.userDropdownLogout i { + color: #dc3545; +} + .topbarDivider { width: 1px; height: 24px;