auto commit

This commit is contained in:
bangdk 2024-11-14 15:20:02 +09:00
parent c07c2a537f
commit 35e2a31442
6 changed files with 211 additions and 116 deletions

View File

@ -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;

View File

@ -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>
);
};

View File

@ -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>
*/

View File

@ -37,7 +37,7 @@ export function useAuth() {
const logout = () => {
clearAuth();
localStorage.removeItem("token");
router.push("/");
router.push("/login");
};
return { user, token, login, logout };

View File

@ -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>;
}
*/

View File

@ -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";
}
// ...