summaryrefslogtreecommitdiff
path: root/lib/vendor-document-list/ship/send-to-shi-button.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-document-list/ship/send-to-shi-button.tsx')
-rw-r--r--lib/vendor-document-list/ship/send-to-shi-button.tsx348
1 files changed, 348 insertions, 0 deletions
diff --git a/lib/vendor-document-list/ship/send-to-shi-button.tsx b/lib/vendor-document-list/ship/send-to-shi-button.tsx
new file mode 100644
index 00000000..1a27a794
--- /dev/null
+++ b/lib/vendor-document-list/ship/send-to-shi-button.tsx
@@ -0,0 +1,348 @@
+// 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
+ projectType: "ship" | "plant"
+}
+
+export function SendToSHIButton({
+ contractId,
+ documents = [],
+ onSyncComplete,
+ projectType
+}: SendToSHIButtonProps) {
+ const [isDialogOpen, setIsDialogOpen] = React.useState(false)
+ const [syncProgress, setSyncProgress] = React.useState(0)
+
+ const targetSystem = projectType === 'ship'?"DOLCE":"SWP"
+
+ const {
+ syncStatus,
+ isLoading: statusLoading,
+ error: statusError,
+ refetch: refetchStatus
+ } = useSyncStatus(contractId, targetSystem)
+
+ 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
+ })
+
+ 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>
+ <div className="flex items-center gap-3">
+ <Button
+ variant="default"
+ size="sm"
+ className="flex items-center 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="h-5 w-5 p-0 text-xs flex items-center justify-center"
+ >
+ {syncStatus.pendingChanges}
+ </Badge>
+ )}
+ </Button>
+ </div>
+ </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