summaryrefslogtreecommitdiff
path: root/lib/swp/table/swp-inbox-table.tsx
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-18 12:15:43 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-18 12:15:43 +0900
commit1a8bf9c1c98454bd0e961b84d14299155ad67e7f (patch)
treea82663c3441811c1b3b90ae5136d3198f83a7697 /lib/swp/table/swp-inbox-table.tsx
parent2399d5e285bb76c68a2c3368b05ac40478886c2b (diff)
(김준회) swp: 그룹핑 로직 수정요청사항 반영, 대용량 파일업로드 formidable 사용한 스트리밍 방식으로 오류 수정
Diffstat (limited to 'lib/swp/table/swp-inbox-table.tsx')
-rw-r--r--lib/swp/table/swp-inbox-table.tsx174
1 files changed, 115 insertions, 59 deletions
diff --git a/lib/swp/table/swp-inbox-table.tsx b/lib/swp/table/swp-inbox-table.tsx
index a2fedeed..a88ff656 100644
--- a/lib/swp/table/swp-inbox-table.tsx
+++ b/lib/swp/table/swp-inbox-table.tsx
@@ -45,12 +45,18 @@ interface TableRowData {
statusNm: string | null;
actvNo: string | null;
crter: string | null; // CRTER (그대로 표시)
- note: string | null; // 첫 번째 파일의 note 또는 buyerSystemComment
+ note: string | null; // Activity의 note 또는 buyerSystemComment
file: SwpFileApiResponse | null; // 업로드 필요 문서는 null
uploadDate: string | null;
- // 각 행이 속한 그룹의 정보
- isFirstFileInRev: boolean;
+ // 각 행이 속한 그룹의 정보 (계층적 rowSpan 처리)
+ isFirstInUpload: boolean;
+ fileCountInUpload: number;
+ isFirstInDoc: boolean;
+ fileCountInDoc: number;
+ isFirstInRev: boolean;
fileCountInRev: number;
+ isFirstInActivity: boolean;
+ fileCountInActivity: number;
// 업로드 필요 문서 여부
isRequiredDoc: boolean;
}
@@ -137,7 +143,7 @@ export function SwpInboxTable({
filteredFiles = files.filter((file) => file.STAT === selectedStatus);
}
- // BOX_SEQ 기준으로 그룹화
+ // 1단계: BOX_SEQ (Upload ID) 기준으로 그룹화
const uploadGroups = new Map<string, SwpFileApiResponse[]>();
if (!selectedStatus || selectedStatus !== "UPLOAD_REQUIRED") {
@@ -150,8 +156,9 @@ export function SwpInboxTable({
});
}
+ // Upload ID별로 처리
uploadGroups.forEach((uploadFiles, uploadId) => {
- // 2. Document No 기준으로 그룹화
+ // 2단계: Document No 기준으로 그룹화
const docGroups = new Map<string, SwpFileApiResponse[]>();
uploadFiles.forEach((file) => {
@@ -162,44 +169,86 @@ export function SwpInboxTable({
docGroups.get(docNo)!.push(file);
});
+ // 전체 Upload ID의 파일 수 계산
+ const totalUploadFileCount = uploadFiles.length;
+ let isFirstInUpload = true;
+
+ // Document No별로 처리
docGroups.forEach((docFiles, docNo) => {
- // 3. 최신 RevNo 찾기 (CRTE_DTM 기준)
+ // 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의 파일들만 필터링
+ // 4단계: 최신 Rev의 파일들만 필터링
const latestRevFiles = docFiles.filter(
(file) => file.REV_NO === latestRevNo
);
- // 5. Upload Date 기준 DESC 정렬
- const sortedFiles = latestRevFiles.sort((a, b) =>
- (b.CRTE_DTM || "").localeCompare(a.CRTE_DTM || "")
- );
+ const totalDocFileCount = latestRevFiles.length;
+ let isFirstInDoc = true;
+
+ // 5단계: Activity 기준으로 그룹화
+ const activityGroups = new Map<string, SwpFileApiResponse[]>();
+
+ latestRevFiles.forEach((file) => {
+ const actvNo = file.ACTV_NO || "NO_ACTIVITY";
+ if (!activityGroups.has(actvNo)) {
+ activityGroups.set(actvNo, []);
+ }
+ activityGroups.get(actvNo)!.push(file);
+ });
- // 6. 최신 파일의 정보로 Rev 메타데이터 설정 (첫 번째 파일의 crter와 note 사용)
- const latestFile = sortedFiles[0];
- if (!latestFile) return;
-
- // 7. 각 파일을 테이블 행으로 변환 (crter와 note는 첫 번째 파일 것으로 통일)
- sortedFiles.forEach((file, idx) => {
- rows.push({
- uploadId,
- docNo,
- revNo: latestRevNo,
- stage: latestFile.STAGE,
- status: latestFile.STAT,
- statusNm: latestFile.STAT_NM,
- actvNo: latestFile.ACTV_NO,
- crter: latestFile.CRTER, // CRTER 그대로
- note: latestFile.NOTE || null, // 첫 번째 파일의 note
- file,
- uploadDate: file.CRTE_DTM,
- isFirstFileInRev: idx === 0,
- fileCountInRev: sortedFiles.length,
- isRequiredDoc: false,
+ let isFirstInRev = true;
+
+ // Activity별로 처리
+ activityGroups.forEach((activityFiles, actvNo) => {
+ // 6단계: Upload Date 기준 DESC 정렬
+ const sortedFiles = activityFiles.sort((a, b) =>
+ (b.CRTE_DTM || "").localeCompare(a.CRTE_DTM || "")
+ );
+
+ const totalActivityFileCount = sortedFiles.length;
+
+ // Activity의 첫 번째 파일에서 메타데이터 가져오기
+ const firstActivityFile = sortedFiles[0];
+ if (!firstActivityFile) return;
+
+ // 7단계: 각 파일을 테이블 행으로 변환
+ sortedFiles.forEach((file, idx) => {
+ rows.push({
+ uploadId,
+ docNo,
+ revNo: latestRevNo,
+ stage: latestStage,
+ status: latestStatus,
+ statusNm: latestStatusNm,
+ actvNo: actvNo === "NO_ACTIVITY" ? null : actvNo,
+ crter: firstActivityFile.CRTER, // Activity 첫 파일의 CRTER
+ note: firstActivityFile.NOTE || null, // Activity 첫 파일의 note
+ file,
+ uploadDate: file.CRTE_DTM,
+ isFirstInUpload,
+ fileCountInUpload: totalUploadFileCount,
+ isFirstInDoc,
+ fileCountInDoc: totalDocFileCount,
+ isFirstInRev,
+ fileCountInRev: totalDocFileCount, // Rev = Doc의 최신 Rev이므로 파일 수 동일
+ isFirstInActivity: idx === 0,
+ fileCountInActivity: totalActivityFileCount,
+ isRequiredDoc: false,
+ });
+
+ // 첫 번째 플래그들 업데이트
+ if (idx === 0) {
+ isFirstInUpload = false;
+ isFirstInDoc = false;
+ isFirstInRev = false;
+ }
});
});
});
@@ -220,8 +269,14 @@ export function SwpInboxTable({
note: doc.buyerSystemComment,
file: null,
uploadDate: null,
- isFirstFileInRev: true,
+ isFirstInUpload: true,
+ fileCountInUpload: 1,
+ isFirstInDoc: true,
+ fileCountInDoc: 1,
+ isFirstInRev: true,
fileCountInRev: 1,
+ isFirstInActivity: true,
+ fileCountInActivity: 1,
isRequiredDoc: true,
});
});
@@ -446,7 +501,7 @@ export function SwpInboxTable({
return (
<TableRow
- key={`${row.uploadId}_${row.docNo}_${row.revNo}_${idx}`}
+ key={`${row.uploadId}_${row.docNo}_${row.revNo}_${row.actvNo}_${idx}`}
className="cursor-pointer hover:bg-muted/50"
onClick={() => handleRowClick(row.docNo)}
>
@@ -460,60 +515,61 @@ export function SwpInboxTable({
) : null}
</TableCell>
- {/* Upload ID - 같은 Rev의 첫 파일에만 표시 */}
- {row.isFirstFileInRev ? (
- <TableCell rowSpan={row.fileCountInRev} className="font-mono text-sm align-top">
+ {/* Upload ID - Upload의 첫 파일에만 표시 */}
+ {row.isFirstInUpload ? (
+ <TableCell rowSpan={row.fileCountInUpload} className="font-mono text-sm align-top" style={{ verticalAlign: "top" }}>
{row.uploadId || <span className="text-muted-foreground">-</span>}
</TableCell>
) : null}
- {/* Document No - 같은 Rev의 첫 파일에만 표시 */}
- {row.isFirstFileInRev ? (
- <TableCell rowSpan={row.fileCountInRev} className="font-mono text-xs align-top">
+ {/* Document No - Document의 첫 파일에만 표시 */}
+ {row.isFirstInDoc ? (
+ <TableCell rowSpan={row.fileCountInDoc} className="font-mono text-xs align-top" style={{ verticalAlign: "top" }}>
{row.docNo}
</TableCell>
) : null}
- {/* Rev No - 같은 Rev의 첫 파일에만 표시 */}
- {row.isFirstFileInRev ? (
- <TableCell rowSpan={row.fileCountInRev} className="align-top">
+ {/* Rev No - Rev의 첫 파일에만 표시 */}
+ {row.isFirstInRev ? (
+ <TableCell rowSpan={row.fileCountInRev} className="align-top" style={{ verticalAlign: "top" }}>
{row.revNo || <span className="text-muted-foreground">-</span>}
</TableCell>
) : null}
- {/* Stage - 같은 Rev의 첫 파일에만 표시 (텍스트로만 표시) */}
- {row.isFirstFileInRev ? (
- <TableCell rowSpan={row.fileCountInRev} className="align-top text-sm">
+ {/* Stage - Rev의 첫 파일에만 표시 */}
+ {row.isFirstInRev ? (
+ <TableCell rowSpan={row.fileCountInRev} className="align-top text-sm" style={{ verticalAlign: "top" }}>
{row.stage || <span className="text-muted-foreground">-</span>}
</TableCell>
) : null}
- {/* Status - 같은 Rev의 첫 파일에만 표시 */}
- {row.isFirstFileInRev ? (
- <TableCell rowSpan={row.fileCountInRev} className="align-top">
+ {/* Status - Rev의 첫 파일에만 표시 */}
+ {row.isFirstInRev ? (
+ <TableCell rowSpan={row.fileCountInRev} className="align-top" style={{ verticalAlign: "top" }}>
{getStatusBadge(row.status, row.statusNm)}
</TableCell>
) : null}
- {/* Activity - 같은 Rev의 첫 파일에만 표시 */}
- {row.isFirstFileInRev ? (
- <TableCell rowSpan={row.fileCountInRev} className="font-mono text-xs align-top">
+ {/* Activity - Activity의 첫 파일에만 표시 */}
+ {row.isFirstInActivity ? (
+ <TableCell rowSpan={row.fileCountInActivity} className="font-mono text-xs align-top" style={{ verticalAlign: "top" }}>
{row.actvNo || <span className="text-muted-foreground">-</span>}
</TableCell>
) : null}
- {/* CRTER - 같은 Rev의 첫 파일에만 표시 */}
- {row.isFirstFileInRev ? (
- <TableCell rowSpan={row.fileCountInRev} className="text-sm font-mono align-top">
+ {/* CRTER (Upload ID User) - Activity의 첫 파일에만 표시 */}
+ {row.isFirstInActivity ? (
+ <TableCell rowSpan={row.fileCountInActivity} className="text-sm font-mono align-top" style={{ verticalAlign: "top" }}>
{row.crter || <span className="text-muted-foreground">-</span>}
</TableCell>
) : null}
- {/* Note - 같은 Rev의 첫 파일에만 표시 (개행문자 처리) */}
- {row.isFirstFileInRev ? (
+ {/* Note - Activity의 첫 파일에만 표시 (개행문자 처리) */}
+ {row.isFirstInActivity ? (
<TableCell
- rowSpan={row.fileCountInRev}
+ rowSpan={row.fileCountInActivity}
className="text-xs max-w-[150px] align-top whitespace-pre-wrap"
+ style={{ verticalAlign: "top" }}
>
{row.note || <span className="text-muted-foreground">-</span>}
</TableCell>
@@ -522,7 +578,7 @@ export function SwpInboxTable({
{/* Attachment File - 각 파일마다 표시 (줄바꿈 허용) */}
<TableCell className="max-w-[400px]">
{row.file ? (
- <div className="flex items-center gap-2">
+ <div className="flex items-center justify-between gap-2">
<span className="text-sm font-mono break-words" style={{ wordBreak: "break-all" }}>
{row.file.FILE_NM}
</span>