auto commit
This commit is contained in:
parent
c07c2a537f
commit
35e2a31442
@ -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 (
|
||||
<AdminGuard>
|
||||
<div className="h-screen flex">
|
||||
@ -29,4 +26,6 @@ export default function AdminLayout({
|
||||
</div>
|
||||
</AdminGuard>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default AdminLayout;
|
||||
|
@ -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 <div>Loading...</div>;
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-6">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-3xl font-bold">유저 관리</h1>
|
||||
<p className="text-muted-foreground">
|
||||
유저를 관리하고 권한을 설정합니다.
|
||||
</p>
|
||||
<AdminGuard requiredPermissions={["users:manage"]}>
|
||||
<div className="container mx-auto py-6">
|
||||
{/* Header */}
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
<div className="space-y-1">
|
||||
<h1 className="text-3xl font-bold">유저 관리</h1>
|
||||
<p className="text-muted-foreground">
|
||||
유저를 관리하고 권한을 설정합니다.
|
||||
</p>
|
||||
</div>
|
||||
<Button onClick={() => setIsOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
유저 추가
|
||||
</Button>
|
||||
</div>
|
||||
<Button onClick={() => setIsOpen(true)}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
유저 추가
|
||||
</Button>
|
||||
|
||||
{/* 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>
|
||||
|
||||
{/* 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>
|
||||
</AdminGuard>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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
|
||||
"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 (
|
||||
<div className="flex items-center justify-center min-h-[200px] text-gray-500">
|
||||
접근 권한이 없습니다.
|
||||
로딩 중...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <>{children}</>;
|
||||
}
|
||||
|
||||
// 사용 예시:
|
||||
/*
|
||||
<AdminGuard requiredPermissions={['users:manage']}>
|
||||
<UserManagementPanel />
|
||||
</AdminGuard>
|
||||
*/
|
||||
|
@ -37,7 +37,7 @@ export function useAuth() {
|
||||
const logout = () => {
|
||||
clearAuth();
|
||||
localStorage.removeItem("token");
|
||||
router.push("/");
|
||||
router.push("/login");
|
||||
};
|
||||
|
||||
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
|
||||
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 <div>접근 권한이 없습니다.</div>;
|
||||
}
|
||||
|
||||
return <div>부서 목록...</div>;
|
||||
}
|
||||
*/
|
||||
|
@ -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';
|
||||
}
|
||||
|
||||
// ...
|
||||
id: string;
|
||||
username: string;
|
||||
role: "super_admin" | "company_admin" | "branch_admin" | "user";
|
||||
permissions: Record<string, boolean>;
|
||||
}
|
||||
|
||||
export interface EnergyUsage {
|
||||
timestamp: string;
|
||||
value: number;
|
||||
type: "electricity" | "gas" | "water" | "steam";
|
||||
}
|
||||
|
||||
// ...
|
||||
|
Loading…
Reference in New Issue
Block a user