diff options
Diffstat (limited to 'components/vendor-data-plant/project-swicher.tsx')
| -rw-r--r-- | components/vendor-data-plant/project-swicher.tsx | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/components/vendor-data-plant/project-swicher.tsx b/components/vendor-data-plant/project-swicher.tsx new file mode 100644 index 00000000..d3123709 --- /dev/null +++ b/components/vendor-data-plant/project-swicher.tsx @@ -0,0 +1,171 @@ +"use client" + +import * as React from "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 { Check, ChevronsUpDown, Loader2 } from "lucide-react" + +interface ContractInfo { + contractId: number + contractName: string +} + +interface ProjectInfo { + projectId: number + projectCode: string + projectName: string + contracts: ContractInfo[] +} + +interface ProjectSwitcherProps { + isCollapsed: boolean + projects: ProjectInfo[] + + // 상위가 관리하는 "현재 선택된 contractId" + selectedContractId: number | null + + // 콜백: 사용자가 "어떤 contract"를 골랐는지 + // => 우리가 projectId도 찾아서 상위 state를 같이 갱신해야 함 + onSelectContract: (projectId: number, contractId: number) => void + + // 로딩 상태 (선택사항) + isLoading?: boolean +} + +export function ProjectSwitcher({ + isCollapsed, + projects, + selectedContractId, + onSelectContract, + isLoading = false, +}: ProjectSwitcherProps) { + const [popoverOpen, setPopoverOpen] = React.useState(false) + const [searchTerm, setSearchTerm] = React.useState("") + + // 현재 선택된 contract 객체 찾기 + const selectedContract = React.useMemo(() => { + if (!selectedContractId) return null + for (const proj of projects) { + const found = proj.contracts.find((c) => c.contractId === selectedContractId) + if (found) { + return { ...found, projectId: proj.projectId, projectName: proj.projectName } + } + } + return null + }, [projects, selectedContractId]) + + // Trigger label => 계약 이름 or placeholder + const triggerLabel = selectedContract?.contractName ?? "Select a contract" + + // 검색어에 따른 필터링된 프로젝트/계약 목록 + const filteredProjects = React.useMemo(() => { + if (!searchTerm) return projects + + return projects.map(project => ({ + ...project, + contracts: project.contracts.filter(contract => + contract.contractName.toLowerCase().includes(searchTerm.toLowerCase()) || + project.projectName.toLowerCase().includes(searchTerm.toLowerCase()) + ) + })).filter(project => project.contracts.length > 0) + }, [projects, searchTerm]) + + // 계약 선택 핸들러 + function handleSelectContract(projectId: number, contractId: number) { + onSelectContract(projectId, contractId) + setPopoverOpen(false) + setSearchTerm("") // 검색어 초기화 + } + + // 총 계약 수 계산 (빈 상태 표시용) + const totalContracts = filteredProjects.reduce((sum, project) => sum + project.contracts.length, 0) + + return ( + <Popover open={popoverOpen} onOpenChange={setPopoverOpen}> + <PopoverTrigger asChild> + <Button + type="button" + variant="outline" + className={cn( + "justify-between relative", + isCollapsed ? "h-9 w-9 shrink-0 items-center justify-center p-0" : "w-full h-9" + )} + disabled={isLoading} + aria-label="Select Contract" + > + {isLoading ? ( + <> + <span className={cn(isCollapsed && "hidden")}>Loading...</span> + <Loader2 className={cn("h-4 w-4 animate-spin", !isCollapsed && "ml-2")} /> + </> + ) : ( + <> + <span className={cn("truncate flex-grow text-left", isCollapsed && "hidden")}> + {triggerLabel} + </span> + <ChevronsUpDown className={cn("h-4 w-4 opacity-50 flex-shrink-0", isCollapsed && "hidden")} /> + </> + )} + </Button> + </PopoverTrigger> + + <PopoverContent className="w-[320px] p-0" align="start"> + <Command> + <CommandInput + placeholder="Search contracts..." + value={searchTerm} + onValueChange={setSearchTerm} + /> + + <CommandList + className="max-h-[320px]" + onWheel={(e) => { + e.stopPropagation() // 이벤트 전파 차단 + const target = e.currentTarget + target.scrollTop += e.deltaY // 직접 스크롤 처리 + }} + > + <CommandEmpty> + {totalContracts === 0 ? "No contracts found." : "No search results."} + </CommandEmpty> + + {filteredProjects.map((project) => ( + <CommandGroup key={project.projectCode} heading={project.projectName}> + {project.contracts.map((contract) => ( + <CommandItem + key={contract.contractId} + onSelect={() => handleSelectContract(project.projectId, contract.contractId)} + value={`${project.projectName} ${contract.contractName}`} + className="truncate" + title={contract.contractName} + > + <span className="truncate">{contract.contractName}</span> + <Check + className={cn( + "ml-auto h-4 w-4 flex-shrink-0", + selectedContractId === contract.contractId ? "opacity-100" : "opacity-0" + )} + /> + </CommandItem> + ))} + </CommandGroup> + ))} + </CommandList> + </Command> + </PopoverContent> + </Popover> + ) +}
\ No newline at end of file |
