diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-13 07:08:01 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-13 07:08:01 +0000 |
| commit | c72d0897f7b37843109c86f61d97eba05ba3ca0d (patch) | |
| tree | 887dd877f3f8beafa92b4d9a7b16c84b4a5795d8 /lib/b-rfq/attachment/vendor-responses-panel.tsx | |
| parent | ff902243a658067fae858a615c0629aa2e0a4837 (diff) | |
(대표님) 20250613 16시 08분 b-rfq, document 등
Diffstat (limited to 'lib/b-rfq/attachment/vendor-responses-panel.tsx')
| -rw-r--r-- | lib/b-rfq/attachment/vendor-responses-panel.tsx | 205 |
1 files changed, 205 insertions, 0 deletions
diff --git a/lib/b-rfq/attachment/vendor-responses-panel.tsx b/lib/b-rfq/attachment/vendor-responses-panel.tsx new file mode 100644 index 00000000..901af3bf --- /dev/null +++ b/lib/b-rfq/attachment/vendor-responses-panel.tsx @@ -0,0 +1,205 @@ +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Skeleton } from "@/components/ui/skeleton" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table" +import { RefreshCw, Download, MessageSquare, Clock, CheckCircle2, XCircle, AlertCircle } from "lucide-react" +import { formatDate } from "@/lib/utils" + +interface VendorResponsesPanelProps { + attachment: any + responses: any[] + isLoading: boolean + onRefresh: () => void +} + +export function VendorResponsesPanel({ + attachment, + responses, + isLoading, + onRefresh +}: VendorResponsesPanelProps) { + + const getStatusIcon = (status: string) => { + switch (status) { + case 'RESPONDED': + return <CheckCircle2 className="h-4 w-4 text-green-600" /> + case 'NOT_RESPONDED': + return <Clock className="h-4 w-4 text-orange-600" /> + case 'WAIVED': + return <XCircle className="h-4 w-4 text-gray-500" /> + case 'REVISION_REQUESTED': + return <AlertCircle className="h-4 w-4 text-yellow-600" /> + default: + return <Clock className="h-4 w-4 text-gray-400" /> + } + } + + const getStatusBadgeVariant = (status: string) => { + switch (status) { + case 'RESPONDED': + return 'default' + case 'NOT_RESPONDED': + return 'secondary' + case 'WAIVED': + return 'outline' + case 'REVISION_REQUESTED': + return 'destructive' + default: + return 'secondary' + } + } + + if (isLoading) { + return ( + <div className="space-y-4"> + <div className="flex items-center justify-between"> + <Skeleton className="h-6 w-48" /> + <Skeleton className="h-9 w-24" /> + </div> + <div className="space-y-3"> + {Array.from({ length: 3 }).map((_, i) => ( + <Skeleton key={i} className="h-12 w-full" /> + ))} + </div> + </div> + ) + } + + return ( + <div className="space-y-4"> + {/* 헤더 */} + <div className="flex items-center justify-between"> + <div className="space-y-1"> + <h3 className="text-lg font-medium flex items-center gap-2"> + <MessageSquare className="h-5 w-5" /> + 벤더 응답 현황: {attachment.originalFileName} + </h3> + <div className="flex flex-wrap gap-2 text-sm text-muted-foreground"> + <Badge variant="outline"> + {attachment.attachmentType} + </Badge> + <span>시리얼: {attachment.serialNo}</span> + <span>등록: {formatDate(attachment.createdAt)}</span> + {attachment.responseStats && ( + <Badge variant="secondary"> + 응답률: {attachment.responseStats.responseRate}% + </Badge> + )} + </div> + </div> + <Button + variant="outline" + size="sm" + onClick={onRefresh} + className="flex items-center gap-2" + > + <RefreshCw className="h-4 w-4" /> + 새로고침 + </Button> + </div> + + {/* 테이블 */} + {responses.length === 0 ? ( + <div className="text-center py-8 text-muted-foreground border rounded-lg"> + 이 문서에 대한 벤더 응답 정보가 없습니다. + </div> + ) : ( + <div className="border rounded-lg"> + <Table> + <TableHeader> + <TableRow> + <TableHead>벤더</TableHead> + <TableHead>국가</TableHead> + <TableHead>응답 상태</TableHead> + <TableHead>리비전</TableHead> + <TableHead>요청일</TableHead> + <TableHead>응답일</TableHead> + <TableHead>벤더 코멘트</TableHead> + <TableHead className="w-[100px]">액션</TableHead> + </TableRow> + </TableHeader> + <TableBody> + {responses.map((response) => ( + <TableRow key={response.id}> + <TableCell className="font-medium"> + <div> + <div>{response.vendorName}</div> + <div className="text-xs text-muted-foreground"> + {response.vendorCode} + </div> + </div> + </TableCell> + + <TableCell> + {response.vendorCountry} + </TableCell> + + <TableCell> + <div className="flex items-center gap-2"> + {getStatusIcon(response.responseStatus)} + <Badge variant={getStatusBadgeVariant(response.responseStatus)}> + {response.responseStatus} + </Badge> + </div> + </TableCell> + + <TableCell> + <div className="text-sm"> + <div>현재: {response.currentRevision}</div> + {response.respondedRevision && ( + <div className="text-muted-foreground"> + 응답: {response.respondedRevision} + </div> + )} + </div> + </TableCell> + + <TableCell> + {formatDate(response.requestedAt)} + </TableCell> + + <TableCell> + {response.respondedAt ? formatDate(response.respondedAt) : '-'} + </TableCell> + + <TableCell> + {response.vendorComment ? ( + <div className="max-w-[200px] truncate" title={response.vendorComment}> + {response.vendorComment} + </div> + ) : ( + '-' + )} + </TableCell> + + <TableCell> + <div className="flex items-center gap-1"> + {response.responseStatus === 'RESPONDED' && ( + <Button + variant="ghost" + size="sm" + className="h-8 w-8 p-0" + title="첨부파일 다운로드" + > + <Download className="h-4 w-4" /> + </Button> + )} + <Button + variant="ghost" + size="sm" + className="h-8 w-8 p-0" + title="상세 보기" + > + <MessageSquare className="h-4 w-4" /> + </Button> + </div> + </TableCell> + </TableRow> + ))} + </TableBody> + </Table> + </div> + )} + </div> + ) +}
\ No newline at end of file |
