diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-17 15:42:29 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-17 15:42:29 +0900 |
| commit | b9f575f6110faabc7b062f84bf491d69d88a10a5 (patch) | |
| tree | 23babc36a1131a861bcb9fbae7fed7314e6ce7e7 /lib/swp/table/swp-inbox-table.tsx | |
| parent | 85d32a79dcf0f7047406039363baa2e06b859ddd (diff) | |
(김준회) swp: GetExternalInboxList를 메인 테이블로 변경
Diffstat (limited to 'lib/swp/table/swp-inbox-table.tsx')
| -rw-r--r-- | lib/swp/table/swp-inbox-table.tsx | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/lib/swp/table/swp-inbox-table.tsx b/lib/swp/table/swp-inbox-table.tsx new file mode 100644 index 00000000..c3f3a243 --- /dev/null +++ b/lib/swp/table/swp-inbox-table.tsx @@ -0,0 +1,244 @@ +"use client"; + +import React, { useState, useMemo } from "react"; +import { + useReactTable, + getCoreRowModel, + flexRender, +} from "@tanstack/react-table"; +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table"; +import { Badge } from "@/components/ui/badge"; +import { ColumnDef } from "@tanstack/react-table"; +import { SwpInboxDocumentDetailDialog } from "./swp-inbox-document-detail-dialog"; +import type { SwpFileApiResponse } from "@/lib/swp/api-client"; + +interface SwpInboxTableProps { + files: SwpFileApiResponse[]; + projNo: string; + vendorCode: string; + userId: string; +} + +// 문서별로 그룹핑된 데이터 타입 +export interface InboxDocumentItem { + ownDocNo: string; + latestRevFileCount: number; // 최신 REV의 파일 개수 + latestStage: string; + latestRevNo: string; + latestStatus: string | null; + latestStatusNm: string | null; + files: SwpFileApiResponse[]; +} + +// 테이블 컬럼 정의 +const inboxDocumentColumns: ColumnDef<InboxDocumentItem>[] = [ + { + accessorKey: "latestStatusNm", + header: "상태", + cell: ({ row }) => { + const statNm = row.original.latestStatusNm; + const stat = row.original.latestStatus; + const displayStatus = statNm || stat || "-"; + + if (!stat) return displayStatus; + + // STAT 코드 기반 색상 결정 + const color = + stat === "SCW03" || stat === "SCW08" ? "bg-green-100 text-green-800" : // Complete, Checked + stat === "SCW02" ? "bg-blue-100 text-blue-800" : // Processing + stat === "SCW01" ? "bg-yellow-100 text-yellow-800" : // Standby + stat === "SCW04" || stat === "SCW05" || stat === "SCW06" ? "bg-red-100 text-red-800" : // Reject, Error Zip, Error Meta + stat === "SCW07" ? "bg-purple-100 text-purple-800" : // Send for Eng Verification + stat === "SCW09" ? "bg-gray-100 text-gray-800" : // Cancelled + stat === "SCW00" ? "bg-orange-100 text-orange-800" : // Upload + "bg-gray-100 text-gray-800"; // 기타 + + return ( + <Badge variant="outline" className={color}> + {displayStatus} + </Badge> + ); + }, + size: 120, + }, + { + accessorKey: "ownDocNo", + header: "OWN_DOC_NO", + cell: ({ row }) => ( + <div className="font-mono text-sm">{row.original.ownDocNo}</div> + ), + size: 300, + }, + { + accessorKey: "latestStage", + header: "최신 스테이지", + cell: ({ row }) => { + const stage = row.original.latestStage; + if (!stage) return "-"; + + const color = + stage === "IFC" ? "bg-green-100 text-green-800" : + stage === "IFA" ? "bg-blue-100 text-blue-800" : + "bg-gray-100 text-gray-800"; + + return ( + <Badge variant="outline" className={color}> + {stage} + </Badge> + ); + }, + size: 120, + }, + { + accessorKey: "latestRevNo", + header: "최신 REV", + cell: ({ row }) => row.original.latestRevNo || "-", + size: 100, + }, + { + accessorKey: "latestRevFileCount", + header: "최신 REV 파일 수", + cell: ({ row }) => ( + <div className="text-center"> + <div className="text-sm font-medium"> + {row.original.latestRevFileCount}개 + </div> + </div> + ), + size: 100, + }, +]; + +export function SwpInboxTable({ + files, + projNo, + vendorCode, + userId, +}: SwpInboxTableProps) { + const [dialogOpen, setDialogOpen] = useState(false); + const [selectedDocument, setSelectedDocument] = useState<InboxDocumentItem | null>(null); + + // 파일들을 문서별로 그룹핑 + const documents = useMemo(() => { + const docMap = new Map<string, SwpFileApiResponse[]>(); + + files.forEach((file) => { + const docNo = file.OWN_DOC_NO; + if (!docMap.has(docNo)) { + docMap.set(docNo, []); + } + docMap.get(docNo)!.push(file); + }); + + const result: InboxDocumentItem[] = []; + + docMap.forEach((docFiles, ownDocNo) => { + // 최신 REV 찾기 (REV_NO 기준으로 정렬) + const sortedByRev = [...docFiles].sort((a, b) => + (b.REV_NO || "").localeCompare(a.REV_NO || "") + ); + const latestRevNo = sortedByRev[0].REV_NO || ""; + + // 최신 REV의 파일들만 필터링 + const latestRevFiles = docFiles.filter(file => file.REV_NO === latestRevNo); + + // 최신 REV 내에서 가장 최근 생성된 파일 찾기 (상태 표시용) + const sortedLatestRevFiles = [...latestRevFiles].sort((a, b) => + (b.CRTE_DTM || "").localeCompare(a.CRTE_DTM || "") + ); + const latestFile = sortedLatestRevFiles[0]; + + result.push({ + ownDocNo, + latestRevFileCount: latestRevFiles.length, // 최신 REV의 파일 개수 + latestStage: latestFile.STAGE || "", + latestRevNo: latestRevNo, + latestStatus: latestFile.STAT, + latestStatusNm: latestFile.STAT_NM, + files: docFiles, // 전체 파일 목록 (상세보기용) + }); + }); + + return result.sort((a, b) => a.ownDocNo.localeCompare(b.ownDocNo)); + }, [files]); + + const table = useReactTable({ + data: documents, + columns: inboxDocumentColumns, + getCoreRowModel: getCoreRowModel(), + }); + + // 문서 클릭 핸들러 + const handleDocumentClick = (document: InboxDocumentItem) => { + setSelectedDocument(document); + setDialogOpen(true); + }; + + return ( + <div className="space-y-4"> + {/* 테이블 */} + <div className="rounded-md border"> + <Table> + <TableHeader> + {table.getHeaderGroups().map((headerGroup) => ( + <TableRow key={headerGroup.id}> + {headerGroup.headers.map((header) => ( + <TableHead key={header.id}> + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + </TableHead> + ))} + </TableRow> + ))} + </TableHeader> + <TableBody> + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + <TableRow + key={row.id} + data-state={row.getIsSelected() && "selected"} + className="hover:bg-muted/50 cursor-pointer" + onClick={() => handleDocumentClick(row.original)} + > + {row.getVisibleCells().map((cell) => ( + <TableCell key={cell.id}> + {flexRender(cell.column.columnDef.cell, cell.getContext())} + </TableCell> + ))} + </TableRow> + )) + ) : ( + <TableRow> + <TableCell colSpan={inboxDocumentColumns.length} className="h-24 text-center"> + 업로드한 파일이 없습니다. + </TableCell> + </TableRow> + )} + </TableBody> + </Table> + </div> + + {/* 문서 상세 Dialog */} + <SwpInboxDocumentDetailDialog + open={dialogOpen} + onOpenChange={setDialogOpen} + document={selectedDocument} + projNo={projNo} + vendorCode={vendorCode} + userId={userId} + /> + </div> + ); +} + |
