diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-28 00:32:31 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-05-28 00:32:31 +0000 |
| commit | 20800b214145ee6056f94ca18fa1054f145eb977 (patch) | |
| tree | b5c8b27febe5b126e6d9ece115ea05eace33a020 /lib/vendor-document-list/table/send-to-shi-button.tsx | |
| parent | e1344a5da1aeef8fbf0f33e1dfd553078c064ccc (diff) | |
(대표님) lib 파트 커밋
Diffstat (limited to 'lib/vendor-document-list/table/send-to-shi-button.tsx')
| -rw-r--r-- | lib/vendor-document-list/table/send-to-shi-button.tsx | 342 |
1 files changed, 342 insertions, 0 deletions
diff --git a/lib/vendor-document-list/table/send-to-shi-button.tsx b/lib/vendor-document-list/table/send-to-shi-button.tsx new file mode 100644 index 00000000..e0360144 --- /dev/null +++ b/lib/vendor-document-list/table/send-to-shi-button.tsx @@ -0,0 +1,342 @@ +// 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 { useSyncStatus, useTriggerSync } from "@/hooks/use-sync-status" +import type { EnhancedDocument } from "@/types/enhanced-documents" + +interface SendToSHIButtonProps { + contractId: number + documents?: EnhancedDocument[] + onSyncComplete?: () => void +} + +export function SendToSHIButton({ + contractId, + documents = [], + onSyncComplete +}: SendToSHIButtonProps) { + const [isDialogOpen, setIsDialogOpen] = React.useState(false) + const [syncProgress, setSyncProgress] = React.useState(0) + + const { + syncStatus, + isLoading: statusLoading, + error: statusError, + refetch: refetchStatus + } = useSyncStatus(contractId, 'SHI') + + const { + triggerSync, + isLoading: isSyncing, + error: syncError + } = useTriggerSync() + + // 에러 상태 표시 + React.useEffect(() => { + if (statusError) { + console.warn('Failed to load sync status:', statusError) + } + }, [statusError]) + + const handleSync = async () => { + if (!contractId) return + + setSyncProgress(0) + + try { + // 진행률 시뮬레이션 + const progressInterval = setInterval(() => { + setSyncProgress(prev => Math.min(prev + 10, 90)) + }, 200) + + const result = await triggerSync({ + contractId, + targetSystem: 'SHI' + }) + + clearInterval(progressInterval) + setSyncProgress(100) + + setTimeout(() => { + setSyncProgress(0) + setIsDialogOpen(false) + + if (result?.success) { + toast.success( + `동기화 완료: ${result.successCount || 0}건 성공`, + { + description: result.successCount > 0 + ? `${result.successCount}개 항목이 SHI 시스템으로 전송되었습니다.` + : '전송할 새로운 변경사항이 없습니다.' + } + ) + } else { + toast.error( + `동기화 부분 실패: ${result?.successCount || 0}건 성공, ${result?.failureCount || 0}건 실패`, + { + description: result?.errors?.[0] || '일부 항목 전송에 실패했습니다.' + } + ) + } + + refetchStatus() // SWR 캐시 갱신 + onSyncComplete?.() + }, 500) + + } catch (error) { + setSyncProgress(0) + + toast.error('동기화 실패', { + description: error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.' + }) + } + } + + const getSyncStatusBadge = () => { + if (statusLoading) { + return <Badge variant="secondary">확인 중...</Badge> + } + + if (statusError) { + return <Badge variant="destructive">오류</Badge> + } + + if (!syncStatus) { + return <Badge variant="secondary">데이터 없음</Badge> + } + + if (syncStatus.pendingChanges > 0) { + return ( + <Badge variant="destructive" className="gap-1"> + <AlertTriangle className="w-3 h-3" /> + {syncStatus.pendingChanges}건 대기 + </Badge> + ) + } + + if (syncStatus.syncedChanges > 0) { + return ( + <Badge variant="default" className="gap-1 bg-green-500 hover:bg-green-600"> + <CheckCircle className="w-3 h-3" /> + 동기화됨 + </Badge> + ) + } + + return <Badge variant="secondary">변경사항 없음</Badge> + } + + const canSync = !statusError && syncStatus?.syncEnabled && syncStatus?.pendingChanges > 0 + + return ( + <> + <Popover> + <PopoverTrigger asChild> + <Button + variant="default" + size="sm" + className="gap-2 relative bg-blue-600 hover:bg-blue-700" + disabled={isSyncing || statusLoading} + > + {isSyncing ? ( + <Loader2 className="w-4 h-4 animate-spin" /> + ) : ( + <Send className="w-4 h-4" /> + )} + <span className="hidden sm:inline">Send to SHI</span> + {syncStatus?.pendingChanges > 0 && ( + <Badge + variant="destructive" + className="absolute -top-2 -right-2 h-5 w-5 p-0 text-xs flex items-center justify-center" + > + {syncStatus.pendingChanges} + </Badge> + )} + </Button> + </PopoverTrigger> + + <PopoverContent className="w-80"> + <div className="space-y-4"> + <div className="space-y-2"> + <h4 className="font-medium">SHI 동기화 상태</h4> + <div className="flex items-center justify-between"> + <span className="text-sm text-muted-foreground">현재 상태</span> + {getSyncStatusBadge()} + </div> + </div> + + {syncStatus && !statusError && ( + <div className="space-y-3"> + <Separator /> + + <div className="grid grid-cols-2 gap-4 text-sm"> + <div> + <div className="text-muted-foreground">대기 중</div> + <div className="font-medium">{syncStatus.pendingChanges || 0}건</div> + </div> + <div> + <div className="text-muted-foreground">동기화됨</div> + <div className="font-medium">{syncStatus.syncedChanges || 0}건</div> + </div> + </div> + + {syncStatus.failedChanges > 0 && ( + <div className="text-sm"> + <div className="text-muted-foreground">실패</div> + <div className="font-medium text-red-600">{syncStatus.failedChanges}건</div> + </div> + )} + + {syncStatus.lastSyncAt && ( + <div className="text-sm"> + <div className="text-muted-foreground">마지막 동기화</div> + <div className="font-medium"> + {new Date(syncStatus.lastSyncAt).toLocaleString()} + </div> + </div> + )} + </div> + )} + + {statusError && ( + <div className="space-y-2"> + <Separator /> + <div className="text-sm text-red-600"> + <div className="font-medium">연결 오류</div> + <div className="text-xs">동기화 상태를 확인할 수 없습니다.</div> + </div> + </div> + )} + + <Separator /> + + <div className="flex gap-2"> + <Button + onClick={() => setIsDialogOpen(true)} + disabled={!canSync || isSyncing} + className="flex-1" + size="sm" + > + {isSyncing ? ( + <> + <Loader2 className="w-4 h-4 mr-2 animate-spin" /> + 동기화 중... + </> + ) : ( + <> + <Send className="w-4 h-4 mr-2" /> + 지금 동기화 + </> + )} + </Button> + + <Button + variant="outline" + size="sm" + onClick={() => refetchStatus()} + disabled={statusLoading} + > + {statusLoading ? ( + <Loader2 className="w-4 h-4 animate-spin" /> + ) : ( + <Settings className="w-4 h-4" /> + )} + </Button> + </div> + </div> + </PopoverContent> + </Popover> + + {/* 동기화 진행 다이얼로그 */} + <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> + <DialogContent className="sm:max-w-md"> + <DialogHeader> + <DialogTitle>SHI 시스템으로 동기화</DialogTitle> + <DialogDescription> + 변경된 문서 데이터를 SHI 시스템으로 전송합니다. + </DialogDescription> + </DialogHeader> + + <div className="space-y-4"> + {syncStatus && !statusError && ( + <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">{syncStatus.pendingChanges || 0}건</span> + </div> + + <div className="text-xs text-muted-foreground"> + 문서, 리비전, 첨부파일의 변경사항이 포함됩니다. + </div> + + {isSyncing && ( + <div className="space-y-2"> + <div className="flex items-center justify-between text-sm"> + <span>진행률</span> + <span>{syncProgress}%</span> + </div> + <Progress value={syncProgress} className="h-2" /> + </div> + )} + </div> + )} + + {statusError && ( + <div className="rounded-lg border border-red-200 p-4"> + <div className="text-sm text-red-600"> + 동기화 상태를 확인할 수 없습니다. 네트워크 연결을 확인해주세요. + </div> + </div> + )} + + <div className="flex justify-end gap-2"> + <Button + variant="outline" + onClick={() => setIsDialogOpen(false)} + disabled={isSyncing} + > + 취소 + </Button> + <Button + onClick={handleSync} + disabled={isSyncing || !canSync} + > + {isSyncing ? ( + <> + <Loader2 className="w-4 h-4 mr-2 animate-spin" /> + 동기화 중... + </> + ) : ( + <> + <Send className="w-4 h-4 mr-2" /> + 동기화 시작 + </> + )} + </Button> + </div> + </div> + </DialogContent> + </Dialog> + </> + ) +}
\ No newline at end of file |
