"use client"; import React, { useState, useEffect, useMemo } from "react"; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Loader2, Download, FileIcon, AlertCircle, ArrowDownToLine, ArrowUpFromLine, } from "lucide-react"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table"; import { fetchGetRevTreeCompleteList, parseRevisionTree, fetchGetActivityFileList, type ActivityFileApiResponse, } from "@/lib/swp/api-client"; import { downloadVendorFile } from "@/lib/swp/vendor-actions"; import type { DocumentListItem } from "@/lib/swp/document-service"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { formatSwpDateShort, formatFileSize } from "@/lib/swp/utils"; interface SwpDocumentDetailDialogProps { open: boolean; onOpenChange: (open: boolean) => void; document: DocumentListItem | null; projNo: string; vendorCode: string; userId: string; } // Activity 행 데이터 interface ActivityRow { revNo: string; revSeq: string; stage: string; actvNo: string; inOut: "IN" | "OUT"; statusCode: string; statusName: string; transmittalNo: string; refActivityNo: string; createDate: string; createEmpNo: string; } export function SwpDocumentDetailDialog({ open, onOpenChange, document, projNo, }: SwpDocumentDetailDialogProps) { const [activities, setActivities] = useState([]); const [isLoading, setIsLoading] = useState(false); const [selectedActivity, setSelectedActivity] = useState(null); const [activityFiles, setActivityFiles] = useState([]); const [isLoadingFiles, setIsLoadingFiles] = useState(false); // 문서 상세 로드 useEffect(() => { if (open && document) { loadDocumentDetail(); } else { // 다이얼로그 닫힐 때 초기화 setSelectedActivity(null); setActivityFiles([]); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open, document?.DOC_NO]); const loadDocumentDetail = async () => { if (!document) return; setIsLoading(true); setSelectedActivity(null); setActivityFiles([]); try { // GetRevTreeCompleteList 호출 const tree = await fetchGetRevTreeCompleteList({ proj_no: projNo, doc_no: document.DOC_NO, }); const parsed = await parseRevisionTree(tree); // Activity를 flat한 배열로 변환 (테이블용) const flatActivities: ActivityRow[] = []; parsed.revisions.forEach((rev) => { rev.activities.forEach((act) => { flatActivities.push({ revNo: rev.revNo, revSeq: rev.revSeq, stage: rev.stage, actvNo: act.actvNo, inOut: act.inOut, statusCode: act.statusCode, statusName: act.statusName, transmittalNo: act.transmittalNo, refActivityNo: act.refActivityNo, createDate: act.createDate, createEmpNo: act.createEmpNo, }); }); }); setActivities(flatActivities); } catch (error) { console.error("문서 상세 조회 실패:", error); toast.error("문서 리비전 트리를 불러오는데 실패했습니다"); } finally { setIsLoading(false); } }; // Activity 선택 및 파일 로드 const handleActivityClick = async (activity: ActivityRow) => { if (selectedActivity?.actvNo === activity.actvNo) { // 같은 Activity 클릭 시 토글 setSelectedActivity(null); setActivityFiles([]); return; } setSelectedActivity(activity); setIsLoadingFiles(true); setActivityFiles([]); try { // GetActivityFileList 호출 const files = await fetchGetActivityFileList({ proj_no: projNo, doc_no: document?.DOC_NO || "", rev_seq: activity.revSeq, }); // 해당 Activity의 파일만 필터링 const activitySpecificFiles = files.filter( (f) => f.ACTV_NO === activity.actvNo ); setActivityFiles(activitySpecificFiles); } catch (error) { console.error("파일 목록 조회 실패:", error); toast.error("파일 목록을 불러오는데 실패했습니다"); } finally { setIsLoadingFiles(false); } }; const handleDownloadFile = async (fileName: string, ownDocNo: string) => { try { toast.info("파일 다운로드 중..."); const result = await downloadVendorFile(projNo, ownDocNo, fileName); if (!result.success || !result.data) { toast.error(result.error || "파일 다운로드 실패"); return; } // Blob 생성 및 다운로드 const blob = new Blob([Buffer.from(result.data)], { type: result.mimeType }); const url = URL.createObjectURL(blob); const link = window.document.createElement("a"); link.href = url; link.download = result.fileName || fileName; window.document.body.appendChild(link); link.click(); window.document.body.removeChild(link); URL.revokeObjectURL(url); toast.success(`파일 다운로드 완료: ${fileName}`); } catch (error) { console.error("파일 다운로드 실패:", error); toast.error("파일 다운로드에 실패했습니다"); } }; // Revision별로 Activity 그룹핑 (rowspan용) const groupedActivities = useMemo(() => { const groups: Map = new Map(); activities.forEach((activity) => { const key = `${activity.revNo}|${activity.stage}`; if (!groups.has(key)) { groups.set(key, []); } groups.get(key)!.push(activity); }); return groups; }, [activities]); return ( 문서 리비전 히스토리 {document && ( {document.DOC_NO} - {document.DOC_TITLE} )} {document && (
{/* 문서 정보 */}
프로젝트:
{document.PROJ_NO}
{document.PROJ_NM && (
{document.PROJ_NM}
)}
패키지:
{document.PKG_NO || "-"}
업체:
{document.CPY_NM || "-"}
{document.VNDR_CD && (
{document.VNDR_CD}
)}
최신 리비전:
{document.LTST_REV_NO || "-"}
총 Activity:
{activities.length}개
{/* Activity 테이블 */} {isLoading ? (
리비전 트리 로딩 중...
) : activities.length > 0 ? ( <> {/* Activity 테이블 (위) */}
Rev Stage IN/OUT Status Transmittal No Activity No Ref Activity Modified By {Array.from(groupedActivities.entries()).map(([key, groupActivities]) => { const [revNo, stage] = key.split("|"); return groupActivities.map((activity, idx) => ( handleActivityClick(activity)} > {/* Rev 컬럼 (첫 행만 표시, rowspan) */} {idx === 0 && ( {revNo} )} {/* Stage 컬럼 (첫 행만 표시, rowspan) */} {idx === 0 && ( {stage} )} {activity.inOut === "IN" ? ( <> IN ) : ( <> OUT )}
{activity.statusName}
{activity.statusCode}
{activity.transmittalNo || "-"} {activity.actvNo} {activity.refActivityNo || "-"} {formatSwpDateShort(activity.createDate)} {activity.createEmpNo}
)); })}
{/* 파일 목록 (아래) */}

파일 목록

{selectedActivity ? ( <>

Activity: {selectedActivity.actvNo}

Rev {selectedActivity.revNo} ({selectedActivity.stage}) / {selectedActivity.inOut}

) : (

Activity를 선택하면 파일 목록이 표시됩니다

)}
{selectedActivity ? ( isLoadingFiles ? (
파일 로딩 중...
) : activityFiles.length > 0 ? (
{activityFiles.map((file) => (
{file.FILE_NM}
{file.FILE_SZ && (
{formatFileSize(file.FILE_SZ)}
)} {file.STAT && ( {file.STAT} )}
))}
) : (

파일이 없습니다

) ) : (

Activity를 선택해주세요

)}
) : (

Activity 정보가 없습니다

)}
)}
); }