feat: sidebar uses menu permissions API for role-based visibility
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import Link from 'next/link';
|
||||
import { usePathname, useRouter } from 'next/navigation';
|
||||
import {
|
||||
@@ -26,19 +26,38 @@ import {
|
||||
Shield,
|
||||
} from 'lucide-react';
|
||||
import { useAuthStore } from '@/stores';
|
||||
import apiClient from '@/services/apiClient';
|
||||
import type { MenuPermission } from '@/types';
|
||||
|
||||
/**
|
||||
* SR 시스템의 메인 사이드바 컴포넌트
|
||||
* ITAM 하위 메뉴를 포함하도록 고도화되었습니다.
|
||||
*/
|
||||
const Sidebar = ({ isCollapsed }: { isCollapsed?: boolean }) => {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
const [isItamOpen, setIsItamOpen] = useState(pathname.startsWith('/itam'));
|
||||
const [isAdminOpen, setIsAdminOpen] = useState(pathname.startsWith('/settings'));
|
||||
const [menuPerms, setMenuPerms] = useState<Record<string, MenuPermission>>({});
|
||||
const [permsLoaded, setPermsLoaded] = useState(false);
|
||||
const user = useAuthStore((s) => s.user);
|
||||
const role = user?.roleCode;
|
||||
const isMgmt = role === 'MANAGER' || role === 'ADMIN';
|
||||
|
||||
useEffect(() => {
|
||||
apiClient.get('/menu-permissions').then((res: any) => {
|
||||
if (res?.success && res.data) {
|
||||
const map: Record<string, MenuPermission> = {};
|
||||
res.data.forEach((p: MenuPermission) => { map[p.menuKey] = p; });
|
||||
setMenuPerms(map);
|
||||
}
|
||||
}).catch(() => {}).finally(() => setPermsLoaded(true));
|
||||
}, []);
|
||||
|
||||
const canAccess = (menuKey: string): boolean => {
|
||||
if (!permsLoaded) return true; // show all until loaded
|
||||
const perm = menuPerms[menuKey];
|
||||
if (!perm) return true;
|
||||
try {
|
||||
const roles: string[] = JSON.parse(perm.allowedRoles);
|
||||
return role ? roles.includes(role) : false;
|
||||
} catch { return true; }
|
||||
};
|
||||
|
||||
const menuItems = [
|
||||
{ name: 'Dashboard', href: '/', icon: LayoutDashboard },
|
||||
@@ -103,8 +122,8 @@ const Sidebar = ({ isCollapsed }: { isCollapsed?: boolean }) => {
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Projects link (management only) */}
|
||||
{isMgmt && (
|
||||
{/* Projects */}
|
||||
{canAccess('projects') && (
|
||||
<Link
|
||||
href="/projects"
|
||||
className={`flex items-center rounded-xl font-bold text-xs transition-all ${
|
||||
@@ -121,6 +140,7 @@ const Sidebar = ({ isCollapsed }: { isCollapsed?: boolean }) => {
|
||||
)}
|
||||
|
||||
{/* ITAM Group Menu */}
|
||||
{canAccess('itam') && (
|
||||
<div className="space-y-1">
|
||||
<button
|
||||
onClick={() => !isCollapsed && setIsItamOpen(!isItamOpen)}
|
||||
@@ -165,6 +185,7 @@ const Sidebar = ({ isCollapsed }: { isCollapsed?: boolean }) => {
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Link
|
||||
href="/work-reports"
|
||||
@@ -194,7 +215,7 @@ const Sidebar = ({ isCollapsed }: { isCollapsed?: boolean }) => {
|
||||
{!isCollapsed && <span className="whitespace-nowrap">Daily Report</span>}
|
||||
</Link>
|
||||
|
||||
{isMgmt && (
|
||||
{canAccess('reports-review') && (
|
||||
<Link
|
||||
href="/work-reports/review"
|
||||
className={`flex items-center rounded-xl font-bold text-xs transition-all ${
|
||||
@@ -210,7 +231,7 @@ const Sidebar = ({ isCollapsed }: { isCollapsed?: boolean }) => {
|
||||
</Link>
|
||||
)}
|
||||
|
||||
{role === 'ADMIN' && (
|
||||
{canAccess('settings') && (
|
||||
<>
|
||||
<button
|
||||
onClick={() => !isCollapsed && setIsAdminOpen(!isAdminOpen)}
|
||||
@@ -272,7 +293,7 @@ const Sidebar = ({ isCollapsed }: { isCollapsed?: boolean }) => {
|
||||
{!isCollapsed && <span className="whitespace-nowrap">Calendar</span>}
|
||||
</Link>
|
||||
|
||||
{isMgmt && (
|
||||
{canAccess('employees') && (
|
||||
<Link
|
||||
href="/employees"
|
||||
className={`flex items-center rounded-xl font-bold text-xs transition-all ${
|
||||
|
||||
Reference in New Issue
Block a user