diff options
Diffstat (limited to 'lib/b-rfq/attachment/vendor-responses-panel.tsx')
| -rw-r--r-- | lib/b-rfq/attachment/vendor-responses-panel.tsx | 386 |
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 |
