From 7a1524ba54f43d0f2a19e4bca2c6a2e0b01c5ef1 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 17 Jun 2025 09:02:32 +0000 Subject: (대표님) 20250617 18시 작업사항 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vendor-response/response-detail-sheet.tsx | 358 +++++++++++++++++++++ 1 file changed, 358 insertions(+) create mode 100644 lib/b-rfq/vendor-response/response-detail-sheet.tsx (limited to 'lib/b-rfq/vendor-response/response-detail-sheet.tsx') diff --git a/lib/b-rfq/vendor-response/response-detail-sheet.tsx b/lib/b-rfq/vendor-response/response-detail-sheet.tsx new file mode 100644 index 00000000..da7f9b01 --- /dev/null +++ b/lib/b-rfq/vendor-response/response-detail-sheet.tsx @@ -0,0 +1,358 @@ +// components/rfq/response-detail-sheet.tsx +"use client"; + +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "@/components/ui/sheet"; +import { + FileText, + Upload, + Download, + AlertCircle, + MessageSquare, + FileCheck, + Eye +} from "lucide-react"; +import { formatDateTime, formatFileSize } from "@/lib/utils"; +import { cn } from "@/lib/utils"; +import type { EnhancedVendorResponse } from "@/lib/b-rfq/service"; + +// 파일 다운로드 핸들러 (API 사용) +async function handleFileDownload( + filePath: string, + fileName: string, + type: "client" | "vendor" = "client", + id?: number +) { + try { + const params = new URLSearchParams({ + path: filePath, + type: type, + }); + + // ID가 있으면 추가 + if (id) { + if (type === "client") { + params.append("revisionId", id.toString()); + } else { + params.append("responseFileId", id.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}`); + } + + // Blob으로 파일 데이터 받기 + const blob = await response.blob(); + + // 임시 URL 생성하여 다운로드 + 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 getEffectiveStatusInfo(effectiveStatus: string) { + switch (effectiveStatus) { + case "NOT_RESPONDED": + return { + label: "미응답", + variant: "outline" as const + }; + case "UP_TO_DATE": + return { + label: "최신", + variant: "default" as const + }; + case "VERSION_MISMATCH": + return { + label: "업데이트 필요", + variant: "secondary" as const + }; + case "REVISION_REQUESTED": + return { + label: "수정요청", + variant: "secondary" as const + }; + case "WAIVED": + return { + label: "포기", + variant: "outline" as const + }; + default: + return { + label: effectiveStatus, + variant: "outline" as const + }; + } +} + +interface ResponseDetailSheetProps { + response: EnhancedVendorResponse; + trigger?: React.ReactNode; +} + +export function ResponseDetailSheet({ response, trigger }: ResponseDetailSheetProps) { + const hasMultipleRevisions = response.attachment?.revisions && response.attachment.revisions.length > 1; + const hasResponseFiles = response.responseAttachments && response.responseAttachments.length > 0; + + return ( + + + {trigger || ( + + )} + + + + + + 상세 정보 - {response.serialNo} + + + {response.attachmentType} • {response.attachment?.revisions?.[0]?.originalFileName} + + + +
+ {/* 기본 정보 */} +
+

+ + 기본 정보 +

+
+
+
상태
+
{getEffectiveStatusInfo(response.effectiveStatus).label}
+
+
+
현재 리비전
+
{response.currentRevision}
+
+
+
응답 리비전
+
{response.respondedRevision || "-"}
+
+
+
응답일
+
+ {response.respondedAt ? formatDateTime(new Date(response.respondedAt)) : "-"} +
+
+
+
요청일
+
+ {formatDateTime(new Date(response.requestedAt))} +
+
+
+
응답 파일 수
+
{response.totalResponseFiles}개
+
+
+
+ + {/* 코멘트 정보 */} +
+

+ + 코멘트 +

+
+ {response.responseComment && ( +
+
발주처 응답 코멘트
+
{response.responseComment}
+
+ )} + {response.vendorComment && ( +
+
내부 메모
+
{response.vendorComment}
+
+ )} + {response.attachment?.revisions?.find(r => r.revisionComment) && ( +
+
발주처 요청 사항
+
+ {response.attachment.revisions.find(r => r.revisionComment)?.revisionComment} +
+
+ )} + {!response.responseComment && !response.vendorComment && !response.attachment?.revisions?.find(r => r.revisionComment) && ( +
+ 코멘트가 없습니다. +
+ )} +
+
+ + {/* 발주처 리비전 히스토리 */} + {hasMultipleRevisions && ( +
+

+ + 발주처 리비전 히스토리 ({response.attachment!.revisions.length}개) +

+
+ {response.attachment!.revisions + .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()) + .map((revision) => ( +
+
+ + {revision.revisionNo} + +
+
{revision.originalFileName}
+
+ {formatFileSize(revision.fileSize)} • {formatDateTime(new Date(revision.createdAt))} +
+ {revision.revisionComment && ( +
+ "{revision.revisionComment}" +
+ )} +
+
+ +
+ {revision.isLatest && ( + 최신 + )} + {revision.revisionNo === response.respondedRevision && ( + + 응답됨 + + )} + +
+
+ ))} +
+
+ )} + + {/* 벤더 응답 파일들 */} + {hasResponseFiles && ( +
+

+ + 벤더 응답 파일들 ({response.totalResponseFiles}개) +

+
+ {response.responseAttachments! + .sort((a, b) => new Date(b.uploadedAt).getTime() - new Date(a.uploadedAt).getTime()) + .map((file) => ( +
+
+ + 파일 #{file.fileSequence} + +
+
{file.originalFileName}
+
+ {formatFileSize(file.fileSize)} • {formatDateTime(new Date(file.uploadedAt))} +
+ {file.description && ( +
+ "{file.description}" +
+ )} +
+
+ +
+ {file.isLatestResponseFile && ( + 최신 + )} + +
+
+ ))} +
+
+ )} + + {!hasMultipleRevisions && !hasResponseFiles && ( +
+ +

추가 파일이나 리비전 정보가 없습니다.

+
+ )} +
+
+
+ ); +} \ No newline at end of file -- cgit v1.2.3