// components/sync/send-to-shi-button.tsx (다중 계약 버전) "use client" import * as React from "react" import { Send, Loader2, CheckCircle, AlertTriangle, Settings } from "lucide-react" import { toast } from "sonner" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" 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 type { EnhancedDocument } from "@/types/enhanced-documents" interface SendToSHIButtonProps { documents?: EnhancedDocument[] onSyncComplete?: () => void projectType: "ship" | "plant" } interface ContractSyncStatus { contractId: number syncStatus: any isLoading: boolean error: any } export function SendToSHIButton({ documents = [], onSyncComplete, projectType }: SendToSHIButtonProps) { const [isDialogOpen, setIsDialogOpen] = React.useState(false) const [syncProgress, setSyncProgress] = React.useState(0) const [currentSyncingContract, setCurrentSyncingContract] = React.useState(null) const targetSystem = projectType === 'ship' ? "DOLCE" : "SWP" // documents에서 contractId 목록 추출 const documentsContractIds = React.useMemo(() => { const uniqueIds = [...new Set(documents.map(doc => doc.contractId).filter(Boolean))] 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]) // 에러 상태 표시 React.useEffect(() => { if (totalStats.hasError) { console.warn('Failed to load sync status for some contracts') } }, [totalStats.hasError]) const handleSync = async () => { if (documentsContractIds.length === 0) return setSyncProgress(0) let successfulSyncs = 0 let failedSyncs = 0 let totalSuccessCount = 0 let totalFailureCount = 0 const errors: string[] = [] try { const contractsToSync = contractStatuses.filter( ({ syncStatus, error }) => !error && syncStatus?.syncEnabled && syncStatus?.pendingChanges > 0 ) if (contractsToSync.length === 0) { toast.info('동기화할 변경사항이 없습니다.') setIsDialogOpen(false) return } // 각 contract별로 순차 동기화 for (let i = 0; i < contractsToSync.length; i++) { const { contractId } = contractsToSync[i] setCurrentSyncingContract(contractId) try { const result = await triggerSync({ contractId, targetSystem }) if (result?.success) { successfulSyncs++ totalSuccessCount += result.successCount || 0 } else { failedSyncs++ totalFailureCount += result?.failureCount || 0 if (result?.errors?.[0]) { errors.push(`Contract ${contractId}: ${result.errors[0]}`) } } } catch (error) { failedSyncs++ const errorMessage = error instanceof Error ? error.message : '알 수 없는 오류' errors.push(`Contract ${contractId}: ${errorMessage}`) } // 진행률 업데이트 setSyncProgress(((i + 1) / contractsToSync.length) * 100) } setCurrentSyncingContract(null) setTimeout(() => { setSyncProgress(0) setIsDialogOpen(false) if (failedSyncs === 0) { toast.success( `모든 계약 동기화 완료: ${totalSuccessCount}건 성공`, { description: `${successfulSyncs}개 계약에서 ${totalSuccessCount}개 항목이 SHI 시스템으로 전송되었습니다.` } ) } else if (successfulSyncs > 0) { toast.warning( `부분 동기화 완료: ${successfulSyncs}개 성공, ${failedSyncs}개 실패`, { description: errors[0] || '일부 계약 동기화에 실패했습니다.' } ) } else { toast.error( `동기화 실패: ${failedSyncs}개 계약 모두 실패`, { description: errors[0] || '모든 계약 동기화에 실패했습니다.' } ) } // 모든 contract 상태 갱신 contractStatuses.forEach(({ refetch }) => refetch?.()) onSyncComplete?.() }, 500) } catch (error) { setSyncProgress(0) setCurrentSyncingContract(null) toast.error('동기화 실패', { description: error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.' }) } } const getSyncStatusBadge = () => { if (totalStats.isLoading) { return 확인 중... } if (totalStats.hasError) { return 오류 } if (documentsContractIds.length === 0) { return 계약 없음 } if (totalStats.totalPending > 0) { return ( {totalStats.totalPending}건 대기 ) } if (totalStats.totalSynced > 0) { return ( 동기화됨 ) } return 변경사항 없음 } const refreshAllStatuses = () => { contractStatuses.forEach(({ refetch }) => refetch?.()) } return ( <>

SHI 동기화 상태

전체 상태 {getSyncStatusBadge()}
{documentsContractIds.length}개 계약 대상
{!totalStats.hasError && documentsContractIds.length > 0 && (
대기 중
{totalStats.totalPending}건
동기화됨
{totalStats.totalSynced}건
실패
{totalStats.totalFailed}건
{/* 계약별 상세 상태 */} {contractStatuses.length > 1 && (
계약별 상태
{contractStatuses.map(({ contractId, syncStatus, isLoading, error }) => (
Contract {contractId} {isLoading ? ( 로딩... ) : error ? ( 오류 ) : syncStatus?.pendingChanges > 0 ? ( {syncStatus.pendingChanges}건 대기 ) : ( 동기화됨 )}
))}
)}
)} {totalStats.hasError && (
연결 오류
일부 계약의 동기화 상태를 확인할 수 없습니다.
)} {documentsContractIds.length === 0 && (
계약 정보 없음
동기화할 문서가 없습니다.
)}
{/* 동기화 진행 다이얼로그 */} SHI 시스템으로 동기화 {documentsContractIds.length}개 계약의 변경된 문서 데이터를 SHI 시스템으로 전송합니다.
{!totalStats.hasError && documentsContractIds.length > 0 && (
전송 대상 {totalStats.totalPending}건
대상 계약 {documentsContractIds.length}개
문서, 리비전, 첨부파일의 변경사항이 포함됩니다.
{isSyncing && (
진행률 {Math.round(syncProgress)}%
{currentSyncingContract && (
현재 처리 중: Contract {currentSyncingContract}
)}
)}
)} {totalStats.hasError && (
일부 계약의 동기화 상태를 확인할 수 없습니다. 네트워크 연결을 확인해주세요.
)} {documentsContractIds.length === 0 && (
동기화할 계약이 없습니다. 문서를 선택해주세요.
)}
) }