summaryrefslogtreecommitdiff
path: root/lib/swp
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-10-24 18:55:27 +0900
committerjoonhoekim <26rote@gmail.com>2025-10-24 18:55:27 +0900
commit39fc95095ac4b99186294f21fe6d8ac0cfab1f6e (patch)
tree50f88724a3e4709316a4b0aa4a3519476d51d86f /lib/swp
parent8b777900a2908efebd767493f77ee3ad489b49a7 (diff)
(김준회) 리비전, 파일 조회는 dialog 로 분리(옥프로 요청)
Diffstat (limited to 'lib/swp')
-rw-r--r--lib/swp/table/swp-table-columns.tsx32
-rw-r--r--lib/swp/table/swp-table.tsx300
2 files changed, 220 insertions, 112 deletions
diff --git a/lib/swp/table/swp-table-columns.tsx b/lib/swp/table/swp-table-columns.tsx
index f79a7441..b18e2b27 100644
--- a/lib/swp/table/swp-table-columns.tsx
+++ b/lib/swp/table/swp-table-columns.tsx
@@ -3,7 +3,7 @@
import { ColumnDef } from "@tanstack/react-table";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
-import { ChevronDown, ChevronRight, FileIcon } from "lucide-react";
+import { ChevronDown, ChevronRight, FileIcon, Download } from "lucide-react";
import { formatDistanceToNow } from "date-fns";
import { ko } from "date-fns/locale";
import type { SwpDocumentWithStats } from "../actions";
@@ -12,20 +12,16 @@ export const swpDocumentColumns: ColumnDef<SwpDocumentWithStats>[] = [
{
id: "expander",
header: () => null,
- cell: ({ row }) => {
- return row.getCanExpand() ? (
+ cell: () => {
+ return (
<Button
variant="ghost"
size="sm"
className="h-8 w-8 p-0"
>
- {row.getIsExpanded() ? (
- <ChevronDown className="h-4 w-4" />
- ) : (
- <ChevronRight className="h-4 w-4" />
- )}
+ <ChevronRight className="h-4 w-4" />
</Button>
- ) : null;
+ );
},
size: 50,
},
@@ -388,5 +384,23 @@ export const swpFileColumns: ColumnDef<FileRow>[] = [
),
size: 100,
},
+ {
+ id: "actions",
+ header: "작업",
+ cell: ({ row }) => (
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() => {
+ // TODO: 파일 다운로드 로직 구현
+ console.log("Download file:", row.original.FILE_NM);
+ }}
+ >
+ <Download className="h-4 w-4 mr-1" />
+ 다운로드
+ </Button>
+ ),
+ size: 120,
+ },
];
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>
);
}