From 4614210aa9878922cfa1e424ce677ef893a1b6b2 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 29 Sep 2025 13:31:40 +0000 Subject: (대표님) 구매 권한설정, data room 등 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../permission-group-assignment-manager.tsx | 666 +++++++++++++++++++++ 1 file changed, 666 insertions(+) create mode 100644 components/permissions/permission-group-assignment-manager.tsx (limited to 'components/permissions/permission-group-assignment-manager.tsx') diff --git a/components/permissions/permission-group-assignment-manager.tsx b/components/permissions/permission-group-assignment-manager.tsx new file mode 100644 index 00000000..cd7531a0 --- /dev/null +++ b/components/permissions/permission-group-assignment-manager.tsx @@ -0,0 +1,666 @@ +// components/permissions/permission-group-assignment-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 { Badge } from "@/components/ui/badge"; +import { Input } from "@/components/ui/input"; +import { Checkbox } from "@/components/ui/checkbox"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +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 { + Users, + User, + Plus, + X, + Search, + Package, + Shield, + Loader2, + UserPlus, + ChevronRight +} from "lucide-react"; +import { toast } from "sonner"; +import { cn } from "@/lib/utils"; +import { + getPermissionGroupAssignments, + assignGroupToRoles, + assignGroupToUsers, + removeGroupFromRole, + removeGroupFromUser, + searchRoles, + searchUsers, +} from "@/lib/permissions/permission-group-assignment-actions"; + +interface PermissionGroup { + id: number; + groupKey: string; + name: string; + description?: string; + domain?: string; + permissionCount: number; + isActive: boolean; +} + +interface AssignedRole { + id: number; + name: string; + domain: string; + userCount: number; + assignedAt: Date; + assignedBy?: string; +} + +interface AssignedUser { + id: number; + name: string; + email: string; + imageUrl?: string; + domain: string; + companyName?: string; + assignedAt: Date; + assignedBy?: string; +} + +export function PermissionGroupAssignmentManager() { + const [groups, setGroups] = useState([]); + const [selectedGroup, setSelectedGroup] = useState(null); + const [assignedRoles, setAssignedRoles] = useState([]); + const [assignedUsers, setAssignedUsers] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + const [loading, setLoading] = useState(false); + const [addRoleDialogOpen, setAddRoleDialogOpen] = useState(false); + const [addUserDialogOpen, setAddUserDialogOpen] = useState(false); + + useEffect(() => { + loadGroups(); + }, []); + + useEffect(() => { + if (selectedGroup) { + loadAssignments(selectedGroup.id); + } + }, [selectedGroup]); + + const loadGroups = async () => { + setLoading(true); + try { + const data = await getPermissionGroupAssignments(); + setGroups(data.groups); + } catch (error) { + toast.error("권한 그룹을 불러오는데 실패했습니다."); + } finally { + setLoading(false); + } + }; + + const loadAssignments = async (groupId: number) => { + try { + const data = await getPermissionGroupAssignments(groupId); + setAssignedRoles(data.roles); + setAssignedUsers(data.users); + } catch (error) { + toast.error("할당 정보를 불러오는데 실패했습니다."); + } + }; + + const handleRemoveRole = async (roleId: number) => { + if (!selectedGroup) return; + + try { + await removeGroupFromRole(selectedGroup.id, roleId); + toast.success("역할에서 권한 그룹이 제거되었습니다."); + loadAssignments(selectedGroup.id); + } catch (error) { + toast.error("권한 그룹 제거에 실패했습니다."); + } + }; + + const handleRemoveUser = async (userId: number) => { + if (!selectedGroup) return; + + try { + await removeGroupFromUser(selectedGroup.id, userId); + toast.success("사용자에서 권한 그룹이 제거되었습니다."); + loadAssignments(selectedGroup.id); + } catch (error) { + toast.error("권한 그룹 제거에 실패했습니다."); + } + }; + + // 그룹 필터링 + const filteredGroups = groups.filter(g => + g.name.toLowerCase().includes(searchQuery.toLowerCase()) || + g.groupKey.toLowerCase().includes(searchQuery.toLowerCase()) || + g.description?.toLowerCase().includes(searchQuery.toLowerCase()) + ); + + return ( +
+ {/* 권한 그룹 목록 */} + + + 권한 그룹 + 할당을 관리할 권한 그룹을 선택하세요. + + +
+ {/* 검색 */} +
+ + setSearchQuery(e.target.value)} + className="pl-8" + /> +
+ + {/* 그룹 목록 */} + {loading ? ( +
+ +
+ ) : ( + +
+ {filteredGroups.map(group => ( + + ))} +
+
+ )} +
+
+
+ + {/* 할당 관리 */} + {selectedGroup ? ( + + +
+ {selectedGroup.name} + +
+ {selectedGroup.groupKey} + {selectedGroup.domain && ( + {selectedGroup.domain} + )} + {selectedGroup.permissionCount}개 권한 +
+
+
+
+ + + + + + 역할 ({assignedRoles.length}) + + + + 사용자 ({assignedUsers.length}) + + + + +
+ + +
+ {assignedRoles.map((role) => ( +
+
+
{role.name}
+
+ {role.domain} • {role.userCount}명 사용자 +
+
+ {new Date(role.assignedAt).toLocaleDateString()} 할당 + {role.assignedBy && ` • ${role.assignedBy}`} +
+
+ +
+ ))} + + {assignedRoles.length === 0 && ( +
+ +

할당된 역할이 없습니다.

+
+ )} +
+
+
+ + +
+ + +
+ {assignedUsers.map((user) => ( +
+
+ + + {user.name[0]} + +
+
{user.name}
+
{user.email}
+
+ + {user.domain} + + {user.companyName && ( + + {user.companyName} + + )} +
+
+
+ +
+ ))} + + {assignedUsers.length === 0 && ( +
+ +

할당된 사용자가 없습니다.

+
+ )} +
+
+
+
+
+
+ ) : ( + +
+ +

권한 그룹을 선택하면 할당 정보가 표시됩니다.

+
+
+ )} + + {/* 역할 추가 다이얼로그 */} + {selectedGroup && ( + { + setAddRoleDialogOpen(false); + loadAssignments(selectedGroup.id); + }} + /> + )} + + {/* 사용자 추가 다이얼로그 */} + {selectedGroup && ( + { + setAddUserDialogOpen(false); + loadAssignments(selectedGroup.id); + }} + /> + )} +
+ ); +} + +// 역할 추가 다이얼로그 +function AddRoleDialog({ + open, + onOpenChange, + group, + onSuccess, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + group: PermissionGroup; + onSuccess: () => void; +}) { + const [availableRoles, setAvailableRoles] = useState([]); + const [selectedRoles, setSelectedRoles] = useState([]); + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + + useEffect(() => { + if (open) { + loadAvailableRoles(); + } + }, [open]); + + const loadAvailableRoles = async () => { + setLoading(true); + try { + const data = await searchRoles(group.id); + setAvailableRoles(data); + } catch (error) { + toast.error("역할 목록을 불러오는데 실패했습니다."); + } finally { + setLoading(false); + } + }; + + const handleSubmit = async () => { + if (selectedRoles.length === 0) { + toast.error("역할을 선택해주세요."); + return; + } + + setSaving(true); + try { + await assignGroupToRoles(group.id, selectedRoles); + toast.success("역할에 권한 그룹이 추가되었습니다."); + onSuccess(); + } catch (error) { + toast.error("권한 그룹 추가에 실패했습니다."); + } finally { + setSaving(false); + } + }; + + return ( + + + + 역할 추가 + + "{group.name}" 그룹을 할당할 역할을 선택하세요. + + + + {loading ? ( +
+ +
+ ) : ( +
+ +
+ {availableRoles.map((role) => ( + + ))} +
+
+ +
+ {selectedRoles.length}개 역할 선택됨 +
+
+ )} + + + + + +
+
+ ); +} + +// 사용자 추가 다이얼로그 +function AddUserDialog({ + open, + onOpenChange, + group, + onSuccess, +}: { + open: boolean; + onOpenChange: (open: boolean) => void; + group: PermissionGroup; + onSuccess: () => void; +}) { + const [searchQuery, setSearchQuery] = useState(""); + const [availableUsers, setAvailableUsers] = useState([]); + const [selectedUsers, setSelectedUsers] = useState([]); + const [loading, setLoading] = useState(false); + const [saving, setSaving] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => { + if (searchQuery && open) { + searchUsersData(searchQuery); + } + }, 300); + return () => clearTimeout(timer); + }, [searchQuery, open]); + + const searchUsersData = async (query: string) => { + setLoading(true); + try { + const data = await searchUsers(query, group.id); + setAvailableUsers(data); + } catch (error) { + toast.error("사용자 검색에 실패했습니다."); + } finally { + setLoading(false); + } + }; + + const handleSubmit = async () => { + if (selectedUsers.length === 0) { + toast.error("사용자를 선택해주세요."); + return; + } + + setSaving(true); + try { + await assignGroupToUsers(group.id, selectedUsers); + toast.success("사용자에게 권한 그룹이 추가되었습니다."); + onSuccess(); + } catch (error) { + toast.error("권한 그룹 추가에 실패했습니다."); + } finally { + setSaving(false); + } + }; + + return ( + + + + 사용자 추가 + + "{group.name}" 그룹을 할당할 사용자를 검색하고 선택하세요. + + + +
+ {/* 검색 */} +
+ + setSearchQuery(e.target.value)} + className="pl-8" + /> +
+ + {/* 사용자 목록 */} + {loading ? ( +
+ +
+ ) : ( + <> + +
+ {availableUsers.map((user) => ( + + ))} +
+
+ + {availableUsers.length > 0 && ( +
+ {selectedUsers.length}명 선택됨 +
+ )} + + )} +
+ + + + + +
+
+ ); +} \ No newline at end of file -- cgit v1.2.3