From ee57cc221ff2edafd3c0f12a181214c602ed257e Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 22 Jul 2025 02:57:00 +0000 Subject: (대표님, 최겸) 이메일 템플릿, 벤더데이터 변경사항 대응, 기술영업 변경요구사항 구현 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ship/send-to-shi-button.tsx | 286 +++++++++++---------- 1 file changed, 156 insertions(+), 130 deletions(-) (limited to 'lib/vendor-document-list/ship/send-to-shi-button.tsx') 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 61893da5..4607c994 100644 --- a/lib/vendor-document-list/ship/send-to-shi-button.tsx +++ b/lib/vendor-document-list/ship/send-to-shi-button.tsx @@ -1,8 +1,8 @@ -// components/sync/send-to-shi-button.tsx (다중 계약 버전) +// components/sync/send-to-shi-button.tsx (최종 완성 버전) "use client" import * as React from "react" -import { Send, Loader2, CheckCircle, AlertTriangle, Settings } from "lucide-react" +import { Send, Loader2, CheckCircle, AlertTriangle, Settings, RefreshCw } from "lucide-react" import { toast } from "sonner" import { Button } from "@/components/ui/button" @@ -22,7 +22,9 @@ import { Badge } from "@/components/ui/badge" import { Progress } from "@/components/ui/progress" import { Separator } from "@/components/ui/separator" import { ScrollArea } from "@/components/ui/scroll-area" -import { useSyncStatus, useTriggerSync } from "@/hooks/use-sync-status" +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" interface SendToSHIButtonProps { @@ -31,13 +33,6 @@ interface SendToSHIButtonProps { projectType: "ship" | "plant" } -interface ContractSyncStatus { - contractId: number - syncStatus: any - isLoading: boolean - error: any -} - export function SendToSHIButton({ documents = [], onSyncComplete, @@ -49,75 +44,45 @@ export function SendToSHIButton({ const targetSystem = projectType === 'ship' ? "DOLCE" : "SWP" - // documents에서 contractId 목록 추출 + // 문서에서 유효한 계약 ID 목록 추출 const documentsContractIds = React.useMemo(() => { - const uniqueIds = [...new Set(documents.map(doc => doc.contractId).filter(Boolean))] + const validIds = documents + .map(doc => doc.contractId) + .filter((id): id is number => typeof id === 'number' && id > 0) + + const uniqueIds = [...new Set(validIds)] return uniqueIds.sort() }, [documents]) - // 각 contract별 동기화 상태 조회 - const contractStatuses = React.useMemo(() => { - return documentsContractIds.map(contractId => { - const { - syncStatus, - isLoading, - error, - refetch - } = useSyncStatus(contractId, targetSystem) - - return { - contractId, - syncStatus, - isLoading, - error, - refetch - } - }) - }, [documentsContractIds, targetSystem]) - - const { - triggerSync, - isLoading: isSyncing, - error: syncError - } = useTriggerSync() - - // 전체 통계 계산 - const totalStats = React.useMemo(() => { - let totalPending = 0 - let totalSynced = 0 - let totalFailed = 0 - let hasError = false - let isLoading = false - - contractStatuses.forEach(({ syncStatus, error, isLoading: loading }) => { - if (error) hasError = true - if (loading) isLoading = true - if (syncStatus) { - totalPending += syncStatus.pendingChanges || 0 - totalSynced += syncStatus.syncedChanges || 0 - totalFailed += syncStatus.failedChanges || 0 - } - }) - - return { - totalPending, - totalSynced, - totalFailed, - hasError, - isLoading, - canSync: totalPending > 0 && !hasError - } - }, [contractStatuses]) + // ✅ 클라이언트 전용 Hook 사용 (서버 사이드 렌더링 호환) + const { contractStatuses, totalStats, refetchAll } = useClientSyncStatus( + documentsContractIds, + targetSystem + ) + + const { triggerSync, isLoading: isSyncing, error: syncError } = useTriggerSync() - // 에러 상태 표시 + // 개발 환경에서 디버깅 정보 React.useEffect(() => { - if (totalStats.hasError) { - console.warn('Failed to load sync status for some contracts') + if (process.env.NODE_ENV === 'development') { + console.log('SendToSHIButton Debug Info:', { + documentsContractIds, + totalStats, + contractStatuses: contractStatuses.map(({ contractId, syncStatus, error }) => ({ + contractId, + pendingChanges: syncStatus?.pendingChanges, + hasError: !!error + })) + }) } - }, [totalStats.hasError]) + }, [documentsContractIds, totalStats, contractStatuses]) + // 동기화 실행 함수 const handleSync = async () => { - if (documentsContractIds.length === 0) return + if (documentsContractIds.length === 0) { + toast.info('동기화할 계약이 없습니다.') + return + } setSyncProgress(0) let successfulSyncs = 0 @@ -127,9 +92,17 @@ export function SendToSHIButton({ const errors: string[] = [] try { - const contractsToSync = contractStatuses.filter( - ({ syncStatus, error }) => !error && syncStatus?.syncEnabled && syncStatus?.pendingChanges > 0 - ) + // 동기화 가능한 계약들만 필터링 + const contractsToSync = contractStatuses.filter(({ syncStatus, error }) => { + if (error) { + console.warn(`Contract ${contractStatuses.find(c => c.error === error)?.contractId} has error:`, error) + return false + } + if (!syncStatus) return false + if (!syncStatus.syncEnabled) return false + if (syncStatus.pendingChanges <= 0) return false + return true + }) if (contractsToSync.length === 0) { toast.info('동기화할 변경사항이 없습니다.') @@ -137,12 +110,15 @@ export function SendToSHIButton({ return } + console.log(`Starting sync for ${contractsToSync.length} contracts`) + // 각 contract별로 순차 동기화 for (let i = 0; i < contractsToSync.length; i++) { const { contractId } = contractsToSync[i] setCurrentSyncingContract(contractId) try { + console.log(`Syncing contract ${contractId}...`) const result = await triggerSync({ contractId, targetSystem @@ -151,17 +127,19 @@ export function SendToSHIButton({ if (result?.success) { successfulSyncs++ totalSuccessCount += result.successCount || 0 + console.log(`Contract ${contractId} sync successful:`, result) } else { failedSyncs++ totalFailureCount += result?.failureCount || 0 - if (result?.errors?.[0]) { - errors.push(`Contract ${contractId}: ${result.errors[0]}`) - } + const errorMsg = result?.errors?.[0] || result?.message || 'Unknown sync error' + errors.push(`Contract ${contractId}: ${errorMsg}`) + console.error(`Contract ${contractId} sync failed:`, result) } } catch (error) { failedSyncs++ const errorMessage = error instanceof Error ? error.message : '알 수 없는 오류' errors.push(`Contract ${contractId}: ${errorMessage}`) + console.error(`Contract ${contractId} sync exception:`, error) } // 진행률 업데이트 @@ -170,6 +148,7 @@ export function SendToSHIButton({ setCurrentSyncingContract(null) + // 결과 처리 및 토스트 표시 setTimeout(() => { setSyncProgress(0) setIsDialogOpen(false) @@ -185,7 +164,7 @@ export function SendToSHIButton({ toast.warning( `부분 동기화 완료: ${successfulSyncs}개 성공, ${failedSyncs}개 실패`, { - description: errors[0] || '일부 계약 동기화에 실패했습니다.' + description: errors.slice(0, 3).join(', ') + (errors.length > 3 ? ' 외 더보기...' : '') } ) } else { @@ -198,7 +177,7 @@ export function SendToSHIButton({ } // 모든 contract 상태 갱신 - contractStatuses.forEach(({ refetch }) => refetch?.()) + refetchAll() onSyncComplete?.() }, 500) @@ -206,19 +185,32 @@ export function SendToSHIButton({ setSyncProgress(0) setCurrentSyncingContract(null) + const errorMessage = syncUtils.formatError(error as any) toast.error('동기화 실패', { - description: error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.' + description: errorMessage }) + console.error('Sync process failed:', error) } } + // 동기화 상태에 따른 뱃지 생성 const getSyncStatusBadge = () => { if (totalStats.isLoading) { - return 확인 중... + return ( + + + 확인 중... + + ) } if (totalStats.hasError) { - return 오류 + return ( + + + 연결 오류 + + ) } if (documentsContractIds.length === 0) { @@ -246,10 +238,6 @@ export function SendToSHIButton({ return 변경사항 없음 } - const refreshAllStatuses = () => { - contractStatuses.forEach(({ refetch }) => refetch?.()) - } - return ( <> @@ -258,7 +246,7 @@ export function SendToSHIButton({ + +
전체 상태 {getSyncStatusBadge()}
+
- {documentsContractIds.length}개 계약 대상 + {documentsContractIds.length}개 계약 대상 • {targetSystem} 시스템
+ {/* 에러 상태 표시 */} + {totalStats.hasError && ( + + + + 일부 계약의 동기화 상태를 확인할 수 없습니다. 네트워크 연결을 확인해주세요. + {process.env.NODE_ENV === 'development' && ( +
+ Debug: {contractStatuses.filter(({ error }) => error).length}개 계약에서 오류 +
+ )} +
+
+ )} + + {/* 정상 상태일 때 통계 표시 */} {!totalStats.hasError && documentsContractIds.length > 0 && (
-
+
대기 중
-
{totalStats.totalPending}건
+
{totalStats.totalPending}건
-
+
동기화됨
-
{totalStats.totalSynced}건
+
{totalStats.totalSynced}건
-
+
실패
{totalStats.totalFailed}건
@@ -319,17 +340,26 @@ export function SendToSHIButton({
{contractStatuses.map(({ contractId, syncStatus, isLoading, error }) => (
- Contract {contractId} + Contract {contractId} {isLoading ? ( - 로딩... + + + 로딩... + ) : error ? ( - 오류 - ) : syncStatus?.pendingChanges > 0 ? ( + + + 오류 + + ) : syncStatus && syncStatus.pendingChanges > 0 ? ( {syncStatus.pendingChanges}건 대기 ) : ( - 동기화됨 + + + 최신 + )}
))} @@ -340,28 +370,18 @@ export function SendToSHIButton({
)} - {totalStats.hasError && ( -
- -
-
연결 오류
-
일부 계약의 동기화 상태를 확인할 수 없습니다.
-
-
- )} - + {/* 계약 정보가 없는 경우 */} {documentsContractIds.length === 0 && ( -
- -
-
계약 정보 없음
-
동기화할 문서가 없습니다.
-
-
+ + + 동기화할 문서가 없습니다. 문서를 선택해주세요. + + )} + {/* 액션 버튼들 */}
{currentSyncingContract && ( -
+
+ 현재 처리 중: Contract {currentSyncingContract}
)} @@ -444,19 +469,20 @@ export function SendToSHIButton({ )} {totalStats.hasError && ( -
-
- 일부 계약의 동기화 상태를 확인할 수 없습니다. 네트워크 연결을 확인해주세요. -
-
+ + + + 일부 계약의 동기화 상태를 확인할 수 없습니다. 네트워크 연결을 확인하고 다시 시도해주세요. + + )} {documentsContractIds.length === 0 && ( -
-
+ + 동기화할 계약이 없습니다. 문서를 선택해주세요. -
-
+ + )}
-- cgit v1.2.3