summaryrefslogtreecommitdiff
path: root/lib/b-rfq/attachment/vendor-responses-panel.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/b-rfq/attachment/vendor-responses-panel.tsx')
-rw-r--r--lib/b-rfq/attachment/vendor-responses-panel.tsx386
1 files changed, 0 insertions, 386 deletions
diff --git a/lib/b-rfq/attachment/vendor-responses-panel.tsx b/lib/b-rfq/attachment/vendor-responses-panel.tsx
deleted file mode 100644
index 0cbe2a08..00000000
--- a/lib/b-rfq/attachment/vendor-responses-panel.tsx
+++ /dev/null
@@ -1,386 +0,0 @@
-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 {
- Popover,
- PopoverContent,
- PopoverTrigger,
-} from "@/components/ui/popover"
-import {
- RefreshCw,
- Download,
- MessageSquare,
- Clock,
- CheckCircle2,
- XCircle,
- AlertCircle,
- FileText,
- Files,
- AlertTriangle
-} from "lucide-react"
-import { formatDate, formatFileSize } from "@/lib/utils"
-import { RequestRevisionDialog } from "./request-revision-dialog"
-
-interface VendorResponsesPanelProps {
- attachment: any
- responses: any[]
- isLoading: boolean
- onRefresh: () => void
-}
-
-// 파일 다운로드 핸들러
-async function handleFileDownload(filePath: string, fileName: string, fileId: number) {
- try {
- const params = new URLSearchParams({
- path: filePath,
- type: "vendor",
- responseFileId: fileId.toString(),
- });
-
- const response = await fetch(`/api/rfq-attachments/download?${params.toString()}`);
-
- if (!response.ok) {
- const errorData = await response.json();
- throw new Error(errorData.error || `Download failed: ${response.status}`);
- }
-
- const blob = await response.blob();
- const url = window.URL.createObjectURL(blob);
- const link = document.createElement('a');
- link.href = url;
- link.download = fileName;
- document.body.appendChild(link);
- link.click();
-
- document.body.removeChild(link);
- window.URL.revokeObjectURL(url);
-
- console.log("✅ 파일 다운로드 성공:", fileName);
- } catch (error) {
- console.error("❌ 파일 다운로드 실패:", error);
- alert(`파일 다운로드에 실패했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`);
- }
-}
-
-// 파일 목록 컴포넌트
-function FilesList({ files }: { files: any[] }) {
- if (files.length === 0) {
- return (
- <div className="text-center py-4 text-muted-foreground text-sm">
- 업로드된 파일이 없습니다.
- </div>
- );
- }
-
- return (
- <div className="space-y-2 max-h-64 overflow-y-auto">
- {files.map((file, index) => (
- <div key={file.id} className="flex items-center justify-between p-3 border rounded-lg bg-green-50 border-green-200">
- <div className="flex items-center gap-2 flex-1 min-w-0">
- <FileText className="h-4 w-4 text-green-600 flex-shrink-0" />
- <div className="min-w-0 flex-1">
- <div className="font-medium text-sm truncate" title={file.originalFileName}>
- {file.originalFileName}
- </div>
- <div className="text-xs text-muted-foreground">
- {formatFileSize(file.fileSize)} • {formatDate(file.uploadedAt)}
- </div>
- {file.description && (
- <div className="text-xs text-muted-foreground italic mt-1" title={file.description}>
- {file.description}
- </div>
- )}
- </div>
- </div>
- <Button
- size="sm"
- variant="ghost"
- onClick={() => handleFileDownload(file.filePath, file.originalFileName, file.id)}
- className="flex-shrink-0 ml-2"
- title="파일 다운로드"
- >
- <Download className="h-4 w-4" />
- </Button>
- </div>
- ))}
- </div>
- );
-}
-
-export function VendorResponsesPanel({
- attachment,
- responses,
- isLoading,
- onRefresh
-}: VendorResponsesPanelProps) {
-
- console.log(responses)
-
- 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>코멘트</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.totalFiles > 0 ? (
- <div className="flex items-center gap-2">
- <Badge variant="secondary" className="text-xs">
- {response.totalFiles}개
- </Badge>
- {response.totalFiles === 1 ? (
- // 파일이 1개면 바로 다운로드
- <Button
- variant="ghost"
- size="sm"
- className="h-8 w-8 p-0"
- onClick={() => {
- const file = response.files[0];
- handleFileDownload(file.filePath, file.originalFileName, file.id);
- }}
- title={response.latestFile?.originalFileName}
- >
- <Download className="h-4 w-4" />
- </Button>
- ) : (
- // 파일이 여러 개면 Popover로 목록 표시
- <Popover>
- <PopoverTrigger asChild>
- <Button
- variant="ghost"
- size="sm"
- className="h-8 w-8 p-0"
- title="파일 목록 보기"
- >
- <Files className="h-4 w-4" />
- </Button>
- </PopoverTrigger>
- <PopoverContent className="w-96" align="start">
- <div className="space-y-2">
- <div className="font-medium text-sm">
- 응답 파일 목록 ({response.totalFiles}개)
- </div>
- <FilesList files={response.files} />
- </div>
- </PopoverContent>
- </Popover>
- )}
- </div>
- ) : (
- <span className="text-muted-foreground text-sm">-</span>
- )}
- </TableCell>
-
- <TableCell>
- <div className="space-y-1 max-w-[200px]">
- {/* 벤더 응답 코멘트 */}
- {response.responseComment && (
- <div className="flex items-center gap-1">
- <div className="w-2 h-2 rounded-full bg-blue-500 flex-shrink-0" title="벤더 응답 코멘트"></div>
- <div className="text-xs text-blue-600 truncate" title={response.responseComment}>
- {response.responseComment}
- </div>
- </div>
- )}
-
- {/* 수정 요청 사유 */}
- {response.revisionRequestComment && (
- <div className="flex items-center gap-1">
- <div className="w-2 h-2 rounded-full bg-red-500 flex-shrink-0" title="수정 요청 사유"></div>
- <div className="text-xs text-red-600 truncate" title={response.revisionRequestComment}>
- {response.revisionRequestComment}
- </div>
- </div>
- )}
-
- {!response.responseComment && !response.revisionRequestComment && (
- <span className="text-muted-foreground text-sm">-</span>
- )}
- </div>
- </TableCell>
-
- {/* 액션 컬럼 - 수정 요청 기능으로 변경 */}
- <TableCell>
- <div className="flex items-center gap-1">
- {response.responseStatus === 'RESPONDED' && (
- <RequestRevisionDialog
- responseId={response.id}
- attachmentType={attachment.attachmentType}
- serialNo={attachment.serialNo}
- vendorName={response.vendorName}
- currentRevision={response.currentRevision}
- onSuccess={onRefresh}
- trigger={
- <Button
- variant="outline"
- size="sm"
- className="h-8 px-2"
- title="수정 요청"
- >
- <AlertTriangle className="h-3 w-3 mr-1" />
- 수정요청
- </Button>
- }
- />
- )}
-
- {response.responseStatus === 'REVISION_REQUESTED' && (
- <Badge variant="secondary" className="text-xs">
- 수정 요청됨
- </Badge>
- )}
-
- {(response.responseStatus === 'NOT_RESPONDED' || response.responseStatus === 'WAIVED') && (
- <span className="text-muted-foreground text-xs">-</span>
- )}
- </div>
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- </div>
- )}
- </div>
- )
-} \ No newline at end of file