diff --git a/fems-app/src/app/(admin)/layout.tsx b/fems-app/src/app/(admin)/layout.tsx index 51746a8..a9ac5fd 100644 --- a/fems-app/src/app/(admin)/layout.tsx +++ b/fems-app/src/app/(admin)/layout.tsx @@ -1,13 +1,10 @@ // src/app/(admin)/layout.tsx +import React from "react"; import AdminGuard from "@/components/auth/AdminGuard"; import { SideNav } from "@/components/layout/SideNav"; import { TopNav } from "@/components/layout/TopNav"; -export default function AdminLayout({ - children, -}: { - children: React.ReactNode; -}) { +const AdminLayout = ({ children }: { children: React.ReactNode }) => { return (
@@ -29,4 +26,6 @@ export default function AdminLayout({
); -} +}; + +export default AdminLayout; diff --git a/fems-app/src/app/(admin)/users/accounts/page.tsx b/fems-app/src/app/(admin)/users/accounts/page.tsx index 5780d4b..6be2046 100644 --- a/fems-app/src/app/(admin)/users/accounts/page.tsx +++ b/fems-app/src/app/(admin)/users/accounts/page.tsx @@ -22,6 +22,7 @@ import { AxiosError } from "axios"; import { UserForm } from "./components/UserForm"; import { Switch } from "@/components/ui/switch"; import { User } from "@/types/user"; +import AdminGuard from "@/components/auth/AdminGuard"; const AccountsPage = () => { const { token, user } = useAuthStore(); @@ -225,71 +226,73 @@ const AccountsPage = () => { if (isLoading) return
Loading...
; return ( -
- {/* Header */} -
-
-

유저 관리

-

- 유저를 관리하고 권한을 설정합니다. -

+ +
+ {/* Header */} +
+
+

유저 관리

+

+ 유저를 관리하고 권한을 설정합니다. +

+
+
- + + {/* Users Table */} + + + 유저 목록 + + + {users && users.length > 0 ? ( + + ) : ( +
+ 등록된 유저가 없습니다. +
+ )} +
+
+ + {/* User Create/Edit Dialog */} + { + setIsOpen(open); + if (!open) setEditingUser(null); + }} + > + + + {editingUser ? "유저 수정" : "새 유저"} + + {editingUser + ? "기존 유저 정보를 수정합니다." + : "새로운 유저를 생성합니다."} + + + { + if (editingUser) { + updateMutation.mutate({ id: editingUser.id, ...data }); + } else { + createMutation.mutate(data); + } + }} + onCancel={() => { + setIsOpen(false); + setEditingUser(null); + }} + /> + +
- - {/* Users Table */} - - - 유저 목록 - - - {users && users.length > 0 ? ( - - ) : ( -
- 등록된 유저가 없습니다. -
- )} -
-
- - {/* User Create/Edit Dialog */} - { - setIsOpen(open); - if (!open) setEditingUser(null); - }} - > - - - {editingUser ? "유저 수정" : "새 유저"} - - {editingUser - ? "기존 유저 정보를 수정합니다." - : "새로운 유저를 생성합니다."} - - - { - if (editingUser) { - updateMutation.mutate({ id: editingUser.id, ...data }); - } else { - createMutation.mutate(data); - } - }} - onCancel={() => { - setIsOpen(false); - setEditingUser(null); - }} - /> - - -
+ ); }; diff --git a/fems-app/src/components/auth/AdminGuard.tsx b/fems-app/src/components/auth/AdminGuard.tsx index 7c8e655..eb16471 100644 --- a/fems-app/src/components/auth/AdminGuard.tsx +++ b/fems-app/src/components/auth/AdminGuard.tsx @@ -1,13 +1,52 @@ +// // src/components/auth/AdminGuard.tsx +// "use client"; + +// import { usePermissions } from "@/hooks/usePermissions"; +// import { ReactNode } from "react"; + +// interface AdminGuardProps { +// children: ReactNode; +// requiredPermissions?: string[]; // 필요한 권한 목록 +// requireAll?: boolean; // true면 모든 권한 필요, false면 하나라도 있으면 됨 +// } + +// export default function AdminGuard({ +// children, +// requiredPermissions = [], +// requireAll = false, +// }: AdminGuardProps) { +// const { hasAllPermissions, hasAnyPermission } = usePermissions(); + +// const hasAccess = +// requiredPermissions.length === 0 +// ? true +// : requireAll +// ? hasAllPermissions(requiredPermissions) +// : hasAnyPermission(requiredPermissions); + +// if (!hasAccess) { +// return ( +//
+// 접근 권한이 없습니다. +//
+// ); +// } + +// return <>{children}; +// } + // src/components/auth/AdminGuard.tsx "use client"; +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; import { usePermissions } from "@/hooks/usePermissions"; -import { ReactNode } from "react"; +import { useAuth } from "@/hooks/useAuth"; interface AdminGuardProps { - children: ReactNode; - requiredPermissions?: string[]; // 필요한 권한 목록 - requireAll?: boolean; // true면 모든 권한 필요, false면 하나라도 있으면 됨 + children: React.ReactNode; + requiredPermissions?: string[]; + requireAll?: boolean; } export default function AdminGuard({ @@ -15,8 +54,38 @@ export default function AdminGuard({ requiredPermissions = [], requireAll = false, }: AdminGuardProps) { + const router = useRouter(); + const { user } = useAuth(); const { hasAllPermissions, hasAnyPermission } = usePermissions(); + useEffect(() => { + // 사용자가 없거나 로그인하지 않은 경우 + if (!user) { + router.push("/login"); + return; + } + + // 권한 체크 + const hasAccess = + requiredPermissions.length === 0 + ? true + : requireAll + ? hasAllPermissions(requiredPermissions) + : hasAnyPermission(requiredPermissions); + + if (!hasAccess) { + router.push("/dashboard/overview"); + } + }, [ + user, + router, + requiredPermissions, + requireAll, + hasAllPermissions, + hasAnyPermission, + ]); + + // 권한 체크 const hasAccess = requiredPermissions.length === 0 ? true @@ -24,20 +93,13 @@ export default function AdminGuard({ ? hasAllPermissions(requiredPermissions) : hasAnyPermission(requiredPermissions); - if (!hasAccess) { + if (!user || !hasAccess) { return (
- 접근 권한이 없습니다. + 로딩 중...
); } return <>{children}; } - -// 사용 예시: -/* - - - -*/ diff --git a/fems-app/src/hooks/useAuth.ts b/fems-app/src/hooks/useAuth.ts index f071ce8..a0f4a88 100644 --- a/fems-app/src/hooks/useAuth.ts +++ b/fems-app/src/hooks/useAuth.ts @@ -37,7 +37,7 @@ export function useAuth() { const logout = () => { clearAuth(); localStorage.removeItem("token"); - router.push("/"); + router.push("/login"); }; return { user, token, login, logout }; diff --git a/fems-app/src/hooks/usePermissions.ts b/fems-app/src/hooks/usePermissions.ts index 114dab7..28f8371 100644 --- a/fems-app/src/hooks/usePermissions.ts +++ b/fems-app/src/hooks/usePermissions.ts @@ -1,39 +1,70 @@ +// // src/hooks/usePermissions.ts +// import { useAuth } from "./useAuth"; + +// export function usePermissions() { +// const { user } = useAuth(); +// const permissions = user?.permissions || {}; + +// const hasPermission = (permission: string): boolean => { +// return !!permissions[permission]; +// }; + +// const hasAnyPermission = (requiredPermissions: string[]): boolean => { +// return requiredPermissions.some((permission) => hasPermission(permission)); +// }; + +// const hasAllPermissions = (requiredPermissions: string[]): boolean => { +// return requiredPermissions.every((permission) => hasPermission(permission)); +// }; + +// return { +// permissions, +// hasPermission, +// hasAnyPermission, +// hasAllPermissions, +// }; +// } + // src/hooks/usePermissions.ts import { useAuth } from "./useAuth"; +import { PATH_PERMISSIONS } from "@/config/permissions"; export function usePermissions() { const { user } = useAuth(); const permissions = user?.permissions || {}; const hasPermission = (permission: string): boolean => { + if (!user) return false; + + // 슈퍼 관리자는 모든 권한을 가짐 + if (user.role === "super_admin") return true; + + // 회사 관리자는 회사 관련 모든 권한을 가짐 + if (user.role === "company_admin") { + if ( + permission.startsWith("company:") || + permission.startsWith("branches:") || + permission.startsWith("users:") || + permission.startsWith("departments:") + ) { + return true; + } + } + return !!permissions[permission]; }; - const hasAnyPermission = (requiredPermissions: string[]): boolean => { + const hasPathPermission = (path: string): boolean => { + const requiredPermissions = PATH_PERMISSIONS[path]; + if (!requiredPermissions) return true; return requiredPermissions.some((permission) => hasPermission(permission)); }; - const hasAllPermissions = (requiredPermissions: string[]): boolean => { - return requiredPermissions.every((permission) => hasPermission(permission)); - }; - return { permissions, hasPermission, - hasAnyPermission, - hasAllPermissions, + hasPathPermission, + hasAnyPermission: (perms: string[]) => perms.some(hasPermission), + hasAllPermissions: (perms: string[]) => perms.every(hasPermission), }; } - -// 사용 예시: -/* -function MyComponent() { - const { hasPermission } = usePermissions(); - - if (!hasPermission('departments:view')) { - return
접근 권한이 없습니다.
; - } - - return
부서 목록...
; -} -*/ diff --git a/fems-app/src/types/index.ts b/fems-app/src/types/index.ts index 9934653..bffc62d 100644 --- a/fems-app/src/types/index.ts +++ b/fems-app/src/types/index.ts @@ -1,15 +1,15 @@ // src/types/index.ts export interface User { - id: string; - username: string; - role: 'admin' | 'user'; - // ... - } - - export interface EnergyUsage { - timestamp: string; - value: number; - type: 'electricity' | 'gas' | 'water' | 'steam'; - } - - // ... \ No newline at end of file + id: string; + username: string; + role: "super_admin" | "company_admin" | "branch_admin" | "user"; + permissions: Record; +} + +export interface EnergyUsage { + timestamp: string; + value: number; + type: "electricity" | "gas" | "water" | "steam"; +} + +// ...