From 20800b214145ee6056f94ca18fa1054f145eb977 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Wed, 28 May 2025 00:32:31 +0000
Subject: (대표님) lib 파트 커밋
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../table/send-to-shi-button.tsx                   | 342 +++++++++++++++++++++
 1 file changed, 342 insertions(+)
 create mode 100644 lib/vendor-document-list/table/send-to-shi-button.tsx

(limited to 'lib/vendor-document-list/table/send-to-shi-button.tsx')

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
-- 
cgit v1.2.3

