summaryrefslogtreecommitdiff
path: root/lib/swp
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-18 15:03:15 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-18 15:03:15 +0900
commit54c654260c098864a8e113d46a242a057b58aae6 (patch)
treea871f0a41bb40ee9a992a51566d42e8404185baa /lib/swp
parent1a8bf9c1c98454bd0e961b84d14299155ad67e7f (diff)
(김준회) swp 수정: Activity Level에 따라 REV/Stage 표시하도록 변경, 필드 NOTE->NOTE1, NOTE2 구조 변경 등
Diffstat (limited to 'lib/swp')
-rw-r--r--lib/swp/api-client.ts3
-rw-r--r--lib/swp/table/swp-inbox-table-columns.tsx14
-rw-r--r--lib/swp/table/swp-inbox-table.tsx98
-rw-r--r--lib/swp/table/swp-note-dialog.tsx44
4 files changed, 117 insertions, 42 deletions
diff --git a/lib/swp/api-client.ts b/lib/swp/api-client.ts
index 17cfbb7c..e54a4c4d 100644
--- a/lib/swp/api-client.ts
+++ b/lib/swp/api-client.ts
@@ -149,7 +149,8 @@ export interface SwpFileApiResponse {
STAT: string | null;
STAT_NM: string | null;
IDX: string | null;
- NOTE: string | null;
+ NOTE1: string | null; // DC Note
+ NOTE2: string | null; // Eng Note
}
// ============================================================================
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>
+ );
+}
+