summaryrefslogtreecommitdiff
path: root/lib/rfq-last/attachment/revision-historty-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rfq-last/attachment/revision-historty-dialog.tsx')
-rw-r--r--lib/rfq-last/attachment/revision-historty-dialog.tsx305
1 files changed, 305 insertions, 0 deletions
diff --git a/lib/rfq-last/attachment/revision-historty-dialog.tsx b/lib/rfq-last/attachment/revision-historty-dialog.tsx
new file mode 100644
index 00000000..6e4772cb
--- /dev/null
+++ b/lib/rfq-last/attachment/revision-historty-dialog.tsx
@@ -0,0 +1,305 @@
+// @/lib/rfq-last/attachment/revision-history-dialog.tsx
+
+"use client";
+
+import * as React from "react";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { Skeleton } from "@/components/ui/skeleton";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import {
+ Download,
+ Eye,
+ FileText,
+ Clock,
+ User,
+ MessageSquare,
+ AlertCircle,
+ CheckCircle,
+} from "lucide-react";
+import { format, formatDistanceToNow } from "date-fns";
+import { ko } from "date-fns/locale";
+import { toast } from "sonner";
+import { downloadFile } from "@/lib/file-download";
+import {
+ getRevisionHistory,
+ type AttachmentWithHistory,
+ type RevisionHistory,
+} from "../service";
+import { formatFileSize } from "@/lib/utils";
+
+interface RevisionHistoryDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ attachmentId: number;
+ attachmentName?: string;
+}
+
+export function RevisionHistoryDialog({
+ open,
+ onOpenChange,
+ attachmentId,
+ attachmentName,
+}: RevisionHistoryDialogProps) {
+ const [loading, setLoading] = React.useState(false);
+ const [historyData, setHistoryData] = React.useState<AttachmentWithHistory | null>(null);
+ const [error, setError] = React.useState<string | null>(null);
+
+ // 다이얼로그가 열릴 때 데이터 로드
+ React.useEffect(() => {
+ if (open && attachmentId) {
+ loadRevisionHistory();
+ }
+ }, [open, attachmentId]);
+
+ const loadRevisionHistory = async () => {
+ setLoading(true);
+ setError(null);
+ try {
+ const result = await getRevisionHistory(attachmentId);
+ if (result.success && result.data) {
+ setHistoryData(result.data);
+ } else {
+ setError(result.error || "리비전 히스토리를 불러올 수 없습니다.");
+ }
+ } catch (err) {
+ console.error("Load revision history error:", err);
+ setError("리비전 히스토리 조회 중 오류가 발생했습니다.");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ // 리비전 다운로드
+ const handleDownloadRevision = async (revision: RevisionHistory) => {
+ try {
+ await downloadFile(revision.filePath, revision.originalFileName, {
+ action: 'download',
+ showToast: true,
+ });
+ } catch (err) {
+ console.error("Download revision error:", err);
+ toast.error("파일 다운로드 중 오류가 발생했습니다.");
+ }
+ };
+
+ // 리비전 미리보기
+ const handlePreviewRevision = async (revision: RevisionHistory) => {
+ try {
+ await downloadFile(revision.filePath, revision.originalFileName, {
+ action: 'preview',
+ showToast: true,
+ });
+ } catch (err) {
+ console.error("Preview revision error:", err);
+ toast.error("파일 미리보기 중 오류가 발생했습니다.");
+ }
+ };
+
+ // 리비전 번호에 따른 색상 결정
+ const getRevisionBadgeVariant = (isLatest: boolean) => {
+ return isLatest ? "default" : "secondary";
+ };
+
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-5xl max-h-[80vh]">
+ <DialogHeader>
+ <DialogTitle className="flex items-center gap-2">
+ <FileText className="h-5 w-5" />
+ 리비전 히스토리
+ </DialogTitle>
+ <DialogDescription>
+ {historyData?.originalFileName || attachmentName || "파일"}의 모든 버전 히스토리를 확인할 수 있습니다.
+ </DialogDescription>
+ </DialogHeader>
+
+ <div className="mt-4">
+ {loading ? (
+ <div className="space-y-3">
+ <Skeleton className="h-10 w-full" />
+ <Skeleton className="h-10 w-full" />
+ <Skeleton className="h-10 w-full" />
+ </div>
+ ) : error ? (
+ <Alert variant="destructive">
+ <AlertCircle className="h-4 w-4" />
+ <AlertDescription>{error}</AlertDescription>
+ </Alert>
+ ) : historyData ? (
+ <>
+ {/* 파일 정보 헤더 */}
+ <div className="mb-4 p-3 bg-muted rounded-lg">
+ <div className="grid grid-cols-2 gap-2 text-sm">
+ <div>
+ <span className="text-muted-foreground">일련번호:</span>{" "}
+ <span className="font-medium font-mono">{historyData.serialNo || "-"}</span>
+ </div>
+ <div>
+ <span className="text-muted-foreground">현재 리비전:</span>{" "}
+ <Badge variant="default" className="ml-1">
+ Rev. {historyData.currentRevision || "A"}
+ </Badge>
+ </div>
+ {historyData.description && (
+ <div className="col-span-2">
+ <span className="text-muted-foreground">설명:</span>{" "}
+ <span className="font-medium">{historyData.description}</span>
+ </div>
+ )}
+ </div>
+ </div>
+
+ {/* 리비전 테이블 */}
+ <ScrollArea className="h-[400px] rounded-md border">
+ <Table>
+ <TableHeader>
+ <TableRow>
+ <TableHead className="w-[80px]">리비전</TableHead>
+ <TableHead>파일명</TableHead>
+ <TableHead className="w-[80px]">크기</TableHead>
+ <TableHead className="w-[100px]">업로드자</TableHead>
+ <TableHead className="w-[150px]">업로드일시</TableHead>
+ <TableHead>코멘트</TableHead>
+ <TableHead className="w-[100px] text-center">작업</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {historyData.revisions.length > 0 ? (
+ historyData.revisions.map((revision) => (
+ <TableRow key={revision.id}>
+ <TableCell>
+ <Badge
+ variant={getRevisionBadgeVariant(revision.isLatest)}
+ className="font-mono"
+ >
+ Rev. {revision.revisionNo}
+ {revision.isLatest && (
+ <CheckCircle className="ml-1 h-3 w-3" />
+ )}
+ </Badge>
+ </TableCell>
+ <TableCell>
+ <div className="flex flex-col">
+ <span className="text-sm font-medium truncate max-w-[200px]" title={revision.originalFileName}>
+ {revision.originalFileName}
+ </span>
+ {revision.fileName !== revision.originalFileName && (
+ <span className="text-xs text-muted-foreground truncate max-w-[200px]">
+ ({revision.fileName})
+ </span>
+ )}
+ </div>
+ </TableCell>
+ <TableCell>
+ <span className="text-sm text-muted-foreground">
+ {formatFileSize(revision.fileSize)}
+ </span>
+ </TableCell>
+ <TableCell>
+ <div className="flex items-center gap-1">
+ <User className="h-3 w-3 text-muted-foreground" />
+ <span className="text-sm">
+ {revision.createdByName || "Unknown"}
+ </span>
+ </div>
+ </TableCell>
+ <TableCell>
+ <div className="flex items-center gap-1">
+ <Clock className="h-3 w-3 text-muted-foreground" />
+ <span className="text-sm">
+ {format(new Date(revision.createdAt), "yyyy-MM-dd HH:mm")}
+ </span>
+ </div>
+ <span className="text-xs text-muted-foreground">
+ {formatDistanceToNow(new Date(revision.createdAt), {
+ addSuffix: true,
+ locale: ko,
+ })}
+ </span>
+ </TableCell>
+ <TableCell>
+ {revision.revisionComment ? (
+ <div className="flex items-start gap-1">
+ <MessageSquare className="h-3 w-3 text-muted-foreground mt-0.5" />
+ <span className="text-sm text-muted-foreground">
+ {revision.revisionComment}
+ </span>
+ </div>
+ ) : (
+ <span className="text-sm text-muted-foreground">-</span>
+ )}
+ </TableCell>
+ <TableCell>
+ <div className="flex items-center justify-center gap-1">
+ <Button
+ variant="ghost"
+ size="icon"
+ className="h-8 w-8"
+ onClick={() => handleDownloadRevision(revision)}
+ title="다운로드"
+ >
+ <Download className="h-4 w-4" />
+ </Button>
+ <Button
+ variant="ghost"
+ size="icon"
+ className="h-8 w-8"
+ onClick={() => handlePreviewRevision(revision)}
+ title="미리보기"
+ >
+ <Eye className="h-4 w-4" />
+ </Button>
+ </div>
+ </TableCell>
+ </TableRow>
+ ))
+ ) : (
+ <TableRow>
+ <TableCell colSpan={7} className="text-center text-muted-foreground py-8">
+ 리비전 히스토리가 없습니다.
+ </TableCell>
+ </TableRow>
+ )}
+ </TableBody>
+ </Table>
+ </ScrollArea>
+
+ {/* 요약 정보 */}
+ <div className="mt-4 flex items-center justify-between text-sm text-muted-foreground">
+ <span>총 {historyData.revisions.length}개의 리비전</span>
+ <span>
+ 최초 업로드:{" "}
+ {historyData.revisions.length > 0
+ ? format(
+ new Date(
+ historyData.revisions[historyData.revisions.length - 1].createdAt
+ ),
+ "yyyy년 MM월 dd일"
+ )
+ : "-"}
+ </span>
+ </div>
+ </>
+ ) : null}
+ </div>
+ </DialogContent>
+ </Dialog>
+ );
+} \ No newline at end of file