From 4eb7532f822c821fb6b69bf103bd075fefba769b Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 15 Jul 2025 10:07:09 +0000 Subject: (대표님) 20250715 협력사 정기평가, spreadJS, roles 서비스에 함수 추가 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ship/send-to-shi-button.tsx | 336 +++++++++++++++------ 1 file changed, 240 insertions(+), 96 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 1a27a794..61893da5 100644 --- a/lib/vendor-document-list/ship/send-to-shi-button.tsx +++ b/lib/vendor-document-list/ship/send-to-shi-button.tsx @@ -1,4 +1,4 @@ -// components/sync/send-to-shi-button.tsx (최종 버전) +// components/sync/send-to-shi-button.tsx (다중 계약 버전) "use client" import * as React from "react" @@ -21,33 +21,59 @@ import { 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 { - contractId: number documents?: EnhancedDocument[] onSyncComplete?: () => void projectType: "ship" | "plant" } +interface ContractSyncStatus { + contractId: number + syncStatus: any + isLoading: boolean + error: any +} + export function SendToSHIButton({ - contractId, 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" + const targetSystem = projectType === 'ship' ? "DOLCE" : "SWP" - const { - syncStatus, - isLoading: statusLoading, - error: statusError, - refetch: refetchStatus - } = useSyncStatus(contractId, targetSystem) + // 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, @@ -55,60 +81,130 @@ export function SendToSHIButton({ 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 (statusError) { - console.warn('Failed to load sync status:', statusError) + if (totalStats.hasError) { + console.warn('Failed to load sync status for some contracts') } - }, [statusError]) + }, [totalStats.hasError]) const handleSync = async () => { - if (!contractId) return + if (documentsContractIds.length === 0) return setSyncProgress(0) + let successfulSyncs = 0 + let failedSyncs = 0 + let totalSuccessCount = 0 + let totalFailureCount = 0 + const errors: string[] = [] try { - // 진행률 시뮬레이션 - const progressInterval = setInterval(() => { - setSyncProgress(prev => Math.min(prev + 10, 90)) - }, 200) - - const result = await triggerSync({ - contractId, - targetSystem - }) - - clearInterval(progressInterval) - setSyncProgress(100) + 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 (result?.success) { + if (failedSyncs === 0) { toast.success( - `동기화 완료: ${result.successCount || 0}건 성공`, + `모든 계약 동기화 완료: ${totalSuccessCount}건 성공`, + { + description: `${successfulSyncs}개 계약에서 ${totalSuccessCount}개 항목이 SHI 시스템으로 전송되었습니다.` + } + ) + } else if (successfulSyncs > 0) { + toast.warning( + `부분 동기화 완료: ${successfulSyncs}개 성공, ${failedSyncs}개 실패`, { - description: result.successCount > 0 - ? `${result.successCount}개 항목이 SHI 시스템으로 전송되었습니다.` - : '전송할 새로운 변경사항이 없습니다.' + description: errors[0] || '일부 계약 동기화에 실패했습니다.' } ) } else { toast.error( - `동기화 부분 실패: ${result?.successCount || 0}건 성공, ${result?.failureCount || 0}건 실패`, + `동기화 실패: ${failedSyncs}개 계약 모두 실패`, { - description: result?.errors?.[0] || '일부 항목 전송에 실패했습니다.' + description: errors[0] || '모든 계약 동기화에 실패했습니다.' } ) } - refetchStatus() // SWR 캐시 갱신 + // 모든 contract 상태 갱신 + contractStatuses.forEach(({ refetch }) => refetch?.()) onSyncComplete?.() }, 500) } catch (error) { setSyncProgress(0) + setCurrentSyncingContract(null) toast.error('동기화 실패', { description: error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.' @@ -117,28 +213,28 @@ export function SendToSHIButton({ } const getSyncStatusBadge = () => { - if (statusLoading) { + if (totalStats.isLoading) { return 확인 중... } - if (statusError) { + if (totalStats.hasError) { return 오류 } - if (!syncStatus) { - return 데이터 없음 + if (documentsContractIds.length === 0) { + return 계약 없음 } - if (syncStatus.pendingChanges > 0) { + if (totalStats.totalPending > 0) { return ( - {syncStatus.pendingChanges}건 대기 + {totalStats.totalPending}건 대기 ) } - if (syncStatus.syncedChanges > 0) { + if (totalStats.totalSynced > 0) { return ( @@ -150,86 +246,116 @@ export function SendToSHIButton({ return 변경사항 없음 } - const canSync = !statusError && syncStatus?.syncEnabled && syncStatus?.pendingChanges > 0 + const refreshAllStatuses = () => { + contractStatuses.forEach(({ refetch }) => refetch?.()) + } return ( <> -
- -
+
+ +
- +

SHI 동기화 상태

- 현재 상태 + 전체 상태 {getSyncStatusBadge()}
+
+ {documentsContractIds.length}개 계약 대상 +
- {syncStatus && !statusError && ( + {!totalStats.hasError && documentsContractIds.length > 0 && (
-
+
대기 중
-
{syncStatus.pendingChanges || 0}건
+
{totalStats.totalPending}건
동기화됨
-
{syncStatus.syncedChanges || 0}건
+
{totalStats.totalSynced}건
-
- - {syncStatus.failedChanges > 0 && ( -
+
실패
-
{syncStatus.failedChanges}건
+
{totalStats.totalFailed}건
- )} +
- {syncStatus.lastSyncAt && ( -
-
마지막 동기화
-
- {new Date(syncStatus.lastSyncAt).toLocaleString()} -
+ {/* 계약별 상세 상태 */} + {contractStatuses.length > 1 && ( +
+
계약별 상태
+ +
+ {contractStatuses.map(({ contractId, syncStatus, isLoading, error }) => ( +
+ Contract {contractId} + {isLoading ? ( + 로딩... + ) : error ? ( + 오류 + ) : syncStatus?.pendingChanges > 0 ? ( + + {syncStatus.pendingChanges}건 대기 + + ) : ( + 동기화됨 + )} +
+ ))} +
+
)}
)} - {statusError && ( + {totalStats.hasError && (
연결 오류
-
동기화 상태를 확인할 수 없습니다.
+
일부 계약의 동기화 상태를 확인할 수 없습니다.
+
+
+ )} + + {documentsContractIds.length === 0 && ( +
+ +
+
계약 정보 없음
+
동기화할 문서가 없습니다.
)} @@ -239,7 +365,7 @@ export function SendToSHIButton({