From 57e3a696f4bdee665cf592463acf48ba83a6e2cd Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Mon, 24 Nov 2025 18:38:08 +0900
Subject: (김준회) dolce rebuild: 파일 업로드 개선, b4 일괄업로드 문서명 파싱
문제 해결
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
lib/dolce/components/file-upload-progress-list.tsx | 11 +++-
lib/dolce/dialogs/add-detail-drawing-dialog.tsx | 69 +++++++++++++++++++++-
lib/dolce/dialogs/b4-bulk-upload-dialog.tsx | 6 ++
lib/dolce/dialogs/b4-upload-validation-dialog.tsx | 30 ++++++----
lib/dolce/utils/upload-with-progress.ts | 7 ++-
5 files changed, 107 insertions(+), 16 deletions(-)
(limited to 'lib')
diff --git a/lib/dolce/components/file-upload-progress-list.tsx b/lib/dolce/components/file-upload-progress-list.tsx
index d54e9eaa..7354c85b 100644
--- a/lib/dolce/components/file-upload-progress-list.tsx
+++ b/lib/dolce/components/file-upload-progress-list.tsx
@@ -85,7 +85,16 @@ function FileUploadProgressItem({ fileProgress }: FileUploadProgressItemProps) {
{/* Progress Bar */}
{status === "uploading" && (
-
+ <>
+
+ {/* 90% 이상일 때 추가 안내 메시지 */}
+ {progress >= 90 && progress < 100 && (
+
+
+ 서버에서 DOLCE API로 전송 중...
+
+ )}
+ >
)}
{/* 에러 메시지 */}
diff --git a/lib/dolce/dialogs/add-detail-drawing-dialog.tsx b/lib/dolce/dialogs/add-detail-drawing-dialog.tsx
index 2f7f15a7..2454b1ba 100644
--- a/lib/dolce/dialogs/add-detail-drawing-dialog.tsx
+++ b/lib/dolce/dialogs/add-detail-drawing-dialog.tsx
@@ -82,6 +82,7 @@ export function AddDetailDrawingDialog({
const [drawingUsage, setDrawingUsage] = useState("");
const [registerKind, setRegisterKind] = useState("");
const [revision, setRevision] = useState("");
+ const [revisionError, setRevisionError] = useState("");
const [isSubmitting, setIsSubmitting] = useState(false);
// 파일 업로드 훅 사용 (진행도 추적)
@@ -96,11 +97,54 @@ export function AddDetailDrawingDialog({
isDragActive,
} = useFileUploadWithProgress();
+ // Revision 유효성 검증 함수
+ const validateRevision = (value: string): string => {
+ if (!value.trim()) {
+ return "Revision을 입력하세요";
+ }
+
+ const upperValue = value.toUpperCase().trim();
+
+ // A-Z 패턴 (단일 알파벳)
+ if (/^[A-Z]$/.test(upperValue)) {
+ return "";
+ }
+
+ // R00-R99 패턴
+ if (/^R\d{2}$/.test(upperValue)) {
+ return "";
+ }
+
+ return "올바른 형식이 아닙니다 (A-Z 또는 R00-R99)";
+ };
+
+ // Revision 입력 핸들러 (자동 변환 포함)
+ const handleRevisionChange = (value: string) => {
+ let processedValue = value.toUpperCase().trim();
+
+ // R1~R9 입력시 R01~R09로 자동 변환
+ const rNumberMatch = processedValue.match(/^R(\d)$/);
+ if (rNumberMatch) {
+ processedValue = `R0${rNumberMatch[1]}`;
+ }
+
+ setRevision(processedValue);
+
+ // 값이 있을 때만 validation
+ if (processedValue) {
+ const error = validateRevision(processedValue);
+ setRevisionError(error);
+ } else {
+ setRevisionError("");
+ }
+ };
+
// 폼 초기화
const resetForm = () => {
setDrawingUsage("");
setRegisterKind("");
setRevision("");
+ setRevisionError("");
clearFiles();
};
@@ -119,8 +163,18 @@ export function AddDetailDrawingDialog({
}
if (!revision.trim()) {
toast.error("Revision을 입력하세요");
+ setRevisionError("Revision을 입력하세요");
+ return;
+ }
+
+ // Revision 형식 검증
+ const revisionValidationError = validateRevision(revision);
+ if (revisionValidationError) {
+ toast.error(revisionValidationError);
+ setRevisionError(revisionValidationError);
return;
}
+
if (files.length === 0) {
toast.error("최소 1개 이상의 파일을 첨부해야 합니다");
return;
@@ -222,6 +276,8 @@ export function AddDetailDrawingDialog({
const handleDrawingUsageChange = (value: string) => {
setDrawingUsage(value);
setRegisterKind("");
+ setRevision("");
+ setRevisionError("");
};
// 현재 선택 가능한 DrawingUsage 및 RegisterKind 옵션
@@ -302,10 +358,21 @@ export function AddDetailDrawingDialog({
Revision
setRevision(e.target.value)}
+ onChange={(e) => handleRevisionChange(e.target.value)}
placeholder="예: A, B, R00, R01"
disabled={!registerKind}
+ className={revisionError ? "border-red-500 focus-visible:ring-red-500" : ""}
/>
+ {revisionError && (
+
+ {revisionError}
+
+ )}
+ {!revisionError && revision && (
+
+ ✓ 올바른 형식입니다
+
+ )}
{/* 파일 업로드 */}
diff --git a/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx b/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx
index cd336e92..75b221da 100644
--- a/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx
+++ b/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx
@@ -591,6 +591,12 @@ export function B4BulkUploadDialog({
{uploadProgress}%
+ {/* 90% 이상일 때 추가 안내 메시지 */}
+ {uploadProgress >= 90 && uploadProgress < 100 && (
+
+ 서버에서 DOLCE API로 전송 중입니다...
+
+ )}
)}
diff --git a/lib/dolce/dialogs/b4-upload-validation-dialog.tsx b/lib/dolce/dialogs/b4-upload-validation-dialog.tsx
index b274d604..f3a7c70a 100644
--- a/lib/dolce/dialogs/b4-upload-validation-dialog.tsx
+++ b/lib/dolce/dialogs/b4-upload-validation-dialog.tsx
@@ -39,8 +39,11 @@ interface B4UploadValidationDialogProps {
/**
* B4 파일명 검증 함수
- * 형식: [버림] [DrawingNo] [RevNo].[확장자]
- * 예시: "testfile GTT-DE-007 R01.pdf" → DrawingNo: "GTT-DE-007", RevNo: "R01"
+ * 형식: [버림] [문서번호토큰1] [문서번호토큰2] ... [리비전번호].[확장자]
+ * 예시: "testfile GTT DE 007 R01.pdf" → DrawingNo: "GTT-DE-007", RevNo: "R01"
+ * - 첫 번째 토큰은 버림
+ * - 마지막 토큰은 RevNo
+ * - 중간 토큰들을 "-"로 연결하여 DrawingNo 생성
*/
export function validateB4FileName(fileName: string): {
valid: boolean;
@@ -57,23 +60,25 @@ export function validateB4FileName(fileName: string): {
};
}
- const extension = fileName.substring(lastDotIndex + 1);
const nameWithoutExt = fileName.substring(0, lastDotIndex);
// 공백으로 분리
const parts = nameWithoutExt.split(" ").filter(p => p.trim() !== "");
- // 최소 3개 파트 필요: [버림], DrawingNo, RevNo
+ // 최소 3개 파트 필요: [버림], [문서번호토큰], [RevNo]
if (parts.length < 3) {
return {
valid: false,
- error: `공백이 최소 2개 있어야 합니다 (현재: ${parts.length - 1}개). 형식: [버림] [DrawingNo] [RevNo].[확장자]`,
+ error: `공백이 최소 2개 있어야 합니다 (현재: ${parts.length - 1}개). 형식: [버림] [문서번호토큰들...] [RevNo].[확장자]`,
};
}
// 첫 번째 토큰은 버림
- const drawingNo = parts[1];
- const revNo = parts[2];
+ // 마지막 토큰은 RevNo
+ // 중간 토큰들을 "-"로 연결하여 DrawingNo 생성
+ const revNo = parts[parts.length - 1];
+ const drawingTokens = parts.slice(1, parts.length - 1);
+ const drawingNo = drawingTokens.join("-");
// 필수 항목이 비어있지 않은지 확인
if (!drawingNo || drawingNo.trim() === "") {
@@ -307,16 +312,19 @@ export function B4UploadValidationDialog({
📋 올바른 파일명 형식
- [버림] [DrawingNo] [RevNo].[확장자]
+ [버림] [문서번호토큰1] [문서번호토큰2] ... [RevNo].[확장자]
- 예: testfile GTT-DE-007 R01.pdf
+ 예: testfile GTT DE 007 R01.pdf → DrawingNo: GTT-DE-007, Rev: R01
- ※ 첫 번째 단어는 무시되며, 공백으로 구분됩니다
+ ※ 첫 번째 단어는 무시됩니다
- ※ 네 번째 이상의 단어가 있으면 무시됩니다
+ ※ 마지막 단어는 리비전 번호(RevNo)입니다
+
+
+ ※ 중간의 모든 단어는 "-"로 연결되어 문서번호(DrawingNo)가 됩니다
diff --git a/lib/dolce/utils/upload-with-progress.ts b/lib/dolce/utils/upload-with-progress.ts
index 1204bf36..c86ed8a0 100644
--- a/lib/dolce/utils/upload-with-progress.ts
+++ b/lib/dolce/utils/upload-with-progress.ts
@@ -46,11 +46,12 @@ export async function uploadFilesWithProgress({
// 전체 업로드 진행도
// 주의: xhr.upload.progress는 클라이언트→서버 전송만 추적
- // 서버에서 DOLCE API로 재업로드하는 과정은 별도 (추적 불가)
+ // 서버에서 DOLCE API로 재업로드하는 과정은 별도 (Node.js fetch는 업로드 진행도 추적 미지원)
+ // → UI에서 90% 이상일 때 "서버에서 DOLCE API로 전송 중..." 메시지 표시
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
- // 전송 완료 = 서버에 도착 (실제 처리 시작)
- // 서버 처리를 위해 최대 95%까지만 표시
+ // 전송 완료 = 서버에 도착 (실제 DOLCE API 업로드 시작)
+ // 서버 처리를 위해 최대 95%까지만 표시 (나머지 5%는 서버→DOLCE 업로드)
const totalProgress = Math.min((event.loaded / event.total) * 95, 95);
// 현재 업로드 중인 파일 인덱스 추정
--
cgit v1.2.3