summaryrefslogtreecommitdiff
path: root/lib/b-rfq/attachment/vendor-responses-panel.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-13 07:08:01 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-13 07:08:01 +0000
commitc72d0897f7b37843109c86f61d97eba05ba3ca0d (patch)
tree887dd877f3f8beafa92b4d9a7b16c84b4a5795d8 /lib/b-rfq/attachment/vendor-responses-panel.tsx
parentff902243a658067fae858a615c0629aa2e0a4837 (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.tsx205
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