diff options
Diffstat (limited to 'components/bidding/ProjectSelectorBid.tsx')
| -rw-r--r-- | components/bidding/ProjectSelectorBid.tsx | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/components/bidding/ProjectSelectorBid.tsx b/components/bidding/ProjectSelectorBid.tsx new file mode 100644 index 00000000..de9e435e --- /dev/null +++ b/components/bidding/ProjectSelectorBid.tsx @@ -0,0 +1,183 @@ +"use client" + +import * as React from "react" +import { Check, ChevronsUpDown, X } from "lucide-react" +import { Button } from "@/components/ui/button" +import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover" +import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/components/ui/command" +import { cn } from "@/lib/utils" +import { getProjects, type Project } from "@/lib/rfqs/service" + +interface ProjectSelectorProps { + selectedProjectId?: number | null; + onProjectSelect: (project: Project) => void; + placeholder?: string; + filterType?: string; // 옵션으로 필터 타입 지정 가능 +} + +export function ProjectSelector({ + selectedProjectId, + onProjectSelect, + placeholder = "프로젝트 선택...", + filterType +}: ProjectSelectorProps) { + const [open, setOpen] = React.useState(false) + const [searchTerm, setSearchTerm] = React.useState("") + const [projects, setProjects] = React.useState<Project[]>([]) + const [isLoading, setIsLoading] = React.useState(false) + const [selectedProject, setSelectedProject] = React.useState<Project | null>(null) + + // 모든 프로젝트 데이터 로드 후 plant 타입만 필터링 + React.useEffect(() => { + async function loadAllProjects() { + setIsLoading(true); + try { + const allProjects = await getProjects(); + + // filterType이 지정된 경우 해당 타입만 필터링 + const filteredByType = filterType + ? allProjects.filter(p => p.type === filterType) + : allProjects; + + console.log(`Loaded ${filteredByType.length} ${filterType || 'all'} projects`); + setProjects(filteredByType); + + // 초기 선택된 프로젝트가 있으면 설정 + if (selectedProjectId) { + const selected = filteredByType.find(p => p.id === selectedProjectId); + if (selected) { + setSelectedProject(selected); + } + } + } catch (error) { + console.error("프로젝트 목록 로드 오류:", error); + } finally { + setIsLoading(false); + } + } + + loadAllProjects(); + }, [selectedProjectId, filterType]); + + // 클라이언트 측에서 검색어로 필터링 + const filteredProjects = React.useMemo(() => { + if (!searchTerm.trim()) return projects; + + const lowerSearch = searchTerm.toLowerCase(); + return projects.filter( + project => + project.projectCode.toLowerCase().includes(lowerSearch) || + project.projectName.toLowerCase().includes(lowerSearch) + ); + }, [projects, searchTerm]); + + // 프로젝트 선택 처리 + const handleSelectProject = (project: Project) => { + // 이미 선택된 프로젝트를 다시 선택하면 선택 해제 + if (selectedProject?.id === project.id) { + setSelectedProject(null); + onProjectSelect(null as any); // 선택 해제를 위해 null 전달 + setOpen(false); + return; + } + + setSelectedProject(project); + onProjectSelect(project); + setOpen(false); + }; + + // 프로젝트 선택 해제 + const handleClearSelection = (e: React.MouseEvent) => { + e.stopPropagation(); // Popover가 열리지 않도록 방지 + setSelectedProject(null); + onProjectSelect(null as any); // 선택 해제를 위해 null 전달 + }; + + return ( + <Popover open={open} onOpenChange={setOpen}> + <PopoverTrigger asChild> + <Button + variant="outline" + role="combobox" + aria-expanded={open} + className="w-full justify-between" + disabled={isLoading} + > + {isLoading ? ( + "프로젝트 로딩 중..." + ) : selectedProject ? ( + <div className="flex items-center justify-between w-full"> + <span>{selectedProject.projectCode}</span> + <div className="flex items-center gap-1"> + <Button + variant="ghost" + size="sm" + className="h-4 w-4 p-0 hover:bg-destructive hover:text-destructive-foreground" + onClick={handleClearSelection} + > + <X className="h-3 w-3" /> + </Button> + <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" /> + </div> + </div> + ) : ( + <div className="flex items-center justify-between w-full"> + <span>{placeholder}</span> + <ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" /> + </div> + )} + </Button> + </PopoverTrigger> + <PopoverContent className="w-[400px] p-0"> + <Command> + <CommandInput + placeholder="프로젝트 코드/이름 검색..." + onValueChange={setSearchTerm} + /> + <CommandList + className="max-h-[300px]" + onWheel={(e) => { + e.stopPropagation(); + const target = e.currentTarget; + target.scrollTop += e.deltaY; + }} + > + {isLoading ? ( + <div className="py-6 text-center text-sm">로딩 중...</div> + ) : filteredProjects.length === 0 ? ( + <CommandEmpty> + {searchTerm + ? "검색 결과가 없습니다" + : `${filterType || '해당 타입의'} 프로젝트가 없습니다`} + </CommandEmpty> + ) : ( + <CommandGroup> + {filteredProjects.map((project) => ( + <CommandItem + key={project.id} + value={`${project.projectCode} ${project.projectName}`} + onSelect={() => handleSelectProject(project)} + > + <Check + className={cn( + "mr-2 h-4 w-4", + selectedProject?.id === project.id + ? "opacity-100" + : "opacity-0" + )} + /> + <span className="font-medium">{project.projectCode}</span> + <span className="ml-2 text-gray-500 truncate">- {project.projectName}</span> + {selectedProject?.id === project.id && ( + <span className="ml-auto text-xs text-muted-foreground">(선택됨)</span> + )} + </CommandItem> + ))} + </CommandGroup> + )} + </CommandList> + </Command> + </PopoverContent> + </Popover> + ); +}
\ No newline at end of file |
