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