"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"; 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; // 벤더 모드인지 여부 (문서번호 검증 필수) } /** * 파일명 검증 함수 (클라이언트 사이드) * 형식: [OWN_DOC_NO]_[REV_NO]_[STAGE].[확장자] 또는 [OWN_DOC_NO]_[REV_NO]_[STAGE]_[자유-파일명].[확장자] * 자유 파일명은 선택사항이며, 포함될 경우 언더스코어를 포함할 수 있음 * @param fileName 검증할 파일명 * @param availableDocNos 업로드 가능한 문서번호 목록 (선택) * @param isVendorMode 벤더 모드인지 여부 (true인 경우 문서번호 검증 필수) * @param docNoToDocClsMap 문서번호 → DOC_CLS (Document Class) 매핑 (Stage 검증용) * @param documentClassStages Document Class → 허용 Stage 목록 매핑 */ export function validateFileName( fileName: string, availableDocNos?: string[], isVendorMode?: boolean, docNoToDocClsMap?: Record, documentClassStages?: Record ): { 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개 파트 필요: ownDocNo, revNo, stage (fileName은 선택사항) if (parts.length < 3) { return { valid: false, error: `언더스코어(_)가 최소 2개 있어야 합니다 (현재: ${parts.length - 1}개). 형식: [OWN_DOC_NO]_[REV_NO]_[STAGE].[확장자]`, }; } // 앞에서부터 3개는 고정: ownDocNo, 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: "문서번호(OWN_DOC_NO)가 비어있습니다", }; } if (!revNo || revNo.trim() === "") { return { valid: false, error: "리비전 번호(REV_NO)가 비어있습니다", }; } if (!stage || stage.trim() === "") { return { valid: false, error: "스테이지(STAGE)가 비어있습니다", }; } // trim된 값 미리 준비 (중복 제거) const trimmedDocNo = ownDocNo.trim(); const trimmedStage = stage.trim(); // 문서번호 검증 (벤더 모드에서는 필수) if (isVendorMode) { // 벤더 모드에서 문서 목록이 비어있으면 에러 if (!availableDocNos || availableDocNos.length === 0) { return { valid: false, error: "할당된 문서가 없거나 문서 목록 로드에 실패했습니다. 페이지를 새로고침하거나 관리자에게 문의하세요.", }; } // 문서번호가 목록에 없으면 에러 if (!availableDocNos.includes(trimmedDocNo)) { return { valid: false, error: `문서번호 '${trimmedDocNo}'는 업로드 권한이 없습니다. 할당된 문서번호를 확인해주세요.`, }; } } // Stage 검증 (Document Class별 허용 Stage 확인) // EVCP DB에서 vendorDocNumber로 Document Class를 조회하고, // 해당 Document Class의 허용 Stage 목록과 비교 if (docNoToDocClsMap && documentClassStages) { const docCls = docNoToDocClsMap[trimmedDocNo]; console.log(`[validateFileName] 문서 '${trimmedDocNo}' → Document Class: '${docCls || "null"}'`); if (!docCls) { // 문서가 EVCP DB에 등록되지 않음 return { valid: false, error: `문서번호 '${trimmedDocNo}'는 문서 리스트에 등록되지 않았습니다. 먼저 문서 리스트를 제출해주세요.`, }; } const allowedStages = documentClassStages[docCls]; console.log(`[validateFileName] Document Class '${docCls}' → 허용 Stage:`, allowedStages); if (!allowedStages || allowedStages.length === 0) { // Document Class에 Stage가 설정되지 않음 return { valid: false, error: `문서 '${trimmedDocNo}'의 Document Class '${docCls}'에 Stage가 설정되지 않았습니다. 관리자에게 문의하세요.`, }; } console.log(`[validateFileName] Stage 검증: '${trimmedStage}' in [${allowedStages.join(", ")}]`); if (!allowedStages.includes(trimmedStage)) { return { valid: false, error: `문서 '${trimmedDocNo}'의 Document Class '${docCls}'에서 Stage '${trimmedStage}'는 허용되지 않습니다. 허용된 Stage: ${allowedStages.join(", ")}`, }; } console.log(`[validateFileName] Stage 검증 통과: '${trimmedStage}'`); } else { // 검증 정보가 로드되지 않음 console.log(`[validateFileName] 검증 정보가 없음 → 업로드 차단`); return { valid: false, error: "문서 정보를 가져올 수 없습니다. 페이지를 새로고침하거나 프로젝트를 다시 선택해주세요.", }; } return { valid: true, parsed: { ownDocNo: trimmedDocNo, revNo: revNo.trim(), stage: trimmedStage, 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}
)}
))}
)}
{/* 형식 안내 */}
📋 올바른 파일명 형식
[OWN_DOC_NO]_[REV_NO]_[STAGE].[확장자]
예: VD-DOC-001_01_IFA.pdf
※ 선택사항: [OWN_DOC_NO]_[REV_NO]_[STAGE]_[파일명].[확장자] (파일명 추가 가능)
※ 파일명에는 언더스코어(_)가 포함될 수 있습니다.
{isVendorMode && ( <>
{availableDocNos.length > 0 ? ( <>ℹ️ 업로드 가능한 문서: {availableDocNos.length}개 ) : ( <>⚠️ 할당된 문서가 없습니다 )}
⚠️ 각 문서의 Document Class에 정의된 Stage만 사용할 수 있습니다.
)}
); }