"use client" import * as React from "react" import { Search, X, Building2, ChevronLeft, ChevronRight } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Badge } from "@/components/ui/badge" import { useDebounce } from "@/hooks/use-debounce" import { cn } from "@/lib/utils" import { Skeleton } from "@/components/ui/skeleton" import { searchOrganizationsForManager } from "@/lib/knox-api/organization-service" // 조직 관리자 타입 정의 export interface OrganizationManagerItem { id: string departmentCode: string departmentName: string managerId: string managerName: string managerTitle: string companyCode: string companyName: string } // 페이지네이션 정보 타입 interface PaginationInfo { page: number perPage: number total: number pageCount: number hasNextPage: boolean hasPrevPage: boolean } export interface OrganizationManagerSelectorProps { /** 선택된 조직 관리자들 */ selectedManagers?: OrganizationManagerItem[] /** 조직 관리자 선택 변경 콜백 */ onManagersChange?: (managers: OrganizationManagerItem[]) => void /** 단일 선택 모드 여부 */ singleSelect?: boolean /** placeholder 텍스트 */ placeholder?: string /** 입력 없이 focus 시 표시할 placeholder */ noValuePlaceHolder?: string /** 비활성화 여부 */ disabled?: boolean /** 최대 선택 가능 조직 관리자 수 */ maxSelections?: number /** 컴포넌트 클래스명 */ className?: string /** 선택 후 팝오버 닫기 여부 */ closeOnSelect?: boolean } export function OrganizationManagerSelector({ selectedManagers = [], onManagersChange, singleSelect = false, placeholder = "조직 관리자를 검색하세요...", noValuePlaceHolder = "조직명 또는 관리자명으로 검색하세요", disabled = false, maxSelections, className, closeOnSelect = true }: OrganizationManagerSelectorProps) { const [searchQuery, setSearchQuery] = React.useState("") const [isSearching, setIsSearching] = React.useState(false) const [searchResults, setSearchResults] = React.useState([]) const [isPopoverOpen, setIsPopoverOpen] = React.useState(false) const [currentPage, setCurrentPage] = React.useState(1) const [pagination, setPagination] = React.useState({ page: 1, perPage: 10, total: 0, pageCount: 0, hasNextPage: false, hasPrevPage: false, }) const [searchError, setSearchError] = React.useState(null) const inputRef = React.useRef(null) // Debounce 적용된 검색어 const debouncedSearchQuery = useDebounce(searchQuery, 300) // 검색 실행 const performSearch = React.useCallback(async (query: string, page: number = 1) => { if (!query.trim()) { setSearchResults([]) setPagination({ page: 1, perPage: 10, total: 0, pageCount: 0, hasNextPage: false, hasPrevPage: false, }) return } setIsSearching(true) setSearchError(null) try { const result = await searchOrganizationsForManager({ search: query, page, perPage: 10, }) setSearchResults(result.data) setPagination({ page: result.pageCount, perPage: 10, total: result.total, pageCount: result.pageCount, hasNextPage: page < result.pageCount, hasPrevPage: page > 1, }) } catch (error) { setSearchError("검색 중 오류가 발생했습니다.") setSearchResults([]) } finally { setIsSearching(false) } }, []) // 검색어 변경 시 검색 실행 React.useEffect(() => { performSearch(debouncedSearchQuery, 1) setCurrentPage(1) }, [debouncedSearchQuery, performSearch]) // 페이지 변경 시 검색 실행 const handlePageChange = React.useCallback((newPage: number) => { setCurrentPage(newPage) performSearch(debouncedSearchQuery, newPage) }, [debouncedSearchQuery, performSearch]) // 선택된 관리자 제거 const removeManager = React.useCallback((managerId: string) => { const updated = selectedManagers.filter(m => m.id !== managerId) onManagersChange?.(updated) }, [selectedManagers, onManagersChange]) // 관리자 선택 const selectManager = React.useCallback((manager: OrganizationManagerItem) => { if (singleSelect) { onManagersChange?.([manager]) if (closeOnSelect) { setIsPopoverOpen(false) } return } // 최대 선택 수 체크 if (maxSelections && selectedManagers.length >= maxSelections) { return } // 이미 선택된 관리자인지 확인 const isAlreadySelected = selectedManagers.some(m => m.id === manager.id) if (isAlreadySelected) { return } const updated = [...selectedManagers, manager] onManagersChange?.(updated) if (closeOnSelect) { setIsPopoverOpen(false) } }, [selectedManagers, onManagersChange, singleSelect, maxSelections, closeOnSelect]) // 전체 선택 해제 const clearAll = React.useCallback(() => { onManagersChange?.([]) }, [onManagersChange]) return (
{/* 선택된 관리자들 표시 */} {selectedManagers.length > 0 && (
{selectedManagers.map((manager) => ( {manager.departmentName} - {manager.managerName} ))} {selectedManagers.length > 1 && ( )}
)} {/* 검색 입력 */}
setSearchQuery(e.target.value)} onFocus={() => setIsPopoverOpen(true)} disabled={disabled} className="w-full" /> {/* 검색 결과 팝오버 */} {isPopoverOpen && (
{/* 검색 중 표시 */} {isSearching && (
{Array.from({ length: 3 }).map((_, i) => (
))}
)} {/* 검색 결과 */} {!isSearching && searchResults.length > 0 && (
{searchResults.map((manager) => { const isSelected = selectedManagers.some(m => m.id === manager.id) return (
selectManager(manager)} >
{manager.departmentName}
{manager.managerName} ({manager.managerTitle})
{manager.companyName}
{isSelected && ( 선택됨 )}
) })}
)} {/* 검색 결과 없음 */} {!isSearching && searchQuery && searchResults.length === 0 && !searchError && (
검색 결과가 없습니다.
)} {/* 오류 메시지 */} {searchError && (
{searchError}
)} {/* 페이지네이션 */} {!isSearching && searchResults.length > 0 && (
{currentPage} / {pagination.pageCount}
)}
)}
{/* 팝오버 외부 클릭 시 닫기 */} {isPopoverOpen && (
setIsPopoverOpen(false)} /> )}
) }