// components/permissions/user-permission-manager.tsx "use client"; import { useState, useEffect, useTransition } from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Input } from "@/components/ui/input"; import { Checkbox } from "@/components/ui/checkbox"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger, } from "@/components/ui/dialog"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Calendar } from "@/components/ui/calendar"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Search, UserPlus, Shield, Clock, AlertTriangle, CheckCircle, XCircle, CalendarIcon, Plus, Minus, Settings, Key, Users, Building, ChevronRight } from "lucide-react"; import { format } from "date-fns"; import { ko } from "date-fns/locale"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { getUserPermissionDetails, grantPermissionToUser, revokePermissionFromUser, searchUsers, getUserRoles, } from "@/lib/permissions/service"; interface User { id: number; name: string; email: string; imageUrl?: string; domain: string; companyName?: string; roles: { id: number; name: string }[]; } interface Permission { id: number; permissionKey: string; name: string; description?: string; permissionType: string; resource: string; action: string; scope: string; menuPath?: string; } interface UserPermission extends Permission { source: "role" | "direct"; roleName?: string; grantedBy?: string; grantedAt?: Date; expiresAt?: Date; reason?: string; isGrant: boolean; } export function UserPermissionManager() { const [searchQuery, setSearchQuery] = useState(""); const [selectedUser, setSelectedUser] = useState(null); const [users, setUsers] = useState([]); const [userPermissions, setUserPermissions] = useState([]); const [availablePermissions, setAvailablePermissions] = useState([]); const [loading, setLoading] = useState(false); const [isPending, startTransition] = useTransition(); const [addPermissionDialogOpen, setAddPermissionDialogOpen] = useState(false); // 사용자 검색 useEffect(() => { const timer = setTimeout(() => { if (searchQuery) { searchUsersData(searchQuery); } }, 300); return () => clearTimeout(timer); }, [searchQuery]); // 선택된 사용자의 권한 로드 useEffect(() => { if (selectedUser) { loadUserPermissions(selectedUser.id); } }, [selectedUser]); const searchUsersData = async (query: string) => { try { const data = await searchUsers(query); setUsers(data); } catch (error) { toast.error("사용자 검색에 실패했습니다."); } }; const loadUserPermissions = async (userId: number) => { setLoading(true); try { const data = await getUserPermissionDetails(userId); setUserPermissions(data.permissions); setAvailablePermissions(data.availablePermissions); } catch (error) { toast.error("권한 정보를 불러오는데 실패했습니다."); } finally { setLoading(false); } }; // 역할 기반 권한과 직접 부여 권한 분리 const rolePermissions = userPermissions.filter(p => p.source === "role"); const directPermissions = userPermissions.filter(p => p.source === "direct"); return (
{/* 사용자 검색 및 선택 */} 사용자 선택 권한을 관리할 사용자를 검색하세요.
{/* 검색 입력 */}
setSearchQuery(e.target.value)} className="pl-8" />
{/* 사용자 목록 */}
{users.map((user) => ( ))}
{/* 권한 상세 */} {selectedUser ? (
{selectedUser.name}의 권한 {selectedUser.email} • {selectedUser.domain}
!userPermissions.some(up => up.id === p.id) )} onSuccess={() => loadUserPermissions(selectedUser.id)} />
전체 권한 ({userPermissions.length}) 역할 기반 ({rolePermissions.length}) 직접 부여 ({directPermissions.length}) loadUserPermissions(selectedUser.id)} />
{/* 역할 표시 */}

보유 역할

{selectedUser.roles.map(role => ( {role.name} ))}
loadUserPermissions(selectedUser.id)} />
) : (

사용자를 선택하면 권한 정보가 표시됩니다.

)}
); } // 권한 목록 컴포넌트 function PermissionList({ permissions, userId, readOnly = false, onRevoke }: { permissions: UserPermission[]; userId: number; readOnly?: boolean; onRevoke?: () => void; }) { const [revoking, setRevoking] = useState(null); const handleRevoke = async (permissionId: number) => { if (!confirm("이 권한을 제거하시겠습니까?")) return; setRevoking(permissionId); try { await revokePermissionFromUser(userId, permissionId); toast.success("권한이 제거되었습니다."); onRevoke?.(); } catch (error) { toast.error("권한 제거에 실패했습니다."); } finally { setRevoking(null); } }; // 권한을 리소스별로 그룹화 const groupedPermissions = permissions.reduce((acc, perm) => { const group = perm.resource; if (!acc[group]) acc[group] = []; acc[group].push(perm); return acc; }, {} as Record); return (
{Object.entries(groupedPermissions).map(([resource, perms]) => (
{resource}
{perms.map((permission) => (
{permission.name} {permission.source === "role" && ( 역할: {permission.roleName} )} {permission.isGrant === false && ( 제한 )}
{permission.description}
타입: {permission.permissionType} 범위: {permission.scope} {permission.expiresAt && ( {format(new Date(permission.expiresAt), "yyyy-MM-dd")} 만료 )}
{permission.reason && (
사유: {permission.reason}
)}
{!readOnly && permission.source === "direct" && ( )}
))}
))}
); } // 권한 추가 다이얼로그 function AddPermissionDialog({ userId, availablePermissions, onSuccess }: { userId: number; availablePermissions: Permission[]; onSuccess: () => void; }) { const [open, setOpen] = useState(false); const [selectedPermissions, setSelectedPermissions] = useState([]); const [reason, setReason] = useState(""); const [expiresAt, setExpiresAt] = useState(); const [isGrant, setIsGrant] = useState(true); const [saving, setSaving] = useState(false); const handleSubmit = async () => { if (selectedPermissions.length === 0) { toast.error("권한을 선택해주세요."); return; } setSaving(true); try { await grantPermissionToUser({ userId, permissionIds: selectedPermissions, isGrant, reason, expiresAt, }); toast.success("권한이 추가되었습니다."); setOpen(false); onSuccess(); // Reset form setSelectedPermissions([]); setReason(""); setExpiresAt(undefined); setIsGrant(true); } catch (error) { toast.error("권한 추가에 실패했습니다."); } finally { setSaving(false); } }; return ( 권한 추가 사용자에게 직접 권한을 부여하거나 제한합니다.
{/* 권한 타입 선택 */}
{/* 권한 선택 */}
{availablePermissions.map(permission => ( ))}
{/* 사유 */}