diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-18 15:03:15 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-18 15:03:15 +0900 |
| commit | 54c654260c098864a8e113d46a242a057b58aae6 (patch) | |
| tree | a871f0a41bb40ee9a992a51566d42e8404185baa /lib/swp/table | |
| parent | 1a8bf9c1c98454bd0e961b84d14299155ad67e7f (diff) | |
(김준회) swp 수정: Activity Level에 따라 REV/Stage 표시하도록 변경, 필드 NOTE->NOTE1, NOTE2 구조 변경 등
Diffstat (limited to 'lib/swp/table')
| -rw-r--r-- | lib/swp/table/swp-inbox-table-columns.tsx | 14 | ||||
| -rw-r--r-- | lib/swp/table/swp-inbox-table.tsx | 98 | ||||
| -rw-r--r-- | lib/swp/table/swp-note-dialog.tsx | 44 |
3 files changed, 115 insertions, 41 deletions
diff --git a/lib/swp/table/swp-inbox-table-columns.tsx b/lib/swp/table/swp-inbox-table-columns.tsx index bd740ca4..dbf25c5d 100644 --- a/lib/swp/table/swp-inbox-table-columns.tsx +++ b/lib/swp/table/swp-inbox-table-columns.tsx @@ -106,6 +106,20 @@ export const swpInboxDocumentColumns: ColumnDef<SwpFileApiResponse>[] = [ size: 100, }, { + accessorKey: "NOTE1", + header: "DC Note", + cell: ({ row }) => { + const note1 = row.original.NOTE1; + if (!note1) return <span className="text-muted-foreground">-</span>; + return ( + <div className="max-w-md truncate" title={note1}> + {note1} + </div> + ); + }, + size: 200, + }, + { accessorKey: "CRTE_DTM", header: "생성일시", cell: ({ row }) => { diff --git a/lib/swp/table/swp-inbox-table.tsx b/lib/swp/table/swp-inbox-table.tsx index a88ff656..633191a1 100644 --- a/lib/swp/table/swp-inbox-table.tsx +++ b/lib/swp/table/swp-inbox-table.tsx @@ -19,6 +19,7 @@ import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { formatSwpDate } from "@/lib/swp/utils"; import { SwpInboxHistoryDialog } from "./swp-inbox-history-dialog"; +import { SwpNoteDialog } from "./swp-note-dialog"; // 업로드 필요 문서 타입 (DB stageDocuments에서 조회) interface RequiredDocument { @@ -45,7 +46,8 @@ interface TableRowData { statusNm: string | null; actvNo: string | null; crter: string | null; // CRTER (그대로 표시) - note: string | null; // Activity의 note 또는 buyerSystemComment + note1: string | null; // DC Note (Activity의 NOTE1 또는 buyerSystemComment) + pkgNo: string | null; // PKG_NO file: SwpFileApiResponse | null; // 업로드 필요 문서는 null uploadDate: string | null; // 각 행이 속한 그룹의 정보 (계층적 rowSpan 처리) @@ -79,6 +81,8 @@ export function SwpInboxTable({ const [selectedFiles, setSelectedFiles] = useState<Set<string>>(new Set()); // 선택된 파일 (fileKey) const [historyDialogOpen, setHistoryDialogOpen] = useState(false); const [selectedDocNo, setSelectedDocNo] = useState<string | null>(null); + const [noteDialogOpen, setNoteDialogOpen] = useState(false); + const [noteDialogContent, setNoteDialogContent] = useState<{ title: string; content: string | null }>({ title: "", content: null }); // Status 집계 (API 응답 + 업로드 필요 문서) const statusCounts = useMemo(() => { @@ -175,39 +179,23 @@ export function SwpInboxTable({ // Document No별로 처리 docGroups.forEach((docFiles, docNo) => { - // 3단계: 최신 RevNo 찾기 (CRTE_DTM 기준) - const sortedByDate = [...docFiles].sort((a, b) => - (b.CRTE_DTM || "").localeCompare(a.CRTE_DTM || "") - ); - const latestRevNo = sortedByDate[0]?.REV_NO || ""; - const latestStage = sortedByDate[0]?.STAGE || null; - const latestStatus = sortedByDate[0]?.STAT || null; - const latestStatusNm = sortedByDate[0]?.STAT_NM || null; - - // 4단계: 최신 Rev의 파일들만 필터링 - const latestRevFiles = docFiles.filter( - (file) => file.REV_NO === latestRevNo - ); - - const totalDocFileCount = latestRevFiles.length; + const totalDocFileCount = docFiles.length; let isFirstInDoc = true; - // 5단계: Activity 기준으로 그룹화 + // 3단계: ACTV_SEQ 기준으로 그룹화 (최신 Rev 필터링 제거) const activityGroups = new Map<string, SwpFileApiResponse[]>(); - latestRevFiles.forEach((file) => { - const actvNo = file.ACTV_NO || "NO_ACTIVITY"; - if (!activityGroups.has(actvNo)) { - activityGroups.set(actvNo, []); + docFiles.forEach((file) => { + const actvSeq = file.ACTV_SEQ || "NO_ACTIVITY"; + if (!activityGroups.has(actvSeq)) { + activityGroups.set(actvSeq, []); } - activityGroups.get(actvNo)!.push(file); + activityGroups.get(actvSeq)!.push(file); }); - let isFirstInRev = true; - // Activity별로 처리 - activityGroups.forEach((activityFiles, actvNo) => { - // 6단계: Upload Date 기준 DESC 정렬 + activityGroups.forEach((activityFiles) => { + // 4단계: Upload Date 기준 DESC 정렬 const sortedFiles = activityFiles.sort((a, b) => (b.CRTE_DTM || "").localeCompare(a.CRTE_DTM || "") ); @@ -218,26 +206,27 @@ export function SwpInboxTable({ const firstActivityFile = sortedFiles[0]; if (!firstActivityFile) return; - // 7단계: 각 파일을 테이블 행으로 변환 + // 5단계: 각 파일을 테이블 행으로 변환 sortedFiles.forEach((file, idx) => { rows.push({ uploadId, docNo, - revNo: latestRevNo, - stage: latestStage, - status: latestStatus, - statusNm: latestStatusNm, - actvNo: actvNo === "NO_ACTIVITY" ? null : actvNo, + revNo: file.REV_NO || null, + stage: file.STAGE || null, + status: file.STAT || null, + statusNm: file.STAT_NM || null, + actvNo: file.ACTV_NO || null, crter: firstActivityFile.CRTER, // Activity 첫 파일의 CRTER - note: firstActivityFile.NOTE || null, // Activity 첫 파일의 note + note1: firstActivityFile.NOTE1 || null, // Activity 첫 파일의 DC Note + pkgNo: file.PKG_NO || null, file, uploadDate: file.CRTE_DTM, isFirstInUpload, fileCountInUpload: totalUploadFileCount, isFirstInDoc, fileCountInDoc: totalDocFileCount, - isFirstInRev, - fileCountInRev: totalDocFileCount, // Rev = Doc의 최신 Rev이므로 파일 수 동일 + isFirstInRev: idx === 0, + fileCountInRev: totalActivityFileCount, // Activity 내 파일 수 isFirstInActivity: idx === 0, fileCountInActivity: totalActivityFileCount, isRequiredDoc: false, @@ -247,7 +236,6 @@ export function SwpInboxTable({ if (idx === 0) { isFirstInUpload = false; isFirstInDoc = false; - isFirstInRev = false; } }); }); @@ -266,7 +254,8 @@ export function SwpInboxTable({ statusNm: "Upload Required", actvNo: null, crter: null, - note: doc.buyerSystemComment, + note1: doc.buyerSystemComment, // DB의 comment를 DC Note에 매핑 + pkgNo: null, file: null, uploadDate: null, isFirstInUpload: true, @@ -488,7 +477,8 @@ export function SwpInboxTable({ <TableHead className="w-[120px]">Status</TableHead> <TableHead className="w-[100px]">Activity</TableHead> <TableHead className="w-[120px]">Upload ID (User)</TableHead> - <TableHead className="w-[150px]">Note</TableHead> + <TableHead className="w-[150px]">DC Note</TableHead> + <TableHead className="w-[120px]">PKG NO</TableHead> <TableHead className="w-[400px]">Attachment File</TableHead> <TableHead className="w-[180px]">Upload Date</TableHead> </TableRow> @@ -564,17 +554,35 @@ export function SwpInboxTable({ </TableCell> ) : null} - {/* Note - Activity의 첫 파일에만 표시 (개행문자 처리) */} + {/* DC Note - Activity의 첫 파일에만 표시 */} {row.isFirstInActivity ? ( <TableCell rowSpan={row.fileCountInActivity} - className="text-xs max-w-[150px] align-top whitespace-pre-wrap" + className="text-xs max-w-[150px] align-top" style={{ verticalAlign: "top" }} + onClick={(e) => { + if (row.note1) { + e.stopPropagation(); + setNoteDialogContent({ title: "DC Note", content: row.note1 }); + setNoteDialogOpen(true); + } + }} > - {row.note || <span className="text-muted-foreground">-</span>} + {row.note1 ? ( + <div className="truncate cursor-pointer hover:text-primary underline" title="클릭하여 전체 내용 보기"> + {row.note1} + </div> + ) : ( + <span className="text-muted-foreground">-</span> + )} </TableCell> ) : null} + {/* PKG NO - 각 파일마다 표시 */} + <TableCell className="font-mono text-sm"> + {row.pkgNo || <span className="text-muted-foreground">-</span>} + </TableCell> + {/* Attachment File - 각 파일마다 표시 (줄바꿈 허용) */} <TableCell className="max-w-[400px]"> {row.file ? ( @@ -620,6 +628,14 @@ export function SwpInboxTable({ projNo={projNo} userId={userId} /> + + {/* Note 전체 내용 Dialog */} + <SwpNoteDialog + open={noteDialogOpen} + onOpenChange={setNoteDialogOpen} + title={noteDialogContent.title} + content={noteDialogContent.content} + /> </div> ); } diff --git a/lib/swp/table/swp-note-dialog.tsx b/lib/swp/table/swp-note-dialog.tsx new file mode 100644 index 00000000..5f86de24 --- /dev/null +++ b/lib/swp/table/swp-note-dialog.tsx @@ -0,0 +1,44 @@ +"use client"; + +import React from "react"; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { ScrollArea } from "@/components/ui/scroll-area"; + +interface SwpNoteDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + title: string; + content: string | null; +} + +/** + * SWP Note 전체 내용 표시 Dialog + * DC Note (NOTE1) 또는 Eng Note (NOTE2)의 전체 내용을 표시합니다. + */ +export function SwpNoteDialog({ + open, + onOpenChange, + title, + content, +}: SwpNoteDialogProps) { + return ( + <Dialog open={open} onOpenChange={onOpenChange}> + <DialogContent className="max-w-2xl max-h-[80vh]"> + <DialogHeader> + <DialogTitle>{title}</DialogTitle> + </DialogHeader> + <ScrollArea className="max-h-[60vh] pr-4"> + <div className="whitespace-pre-wrap text-sm"> + {content || <span className="text-muted-foreground">내용이 없습니다.</span>} + </div> + </ScrollArea> + </DialogContent> + </Dialog> + ); +} + |
