From 39fc95095ac4b99186294f21fe6d8ac0cfab1f6e Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Fri, 24 Oct 2025 18:55:27 +0900
Subject: (김준회) 리비전, 파일 조회는 dialog 로 분리(옥프로 요청)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/swp/table/swp-table.tsx | 300 +++++++++++++++++++++++++++++---------------
 1 file changed, 197 insertions(+), 103 deletions(-)

(limited to 'lib/swp/table/swp-table.tsx')

diff --git a/lib/swp/table/swp-table.tsx b/lib/swp/table/swp-table.tsx
index fb3a504e..8ae90bdd 100644
--- a/lib/swp/table/swp-table.tsx
+++ b/lib/swp/table/swp-table.tsx
@@ -17,6 +17,13 @@ import {
   TableRow,
 } from "@/components/ui/table";
 import { Button } from "@/components/ui/button";
+import {
+  Dialog,
+  DialogContent,
+  DialogHeader,
+  DialogTitle,
+  DialogDescription,
+} from "@/components/ui/dialog";
 import { Loader2 } from "lucide-react";
 import { swpDocumentColumns, swpRevisionColumns, swpFileColumns, type RevisionRow, type FileRow } from "./swp-table-columns";
 import { fetchDocumentRevisions, fetchRevisionFiles, type SwpDocumentWithStats } from "../actions";
