257 lines
8.3 KiB
TypeScript
257 lines
8.3 KiB
TypeScript
// src/app/(admin/)users/roles/page.tsx
|
|
"use client";
|
|
|
|
import React from "react";
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import { useToast } from "@/hooks/use-toast";
|
|
import { Plus, Edit, Trash2 } from "lucide-react";
|
|
import { RoleForm } from "./components/RoleForm";
|
|
import { api } from "@/lib/api";
|
|
import { AxiosError } from "axios";
|
|
import { useAuthStore } from "@/stores/auth";
|
|
import { Role } from "@/types/user";
|
|
|
|
const RolesPage = () => {
|
|
const { token, user } = useAuthStore();
|
|
const [isOpen, setIsOpen] = React.useState(false);
|
|
const [editingRole, setEditingRole] = React.useState<Role | null>(null);
|
|
const { toast } = useToast();
|
|
const queryClient = useQueryClient();
|
|
|
|
const { data: roles, isLoading } = useQuery<Role[]>({
|
|
queryKey: ["roles", user?.companyId],
|
|
queryFn: async () => {
|
|
const { data } = await api.get<Role[]>(
|
|
`/api/v1/admin/roles/${user?.companyId}`
|
|
);
|
|
return data;
|
|
},
|
|
enabled: !!token && !!user?.companyId,
|
|
});
|
|
|
|
const createMutation = useMutation({
|
|
mutationFn: async (newRole: Partial<Role>) => {
|
|
const roleWithCompanyId = {
|
|
...newRole,
|
|
companyId: user?.companyId, // companyId 추가
|
|
};
|
|
const { data } = await api.post<Role>(
|
|
"/api/v1/admin/roles",
|
|
roleWithCompanyId
|
|
);
|
|
return data;
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["roles"] });
|
|
setIsOpen(false);
|
|
toast({
|
|
title: "권한 그룹 생성",
|
|
description: "새로운 권한 그룹이 생성되었습니다.",
|
|
});
|
|
},
|
|
onError: (error: AxiosError) => {
|
|
toast({
|
|
title: "권한 그룹 생성 실패",
|
|
description: (error.response?.data as { message: string }).message,
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const updateMutation = useMutation({
|
|
mutationFn: async (role: Role) => {
|
|
const { data } = await api.put<Role>(
|
|
`/api/v1/admin/roles/${role.id}`,
|
|
role
|
|
);
|
|
return data;
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["roles"] });
|
|
setIsOpen(false);
|
|
setEditingRole(null);
|
|
toast({
|
|
title: "권한 그룹 수정",
|
|
description: "권한 그룹이 수정되었습니다.",
|
|
});
|
|
},
|
|
onError: (error: AxiosError) => {
|
|
toast({
|
|
title: "권한 그룹 수정 실패",
|
|
description: (error.response?.data as { message: string }).message,
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const deleteMutation = useMutation({
|
|
mutationFn: async (id: string) => {
|
|
await api.delete(`/api/v1/admin/roles/${id}`);
|
|
},
|
|
onSuccess: () => {
|
|
queryClient.invalidateQueries({ queryKey: ["roles"] });
|
|
toast({
|
|
title: "권한 그룹 삭제",
|
|
description: "권한 그룹이 삭제되었습니다.",
|
|
});
|
|
},
|
|
onError: (error: AxiosError) => {
|
|
toast({
|
|
title: "권한 그룹 삭제 실패",
|
|
description: (error.response?.data as { message: string }).message,
|
|
variant: "destructive",
|
|
});
|
|
},
|
|
});
|
|
|
|
const permissionCategories = {
|
|
company: "회사 관리",
|
|
user: "사용자 관리",
|
|
energy: "에너지 관리",
|
|
equipment: "설비 관리",
|
|
report: "보고서",
|
|
};
|
|
|
|
const permissionTypes = {
|
|
read: "조회",
|
|
create: "생성",
|
|
update: "수정",
|
|
delete: "삭제",
|
|
};
|
|
|
|
if (isLoading) return <div>Loading...</div>;
|
|
|
|
return (
|
|
<div className="container mx-auto py-6">
|
|
<div className="flex justify-between items-center mb-6">
|
|
<h1 className="text-3xl font-bold">권한 그룹 관리</h1>
|
|
<Button onClick={() => setIsOpen(true)}>
|
|
<Plus className="mr-2 h-4 w-4" />
|
|
권한 그룹 추가
|
|
</Button>
|
|
</div>
|
|
<div className="grid gap-4 grid-cols-1 md:grid-cols-2 lg:grid-cols-3">
|
|
{roles?.map((role) => (
|
|
<Card key={role.id}>
|
|
<CardHeader className="flex flex-row items-center justify-between space-y-0">
|
|
<div className="flex items-center gap-2">
|
|
<div
|
|
className={`w-2 h-2 rounded-full ${
|
|
role.isActive ? "bg-green-500" : "bg-gray-400"
|
|
}`}
|
|
/>
|
|
<CardTitle className="text-sm font-medium">
|
|
{role.name}
|
|
</CardTitle>
|
|
</div>
|
|
<div className="flex space-x-1">
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-8 w-8 p-0 hover:bg-background/80"
|
|
onClick={() => {
|
|
setEditingRole(role);
|
|
setIsOpen(true);
|
|
}}
|
|
>
|
|
<Edit className="h-4 w-4" />
|
|
</Button>
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
className="h-8 w-8 p-0 hover:bg-destructive/10 hover:text-destructive"
|
|
onClick={() => {
|
|
if (confirm("정말 삭제하시겠습니까?")) {
|
|
deleteMutation.mutate(role.id);
|
|
}
|
|
}}
|
|
>
|
|
<Trash2 className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent className="pt-4">
|
|
<div className="space-y-4">
|
|
<div>
|
|
<p className="text-sm text-muted-foreground">
|
|
{role.description || "설명이 없습니다."}
|
|
</p>
|
|
</div>
|
|
<div className="space-y-3">
|
|
{Object.entries(role.permissions).map(
|
|
([category, permissions]) => (
|
|
<div key={category} className="space-y-1">
|
|
<div className="text-sm font-medium">
|
|
{
|
|
permissionCategories[
|
|
category as keyof typeof permissionCategories
|
|
]
|
|
}
|
|
</div>
|
|
<div className="flex flex-wrap gap-1">
|
|
{permissions.map((p) => (
|
|
<span
|
|
key={p}
|
|
className="inline-flex items-center px-2 py-1 rounded-md text-xs font-medium bg-primary/10 text-primary"
|
|
>
|
|
{
|
|
permissionTypes[
|
|
p as keyof typeof permissionTypes
|
|
]
|
|
}
|
|
</span>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
{/* 중첩된 Dialog를 제거하고 하나의 Dialog만 사용 */}
|
|
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
|
<DialogContent className="max-w-3xl max-h-[80vh] overflow-y-auto">
|
|
<DialogHeader>
|
|
<DialogTitle>
|
|
{editingRole ? "권한 그룹 수정" : "새 권한 그룹"}
|
|
</DialogTitle>
|
|
<DialogDescription>
|
|
{editingRole
|
|
? "기존 권한 그룹의 정보를 수정합니다."
|
|
: "새로운 권한 그룹을 생성합니다."}
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<RoleForm
|
|
initialData={editingRole || undefined}
|
|
onSubmit={(data) => {
|
|
if (editingRole) {
|
|
updateMutation.mutate({ ...data, id: editingRole.id });
|
|
} else {
|
|
createMutation.mutate(data);
|
|
}
|
|
}}
|
|
onCancel={() => {
|
|
setIsOpen(false);
|
|
setEditingRole(null);
|
|
}}
|
|
/>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default RolesPage;
|