summaryrefslogtreecommitdiff
path: root/components/bidding/ProjectSelectorBid.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'components/bidding/ProjectSelectorBid.tsx')
-rw-r--r--components/bidding/ProjectSelectorBid.tsx183
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