@@ -43,6 +50,8 @@ export function SwpTable({
   const [fileData, setFileData] = useState<Record<number, FileRow[]>>({});
   const [loadingRevisions, setLoadingRevisions] = useState<Set<string>>(new Set());
   const [loadingFiles, setLoadingFiles] = useState<Set<number>>(new Set());
+  const [dialogOpen, setDialogOpen] = useState(false);
+  const [selectedDocument, setSelectedDocument] = useState<SwpDocumentWithStats | null>(null);
 
   const table = useReactTable({
     data: initialData,
@@ -104,10 +113,26 @@ export function SwpTable({
     }
   };
 
-  // 문서 행 확장 핸들러
-  const handleDocumentExpand = (docNo: string, isExpanded: boolean) => {
-    if (isExpanded) {
-      loadRevisions(docNo);
+  // 문서 클릭 핸들러 - Dialog 열기
+  const handleDocumentClick = async (document: SwpDocumentWithStats) => {
+    setSelectedDocument(document);
+    setDialogOpen(true);
+    
+    // 리비전 데이터 로드
+    if (!revisionData[document.DOC_NO]) {
+      await loadRevisions(document.DOC_NO);
+    }
+  };
+
+  // 모든 리비전의 파일을 로드
+  const loadAllFiles = async (docNo: string) => {
+    const revisions = revisionData[docNo];
+    if (!revisions) return;
+
+    for (const revision of revisions) {
+      if (!fileData[revision.id]) {
+        await loadFiles(revision.id);
+      }
     }
   };
 
@@ -145,10 +170,8 @@ export function SwpTable({
                       <TableCell key={cell.id}>
                         {cell.column.id === "expander" ? (
                           <div
-                            onClick={() => {
-                              row.toggleExpanded();
-                              handleDocumentExpand(row.original.DOC_NO, row.getIsExpanded());
-                            }}
+                            onClick={() => handleDocumentClick(row.original)}
+                            className="cursor-pointer"
                           >
                             {flexRender(
                               cell.column.columnDef.cell,
@@ -161,31 +184,6 @@ export function SwpTable({
                       </TableCell>
                     ))}
                   </TableRow>
-
-                  {/* 리비전 행들 (확장 시) */}
-                  {row.getIsExpanded() && (
-                    <TableRow>
-                      <TableCell colSpan={swpDocumentColumns.length} className="p-0 bg-muted/30">
-                        {loadingRevisions.has(row.original.DOC_NO) ? (
-                          <div className="flex items-center justify-center p-8">
-                            <Loader2 className="h-6 w-6 animate-spin" />
-                            <span className="ml-2">리비전 로딩 중...</span>
-                          </div>
-                        ) : revisionData[row.original.DOC_NO]?.length ? (
-                          <RevisionSubTable
-                            revisions={revisionData[row.original.DOC_NO]}
-                            fileData={fileData}
-                            loadingFiles={loadingFiles}
-                            onLoadFiles={loadFiles}
-                          />
-                        ) : (
-                          <div className="p-8 text-center text-muted-foreground">
-                            리비전 없음
-                          </div>
-                        )}
-                      </TableCell>
-                    </TableRow>
-                  )}
                 </React.Fragment>
               ))
             ) : (
@@ -227,28 +225,95 @@ export function SwpTable({
           </Button>
         </div>
       </div>
+
+      {/* 문서 상세 Dialog */}
+      <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
+        <DialogContent className="max-w-6xl max-h-[90vh]">
+          <DialogHeader>
+            <DialogTitle>문서 상세</DialogTitle>
+            {selectedDocument && (
+              <DialogDescription>
+                {selectedDocument.DOC_NO} - {selectedDocument.DOC_TITLE}
+              </DialogDescription>
+            )}
+          </DialogHeader>
+
+          {selectedDocument && (
+            <div className="space-y-4 overflow-y-auto">
+              {/* 문서 정보 */}
+              <div className="grid grid-cols-1 md:grid-cols-4 gap-4 p-4 bg-muted/30 rounded-lg">
+                <div>
+                  <span className="text-sm font-semibold">프로젝트:</span>
+                  <div className="text-sm">{selectedDocument.PROJ_NO}</div>
+                  {selectedDocument.PROJ_NM && (
+                    <div className="text-xs text-muted-foreground">{selectedDocument.PROJ_NM}</div>
+                  )}
+                </div>
+                <div>
+                  <span className="text-sm font-semibold">패키지:</span>
+                  <div className="text-sm">{selectedDocument.PKG_NO || "-"}</div>
+                </div>
+                <div>
+                  <span className="text-sm font-semibold">업체:</span>
+                  <div className="text-sm">{selectedDocument.CPY_NM || "-"}</div>
+                  {selectedDocument.VNDR_CD && (
+                    <div className="text-xs text-muted-foreground">{selectedDocument.VNDR_CD}</div>
+                  )}
+                </div>
+                <div>
+                  <span className="text-sm font-semibold">최신 리비전:</span>
+                  <div className="text-sm">{selectedDocument.LTST_REV_NO || "-"}</div>
+                </div>
+              </div>
+
+              {/* 리비전 및 파일 목록 */}
+              {loadingRevisions.has(selectedDocument.DOC_NO) ? (
+                <div className="flex items-center justify-center p-8">
+                  <Loader2 className="h-6 w-6 animate-spin" />
+                  <span className="ml-2">리비전 로딩 중...</span>
+                </div>
+              ) : revisionData[selectedDocument.DOC_NO]?.length ? (
+                <DocumentDetailView
+                  revisions={revisionData[selectedDocument.DOC_NO]}
+                  fileData={fileData}
+                  loadingFiles={loadingFiles}
+                  onLoadFiles={loadFiles}
+                  onLoadAllFiles={() => loadAllFiles(selectedDocument.DOC_NO)}
+                />
+              ) : (
+                <div className="p-8 text-center text-muted-foreground">
+                  리비전 없음
+                </div>
+              )}
+            </div>
+          )}
+        </DialogContent>
+      </Dialog>
     </div>
   );
 }
 
 // ============================================================================
-// 리비전 서브 테이블
+// 문서 상세 뷰 (Dialog용)
 // ============================================================================
 
-interface RevisionSubTableProps {
+interface DocumentDetailViewProps {
   revisions: RevisionRow[];
   fileData: Record<number, FileRow[]>;
   loadingFiles: Set<number>;
   onLoadFiles: (revisionId: number) => void;
+  onLoadAllFiles: () => void;
 }
 
-function RevisionSubTable({
+function DocumentDetailView({
   revisions,
   fileData,
   loadingFiles,
   onLoadFiles,
-}: RevisionSubTableProps) {
+  onLoadAllFiles,
+}: DocumentDetailViewProps) {
   const [expandedRevisions, setExpandedRevisions] = useState<ExpandedState>({});
+  const [allExpanded, setAllExpanded] = useState(false);
 
   const revisionTable = useReactTable({
     data: revisions,
@@ -262,80 +327,109 @@ function RevisionSubTable({
     getRowCanExpand: () => true,
   });
 
-  const handleRevisionExpand = (revisionId: number, isExpanded: boolean) => {
-    if (isExpanded) {
-      onLoadFiles(revisionId);
+  const handleExpandAll = () => {
+    if (allExpanded) {
+      setExpandedRevisions({});
+    } else {
+      const expanded: ExpandedState = {};
+      revisions.forEach((_, index) => {
+        expanded[index] = true;
+      });
+      setExpandedRevisions(expanded);
+      onLoadAllFiles();
     }
+    setAllExpanded(!allExpanded);
+  };
+
+  const handleRevisionExpand = (revisionId: number) => {
+    onLoadFiles(revisionId);
   };
 
   return (
-    <div className="border-l-4 border-blue-200">
-      <Table>
-        <TableHeader>
-          {revisionTable.getHeaderGroups().map((headerGroup) => (
-            <TableRow key={headerGroup.id} className="bg-muted/50">
-              {headerGroup.headers.map((header) => (
-                <TableHead key={header.id} className="font-semibold">
-                  {header.isPlaceholder
-                    ? null
-                    : flexRender(
-                        header.column.columnDef.header,
-                        header.getContext()
-                      )}
-                </TableHead>
-              ))}
-            </TableRow>
-          ))}
-        </TableHeader>
-        <TableBody>
-          {revisionTable.getRowModel().rows.map((row) => (
-            <React.Fragment key={row.id}>
-              {/* 리비전 행 */}
-              <TableRow className="bg-muted/20">
-                {row.getVisibleCells().map((cell) => (
-                  <TableCell key={cell.id}>
-                    {cell.column.id === "expander" ? (
-                      <div
-                        onClick={() => {
-                          row.toggleExpanded();
-                          handleRevisionExpand(row.original.id, row.getIsExpanded());
-                        }}
-                      >
-                        {flexRender(
-                          cell.column.columnDef.cell,
-                          cell.getContext()
+    <div className="space-y-4">
+      {/* 전체 펼치기/접기 버튼 */}
+      <div className="flex justify-end">
+        <Button
+          variant="outline"
+          size="sm"
+          onClick={handleExpandAll}
+        >
+          {allExpanded ? "모두 접기" : "모두 펼치기"}
+        </Button>
+      </div>
+
+      {/* 리비전 테이블 */}
+      <div className="rounded-md border">
+        <Table>
+          <TableHeader>
+            {revisionTable.getHeaderGroups().map((headerGroup) => (
+              <TableRow key={headerGroup.id} className="bg-muted/50">
+                {headerGroup.headers.map((header) => (
+                  <TableHead key={header.id} className="font-semibold">
+                    {header.isPlaceholder
+                      ? null
+                      : flexRender(
+                          header.column.columnDef.header,
+                          header.getContext()
                         )}
-                      </div>
-                    ) : (
-                      flexRender(cell.column.columnDef.cell, cell.getContext())
-                    )}
-                  </TableCell>
+                  </TableHead>
                 ))}
               </TableRow>
-
-              {/* 파일 행들 (확장 시) */}
-              {row.getIsExpanded() && (
-                <TableRow>
-                  <TableCell colSpan={swpRevisionColumns.length} className="p-0 bg-blue-50/30">
-                    {loadingFiles.has(row.original.id) ? (
-                      <div className="flex items-center justify-center p-4">
-                        <Loader2 className="h-5 w-5 animate-spin" />
-                        <span className="ml-2 text-sm">파일 로딩 중...</span>
-                      </div>
-                    ) : fileData[row.original.id]?.length ? (
-                      <FileSubTable files={fileData[row.original.id]} />
-                    ) : (
-                      <div className="p-4 text-center text-sm text-muted-foreground">
-                        파일 없음
-                      </div>
-                    )}
-                  </TableCell>
+            ))}
+          </TableHeader>
+          <TableBody>
+            {revisionTable.getRowModel().rows.map((row) => (
+              <React.Fragment key={row.id}>
+                {/* 리비전 행 */}
+                <TableRow className="bg-muted/20">
+                  {row.getVisibleCells().map((cell) => (
+                    <TableCell key={cell.id}>
+                      {cell.column.id === "expander" ? (
+                        <div
+                          onClick={() => {
+                            row.toggleExpanded();
+                            if (!row.getIsExpanded()) {
+                              handleRevisionExpand(row.original.id);
+                            }
+                          }}
+                          className="cursor-pointer"
+                        >
+                          {flexRender(
+                            cell.column.columnDef.cell,
+                            cell.getContext()
+                          )}
+                        </div>
+                      ) : (
+                        flexRender(cell.column.columnDef.cell, cell.getContext())
+                      )}
+                    </TableCell>
+                  ))}
                 </TableRow>
-              )}
-            </React.Fragment>
-          ))}
-        </TableBody>
-      </Table>
+
+                {/* 파일 행들 (확장 시) */}
+                {row.getIsExpanded() && (
+                  <TableRow>
+                    <TableCell colSpan={swpRevisionColumns.length} className="p-0 bg-blue-50/30">
+                      {loadingFiles.has(row.original.id) ? (
+                        <div className="flex items-center justify-center p-4">
+                          <Loader2 className="h-5 w-5 animate-spin" />
+                          <span className="ml-2 text-sm">파일 로딩 중...</span>
+                        </div>
+                      ) : fileData[row.original.id]?.length ? (
+                        <FileSubTable files={fileData[row.original.id]} />
+                      ) : (
+                        <div className="p-4 text-center text-sm text-muted-foreground">
+                          파일 없음
+                        </div>
+                      )}
+                    </TableCell>
+                  </TableRow>
+                )}
+              </React.Fragment>
+            ))}
+          </TableBody>
+        </Table>
+      </div>
     </div>
   );
 }
-- 
cgit v1.2.3

