diff options
Diffstat (limited to 'lib/rfq-last/attachment/revision-historty-dialog.tsx')
| -rw-r--r-- | lib/rfq-last/attachment/revision-historty-dialog.tsx | 305 |
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 |
