diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-27 17:48:28 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-27 17:48:28 +0900 |
| commit | 95984e67b8d57fbe1431fcfedf3bb682f28416b3 (patch) | |
| tree | 79953157e70b30c3c65ae52a01adb65fd4344bee /lib | |
| parent | 647e2e487238aed36ff9a880648e5c3e8725160f (diff) | |
(김준회) swp 영문 처리
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/swp/table/swp-document-detail-dialog.tsx | 56 | ||||
| -rw-r--r-- | lib/swp/table/swp-help-dialog.tsx | 78 | ||||
| -rw-r--r-- | lib/swp/table/swp-inbox-document-detail-dialog.tsx | 80 | ||||
| -rw-r--r-- | lib/swp/table/swp-inbox-history-dialog.tsx | 40 | ||||
| -rw-r--r-- | lib/swp/table/swp-inbox-table-columns.tsx | 14 | ||||
| -rw-r--r-- | lib/swp/table/swp-inbox-table.tsx | 30 | ||||
| -rw-r--r-- | lib/swp/table/swp-note-dialog.tsx | 6 | ||||
| -rw-r--r-- | lib/swp/table/swp-revision-list-dialog.tsx | 28 | ||||
| -rw-r--r-- | lib/swp/table/swp-table-columns.tsx | 30 | ||||
| -rw-r--r-- | lib/swp/table/swp-table-toolbar.tsx | 90 | ||||
| -rw-r--r-- | lib/swp/table/swp-table.tsx | 8 | ||||
| -rw-r--r-- | lib/swp/table/swp-upload-result-dialog.tsx | 14 | ||||
| -rw-r--r-- | lib/swp/table/swp-upload-validation-dialog.tsx | 72 | ||||
| -rw-r--r-- | lib/swp/table/swp-uploaded-files-dialog.tsx | 62 |
14 files changed, 304 insertions, 304 deletions
diff --git a/lib/swp/table/swp-document-detail-dialog.tsx b/lib/swp/table/swp-document-detail-dialog.tsx index 77ef77f7..ef5cf6f9 100644 --- a/lib/swp/table/swp-document-detail-dialog.tsx +++ b/lib/swp/table/swp-document-detail-dialog.tsx @@ -122,8 +122,8 @@ export function SwpDocumentDetailDialog({ setActivities(flatActivities); } catch (error) { - console.error("문서 상세 조회 실패:", error); - toast.error("문서 리비전 트리를 불러오는데 실패했습니다"); + console.error("Failed to retrieve document details:", error); + toast.error("Failed to load document revision tree"); } finally { setIsLoading(false); } @@ -157,8 +157,8 @@ export function SwpDocumentDetailDialog({ setActivityFiles(activitySpecificFiles); } catch (error) { - console.error("파일 목록 조회 실패:", error); - toast.error("파일 목록을 불러오는데 실패했습니다"); + console.error("Failed to retrieve file list:", error); + toast.error("Failed to load file list"); } finally { setIsLoadingFiles(false); } @@ -166,11 +166,11 @@ export function SwpDocumentDetailDialog({ const handleDownloadFile = async (fileName: string, ownDocNo: string) => { try { - toast.info("파일 다운로드 중..."); + toast.info("Downloading file..."); const result = await downloadVendorFile(projNo, ownDocNo, fileName); if (!result.success || !result.data) { - toast.error(result.error || "파일 다운로드 실패"); + toast.error(result.error || "File download failed"); return; } @@ -185,10 +185,10 @@ export function SwpDocumentDetailDialog({ window.document.body.removeChild(link); URL.revokeObjectURL(url); - toast.success(`파일 다운로드 완료: ${fileName}`); + toast.success(`File download complete: ${fileName}`); } catch (error) { - console.error("파일 다운로드 실패:", error); - toast.error("파일 다운로드에 실패했습니다"); + console.error("File download failed:", error); + toast.error("Failed to download file"); } }; @@ -221,7 +221,7 @@ export function SwpDocumentDetailDialog({ <Dialog open={open} onOpenChange={onOpenChange}> <DialogContent className="max-w-[95vw] h-[90vh] overflow-hidden flex flex-col"> <DialogHeader> - <DialogTitle className="text-base">문서 리비전 히스토리</DialogTitle> + <DialogTitle className="text-base">Document Revision History</DialogTitle> {document && ( <DialogDescription className="text-xs"> {document.DOC_NO} - {document.DOC_TITLE} @@ -234,30 +234,30 @@ export function SwpDocumentDetailDialog({ {/* 문서 정보 */} <div className="flex items-center gap-4 px-3 py-2 bg-muted/30 rounded text-xs"> <div className="flex items-center gap-1"> - <span className="font-semibold">프로젝트:</span> + <span className="font-semibold">Project:</span> <span>{document.PROJ_NO}</span> {document.PROJ_NM && ( <span className="text-muted-foreground">({document.PROJ_NM})</span> )} </div> <div className="flex items-center gap-1"> - <span className="font-semibold">패키지:</span> + <span className="font-semibold">Package:</span> <span>{document.PKG_NO || "-"}</span> </div> <div className="flex items-center gap-1"> - <span className="font-semibold">업체:</span> + <span className="font-semibold">Vendor:</span> <span>{document.CPY_NM || "-"}</span> {document.VNDR_CD && ( <span className="text-muted-foreground">({document.VNDR_CD})</span> )} </div> <div className="flex items-center gap-1"> - <span className="font-semibold">최신 리비전:</span> + <span className="font-semibold">Latest Revision:</span> <span>{document.LTST_REV_NO || "-"}</span> </div> <div className="flex items-center gap-1"> - <span className="font-semibold">총 Activity:</span> - <span>{activities.length}개</span> + <span className="font-semibold">Total Activity:</span> + <span>{activities.length}</span> </div> </div> @@ -265,7 +265,7 @@ export function SwpDocumentDetailDialog({ {isLoading ? ( <div className="flex items-center justify-center p-8"> <Loader2 className="h-5 w-5 animate-spin" /> - <span className="ml-2 text-sm">리비전 트리 로딩 중...</span> + <span className="ml-2 text-sm">Loading revision tree...</span> </div> ) : activities.length > 0 ? ( <> @@ -357,14 +357,14 @@ export function SwpDocumentDetailDialog({ {/* 파일 목록 (아래) */} <div className="border rounded-lg overflow-hidden h-[30vh] flex flex-col"> <div className="px-3 py-1.5 bg-muted/50 border-b flex-shrink-0"> - <h3 className="font-semibold text-xs">파일 목록</h3> + <h3 className="font-semibold text-xs">File List</h3> {selectedActivity ? ( <p className="text-[10px] text-muted-foreground mt-0.5"> Activity: {selectedActivity.actvNo} / Rev {selectedActivity.revNo} ({selectedActivity.stage}) / {selectedActivity.inOut} </p> ) : ( <p className="text-[10px] text-muted-foreground mt-0.5"> - Activity를 선택하면 파일 목록이 표시됩니다 + Select an Activity to view the file list </p> )} </div> @@ -373,16 +373,16 @@ export function SwpDocumentDetailDialog({ isLoadingFiles ? ( <div className="flex items-center justify-center h-full"> <Loader2 className="h-4 w-4 animate-spin" /> - <span className="ml-2 text-xs">파일 로딩 중...</span> + <span className="ml-2 text-xs">Loading files...</span> </div> ) : activityFiles.length > 0 ? ( <Table> <TableHeader className="sticky top-0 bg-background"> <TableRow> - <TableHead className="min-w-[200px] text-xs h-8">파일명</TableHead> - <TableHead className="w-[90px] text-xs h-8">크기</TableHead> - <TableHead className="w-[100px] text-xs h-8">날짜</TableHead> - <TableHead className="w-[90px] text-xs h-8">다운로드</TableHead> + <TableHead className="min-w-[200px] text-xs h-8">File Name</TableHead> + <TableHead className="w-[90px] text-xs h-8">Size</TableHead> + <TableHead className="w-[100px] text-xs h-8">Date</TableHead> + <TableHead className="w-[90px] text-xs h-8">Download</TableHead> </TableRow> </TableHeader> <TableBody> @@ -405,7 +405,7 @@ export function SwpDocumentDetailDialog({ onClick={() => handleDownloadFile(file.FILE_NM, document.OWN_DOC_NO || document.DOC_NO)} > <Download className="h-3 w-3 mr-1" /> - 다운로드 + Download </Button> </TableCell> </TableRow> @@ -416,7 +416,7 @@ export function SwpDocumentDetailDialog({ <div className="flex items-center justify-center h-full text-xs text-muted-foreground"> <div className="text-center"> <AlertCircle className="h-6 w-6 mx-auto mb-1 opacity-50" /> - <p>파일이 없습니다</p> + <p>No files</p> </div> </div> ) @@ -424,7 +424,7 @@ export function SwpDocumentDetailDialog({ <div className="flex items-center justify-center h-full text-xs text-muted-foreground"> <div className="text-center"> <FileIcon className="h-8 w-8 mx-auto mb-1 opacity-30" /> - <p>Activity를 선택해주세요</p> + <p>Please select an Activity</p> </div> </div> )} @@ -434,7 +434,7 @@ export function SwpDocumentDetailDialog({ ) : ( <div className="p-8 text-center text-muted-foreground"> <AlertCircle className="h-10 w-10 mx-auto mb-2 opacity-50" /> - <p className="text-sm">Activity 정보가 없습니다</p> + <p className="text-sm">No Activity information</p> </div> )} </div> diff --git a/lib/swp/table/swp-help-dialog.tsx b/lib/swp/table/swp-help-dialog.tsx index 7b18c100..a355f5ad 100644 --- a/lib/swp/table/swp-help-dialog.tsx +++ b/lib/swp/table/swp-help-dialog.tsx @@ -18,21 +18,21 @@ export function SwpUploadHelpDialog() { <DialogTrigger asChild> <Button variant="outline" size="sm" className="gap-2"> <HelpCircle className="h-4 w-4" /> - SWP 제출 메뉴 가이드 + SWP Submission Menu Guide </Button> </DialogTrigger> <DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto"> <DialogHeader> - <DialogTitle>파일 업로드 가이드</DialogTitle> + <DialogTitle>File Upload Guide</DialogTitle> <DialogDescription> - 올바른 파일명 형식으로 업로드해주세요 + Please upload with the correct file name format </DialogDescription> </DialogHeader> <div className="space-y-6"> {/* 탭 설명 */} <div className="space-y-3"> - <h3 className="text-sm font-semibold">탭 설명</h3> + <h3 className="text-sm font-semibold">Tab Description</h3> <div className="rounded-lg border p-4 space-y-3"> <div> @@ -40,8 +40,8 @@ export function SwpUploadHelpDialog() { DOCUMENT REGISTRATION TAB </Badge> <p className="text-sm text-muted-foreground"> - 파일을 업로드한 현황입니다. SHI가 파일 접수 여부를 응답할 예정입니다. - 업로드한 파일의 승낙 여부 등을 확인할 수 있으며, 접수 전의 Standby 상태의 경우 업로드를 취소할 수 있습니다. + This shows the status of uploaded files. SHI will respond with file acceptance status. + You can check the acceptance status of uploaded files, and cancel uploads in Standby status before acceptance. </p> </div> @@ -50,8 +50,8 @@ export function SwpUploadHelpDialog() { DOCUMENT LIST TAB </Badge> <p className="text-sm text-muted-foreground"> - 파일을 업로드한 뒤, SHI가 업로드한 파일을 수락하면 Rev, Activity No가 만들어지며, 해당 테이블에 추가됩니다. - 수락시 Activity No 가 부여됩니다. + After uploading a file, if SHI accepts the uploaded file, Rev and Activity No are created and added to this table. + Activity No is assigned upon acceptance. </p> </div> </div> @@ -59,29 +59,29 @@ export function SwpUploadHelpDialog() { {/* 파일명 형식 */} <div className="space-y-2"> - <h3 className="text-sm font-semibold">파일명 형식</h3> + <h3 className="text-sm font-semibold">File Name Format</h3> <div className="rounded-lg bg-muted p-4 font-mono text-sm"> - [DOC_NO]_[REV_NO]_[STAGE].[확장자] + [DOC_NO]_[REV_NO]_[STAGE].[Extension] </div> <p className="text-xs text-muted-foreground"> - [주의] 언더스코어(_)가 최소 2개 이상 있어야 합니다 + [Note] Must contain at least 2 underscores (_) </p> <p className="text-xs text-muted-foreground"> - [선택사항] 4번째 항목으로 파일명을 추가할 수 있습니다 (예: [DOC_NO]_[REV_NO]_[STAGE]_[파일명].[확장자]) + [Optional] You can add a file name as the 4th item (e.g. [DOC_NO]_[REV_NO]_[STAGE]_[FileName].[Extension]) </p> </div> {/* 각 항목 설명 - 1라인 형태 */} <div className="space-y-3"> - <h3 className="text-sm font-semibold">항목 설명</h3> + <h3 className="text-sm font-semibold">Item Description</h3> <div className="flex items-center gap-3 rounded-lg border p-3"> <Badge variant="secondary" className="font-mono shrink-0"> DOC_NO </Badge> <div className="text-sm"> - <span className="font-medium">벤더의 문서번호</span> - <span className="text-muted-foreground"> - 프로젝트마다 유니크해야 합니다</span> + <span className="font-medium">Vendor Document Number</span> + <span className="text-muted-foreground"> - Must be unique per project</span> </div> </div> @@ -90,8 +90,8 @@ export function SwpUploadHelpDialog() { REV_NO </Badge> <div className="text-sm"> - <span className="font-medium">리비전 번호</span> - <span className="text-muted-foreground"> - 보통 01, 02 같은 식으로 피드백에 따라 증가합니다</span> + <span className="font-medium">Revision Number</span> + <span className="text-muted-foreground"> - Usually increments like 01, 02 based on feedback</span> </div> </div> @@ -100,32 +100,32 @@ export function SwpUploadHelpDialog() { STAGE </Badge> <div className="text-sm"> - <span className="font-medium">스테이지</span> - <span className="text-muted-foreground"> - 스테이지 정보를 입력해주세요. (예: IFA, IFC)</span> + <span className="font-medium">Stage</span> + <span className="text-muted-foreground"> - Please enter stage information. (e.g. IFA, IFC)</span> </div> </div> <div className="flex items-center gap-3 rounded-lg border p-3"> <Badge variant="secondary" className="font-mono shrink-0"> - 파일명 + File Name </Badge> <div className="text-sm"> - <span className="font-medium">자유 파일명 (선택사항)</span> - <span className="text-muted-foreground"> - 문서를 식별할 수 있는 이름 (언더스코어 포함 가능, 생략 가능)</span> + <span className="font-medium">Free File Name (Optional)</span> + <span className="text-muted-foreground"> - Name to identify the document (Underscores allowed, optional)</span> </div> </div> </div> {/* 예시 */} <div className="space-y-2"> - <h3 className="text-sm font-semibold">올바른 예시</h3> + <h3 className="text-sm font-semibold">Correct Examples</h3> <div className="space-y-2"> <div className="rounded-lg bg-green-50 dark:bg-green-950/30 border border-green-200 dark:border-green-800 p-3"> <code className="text-xs font-mono text-green-700 dark:text-green-300"> VD-DOC-001_01_IFA.pdf </code> <p className="text-xs text-green-600 dark:text-green-400 mt-1"> - [O] 기본 형식 (파일명 생략) + [O] Basic Format (File Name Omitted) </p> </div> <div className="rounded-lg bg-green-50 dark:bg-green-950/30 border border-green-200 dark:border-green-800 p-3"> @@ -133,7 +133,7 @@ export function SwpUploadHelpDialog() { VD-DOC-001_01_IFA_drawing_final.pdf </code> <p className="text-xs text-green-600 dark:text-green-400 mt-1"> - [O] 파일명 추가 (파일명에 언더스코어 포함 가능) + [O] File Name Added (Underscores allowed in file name) </p> </div> <div className="rounded-lg bg-green-50 dark:bg-green-950/30 border border-green-200 dark:border-green-800 p-3"> @@ -141,7 +141,7 @@ export function SwpUploadHelpDialog() { TECH-SPEC-002_02_IFC.dwg </code> <p className="text-xs text-green-600 dark:text-green-400 mt-1"> - [O] 기본 형식 사용 + [O] Basic Format Used </p> </div> <div className="rounded-lg bg-green-50 dark:bg-green-950/30 border border-green-200 dark:border-green-800 p-3"> @@ -149,7 +149,7 @@ export function SwpUploadHelpDialog() { DOC-003_03_IFA_test_result_data.xlsx </code> <p className="text-xs text-green-600 dark:text-green-400 mt-1"> - [O] 파일명 추가 (여러 단어 조합 가능) + [O] File Name Added (Multiple words allowed) </p> </div> </div> @@ -157,14 +157,14 @@ export function SwpUploadHelpDialog() { {/* 잘못된 예시 */} <div className="space-y-2"> - <h3 className="text-sm font-semibold">잘못된 예시</h3> + <h3 className="text-sm font-semibold">Incorrect Examples</h3> <div className="space-y-2"> <div className="rounded-lg bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800 p-3"> <code className="text-xs font-mono text-red-700 dark:text-red-300"> VD-DOC-001-01-IFA.pdf </code> <p className="text-xs text-red-600 dark:text-red-400 mt-1"> - [X] 언더스코어(_) 대신 하이픈(-) 사용 + [X] Hyphen (-) used instead of Underscore (_) </p> </div> <div className="rounded-lg bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800 p-3"> @@ -172,7 +172,7 @@ export function SwpUploadHelpDialog() { VD-DOC-001_01.pdf </code> <p className="text-xs text-red-600 dark:text-red-400 mt-1"> - [X] STAGE 정보 누락 (최소 3개 항목 필요) + [X] STAGE info missing (Minimum 3 items required) </p> </div> <div className="rounded-lg bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800 p-3"> @@ -180,7 +180,7 @@ export function SwpUploadHelpDialog() { VD DOC 001_01_IFA.pdf </code> <p className="text-xs text-red-600 dark:text-red-400 mt-1"> - [X] 공백 포함 (언더스코어 사용 필요) + [X] Contains spaces (Use underscores) </p> </div> <div className="rounded-lg bg-red-50 dark:bg-red-950/30 border border-red-200 dark:border-red-800 p-3"> @@ -188,7 +188,7 @@ export function SwpUploadHelpDialog() { VD-DOC-001__IFA.pdf </code> <p className="text-xs text-red-600 dark:text-red-400 mt-1"> - [X] REV_NO 비어있음 (빈 항목 불가) + [X] REV_NO empty (Cannot be empty) </p> </div> </div> @@ -197,15 +197,15 @@ export function SwpUploadHelpDialog() { {/* 주의사항 */} <div className="rounded-lg bg-amber-50 dark:bg-amber-950/30 border border-amber-200 dark:border-amber-800 p-4"> <h3 className="text-sm font-semibold text-amber-900 dark:text-amber-100 mb-2"> - [주의사항] + [Precautions] </h3> <ul className="text-xs text-amber-800 dark:text-amber-200 space-y-1 list-disc list-inside"> - <li>파일명은 최소 [DOC_NO]_[REV_NO]_[STAGE].[확장자] 형식이어야 합니다</li> - <li>DOC_NO는 현재 프로젝트에 할당된 문서번호여야 합니다</li> - <li>4번째 항목(파일명)은 선택사항으로 생략 가능합니다</li> - <li>업로드 날짜/시간은 시스템에서 자동으로 생성됩니다</li> - <li>같은 파일명으로 이미 업로드된 파일이 있으면 덮어쓰지 않고 오류 처리됩니다</li> - <li>프로젝트를 먼저 선택해야 업로드 버튼이 활성화됩니다</li> + <li>File name must be at least in [DOC_NO]_[REV_NO]_[STAGE].[Extension] format</li> + <li>DOC_NO must be a document number assigned to the current project</li> + <li>The 4th item (File Name) is optional and can be omitted</li> + <li>Upload date/time is automatically generated by the system</li> + <li>If a file with the same name is already uploaded, it will not be overwritten and will be treated as an error</li> + <li>You must select a project first to enable the upload button</li> </ul> </div> </div> diff --git a/lib/swp/table/swp-inbox-document-detail-dialog.tsx b/lib/swp/table/swp-inbox-document-detail-dialog.tsx index ca7fcf1b..7f814267 100644 --- a/lib/swp/table/swp-inbox-document-detail-dialog.tsx +++ b/lib/swp/table/swp-inbox-document-detail-dialog.tsx @@ -35,7 +35,7 @@ interface SwpInboxDocumentDetailDialogProps { userId: string; } -// 리비전별 그룹 타입 +// Group type by revision interface RevisionGroup { revNo: string; stage: string; @@ -43,7 +43,7 @@ interface RevisionGroup { totalFiles: number; } -// Activity별 그룹 타입 (activity가 null일 수 있음) +// Group type by Activity (activity can be null) interface ActivityGroup { actvNo: string | null; files: SwpFileApiResponse[]; @@ -59,7 +59,7 @@ export function SwpInboxDocumentDetailDialog({ const [expandedActivities, setExpandedActivities] = useState<Set<string>>(new Set()); const [isAllExpanded, setIsAllExpanded] = useState(true); - // 파일들을 리비전 > Activity 구조로 그룹핑 + // Group files into Revision > Activity structure const revisionGroups = useMemo(() => { if (!document) return []; @@ -78,7 +78,7 @@ export function SwpInboxDocumentDetailDialog({ revMap.forEach((revFiles, revKey) => { const [revNo, stage] = revKey.split("|"); - // Activity별로 그룹핑 (null 가능) + // Group by Activity (nullable) const actMap = new Map<string | null, SwpFileApiResponse[]>(); revFiles.forEach((file) => { @@ -94,7 +94,7 @@ export function SwpInboxDocumentDetailDialog({ activities.push({ actvNo, files }); }); - // Activity가 없는 것을 먼저, 있는 것을 나중에 정렬 + // Sort: No Activity first, then with Activity activities.sort((a, b) => { if (a.actvNo === null && b.actvNo !== null) return -1; if (a.actvNo !== null && b.actvNo === null) return 1; @@ -110,11 +110,11 @@ export function SwpInboxDocumentDetailDialog({ }); }); - // 리비전 번호로 정렬 (최신이 위로) + // Sort by revision number (newest first) return result.sort((a, b) => b.revNo.localeCompare(a.revNo)); }, [document]); - // Dialog가 열릴 때 모두 펼치기 + // Expand all when dialog opens React.useEffect(() => { if (open && revisionGroups.length > 0) { const allRevKeys = new Set<string>(); @@ -160,15 +160,15 @@ export function SwpInboxDocumentDetailDialog({ }); }; - // 일괄 열기/닫기 + // Toggle all const handleToggleAll = () => { if (isAllExpanded) { - // 모두 닫기 + // Collapse all setExpandedRevisions(new Set()); setExpandedActivities(new Set()); setIsAllExpanded(false); } else { - // 모두 열기 + // Expand all const allRevKeys = new Set<string>(); const allActKeys = new Set<string>(); @@ -191,27 +191,27 @@ export function SwpInboxDocumentDetailDialog({ const handleCancelFile = async (boxSeq: string, actvSeq: string, fileName: string) => { try { await cancelVendorFile(boxSeq, actvSeq); - toast.success(`파일 취소 완료: ${fileName}`); + toast.success(`File cancelled: ${fileName}`); - // Dialog를 닫고 부모 컴포넌트가 새로고침하도록 함 + // Close dialog and trigger parent refresh onOpenChange(false); } catch (error) { - console.error("파일 취소 실패:", error); - toast.error("파일 취소에 실패했습니다"); + console.error("Failed to cancel file:", error); + toast.error("Failed to cancel file"); } }; const handleDownloadFile = async (fileName: string, ownDocNo: string) => { try { - toast.info("파일 다운로드 중..."); + toast.info("Downloading file..."); const result = await downloadVendorFile(projNo, ownDocNo, fileName); if (!result.success || !result.data) { - toast.error(result.error || "파일 다운로드 실패"); + toast.error(result.error || "File download failed"); return; } - // Blob 생성 및 다운로드 + // Create Blob and download const blob = new Blob([Buffer.from(result.data)], { type: result.mimeType }); const url = URL.createObjectURL(blob); const link = window.document.createElement("a"); @@ -222,10 +222,10 @@ export function SwpInboxDocumentDetailDialog({ window.document.body.removeChild(link); URL.revokeObjectURL(url); - toast.success(`파일 다운로드 완료: ${fileName}`); + toast.success(`File downloaded: ${fileName}`); } catch (error) { - console.error("파일 다운로드 실패:", error); - toast.error("파일 다운로드에 실패했습니다"); + console.error("File download failed:", error); + toast.error("Failed to download file"); } }; @@ -233,7 +233,7 @@ export function SwpInboxDocumentDetailDialog({ <Dialog open={open} onOpenChange={onOpenChange}> <DialogContent className="max-w-6xl max-h-[90vh] overflow-y-auto"> <DialogHeader> - <DialogTitle>업로드 파일 상세</DialogTitle> + <DialogTitle>Uploaded File Details</DialogTitle> {document && ( <DialogDescription> {document.ownDocNo} @@ -243,30 +243,30 @@ export function SwpInboxDocumentDetailDialog({ {document && ( <div className="space-y-4"> - {/* 문서 정보 */} + {/* Document Info */} <div className="grid grid-cols-1 md:grid-cols-4 gap-4 p-4 bg-muted/30 rounded-lg"> <div> <span className="text-sm font-semibold">OWN_DOC_NO:</span> <div className="text-sm font-mono">{document.ownDocNo}</div> </div> <div> - <span className="text-sm font-semibold">최신 스테이지:</span> + <span className="text-sm font-semibold">Latest Stage:</span> <div className="text-sm">{document.latestStage || "-"}</div> </div> <div> - <span className="text-sm font-semibold">최신 리비전:</span> + <span className="text-sm font-semibold">Latest Revision:</span> <div className="text-sm">{document.latestRevNo || "-"}</div> </div> <div> - <span className="text-sm font-semibold">최신 REV 파일:</span> - <div className="text-sm">{document.latestRevFileCount}개</div> + <span className="text-sm font-semibold">Latest REV Files:</span> + <div className="text-sm">{document.latestRevFileCount}</div> </div> </div> - {/* 리비전 및 액티비티 트리 */} + {/* Revision and Activity Tree */} {revisionGroups.length > 0 ? ( <div className="space-y-2"> - {/* 일괄 열기/닫기 버튼 */} + {/* Toggle All Button */} <div className="flex justify-end"> <Button variant="outline" @@ -276,12 +276,12 @@ export function SwpInboxDocumentDetailDialog({ {isAllExpanded ? ( <> <ChevronDown className="h-4 w-4 mr-2" /> - 일괄 닫기 + Collapse All </> ) : ( <> <ChevronRight className="h-4 w-4 mr-2" /> - 일괄 열기 + Expand All </> )} </Button> @@ -292,7 +292,7 @@ export function SwpInboxDocumentDetailDialog({ return ( <div key={revKey} className="border rounded-lg"> - {/* 리비전 헤더 */} + {/* Revision Header */} <div className="flex items-center justify-between p-3 bg-muted/50 cursor-pointer hover:bg-muted" onClick={() => toggleRevision(revKey)} @@ -314,12 +314,12 @@ export function SwpInboxDocumentDetailDialog({ {revision.stage} </Badge> <span className="text-sm text-muted-foreground"> - {revision.activities.length}개 그룹 / {revision.totalFiles}개 파일 + {revision.activities.length} Groups / {revision.totalFiles} Files </span> </div> </div> - {/* 액티비티 목록 (또는 Activity 없는 파일들) */} + {/* Activity List (or files without Activity) */} {isRevExpanded && ( <div className="p-2 space-y-2"> {revision.activities.map((activity) => { @@ -328,7 +328,7 @@ export function SwpInboxDocumentDetailDialog({ return ( <div key={actKey} className="border rounded-md"> - {/* 액티비티 헤더 */} + {/* Activity Header */} <div className="flex items-center justify-between p-2 bg-muted/30 cursor-pointer hover:bg-muted/50" onClick={() => toggleActivity(actKey)} @@ -350,16 +350,16 @@ export function SwpInboxDocumentDetailDialog({ </> ) : ( <Badge variant="outline" className="bg-gray-100 text-gray-800"> - Activity 없음 + No Activity </Badge> )} <span className="text-xs text-muted-foreground"> - {activity.files.length}개 파일 + {activity.files.length} Files </span> </div> </div> - {/* 파일 목록 */} + {/* File List */} {isActExpanded && ( <div className="p-2 space-y-1"> {activity.files.map((file, idx) => ( @@ -398,7 +398,7 @@ export function SwpInboxDocumentDetailDialog({ onClick={() => handleCancelFile(file.BOX_SEQ!, file.ACTV_SEQ!, file.FILE_NM)} > <XCircle className="h-4 w-4 mr-1" /> - 취소 + Cancel </Button> )} {file.FLD_PATH && ( @@ -408,7 +408,7 @@ export function SwpInboxDocumentDetailDialog({ onClick={() => handleDownloadFile(file.FILE_NM, document.ownDocNo)} > <Download className="h-4 w-4 mr-1" /> - 다운로드 + Download </Button> )} </div> @@ -428,7 +428,7 @@ export function SwpInboxDocumentDetailDialog({ ) : ( <div className="p-8 text-center text-muted-foreground"> <AlertCircle className="h-12 w-12 mx-auto mb-2 opacity-50" /> - <p>파일 정보가 없습니다</p> + <p>No file information available</p> </div> )} </div> diff --git a/lib/swp/table/swp-inbox-history-dialog.tsx b/lib/swp/table/swp-inbox-history-dialog.tsx index fbb75f3c..e46829d0 100644 --- a/lib/swp/table/swp-inbox-history-dialog.tsx +++ b/lib/swp/table/swp-inbox-history-dialog.tsx @@ -217,7 +217,7 @@ export function SwpInboxHistoryDialog({ const handleCancelFile = async (file: SwpFileApiResponse) => { if (!file.BOX_SEQ || !file.ACTV_SEQ) { - toast.error("취소할 수 없는 파일입니다 (BOX_SEQ 또는 ACTV_SEQ 없음)"); + toast.error("File cannot be canceled (Missing BOX_SEQ or ACTV_SEQ)"); return; } @@ -236,13 +236,13 @@ export function SwpInboxHistoryDialog({ userId, }); - toast.success(`파일 취소 완료: ${file.FILE_NM}`); + toast.success(`File canceled: ${file.FILE_NM}`); // 취소된 파일로 마킹 (상태 변경) setCancelledFiles((prev) => new Set(prev).add(fileKey)); } catch (error) { - console.error("파일 취소 실패:", error); - toast.error("파일 취소에 실패했습니다"); + console.error("File cancel failed:", error); + toast.error("Failed to cancel file"); } finally { setCancellingFiles((prev) => { const newSet = new Set(prev); @@ -254,7 +254,7 @@ export function SwpInboxHistoryDialog({ const handleDownloadFile = async (file: SwpFileApiResponse) => { try { - toast.info("파일 다운로드 준비 중..."); + toast.info("Preparing file download..."); // API route를 통해 다운로드 const downloadUrl = `/api/swp/download/${encodeURIComponent(file.OWN_DOC_NO)}?projNo=${encodeURIComponent(projNo)}&fileName=${encodeURIComponent(file.FILE_NM)}`; @@ -262,10 +262,10 @@ export function SwpInboxHistoryDialog({ // 새 탭에서 다운로드 window.open(downloadUrl, "_blank"); - toast.success(`파일 다운로드 시작: ${file.FILE_NM}`); + toast.success(`File download started: ${file.FILE_NM}`); } catch (error) { - console.error("파일 다운로드 실패:", error); - toast.error("파일 다운로드에 실패했습니다"); + console.error("File download failed:", error); + toast.error("Failed to download file"); } }; @@ -284,10 +284,10 @@ export function SwpInboxHistoryDialog({ <Dialog open={open} onOpenChange={onOpenChange}> <DialogContent className="max-w-6xl max-h-[90vh] overflow-y-auto"> <DialogHeader> - <DialogTitle>Document 전체 이력</DialogTitle> + <DialogTitle>Document History</DialogTitle> {docNo && ( <DialogDescription> - {docNo} - 총 {documentFiles.length}개 파일 + {docNo} - Total {documentFiles.length} files </DialogDescription> )} </DialogHeader> @@ -299,7 +299,7 @@ export function SwpInboxHistoryDialog({ <div className="relative flex-1"> <Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" /> <Input - placeholder="Rev No, Activity No, File Name, Stage로 검색..." + placeholder="Search by Rev No, Activity No, File Name, Stage..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} className="pl-10" @@ -313,12 +313,12 @@ export function SwpInboxHistoryDialog({ {isAllExpanded ? ( <> <ChevronDown className="h-4 w-4 mr-2" /> - 일괄 닫기 + Collapse All </> ) : ( <> <ChevronRight className="h-4 w-4 mr-2" /> - 일괄 열기 + Expand All </> )} </Button> @@ -327,7 +327,7 @@ export function SwpInboxHistoryDialog({ {/* 검색 결과 안내 */} {searchQuery && ( <div className="text-sm text-muted-foreground"> - 검색 결과: {filteredFiles.length}개 파일 (전체 {documentFiles.length}개) + Search Results: {filteredFiles.length} files (Total {documentFiles.length}) </div> )} @@ -358,7 +358,7 @@ export function SwpInboxHistoryDialog({ {revision.stage} </Badge> <span className="text-sm text-muted-foreground"> - {revision.activities.length}개 그룹 / {revision.totalFiles}개 파일 + {revision.activities.length} groups / {revision.totalFiles} files </span> </div> </div> @@ -394,11 +394,11 @@ export function SwpInboxHistoryDialog({ </> ) : ( <Badge variant="outline" className="bg-gray-100 text-gray-800"> - Activity 없음 + No Activity </Badge> )} <span className="text-xs text-muted-foreground"> - {activity.files.length}개 파일 + {activity.files.length} files </span> </div> </div> @@ -463,12 +463,12 @@ export function SwpInboxHistoryDialog({ {isCancelling ? ( <> <Loader2 className="h-4 w-4 mr-1 animate-spin" /> - 취소 중... + Canceling... </> ) : ( <> <XCircle className="h-4 w-4 mr-1" /> - 취소 + Cancel </> )} </Button> @@ -497,7 +497,7 @@ export function SwpInboxHistoryDialog({ </div> ) : ( <div className="p-8 text-center text-muted-foreground"> - {searchQuery ? "검색 결과가 없습니다" : "파일 정보가 없습니다"} + {searchQuery ? "No search results" : "No file information"} </div> )} </div> diff --git a/lib/swp/table/swp-inbox-table-columns.tsx b/lib/swp/table/swp-inbox-table-columns.tsx index dbf25c5d..f7596578 100644 --- a/lib/swp/table/swp-inbox-table-columns.tsx +++ b/lib/swp/table/swp-inbox-table-columns.tsx @@ -8,7 +8,7 @@ import type { SwpFileApiResponse } from "@/lib/swp/api-client"; export const swpInboxDocumentColumns: ColumnDef<SwpFileApiResponse>[] = [ { accessorKey: "STAT_NM", - header: "최신 리비전의 최신 파일 상태", + header: "Latest File Status of Latest Revision", cell: ({ row }) => { const statNm = row.original.STAT_NM; const stat = row.original.STAT; @@ -16,7 +16,7 @@ export const swpInboxDocumentColumns: ColumnDef<SwpFileApiResponse>[] = [ if (!stat) return displayStatus; - // STAT 코드 기반 색상 결정 + // Determine color based on STAT code const color = stat === "SCW03" || stat === "SCW08" ? "bg-green-100 text-green-800" : // Complete, Checked stat === "SCW02" ? "bg-blue-100 text-blue-800" : // Processing @@ -25,7 +25,7 @@ export const swpInboxDocumentColumns: ColumnDef<SwpFileApiResponse>[] = [ stat === "SCW07" ? "bg-purple-100 text-purple-800" : // Send for Eng Verification stat === "SCW09" ? "bg-gray-100 text-gray-800" : // Cancelled stat === "SCW00" ? "bg-orange-100 text-orange-800" : // Upload - "bg-gray-100 text-gray-800"; // 기타 + "bg-gray-100 text-gray-800"; // Others return ( <Badge variant="outline" className={color}> @@ -45,7 +45,7 @@ export const swpInboxDocumentColumns: ColumnDef<SwpFileApiResponse>[] = [ }, { accessorKey: "FILE_NM", - header: "파일명", + header: "File Name", cell: ({ row }) => ( <div className="max-w-md truncate" title={row.original.FILE_NM}> {row.original.FILE_NM} @@ -55,7 +55,7 @@ export const swpInboxDocumentColumns: ColumnDef<SwpFileApiResponse>[] = [ }, { accessorKey: "STAGE", - header: "스테이지", + header: "Stage", cell: ({ row }) => { const stage = row.original.STAGE; if (!stage) return "-"; @@ -91,7 +91,7 @@ export const swpInboxDocumentColumns: ColumnDef<SwpFileApiResponse>[] = [ }, { accessorKey: "FILE_SZ", - header: "파일 크기", + header: "File Size", cell: ({ row }) => { const size = row.original.FILE_SZ; if (!size) return "-"; @@ -121,7 +121,7 @@ export const swpInboxDocumentColumns: ColumnDef<SwpFileApiResponse>[] = [ }, { accessorKey: "CRTE_DTM", - header: "생성일시", + header: "Created Date", cell: ({ row }) => { const date = row.original.CRTE_DTM; if (!date) return "-"; diff --git a/lib/swp/table/swp-inbox-table.tsx b/lib/swp/table/swp-inbox-table.tsx index d070f2fd..788fcb70 100644 --- a/lib/swp/table/swp-inbox-table.tsx +++ b/lib/swp/table/swp-inbox-table.tsx @@ -389,7 +389,7 @@ export function SwpInboxTable({ // 선택된 파일 일괄 취소 const handleBulkCancel = async () => { if (selectedFiles.size === 0) { - toast.error("취소할 파일을 선택해주세요"); + toast.error("Please select files to cancel"); return; } @@ -398,12 +398,12 @@ export function SwpInboxTable({ ); if (filesToCancel.length === 0) { - toast.error("취소할 파일이 없습니다"); + toast.error("No files to cancel"); return; } try { - toast.info(`${filesToCancel.length}개 파일 취소 중...`); + toast.info(`Canceling ${filesToCancel.length} files...`); // 병렬 취소 const cancelPromises = filesToCancel.map((row) => @@ -416,21 +416,21 @@ export function SwpInboxTable({ await Promise.all(cancelPromises); - toast.success(`${filesToCancel.length}개 파일 취소 완료`); + toast.success(`Canceled ${filesToCancel.length} files`); setSelectedFiles(new Set()); // 선택 초기화 // 페이지 리프레시 window.location.reload(); } catch (error) { - console.error("일괄 취소 실패:", error); - toast.error("일부 파일 취소에 실패했습니다"); + console.error("Bulk cancel failed:", error); + toast.error("Failed to cancel some files"); } }; const handleDownloadFile = async (file: SwpFileApiResponse) => { try { - toast.info("파일 다운로드 준비 중..."); + toast.info("Preparing file download..."); // API route를 통해 다운로드 const downloadUrl = `/api/swp/download/${encodeURIComponent(file.OWN_DOC_NO)}?projNo=${encodeURIComponent(projNo)}&fileName=${encodeURIComponent(file.FILE_NM)}`; @@ -438,10 +438,10 @@ export function SwpInboxTable({ // 새 탭에서 다운로드 window.open(downloadUrl, "_blank"); - toast.success(`파일 다운로드 시작: ${file.FILE_NM}`); + toast.success(`File download started: ${file.FILE_NM}`); } catch (error) { - console.error("파일 다운로드 실패:", error); - toast.error("파일 다운로드에 실패했습니다"); + console.error("File download failed:", error); + toast.error("Failed to download file"); } }; @@ -477,7 +477,7 @@ export function SwpInboxTable({ if (files.length === 0 && requiredDocs.length === 0) { return ( <div className="border rounded-lg p-8 text-center text-muted-foreground"> - 업로드한 파일이 없습니다. + No uploaded files. </div> ); } @@ -493,7 +493,7 @@ export function SwpInboxTable({ onClick={() => setSelectedStatus(null)} className="h-9" > - 전체 ({files.length + requiredDocs.length}) + All ({files.length + requiredDocs.length}) </Button> {statusCounts.map((statusCount) => ( <Button @@ -516,7 +516,7 @@ export function SwpInboxTable({ {selectedFiles.size > 0 && ( <div className="flex items-center gap-2"> <span className="text-sm text-muted-foreground"> - {selectedFiles.size}개 선택됨 + {selectedFiles.size} selected </span> <Button variant="destructive" @@ -533,7 +533,7 @@ export function SwpInboxTable({ {/* 테이블 */} {tableRows.length === 0 ? ( <div className="border rounded-lg p-8 text-center text-muted-foreground"> - 해당 상태의 파일이 없습니다. + No files with this status. </div> ) : ( <div className="rounded-md border overflow-x-auto"> @@ -685,7 +685,7 @@ export function SwpInboxTable({ }} > {row.note1 ? ( - <div className="truncate cursor-pointer hover:text-primary underline" title="클릭하여 전체 내용 보기"> + <div className="truncate cursor-pointer hover:text-primary underline" title="Click to view full content"> {row.note1} </div> ) : ( diff --git a/lib/swp/table/swp-note-dialog.tsx b/lib/swp/table/swp-note-dialog.tsx index 5f86de24..c8372d68 100644 --- a/lib/swp/table/swp-note-dialog.tsx +++ b/lib/swp/table/swp-note-dialog.tsx @@ -17,8 +17,8 @@ interface SwpNoteDialogProps { } /** - * SWP Note 전체 내용 표시 Dialog - * DC Note (NOTE1) 또는 Eng Note (NOTE2)의 전체 내용을 표시합니다. + * SWP Note Full Content Dialog + * Displays the full content of DC Note (NOTE1) or Eng Note (NOTE2). */ export function SwpNoteDialog({ open, @@ -34,7 +34,7 @@ export function SwpNoteDialog({ </DialogHeader> <ScrollArea className="max-h-[60vh] pr-4"> <div className="whitespace-pre-wrap text-sm"> - {content || <span className="text-muted-foreground">내용이 없습니다.</span>} + {content || <span className="text-muted-foreground">No content.</span>} </div> </ScrollArea> </DialogContent> diff --git a/lib/swp/table/swp-revision-list-dialog.tsx b/lib/swp/table/swp-revision-list-dialog.tsx index 8924db81..66892469 100644 --- a/lib/swp/table/swp-revision-list-dialog.tsx +++ b/lib/swp/table/swp-revision-list-dialog.tsx @@ -55,7 +55,7 @@ export function SwpRevisionListDialog({ <Dialog open={open} onOpenChange={onOpenChange}> <DialogContent className="max-w-6xl max-h-[90vh]"> <DialogHeader> - <DialogTitle>문서 상세</DialogTitle> + <DialogTitle>Document Detail</DialogTitle> {document && ( <DialogDescription> {document.DOC_NO} - {document.DOC_TITLE} @@ -68,25 +68,25 @@ export function SwpRevisionListDialog({ {/* 문서 정보 */} <div className="grid grid-cols-1 md:grid-cols-4 gap-4 p-4 bg-muted/30 rounded-lg"> <div> - <span className="text-sm font-semibold">프로젝트:</span> + <span className="text-sm font-semibold">Project:</span> <div className="text-sm">{document.PROJ_NO}</div> {document.PROJ_NM && ( <div className="text-xs text-muted-foreground">{document.PROJ_NM}</div> )} </div> <div> - <span className="text-sm font-semibold">패키지:</span> + <span className="text-sm font-semibold">Package:</span> <div className="text-sm">{document.PKG_NO || "-"}</div> </div> <div> - <span className="text-sm font-semibold">업체:</span> + <span className="text-sm font-semibold">Company:</span> <div className="text-sm">{document.CPY_NM || "-"}</div> {document.VNDR_CD && ( <div className="text-xs text-muted-foreground">{document.VNDR_CD}</div> )} </div> <div> - <span className="text-sm font-semibold">마지막 리비전 넘버:</span> + <span className="text-sm font-semibold">Last Revision Number:</span> <div className="text-sm">{document.LTST_REV_NO || "-"}</div> </div> </div> @@ -95,7 +95,7 @@ export function SwpRevisionListDialog({ {loadingRevisions ? ( <div className="flex items-center justify-center p-8"> <Loader2 className="h-6 w-6 animate-spin" /> - <span className="ml-2">리비전 로딩 중...</span> + <span className="ml-2">Loading revisions...</span> </div> ) : revisions.length ? ( <DocumentDetailView @@ -107,7 +107,7 @@ export function SwpRevisionListDialog({ /> ) : ( <div className="p-8 text-center text-muted-foreground"> - 리비전 없음 + No revisions </div> )} </div> @@ -178,11 +178,11 @@ function DocumentDetailView({ size="sm" onClick={handleExpandAll} > - {allExpanded ? "모두 접기" : "모두 펼치기"} + {allExpanded ? "Collapse All" : "Expand All"} </Button> </div> - {/* 리비전 테이블 */} + {/* Revision Table */} <div className="rounded-md border"> <Table> <TableHeader> @@ -204,7 +204,7 @@ function DocumentDetailView({ <TableBody> {revisionTable.getRowModel().rows.map((row) => ( <React.Fragment key={row.id}> - {/* 리비전 행 */} + {/* Revision Row */} <TableRow className="bg-muted/20"> {row.getVisibleCells().map((cell) => ( <TableCell key={cell.id}> @@ -230,20 +230,20 @@ function DocumentDetailView({ ))} </TableRow> - {/* 파일 행들 (확장 시) */} + {/* File Rows (when expanded) */} {row.getIsExpanded() && ( <TableRow> <TableCell colSpan={swpRevisionColumns.length} className="p-0 bg-blue-50/30"> {loadingFiles.has(row.original.id) ? ( <div className="flex items-center justify-center p-4"> <Loader2 className="h-5 w-5 animate-spin" /> - <span className="ml-2 text-sm">파일 로딩 중...</span> + <span className="ml-2 text-sm">Loading files...</span> </div> ) : fileData[row.original.id]?.length ? ( <FileSubTable files={fileData[row.original.id]} /> ) : ( <div className="p-4 text-center text-sm text-muted-foreground"> - 파일 없음 + No files </div> )} </TableCell> @@ -259,7 +259,7 @@ function DocumentDetailView({ } // ============================================================================ -// 파일 서브 테이블 +// File Sub Table // ============================================================================ interface FileSubTableProps { diff --git a/lib/swp/table/swp-table-columns.tsx b/lib/swp/table/swp-table-columns.tsx index 261cf960..9cb645a5 100644 --- a/lib/swp/table/swp-table-columns.tsx +++ b/lib/swp/table/swp-table-columns.tsx @@ -11,10 +11,10 @@ import { toast } from "sonner"; export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ { accessorKey: "LTST_ACTV_STAT", - header: "상태", + header: "Status", cell: ({ row }) => { const status = row.original.LTST_ACTV_STAT; - if (!status) return "-"; + if (!status) return "Waiting"; const color = status.includes("Complete") ? "bg-green-100 text-green-800" : @@ -54,7 +54,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ }, { accessorKey: "DOC_TITLE", - header: "문서제목", + header: "Document Title", cell: ({ row }) => ( <div className="truncate" title={row.original.DOC_TITLE}> {row.original.DOC_TITLE} @@ -66,7 +66,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ }, { accessorKey: "PROJ_NO", - header: "프로젝트", + header: "Project", cell: ({ row }) => ( <div> <div className="font-medium">{row.original.PROJ_NO}</div> @@ -83,7 +83,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ }, { accessorKey: "PKG_NO", - header: "패키지", + header: "Package", cell: ({ row }) => row.original.PKG_NO || "-", size: 100, minSize: 100, @@ -91,7 +91,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ }, { accessorKey: "VNDR_CD", - header: "업체", + header: "Vendor", cell: ({ row }) => ( <div> {row.original.VNDR_CD && ( @@ -110,7 +110,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ }, { accessorKey: "STAGE", - header: "최신 스테이지", + header: "Latest Stage", cell: ({ row }) => { const stage = row.original.STAGE; if (!stage) return "-"; @@ -132,7 +132,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ }, { accessorKey: "LTST_REV_NO", - header: "최신 REV", + header: "Latest REV", cell: ({ row }) => row.original.LTST_REV_NO || "-", size: 100, minSize: 100, @@ -141,7 +141,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ { id: "actions", - header: "커버페이지 다운로드", + header: "Download Cover Page", cell: function ActionCell({ row }) { const [isDownloading, setIsDownloading] = React.useState(false); @@ -152,7 +152,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ const projectCode = row.original.PROJ_NO; if (!docNumber || !projectCode) { - toast.error("문서 번호 또는 프로젝트 정보가 없습니다."); + toast.error("Missing document number or project information."); return; } @@ -165,7 +165,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ ); if (!projectIdResponse.ok) { - toast.error("프로젝트 정보를 찾을 수 없습니다."); + toast.error("Project information not found."); return; } @@ -178,7 +178,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ if (!response.ok) { const error = await response.json(); - throw new Error(error.message || "커버페이지 다운로드 실패"); + throw new Error(error.message || "Failed to download cover page"); } // 3. 파일 다운로드 @@ -192,14 +192,14 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ window.URL.revokeObjectURL(url); document.body.removeChild(a); - toast.success("커버페이지 다운로드가 시작되었습니다."); + toast.success("Cover page download started."); } catch (error) { console.error("커버페이지 다운로드 오류:", error); toast.error( error instanceof Error ? error.message - : "커버페이지 다운로드 중 오류가 발생했습니다." + : "An error occurred while downloading the cover page." ); } finally { setIsDownloading(false); @@ -213,7 +213,7 @@ export const swpDocumentColumns: ColumnDef<DocumentListItem>[] = [ onClick={handleDownloadCover} disabled={isDownloading} className="h-8 w-8 p-0" - title="커버페이지 다운로드" + title="Download Cover Page" > {isDownloading ? ( <Loader2 className="h-4 w-4 animate-spin" /> diff --git a/lib/swp/table/swp-table-toolbar.tsx b/lib/swp/table/swp-table-toolbar.tsx index 276eca14..ab01a87d 100644 --- a/lib/swp/table/swp-table-toolbar.tsx +++ b/lib/swp/table/swp-table-toolbar.tsx @@ -140,7 +140,7 @@ export function SwpTableToolbar({ const loadDocumentClassInfo = async () => { try { - console.log(`[SwpTableToolbar] 프로젝트 ${projNo} 문서 정보 로드 시작`); + console.log(`[SwpTableToolbar] Start loading document info for project ${projNo}`); // 서버 액션 호출 const result = await getDocumentClassInfoByProjectCode(projNo); @@ -150,32 +150,32 @@ export function SwpTableToolbar({ setVendorDocNumberToDocClassMap(result.vendorDocNumberToDocClassMap); setDocumentClassStages(result.documentClassStages); - console.log(`[SwpTableToolbar] 문서 정보 로드 완료:`, { + console.log(`[SwpTableToolbar] Document info load complete:`, { vendorDocNumbers: Object.keys(result.vendorDocNumberToDocClassMap).length, documentClassStages: result.documentClassStages, }); } else { - console.warn(`[SwpTableToolbar] 문서 정보 로드 실패:`, result.error); + console.warn(`[SwpTableToolbar] Document info load failed:`, result.error); setVendorDocNumberToDocClassMap({}); setDocumentClassStages({}); toast({ variant: "destructive", - title: "문서 정보 로드 실패", - description: result.error || "문서 정보를 가져올 수 없습니다.", + title: "Failed to load document info", + description: result.error || "Cannot retrieve document info.", }); } } } catch (error) { if (!isCancelled) { - console.error('[SwpTableToolbar] 문서 정보 로드 실패:', error); + console.error('[SwpTableToolbar] Failed to load document info:', error); setVendorDocNumberToDocClassMap({}); setDocumentClassStages({}); toast({ variant: "destructive", - title: "문서 정보 로드 실패", - description: "문서 정보를 가져올 수 없습니다. 페이지를 새로고침해주세요.", + title: "Failed to load document info", + description: "Cannot retrieve document info. Please refresh the page.", }); } } @@ -197,8 +197,8 @@ export function SwpTableToolbar({ if (!projNo) { toast({ variant: "destructive", - title: "프로젝트 선택 필요", - description: "파일을 업로드할 프로젝트를 먼저 선택해주세요.", + title: "Project selection required", + description: "Please select a project to upload files to first.", }); onFilesProcessed?.(); return; @@ -207,8 +207,8 @@ export function SwpTableToolbar({ if (!vendorCode) { toast({ variant: "destructive", - title: "업체 코드 오류", - description: "벤더 정보를 가져올 수 없습니다.", + title: "Vendor code error", + description: "Cannot retrieve vendor info.", }); onFilesProcessed?.(); return; @@ -244,8 +244,8 @@ export function SwpTableToolbar({ if (!projNo) { toast({ variant: "destructive", - title: "프로젝트 선택 필요", - description: "파일을 업로드할 프로젝트를 먼저 선택해주세요.", + title: "Project selection required", + description: "Please select a project to upload files to first.", }); return; } @@ -253,8 +253,8 @@ export function SwpTableToolbar({ if (!vendorCode) { toast({ variant: "destructive", - title: "업체 코드 오류", - description: "벤더 정보를 가져올 수 없습니다.", + title: "Vendor code error", + description: "Cannot retrieve vendor info.", }); return; } @@ -304,8 +304,8 @@ export function SwpTableToolbar({ startUpload(async () => { try { toast({ - title: "파일 업로드 시작", - description: `${validFiles.length}개 파일을 업로드합니다...`, + title: "File upload started", + description: `Uploading ${validFiles.length} files...`, }); const formData = new FormData(); @@ -322,7 +322,7 @@ export function SwpTableToolbar({ }); if (!response.ok) { - throw new Error(`업로드 실패: ${response.statusText}`); + throw new Error(`Upload failed: ${response.statusText}`); } const result = await response.json(); @@ -335,7 +335,7 @@ export function SwpTableToolbar({ setShowResultDialog(true); toast({ - title: result.success ? "업로드 완료" : "일부 업로드 실패", + title: result.success ? "Upload complete" : "Some uploads failed", description: result.message, }); @@ -345,21 +345,21 @@ export function SwpTableToolbar({ onUploadComplete?.(); toast({ - title: "문서 목록 갱신 중", - description: "외부 시스템 처리를 기다리는 중입니다...", + title: "Refreshing document list", + description: "Waiting for external system processing...", }); // 2초 딜레이 후 새로고침 setTimeout(() => { onRefresh(); toast({ - title: "갱신 완료", - description: "업로드된 파일이 문서 목록에 반영되었습니다.", + title: "Refresh complete", + description: "Uploaded files have been reflected in the document list.", }); }, 2000); } } catch (error) { - console.error("파일 업로드 실패:", error); + console.error("File upload failed:", error); // 검증 다이얼로그 닫기 setShowValidationDialog(false); @@ -367,7 +367,7 @@ export function SwpTableToolbar({ const errorResults = validFiles.map((file) => ({ fileName: file.name, success: false, - error: error instanceof Error ? error.message : "알 수 없는 오류", + error: error instanceof Error ? error.message : "Unknown error", })); setUploadResults(errorResults); @@ -444,7 +444,7 @@ export function SwpTableToolbar({ disabled={isRefreshing || !projNo} > <RefreshCw className={`h-4 w-4 mr-2 ${isRefreshing ? "animate-spin" : ""}`} /> - 새로고침 + Refresh </Button> <Button variant="outline" @@ -453,7 +453,7 @@ export function SwpTableToolbar({ disabled={isUploading || !projNo} > <Upload className={`h-4 w-4 mr-2 ${isUploading ? "animate-pulse" : ""}`} /> - {isUploading ? "업로드 중..." : "파일 업로드"} + {isUploading ? "Uploading..." : "Upload File"} </Button> {/* 별도 탭으로 분리하고 메인 테이블로 변경하였음. */} @@ -472,7 +472,7 @@ export function SwpTableToolbar({ {/* 검색 필터 */} <div className="rounded-lg border p-4 space-y-4"> <div className="flex items-center justify-between"> - <h3 className="text-sm font-semibold">검색 필터</h3> + <h3 className="text-sm font-semibold">Search Filter</h3> <Button variant="ghost" size="sm" @@ -481,14 +481,14 @@ export function SwpTableToolbar({ disabled={isRefreshing} > <X className="h-4 w-4 mr-1" /> - 초기화 + Reset </Button> </div> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"> {/* 프로젝트 번호 */} <div className="space-y-2"> - <Label htmlFor="projNo">프로젝트 번호</Label> + <Label htmlFor="projNo">Project Number</Label> {projects.length > 0 ? ( <Popover open={projectSearchOpen} onOpenChange={setProjectSearchOpen}> <PopoverTrigger asChild> @@ -507,7 +507,7 @@ export function SwpTableToolbar({ {"]"} </span> ) : ( - <span className="text-muted-foreground">프로젝트 선택</span> + <span className="text-muted-foreground">Select Project</span> )} <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" /> </Button> @@ -517,7 +517,7 @@ export function SwpTableToolbar({ <div className="flex items-center border rounded-md px-3"> <Search className="h-4 w-4 mr-2 opacity-50" /> <Input - placeholder="프로젝트 번호 또는 이름으로 검색..." + placeholder="Search by project number or name..." value={projectSearch} onChange={(e) => setProjectSearch(e.target.value)} className="border-0 focus-visible:ring-0 focus-visible:ring-offset-0" @@ -548,7 +548,7 @@ export function SwpTableToolbar({ ))} {filteredProjects.length === 0 && ( <div className="py-6 text-center text-sm text-muted-foreground"> - 검색 결과가 없습니다. + No search results. </div> )} </div> @@ -558,7 +558,7 @@ export function SwpTableToolbar({ ) : ( <Input id="projNo" - placeholder="계약된 프로젝트가 없습니다" + placeholder="No contracted projects" value={projNo} disabled className="bg-muted" @@ -568,10 +568,10 @@ export function SwpTableToolbar({ {/* 문서 번호 */} <div className="space-y-2"> - <Label htmlFor="docNo">문서 번호</Label> + <Label htmlFor="docNo">Document Number</Label> <Input id="docNo" - placeholder="문서 번호 검색" + placeholder="Search Document Number" value={localFilters.docNo || ""} onChange={(e) => setLocalFilters({ ...localFilters, docNo: e.target.value }) @@ -582,10 +582,10 @@ export function SwpTableToolbar({ {/* 문서 제목 */} <div className="space-y-2"> - <Label htmlFor="docTitle">문서 제목</Label> + <Label htmlFor="docTitle">Document Title</Label> <Input id="docTitle" - placeholder="제목 검색" + placeholder="Search Title" value={localFilters.docTitle || ""} onChange={(e) => setLocalFilters({ ...localFilters, docTitle: e.target.value }) @@ -596,10 +596,10 @@ export function SwpTableToolbar({ {/* 패키지 번호 */} <div className="space-y-2"> - <Label htmlFor="pkgNo">패키지</Label> + <Label htmlFor="pkgNo">Package</Label> <Input id="pkgNo" - placeholder="패키지 번호" + placeholder="Package Number" value={localFilters.pkgNo || ""} onChange={(e) => setLocalFilters({ ...localFilters, pkgNo: e.target.value }) @@ -610,10 +610,10 @@ export function SwpTableToolbar({ {/* 스테이지 */} <div className="space-y-2"> - <Label htmlFor="stage">스테이지</Label> + <Label htmlFor="stage">Stage</Label> <Input id="stage" - placeholder="스테이지 입력 (예: IFC, IFA)" + placeholder="Enter Stage (e.g. IFC, IFA)" value={localFilters.stage || ""} onChange={(e) => setLocalFilters({ ...localFilters, stage: e.target.value }) @@ -624,7 +624,7 @@ export function SwpTableToolbar({ {/* 상태 */} <div className="space-y-2"> - <Label htmlFor="status">상태</Label> + <Label htmlFor="status">Status</Label> <Input id="status" placeholder="ex. standby.." @@ -644,7 +644,7 @@ export function SwpTableToolbar({ disabled={isRefreshing} > <Search className={`h-4 w-4 mr-2 ${isRefreshing ? "animate-spin" : ""}`} /> - {isRefreshing ? "로딩 중..." : "검색"} + {isRefreshing ? "Loading..." : "Search"} </Button> </div> </div> diff --git a/lib/swp/table/swp-table.tsx b/lib/swp/table/swp-table.tsx index 6f810415..348f388b 100644 --- a/lib/swp/table/swp-table.tsx +++ b/lib/swp/table/swp-table.tsx @@ -47,7 +47,7 @@ export function SwpTable({ const statusMap = new Map<string, number>(); documents.forEach((doc) => { - const status = doc.LTST_ACTV_STAT || "UNKNOWN"; + const status = doc.LTST_ACTV_STAT || "Waiting"; statusMap.set(status, (statusMap.get(status) || 0) + 1); }); @@ -68,7 +68,7 @@ export function SwpTable({ if (!selectedStatus) { return documents; } - return documents.filter((doc) => doc.LTST_ACTV_STAT === selectedStatus); + return documents.filter((doc) => (doc.LTST_ACTV_STAT || "Waiting") === selectedStatus); }, [documents, selectedStatus]); const table = useReactTable({ @@ -95,7 +95,7 @@ export function SwpTable({ onClick={() => setSelectedStatus(null)} className="h-9" > - 전체 ({documents.length}) + All ({documents.length}) </Button> {statusCounts.map((statusCount) => ( <Button @@ -162,7 +162,7 @@ export function SwpTable({ ) : ( <TableRow> <TableCell colSpan={swpDocumentColumns.length} className="h-24 text-center"> - 데이터 없음 + No data </TableCell> </TableRow> )} diff --git a/lib/swp/table/swp-upload-result-dialog.tsx b/lib/swp/table/swp-upload-result-dialog.tsx index 06caf66e..803d00f0 100644 --- a/lib/swp/table/swp-upload-result-dialog.tsx +++ b/lib/swp/table/swp-upload-result-dialog.tsx @@ -35,9 +35,9 @@ export function SwpUploadResultDialog({ <Dialog open={open} onOpenChange={onOpenChange}> <DialogContent className="max-w-3xl max-h-[80vh] flex flex-col"> <DialogHeader> - <DialogTitle>파일 업로드 결과</DialogTitle> + <DialogTitle>File Upload Result</DialogTitle> <DialogDescription> - 총 {totalCount}개 파일 중 성공 {successCount}개, 실패 {failCount}개 + Total {totalCount} files: {successCount} succeeded, {failCount} failed </DialogDescription> </DialogHeader> @@ -70,16 +70,16 @@ export function SwpUploadResultDialog({ {result.success ? ( <p className="text-sm text-green-700 dark:text-green-300"> - 업로드 성공 + Upload Successful </p> ) : ( <div className="space-y-1"> <p className="text-sm font-medium text-red-700 dark:text-red-300"> - 업로드 실패 + Upload Failed </p> {result.error && ( <p className="text-sm text-red-600 dark:text-red-400 break-words"> - 사유: {result.error} + Reason: {result.error} </p> )} </div> @@ -94,11 +94,11 @@ export function SwpUploadResultDialog({ <div className="text-sm text-muted-foreground"> {failCount > 0 && ( <span className="text-red-600 dark:text-red-400 font-medium"> - 실패한 파일을 확인하고 다시 업로드해주세요. + Please check the failed files and try uploading again. </span> )} </div> - <Button onClick={() => onOpenChange(false)}>확인</Button> + <Button onClick={() => onOpenChange(false)}>Confirm</Button> </div> </DialogContent> </Dialog> diff --git a/lib/swp/table/swp-upload-validation-dialog.tsx b/lib/swp/table/swp-upload-validation-dialog.tsx index 803b1564..3357ec7a 100644 --- a/lib/swp/table/swp-upload-validation-dialog.tsx +++ b/lib/swp/table/swp-upload-validation-dialog.tsx @@ -69,7 +69,7 @@ export function validateFileName( if (lastDotIndex === -1) { return { valid: false, - error: "파일 확장자가 없습니다", + error: "File extension missing", }; } @@ -83,7 +83,7 @@ export function validateFileName( if (parts.length < 3) { return { valid: false, - error: `언더스코어(_)가 최소 2개 있어야 합니다 (현재: ${parts.length - 1}개). 형식: [OWN_DOC_NO]_[REV_NO]_[STAGE].[확장자]`, + error: `Must have at least 2 underscores (_) (Current: ${parts.length - 1}). Format: [OWN_DOC_NO]_[REV_NO]_[STAGE].[Extension]`, }; } @@ -99,21 +99,21 @@ export function validateFileName( if (!ownDocNo || ownDocNo.trim() === "") { return { valid: false, - error: "문서번호(OWN_DOC_NO)가 비어있습니다", + error: "Document Number (OWN_DOC_NO) is empty", }; } if (!revNo || revNo.trim() === "") { return { valid: false, - error: "리비전 번호(REV_NO)가 비어있습니다", + error: "Revision Number (REV_NO) is empty", }; } if (!stage || stage.trim() === "") { return { valid: false, - error: "스테이지(STAGE)가 비어있습니다", + error: "Stage (STAGE) is empty", }; } @@ -127,7 +127,7 @@ export function validateFileName( if (!availableDocNos || availableDocNos.length === 0) { return { valid: false, - error: "할당된 문서가 없거나 문서 목록 로드에 실패했습니다. 페이지를 새로고침하거나 관리자에게 문의하세요.", + error: "No assigned documents or failed to load document list. Please refresh the page or contact administrator.", }; } @@ -135,7 +135,7 @@ export function validateFileName( if (!availableDocNos.includes(trimmedDocNo)) { return { valid: false, - error: `문서번호 '${trimmedDocNo}'는 업로드 권한이 없습니다. 할당된 문서번호를 확인해주세요.`, + error: `Document number '${trimmedDocNo}' does not have upload permission. Please check assigned document numbers.`, }; } } @@ -152,7 +152,7 @@ export function validateFileName( // 문서가 EVCP DB에 등록되지 않음 return { valid: false, - error: `문서번호 '${trimmedDocNo}'는 문서 리스트에 등록되지 않았습니다. 먼저 문서 리스트를 제출해주세요.`, + error: `Document number '${trimmedDocNo}' is not registered in the document list. Please submit the document list first.`, }; } @@ -163,7 +163,7 @@ export function validateFileName( // Document Class에 Stage가 설정되지 않음 return { valid: false, - error: `문서 '${trimmedDocNo}'의 Document Class '${docCls}'에 Stage가 설정되지 않았습니다. 관리자에게 문의하세요.`, + error: `Stage is not set for Document Class '${docCls}' of document '${trimmedDocNo}'. Please contact administrator.`, }; } @@ -171,7 +171,7 @@ export function validateFileName( if (!allowedStages.includes(trimmedStage)) { return { valid: false, - error: `문서 '${trimmedDocNo}'의 Document Class '${docCls}'에서 Stage '${trimmedStage}'는 허용되지 않습니다. 허용된 Stage: ${allowedStages.join(", ")}`, + error: `Stage '${trimmedStage}' is not allowed for Document Class '${docCls}' of document '${trimmedDocNo}'. Allowed Stages: ${allowedStages.join(", ")}`, }; } @@ -181,7 +181,7 @@ export function validateFileName( console.log(`[validateFileName] 검증 정보가 없음 → 업로드 차단`); return { valid: false, - error: "문서 정보를 가져올 수 없습니다. 페이지를 새로고침하거나 프로젝트를 다시 선택해주세요.", + error: "Cannot retrieve document info. Please refresh the page or re-select the project.", }; } @@ -198,7 +198,7 @@ export function validateFileName( } catch (error) { return { valid: false, - error: error instanceof Error ? error.message : "알 수 없는 오류", + error: error instanceof Error ? error.message : "Unknown error", }; } } @@ -234,9 +234,9 @@ export function SwpUploadValidationDialog({ <Dialog open={open} onOpenChange={onOpenChange}> <DialogContent className="max-w-3xl max-h-[80vh] flex flex-col"> <DialogHeader className="flex-shrink-0"> - <DialogTitle>파일 업로드 검증</DialogTitle> + <DialogTitle>File Upload Validation</DialogTitle> <DialogDescription> - 선택한 파일의 파일명 형식을 검증합니다 + Validating file name format of selected files </DialogDescription> </DialogHeader> @@ -244,17 +244,17 @@ export function SwpUploadValidationDialog({ {/* 요약 통계 */} <div className="grid grid-cols-3 gap-4"> <div className="rounded-lg border p-3"> - <div className="text-sm text-muted-foreground">전체 파일</div> + <div className="text-sm text-muted-foreground">Total Files</div> <div className="text-2xl font-bold">{validationResults.length}</div> </div> <div className="rounded-lg border p-3 bg-green-50 dark:bg-green-950/30"> - <div className="text-sm text-green-600 dark:text-green-400">검증 성공</div> + <div className="text-sm text-green-600 dark:text-green-400">Validation Success</div> <div className="text-2xl font-bold text-green-600 dark:text-green-400"> {validFiles.length} </div> </div> <div className="rounded-lg border p-3 bg-red-50 dark:bg-red-950/30"> - <div className="text-sm text-red-600 dark:text-red-400">검증 실패</div> + <div className="text-sm text-red-600 dark:text-red-400">Validation Failed</div> <div className="text-2xl font-bold text-red-600 dark:text-red-400"> {invalidFiles.length} </div> @@ -266,8 +266,8 @@ export function SwpUploadValidationDialog({ <Alert variant="destructive"> <AlertCircle className="h-4 w-4" /> <AlertDescription> - {invalidFiles.length}개 파일의 파일명 형식이 올바르지 않습니다. - 검증에 성공한 {validFiles.length}개 파일만 업로드됩니다. + {invalidFiles.length} files have incorrect file name format. + Only {validFiles.length} successfully validated files will be uploaded. </AlertDescription> </Alert> )} @@ -276,7 +276,7 @@ export function SwpUploadValidationDialog({ <Alert variant="destructive"> <XCircle className="h-4 w-4" /> <AlertDescription> - 업로드 가능한 파일이 없습니다. 파일명 형식을 확인해주세요. + No uploadable files. Please check file name format. </AlertDescription> </Alert> )} @@ -289,7 +289,7 @@ export function SwpUploadValidationDialog({ <div className="space-y-2"> <h4 className="text-sm font-semibold text-green-600 dark:text-green-400 flex items-center gap-2"> <CheckCircle2 className="h-4 w-4" /> - 검증 성공 ({validFiles.length}개) + Validation Success ({validFiles.length}) </h4> {validFiles.map((result, index) => ( <div @@ -304,7 +304,7 @@ export function SwpUploadValidationDialog({ {result.parsed && ( <div className="flex flex-wrap gap-1 mt-2"> <Badge variant="outline" className="text-xs"> - 문서: {result.parsed.ownDocNo} + Doc: {result.parsed.ownDocNo} </Badge> <Badge variant="outline" className="text-xs"> Rev: {result.parsed.revNo} @@ -314,11 +314,11 @@ export function SwpUploadValidationDialog({ </Badge> {result.parsed.fileName && ( <Badge variant="outline" className="text-xs"> - 파일명: {result.parsed.fileName} + FileName: {result.parsed.fileName} </Badge> )} <Badge variant="outline" className="text-xs"> - 확장자: .{result.parsed.extension} + Ext: .{result.parsed.extension} </Badge> </div> )} @@ -335,7 +335,7 @@ export function SwpUploadValidationDialog({ <div className="space-y-2 mt-4"> <h4 className="text-sm font-semibold text-red-600 dark:text-red-400 flex items-center gap-2"> <XCircle className="h-4 w-4" /> - 검증 실패 ({invalidFiles.length}개) + Validation Failed ({invalidFiles.length}) </h4> {invalidFiles.map((result, index) => ( <div @@ -365,31 +365,31 @@ export function SwpUploadValidationDialog({ {/* 형식 안내 */} <div className="rounded-lg bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800 p-3"> <div className="text-sm font-medium text-blue-900 dark:text-blue-100 mb-1"> - 📋 올바른 파일명 형식 + 📋 Correct File Name Format </div> <code className="text-xs text-blue-700 dark:text-blue-300"> - [OWN_DOC_NO]_[REV_NO]_[STAGE].[확장자] + [OWN_DOC_NO]_[REV_NO]_[STAGE].[Extension] </code> <div className="text-xs text-blue-600 dark:text-blue-400 mt-1"> - 예: VD-DOC-001_01_IFA.pdf + Ex: VD-DOC-001_01_IFA.pdf </div> <div className="text-xs text-blue-600 dark:text-blue-400 mt-1"> - ※ 선택사항: [OWN_DOC_NO]_[REV_NO]_[STAGE]_[파일명].[확장자] (파일명 추가 가능) + ※ Optional: [OWN_DOC_NO]_[REV_NO]_[STAGE]_[FileName].[Extension] (FileName can be added) </div> <div className="text-xs text-blue-600 dark:text-blue-400 mt-1"> - ※ 파일명에는 언더스코어(_)가 포함될 수 있습니다. + ※ File name can contain underscores (_). </div> {isVendorMode && ( <> <div className="text-xs text-blue-600 dark:text-blue-400 mt-2 pt-2 border-t border-blue-200 dark:border-blue-800"> {availableDocNos.length > 0 ? ( - <>ℹ️ 업로드 가능한 문서: {availableDocNos.length}개</> + <>ℹ️ Uploadable Documents: {availableDocNos.length}</> ) : ( - <>⚠️ 할당된 문서가 없습니다</> + <>⚠️ No assigned documents</> )} </div> <div className="text-xs text-blue-600 dark:text-blue-400 mt-1"> - ⚠️ 각 문서의 Document Class에 정의된 Stage만 사용할 수 있습니다. + ⚠️ Only Stages defined in each document's Document Class can be used. </div> </> )} @@ -402,7 +402,7 @@ export function SwpUploadValidationDialog({ onClick={handleCancel} disabled={isUploading} > - 취소 + Cancel </Button> <Button onClick={handleUpload} @@ -411,12 +411,12 @@ export function SwpUploadValidationDialog({ {isUploading ? ( <> <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2" /> - 업로드 중... + Uploading... </> ) : ( <> <Upload className="h-4 w-4 mr-2" /> - 업로드 ({validFiles.length}개) + Upload ({validFiles.length}) </> )} </Button> diff --git a/lib/swp/table/swp-uploaded-files-dialog.tsx b/lib/swp/table/swp-uploaded-files-dialog.tsx index 14d69df4..e92cb0ad 100644 --- a/lib/swp/table/swp-uploaded-files-dialog.tsx +++ b/lib/swp/table/swp-uploaded-files-dialog.tsx @@ -90,8 +90,8 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi if (!projNo) { toast({ variant: "destructive", - title: "조회 불가", - description: "프로젝트 정보가 필요합니다.", + title: "Unable to retrieve files", + description: "Project information is required.", }); return; } @@ -102,15 +102,15 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi const result = await fetchVendorUploadedFiles(projNo); setFiles(result); toast({ - title: "조회 완료", - description: `${result.length}개의 파일을 조회했습니다.`, + title: "Files retrieved successfully", + description: `${result.length} files retrieved.`, }); } catch (error) { - console.error("파일 목록 조회 실패:", error); + console.error("Failed to retrieve files:", error); toast({ variant: "destructive", - title: "조회 실패", - description: error instanceof Error ? error.message : "알 수 없는 오류", + title: "Failed to retrieve files", + description: error instanceof Error ? error.message : "Unknown error", }); } }); @@ -121,8 +121,8 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi if (!file.BOX_SEQ || !file.ACTV_SEQ) { toast({ variant: "destructive", - title: "취소 불가", - description: "파일 정보가 올바르지 않습니다.", + title: "Unable to cancel file", + description: "File information is invalid.", }); return; } @@ -138,18 +138,18 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi }); toast({ - title: "취소 완료", - description: `${file.FILE_NM} 파일이 취소되었습니다.`, + title: "File canceled successfully", + description: `${file.FILE_NM} file canceled.`, }); // 목록 새로고침 loadFiles(); } catch (error) { - console.error("파일 취소 실패:", error); + console.error("Failed to cancel file:", error); toast({ variant: "destructive", - title: "취소 실패", - description: error instanceof Error ? error.message : "알 수 없는 오류", + title: "Failed to cancel file", + description: error instanceof Error ? error.message : "Unknown error", }); } finally { setCancellingFiles((prev) => { @@ -192,14 +192,14 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi <DialogTrigger asChild> <Button variant="outline" size="sm" disabled={!projNo}> <FileText className="h-4 w-4 mr-2" /> - 업로드 파일 관리 + Upload File Management </Button> </DialogTrigger> <DialogContent className="max-w-4xl max-h-[80vh]"> <DialogHeader> - <DialogTitle>업로드한 파일 목록</DialogTitle> + <DialogTitle>Uploaded File List</DialogTitle> <DialogDescription> - 프로젝트: {projNo} | 업체: {vndrCd} + Project: {projNo} | Company: {vndrCd} </DialogDescription> </DialogHeader> @@ -207,7 +207,7 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi {/* 액션 바 */} <div className="flex items-center justify-between"> <div className="text-sm text-muted-foreground"> - 총 {files.length}개 파일 + Total {files.length} files </div> <Button variant="outline" @@ -218,12 +218,12 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi {isLoading ? ( <> <Loader2 className="h-4 w-4 mr-2 animate-spin" /> - 조회 중... + Loading... </> ) : ( <> <RefreshCw className="h-4 w-4 mr-2" /> - 새로고침 + Refresh </> )} </Button> @@ -234,11 +234,11 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi {isLoading && files.length === 0 ? ( <div className="flex items-center justify-center h-full text-muted-foreground"> <Loader2 className="h-6 w-6 animate-spin mr-2" /> - 파일 목록을 조회하는 중... + Loading files... </div> ) : files.length === 0 ? ( <div className="flex items-center justify-center h-full text-muted-foreground"> - 업로드한 파일이 없습니다. + No files uploaded. </div> ) : ( <div className="space-y-2"> @@ -257,7 +257,7 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi <FileText className="h-4 w-4 text-blue-600 shrink-0" /> <span className="font-semibold">{docNo}</span> <Badge variant="outline" className="text-xs"> - {docNode.revisions.size}개 리비전 + {docNode.revisions.size} revisions </Badge> </div> @@ -280,7 +280,7 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi )} <span className="font-medium text-sm">Rev: {revNo}</span> <Badge variant="secondary" className="text-xs"> - {revNode.files.files.length}개 파일 + {revNode.files.files.length} files </Badge> </div> @@ -303,7 +303,7 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi <div className="flex items-center gap-2 text-xs text-muted-foreground"> <span>Stage: {file.STAGE}</span> <span>•</span> - <span>상태: {file.STAT_NM || file.STAT || "알 수 없음"}</span> + <span>Status: {file.STAT_NM || file.STAT || "Unknown"}</span> </div> </div> <Button @@ -313,19 +313,19 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi disabled={!isCancellable || isCancelling} title={ isCancellable - ? "파일 업로드 취소" - : `취소 불가 (상태: ${file.STAT_NM || file.STAT})` + ? "Cancel file upload" + : `Cancel not possible (Status: ${file.STAT_NM || file.STAT})` } > {isCancelling ? ( <> <Loader2 className="h-3 w-3 mr-1 animate-spin" /> - 취소 중... + Canceling... </> ) : ( <> <X className="h-3 w-3 mr-1" /> - 취소 + Cancel </> )} </Button> @@ -348,8 +348,8 @@ export function SwpUploadedFilesDialog({ projNo, vndrCd, userId }: SwpUploadedFi {/* 안내 메시지 */} <div className="rounded-lg bg-blue-50 dark:bg-blue-950/30 border border-blue-200 dark:border-blue-800 p-3"> <div className="text-xs text-blue-600 dark:text-blue-400 space-y-1"> - <p>ℹ️ 접수 전(SCW01) 상태의 파일만 취소할 수 있습니다.</p> - <p>ℹ️ 취소된 파일은 목록에서 제거됩니다.</p> + <p>Files in the 'SCW01' status can only be canceled.</p> + <p>Canceled files will be removed from the list.</p> </div> </div> </div> |
