// components/permissions/permission-crud-manager.tsx "use client"; import { useState, useEffect } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Badge } from "@/components/ui/badge"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Plus, Edit, Trash2, MoreVertical, Search, Filter, Key, Shield, Copy, CheckCircle, AlertTriangle } from "lucide-react"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { getAllPermissions, createPermission, updatePermission, deletePermission, getPermissionCategories, } from "@/lib/permissions/permission-settings-actions"; interface Permission { id: number; permissionKey: string; name: string; description?: string; permissionType: string; resource: string; action: string; scope: string; menuPath?: string; uiElement?: string; isSystem: boolean; isActive: boolean; createdAt: Date; updatedAt: Date; } export function PermissionCrudManager() { const [permissions, setPermissions] = useState([]); const [filteredPermissions, setFilteredPermissions] = useState([]); const [categories, setCategories] = useState<{ resource: string; count: number }[]>([]); const [selectedCategory, setSelectedCategory] = useState("all"); const [searchQuery, setSearchQuery] = useState(""); const [loading, setLoading] = useState(false); const [createDialogOpen, setCreateDialogOpen] = useState(false); const [editingPermission, setEditingPermission] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [deletingPermission, setDeletingPermission] = useState(null); useEffect(() => { loadPermissions(); loadCategories(); }, []); useEffect(() => { filterPermissions(); }, [permissions, selectedCategory, searchQuery]); const loadPermissions = async () => { setLoading(true); try { const data = await getAllPermissions(); setPermissions(data); } catch (error) { toast.error("권한 목록을 불러오는데 실패했습니다."); } finally { setLoading(false); } }; const loadCategories = async () => { try { const data = await getPermissionCategories(); setCategories(data); } catch (error) { console.error("카테고리 로드 실패:", error); } }; const filterPermissions = () => { let filtered = permissions; if (selectedCategory !== "all") { filtered = filtered.filter(p => p.resource === selectedCategory); } if (searchQuery) { filtered = filtered.filter(p => p.name.toLowerCase().includes(searchQuery.toLowerCase()) || p.permissionKey.toLowerCase().includes(searchQuery.toLowerCase()) || p.description?.toLowerCase().includes(searchQuery.toLowerCase()) ); } setFilteredPermissions(filtered); }; const handleDelete = async () => { if (!deletingPermission) return; try { await deletePermission(deletingPermission.id); toast.success("권한이 삭제되었습니다."); loadPermissions(); setDeleteDialogOpen(false); setDeletingPermission(null); } catch (error) { toast.error("권한 삭제에 실패했습니다."); } }; const openDeleteDialog = (permission: Permission) => { setDeletingPermission(permission); setDeleteDialogOpen(true); }; return (
{/* 헤더 및 필터 */}
권한 목록 시스템에 등록된 모든 권한을 관리합니다.
{/* 검색 */}
setSearchQuery(e.target.value)} className="pl-8" />
{/* 카테고리 필터 */}
{/* 권한 테이블 */}
권한명 권한 키 타입 리소스 액션 범위 상태 작업 {filteredPermissions.map(permission => (
{permission.name}
{permission.description && (
{permission.description}
)}
{permission.permissionKey} {permission.permissionType} {permission.resource} {permission.action} {permission.scope}
{permission.isActive ? ( 활성 ) : ( 비활성 )} {permission.isSystem && ( 시스템 )}
{ navigator.clipboard.writeText(permission.permissionKey); toast.success("권한 키가 복사되었습니다."); }} > 키 복사 setEditingPermission(permission)} > 수정 openDeleteDialog(permission)} className="text-destructive" disabled={permission.isSystem} > 삭제
))}
{/* 권한 생성/수정 다이얼로그 */} { if (!open) { setCreateDialogOpen(false); setEditingPermission(null); } }} permission={editingPermission} onSuccess={() => { setCreateDialogOpen(false); setEditingPermission(null); loadPermissions(); }} /> {/* 삭제 확인 다이얼로그 */}
권한 삭제 확인
{deletingPermission && (

"{deletingPermission.name}" 권한을 삭제하시겠습니까?

권한 키: {deletingPermission.permissionKey}
리소스: {deletingPermission.resource}
액션: {deletingPermission.action}

⚠️ 주의: 이 작업은 되돌릴 수 없습니다

이 권한과 관련된 모든 역할 및 사용자 할당이 제거됩니다.

)}
setDeletingPermission(null)}> 취소 삭제
); } // 권한 생성/수정 폼 다이얼로그 function PermissionFormDialog({ open, onOpenChange, permission, onSuccess, }: { open: boolean; onOpenChange: (open: boolean) => void; permission?: Permission | null; onSuccess: () => void; }) { const [formData, setFormData] = useState({ permissionKey: "", name: "", description: "", permissionType: "action", resource: "", action: "", scope: "own", menuPath: "", uiElement: "", isActive: true, }); const [saving, setSaving] = useState(false); useEffect(() => { if (permission) { setFormData({ permissionKey: permission.permissionKey, name: permission.name, description: permission.description || "", permissionType: permission.permissionType, resource: permission.resource, action: permission.action, scope: permission.scope, menuPath: permission.menuPath || "", uiElement: permission.uiElement || "", isActive: permission.isActive, }); } else { setFormData({ permissionKey: "", name: "", description: "", permissionType: "action", resource: "", action: "", scope: "own", menuPath: "", uiElement: "", isActive: true, }); } }, [permission]); const handleSubmit = async () => { if (!formData.permissionKey || !formData.name || !formData.resource || !formData.action) { toast.error("필수 항목을 입력해주세요."); return; } setSaving(true); try { if (permission) { await updatePermission(permission.id, formData); toast.success("권한이 수정되었습니다."); } else { await createPermission(formData); toast.success("권한이 생성되었습니다."); } onSuccess(); } catch (error: any) { toast.error(error.message || "권한 저장에 실패했습니다."); } finally { setSaving(false); } }; // 권한 키 자동 생성 const generatePermissionKey = () => { if (formData.resource && formData.action) { const key = `${formData.resource}.${formData.action}`.toLowerCase().replace(/\s+/g, '_'); setFormData({ ...formData, permissionKey: key }); } }; return ( {permission ? "권한 수정" : "권한 생성"} 새로운 권한을 생성하거나 기존 권한을 수정합니다.
setFormData({ ...formData, permissionKey: e.target.value })} placeholder="예: rfq.vendor.create" /> {!permission && ( )}
setFormData({ ...formData, name: e.target.value })} placeholder="예: RFQ 벤더 추가" />