From 95866a13ba4e1c235373834460aa284b763fe0d9 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 23 Jun 2025 09:03:29 +0000 Subject: (최겸) 기술영업 RFQ 개발(0620 요구사항, 첨부파일, REV 등) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail-table/quotation-history-dialog.tsx | 312 +++++++++++++++++++++ 1 file changed, 312 insertions(+) create mode 100644 lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx (limited to 'lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx') diff --git a/lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx b/lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx new file mode 100644 index 00000000..7832fa2b --- /dev/null +++ b/lib/techsales-rfq/table/detail-table/quotation-history-dialog.tsx @@ -0,0 +1,312 @@ +"use client" + +import * as React from "react" +import { useState, useEffect } from "react" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog" +import { Badge } from "@/components/ui/badge" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Separator } from "@/components/ui/separator" +import { Skeleton } from "@/components/ui/skeleton" +import { Clock, User, FileText, AlertCircle, Paperclip } from "lucide-react" +import { formatDate } from "@/lib/utils" +import { toast } from "sonner" + +interface QuotationAttachment { + id: number + quotationId: number + revisionId: number + fileName: string + originalFileName: string + fileSize: number + fileType: string | null + filePath: string + description: string | null + isVendorUpload: boolean + createdAt: Date + updatedAt: Date +} + +interface QuotationSnapshot { + currency: string | null + totalPrice: string | null + validUntil: Date | null + remark: string | null + status: string | null + quotationVersion: number | null + submittedAt: Date | null + acceptedAt: Date | null + updatedAt: Date | null +} + +interface QuotationRevision { + id: number + version: number + snapshot: QuotationSnapshot + changeReason: string | null + revisionNote: string | null + revisedBy: number | null + revisedAt: Date + revisedByName: string | null + attachments: QuotationAttachment[] +} + +interface QuotationHistoryData { + current: { + id: number + currency: string | null + totalPrice: string | null + validUntil: Date | null + remark: string | null + status: string + quotationVersion: number | null + submittedAt: Date | null + acceptedAt: Date | null + updatedAt: Date | null + attachments: QuotationAttachment[] + } + revisions: QuotationRevision[] +} + +interface QuotationHistoryDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + quotationId: number | null +} + +const statusConfig = { + "Draft": { label: "초안", color: "bg-yellow-100 text-yellow-800" }, + "Submitted": { label: "제출됨", color: "bg-blue-100 text-blue-800" }, + "Revised": { label: "수정됨", color: "bg-purple-100 text-purple-800" }, + "Accepted": { label: "승인됨", color: "bg-green-100 text-green-800" }, + "Rejected": { label: "거절됨", color: "bg-red-100 text-red-800" }, +} + +function QuotationCard({ + data, + version, + isCurrent = false, + changeReason, + revisedBy, + revisedAt, + attachments +}: { + data: QuotationSnapshot | QuotationHistoryData["current"] + version: number + isCurrent?: boolean + changeReason?: string | null + revisedBy?: string | null + revisedAt?: Date + attachments?: QuotationAttachment[] +}) { + const statusInfo = statusConfig[data.status as keyof typeof statusConfig] || + { label: data.status || "알 수 없음", color: "bg-gray-100 text-gray-800" } + + return ( + + +
+ + 버전 {version} + {isCurrent && 현재} + + + {statusInfo.label} + +
+ {changeReason && ( +
+ + {changeReason} +
+ )} +
+ +
+
+

견적 금액

+

+ {data.totalPrice ? `${data.currency} ${Number(data.totalPrice).toLocaleString()}` : "미입력"} +

+
+
+

유효 기한

+

+ {data.validUntil ? formatDate(data.validUntil) : "미설정"} +

+
+
+ + {data.remark && ( +
+

비고

+

{data.remark}

+
+ )} + + {/* 첨부파일 섹션 */} + {attachments && attachments.length > 0 && ( +
+

+ + 첨부파일 ({attachments.length}개) +

+
+ {attachments.map((attachment) => ( +
+
+
+

+ {attachment.originalFileName} +

+ {attachment.description && ( +

+ {attachment.description} +

+ )} +
+
+
+ {(attachment.fileSize / 1024 / 1024).toFixed(2)} MB +
+
+ ))} +
+
+ )} + + + +
+
+ + + {isCurrent + ? `수정: ${data.updatedAt ? formatDate(data.updatedAt) : "N/A"}` + : `변경: ${revisedAt ? formatDate(revisedAt) : "N/A"}` + } + +
+ {revisedBy && ( +
+ + {revisedBy} +
+ )} +
+
+
+ ) +} + +export function QuotationHistoryDialog({ + open, + onOpenChange, + quotationId +}: QuotationHistoryDialogProps) { + const [data, setData] = useState(null) + const [isLoading, setIsLoading] = useState(false) + + useEffect(() => { + if (open && quotationId) { + loadQuotationHistory() + } + }, [open, quotationId]) + + const loadQuotationHistory = async () => { + if (!quotationId) return + + try { + setIsLoading(true) + const { getTechSalesVendorQuotationWithRevisions } = await import("@/lib/techsales-rfq/service") + + const result = await getTechSalesVendorQuotationWithRevisions(quotationId) + + if (result.error) { + toast.error(result.error) + return + } + + setData(result.data as QuotationHistoryData) + } catch (error) { + console.error("견적 히스토리 로드 오류:", error) + toast.error("견적 히스토리를 불러오는 중 오류가 발생했습니다") + } finally { + setIsLoading(false) + } + } + + const handleOpenChange = (newOpen: boolean) => { + onOpenChange(newOpen) + if (!newOpen) { + setData(null) // 다이얼로그 닫을 때 데이터 초기화 + } + } + + return ( + + + + 견적서 수정 히스토리 + + 견적서의 변경 이력을 확인할 수 있습니다. 최신 버전부터 순서대로 표시됩니다. + + + +
+ {isLoading ? ( +
+ {[1, 2, 3].map((i) => ( +
+ + +
+ ))} +
+ ) : data ? ( + <> + {/* 현재 버전 */} + + + {/* 이전 버전들 */} + {data.revisions.length > 0 ? ( + data.revisions.map((revision) => ( + + )) + ) : ( +
+ +

수정 이력이 없습니다.

+

이 견적서는 아직 수정되지 않았습니다.

+
+ )} + + ) : ( +
+ +

견적서 정보를 불러올 수 없습니다.

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