diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-13 07:08:01 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-13 07:08:01 +0000 |
| commit | c72d0897f7b37843109c86f61d97eba05ba3ca0d (patch) | |
| tree | 887dd877f3f8beafa92b4d9a7b16c84b4a5795d8 /lib/vendor-document-list/ship/import-from-dolce-button.tsx | |
| parent | ff902243a658067fae858a615c0629aa2e0a4837 (diff) | |
(대표님) 20250613 16시 08분 b-rfq, document 등
Diffstat (limited to 'lib/vendor-document-list/ship/import-from-dolce-button.tsx')
| -rw-r--r-- | lib/vendor-document-list/ship/import-from-dolce-button.tsx | 356 |
1 files changed, 356 insertions, 0 deletions
diff --git a/lib/vendor-document-list/ship/import-from-dolce-button.tsx b/lib/vendor-document-list/ship/import-from-dolce-button.tsx new file mode 100644 index 00000000..519d40cb --- /dev/null +++ b/lib/vendor-document-list/ship/import-from-dolce-button.tsx @@ -0,0 +1,356 @@ +"use client" + +import * as React from "react" +import { RefreshCw, Download, Loader2, CheckCircle, AlertTriangle } 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" + +interface ImportFromDOLCEButtonProps { + contractId: number + onImportComplete?: () => void +} + +interface ImportStatus { + lastImportAt?: string + availableDocuments: number + newDocuments: number + updatedDocuments: number + importEnabled: boolean +} + +export function ImportFromDOLCEButton({ + contractId, + onImportComplete +}: ImportFromDOLCEButtonProps) { + const [isDialogOpen, setIsDialogOpen] = React.useState(false) + const [importProgress, setImportProgress] = React.useState(0) + const [isImporting, setIsImporting] = React.useState(false) + const [importStatus, setImportStatus] = React.useState<ImportStatus | null>(null) + const [statusLoading, setStatusLoading] = React.useState(false) + + // DOLCE 상태 조회 + const fetchImportStatus = async () => { + setStatusLoading(true) + try { + const response = await fetch(`/api/sync/import/status?contractId=${contractId}&sourceSystem=DOLCE`) + if (!response.ok) { + const errorData = await response.json().catch(() => ({})) + throw new Error(errorData.message || 'Failed to fetch import status') + } + + const status = await response.json() + setImportStatus(status) + + // 프로젝트 코드가 없는 경우 에러 처리 + if (status.error) { + toast.error(`상태 확인 실패: ${status.error}`) + setImportStatus(null) + } + } catch (error) { + console.error('Failed to fetch import status:', error) + toast.error('DOLCE 상태를 확인할 수 없습니다. 프로젝트 설정을 확인해주세요.') + setImportStatus(null) + } finally { + setStatusLoading(false) + } + } + + // 컴포넌트 마운트 시 상태 조회 + React.useEffect(() => { + fetchImportStatus() + }, [contractId]) + + const handleImport = async () => { + if (!contractId) return + + setImportProgress(0) + setIsImporting(true) + + try { + // 진행률 시뮬레이션 + const progressInterval = setInterval(() => { + setImportProgress(prev => Math.min(prev + 15, 90)) + }, 300) + + const response = await fetch('/api/sync/import', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + contractId, + sourceSystem: 'DOLCE' + }) + }) + + if (!response.ok) { + const errorData = await response.json() + throw new Error(errorData.message || 'Import failed') + } + + const result = await response.json() + + clearInterval(progressInterval) + setImportProgress(100) + + setTimeout(() => { + setImportProgress(0) + setIsDialogOpen(false) + setIsImporting(false) + + if (result?.success) { + const { newCount = 0, updatedCount = 0, skippedCount = 0 } = result + toast.success( + `DOLCE 가져오기 완료`, + { + description: `신규 ${newCount}건, 업데이트 ${updatedCount}건, 건너뜀 ${skippedCount}건 (B3/B4/B5 포함)` + } + ) + } else { + toast.error( + `DOLCE 가져오기 부분 실패`, + { + description: result?.message || '일부 DrawingKind에서 가져오기에 실패했습니다.' + } + ) + } + + fetchImportStatus() // 상태 갱신 + onImportComplete?.() + }, 500) + + } catch (error) { + setImportProgress(0) + setIsImporting(false) + + toast.error('DOLCE 가져오기 실패', { + description: error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.' + }) + } + } + + const getStatusBadge = () => { + if (statusLoading) { + return <Badge variant="secondary">DOLCE 연결 확인 중...</Badge> + } + + if (!importStatus) { + return <Badge variant="destructive">DOLCE 연결 오류</Badge> + } + + if (!importStatus.importEnabled) { + return <Badge variant="secondary">DOLCE 가져오기 비활성화</Badge> + } + + if (importStatus.newDocuments > 0 || importStatus.updatedDocuments > 0) { + return ( + <Badge variant="default" className="gap-1 bg-blue-500 hover:bg-blue-600"> + <AlertTriangle className="w-3 h-3" /> + 업데이트 가능 (B3/B4/B5) + </Badge> + ) + } + + return ( + <Badge variant="default" className="gap-1 bg-green-500 hover:bg-green-600"> + <CheckCircle className="w-3 h-3" /> + DOLCE와 동기화됨 + </Badge> + ) + } + + const canImport = importStatus?.importEnabled && + (importStatus?.newDocuments > 0 || importStatus?.updatedDocuments > 0) + + return ( + <> + <Popover> + <PopoverTrigger asChild> + <div className="flex items-center gap-3"> + <Button + variant="outline" + size="sm" + className="flex items-center border-blue-200 hover:bg-blue-50" + disabled={isImporting || statusLoading} + > + {isImporting ? ( + <Loader2 className="w-4 h-4 animate-spin" /> + ) : ( + <Download className="w-4 h-4" /> + )} + <span className="hidden sm:inline">DOLCE에서 가져오기</span> + {importStatus && (importStatus.newDocuments > 0 || importStatus.updatedDocuments > 0) && ( + <Badge + variant="default" + className="h-5 w-5 p-0 text-xs flex items-center justify-center bg-blue-500" + > + {importStatus.newDocuments + importStatus.updatedDocuments} + </Badge> + )} + </Button> + </div> + </PopoverTrigger> + + <PopoverContent className="w-80"> + <div className="space-y-4"> + <div className="space-y-2"> + <h4 className="font-medium">DOLCE 가져오기 상태</h4> + <div className="flex items-center justify-between"> + <span className="text-sm text-muted-foreground">현재 상태</span> + {getStatusBadge()} + </div> + </div> + + {importStatus && ( + <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">{importStatus.newDocuments || 0}건</div> + </div> + <div> + <div className="text-muted-foreground">업데이트</div> + <div className="font-medium">{importStatus.updatedDocuments || 0}건</div> + </div> + </div> + + <div className="text-sm"> + <div className="text-muted-foreground">DOLCE 전체 문서 (B3/B4/B5)</div> + <div className="font-medium">{importStatus.availableDocuments || 0}건</div> + </div> + + {importStatus.lastImportAt && ( + <div className="text-sm"> + <div className="text-muted-foreground">마지막 가져오기</div> + <div className="font-medium"> + {new Date(importStatus.lastImportAt).toLocaleString()} + </div> + </div> + )} + </div> + )} + + <Separator /> + + <div className="flex gap-2"> + <Button + onClick={() => setIsDialogOpen(true)} + disabled={!canImport || isImporting} + className="flex-1" + size="sm" + > + {isImporting ? ( + <> + <Loader2 className="w-4 h-4 mr-2 animate-spin" /> + 가져오는 중... + </> + ) : ( + <> + <Download className="w-4 h-4 mr-2" /> + 지금 가져오기 + </> + )} + </Button> + + <Button + variant="outline" + size="sm" + onClick={fetchImportStatus} + disabled={statusLoading} + > + {statusLoading ? ( + <Loader2 className="w-4 h-4 animate-spin" /> + ) : ( + <RefreshCw className="w-4 h-4" /> + )} + </Button> + </div> + </div> + </PopoverContent> + </Popover> + + {/* 가져오기 진행 다이얼로그 */} + <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}> + <DialogContent className="sm:max-w-md"> + <DialogHeader> + <DialogTitle>DOLCE에서 문서 목록 가져오기</DialogTitle> + <DialogDescription> + 삼성중공업 DOLCE 시스템에서 최신 문서 목록을 가져옵니다. + </DialogDescription> + </DialogHeader> + + <div className="space-y-4"> + {importStatus && ( + <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"> + {(importStatus.newDocuments || 0) + (importStatus.updatedDocuments || 0)}건 + </span> + </div> + + <div className="text-xs text-muted-foreground"> + 신규 문서와 업데이트된 문서가 포함됩니다. (B3, B4, B5) + <br /> + B4 문서의 경우 GTTPreDwg, GTTWorkingDwg 이슈 스테이지가 자동 생성됩니다. + </div> + + {isImporting && ( + <div className="space-y-2"> + <div className="flex items-center justify-between text-sm"> + <span>진행률</span> + <span>{importProgress}%</span> + </div> + <Progress value={importProgress} className="h-2" /> + </div> + )} + </div> + )} + + <div className="flex justify-end gap-2"> + <Button + variant="outline" + onClick={() => setIsDialogOpen(false)} + disabled={isImporting} + > + 취소 + </Button> + <Button + onClick={handleImport} + disabled={isImporting || !canImport} + > + {isImporting ? ( + <> + <Loader2 className="w-4 h-4 mr-2 animate-spin" /> + 가져오는 중... + </> + ) : ( + <> + <Download className="w-4 h-4 mr-2" /> + 가져오기 시작 + </> + )} + </Button> + </div> + </div> + </DialogContent> + </Dialog> + </> + ) +}
\ No newline at end of file |
