From 2ecdac866c19abea0b5389708fcdf5b3889c969a Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Wed, 29 Oct 2025 15:59:04 +0900 Subject: (김준회) SWP 파일 업로드 취소 기능 추가, 업로드 파일명 검증로직에서 파일명 비필수로 변경 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/swp/table/swp-upload-validation-dialog.tsx | 373 +++++++++++++++++++++++++ 1 file changed, 373 insertions(+) create mode 100644 lib/swp/table/swp-upload-validation-dialog.tsx (limited to 'lib/swp/table/swp-upload-validation-dialog.tsx') diff --git a/lib/swp/table/swp-upload-validation-dialog.tsx b/lib/swp/table/swp-upload-validation-dialog.tsx new file mode 100644 index 00000000..2d17e041 --- /dev/null +++ b/lib/swp/table/swp-upload-validation-dialog.tsx @@ -0,0 +1,373 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from "@/components/ui/dialog"; +import { Alert, AlertDescription } from "@/components/ui/alert"; +import { Badge } from "@/components/ui/badge"; +import { CheckCircle2, XCircle, AlertCircle, Upload } from "lucide-react"; +import { ScrollArea } from "@/components/ui/scroll-area"; + +interface FileValidationResult { + file: File; + valid: boolean; + parsed?: { + ownDocNo: string; + revNo: string; + stage: string; + fileName: string; + extension: string; + }; + error?: string; +} + +interface SwpUploadValidationDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + validationResults: FileValidationResult[]; + onConfirmUpload: (validFiles: File[]) => void; + isUploading: boolean; + availableDocNos?: string[]; // 업로드 가능한 문서번호 목록 + isVendorMode?: boolean; // 벤더 모드인지 여부 (문서번호 검증 필수) +} + +/** + * 파일명 검증 함수 (클라이언트 사이드) + * 형식: [DOC_NO]_[REV_NO]_[STAGE].[확장자] 또는 [DOC_NO]_[REV_NO]_[STAGE]_[자유-파일명].[확장자] + * 자유 파일명은 선택사항이며, 포함될 경우 언더스코어를 포함할 수 있음 + * @param fileName 검증할 파일명 + * @param availableDocNos 업로드 가능한 문서번호 목록 (선택) + * @param isVendorMode 벤더 모드인지 여부 (true인 경우 문서번호 검증 필수) + */ +export function validateFileName( + fileName: string, + availableDocNos?: string[], + isVendorMode?: boolean +): { + valid: boolean; + parsed?: { + ownDocNo: string; + revNo: string; + stage: string; + fileName: string; + extension: string; + }; + error?: string; +} { + try { + // 확장자 분리 + const lastDotIndex = fileName.lastIndexOf("."); + if (lastDotIndex === -1) { + return { + valid: false, + error: "파일 확장자가 없습니다", + }; + } + + const extension = fileName.substring(lastDotIndex + 1); + const nameWithoutExt = fileName.substring(0, lastDotIndex); + + // 언더스코어로 분리 + const parts = nameWithoutExt.split("_"); + + // 최소 3개 파트 필요: docNo, revNo, stage (fileName은 선택사항) + if (parts.length < 3) { + return { + valid: false, + error: `언더스코어(_)가 최소 2개 있어야 합니다 (현재: ${parts.length - 1}개). 형식: [DOC_NO]_[REV_NO]_[STAGE].[확장자]`, + }; + } + + // 앞에서부터 3개는 고정: docNo, revNo, stage + const ownDocNo = parts[0]; + const revNo = parts[1]; + const stage = parts[2]; + + // 나머지는 자유 파일명 (선택사항, 언더스코어 포함 가능) + const customFileName = parts.length > 3 ? parts.slice(3).join("_") : ""; + + // 필수 항목이 비어있지 않은지 확인 + if (!ownDocNo || ownDocNo.trim() === "") { + return { + valid: false, + error: "문서번호(DOC_NO)가 비어있습니다", + }; + } + + if (!revNo || revNo.trim() === "") { + return { + valid: false, + error: "리비전 번호(REV_NO)가 비어있습니다", + }; + } + + if (!stage || stage.trim() === "") { + return { + valid: false, + error: "스테이지(STAGE)가 비어있습니다", + }; + } + + // 문서번호 검증 (벤더 모드에서는 필수) + if (isVendorMode) { + const trimmedDocNo = ownDocNo.trim(); + + // 벤더 모드에서 문서 목록이 비어있으면 에러 + if (!availableDocNos || availableDocNos.length === 0) { + return { + valid: false, + error: "할당된 문서가 없거나 문서 목록 로드에 실패했습니다. 페이지를 새로고침하거나 관리자에게 문의하세요.", + }; + } + + // 문서번호가 목록에 없으면 에러 + if (!availableDocNos.includes(trimmedDocNo)) { + return { + valid: false, + error: `문서번호 '${trimmedDocNo}'는 업로드 권한이 없습니다. 할당된 문서번호를 확인해주세요.`, + }; + } + } + + return { + valid: true, + parsed: { + ownDocNo: ownDocNo.trim(), + revNo: revNo.trim(), + stage: stage.trim(), + fileName: customFileName.trim(), + extension, + }, + }; + } catch (error) { + return { + valid: false, + error: error instanceof Error ? error.message : "알 수 없는 오류", + }; + } +} + +/** + * 업로드 전 파일 검증 다이얼로그 + */ +export function SwpUploadValidationDialog({ + open, + onOpenChange, + validationResults, + onConfirmUpload, + isUploading, + availableDocNos = [], + isVendorMode = false, +}: SwpUploadValidationDialogProps) { + const validFiles = validationResults.filter((r) => r.valid); + const invalidFiles = validationResults.filter((r) => !r.valid); + + const handleUpload = () => { + if (validFiles.length > 0) { + onConfirmUpload(validFiles.map((r) => r.file)); + } + }; + + const handleCancel = () => { + if (!isUploading) { + onOpenChange(false); + } + }; + + return ( + + + + 파일 업로드 검증 + + 선택한 파일의 파일명 형식을 검증합니다 + + + +
+ {/* 요약 통계 */} +
+
+
전체 파일
+
{validationResults.length}
+
+
+
검증 성공
+
+ {validFiles.length} +
+
+
+
검증 실패
+
+ {invalidFiles.length} +
+
+
+ + {/* 경고 메시지 */} + {invalidFiles.length > 0 && ( + + + + {invalidFiles.length}개 파일의 파일명 형식이 올바르지 않습니다. + 검증에 성공한 {validFiles.length}개 파일만 업로드됩니다. + + + )} + + {validFiles.length === 0 && ( + + + + 업로드 가능한 파일이 없습니다. 파일명 형식을 확인해주세요. + + + )} + + {/* 파일 목록 */} + +
+ {/* 검증 성공 파일 */} + {validFiles.length > 0 && ( +
+

+ + 검증 성공 ({validFiles.length}개) +

+ {validFiles.map((result, index) => ( +
+
+
+
+ {result.file.name} +
+ {result.parsed && ( +
+ + 문서: {result.parsed.ownDocNo} + + + Rev: {result.parsed.revNo} + + + Stage: {result.parsed.stage} + + {result.parsed.fileName && ( + + 파일명: {result.parsed.fileName} + + )} + + 확장자: .{result.parsed.extension} + +
+ )} +
+ +
+
+ ))} +
+ )} + + {/* 검증 실패 파일 */} + {invalidFiles.length > 0 && ( +
+

+ + 검증 실패 ({invalidFiles.length}개) +

+ {invalidFiles.map((result, index) => ( +
+
+
+
+ {result.file.name} +
+ {result.error && ( +
+ ✗ {result.error} +
+ )} +
+ +
+
+ ))} +
+ )} +
+
+ + {/* 형식 안내 */} +
+
+ 올바른 파일명 형식 +
+ + [DOC_NO]_[REV_NO]_[STAGE].[확장자] + +
+ 예: VD-DOC-001_01_IFA.pdf +
+
+ ※ 선택사항: [DOC_NO]_[REV_NO]_[STAGE]_[파일명].[확장자] (파일명 추가 가능) +
+
+ ※ 파일명에는 언더스코어(_)가 포함될 수 있습니다. +
+ {isVendorMode && ( +
+ {availableDocNos.length > 0 ? ( + <>ℹ️ 업로드 가능한 문서: {availableDocNos.length}개 + ) : ( + <>⚠️ 할당된 문서가 없습니다 + )} +
+ )} +
+
+ + + + + +
+
+ ); +} + -- cgit v1.2.3