mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-26 18:42:49 +00:00
340 lines
13 KiB
TypeScript
340 lines
13 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
ChevronDown,
|
|
Globe,
|
|
LogOut,
|
|
User,
|
|
Key,
|
|
Home,
|
|
Menu,
|
|
} from "lucide-react";
|
|
import { Button } from "@/shared/common/ui/button";
|
|
import { Avatar, AvatarFallback } from "@/shared/common/ui/avatar";
|
|
import { useAuthUser } from "@/shared/hooks/useAuthUser";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
} from "@/shared/common/ui/dropdown-menu";
|
|
import { useTranslation } from "react-i18next";
|
|
import { NavLink, useNavigate, useLocation } from "react-router-dom";
|
|
import { useRef, useEffect, useState } from "react";
|
|
import { FiBell } from "react-icons/fi";
|
|
import { useUser } from "@/shared/context/UserContext";
|
|
import {
|
|
UI_LANGUAGE_OPTIONS,
|
|
getUiLanguageLabel,
|
|
resolveUiLanguage,
|
|
} from "@/shared/i18n/uiLanguages";
|
|
import { useNotifications } from "@/shared/hooks/useNotification";
|
|
import NotificationList from "@/record-management/components/NotificationList";
|
|
import {
|
|
resolveModuleConfig,
|
|
useTenantConfig,
|
|
} from "@/layout/components/TenantConfig";
|
|
import {
|
|
Tooltip,
|
|
TooltipContent,
|
|
TooltipTrigger,
|
|
} from "@/shared/common/ui/tooltip";
|
|
|
|
export const TopBar = () => {
|
|
const { t, i18n } = useTranslation();
|
|
const navigate = useNavigate();
|
|
const { pathname } = useLocation();
|
|
const { logout } = useAuthUser();
|
|
const userDetails = useUser();
|
|
const { config: tenantConfig } = useTenantConfig();
|
|
const moduleConfig = resolveModuleConfig(tenantConfig);
|
|
const isAdminModuleVisible = userDetails?.roles?.some(
|
|
(role: { key: string }) =>
|
|
role.key === "unit_admin" ||
|
|
role.key === "organization_admin" ||
|
|
role.key === "super_admin",
|
|
);
|
|
const visibleModuleCount = [
|
|
moduleConfig.recordManagement,
|
|
moduleConfig.performance,
|
|
moduleConfig.objective,
|
|
moduleConfig.dms,
|
|
moduleConfig.siteManagement && isAdminModuleVisible,
|
|
].filter(Boolean).length;
|
|
const shouldShowHomePageLink = visibleModuleCount > 1;
|
|
const fullName = userDetails?.name?.en || t("user");
|
|
const splittedName = fullName.trim().split(" ");
|
|
const initials =
|
|
splittedName.length === 1
|
|
? splittedName[0][0]
|
|
: `${splittedName[0][0]}${splittedName[1][0]}`;
|
|
|
|
const changeLanguage = (lng: string) => {
|
|
i18n.changeLanguage(lng);
|
|
};
|
|
const currentLanguage = resolveUiLanguage(i18n.language);
|
|
const handleLogout = () => {
|
|
logout();
|
|
};
|
|
|
|
const userRoles = userDetails?.roles?.map((role: any) => role.key) || [];
|
|
const isSuperAdmin = userRoles.includes("super_admin");
|
|
const isOrgAdmin =
|
|
userRoles.includes("unit_admin") ||
|
|
userRoles.includes("organization_admin");
|
|
|
|
const modulesList = [
|
|
{
|
|
id: "homepage",
|
|
label: t("nav.homePage", "Home Page"),
|
|
path: "/homepage",
|
|
},
|
|
...(moduleConfig.recordManagement
|
|
? [
|
|
{
|
|
id: "recordManagement",
|
|
label: t("nav.Record Management", "Record Management"),
|
|
path: "/record-management/dashboard",
|
|
},
|
|
]
|
|
: []),
|
|
...(moduleConfig.performance
|
|
? [
|
|
{
|
|
id: "performanceManagement",
|
|
label: t("nav.PerformanceManagement", "Performance Management"),
|
|
path: "/performance-management/plan-years",
|
|
},
|
|
]
|
|
: []),
|
|
...(moduleConfig.objective
|
|
? [
|
|
{
|
|
id: "objectiveManagement",
|
|
label: t("nav.objectiveManagement", "Objective Management"),
|
|
path: "/objective-management/plan-years",
|
|
},
|
|
]
|
|
: []),
|
|
...(moduleConfig.dms
|
|
? [
|
|
{
|
|
id: "documentManagement",
|
|
label: t("nav.DocumentManagement", "Document Management"),
|
|
path: "/dms/dashboard",
|
|
},
|
|
]
|
|
: []),
|
|
...(isOrgAdmin && moduleConfig.siteManagement
|
|
? [
|
|
{
|
|
id: "orgAdmin",
|
|
label: t("nav.admin", "Admin"),
|
|
path: "/user-management/user_management-dashboard",
|
|
},
|
|
]
|
|
: []),
|
|
...(isSuperAdmin
|
|
? [
|
|
{
|
|
id: "superAdmin",
|
|
label: t("OrganizationAdmin", "Super Admin"),
|
|
path: "/user-management/dashboard",
|
|
},
|
|
]
|
|
: []),
|
|
];
|
|
|
|
const [openNotifications, setOpenNotifications] = useState(false);
|
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
|
|
const { unseenCount, refresh } = useNotifications({
|
|
take: 1,
|
|
skip: 0,
|
|
orderBy: "createdAt:DESC",
|
|
});
|
|
|
|
// Close notification panel on outside click
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (
|
|
dropdownRef.current &&
|
|
!dropdownRef.current.contains(event.target as Node)
|
|
) {
|
|
setOpenNotifications(false);
|
|
}
|
|
};
|
|
document.addEventListener("mousedown", handleClickOutside);
|
|
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
}, []);
|
|
|
|
return (
|
|
<>
|
|
<header className="fixed top-0 left-0 right-0 z-50 flex justify-between items-center gap-3 px-6 py-3 border-b dark:border-gray-800 bg-white dark:bg-gray-900 shadow-sm">
|
|
<div className="flex items-center gap-2">
|
|
<DropdownMenu>
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-9 w-9 flex-shrink-0 rounded-xl bg-white/90 text-gray-600 shadow-sm ring-1 ring-inset ring-black/5 transition-colors hover:bg-primary-50 hover:text-primary-700 hover:ring-primary-200 dark:bg-gray-800/90 dark:text-gray-300 dark:ring-white/10 dark:hover:bg-primary-900/20 dark:hover:text-primary-300 dark:hover:ring-primary-700/40"
|
|
aria-label={t("nav.moduleNavigationHint")}
|
|
title={t("nav.moduleNavigationHint")}
|
|
>
|
|
<Menu className="h-5 w-5" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="bottom" sideOffset={6}>
|
|
{t("nav.moduleNavigationHint")}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
<DropdownMenuContent
|
|
align="start"
|
|
className="w-56 dark:bg-gray-900 dark:border-gray-800"
|
|
>
|
|
{modulesList
|
|
.filter((item) => {
|
|
const currentPath = pathname;
|
|
if (item.id === "homepage") {
|
|
return !currentPath.startsWith("/homepage");
|
|
}
|
|
if (item.id === "recordManagement") {
|
|
return !currentPath.startsWith("/record-management");
|
|
}
|
|
if (item.id === "performanceManagement") {
|
|
return !currentPath.startsWith("/performance-management");
|
|
}
|
|
if (item.id === "objectiveManagement") {
|
|
return !currentPath.startsWith("/objective-management");
|
|
}
|
|
if (item.id === "documentManagement") {
|
|
return !currentPath.startsWith("/dms");
|
|
}
|
|
if (item.id === "complaints") {
|
|
return !currentPath.startsWith("/complaints");
|
|
}
|
|
if (item.id === "orgAdmin" || item.id === "superAdmin") {
|
|
return !currentPath.startsWith("/user-management");
|
|
}
|
|
return true;
|
|
})
|
|
.map((item) => (
|
|
<DropdownMenuItem
|
|
key={item.id}
|
|
onClick={() => navigate(item.path)}
|
|
className="cursor-pointer hover:bg-primary-50 dark:hover:bg-primary-900/30 hover:text-primary-700 dark:hover:text-primary-400 py-2.5"
|
|
>
|
|
<span>{item.label}</span>
|
|
</DropdownMenuItem>
|
|
))}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-3">
|
|
|
|
{/* Notification bell */}
|
|
<div className="relative" ref={dropdownRef}>
|
|
<Button
|
|
variant="ghost"
|
|
onClick={() => {
|
|
setOpenNotifications((prev) => {
|
|
const next = !prev;
|
|
if (next) refresh();
|
|
return next;
|
|
});
|
|
}}
|
|
aria-label="Toggle Notifications"
|
|
className="p-2 hover:bg-primary-50 dark:hover:bg-primary-900/30 rounded-full transition-colors duration-200 relative">
|
|
<FiBell className="w-5 h-5 text-gray-700 dark:text-gray-300" />
|
|
{unseenCount > 0 && (
|
|
<span className="absolute -top-1 -right-1 min-w-4.5 rounded-full bg-red-500 px-1.5 py-0.5 text-center text-[10px] font-bold text-white shadow">
|
|
{unseenCount > 99 ? "99+" : unseenCount}
|
|
</span>
|
|
)}
|
|
</Button>
|
|
|
|
{openNotifications && (
|
|
<div className="absolute right-0 z-50 mt-2 w-88 overflow-hidden rounded-2xl bg-white shadow-2xl ring-1 ring-black/5 dark:bg-gray-900 dark:ring-white/10 sm:w-96">
|
|
<NotificationList />
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<Button
|
|
variant="ghost"
|
|
className="gap-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-primary-50 dark:hover:bg-primary-900/30 hover:text-primary-700 dark:hover:text-primary-400 transition-colors duration-200 font-medium">
|
|
<Globe className="w-4 h-4" />
|
|
{getUiLanguageLabel(currentLanguage, t)}
|
|
<ChevronDown className="w-4 h-4" />
|
|
</Button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="end"
|
|
className="min-w-[140px] dark:bg-gray-900 dark:border-gray-800">
|
|
<DropdownMenuItem
|
|
onClick={() => changeLanguage("en")}
|
|
className="cursor-pointer hover:bg-primary-50 dark:hover:bg-primary-900/30 hover:text-primary-700 dark:hover:text-primary-400">
|
|
English
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
onClick={() => changeLanguage("am")}
|
|
className="cursor-pointer hover:bg-primary-50 dark:hover:bg-primary-900/30 hover:text-primary-700 dark:hover:text-primary-400">
|
|
አማርኛ
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<div className="flex items-center gap-3 cursor-pointer hover:bg-primary-50 dark:hover:bg-primary-900/30 rounded-lg px-3 py-2 transition-colors duration-200 group">
|
|
<Avatar className="w-9 h-9 border-2 border-primary-100 dark:border-primary-900 group-hover:border-primary-200 dark:group-hover:border-primary-800 transition-colors">
|
|
<AvatarFallback className="bg-primary-100 dark:bg-primary-900 text-primary-700 dark:text-primary-400 font-semibold text-sm">
|
|
{initials.toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<div className="text-xs text-right">
|
|
<div className="font-semibold text-gray-900 dark:text-gray-100">
|
|
{userDetails?.name?.en}
|
|
</div>
|
|
<div className="text-gray-500 dark:text-gray-400 text-[11px]">
|
|
@{userDetails?.username}
|
|
</div>
|
|
</div>
|
|
<ChevronDown className="w-4 h-4 text-gray-500 dark:text-gray-400 group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors" />
|
|
</div>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="end"
|
|
className="w-52 dark:bg-gray-900 dark:border-gray-800">
|
|
<DropdownMenuItem
|
|
onClick={() => navigate("/profile")}
|
|
className="cursor-pointer hover:bg-primary-50 dark:hover:bg-primary-900/30 hover:text-primary-700 dark:hover:text-primary-400 py-2.5">
|
|
<User className="w-4 h-4 mr-3" />
|
|
{t("header.viewProfile")}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
onClick={() => navigate("/dashboard/profile")}
|
|
className="cursor-pointer hover:bg-primary-50 dark:hover:bg-primary-900/30 hover:text-primary-700 dark:hover:text-primary-400 py-2.5">
|
|
<Key className="w-4 h-4 mr-3" />
|
|
{t("header.changePassword")}
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem
|
|
onClick={handleLogout}
|
|
className="cursor-pointer hover:bg-red-50 dark:hover:bg-red-900/30 hover:text-red-700 dark:hover:text-red-400 py-2.5">
|
|
<LogOut className="w-4 h-4 mr-3" />
|
|
{t("header.signOut")}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</header>
|
|
</>
|
|
);
|
|
};
|