import { Settings, Plus, MoreHorizontal } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem } from '@/components/ui/dropdown-menu'; import { keepPreviousData } from '@tanstack/react-query'; import { useOverlayRouter } from '@/lib/useOverlayRouter'; import { PageContainer } from '@/components/layout/PageContainer'; import { PageToolbar, SearchInput } from '@/components/layout/PageToolbar'; import { TablePagination } from '@/components/ui/table-pagination'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; import { useServerList } from '@/lib/useServerList'; import { useQuotations } from '@/features/quotations/hooks/useQuotations'; import { useGetQuotation } from '@/api/generated/quotation/quotation'; import { QuotationTable } from '@/features/quotations/components/QuotationTable'; import { QuotationDetailSheet } from '@/features/quotations/components/QuotationDetailSheet'; import { QuotationCreateModal } from '@/features/quotations/components/QuotationCreateModal'; import { QuotationSettingsModal } from '@/features/quotations/components/QuotationSettingsModal'; import { QUOTATION_STATUS_OPTIONS, QUOTATION_TYPE_OPTIONS } from '@/features/quotations/types'; import type { ListQuotationsParams } from '@/api/generated/model/listQuotationsParams'; export default function QuotationPage() { // 검색/상태·유형 필터/페이지 상태 → 서버 쿼리 파라미터로 변환. const list = useServerList({ pageSize: 10, initialFilters: { status: 'ALL', type: 'ALL', mine: 'ALL' } }); const statusFilter = list.filters.status; const typeFilter = list.filters.type; const mineFilter = list.filters.mine; const statusOptions = QUOTATION_STATUS_OPTIONS; const typeOptions = QUOTATION_TYPE_OPTIONS; const params: ListQuotationsParams = { search: list.debouncedSearch || undefined, status: statusFilter !== 'ALL' ? statusFilter : undefined, type: typeFilter !== 'ALL' ? typeFilter : undefined, mine: mineFilter === 'MINE' ? true : undefined, page: list.page, size: list.pageSize, }; const { products, partners, cards, quotations, total, quotationSettings, closeQuotation, awardQuotation, addSetting, deleteSetting, createQuotation, regenerateQuotation, notifyQuotation, notifySession, } = useQuotations(params); const totalPages = list.totalPages(total); const overlay = useOverlayRouter(['detail', 'create', 'settings']); const detailId = overlay.get('detail'); const isCreateOpen = overlay.has('create'); const isSettingsOpen = overlay.has('settings'); // 상세 요약은 리스트에서 find 하지 않고 단건 API 로 받아온다(딥링크 시 리스트 의존 제거). // 탭 복귀 시 재조회(자리비운 사이 스케줄러가 마감/낙찰/재생성했을 수 있음). 전역 기본은 false라 상세만 켠다. // 라운드 전환(detailId 교체) 시 새 데이터 도착 전까지 이전 견적을 유지한다. // 이렇게 해야 activeQuotation 이 잠시 null 로 떨어지지 않아 시트(key={qt_id})가 언마운트→재마운트되지 않고, // 그 사이 useScrollLock 이 풀려 배경이 스크롤되는 현상도 사라진다. (목록 등 다른 쿼리와 동일한 패턴) const detailQuery = useGetQuotation(detailId ?? '', { query: { enabled: !!detailId, refetchOnWindowFocus: true, placeholderData: keepPreviousData }, }); // detailId 로 게이트한다 — keepPreviousData 가 닫은 뒤에도 이전 견적을 들고 있어 // detailId 가 null(닫힘)이어도 시트가 안 사라지던 버그 방지. 라운드 전환(둘 다 truthy)은 영향 없음. const activeQuotation = detailId ? (detailQuery.data?.quotation ?? null) : null; return ( {/* 검색/액션 바 + 테이블을 한 카드로 붙인다(포털형). */}
}> overlay.open('settings')}> 견적 세팅 } > list.setSearch(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && list.submitSearch()} onClear={list.clearSearch} placeholder="견적명 또는 견적 번호로 검색..." />
overlay.open('detail', id)} onFilterChain={(number) => { // 견적번호 클릭 → 검색어를 그 번호로 즉시 세팅(같은 체인의 차수만 모아 보기). list.setSearch(number); list.submitSearch(); }} footer={ } />
{detailId && activeQuotation && ( overlay.open('detail', qtId, { replace: true })} onRegenerate={regenerateQuotation} onNotify={notifyQuotation} onNotifySession={notifySession} onClose={overlay.close} /> )} {isCreateOpen && ( { const qtId = await createQuotation(input); if (qtId) overlay.open('detail', qtId); // 생성 완료된 실제 qt_id 로 상세(협상현황) 자동 오픈 return !!qtId; }} onClose={overlay.close} /> )} {isSettingsOpen && ( )}
); }