diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-04-08 03:08:19 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-04-08 03:08:19 +0000 |
| commit | 9ceed79cf32c896f8a998399bf1b296506b2cd4a (patch) | |
| tree | f84750fa6cac954d5e31221fc47a54c655fc06a9 /lib/rfqs/table/BudgetaryRfqSelector.tsx | |
| parent | 230ce796836c25df26c130dbcd616ef97d12b2ec (diff) | |
로그인 및 미들웨어 처리. 구조 변경
Diffstat (limited to 'lib/rfqs/table/BudgetaryRfqSelector.tsx')
| -rw-r--r-- | lib/rfqs/table/BudgetaryRfqSelector.tsx | 261 |
1 files changed, 0 insertions, 261 deletions
diff --git a/lib/rfqs/table/BudgetaryRfqSelector.tsx b/lib/rfqs/table/BudgetaryRfqSelector.tsx deleted file mode 100644 index cea53c1d..00000000 --- a/lib/rfqs/table/BudgetaryRfqSelector.tsx +++ /dev/null @@ -1,261 +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" - -interface BudgetaryRfqSelectorProps { - selectedRfqId?: number; - onRfqSelect: (rfq: BudgetaryRfq | null) => void; - placeholder?: string; -} - -export function BudgetaryRfqSelector({ - selectedRfqId, - onRfqSelect, - placeholder = "Budgetary RFQ 선택..." -}: BudgetaryRfqSelectorProps) { - const [searchTerm, setSearchTerm] = React.useState(""); - const debouncedSearchTerm = useDebounce(searchTerm, 300); - - const [open, setOpen] = React.useState(false); - const [loading, setLoading] = React.useState(false); - const [budgetaryRfqs, setBudgetaryRfqs] = React.useState<BudgetaryRfq[]>([]); - const [selectedRfq, setSelectedRfq] = React.useState<BudgetaryRfq | 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); - - // 초기 선택된 RFQ가 있을 경우 로드 - React.useEffect(() => { - if (selectedRfqId && open) { - const loadSelectedRfq = async () => { - try { - const result = await getBudgetaryRfqs({ - limit: 1, - // null을 undefined로 변환하여 타입 오류 해결 - projectId: selectedRfq?.projectId ?? undefined - }); - - if ('rfqs' in result && result.rfqs) { - // 옵셔널 체이닝 또는 조건부 검사로 undefined 체크 - const foundRfq = result.rfqs.find(rfq => rfq.id === selectedRfqId); - if (foundRfq) { - setSelectedRfq(foundRfq); - } - } - } catch (error) { - console.error("선택된 RFQ 로드 오류:", error); - } - }; - - if (!selectedRfq || selectedRfq.id !== selectedRfqId) { - loadSelectedRfq(); - } - } - }, [selectedRfqId, open, selectedRfq]); - - // 검색어 변경 시 데이터 리셋 및 재로드 - React.useEffect(() => { - if (open) { - setPage(1); - setHasMore(true); - setBudgetaryRfqs([]); - loadBudgetaryRfqs(1, true); - } - }, [debouncedSearchTerm, open]); - - // 데이터 로드 함수 - const loadBudgetaryRfqs = async (pageToLoad: number, reset = false) => { - if (!open) return; - - setLoading(true); - try { - const limit = 20; // 한 번에 로드할 항목 수 - const result = await getBudgetaryRfqs({ - search: debouncedSearchTerm, - limit, - offset: (pageToLoad - 1) * limit, - }); - - if ('rfqs' in result && result.rfqs) { - if (reset) { - setBudgetaryRfqs(result.rfqs); - } else { - setBudgetaryRfqs(prev => [...prev, ...result.rfqs]); - } - - setTotalCount(result.totalCount); - setHasMore(result.rfqs.length === limit && (pageToLoad * limit) < result.totalCount); - setPage(pageToLoad); - } - } catch (error) { - console.error("Budgetary RFQs 로드 오류:", error); - } finally { - setLoading(false); - } - }; - - // 무한 스크롤 처리 - const handleScroll = () => { - if (listRef.current) { - const { scrollTop, scrollHeight, clientHeight } = listRef.current; - - // 스크롤이 90% 이상 내려갔을 때 다음 페이지 로드 - if (scrollTop + clientHeight >= scrollHeight * 0.9 && !loading && hasMore) { - loadBudgetaryRfqs(page + 1); - } - } - }; - - // RFQ를 프로젝트별로 그룹화하는 함수 - const groupRfqsByProject = (rfqs: BudgetaryRfq[]) => { - const groups: Record<string, { - projectId: number | null; - projectCode: string | null; - projectName: string | null; - rfqs: BudgetaryRfq[]; - }> = {}; - - // '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(budgetaryRfqs); - }, [budgetaryRfqs]); - - // RFQ 선택 처리 - const handleRfqSelect = (rfq: BudgetaryRfq | null) => { - setSelectedRfq(rfq); - onRfqSelect(rfq); - setOpen(false); - }; - - 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="Budgetary RFQ 코드/설명/프로젝트 검색..." - 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" - )} - /> - <span className="font-medium">{rfq.rfqCode || ""}</span> - <span className="ml-2 text-gray-500 truncate"> - - {rfq.description || ""} - </span> - </CommandItem> - ))} - </CommandGroup> - ))} - - {loading && ( - <div className="py-2 text-center"> - <Loader className="h-4 w-4 animate-spin mx-auto" /> - </div> - )} - - {!loading && !hasMore && budgetaryRfqs.length > 0 && ( - <div className="py-2 text-center text-sm text-muted-foreground"> - 총 {totalCount}개 중 {budgetaryRfqs.length}개 표시됨 - </div> - )} - </CommandList> - </Command> - </PopoverContent> - </Popover> - ); -}
\ No newline at end of file |
