summaryrefslogtreecommitdiff
path: root/lib/dolce/utils
diff options
context:
space:
mode:
Diffstat (limited to 'lib/dolce/utils')
-rw-r--r--lib/dolce/utils/code-translator.ts208
-rw-r--r--lib/dolce/utils/date-formatter.ts54
-rw-r--r--lib/dolce/utils/upload-with-progress.ts75
3 files changed, 323 insertions, 14 deletions
diff --git a/lib/dolce/utils/code-translator.ts b/lib/dolce/utils/code-translator.ts
new file mode 100644
index 00000000..19cb4217
--- /dev/null
+++ b/lib/dolce/utils/code-translator.ts
@@ -0,0 +1,208 @@
+/**
+ * DOLCE 코드 값 번역 유틸리티
+ *
+ * 코드 값을 다국어로 번역하고, 검색 시 번역된 텍스트도 매칭할 수 있도록 지원
+ */
+
+// B3 DrawingUsage 번역
+export function translateB3DrawingUsage(code: string, lng: string): string {
+ const translations: Record<string, Record<string, string>> = {
+ "Approval": {
+ ko: "승인용",
+ en: "Approval",
+ },
+ "Working": {
+ ko: "작업용",
+ en: "Working",
+ },
+ "APP": {
+ ko: "승인용",
+ en: "Approval",
+ },
+ "WOR": {
+ ko: "작업용",
+ en: "Working",
+ },
+ };
+
+ return translations[code]?.[lng] || code;
+}
+
+// B3 RegisterKind 번역
+export function translateB3RegisterKind(code: string, lng: string): string {
+ const translations: Record<string, Record<string, string>> = {
+ "APPR": {
+ ko: "승인 제출용 도면(Full)",
+ en: "For Approval(Full)",
+ },
+ "APPP": {
+ ko: "승인 제출용 도면(Partial)",
+ en: "For Approval(Partial)",
+ },
+ "WORK": {
+ ko: "작업용 입수도면(Full)",
+ en: "For Working(Full)",
+ },
+ "WORP": {
+ ko: "작업용 입수도면(Partial)",
+ en: "For Working(Partial)",
+ },
+ };
+
+ return translations[code]?.[lng] || code;
+}
+
+// B4 DrawingUsage 번역
+export function translateB4DrawingUsage(code: string, lng: string): string {
+ const translations: Record<string, Record<string, string>> = {
+ "REC": {
+ ko: "입수용",
+ en: "GTT→SHI",
+ },
+ "SUB": {
+ ko: "제출용",
+ en: "SHI→GTT",
+ },
+ };
+
+ return translations[code]?.[lng] || code;
+}
+
+// B4 RegisterKind 번역
+export function translateB4RegisterKind(code: string, lng: string): string {
+ const translations: Record<string, Record<string, string>> = {
+ "RECW": {
+ ko: "Working 도면입수(GTT→SHI)",
+ en: "Working Dwg(GTT→SHI)",
+ },
+ "RECP": {
+ ko: "Pre. 도면입수(GTT→SHI)",
+ en: "Pre. Dwg(GTT→SHI)",
+ },
+ "SUBW": {
+ ko: "Working 제출용(SHI→GTT)",
+ en: "Working Submission(SHI→GTT)",
+ },
+ "SUBP": {
+ ko: "Pre. 제출용(SHI→GTT)",
+ en: "Pre. Submission(SHI→GTT)",
+ },
+ };
+
+ return translations[code]?.[lng] || code;
+}
+
+// DrawingMoveGbn 번역 (B4 GTT)
+export function translateDrawingMoveGbn(code: string, lng: string): string {
+ const translations: Record<string, Record<string, string>> = {
+ "도면입수": {
+ ko: "도면입수",
+ en: "Receipt",
+ },
+ "도면제출": {
+ ko: "도면제출",
+ en: "Submission",
+ },
+ };
+
+ return translations[code]?.[lng] || code;
+}
+
+// 통합 번역 함수
+export function translateDolceCode(
+ codeType: "B3_DrawingUsage" | "B3_RegisterKind" | "B4_DrawingUsage" | "B4_RegisterKind" | "DrawingMoveGbn",
+ code: string,
+ lng: string
+): string {
+ switch (codeType) {
+ case "B3_DrawingUsage":
+ return translateB3DrawingUsage(code, lng);
+ case "B3_RegisterKind":
+ return translateB3RegisterKind(code, lng);
+ case "B4_DrawingUsage":
+ return translateB4DrawingUsage(code, lng);
+ case "B4_RegisterKind":
+ return translateB4RegisterKind(code, lng);
+ case "DrawingMoveGbn":
+ return translateDrawingMoveGbn(code, lng);
+ default:
+ return code;
+ }
+}
+
+// 검색용: 코드와 번역된 텍스트 모두 매칭
+export function matchesTranslatedCode(
+ codeType: "B3_DrawingUsage" | "B3_RegisterKind" | "B4_DrawingUsage" | "B4_RegisterKind" | "DrawingMoveGbn",
+ code: string,
+ searchTerm: string,
+ lng: string
+): boolean {
+ if (!searchTerm) return true;
+
+ const normalizedSearch = searchTerm.toLowerCase();
+
+ // 원본 코드로 검색
+ if (code.toLowerCase().includes(normalizedSearch)) {
+ return true;
+ }
+
+ // 번역된 텍스트로 검색
+ const translated = translateDolceCode(codeType, code, lng);
+ if (translated.toLowerCase().includes(normalizedSearch)) {
+ return true;
+ }
+
+ // 다른 언어의 번역도 검색 (한국어 사용자가 영어로 검색하는 경우)
+ const otherLng = lng === "ko" ? "en" : "ko";
+ const otherTranslated = translateDolceCode(codeType, code, otherLng);
+ if (otherTranslated.toLowerCase().includes(normalizedSearch)) {
+ return true;
+ }
+
+ return false;
+}
+
+// 옵션 목록 생성 (Select 컴포넌트용)
+export function getB3DrawingUsageOptions(lng: string) {
+ return [
+ { value: "APP", label: translateB3DrawingUsage("APP", lng) },
+ { value: "WOR", label: translateB3DrawingUsage("WOR", lng) },
+ ];
+}
+
+export function getB3RegisterKindOptions(drawingUsage: string, lng: string) {
+ if (drawingUsage === "APP") {
+ return [
+ { value: "APPR", label: translateB3RegisterKind("APPR", lng) },
+ { value: "APPP", label: translateB3RegisterKind("APPP", lng) },
+ ];
+ } else if (drawingUsage === "WOR") {
+ return [
+ { value: "WORK", label: translateB3RegisterKind("WORK", lng) },
+ { value: "WORP", label: translateB3RegisterKind("WORP", lng) },
+ ];
+ }
+ return [];
+}
+
+export function getB4DrawingUsageOptions(lng: string) {
+ return [
+ { value: "REC", label: translateB4DrawingUsage("REC", lng) },
+ ];
+}
+
+export function getB4RegisterKindOptions(drawingUsage: string, lng: string) {
+ if (drawingUsage === "REC") {
+ return [
+ { value: "RECP", label: translateB4RegisterKind("RECP", lng) },
+ { value: "RECW", label: translateB4RegisterKind("RECW", lng) },
+ ];
+ } else if (drawingUsage === "SUB") {
+ return [
+ { value: "SUBP", label: translateB4RegisterKind("SUBP", lng) },
+ { value: "SUBW", label: translateB4RegisterKind("SUBW", lng) },
+ ];
+ }
+ return [];
+}
+
diff --git a/lib/dolce/utils/date-formatter.ts b/lib/dolce/utils/date-formatter.ts
new file mode 100644
index 00000000..83e78b0d
--- /dev/null
+++ b/lib/dolce/utils/date-formatter.ts
@@ -0,0 +1,54 @@
+/**
+ * DOLCE 날짜 포맷팅 유틸리티
+ *
+ * SWP의 날짜 포맷팅 함수를 재사용
+ * 모든 날짜는 KST (Korea Standard Time, GMT+9) 타임존
+ */
+
+import { formatSwpDate } from "@/lib/swp/utils";
+
+/**
+ * SWP와 동일한 방식
+ */
+export function formatDolceDateTime(dateStr: string | null): string {
+ if (!dateStr) return "-";
+ return formatSwpDate(dateStr);
+}
+
+/**
+ * YYYYMMDD 형식을 YYYY-MM-DD로 변환
+ *
+ * @param dateStr "20170220" 형식
+ * @returns "2017-02-20"
+ */
+export function formatDolceDateYYYYMMDD(dateStr: string | null): string | null {
+ if (!dateStr || dateStr.length !== 8) return null;
+
+ try {
+ const year = dateStr.substring(0, 4);
+ const month = dateStr.substring(4, 6);
+ const day = dateStr.substring(6, 8);
+
+ return `${year}-${month}-${day}`;
+ } catch {
+ return dateStr;
+ }
+}
+
+/**
+ * 통합 날짜 포맷팅
+ *
+ * @param dateStr 날짜 문자열 (다양한 형식 지원)
+ * @returns 포맷팅된 날짜 문자열
+ */
+export function formatDolceDate(dateStr: string | null): string {
+ if (!dateStr) return "-";
+
+ // YYYYMMDD 형식 (8자리 숫자)
+ if (/^\d{8}$/.test(dateStr)) {
+ return formatDolceDateYYYYMMDD(dateStr) || dateStr;
+ }
+
+ // 날짜+시간 형식
+ return formatDolceDateTime(dateStr);
+}
diff --git a/lib/dolce/utils/upload-with-progress.ts b/lib/dolce/utils/upload-with-progress.ts
index 8e36afe4..c86ed8a0 100644
--- a/lib/dolce/utils/upload-with-progress.ts
+++ b/lib/dolce/utils/upload-with-progress.ts
@@ -40,24 +40,31 @@ export async function uploadFilesWithProgress({
});
const xhr = new XMLHttpRequest();
+
+ // 타임아웃 설정 (1시간)
+ xhr.timeout = 3600000; // 1시간 (밀리초)
- // 전체 업로드 진행도 (단순화: 전체 진행도를 각 파일에 분배)
+ // 전체 업로드 진행도
+ // 주의: xhr.upload.progress는 클라이언트→서버 전송만 추적
+ // 서버에서 DOLCE API로 재업로드하는 과정은 별도 (Node.js fetch는 업로드 진행도 추적 미지원)
+ // → UI에서 90% 이상일 때 "서버에서 DOLCE API로 전송 중..." 메시지 표시
xhr.upload.addEventListener("progress", (event) => {
if (event.lengthComputable) {
- const totalProgress = (event.loaded / event.total) * 100;
+ // 전송 완료 = 서버에 도착 (실제 DOLCE API 업로드 시작)
+ // 서버 처리를 위해 최대 95%까지만 표시 (나머지 5%는 서버→DOLCE 업로드)
+ const totalProgress = Math.min((event.loaded / event.total) * 95, 95);
// 현재 업로드 중인 파일 인덱스 추정
- const filesCompleted = Math.floor((totalProgress / 100) * files.length);
+ const filesCompleted = Math.floor((totalProgress / 95) * files.length);
const currentFileIndex = Math.min(filesCompleted, files.length - 1);
// 각 파일별 진행도 계산
files.forEach((_, index) => {
if (index < filesCompleted) {
- callbacks.onProgress(index, 100);
- callbacks.onFileComplete(index);
+ callbacks.onProgress(index, 95);
} else if (index === currentFileIndex) {
- const fileProgress = ((totalProgress / 100) * files.length - filesCompleted) * 100;
- callbacks.onProgress(index, Math.min(fileProgress, 99));
+ const fileProgress = ((totalProgress / 95) * files.length - filesCompleted) * 95;
+ callbacks.onProgress(index, Math.min(fileProgress, 94));
} else {
callbacks.onProgress(index, 0);
}
@@ -70,15 +77,35 @@ export async function uploadFilesWithProgress({
try {
const response = JSON.parse(xhr.responseText);
- // 모든 파일 완료 처리
- files.forEach((_, index) => {
- callbacks.onProgress(index, 100);
- callbacks.onFileComplete(index);
- });
+ // 서버 응답 검증
+ if (response.success) {
+ console.log(`[업로드 클라이언트] 서버 처리 완료: ${response.uploadedCount}개 파일`);
+
+ // 서버에서 실제 처리 완료 시에만 100%
+ files.forEach((_, index) => {
+ callbacks.onProgress(index, 100);
+ callbacks.onFileComplete(index);
+ });
- resolve(response);
+ resolve(response);
+ } else {
+ // 서버에서 에러 응답
+ const errorMsg = response.error || "서버에서 업로드 실패";
+ console.error(`[업로드 클라이언트] 서버 에러:`, errorMsg);
+
+ files.forEach((_, index) => {
+ callbacks.onFileError(index, errorMsg);
+ });
+
+ resolve({
+ success: false,
+ error: errorMsg,
+ });
+ }
} catch (error) {
- const errorMsg = "응답 파싱 실패";
+ const errorMsg = `응답 파싱 실패: ${xhr.responseText?.substring(0, 100)}`;
+ console.error(`[업로드 클라이언트] 파싱 에러:`, error, xhr.responseText);
+
files.forEach((_, index) => {
callbacks.onFileError(index, errorMsg);
});
@@ -89,6 +116,8 @@ export async function uploadFilesWithProgress({
}
} else {
const errorMsg = `업로드 실패: ${xhr.status} ${xhr.statusText}`;
+ console.error(`[업로드 클라이언트] HTTP 에러:`, errorMsg, xhr.responseText);
+
files.forEach((_, index) => {
callbacks.onFileError(index, errorMsg);
});
@@ -101,6 +130,8 @@ export async function uploadFilesWithProgress({
xhr.addEventListener("error", () => {
const errorMsg = "네트워크 오류";
+ console.error(`[업로드 클라이언트] 네트워크 에러`);
+
files.forEach((_, index) => {
callbacks.onFileError(index, errorMsg);
});
@@ -112,6 +143,21 @@ export async function uploadFilesWithProgress({
xhr.addEventListener("abort", () => {
const errorMsg = "업로드가 취소되었습니다";
+ console.warn(`[업로드 클라이언트] 업로드 취소됨`);
+
+ files.forEach((_, index) => {
+ callbacks.onFileError(index, errorMsg);
+ });
+ resolve({
+ success: false,
+ error: errorMsg,
+ });
+ });
+
+ xhr.addEventListener("timeout", () => {
+ const errorMsg = "업로드 타임아웃 (1시간 초과)";
+ console.error(`[업로드 클라이언트] 타임아웃 발생 (1시간 초과)`);
+
files.forEach((_, index) => {
callbacks.onFileError(index, errorMsg);
});
@@ -121,6 +167,7 @@ export async function uploadFilesWithProgress({
});
});
+ console.log(`[업로드 클라이언트] 시작: ${files.length}개 파일`);
xhr.open("POST", "/api/dolce/upload-files");
xhr.send(formData);
});