From 8b23b471638a155fd1bfa3a8c853b26d9315b272 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 26 Sep 2025 09:57:24 +0000 Subject: (대표님) 권한관리, 문서업로드, rfq첨부, SWP문서룰 등 (최겸) 입찰 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../permissions/permission-group-manager.tsx | 799 +++++++++++++++++++++ 1 file changed, 799 insertions(+) create mode 100644 components/permissions/permission-group-manager.tsx (limited to 'components/permissions/permission-group-manager.tsx') diff --git a/components/permissions/permission-group-manager.tsx b/components/permissions/permission-group-manager.tsx new file mode 100644 index 00000000..11aac6cf --- /dev/null +++ b/components/permissions/permission-group-manager.tsx @@ -0,0 +1,799 @@ +// components/permissions/permission-group-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 { Checkbox } from "@/components/ui/checkbox"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { + Shield, + Plus, + Edit, + Trash2, + Copy, + Users, + Key, + MoreVertical, + Package, + ChevronRight, + Loader2, + Search +} from "lucide-react"; +import { toast } from "sonner"; +import { cn } from "@/lib/utils"; +import { + getPermissionGroups, + createPermissionGroup, + updatePermissionGroup, + deletePermissionGroup, + getGroupPermissions, + updateGroupPermissions, + clonePermissionGroup, + getGroupAssignments, +} from "@/lib/permissions/permission-group-actions"; + +interface PermissionGroup { + id: number; + groupKey: string; + name: string; + description?: string; + domain?: string; + isActive: boolean; + permissionCount: number; + roleCount: number; + userCount: number; + createdAt: Date; + updatedAt: Date; +} + +interface Permission { + id: number; + permissionKey: string; + name: string; + description?: string; + resource: string; + action: string; + permissionType: string; + scope: string; +} + +export function PermissionGroupManager() { + const [groups, setGroups] = useState([]); + const [selectedGroup, setSelectedGroup] = useState(null); + const [groupPermissions, setGroupPermissions] = useState([]); + const [availablePermissions, setAvailablePermissions] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [loading, setLoading] = useState(false); + const [createDialogOpen, setCreateDialogOpen] = useState(false); + const [editingGroup, setEditingGroup] = useState(null); + const [permissionDialogOpen, setPermissionDialogOpen] = useState(false); + + useEffect(() => { + loadGroups(); + }, []); + + useEffect(() => { + if (selectedGroup) { + loadGroupPermissions(selectedGroup.id); + } + }, [selectedGroup]); + + const loadGroups = async () => { + setLoading(true); + try { + const data = await getPermissionGroups(); + setGroups(data); + } catch (error) { + toast.error("권한 그룹을 불러오는데 실패했습니다."); + } finally { + setLoading(false); + } + }; + + const loadGroupPermissions = async (groupId: number) => { + try { + const data = await getGroupPermissions(groupId); + setGroupPermissions(data.permissions); + setAvailablePermissions(data.availablePermissions); + } catch (error) { + toast.error("권한 정보를 불러오는데 실패했습니다."); + } + }; + + const handleDelete = async (id: number) => { + if (!confirm("이 권한 그룹을 삭제하시겠습니까? 관련된 모든 할당이 제거됩니다.")) { + return; + } + + try { + await deletePermissionGroup(id); + toast.success("권한 그룹이 삭제되었습니다."); + if (selectedGroup?.id === id) { + setSelectedGroup(null); + } + loadGroups(); + } catch (error) { + toast.error("권한 그룹 삭제에 실패했습니다."); + } + }; + + const handleClone = async (group: PermissionGroup) => { + try { + const cloned = await clonePermissionGroup(group.id); + toast.success(`"${cloned.name}" 그룹이 생성되었습니다.`); + loadGroups(); + } catch (error) { + toast.error("권한 그룹 복제에 실패했습니다."); + } + }; + + // 검색 필터링 + const filteredGroups = groups.filter(group => + group.name.toLowerCase().includes(searchQuery.toLowerCase()) || + group.groupKey.toLowerCase().includes(searchQuery.toLowerCase()) || + group.description?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return ( +
+ + +
+
+ 권한 그룹 + + 관련된 권한들을 그룹으로 묶어 효율적으로 관리합니다. + +
+ +
+
+ + {/* 검색 */} +
+
+ + setSearchQuery(e.target.value)} + className="pl-8" + /> +
+
+ + {/* 그룹 목록 */} +
+ {filteredGroups.map(group => ( + setSelectedGroup(group)} + onEdit={() => setEditingGroup(group)} + onClone={() => handleClone(group)} + onDelete={() => handleDelete(group.id)} + onManagePermissions={() => { + setSelectedGroup(group); + setPermissionDialogOpen(true); + }} + /> + ))} +
+
+
+ + {/* 선택된 그룹 상세 */} + {selectedGroup && ( + setPermissionDialogOpen(true)} + /> + )} + + {/* 그룹 생성/수정 다이얼로그 */} + { + if (!open) { + setCreateDialogOpen(false); + setEditingGroup(null); + } + }} + group={editingGroup} + onSuccess={() => { + setCreateDialogOpen(false); + setEditingGroup(null); + loadGroups(); + }} + /> + + {/* 권한 관리 다이얼로그 */} + {selectedGroup && ( + { + loadGroupPermissions(selectedGroup.id); + loadGroups(); + }} + /> + )} +
+ ); +} + +// 그룹 카드 컴포넌트 +function GroupCard({ + group, + isSelected, + onSelect, + onEdit, + onClone, + onDelete, + onManagePermissions, +}: { + group: PermissionGroup; + isSelected: boolean; + onSelect: () => void; + onEdit: () => void; + onClone: () => void; + onDelete: () => void; + onManagePermissions: () => void; +}) { + return ( + + +
+
+ {group.name} +
+ + {group.groupKey} + + {group.domain && ( + + {group.domain} + + )} +
+
+ + e.stopPropagation()}> + + + + { + e.stopPropagation(); + onManagePermissions(); + }}> + + 권한 관리 + + { + e.stopPropagation(); + onEdit(); + }}> + + 수정 + + { + e.stopPropagation(); + onClone(); + }}> + + 복제 + + + { + e.stopPropagation(); + onDelete(); + }} + className="text-destructive" + > + + 삭제 + + + +
+
+ + {group.description && ( +

+ {group.description} +

+ )} +
+
+ + {group.permissionCount}개 권한 +
+
+ + {group.roleCount}개 역할 +
+
+ + {group.userCount}명 +
+
+
+
+ ); +} + +// 그룹 상세 카드 +function GroupDetailCard({ + group, + permissions, + onEditPermissions, +}: { + group: PermissionGroup; + permissions: Permission[]; + onEditPermissions: () => void; +}) { + // 리소스별로 권한 그룹화 + const groupedPermissions = permissions.reduce((acc, perm) => { + const resource = perm.resource; + if (!acc[resource]) acc[resource] = []; + acc[resource].push(perm); + return acc; + }, {} as Record); + + return ( + + +
+
+ {group.name} 권한 목록 + 이 그룹에 포함된 모든 권한입니다. +
+ +
+
+ + + {Object.entries(groupedPermissions).map(([resource, perms]) => ( + + +
+ {resource} + {perms.length}개 +
+
+ +
+ {perms.map(permission => ( +
+ + {permission.action} + +
+
{permission.name}
+
+ {permission.permissionKey} +
+ {permission.description && ( +
+ {permission.description} +
+ )} +
+ + {permission.scope} + +
+ ))} +
+
+
+ ))} +
+
+
+ ); +} + +// 그룹 생성/수정 폼 다이얼로그 +function GroupFormDialog({ + open, + onOpenChange, + group, + onSuccess, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + group?: PermissionGroup | null; + onSuccess: () => void; +}) { + const [formData, setFormData] = useState({ + groupKey: "", + name: "", + description: "", + domain: "", + isActive: true, + }); + const [saving, setSaving] = useState(false); + + useEffect(() => { + if (group) { + setFormData({ + groupKey: group.groupKey, + name: group.name, + description: group.description || "", + domain: group.domain || "", + isActive: group.isActive, + }); + } else { + setFormData({ + groupKey: "", + name: "", + description: "", + domain: "", + isActive: true, + }); + } + }, [group]); + + const handleSubmit = async () => { + if (!formData.groupKey || !formData.name) { + toast.error("필수 항목을 입력해주세요."); + return; + } + + setSaving(true); + try { + if (group) { + await updatePermissionGroup(group.id, formData); + toast.success("권한 그룹이 수정되었습니다."); + } else { + await createPermissionGroup(formData); + toast.success("권한 그룹이 생성되었습니다."); + } + onSuccess(); + } catch (error: any) { + toast.error(error.message || "권한 그룹 저장에 실패했습니다."); + } finally { + setSaving(false); + } + }; + + return ( + + + + {group ? "권한 그룹 수정" : "권한 그룹 생성"} + + 권한 그룹 정보를 입력하세요. + + + +
+
+ + setFormData({ ...formData, groupKey: e.target.value })} + placeholder="예: rfq_manager" + /> +
+ +
+ + setFormData({ ...formData, name: e.target.value })} + placeholder="예: RFQ 관리자 권한" + /> +
+ +
+ +