duckil_plm/fems-app/src/app/(admin)/users/roles/page.tsx
2024-11-14 14:14:36 +09:00

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;