summaryrefslogtreecommitdiff
path: root/lib/swp/table
diff options
context:
space:
mode:
authorjoonhoekim <26rote@gmail.com>2025-11-27 17:48:28 +0900
committerjoonhoekim <26rote@gmail.com>2025-11-27 17:48:28 +0900
commit95984e67b8d57fbe1431fcfedf3bb682f28416b3 (patch)
tree79953157e70b30c3c65ae52a01adb65fd4344bee /lib/swp/table
parent647e2e487238aed36ff9a880648e5c3e8725160f (diff)
(김준회) swp 영문 처리
Diffstat (limited to 'lib/swp/table')
-rw-r--r--lib/swp/table/swp-document-detail-dialog.tsx56
-rw-r--r--lib/swp/table/swp-help-dialog.tsx78
-rw-r--r--lib/swp/table/swp-inbox-document-detail-dialog.tsx80
-rw-r--r--lib/swp/table/swp-inbox-history-dialog.tsx40
-rw-r--r--lib/swp/table/swp-inbox-table-columns.tsx14
-rw-r--r--lib/swp/table/swp-inbox-table.tsx30
-rw-r--r--lib/swp/table/swp-note-dialog.tsx6
-rw-r--r--lib/swp/table/swp-revision-list-dialog.tsx28
-rw-r--r--lib/swp/table/swp-table-columns.tsx30
-rw-r--r--lib/swp/table/swp-table-toolbar.tsx90
-rw-r--r--lib/swp/table/swp-table.tsx8
-rw-r--r--lib/swp/table/swp-upload-result-dialog.tsx14
-rw-r--r--lib/swp/table/swp-upload-validation-dialog.tsx72
-rw-r--r--lib/swp/table/swp-uploaded-files-dialog.tsx62
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>