import { type ReactNode } from 'react'; import { Clock, Building2, Link2, CornerDownRight } from 'lucide-react'; import { DataTable } from '@/components/ui/data-table'; import { Typography, typographyVariants } from '@/components/ui/typography'; import { cn } from '@/lib/utils'; import { useQuotationChain } from '../hooks/useQuotationChain'; import { type Estimate, type Product, type ChainRoundState, quotationStatusLabel, quotationTypeLabel, chainRoundState, is1v1, CHAIN_ROUND_STATE_LABEL, } from '../types'; import { QuotationStatus } from '@/api/generated/model'; type QuotationTableProps = { data: Estimate[]; products: Product[]; onOpenDetail: (id: string) => void; /** 견적번호 클릭 → 그 번호로 목록 필터(같은 체인의 차수만 모아 보기). */ onFilterChain?: (number: string) => void; footer?: ReactNode; /** 툴바와 한 카드로 붙일 때 테이블 자체 테두리/라운드를 죽이는 용도 */ className?: string; }; const statusBadgeClass = (status?: number | null) => { switch (status) { case QuotationStatus.CREATED: return 'bg-yellow-50 text-yellow-700 border-yellow-300'; case QuotationStatus.IN_PROGRESS: return 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950/25 dark:text-emerald-400 border-emerald-300/40'; case QuotationStatus.CLOSED: return 'bg-blue-50 text-blue-700 border-blue-300'; default: return 'bg-zinc-100 text-zinc-600'; } }; // 마감결과 배지 색. 낙찰=초록 / 개찰(낙찰자 미정, 수동 처리)=주황 / 진행중=회색(아직 마감 전). const outcomeBadgeClass = (state: ChainRoundState) => { switch (state) { case 'awarded': return 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950/25 dark:text-emerald-400 border-emerald-300/50'; case 'opened': return 'bg-amber-50 text-amber-700 dark:bg-amber-950/25 dark:text-amber-400 border-amber-300/50'; default: return 'bg-zinc-100 text-zinc-500 dark:bg-zinc-800/40 dark:text-zinc-400 border-zinc-300/40'; } }; export function QuotationTable({ data, products, onOpenDetail, onFilterChain, footer, className }: QuotationTableProps) { return ( est.id ?? ''} onRowClick={(est) => onOpenDetail(est.id ?? '')} empty="진행 중인 전자 견적 및 자동 협상 계약 내역이 존재하지 않습니다." footer={footer} columns={[ { header: '견적건명', cell: (est) => { const product = products.find((p) => p.id === est.productId); const productName = product?.name ?? est.productName; // 목록에 없으면 서버 조인 상품명으로 폴백 return (
{est.title} 대상 상품: {productName ?? '확인 불가'} (₩{(product?.price ?? 0).toLocaleString()}) {/* 이 견적에서 재생성된 다음 차수(직속 자식)만 행 밑에 표시 — 전체 체인 반복 X */}
); }, }, { header: '견적번호', cellClassName: 'font-mono text-muted-foreground', cell: (est) => onFilterChain && est.number ? ( ) : ( {est.number} ), }, { header: '유형', align: 'center', // 유형은 고정 속성이라 pill 대신 평문 — 협상(1:1)만 살짝 진하게, 경매(1:N)는 연하게. cell: (est) => ( {quotationTypeLabel(est.type)} ), }, { header: '차수', align: 'center', cell: (est) => ( {est.round}차 ), }, { header: '견적상태', align: 'center', cell: (est) => ( {quotationStatusLabel(est.status)} ), }, { header: '마감결과', align: 'center', // 상세 마감사유는 상세 드로어에서만. 목록은 거친 결과(낙찰/재생성/결렬/진행중)만, // 낙찰이면 배지에 '낙찰 - 낙찰사명' 한 줄로 붙인다. cell: (est) => { const state = chainRoundState(est); const winner = state === 'awarded' ? est.preferred_sp_name : null; return ( {CHAIN_ROUND_STATE_LABEL[state]} {winner ? ` - ${winner}` : ''} ); }, }, { header: '마감기한', cellClassName: 'font-mono text-muted-foreground whitespace-nowrap', cell: (est) => (
{est.dueDate}
), }, { header: '생성일', cellClassName: 'font-mono text-muted-foreground whitespace-nowrap', cell: (est) => ( {est.createdDate ?? '-'} ), }, { header: '작성자', align: 'center', cellClassName: 'text-muted-foreground whitespace-nowrap', cell: (est) => ( {est.creatorName ?? '-'} ), }, { header: '협력사수', align: 'center', cellClassName: 'font-mono font-bold text-foreground', cell: (est) => ( {est.participationCount}개사 ), }, ]} /> ); } function RegeneratedChild({ number, currentQtId, onOpenRound, }: { number?: string | null; currentQtId?: string; onOpenRound: (qtId: string) => void; }) { const { rounds } = useQuotationChain(number); const current = rounds.find((r) => r.qt_id === currentQtId); // rounds 는 round 오름차순 → 현재보다 큰 첫 라운드가 직속 자식. const child = current ? rounds.find((r) => r.round > current.round) : undefined; if (!child) return null; return (
재생성됨
); }