diff options
Diffstat (limited to 'lib/rfqs/table/ParentRfqSelector.tsx')
| -rw-r--r-- | lib/rfqs/table/ParentRfqSelector.tsx | 307 |
1 files changed, 0 insertions, 307 deletions
diff --git a/lib/rfqs/table/ParentRfqSelector.tsx b/lib/rfqs/table/ParentRfqSelector.tsx deleted file mode 100644 index 0edb1233..00000000 --- a/lib/rfqs/table/ParentRfqSelector.tsx +++ /dev/null @@ -1,307 +0,0 @@ -"use client" - -import * as React from "react" -import { Check, ChevronsUpDown, Loader } from "lucide-react" -import { Button } from "@/components/ui/button" -import { Command, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem } from "@/components/ui/command" -import { Popover, PopoverTrigger, PopoverContent } from "@/components/ui/popover" -import { cn } from "@/lib/utils" -import { useDebounce } from "@/hooks/use-debounce" -import { getBudgetaryRfqs, type BudgetaryRfq } from "../service" -import { RfqType } from "../validations" - -// ParentRfq 타입 정의 (서비스의 BudgetaryRfq와 호환되어야 함) -interface ParentRfq { - id: number; - rfqCode: string; - description: string | null; - rfqType: RfqType; - projectId: number | null; - projectCode: string | null; - projectName: string | null; -} - -interface ParentRfqSelectorProps { - selectedRfqId?: number; - onRfqSelect: (rfq: ParentRfq | null) => void; - rfqType: RfqType; // 현재 생성 중인 RFQ 타입 - parentRfqTypes: RfqType[]; // 선택 가능한 부모 RFQ 타입 목록 - placeholder?: string; -} - -export function ParentRfqSelector({ - selectedRfqId, - onRfqSelect, - rfqType, - parentRfqTypes, - placeholder = "부모 RFQ 선택..." -}: ParentRfqSelectorProps) { - const [searchTerm, setSearchTerm] = React.useState(""); - const debouncedSearchTerm = useDebounce(searchTerm, 300); - - const [open, setOpen] = React.useState(false); - const [loading, setLoading] = React.useState(false); - const [parentRfqs, setParentRfqs] = React.useState<ParentRfq[]>([]); - const [selectedRfq, setSelectedRfq] = React.useState<ParentRfq | null>(null); - const [page, setPage] = React.useState(1); - const [hasMore, setHasMore] = React.useState(true); - const [totalCount, setTotalCount] = React.useState(0); - - const listRef = React.useRef<HTMLDivElement>(null); - - // 타입별로 적절한 검색 placeholder 생성 - const getSearchPlaceholder = () => { - if (rfqType === RfqType.PURCHASE) { - return "BUDGETARY/PURCHASE_BUDGETARY RFQ 검색..."; - } else if (rfqType === RfqType.PURCHASE_BUDGETARY) { - return "BUDGETARY RFQ 검색..."; - } - return "RFQ 코드/설명/프로젝트 검색..."; - }; - - // 초기 선택된 RFQ가 있을 경우 로드 - React.useEffect(() => { - if (selectedRfqId && open) { - const loadSelectedRfq = async () => { - try { - // 단일 RFQ를 id로 조회하는 API 호출 - const result = await getBudgetaryRfqs({ - limit: 1, - rfqId: selectedRfqId - }); - - if ('rfqs' in result && result.rfqs && result.rfqs.length > 0) { - setSelectedRfq(result.rfqs[0] as unknown as ParentRfq); - } - } catch (error) { - console.error("선택된 RFQ 로드 오류:", error); - } - }; - - if (!selectedRfq || selectedRfq.id !== selectedRfqId) { - loadSelectedRfq(); - } - } - }, [selectedRfqId, open, selectedRfq]); - - // 검색어 변경 시 데이터 리셋 및 재로드 - React.useEffect(() => { - if (open) { - setPage(1); - setHasMore(true); - setParentRfqs([]); - loadParentRfqs(1, true); - } - }, [debouncedSearchTerm, open, parentRfqTypes]); - - // 데이터 로드 함수 - const loadParentRfqs = async (pageToLoad: number, reset = false) => { - if (!open || parentRfqTypes.length === 0) return; - - setLoading(true); - try { - const limit = 20; // 한 번에 로드할 항목 수 - const result = await getBudgetaryRfqs({ - search: debouncedSearchTerm, - limit, - offset: (pageToLoad - 1) * limit, - rfqTypes: parentRfqTypes // 현재 RFQ 타입에 맞는 부모 RFQ 타입들로 필터링 - }); - - if ('rfqs' in result && result.rfqs) { - if (reset) { - setParentRfqs(result.rfqs as unknown as ParentRfq[]); - } else { - setParentRfqs(prev => [...prev, ...(result.rfqs as unknown as ParentRfq[])]); - } - - setTotalCount(result.totalCount); - setHasMore(result.rfqs.length === limit && (pageToLoad * limit) < result.totalCount); - setPage(pageToLoad); - } - } catch (error) { - console.error("부모 RFQ 로드 오류:", error); - } finally { - setLoading(false); - } - }; - - // 무한 스크롤 처리 - const handleScroll = () => { - if (listRef.current) { - const { scrollTop, scrollHeight, clientHeight } = listRef.current; - - // 스크롤이 90% 이상 내려갔을 때 다음 페이지 로드 - if (scrollTop + clientHeight >= scrollHeight * 0.9 && !loading && hasMore) { - loadParentRfqs(page + 1); - } - } - }; - - // RFQ를 프로젝트별로 그룹화하는 함수 - const groupRfqsByProject = (rfqs: ParentRfq[]) => { - const groups: Record<string, { - projectId: number | null; - projectCode: string | null; - projectName: string | null; - rfqs: ParentRfq[]; - }> = {}; - - // 'No Project' 그룹 기본 생성 - groups['no-project'] = { - projectId: null, - projectCode: null, - projectName: null, - rfqs: [] - }; - - // 프로젝트별로 RFQ 그룹화 - rfqs.forEach(rfq => { - const key = rfq.projectId ? `project-${rfq.projectId}` : 'no-project'; - - if (!groups[key] && rfq.projectId) { - groups[key] = { - projectId: rfq.projectId, - projectCode: rfq.projectCode, - projectName: rfq.projectName, - rfqs: [] - }; - } - - groups[key].rfqs.push(rfq); - }); - - // 필터링된 결과가 있는 그룹만 남기기 - return Object.values(groups).filter(group => group.rfqs.length > 0); - }; - - // 그룹화된 RFQ 목록 - const groupedRfqs = React.useMemo(() => { - return groupRfqsByProject(parentRfqs); - }, [parentRfqs]); - - // RFQ 선택 처리 - const handleRfqSelect = (rfq: ParentRfq | null) => { - setSelectedRfq(rfq); - onRfqSelect(rfq); - setOpen(false); - }; - - // RFQ 타입에 따른 표시 형식 - const getRfqTypeLabel = (type: RfqType) => { - switch(type) { - case RfqType.BUDGETARY: - return "BUDGETARY"; - case RfqType.PURCHASE_BUDGETARY: - return "PURCHASE_BUDGETARY"; - case RfqType.PURCHASE: - return "PURCHASE"; - default: - return type; - } - }; - - return ( - <Popover open={open} onOpenChange={setOpen}> - <PopoverTrigger asChild> - <Button - variant="outline" - role="combobox" - aria-expanded={open} - className="w-full justify-between" - > - {selectedRfq - ? `${selectedRfq.rfqCode || ""} - ${selectedRfq.description || ""}` - : placeholder} - <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> - </Button> - </PopoverTrigger> - <PopoverContent className="w-[400px] p-0"> - <Command> - <CommandInput - placeholder={getSearchPlaceholder()} - value={searchTerm} - onValueChange={setSearchTerm} - /> - <CommandList - className="max-h-[300px]" - ref={listRef} - onScroll={handleScroll} - > - <CommandEmpty>검색 결과가 없습니다</CommandEmpty> - - <CommandGroup> - <CommandItem - value="none" - onSelect={() => handleRfqSelect(null)} - > - <Check - className={cn( - "mr-2 h-4 w-4", - !selectedRfq - ? "opacity-100" - : "opacity-0" - )} - /> - <span className="font-medium">선택 안함</span> - </CommandItem> - </CommandGroup> - - {groupedRfqs.map((group, index) => ( - <CommandGroup - key={`group-${group.projectId || index}`} - heading={ - group.projectId - ? `${group.projectCode || ""} - ${group.projectName || ""}` - : "프로젝트 없음" - } - > - {group.rfqs.map((rfq) => ( - <CommandItem - key={rfq.id} - value={`${rfq.rfqCode || ""} ${rfq.description || ""}`} - onSelect={() => handleRfqSelect(rfq)} - > - <Check - className={cn( - "mr-2 h-4 w-4", - selectedRfq?.id === rfq.id - ? "opacity-100" - : "opacity-0" - )} - /> - <div className="flex flex-col"> - <div className="flex items-center"> - <span className="font-medium">{rfq.rfqCode || ""}</span> - <span className="ml-2 text-xs px-1.5 py-0.5 rounded bg-slate-100 text-slate-700"> - {getRfqTypeLabel(rfq.rfqType)} - </span> - </div> - {rfq.description && ( - <span className="text-sm text-gray-500 truncate"> - {rfq.description} - </span> - )} - </div> - </CommandItem> - ))} - </CommandGroup> - ))} - - {loading && ( - <div className="py-2 text-center"> - <Loader className="h-4 w-4 animate-spin mx-auto" /> - </div> - )} - - {!loading && !hasMore && parentRfqs.length > 0 && ( - <div className="py-2 text-center text-sm text-muted-foreground"> - 총 {totalCount}개 중 {parentRfqs.length}개 표시됨 - </div> - )} - </CommandList> - </Command> - </PopoverContent> - </Popover> - ); -}
\ No newline at end of file |
