summaryrefslogtreecommitdiff
path: root/lib/swp/table/swp-revision-list-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/swp/table/swp-revision-list-dialog.tsx')
-rw-r--r--lib/swp/table/swp-revision-list-dialog.tsx310
1 files changed, 310 insertions, 0 deletions
diff --git a/lib/swp/table/swp-revision-list-dialog.tsx b/lib/swp/table/swp-revision-list-dialog.tsx
new file mode 100644
index 00000000..74402bd9
--- /dev/null
+++ b/lib/swp/table/swp-revision-list-dialog.tsx
@@ -0,0 +1,310 @@
+"use client";
+
+import React, { useState } from "react";
+import {
+ useReactTable,
+ getCoreRowModel,
+ getExpandedRowModel,
+ flexRender,
+ ExpandedState,
+} from "@tanstack/react-table";
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+} from "@/components/ui/dialog";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import { Button } from "@/components/ui/button";
+import { Loader2 } from "lucide-react";
+import { swpRevisionColumns, swpFileColumns, type RevisionRow, type FileRow } from "./swp-table-columns";
+import type { SwpDocumentWithStats } from "../actions";
+
+interface SwpRevisionListDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ document: SwpDocumentWithStats | null;
+ revisions: RevisionRow[];
+ fileData: Record<number, FileRow[]>;
+ loadingRevisions: boolean;
+ loadingFiles: Set<number>;
+ onLoadFiles: (revisionId: number) => void;
+ onLoadAllFiles: () => void;
+}
+
+export function SwpRevisionListDialog({
+ open,
+ onOpenChange,
+ document,
+ revisions,
+ fileData,
+ loadingRevisions,
+ loadingFiles,
+ onLoadFiles,
+ onLoadAllFiles,
+}: SwpRevisionListDialogProps) {
+ return (
+ <Dialog open={open} onOpenChange={onOpenChange}>
+ <DialogContent className="max-w-6xl max-h-[90vh]">
+ <DialogHeader>
+ <DialogTitle>문서 상세</DialogTitle>
+ {document && (
+ <DialogDescription>
+ {document.DOC_NO} - {document.DOC_TITLE}
+ </DialogDescription>
+ )}
+ </DialogHeader>
+
+ {document && (
+ <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">{document.PROJ_NO}</div>
+ {document.PROJ_NM && (
+ <div className="text-xs text-muted-foreground">{document.PROJ_NM}</div>
+ )}
+ </div>
+ <div>
+ <span className="text-sm font-semibold">패키지:</span>
+ <div className="text-sm">{document.PKG_NO || "-"}</div>
+ </div>
+ <div>
+ <span className="text-sm font-semibold">업체:</span>
+ <div className="text-sm">{document.CPY_NM || "-"}</div>
+ {document.VNDR_CD && (
+ <div className="text-xs text-muted-foreground">{document.VNDR_CD}</div>
+ )}
+ </div>
+ <div>
+ <span className="text-sm font-semibold">최신 리비전:</span>
+ <div className="text-sm">{document.LTST_REV_NO || "-"}</div>
+ </div>
+ </div>
+
+ {/* 리비전 및 파일 목록 */}
+ {loadingRevisions ? (
+ <div className="flex items-center justify-center p-8">
+ <Loader2 className="h-6 w-6 animate-spin" />
+ <span className="ml-2">리비전 로딩 중...</span>
+ </div>
+ ) : revisions.length ? (
+ <DocumentDetailView
+ revisions={revisions}
+ fileData={fileData}
+ loadingFiles={loadingFiles}
+ onLoadFiles={onLoadFiles}
+ onLoadAllFiles={onLoadAllFiles}
+ />
+ ) : (
+ <div className="p-8 text-center text-muted-foreground">
+ 리비전 없음
+ </div>
+ )}
+ </div>
+ )}
+ </DialogContent>
+ </Dialog>
+ );
+}
+
+// ============================================================================
+// 문서 상세 뷰 (Dialog용)
+// ============================================================================
+
+interface DocumentDetailViewProps {
+ revisions: RevisionRow[];
+ fileData: Record<number, FileRow[]>;
+ loadingFiles: Set<number>;
+ onLoadFiles: (revisionId: number) => void;
+ onLoadAllFiles: () => void;
+}
+
+function DocumentDetailView({
+ revisions,
+ fileData,
+ loadingFiles,
+ onLoadFiles,
+ onLoadAllFiles,
+}: DocumentDetailViewProps) {
+ const [expandedRevisions, setExpandedRevisions] = useState<ExpandedState>({});
+ const [allExpanded, setAllExpanded] = useState(false);
+
+ const revisionTable = useReactTable({
+ data: revisions,
+ columns: swpRevisionColumns,
+ state: {
+ expanded: expandedRevisions,
+ },
+ onExpandedChange: setExpandedRevisions,
+ getCoreRowModel: getCoreRowModel(),
+ getExpandedRowModel: getExpandedRowModel(),
+ getRowCanExpand: () => true,
+ });
+
+ 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="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()
+ )}
+ </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();
+ 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>
+
+ {/* 파일 행들 (확장 시) */}
+ {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>
+ );
+}
+
+// ============================================================================
+// 파일 서브 테이블
+// ============================================================================
+
+interface FileSubTableProps {
+ files: FileRow[];
+}
+
+function FileSubTable({ files }: FileSubTableProps) {
+ const fileTable = useReactTable({
+ data: files,
+ columns: swpFileColumns,
+ getCoreRowModel: getCoreRowModel(),
+ });
+
+ return (
+ <div className="border-l-4 border-green-200">
+ <Table>
+ <TableHeader>
+ {fileTable.getHeaderGroups().map((headerGroup) => (
+ <TableRow key={headerGroup.id} className="bg-blue-50/50">
+ {headerGroup.headers.map((header) => (
+ <TableHead key={header.id} className="font-semibold text-xs">
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+ </TableHead>
+ ))}
+ </TableRow>
+ ))}
+ </TableHeader>
+ <TableBody>
+ {fileTable.getRowModel().rows.map((row) => (
+ <TableRow key={row.id} className="bg-green-50/20 hover:bg-green-50/40">
+ {row.getVisibleCells().map((cell) => (
+ <TableCell key={cell.id} className="py-2">
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+ </TableCell>
+ ))}
+ </TableRow>
+ ))}
+ </TableBody>
+ </Table>
+ </div>
+ );
+}
+