From c54a2445b6285d06c0ce3afa1cd3aa6aecf6de94 Mon Sep 17 00:00:00 2001 From: joonhoekim <26rote@gmail.com> Date: Mon, 24 Nov 2025 20:13:50 +0900 Subject: (김준회) dolce rebuild: i18n 지원 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/dolce/dialogs/add-detail-drawing-dialog.tsx | 85 ++++----- lib/dolce/dialogs/b4-bulk-upload-dialog.tsx | 109 +++++------ lib/dolce/dialogs/detail-drawing-dialog.tsx | 53 +++--- .../dialogs/upload-files-to-detail-dialog.tsx | 1 + lib/dolce/table/detail-drawing-columns.tsx | 119 ++++++++++++ lib/dolce/table/drawing-list-columns.tsx | 146 +++++++-------- lib/dolce/table/file-list-columns.tsx | 18 +- lib/dolce/table/gtt-drawing-list-columns.tsx | 79 ++++---- lib/dolce/utils/code-translator.ts | 208 +++++++++++++++++++++ lib/dolce/utils/date-formatter.ts | 54 ++++++ 10 files changed, 633 insertions(+), 239 deletions(-) create mode 100644 lib/dolce/utils/code-translator.ts create mode 100644 lib/dolce/utils/date-formatter.ts (limited to 'lib') diff --git a/lib/dolce/dialogs/add-detail-drawing-dialog.tsx b/lib/dolce/dialogs/add-detail-drawing-dialog.tsx index 2454b1ba..48614ecf 100644 --- a/lib/dolce/dialogs/add-detail-drawing-dialog.tsx +++ b/lib/dolce/dialogs/add-detail-drawing-dialog.tsx @@ -26,6 +26,12 @@ import { v4 as uuidv4 } from "uuid"; import { useFileUploadWithProgress } from "../hooks/use-file-upload-with-progress"; import { uploadFilesWithProgress } from "../utils/upload-with-progress"; import { FileUploadProgressList } from "../components/file-upload-progress-list"; +import { + getB3DrawingUsageOptions, + getB3RegisterKindOptions, + getB4DrawingUsageOptions, + getB4RegisterKindOptions +} from "../utils/code-translator"; interface AddDetailDrawingDialogProps { open: boolean; @@ -36,38 +42,10 @@ interface AddDetailDrawingDialogProps { userName: string; userEmail: string; onComplete: () => void; - drawingKind: "B3" | "B4"; // 추가 + drawingKind: "B3" | "B4"; + lng?: string; // i18n support } -// B3 벤더의 선택 옵션 -const B3_DRAWING_USAGE_OPTIONS = [ - { value: "APP", label: "APPROVAL (승인용)" }, - { value: "WOR", label: "WORKING (작업용)" }, -]; - -const B3_REGISTER_KIND_OPTIONS: Record> = { - APP: [ - { value: "APPR", label: "승인용 도면 (Full)", revisionRule: "예: A, B, C 또는 R00, R01, R02" }, - { value: "APPR-P", label: "승인용 도면 (Partial)", revisionRule: "예: A, B, C 또는 R00, R01, R02" }, - ], - WOR: [ - { value: "WORK", label: "작업용 입수도면 (Full)", revisionRule: "예: A, B, C 또는 R00, R01, R02" }, - { value: "WORK-P", label: "작업용 입수도면 (Partial)", revisionRule: "예: A, B, C 또는 R00, R01, R02" }, - ], -}; - -// B4 벤더(GTT)의 선택 옵션 -const B4_DRAWING_USAGE_OPTIONS = [ - { value: "REC", label: "RECEIVE (입수용)" }, -]; - -const B4_REGISTER_KIND_OPTIONS: Record> = { - REC: [ - { value: "RECP", label: "Pre. 도면입수", revisionRule: "예: R00, R01, R02, R03" }, - { value: "RECW", label: "Working 도면입수", revisionRule: "예: R00, R01, R02, R03" }, - ], -}; - export function AddDetailDrawingDialog({ open, onOpenChange, @@ -78,6 +56,7 @@ export function AddDetailDrawingDialog({ userEmail, onComplete, drawingKind, + lng = "ko", }: AddDetailDrawingDialogProps) { const [drawingUsage, setDrawingUsage] = useState(""); const [registerKind, setRegisterKind] = useState(""); @@ -85,6 +64,21 @@ export function AddDetailDrawingDialog({ const [revisionError, setRevisionError] = useState(""); const [isSubmitting, setIsSubmitting] = useState(false); + // 옵션 생성 (다국어 지원) + const drawingUsageOptions = drawingKind === "B3" + ? getB3DrawingUsageOptions(lng) + : getB4DrawingUsageOptions(lng); + + const registerKindOptions = drawingKind === "B3" + ? getB3RegisterKindOptions(drawingUsage, lng).map(opt => ({ + ...opt, + revisionRule: lng === "ko" ? "예: A, B, C 또는 R00, R01, R02" : "e.g. A, B, C or R00, R01, R02" + })) + : getB4RegisterKindOptions(drawingUsage, lng).map(opt => ({ + ...opt, + revisionRule: lng === "ko" ? "예: R00, R01, R02, R03" : "e.g. R00, R01, R02, R03" + })); + // 파일 업로드 훅 사용 (진행도 추적) const { fileProgresses, @@ -118,20 +112,13 @@ export function AddDetailDrawingDialog({ return "올바른 형식이 아닙니다 (A-Z 또는 R00-R99)"; }; - // Revision 입력 핸들러 (자동 변환 포함) + // 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]}`; - } - + const processedValue = value.toUpperCase(); setRevision(processedValue); // 값이 있을 때만 validation - if (processedValue) { + if (processedValue.trim()) { const error = validateRevision(processedValue); setRevisionError(error); } else { @@ -280,17 +267,17 @@ export function AddDetailDrawingDialog({ setRevisionError(""); }; - // 현재 선택 가능한 DrawingUsage 및 RegisterKind 옵션 - const drawingUsageOptions = drawingKind === "B4" ? B4_DRAWING_USAGE_OPTIONS : B3_DRAWING_USAGE_OPTIONS; - const registerKindOptionsMap = drawingKind === "B4" ? B4_REGISTER_KIND_OPTIONS : B3_REGISTER_KIND_OPTIONS; - - const registerKindOptions = drawingUsage - ? registerKindOptionsMap[drawingUsage] || [] - : []; - // 선택된 RegisterKind의 Revision Rule const revisionRule = registerKindOptions.find((opt) => opt.value === registerKind)?.revisionRule || ""; + // 추가 버튼 활성화 조건 + const isFormValid = + drawingUsage.trim() !== "" && + registerKind.trim() !== "" && + revision.trim() !== "" && + !revisionError && + files.length > 0; + return ( @@ -467,7 +454,7 @@ export function AddDetailDrawingDialog({ - diff --git a/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx b/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx index 75b221da..1be7f226 100644 --- a/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx +++ b/lib/dolce/dialogs/b4-bulk-upload-dialog.tsx @@ -22,6 +22,7 @@ import { import { FolderOpen, Loader2, ChevronRight, ChevronLeft, CheckCircle2 } from "lucide-react"; import { toast } from "sonner"; import { Progress } from "@/components/ui/progress"; +import { useTranslation } from "@/i18n/client"; import { validateB4FileName, B4UploadValidationDialog, @@ -44,20 +45,9 @@ interface B4BulkUploadDialogProps { userEmail: string; vendorCode: string; onUploadComplete?: () => void; + lng: string; } -// B4 GTT 옵션 -const B4_DRAWING_USAGE_OPTIONS = [ - { value: "REC", label: "RECEIVE (입수용)" }, -]; - -const B4_REGISTER_KIND_OPTIONS: Record> = { - REC: [ - { value: "RECP", label: "Pre. 도면입수" }, - { value: "RECW", label: "Working 도면입수" }, - ], -}; - type UploadStep = "settings" | "files" | "validation" | "uploading" | "complete"; export function B4BulkUploadDialog({ @@ -69,7 +59,9 @@ export function B4BulkUploadDialog({ userEmail, vendorCode, onUploadComplete, + lng, }: B4BulkUploadDialogProps) { + const { t } = useTranslation(lng, "dolce"); const [currentStep, setCurrentStep] = useState("settings"); const [drawingUsage, setDrawingUsage] = useState("REC"); const [registerKind, setRegisterKind] = useState(""); @@ -81,6 +73,18 @@ export function B4BulkUploadDialog({ const [uploadProgress, setUploadProgress] = useState(0); const [uploadResult, setUploadResult] = useState(null); + // B4 GTT 옵션 (코드 번역 유틸리티 사용) + const drawingUsageOptions = [ + { value: "REC", label: t("bulkUpload.drawingUsageReceive") }, + ]; + + const registerKindOptionsMap: Record> = { + REC: [ + { value: "RECP", label: t("bulkUpload.registerKindRecP") }, + { value: "RECW", label: t("bulkUpload.registerKindRecW") }, + ], + }; + // 다이얼로그 닫을 때 초기화 React.useEffect(() => { if (!open) { @@ -105,12 +109,12 @@ export function B4BulkUploadDialog({ const newFiles = files.filter((f) => !existingNames.has(f.name)); if (newFiles.length === 0) { - toast.error("이미 선택된 파일입니다"); + toast.error(t("bulkUpload.duplicateFileError")); return; } setSelectedFiles((prev) => [...prev, ...newFiles]); - toast.success(`${newFiles.length}개 파일이 선택되었습니다`); + toast.success(t("bulkUpload.filesSelectedSuccess", { count: newFiles.length })); }; // Drag & Drop 핸들러 @@ -153,7 +157,7 @@ export function B4BulkUploadDialog({ // 1단계 완료 (설정) const handleSettingsNext = () => { if (!registerKind) { - toast.error("등록종류를 선택하세요"); + toast.error(t("bulkUpload.selectRegisterKindError")); return; } setCurrentStep("files"); @@ -162,7 +166,7 @@ export function B4BulkUploadDialog({ // 2단계 완료 (파일 선택) const handleFilesNext = () => { if (selectedFiles.length === 0) { - toast.error("파일을 선택해주세요"); + toast.error(t("bulkUpload.selectFilesError")); return; } setCurrentStep("validation"); @@ -220,7 +224,7 @@ export function B4BulkUploadDialog({ return { ...parseResult, mappingStatus: "not_found" as const, - error: "DOLCE 시스템에서 도면을 찾을 수 없습니다", + error: t("validation.notFound"), }; } @@ -229,7 +233,7 @@ export function B4BulkUploadDialog({ return { ...parseResult, mappingStatus: "not_found" as const, - error: "해당 도면번호가 프로젝트에 등록되어 있지 않습니다", + error: t("validation.notRegistered"), }; } @@ -238,7 +242,7 @@ export function B4BulkUploadDialog({ return { ...parseResult, mappingStatus: "not_found" as const, - error: "도면입수(GTT Deliverables)인 도면만 업로드 가능합니다", + error: t("validation.notGttDeliverables"), }; } @@ -256,7 +260,7 @@ export function B4BulkUploadDialog({ } catch (error) { console.error("검증 실패:", error); toast.error( - error instanceof Error ? error.message : "검증 중 오류가 발생했습니다" + error instanceof Error ? error.message : t("bulkUpload.validationError") ); } }; @@ -393,11 +397,11 @@ export function B4BulkUploadDialog({ setUploadResult(result); setCurrentStep("complete"); - toast.success(`${successCount}/${validFiles.length}개 파일 업로드 완료`); + toast.success(t("bulkUpload.uploadSuccessToast", { successCount, total: validFiles.length })); } catch (error) { console.error("[B4 일괄 업로드] 실패:", error); toast.error( - error instanceof Error ? error.message : "업로드 중 오류가 발생했습니다" + error instanceof Error ? error.message : t("bulkUpload.uploadError") ); setCurrentStep("files"); } finally { @@ -406,7 +410,7 @@ export function B4BulkUploadDialog({ }; const registerKindOptions = drawingUsage - ? B4_REGISTER_KIND_OPTIONS[drawingUsage] || [] + ? registerKindOptionsMap[drawingUsage] || [] : []; const handleDrawingUsageChange = (value: string) => { @@ -419,11 +423,11 @@ export function B4BulkUploadDialog({ - B4 일괄 업로드 + {t("bulkUpload.title")} - {currentStep === "settings" && "업로드 설정을 선택하세요"} - {currentStep === "files" && "파일명 형식: [버림] [DrawingNo] [RevNo].[확장자] (예: testfile GTT-DE-007 R01.pdf)"} - {currentStep === "validation" && "파일 검증 중..."} + {currentStep === "settings" && t("bulkUpload.stepSettings")} + {currentStep === "files" && t("bulkUpload.stepFiles")} + {currentStep === "validation" && t("bulkUpload.stepValidation")} @@ -433,13 +437,13 @@ export function B4BulkUploadDialog({ <> {/* 도면용도 선택 */}
- + - + {registerKindOptions.map((option) => ( @@ -468,7 +472,7 @@ export function B4BulkUploadDialog({

- 선택한 등록종류가 모든 파일에 적용됩니다 + {t("bulkUpload.registerKindNote")}

@@ -514,11 +518,11 @@ export function B4BulkUploadDialog({ }`} > {isDragging - ? "파일을 여기에 놓으세요" - : "클릭하거나 파일을 드래그하여 선택"} + ? t("bulkUpload.fileDropHere") + : t("bulkUpload.fileSelectArea")}

- PDF, DOC, DOCX, XLS, XLSX, DWG, DXF, ZIP + {t("bulkUpload.fileTypes")}

@@ -528,14 +532,14 @@ export function B4BulkUploadDialog({

- 선택된 파일 ({selectedFiles.length}개) + {t("bulkUpload.selectedFiles", { count: selectedFiles.length })}

@@ -555,7 +559,7 @@ export function B4BulkUploadDialog({ size="sm" onClick={() => handleRemoveFile(index)} > - 제거 + {t("bulkUpload.removeFile")}
))} @@ -570,7 +574,7 @@ export function B4BulkUploadDialog({

- 파일 검증 중입니다... + {t("bulkUpload.validating")}

)} @@ -580,21 +584,21 @@ export function B4BulkUploadDialog({
-

파일 업로드 중...

+

{t("bulkUpload.uploading")}

- 잠시만 기다려주세요 + {t("bulkUpload.uploadingWait")}

- 진행률 + {t("bulkUpload.uploadProgress")} {uploadProgress}%
{/* 90% 이상일 때 추가 안내 메시지 */} {uploadProgress >= 90 && uploadProgress < 100 && (

- 서버에서 DOLCE API로 전송 중입니다... + {t("bulkUpload.uploadingToServer")}

)}
@@ -606,16 +610,16 @@ export function B4BulkUploadDialog({
-

업로드 완료!

+

{t("bulkUpload.uploadComplete")}

- {uploadResult.successCount}개 파일이 성공적으로 업로드되었습니다 + {t("bulkUpload.uploadSuccessMessage", { count: uploadResult.successCount })}

{uploadResult.failCount && uploadResult.failCount > 0 && (

- {uploadResult.failCount}개 파일 업로드 실패 + {t("bulkUpload.uploadFailMessage", { count: uploadResult.failCount })}

)} @@ -627,7 +631,7 @@ export function B4BulkUploadDialog({ onUploadComplete?.(); }} > - 확인 + {t("bulkUpload.confirmButton")}
@@ -643,13 +647,13 @@ export function B4BulkUploadDialog({ variant="outline" onClick={() => onOpenChange(false)} > - 취소 + {t("bulkUpload.cancelButton")} @@ -662,13 +666,13 @@ export function B4BulkUploadDialog({ onClick={() => setCurrentStep("settings")} > - 이전 + {t("bulkUpload.previousButton")} @@ -685,6 +689,7 @@ export function B4BulkUploadDialog({ validationResults={validationResults} onConfirmUpload={handleConfirmUpload} isUploading={isUploading} + lng={lng} /> ); diff --git a/lib/dolce/dialogs/detail-drawing-dialog.tsx b/lib/dolce/dialogs/detail-drawing-dialog.tsx index a06c9688..d9df58db 100644 --- a/lib/dolce/dialogs/detail-drawing-dialog.tsx +++ b/lib/dolce/dialogs/detail-drawing-dialog.tsx @@ -12,6 +12,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { Plus, RefreshCw, Upload, Loader2 } from "lucide-react"; import { toast } from "sonner"; +import { useTranslation } from "@/i18n/client"; import { UnifiedDwgReceiptItem, DetailDwgReceiptItem, @@ -20,7 +21,7 @@ import { fetchFileInfoList, } from "../actions"; import { DrawingListTable } from "../table/drawing-list-table"; -import { detailDrawingColumns } from "../table/detail-drawing-columns"; +import { createDetailDrawingColumns } from "../table/detail-drawing-columns"; import { createFileListColumns } from "../table/file-list-columns"; import { AddDetailDrawingDialog } from "./add-detail-drawing-dialog"; import { UploadFilesToDetailDialog } from "./upload-files-to-detail-dialog"; @@ -34,6 +35,7 @@ interface DetailDrawingDialogProps { userName: string; userEmail: string; drawingKind: "B3" | "B4"; + lng: string; } export function DetailDrawingDialog({ @@ -45,7 +47,9 @@ export function DetailDrawingDialog({ userName, userEmail, drawingKind, + lng, }: DetailDrawingDialogProps) { + const { t } = useTranslation(lng, "dolce"); const [detailDrawings, setDetailDrawings] = useState([]); const [selectedDetail, setSelectedDetail] = useState(null); const [files, setFiles] = useState([]); @@ -75,11 +79,11 @@ export function DetailDrawingDialog({ } } catch (error) { console.error("상세도면 로드 실패:", error); - toast.error("상세도면 로드에 실패했습니다"); + toast.error(t("detailDialog.detailLoadError")); } finally { setIsLoading(false); } - }, [drawing, selectedDetail]); + }, [drawing, selectedDetail, t]); // 파일 목록 로드 const loadFiles = useCallback(async () => { @@ -94,11 +98,11 @@ export function DetailDrawingDialog({ setFiles(data); } catch (error) { console.error("파일 목록 로드 실패:", error); - toast.error("파일 목록 로드에 실패했습니다"); + toast.error(t("detailDialog.fileLoadError")); } finally { setIsLoadingFiles(false); } - }, [selectedDetail]); + }, [selectedDetail, t]); // 다이얼로그 열릴 때 데이터 로드 useEffect(() => { @@ -120,7 +124,7 @@ export function DetailDrawingDialog({ const handleDownload = async (file: FileInfoItem) => { try { - toast.info("파일 다운로드를 준비 중입니다..."); + toast.info(t("detailDialog.downloadPreparing")); // 파일 생성자의 userId를 사용하여 다운로드 const response = await fetch("/api/dolce/download", { @@ -136,7 +140,7 @@ export function DetailDrawingDialog({ }); if (!response.ok) { - throw new Error("파일 다운로드 실패"); + throw new Error(t("detailDialog.downloadError")); } const blob = await response.blob(); @@ -149,10 +153,10 @@ export function DetailDrawingDialog({ window.URL.revokeObjectURL(url); document.body.removeChild(a); - toast.success("파일 다운로드가 완료되었습니다"); + toast.success(t("detailDialog.downloadSuccess")); } catch (error) { console.error("파일 다운로드 실패:", error); - toast.error("파일 다운로드에 실패했습니다"); + toast.error(t("detailDialog.downloadError")); } }; @@ -170,7 +174,7 @@ export function DetailDrawingDialog({ loadFiles(); }; - const fileColumns = createFileListColumns({ onDownload: handleDownload }); + const fileColumns = createFileListColumns({ onDownload: handleDownload, lng }); // RegisterId + UploadId 조합으로 고유 ID 생성 const getDetailDrawingId = (detail: DetailDwgReceiptItem) => { @@ -188,10 +192,15 @@ export function DetailDrawingDialog({ - 상세도면 정보 + {t("detailDialog.title")} {drawing && ( - {drawing.DrawingNo} | 프로젝트: {drawing.ProjectNo} | Discipline: {drawing.Discipline} | 종류: {drawing.DrawingKind} + {t("detailDialog.subtitle", { + drawingNo: drawing.DrawingNo, + projectNo: drawing.ProjectNo, + discipline: drawing.Discipline, + drawingKind: drawing.DrawingKind + })} )} @@ -201,7 +210,7 @@ export function DetailDrawingDialog({ {/* 상단: 상세도면 리스트 */} - 상세도면 목록 + {t("detailDialog.detailListTitle")}
{canAddDetailDrawing && ( )}
- columns={detailDrawingColumns} + columns={createDetailDrawingColumns(lng, t)} data={detailDrawings} onRowClick={setSelectedDetail} selectedRow={selectedDetail || undefined} @@ -239,8 +248,8 @@ export function DetailDrawingDialog({ - 첨부파일 목록 - {selectedDetail && ` - Rev. ${selectedDetail.DrawingRevNo}`} + {t("detailDialog.fileListTitle")} + {selectedDetail && t("detailDialog.fileListSubtitle", { revNo: selectedDetail.DrawingRevNo })} {selectedDetail && canAddDetailDrawing && ( )} {!selectedDetail ? (
- 상세도면을 선택하세요 + {t("detailDialog.selectDetailDrawing")}
) : isLoadingFiles ? (
- Loading files... + {t("detailDialog.loadingFiles")}
@@ -292,6 +301,7 @@ export function DetailDrawingDialog({ userEmail={userEmail} onComplete={handleAddComplete} drawingKind={drawingKind} + lng={lng} /> {selectedDetail && ( @@ -303,6 +313,7 @@ export function DetailDrawingDialog({ revNo={selectedDetail.DrawingRevNo} userId={userId} onUploadComplete={handleUploadComplete} + lng={lng} /> )} diff --git a/lib/dolce/dialogs/upload-files-to-detail-dialog.tsx b/lib/dolce/dialogs/upload-files-to-detail-dialog.tsx index 431a2c77..09f68614 100644 --- a/lib/dolce/dialogs/upload-files-to-detail-dialog.tsx +++ b/lib/dolce/dialogs/upload-files-to-detail-dialog.tsx @@ -26,6 +26,7 @@ interface UploadFilesToDetailDialogProps { revNo: string; userId: string; onUploadComplete?: () => void; + lng?: string; // i18n support } export function UploadFilesToDetailDialog({ diff --git a/lib/dolce/table/detail-drawing-columns.tsx b/lib/dolce/table/detail-drawing-columns.tsx index 7f519179..77d25953 100644 --- a/lib/dolce/table/detail-drawing-columns.tsx +++ b/lib/dolce/table/detail-drawing-columns.tsx @@ -2,7 +2,41 @@ import { ColumnDef } from "@tanstack/react-table"; import { DetailDwgReceiptItem } from "../actions"; +import { formatDolceDateTime } from "../utils/date-formatter"; +// DOLCE API ENM 필드가 제대로 번역되지 않아 직접 매핑 +const DRAWING_USAGE_MAP: Record = { + APP: { ko: "승인용", en: "Approval" }, + WOR: { ko: "작업용", en: "Working" }, + REC: { ko: "입수용 / GTT→SHI", en: "GTT→SHI" }, + SUB: { ko: "제출용 / SHI→GTT", en: "SHI→GTT" }, +}; + +const REGISTER_KIND_MAP: Record = { + 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)" }, + RECW: { ko: "Working 도면입수(GTT→SHI)", en: "Working Dwg(GTT→SHI)" }, + RECP: { ko: "Pre. 도면입수(GTT→SHI)", en: "Pre. Dwg(GTT→SHI)" }, +}; + +// 카테고리는 API에서 ENM이 제공되는 것으로 가정 (필요시 추가) +const translateDrawingUsage = (code: string | null, lng: string): string => { + if (!code) return ""; + const mapped = DRAWING_USAGE_MAP[code]; + if (!mapped) return code; + return lng === "en" ? mapped.en : mapped.ko; +}; + +const translateRegisterKind = (code: string | null, lng: string): string => { + if (!code) return ""; + const mapped = REGISTER_KIND_MAP[code]; + if (!mapped) return code; + return lng === "en" ? mapped.en : mapped.ko; +}; + +// 기본 컬럼 (기존 호환성 유지) export const detailDrawingColumns: ColumnDef[] = [ { accessorKey: "RegisterSerialNo", @@ -78,3 +112,88 @@ export const detailDrawingColumns: ColumnDef[] = [ }, ]; +// 다국어 지원 컬럼 생성 함수 +export function createDetailDrawingColumns( + lng: string, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + t: any +): ColumnDef[] { + return [ + { + accessorKey: "RegisterSerialNo", + header: t("detailDrawing.columns.serialNo"), + minSize: 80, + cell: ({ row }) => { + return
{row.getValue("RegisterSerialNo")}
; + }, + }, + { + accessorKey: "DrawingRevNo", + header: t("detailDrawing.columns.revNo"), + minSize: 100, + cell: ({ row }) => { + return
{row.getValue("DrawingRevNo")}
; + }, + }, + { + accessorKey: "Status", + header: t("detailDrawing.columns.status"), + minSize: 120, + cell: ({ row }) => { + return
{row.getValue("Status")}
; + }, + }, + { + accessorKey: "CategoryENM", + header: t("detailDrawing.columns.category"), + minSize: 120, + cell: ({ row }) => { + const categoryENM = row.getValue("CategoryENM") as string; + const categoryNM = row.original.CategoryNM; + // 영어인 경우 ENM, 한국어인 경우 NM 사용 + return
{lng === "en" ? (categoryENM || categoryNM) : (categoryNM || categoryENM)}
; + }, + }, + { + accessorKey: "DrawingUsageENM", + header: t("detailDrawing.columns.drawingUsage"), + minSize: 100, + cell: ({ row }) => { + // API의 ENM이 제대로 번역되지 않아 코드 값으로 직접 변환 + const usageCode = row.getValue("DrawingUsageENM") as string | null; + const translated = translateDrawingUsage(usageCode, lng); + return
{translated || usageCode || row.original.DrawingUsageNM}
; + }, + }, + { + accessorKey: "RegisterKindENM", + header: t("detailDrawing.columns.registerKind"), + minSize: 180, + cell: ({ row }) => { + // API의 ENM이 제대로 번역되지 않아 코드 값으로 직접 변환 + const kindCode = row.getValue("RegisterKindENM") as string | null; + const translated = translateRegisterKind(kindCode, lng); + return
{translated || kindCode || row.original.RegisterKindNM}
; + }, + }, + { + accessorKey: "CreateUserNM", + header: t("detailDrawing.columns.createdBy"), + minSize: 150, + cell: ({ row }) => { + const userENM = row.original.CreateUserENM; + const userNM = row.getValue("CreateUserNM") as string; + return
{lng === "en" ? (userENM || userNM) : (userNM || userENM)}
; + }, + }, + { + accessorKey: "CreateDt", + header: t("detailDrawing.columns.createdAt"), + minSize: 200, + cell: ({ row }) => { + const date = row.getValue("CreateDt") as string; + return
{formatDolceDateTime(date)}
; + }, + }, + ]; +} diff --git a/lib/dolce/table/drawing-list-columns.tsx b/lib/dolce/table/drawing-list-columns.tsx index 0e18266d..58631084 100644 --- a/lib/dolce/table/drawing-list-columns.tsx +++ b/lib/dolce/table/drawing-list-columns.tsx @@ -2,86 +2,86 @@ import { ColumnDef } from "@tanstack/react-table"; import { DwgReceiptItem } from "../actions"; +import { formatDolceDateYYYYMMDD, formatDolceDateTime } from "../utils/date-formatter"; -export const drawingListColumns: ColumnDef[] = [ - { - accessorKey: "DrawingNo", - header: "도면번호", - minSize: 200, - cell: ({ row }) => { - return
{row.getValue("DrawingNo")}
; +export function drawingListColumns(lng: string, t: any): ColumnDef[] { + return [ + { + accessorKey: "DrawingNo", + header: t("drawingList.columns.drawingNo"), + minSize: 200, + cell: ({ row }) => { + return
{row.getValue("DrawingNo")}
; + }, }, - }, - { - accessorKey: "DrawingName", - header: "도면명", - minSize: 400, - cell: ({ row }) => { - return
{row.getValue("DrawingName")}
; + { + accessorKey: "DrawingName", + header: t("drawingList.columns.drawingName"), + minSize: 600, + cell: ({ row }) => { + return
{row.getValue("DrawingName")}
; + }, }, - }, - { - accessorKey: "Discipline", - header: "설계공종", - minSize: 80, - }, - { - accessorKey: "Manager", - header: "담당자명", - minSize: 200, - cell: ({ row }) => { - const managerENM = row.original.ManagerENM; - const manager = row.getValue("Manager"); - return
{managerENM || manager}
; + { + accessorKey: "Discipline", + header: t("drawingList.columns.discipline"), + minSize: 80, }, - }, - { - accessorKey: "AppDwg_PlanDate", - header: "승인도면 예정일", - minSize: 140, - cell: ({ row }) => { - const date = row.getValue("AppDwg_PlanDate") as string; - if (!date || date.length !== 8) return null; - return `${date.substring(0, 4)}-${date.substring(4, 6)}-${date.substring(6, 8)}`; + { + accessorKey: "Manager", + header: t("drawingList.columns.manager"), + minSize: 200, + cell: ({ row }) => { + const managerENM = row.original.ManagerENM; + const manager = row.getValue("Manager"); + return
{managerENM || manager}
; + }, }, - }, - { - accessorKey: "AppDwg_ResultDate", - header: "승인도면 결과일", - minSize: 140, - cell: ({ row }) => { - const date = row.getValue("AppDwg_ResultDate") as string; - if (!date || date.length !== 8) return null; - return `${date.substring(0, 4)}-${date.substring(4, 6)}-${date.substring(6, 8)}`; + { + accessorKey: "AppDwg_PlanDate", + header: t("drawingList.columns.appDwgPlanDate"), + minSize: 140, + cell: ({ row }) => { + const date = row.getValue("AppDwg_PlanDate") as string; + return formatDolceDateYYYYMMDD(date); + }, }, - }, - { - accessorKey: "WorDwg_PlanDate", - header: "작업도면 예정일", - minSize: 140, - cell: ({ row }) => { - const date = row.getValue("WorDwg_PlanDate") as string; - if (!date || date.length !== 8) return null; - return `${date.substring(0, 4)}-${date.substring(4, 6)}-${date.substring(6, 8)}`; + { + accessorKey: "AppDwg_ResultDate", + header: t("drawingList.columns.appDwgResultDate"), + minSize: 140, + cell: ({ row }) => { + const date = row.getValue("AppDwg_ResultDate") as string; + return formatDolceDateYYYYMMDD(date); + }, }, - }, - { - accessorKey: "WorDwg_ResultDate", - header: "작업도면 결과일", - minSize: 140, - cell: ({ row }) => { - const date = row.getValue("WorDwg_ResultDate") as string; - if (!date || date.length !== 8) return null; - return `${date.substring(0, 4)}-${date.substring(4, 6)}-${date.substring(6, 8)}`; + { + accessorKey: "WorDwg_PlanDate", + header: t("drawingList.columns.worDwgPlanDate"), + minSize: 140, + cell: ({ row }) => { + const date = row.getValue("WorDwg_PlanDate") as string; + return formatDolceDateYYYYMMDD(date); + }, }, - }, - { - accessorKey: "CreateDt", - header: "생성일시", - minSize: 200, - cell: ({ row }) => { - return
{row.getValue("CreateDt")}
; + { + accessorKey: "WorDwg_ResultDate", + header: t("drawingList.columns.worDwgResultDate"), + minSize: 140, + cell: ({ row }) => { + const date = row.getValue("WorDwg_ResultDate") as string; + return formatDolceDateYYYYMMDD(date); + }, }, - }, -]; + { + accessorKey: "CreateDt", + header: t("drawingList.columns.createDt"), + minSize: 260, + cell: ({ row }) => { + const date = row.getValue("CreateDt") as string; + return
{formatDolceDateTime(date)}
; + }, + }, + ]; +} diff --git a/lib/dolce/table/file-list-columns.tsx b/lib/dolce/table/file-list-columns.tsx index f703d56d..36a579a3 100644 --- a/lib/dolce/table/file-list-columns.tsx +++ b/lib/dolce/table/file-list-columns.tsx @@ -4,17 +4,20 @@ import { ColumnDef } from "@tanstack/react-table"; import { FileInfoItem } from "../actions"; import { Button } from "@/components/ui/button"; import { Download } from "lucide-react"; +import { formatDolceDateTime } from "../utils/date-formatter"; interface FileListColumnsProps { onDownload: (file: FileInfoItem) => void; + lng?: string; } export const createFileListColumns = ({ onDownload, + lng = "ko", }: FileListColumnsProps): ColumnDef[] => [ { accessorKey: "FileSeq", - header: "순번", + header: lng === "ko" ? "순번" : "No.", minSize: 80, cell: ({ row }) => { return
{row.getValue("FileSeq")}
; @@ -22,7 +25,7 @@ export const createFileListColumns = ({ }, { accessorKey: "FileName", - header: "파일명", + header: lng === "ko" ? "파일명" : "File Name", minSize: 300, cell: ({ row }) => { return
{row.getValue("FileName")}
; @@ -30,7 +33,7 @@ export const createFileListColumns = ({ }, { accessorKey: "FileSize", - header: "파일크기", + header: lng === "ko" ? "파일크기" : "File Size", minSize: 100, cell: ({ row }) => { const size = parseInt(row.getValue("FileSize") as string); @@ -43,15 +46,16 @@ export const createFileListColumns = ({ }, { accessorKey: "CreateDt", - header: "생성일시", + header: lng === "ko" ? "생성일시" : "Created Date", minSize: 200, cell: ({ row }) => { - return
{row.getValue("CreateDt")}
; + const date = row.getValue("CreateDt") as string; + return
{formatDolceDateTime(date)}
; }, }, { id: "actions", - header: "다운로드", + header: lng === "ko" ? "다운로드" : "Download", minSize: 120, cell: ({ row }) => { return ( @@ -61,7 +65,7 @@ export const createFileListColumns = ({ onClick={() => onDownload(row.original)} > - 다운로드 + {lng === "ko" ? "다운로드" : "Download"} ); }, diff --git a/lib/dolce/table/gtt-drawing-list-columns.tsx b/lib/dolce/table/gtt-drawing-list-columns.tsx index 2ff2d7e2..093fc10c 100644 --- a/lib/dolce/table/gtt-drawing-list-columns.tsx +++ b/lib/dolce/table/gtt-drawing-list-columns.tsx @@ -2,27 +2,27 @@ import { ColumnDef } from "@tanstack/react-table"; import { GttDwgReceiptItem } from "../actions"; - -// 날짜 포맷 헬퍼 -function formatDate(dateStr: string | null): string | null { - if (!dateStr || dateStr.length !== 8) return null; - return `${dateStr.substring(0, 4)}-${dateStr.substring(4, 6)}-${dateStr.substring(6, 8)}`; -} +import { translateDrawingMoveGbn } from "../utils/code-translator"; +import { formatDolceDateYYYYMMDD, formatDolceDateTime } from "../utils/date-formatter"; // Document Type 필터 export type DocumentType = "ALL" | "GTT_DELIVERABLES" | "SHI_INPUT"; interface GttDrawingListColumnsOptions { documentType: DocumentType; + lng: string; + t: any; } export function createGttDrawingListColumns({ documentType, + lng, + t, }: GttDrawingListColumnsOptions): ColumnDef[] { const baseColumns: ColumnDef[] = [ { accessorKey: "DrawingNo", - header: "도면번호", + header: t("drawingList.columns.drawingNo"), minSize: 200, cell: ({ row }) => { return
{row.getValue("DrawingNo")}
; @@ -30,7 +30,7 @@ export function createGttDrawingListColumns({ }, { accessorKey: "DrawingName", - header: "도면명", + header: t("drawingList.columns.drawingName"), minSize: 400, cell: ({ row }) => { return
{row.getValue("DrawingName")}
; @@ -38,12 +38,12 @@ export function createGttDrawingListColumns({ }, { accessorKey: "Discipline", - header: "설계공종", + header: t("drawingList.columns.discipline"), minSize: 80, }, { accessorKey: "Manager", - header: "담당자명", + header: t("drawingList.columns.manager"), minSize: 200, cell: ({ row }) => { const managerENM = row.original.ManagerENM; @@ -53,8 +53,12 @@ export function createGttDrawingListColumns({ }, { accessorKey: "DrawingMoveGbn", - header: "구분", + header: t("drawingList.columns.category"), minSize: 120, + cell: ({ row }) => { + const value = row.getValue("DrawingMoveGbn") as string; + return
{translateDrawingMoveGbn(value, lng)}
; + }, }, ]; @@ -66,39 +70,39 @@ export function createGttDrawingListColumns({ dateColumns.push( { accessorKey: "GTTInput_PlanDate", - header: "GTT Input 예정일", + header: t("drawingList.columns.gttInputPlanDate"), minSize: 150, - cell: ({ row }) => formatDate(row.getValue("GTTInput_PlanDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTInput_PlanDate")), }, { accessorKey: "GTTInput_ResultDate", - header: "GTT Input 결과일", + header: t("drawingList.columns.gttInputResultDate"), minSize: 150, - cell: ({ row }) => formatDate(row.getValue("GTTInput_ResultDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTInput_ResultDate")), }, { accessorKey: "GTTPreDwg_PlanDate", - header: "GTT Pre 예정일", + header: t("drawingList.columns.gttPreDwgPlanDate"), minSize: 140, - cell: ({ row }) => formatDate(row.getValue("GTTPreDwg_PlanDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTPreDwg_PlanDate")), }, { accessorKey: "GTTPreDwg_ResultDate", - header: "GTT Pre 결과일", + header: t("drawingList.columns.gttPreDwgResultDate"), minSize: 140, - cell: ({ row }) => formatDate(row.getValue("GTTPreDwg_ResultDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTPreDwg_ResultDate")), }, { accessorKey: "GTTWorkingDwg_PlanDate", - header: "GTT Working 예정일", + header: t("drawingList.columns.gttWorkingDwgPlanDate"), minSize: 160, - cell: ({ row }) => formatDate(row.getValue("GTTWorkingDwg_PlanDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTWorkingDwg_PlanDate")), }, { accessorKey: "GTTWorkingDwg_ResultDate", - header: "GTT Working 결과일", + header: t("drawingList.columns.gttWorkingDwgResultDate"), minSize: 160, - cell: ({ row }) => formatDate(row.getValue("GTTWorkingDwg_ResultDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTWorkingDwg_ResultDate")), } ); } @@ -107,15 +111,15 @@ export function createGttDrawingListColumns({ dateColumns.push( { accessorKey: "GTTInput_PlanDate", - header: "Input 예정일", + header: t("drawingList.columns.inputPlanDate"), minSize: 120, - cell: ({ row }) => formatDate(row.getValue("GTTInput_PlanDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTInput_PlanDate")), }, { accessorKey: "GTTInput_ResultDate", - header: "Input 결과일", + header: t("drawingList.columns.inputResultDate"), minSize: 120, - cell: ({ row }) => formatDate(row.getValue("GTTInput_ResultDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTInput_ResultDate")), } ); } @@ -124,27 +128,27 @@ export function createGttDrawingListColumns({ dateColumns.push( { accessorKey: "GTTPreDwg_PlanDate", - header: "Pre 예정일", + header: t("drawingList.columns.prePlanDate"), minSize: 120, - cell: ({ row }) => formatDate(row.getValue("GTTPreDwg_PlanDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTPreDwg_PlanDate")), }, { accessorKey: "GTTPreDwg_ResultDate", - header: "Pre 결과일", + header: t("drawingList.columns.preResultDate"), minSize: 120, - cell: ({ row }) => formatDate(row.getValue("GTTPreDwg_ResultDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTPreDwg_ResultDate")), }, { accessorKey: "GTTWorkingDwg_PlanDate", - header: "Working 예정일", + header: t("drawingList.columns.workingPlanDate"), minSize: 130, - cell: ({ row }) => formatDate(row.getValue("GTTWorkingDwg_PlanDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTWorkingDwg_PlanDate")), }, { accessorKey: "GTTWorkingDwg_ResultDate", - header: "Working 결과일", + header: t("drawingList.columns.workingResultDate"), minSize: 130, - cell: ({ row }) => formatDate(row.getValue("GTTWorkingDwg_ResultDate")), + cell: ({ row }) => formatDolceDateYYYYMMDD(row.getValue("GTTWorkingDwg_ResultDate")), } ); } @@ -153,10 +157,11 @@ export function createGttDrawingListColumns({ const endColumns: ColumnDef[] = [ { accessorKey: "CreateDt", - header: "생성일시", + header: t("drawingList.columns.createDt"), minSize: 200, cell: ({ row }) => { - return
{row.getValue("CreateDt")}
; + const date = row.getValue("CreateDt") as string; + return
{formatDolceDateTime(date)}
; }, }, ]; 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> = { + "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> = { + "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> = { + "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> = { + "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> = { + "도면입수": { + 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); +} -- cgit v1.2.3