auto commit
This commit is contained in:
parent
c07c2a537f
commit
35e2a31442
fems-app/src
app/(admin)
components/auth
hooks
types
@ -1,13 +1,10 @@
|
|||||||
// src/app/(admin)/layout.tsx
|
// src/app/(admin)/layout.tsx
|
||||||
|
import React from "react";
|
||||||
import AdminGuard from "@/components/auth/AdminGuard";
|
import AdminGuard from "@/components/auth/AdminGuard";
|
||||||
import { SideNav } from "@/components/layout/SideNav";
|
import { SideNav } from "@/components/layout/SideNav";
|
||||||
import { TopNav } from "@/components/layout/TopNav";
|
import { TopNav } from "@/components/layout/TopNav";
|
||||||
|
|
||||||
export default function AdminLayout({
|
const AdminLayout = ({ children }: { children: React.ReactNode }) => {
|
||||||
children,
|
|
||||||
}: {
|
|
||||||
children: React.ReactNode;
|
|
||||||
}) {
|
|
||||||
return (
|
return (
|
||||||
<AdminGuard>
|
<AdminGuard>
|
||||||
<div className="h-screen flex">
|
<div className="h-screen flex">
|
||||||
@ -29,4 +26,6 @@ export default function AdminLayout({
|
|||||||
</div>
|
</div>
|
||||||
</AdminGuard>
|
</AdminGuard>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default AdminLayout;
|
||||||
|
@ -22,6 +22,7 @@ import { AxiosError } from "axios";
|
|||||||
import { UserForm } from "./components/UserForm";
|
import { UserForm } from "./components/UserForm";
|
||||||
import { Switch } from "@/components/ui/switch";
|
import { Switch } from "@/components/ui/switch";
|
||||||
import { User } from "@/types/user";
|
import { User } from "@/types/user";
|
||||||
|
import AdminGuard from "@/components/auth/AdminGuard";
|
||||||
|
|
||||||
const AccountsPage = () => {
|
const AccountsPage = () => {
|
||||||
const { token, user } = useAuthStore();
|
const { token, user } = useAuthStore();
|
||||||
@ -225,71 +226,73 @@ const AccountsPage = () => {
|
|||||||
if (isLoading) return <div>Loading...</div>;
|
if (isLoading) return <div>Loading...</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto py-6">
|
<AdminGuard requiredPermissions={["users:manage"]}>
|
||||||
{/* Header */}
|
<div className="container mx-auto py-6">
|
||||||
<div className="flex justify-between items-center mb-6">
|
{/* Header */}
|
||||||
<div className="space-y-1">
|
<div className="flex justify-between items-center mb-6">
|
||||||
<h1 className="text-3xl font-bold">유저 관리</h1>
|
<div className="space-y-1">
|
||||||
<p className="text-muted-foreground">
|
<h1 className="text-3xl font-bold">유저 관리</h1>
|
||||||
유저를 관리하고 권한을 설정합니다.
|
<p className="text-muted-foreground">
|
||||||
</p>
|
유저를 관리하고 권한을 설정합니다.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button onClick={() => setIsOpen(true)}>
|
||||||
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
|
유저 추가
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={() => setIsOpen(true)}>
|
|
||||||
<Plus className="mr-2 h-4 w-4" />
|
{/* Users Table */}
|
||||||
유저 추가
|
<Card>
|
||||||
</Button>
|
<CardHeader>
|
||||||
|
<CardTitle>유저 목록</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
{users && users.length > 0 ? (
|
||||||
|
<DataTable columns={columns} data={users} />
|
||||||
|
) : (
|
||||||
|
<div className="text-center py-12 text-muted-foreground">
|
||||||
|
등록된 유저가 없습니다.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* User Create/Edit Dialog */}
|
||||||
|
<Dialog
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
setIsOpen(open);
|
||||||
|
if (!open) setEditingUser(null);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>{editingUser ? "유저 수정" : "새 유저"}</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
{editingUser
|
||||||
|
? "기존 유저 정보를 수정합니다."
|
||||||
|
: "새로운 유저를 생성합니다."}
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<UserForm
|
||||||
|
initialData={editingUser || undefined}
|
||||||
|
onSubmit={(data) => {
|
||||||
|
if (editingUser) {
|
||||||
|
updateMutation.mutate({ id: editingUser.id, ...data });
|
||||||
|
} else {
|
||||||
|
createMutation.mutate(data);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onCancel={() => {
|
||||||
|
setIsOpen(false);
|
||||||
|
setEditingUser(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
|
</AdminGuard>
|
||||||
{/* Users Table */}
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>유저 목록</CardTitle>
|
|
||||||
</CardHeader>
|
|
||||||
<CardContent>
|
|
||||||
{users && users.length > 0 ? (
|
|
||||||
<DataTable columns={columns} data={users} />
|
|
||||||
) : (
|
|
||||||
<div className="text-center py-12 text-muted-foreground">
|
|
||||||
등록된 유저가 없습니다.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* User Create/Edit Dialog */}
|
|
||||||
<Dialog
|
|
||||||
open={isOpen}
|
|
||||||
onOpenChange={(open) => {
|
|
||||||
setIsOpen(open);
|
|
||||||
if (!open) setEditingUser(null);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>{editingUser ? "유저 수정" : "새 유저"}</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
{editingUser
|
|
||||||
? "기존 유저 정보를 수정합니다."
|
|
||||||
: "새로운 유저를 생성합니다."}
|
|
||||||
</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<UserForm
|
|
||||||
initialData={editingUser || undefined}
|
|
||||||
onSubmit={(data) => {
|
|
||||||
if (editingUser) {
|
|
||||||
updateMutation.mutate({ id: editingUser.id, ...data });
|
|
||||||
} else {
|
|
||||||
createMutation.mutate(data);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
onCancel={() => {
|
|
||||||
setIsOpen(false);
|
|
||||||
setEditingUser(null);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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 (
|
||||||
|
// <div className="flex items-center justify-center min-h-[200px] text-gray-500">
|
||||||
|
// 접근 권한이 없습니다.
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return <>{children}</>;
|
||||||
|
// }
|
||||||
|
|
||||||
// src/components/auth/AdminGuard.tsx
|
// src/components/auth/AdminGuard.tsx
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
import { usePermissions } from "@/hooks/usePermissions";
|
import { usePermissions } from "@/hooks/usePermissions";
|
||||||
import { ReactNode } from "react";
|
import { useAuth } from "@/hooks/useAuth";
|
||||||
|
|
||||||
interface AdminGuardProps {
|
interface AdminGuardProps {
|
||||||
children: ReactNode;
|
children: React.ReactNode;
|
||||||
requiredPermissions?: string[]; // 필요한 권한 목록
|
requiredPermissions?: string[];
|
||||||
requireAll?: boolean; // true면 모든 권한 필요, false면 하나라도 있으면 됨
|
requireAll?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AdminGuard({
|
export default function AdminGuard({
|
||||||
@ -15,8 +54,38 @@ export default function AdminGuard({
|
|||||||
requiredPermissions = [],
|
requiredPermissions = [],
|
||||||
requireAll = false,
|
requireAll = false,
|
||||||
}: AdminGuardProps) {
|
}: AdminGuardProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const { user } = useAuth();
|
||||||
const { hasAllPermissions, hasAnyPermission } = usePermissions();
|
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 =
|
const hasAccess =
|
||||||
requiredPermissions.length === 0
|
requiredPermissions.length === 0
|
||||||
? true
|
? true
|
||||||
@ -24,20 +93,13 @@ export default function AdminGuard({
|
|||||||
? hasAllPermissions(requiredPermissions)
|
? hasAllPermissions(requiredPermissions)
|
||||||
: hasAnyPermission(requiredPermissions);
|
: hasAnyPermission(requiredPermissions);
|
||||||
|
|
||||||
if (!hasAccess) {
|
if (!user || !hasAccess) {
|
||||||
return (
|
return (
|
||||||
<div className="flex items-center justify-center min-h-[200px] text-gray-500">
|
<div className="flex items-center justify-center min-h-[200px] text-gray-500">
|
||||||
접근 권한이 없습니다.
|
로딩 중...
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>{children}</>;
|
return <>{children}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 사용 예시:
|
|
||||||
/*
|
|
||||||
<AdminGuard requiredPermissions={['users:manage']}>
|
|
||||||
<UserManagementPanel />
|
|
||||||
</AdminGuard>
|
|
||||||
*/
|
|
||||||
|
@ -37,7 +37,7 @@ export function useAuth() {
|
|||||||
const logout = () => {
|
const logout = () => {
|
||||||
clearAuth();
|
clearAuth();
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
router.push("/");
|
router.push("/login");
|
||||||
};
|
};
|
||||||
|
|
||||||
return { user, token, login, logout };
|
return { user, token, login, logout };
|
||||||
|
@ -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
|
// src/hooks/usePermissions.ts
|
||||||
import { useAuth } from "./useAuth";
|
import { useAuth } from "./useAuth";
|
||||||
|
import { PATH_PERMISSIONS } from "@/config/permissions";
|
||||||
|
|
||||||
export function usePermissions() {
|
export function usePermissions() {
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
const permissions = user?.permissions || {};
|
const permissions = user?.permissions || {};
|
||||||
|
|
||||||
const hasPermission = (permission: string): boolean => {
|
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];
|
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));
|
return requiredPermissions.some((permission) => hasPermission(permission));
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasAllPermissions = (requiredPermissions: string[]): boolean => {
|
|
||||||
return requiredPermissions.every((permission) => hasPermission(permission));
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
permissions,
|
permissions,
|
||||||
hasPermission,
|
hasPermission,
|
||||||
hasAnyPermission,
|
hasPathPermission,
|
||||||
hasAllPermissions,
|
hasAnyPermission: (perms: string[]) => perms.some(hasPermission),
|
||||||
|
hasAllPermissions: (perms: string[]) => perms.every(hasPermission),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// 사용 예시:
|
|
||||||
/*
|
|
||||||
function MyComponent() {
|
|
||||||
const { hasPermission } = usePermissions();
|
|
||||||
|
|
||||||
if (!hasPermission('departments:view')) {
|
|
||||||
return <div>접근 권한이 없습니다.</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div>부서 목록...</div>;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
// src/types/index.ts
|
// src/types/index.ts
|
||||||
export interface User {
|
export interface User {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
role: 'admin' | 'user';
|
role: "super_admin" | "company_admin" | "branch_admin" | "user";
|
||||||
// ...
|
permissions: Record<string, boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EnergyUsage {
|
export interface EnergyUsage {
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
value: number;
|
value: number;
|
||||||
type: 'electricity' | 'gas' | 'water' | 'steam';
|
type: "electricity" | "gas" | "water" | "steam";
|
||||||
}
|
}
|
||||||
|
|
||||||
// ...
|
// ...
|
||||||
|
Loading…
Reference in New Issue
Block a user