diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-30 08:28:13 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-30 08:28:13 +0000 |
| commit | 5b6313f16f508882a0ea67716b7dbaa1c6967f04 (patch) | |
| tree | 3d1d8dafea2f31274ace3fbda08333e889e06d1c /lib/menu-list/table/manager-select.tsx | |
| parent | 3f0fad18483a5c800c79c5e33946d9bb384c10e2 (diff) | |
(대표님) 20250630 16시 - 유저 도메인별 라우터 분리와 보안성검토 대응
Diffstat (limited to 'lib/menu-list/table/manager-select.tsx')
| -rw-r--r-- | lib/menu-list/table/manager-select.tsx | 192 |
1 files changed, 192 insertions, 0 deletions
diff --git a/lib/menu-list/table/manager-select.tsx b/lib/menu-list/table/manager-select.tsx new file mode 100644 index 00000000..a4bcccd7 --- /dev/null +++ b/lib/menu-list/table/manager-select.tsx @@ -0,0 +1,192 @@ +"use client"; + +import { useState } from "react"; +import { Check, ChevronsUpDown, Search, X } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, +} from "@/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; +import { toast } from "sonner"; +import { updateMenuManager } from "../servcie"; + +interface User { + id: number; + name: string; + email: string; + domain: string; +} + +interface ManagerSelectProps { + menuPath: string; + currentManagerId?: number | null; + users: User[]; + placeholder: string; + otherManagerId?: number | null; // 다른 담당자 ID (중복 선택 방지용) + type: "manager1" | "manager2"; +} + +export function ManagerSelect({ + menuPath, + currentManagerId, + users, + placeholder, + otherManagerId, + type +}: ManagerSelectProps) { + const [open, setOpen] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + // 선택 가능한 사용자 필터링 (다른 담당자로 이미 선택된 사용자 제외) + const availableUsers = users.filter(user => + user.id !== otherManagerId || user.id === currentManagerId + ); + + // 현재 선택된 사용자 찾기 + const selectedUser = availableUsers.find(user => user.id === currentManagerId); + + const handleSelect = async (userId: number | null) => { + setIsLoading(true); + setOpen(false); + + try { + // 현재 담당자 정보 구성 + const updateData = type === "manager1" + ? { manager1Id: userId, manager2Id: otherManagerId } + : { manager1Id: otherManagerId, manager2Id: userId }; + + const result = await updateMenuManager( + menuPath, + updateData.manager1Id, + updateData.manager2Id + ); + + if (result.success) { + toast.success(result.message); + } else { + toast.error(result.message); + } + } catch (error) { + toast.error("담당자 업데이트 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + } + }; + + const handleClear = () => { + handleSelect(null); + }; + + return ( + <Popover open={open} onOpenChange={setOpen}> + <PopoverTrigger asChild> + <Button + variant="outline" + role="combobox" + aria-expanded={open} + className="w-full justify-between text-left font-normal" + disabled={isLoading} + > + {selectedUser ? ( + <div className="flex items-center justify-between w-full"> + <div className="flex flex-col min-w-0 flex-1"> + <span className="font-medium truncate">{selectedUser.name}</span> + <span className="text-xs text-muted-foreground truncate"> + {selectedUser.email} + </span> + </div> + {!isLoading && ( + <Button + variant="ghost" + size="sm" + className="h-auto p-1 hover:bg-transparent" + onClick={(e) => { + e.stopPropagation(); + handleClear(); + }} + > + <X className="h-3 w-3" /> + </Button> + )} + </div> + ) : ( + <span className="text-muted-foreground">{placeholder}</span> + )} + <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> + </Button> + </PopoverTrigger> + + <PopoverContent className="w-[400px] p-0" align="start"> + <Command> + <CommandInput + placeholder="사용자 검색..." + className="h-9" + /> + <CommandList> + <CommandEmpty>검색 결과가 없습니다.</CommandEmpty> + <CommandGroup> + {/* 담당자 없음 옵션 */} + <CommandItem + value="none" + onSelect={() => handleSelect(null)} + className="flex items-center gap-2" + > + <Check + className={cn( + "mr-2 h-4 w-4", + !selectedUser ? "opacity-100" : "opacity-0" + )} + /> + <div className="flex flex-col"> + <span className="font-medium">담당자 없음</span> + <span className="text-xs text-muted-foreground"> + 담당자를 지정하지 않습니다 + </span> + </div> + </CommandItem> + + {/* 사용자 목록 */} + {availableUsers.map((user) => ( + <CommandItem + key={user.id} + value={`${user.name} ${user.email}`} // 검색을 위해 이름과 이메일 모두 포함 + onSelect={() => handleSelect(user.id)} + className="flex items-center gap-2" + disabled={user.id === otherManagerId && user.id !== currentManagerId} + > + <Check + className={cn( + "mr-2 h-4 w-4", + selectedUser?.id === user.id ? "opacity-100" : "opacity-0" + )} + /> + <div className="flex flex-col min-w-0 flex-1"> + <span className="font-medium truncate">{user.name}</span> + <span className="text-xs text-muted-foreground truncate"> + {user.email} + </span> + </div> + {user.id === otherManagerId && user.id !== currentManagerId && ( + <span className="text-xs text-amber-600 bg-amber-50 px-1.5 py-0.5 rounded"> + 다른 담당자로 선택됨 + </span> + )} + </CommandItem> + ))} + </CommandGroup> + </CommandList> + </Command> + </PopoverContent> + </Popover> + ); +}
\ No newline at end of file |
