summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/ship
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-08-11 09:02:00 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-08-11 09:02:00 +0000
commitcbb4c7fe0b94459162ad5e998bc05cd293e0ff96 (patch)
tree0a26712f7685e4f6511e637b9a81269d90a47c8f /lib/vendor-document-list/ship
parenteb654f88214095f71be142b989e620fd28db3f69 (diff)
(대표님) 입찰, EDP 변경사항 대응, spreadJS 오류 수정, 벤더실사 수정
Diffstat (limited to 'lib/vendor-document-list/ship')
-rw-r--r--lib/vendor-document-list/ship/import-from-dolce-button.tsx152
-rw-r--r--lib/vendor-document-list/ship/send-to-shi-button.tsx127
2 files changed, 152 insertions, 127 deletions
diff --git a/lib/vendor-document-list/ship/import-from-dolce-button.tsx b/lib/vendor-document-list/ship/import-from-dolce-button.tsx
index 90796d8e..1ffe466d 100644
--- a/lib/vendor-document-list/ship/import-from-dolce-button.tsx
+++ b/lib/vendor-document-list/ship/import-from-dolce-button.tsx
@@ -25,6 +25,8 @@ import { SimplifiedDocumentsView } from "@/db/schema"
import { ImportStatus } from "../import-service"
import { useSession } from "next-auth/react"
import { getProjectIdsByVendor } from "../service"
+import { useParams } from "next/navigation"
+import { useTranslation } from "@/i18n/client"
// 🔥 API 응답 캐시 (컴포넌트 외부에 선언하여 인스턴스 간 공유)
const statusCache = new Map<string, { data: ImportStatus; timestamp: number }>()
@@ -69,6 +71,10 @@ export function ImportFromDOLCEButton({
const { data: session } = useSession()
const vendorId = session?.user.companyId
+ const params = useParams()
+ const lng = (params?.lng as string) || "ko"
+ const { t } = useTranslation(lng, "engineering")
+
// 🔥 allDocuments에서 projectIds 추출 (props로 전달받은 경우 사용)
const documentsProjectIds = React.useMemo(() => {
if (propProjectIds) return propProjectIds // props로 받은 경우 그대로 사용
@@ -182,44 +188,34 @@ export function ImportFromDOLCEButton({
} catch (error) {
console.error('Failed to fetch import statuses:', error)
- toast.error('Unable to check status. Please verify project settings.')
+ toast.error(t('dolceImport.messages.statusCheckError'))
} finally {
setStatusLoading(false)
}
- }, [debouncedProjectIds, fetchImportStatusCached])
+ }, [debouncedProjectIds, fetchImportStatusCached, t])
// 🔥 vendorId로 projects 가져오기 (최적화)
React.useEffect(() => {
- let isCancelled = false
-
- const fetchVendorProjects = async () => {
- if (allDocuments.length === 0 && vendorId && !loadingVendorProjects) {
- setLoadingVendorProjects(true)
- try {
- const projectIds = await getProjectIdsByVendor(vendorId)
- if (!isCancelled) {
- setVendorProjectIds(projectIds)
- }
- } catch (error) {
- console.error('Failed to fetch vendor projects:', error)
- if (!isCancelled) {
- toast.error('Failed to fetch project information.')
- }
- } finally {
- if (!isCancelled) {
- setLoadingVendorProjects(false)
- }
- }
- }
- }
-
- fetchVendorProjects()
-
- return () => {
- isCancelled = true
- }
- }, [allDocuments.length, vendorId, loadingVendorProjects])
-
+ let isCancelled = false;
+
+ if (allDocuments.length !== 0 || !vendorId) return;
+
+ setLoadingVendorProjects(true);
+
+ getProjectIdsByVendor(vendorId)
+ .then((projectIds) => {
+ if (!isCancelled) setVendorProjectIds(projectIds);
+ })
+ .catch((error) => {
+ if (!isCancelled) toast.error(t('dolceImport.messages.projectFetchError'));
+ })
+ .finally(() => {
+ if (!isCancelled) setLoadingVendorProjects(false);
+ });
+
+ return () => { isCancelled = true; };
+ }, [allDocuments, vendorId, t]);
+
// 🔥 컴포넌트 마운트 시 상태 조회 (디바운싱 적용)
React.useEffect(() => {
if (debouncedProjectIds.length > 0) {
@@ -314,16 +310,21 @@ export function ImportFromDOLCEButton({
if (totalResult.success) {
toast.success(
- `DOLCE import completed`,
+ t('dolceImport.messages.importSuccess'),
{
- description: `New ${totalResult.newCount}, Updated ${totalResult.updatedCount}, Skipped ${totalResult.skippedCount} (${projectIds.length} projects)`
+ description: t('dolceImport.messages.importSuccessDescription', {
+ newCount: totalResult.newCount,
+ updatedCount: totalResult.updatedCount,
+ skippedCount: totalResult.skippedCount,
+ projectCount: projectIds.length
+ })
}
)
} else {
toast.error(
- `DOLCE import partially failed`,
+ t('dolceImport.messages.importPartiallyFailed'),
{
- description: 'Some projects failed to import.'
+ description: t('dolceImport.messages.importPartiallyFailedDescription')
}
)
}
@@ -338,35 +339,35 @@ export function ImportFromDOLCEButton({
setImportProgress(0)
setIsImporting(false)
- toast.error('DOLCE import failed', {
- description: error instanceof Error ? error.message : 'An unknown error occurred.'
+ toast.error(t('dolceImport.messages.importFailed'), {
+ description: error instanceof Error ? error.message : t('dolceImport.messages.unknownError')
})
}
- }, [projectIds, fetchAllImportStatus, onImportComplete])
+ }, [projectIds, fetchAllImportStatus, onImportComplete, t])
// 🔥 상태 뱃지 메모이제이션
const statusBadge = React.useMemo(() => {
if (loadingVendorProjects) {
- return <Badge variant="secondary">Loading project information...</Badge>
+ return <Badge variant="secondary">{t('dolceImport.status.loadingProjectInfo')}</Badge>
}
if (statusLoading) {
- return <Badge variant="secondary">Checking DOLCE connection...</Badge>
+ return <Badge variant="secondary">{t('dolceImport.status.checkingConnection')}</Badge>
}
if (importStatusMap.size === 0) {
- return <Badge variant="destructive">DOLCE Connection Error</Badge>
+ return <Badge variant="destructive">{t('dolceImport.status.connectionError')}</Badge>
}
if (!totalStats.importEnabled) {
- return <Badge variant="secondary">DOLCE Import Disabled</Badge>
+ return <Badge variant="secondary">{t('dolceImport.status.importDisabled')}</Badge>
}
if (totalStats.newDocuments > 0 || totalStats.updatedDocuments > 0) {
return (
<Badge variant="samsung" className="gap-1">
<AlertTriangle className="w-3 h-3" />
- Updates Available ({projectIds.length} projects)
+ {t('dolceImport.status.updatesAvailable', { projectCount: projectIds.length })}
</Badge>
)
}
@@ -374,10 +375,10 @@ export function ImportFromDOLCEButton({
return (
<Badge variant="default" className="gap-1 bg-green-500 hover:bg-green-600">
<CheckCircle className="w-3 h-3" />
- Synchronized with DOLCE
+ {t('dolceImport.status.synchronized')}
</Badge>
)
- }, [loadingVendorProjects, statusLoading, importStatusMap.size, totalStats, projectIds.length])
+ }, [loadingVendorProjects, statusLoading, importStatusMap.size, totalStats, projectIds.length, t])
const canImport = totalStats.importEnabled &&
(totalStats.newDocuments > 0 || totalStats.updatedDocuments > 0)
@@ -389,7 +390,7 @@ export function ImportFromDOLCEButton({
}, [fetchAllImportStatus])
// 로딩 중이거나 projectIds가 없으면 버튼을 표시하지 않음
- if (loadingVendorProjects || projectIds.length === 0) {
+ if (projectIds.length === 0) {
return null
}
@@ -409,7 +410,7 @@ export function ImportFromDOLCEButton({
) : (
<Download className="w-4 h-4" />
)}
- <span className="hidden sm:inline">Get List</span>
+ <span className="hidden sm:inline">{t('dolceImport.buttons.getList')}</span>
{totalStats.newDocuments + totalStats.updatedDocuments > 0 && (
<Badge
variant="samsung"
@@ -425,9 +426,9 @@ export function ImportFromDOLCEButton({
<PopoverContent className="w-96">
<div className="space-y-4">
<div className="space-y-2">
- <h4 className="font-medium">DOLCE Import Status</h4>
+ <h4 className="font-medium">{t('dolceImport.labels.importStatus')}</h4>
<div className="flex items-center justify-between">
- <span className="text-sm text-muted-foreground">Current Status</span>
+ <span className="text-sm text-muted-foreground">{t('dolceImport.labels.currentStatus')}</span>
{statusBadge}
</div>
</div>
@@ -435,17 +436,17 @@ export function ImportFromDOLCEButton({
{/* 프로젝트 소스 표시 */}
{allDocuments.length === 0 && vendorProjectIds.length > 0 && (
<div className="text-xs text-blue-600 bg-blue-50 p-2 rounded">
- No documents found, importing from all projects.
+ {t('dolceImport.descriptions.noDocumentsImportAll')}
</div>
)}
{/* 다중 프로젝트 정보 표시 */}
{projectIds.length > 1 && (
<div className="text-sm">
- <div className="text-muted-foreground">Target Projects</div>
- <div className="font-medium">{projectIds.length} projects</div>
+ <div className="text-muted-foreground">{t('dolceImport.labels.targetProjects')}</div>
+ <div className="font-medium">{t('dolceImport.labels.projectCount', { count: projectIds.length })}</div>
<div className="text-xs text-muted-foreground">
- Project IDs: {projectIds.join(', ')}
+ {t('dolceImport.labels.projectIds')}: {projectIds.join(', ')}
</div>
</div>
)}
@@ -456,17 +457,17 @@ export function ImportFromDOLCEButton({
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
- <div className="text-muted-foreground">New Documents</div>
+ <div className="text-muted-foreground">{t('dolceImport.labels.newDocuments')}</div>
<div className="font-medium">{totalStats.newDocuments || 0}</div>
</div>
<div>
- <div className="text-muted-foreground">Updates</div>
+ <div className="text-muted-foreground">{t('dolceImport.labels.updates')}</div>
<div className="font-medium">{totalStats.updatedDocuments || 0}</div>
</div>
</div>
<div className="text-sm">
- <div className="text-muted-foreground">Total Documents (B3/B4/B5)</div>
+ <div className="text-muted-foreground">{t('dolceImport.labels.totalDocuments')}</div>
<div className="font-medium">{totalStats.availableDocuments || 0}</div>
</div>
@@ -474,20 +475,23 @@ export function ImportFromDOLCEButton({
{projectIds.length > 1 && (
<details className="text-sm">
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
- Details by Project
+ {t('dolceImport.labels.detailsByProject')}
</summary>
<div className="mt-2 space-y-2 pl-2 border-l-2 border-muted">
{projectIds.map(projectId => {
const status = importStatusMap.get(projectId)
return (
<div key={projectId} className="text-xs">
- <div className="font-medium">Project {projectId}</div>
+ <div className="font-medium">{t('dolceImport.labels.projectLabel', { projectId })}</div>
{status ? (
<div className="text-muted-foreground">
- New {status.newDocuments}, Updates {status.updatedDocuments}
+ {t('dolceImport.descriptions.projectDetails', {
+ newDocuments: status.newDocuments,
+ updatedDocuments: status.updatedDocuments
+ })}
</div>
) : (
- <div className="text-destructive">Status check failed</div>
+ <div className="text-destructive">{t('dolceImport.status.statusCheckFailed')}</div>
)}
</div>
)
@@ -510,12 +514,12 @@ export function ImportFromDOLCEButton({
{isImporting ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
- Importing...
+ {t('dolceImport.buttons.importing')}
</>
) : (
<>
<Download className="w-4 h-4 mr-2" />
- Import Now
+ {t('dolceImport.buttons.importNow')}
</>
)}
</Button>
@@ -541,10 +545,10 @@ export function ImportFromDOLCEButton({
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
- <DialogTitle>Import Document List from DOLCE</DialogTitle>
+ <DialogTitle>{t('dolceImport.dialog.title')}</DialogTitle>
<DialogDescription>
- Import the latest document list from Samsung Heavy Industries DOLCE system.
- {projectIds.length > 1 && ` (${projectIds.length} projects targeted)`}
+ {t('dolceImport.dialog.description')}
+ {projectIds.length > 1 && ` (${t('dolceImport.dialog.multipleProjects', { count: projectIds.length })})`}
</DialogDescription>
</DialogHeader>
@@ -552,20 +556,20 @@ export function ImportFromDOLCEButton({
{totalStats && (
<div className="rounded-lg border p-4 space-y-3">
<div className="flex items-center justify-between text-sm">
- <span>Items to Import</span>
+ <span>{t('dolceImport.labels.itemsToImport')}</span>
<span className="font-medium">
{totalStats.newDocuments + totalStats.updatedDocuments}
</span>
</div>
<div className="text-xs text-muted-foreground">
- Includes new and updated documents (B3, B4, B5).
+ {t('dolceImport.descriptions.includesNewAndUpdated')}
<br />
- For B4 documents, GTTPreDwg and GTTWorkingDwg issue stages will be auto-generated.
+ {t('dolceImport.descriptions.b4DocumentsNote')}
{projectIds.length > 1 && (
<>
<br />
- Will import sequentially from {projectIds.length} projects.
+ {t('dolceImport.descriptions.sequentialImport', { count: projectIds.length })}
</>
)}
</div>
@@ -573,7 +577,7 @@ export function ImportFromDOLCEButton({
{isImporting && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
- <span>Progress</span>
+ <span>{t('dolceImport.labels.progress')}</span>
<span>{importProgress}%</span>
</div>
<Progress value={importProgress} className="h-2" />
@@ -588,7 +592,7 @@ export function ImportFromDOLCEButton({
onClick={() => setIsDialogOpen(false)}
disabled={isImporting}
>
- Cancel
+ {t('buttons.cancel')}
</Button>
<Button
onClick={handleImport}
@@ -597,12 +601,12 @@ export function ImportFromDOLCEButton({
{isImporting ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
- Importing...
+ {t('dolceImport.buttons.importing')}
</>
) : (
<>
<Download className="w-4 h-4 mr-2" />
- Start Import
+ {t('dolceImport.buttons.startImport')}
</>
)}
</Button>
diff --git a/lib/vendor-document-list/ship/send-to-shi-button.tsx b/lib/vendor-document-list/ship/send-to-shi-button.tsx
index 7236dfde..447b461b 100644
--- a/lib/vendor-document-list/ship/send-to-shi-button.tsx
+++ b/lib/vendor-document-list/ship/send-to-shi-button.tsx
@@ -26,6 +26,8 @@ import { Alert, AlertDescription } from "@/components/ui/alert"
// ✅ 업데이트된 Hook import
import { useClientSyncStatus, useTriggerSync, syncUtils } from "@/hooks/use-sync-status"
import type { EnhancedDocument } from "@/types/enhanced-documents"
+import { useParams } from "next/navigation"
+import { useTranslation } from "@/i18n/client"
interface SendToSHIButtonProps {
documents?: EnhancedDocument[]
@@ -42,6 +44,10 @@ export function SendToSHIButton({
const [syncProgress, setSyncProgress] = React.useState(0)
const [currentSyncingContract, setCurrentSyncingContract] = React.useState<number | null>(null)
+ const params = useParams()
+ const lng = (params?.lng as string) || "ko"
+ const { t } = useTranslation(lng, "engineering")
+
const targetSystem = projectType === 'ship' ? "DOLCE" : "SWP"
// 문서에서 유효한 계약 ID 목록 추출 (projectId 사용)
@@ -80,7 +86,7 @@ export function SendToSHIButton({
// 동기화 실행 함수
const handleSync = async () => {
if (documentsContractIds.length === 0) {
- toast.info('동기화할 계약이 없습니다.')
+ toast.info(t('shiSync.messages.noContractsToSync'))
return
}
@@ -105,7 +111,7 @@ export function SendToSHIButton({
})
if (contractsToSync.length === 0) {
- toast.info('동기화할 변경사항이 없습니다.')
+ toast.info(t('shiSync.messages.noPendingChanges'))
setIsDialogOpen(false)
return
}
@@ -132,13 +138,13 @@ export function SendToSHIButton({
failedSyncs++
totalFailureCount += result?.failureCount || 0
const errorMsg = result?.errors?.[0] || result?.message || 'Unknown sync error'
- errors.push(`Contract ${projectId}: ${errorMsg}`)
+ errors.push(t('shiSync.messages.contractError', { projectId, error: errorMsg }))
console.error(`Contract ${projectId} sync failed:`, result)
}
} catch (error) {
failedSyncs++
- const errorMessage = error instanceof Error ? error.message : '알 수 없는 오류'
- errors.push(`Contract ${projectId}: ${errorMessage}`)
+ const errorMessage = error instanceof Error ? error.message : t('shiSync.messages.unknownError')
+ errors.push(t('shiSync.messages.contractError', { projectId, error: errorMessage }))
console.error(`Contract ${projectId} sync exception:`, error)
}
@@ -155,23 +161,30 @@ export function SendToSHIButton({
if (failedSyncs === 0) {
toast.success(
- `모든 계약 동기화 완료: ${totalSuccessCount}건 성공`,
+ t('shiSync.messages.allSyncCompleted', { successCount: totalSuccessCount }),
{
- description: `${successfulSyncs}개 계약에서 ${totalSuccessCount}개 항목이 SHI 시스템으로 전송되었습니다.`
+ description: t('shiSync.messages.allSyncCompletedDescription', {
+ contractCount: successfulSyncs,
+ itemCount: totalSuccessCount
+ })
}
)
} else if (successfulSyncs > 0) {
toast.warning(
- `부분 동기화 완료: ${successfulSyncs}개 성공, ${failedSyncs}개 실패`,
+ t('shiSync.messages.partialSyncCompleted', {
+ successfulCount: successfulSyncs,
+ failedCount: failedSyncs
+ }),
{
- description: errors.slice(0, 3).join(', ') + (errors.length > 3 ? ' 외 더보기...' : '')
+ description: errors.slice(0, 3).join(', ') +
+ (errors.length > 3 ? t('shiSync.messages.andMore') : '')
}
)
} else {
toast.error(
- `동기화 실패: ${failedSyncs}개 계약 모두 실패`,
+ t('shiSync.messages.allSyncFailed', { failedCount: failedSyncs }),
{
- description: errors[0] || '모든 계약 동기화에 실패했습니다.'
+ description: errors[0] || t('shiSync.messages.allContractsSyncFailed')
}
)
}
@@ -186,7 +199,7 @@ export function SendToSHIButton({
setCurrentSyncingContract(null)
const errorMessage = syncUtils.formatError(error as Error)
- toast.error('동기화 실패', {
+ toast.error(t('shiSync.messages.syncFailed'), {
description: errorMessage
})
console.error('Sync process failed:', error)
@@ -199,7 +212,7 @@ export function SendToSHIButton({
return (
<Badge variant="secondary" className="gap-1">
<Loader2 className="w-3 h-3 animate-spin" />
- 확인 중...
+ {t('shiSync.status.checking')}
</Badge>
)
}
@@ -208,20 +221,20 @@ export function SendToSHIButton({
return (
<Badge variant="destructive" className="gap-1">
<AlertTriangle className="w-3 h-3" />
- 연결 오류
+ {t('shiSync.status.connectionError')}
</Badge>
)
}
if (documentsContractIds.length === 0) {
- return <Badge variant="secondary">계약 없음</Badge>
+ return <Badge variant="secondary">{t('shiSync.status.noContracts')}</Badge>
}
if (totalStats.totalPending > 0) {
return (
<Badge variant="destructive" className="gap-1">
<AlertTriangle className="w-3 h-3" />
- {totalStats.totalPending}건 대기
+ {t('shiSync.status.pendingItems', { count: totalStats.totalPending })}
</Badge>
)
}
@@ -230,12 +243,12 @@ export function SendToSHIButton({
return (
<Badge variant="default" className="gap-1 bg-green-500 hover:bg-green-600">
<CheckCircle className="w-3 h-3" />
- 동기화됨
+ {t('shiSync.status.synchronized')}
</Badge>
)
}
- return <Badge variant="secondary">변경사항 없음</Badge>
+ return <Badge variant="secondary">{t('shiSync.status.noChanges')}</Badge>
}
return (
@@ -254,7 +267,7 @@ export function SendToSHIButton({
) : (
<Send className="w-4 h-4" />
)}
- <span className="hidden sm:inline">Send to SHI</span>
+ <span className="hidden sm:inline">{t('shiSync.buttons.sendToSHI')}</span>
{totalStats.totalPending > 0 && (
<Badge
variant="destructive"
@@ -271,7 +284,7 @@ export function SendToSHIButton({
<div className="space-y-4">
<div className="space-y-2">
<div className="flex items-center justify-between">
- <h4 className="font-medium">SHI 동기화 상태</h4>
+ <h4 className="font-medium">{t('shiSync.labels.syncStatus')}</h4>
<Button
variant="ghost"
size="sm"
@@ -288,12 +301,15 @@ export function SendToSHIButton({
</div>
<div className="flex items-center justify-between">
- <span className="text-sm text-muted-foreground">전체 상태</span>
+ <span className="text-sm text-muted-foreground">{t('shiSync.labels.overallStatus')}</span>
{getSyncStatusBadge()}
</div>
<div className="text-xs text-muted-foreground">
- {documentsContractIds.length}개 계약 대상 • {targetSystem} 시스템
+ {t('shiSync.descriptions.targetInfo', {
+ contractCount: documentsContractIds.length,
+ targetSystem
+ })}
</div>
</div>
@@ -302,10 +318,12 @@ export function SendToSHIButton({
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
- 일부 계약의 동기화 상태를 확인할 수 없습니다. 네트워크 연결을 확인해주세요.
+ {t('shiSync.descriptions.statusCheckError')}
{process.env.NODE_ENV === 'development' && (
<div className="text-xs mt-1 font-mono">
- Debug: {contractStatuses.filter(({ error }) => error).length}개 계약에서 오류
+ Debug: {t('shiSync.descriptions.contractsWithError', {
+ count: contractStatuses.filter(({ error }) => error).length
+ })}
</div>
)}
</AlertDescription>
@@ -319,46 +337,46 @@ export function SendToSHIButton({
<div className="grid grid-cols-3 gap-4 text-sm">
<div className="text-center">
- <div className="text-muted-foreground">대기 중</div>
- <div className="font-medium text-orange-600">{totalStats.totalPending}건</div>
+ <div className="text-muted-foreground">{t('shiSync.labels.pending')}</div>
+ <div className="font-medium text-orange-600">{t('shiSync.labels.itemCount', { count: totalStats.totalPending })}</div>
</div>
<div className="text-center">
- <div className="text-muted-foreground">동기화됨</div>
- <div className="font-medium text-green-600">{totalStats.totalSynced}건</div>
+ <div className="text-muted-foreground">{t('shiSync.labels.synced')}</div>
+ <div className="font-medium text-green-600">{t('shiSync.labels.itemCount', { count: totalStats.totalSynced })}</div>
</div>
<div className="text-center">
- <div className="text-muted-foreground">실패</div>
- <div className="font-medium text-red-600">{totalStats.totalFailed}건</div>
+ <div className="text-muted-foreground">{t('shiSync.labels.failed')}</div>
+ <div className="font-medium text-red-600">{t('shiSync.labels.itemCount', { count: totalStats.totalFailed })}</div>
</div>
</div>
{/* 계약별 상세 상태 */}
{contractStatuses.length > 1 && (
<div className="space-y-2">
- <div className="text-sm font-medium">계약별 상태</div>
+ <div className="text-sm font-medium">{t('shiSync.labels.statusByContract')}</div>
<ScrollArea className="h-32">
<div className="space-y-2">
{contractStatuses.map(({ projectId, syncStatus, isLoading, error }) => (
<div key={projectId} className="flex items-center justify-between text-xs p-2 rounded border">
- <span className="font-medium">Contract {projectId}</span>
+ <span className="font-medium">{t('shiSync.labels.contractLabel', { projectId })}</span>
{isLoading ? (
<Badge variant="secondary" className="text-xs">
<Loader2 className="w-3 h-3 mr-1 animate-spin" />
- 로딩...
+ {t('shiSync.status.loading')}
</Badge>
) : error ? (
<Badge variant="destructive" className="text-xs">
<AlertTriangle className="w-3 h-3 mr-1" />
- 오류
+ {t('shiSync.status.error')}
</Badge>
) : syncStatus && syncStatus.pendingChanges > 0 ? (
<Badge variant="destructive" className="text-xs">
- {syncStatus.pendingChanges}건 대기
+ {t('shiSync.status.pendingCount', { count: syncStatus.pendingChanges })}
</Badge>
) : (
<Badge variant="secondary" className="text-xs">
<CheckCircle className="w-3 h-3 mr-1" />
- 최신
+ {t('shiSync.status.upToDate')}
</Badge>
)}
</div>
@@ -374,7 +392,7 @@ export function SendToSHIButton({
{documentsContractIds.length === 0 && (
<Alert>
<AlertDescription>
- 동기화할 문서가 없습니다. 문서를 선택해주세요.
+ {t('shiSync.descriptions.noDocumentsToSync')}
</AlertDescription>
</Alert>
)}
@@ -392,12 +410,12 @@ export function SendToSHIButton({
{isSyncing ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
- 동기화 중...
+ {t('shiSync.buttons.syncing')}
</>
) : (
<>
<Send className="w-4 h-4 mr-2" />
- 지금 동기화
+ {t('shiSync.buttons.syncNow')}
</>
)}
</Button>
@@ -426,10 +444,13 @@ export function SendToSHIButton({
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Send className="w-5 h-5" />
- SHI 시스템으로 동기화
+ {t('shiSync.dialog.title')}
</DialogTitle>
<DialogDescription>
- {documentsContractIds.length}개 계약의 변경된 문서 데이터를 {targetSystem} 시스템으로 전송합니다.
+ {t('shiSync.dialog.description', {
+ contractCount: documentsContractIds.length,
+ targetSystem
+ })}
</DialogDescription>
</DialogHeader>
@@ -437,30 +458,30 @@ export function SendToSHIButton({
{!totalStats.hasError && documentsContractIds.length > 0 && (
<div className="rounded-lg border p-4 space-y-3">
<div className="flex items-center justify-between text-sm">
- <span>전송 대상</span>
- <span className="font-medium">{totalStats.totalPending}건</span>
+ <span>{t('shiSync.labels.syncTarget')}</span>
+ <span className="font-medium">{t('shiSync.labels.itemCount', { count: totalStats.totalPending })}</span>
</div>
<div className="flex items-center justify-between text-sm">
- <span>대상 계약</span>
- <span className="font-medium">{documentsContractIds.length}개</span>
+ <span>{t('shiSync.labels.targetContracts')}</span>
+ <span className="font-medium">{t('shiSync.labels.contractCount', { count: documentsContractIds.length })}</span>
</div>
<div className="text-xs text-muted-foreground">
- 문서, 리비전, 첨부파일의 변경사항이 포함됩니다.
+ {t('shiSync.descriptions.includesChanges')}
</div>
{isSyncing && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
- <span>진행률</span>
+ <span>{t('shiSync.labels.progress')}</span>
<span>{Math.round(syncProgress)}%</span>
</div>
<Progress value={syncProgress} className="h-2" />
{currentSyncingContract && (
<div className="text-xs text-muted-foreground flex items-center gap-1">
<Loader2 className="w-3 h-3 animate-spin" />
- 현재 처리 중: Contract {currentSyncingContract}
+ {t('shiSync.descriptions.currentlyProcessing', { contractId: currentSyncingContract })}
</div>
)}
</div>
@@ -472,7 +493,7 @@ export function SendToSHIButton({
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
- 일부 계약의 동기화 상태를 확인할 수 없습니다. 네트워크 연결을 확인하고 다시 시도해주세요.
+ {t('shiSync.descriptions.dialogStatusCheckError')}
</AlertDescription>
</Alert>
)}
@@ -480,7 +501,7 @@ export function SendToSHIButton({
{documentsContractIds.length === 0 && (
<Alert>
<AlertDescription>
- 동기화할 계약이 없습니다. 문서를 선택해주세요.
+ {t('shiSync.descriptions.noContractsToSync')}
</AlertDescription>
</Alert>
)}
@@ -491,7 +512,7 @@ export function SendToSHIButton({
onClick={() => setIsDialogOpen(false)}
disabled={isSyncing}
>
- 취소
+ {t('buttons.cancel')}
</Button>
<Button
onClick={handleSync}
@@ -500,12 +521,12 @@ export function SendToSHIButton({
{isSyncing ? (
<>
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
- 동기화 중...
+ {t('shiSync.buttons.syncing')}
</>
) : (
<>
<Send className="w-4 h-4 mr-2" />
- 동기화 시작
+ {t('shiSync.buttons.startSync')}
</>
)}
</Button>