diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-13 07:11:18 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-13 07:11:18 +0000 |
| commit | 0fddf148402fd6b99a1b3800d73679899bcb2ed3 (patch) | |
| tree | eb51c02e6fa6037ddcc38a3b57d10d8c739125cf /lib/vendor-document-list/ship/import-from-dolce-button.tsx | |
| parent | c72d0897f7b37843109c86f61d97eba05ba3ca0d (diff) | |
(대표님) 20250613 16시 10분 global css, 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 | 258 |
1 files changed, 185 insertions, 73 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 index 519d40cb..23d80981 100644 --- a/lib/vendor-document-list/ship/import-from-dolce-button.tsx +++ b/lib/vendor-document-list/ship/import-from-dolce-button.tsx @@ -20,52 +20,87 @@ import { import { Badge } from "@/components/ui/badge" import { Progress } from "@/components/ui/progress" import { Separator } from "@/components/ui/separator" +import { SimplifiedDocumentsView } from "@/db/schema" +import { ImportStatus } from "../import-service" interface ImportFromDOLCEButtonProps { - contractId: number + allDocuments: SimplifiedDocumentsView[] // contractId 대신 문서 배열 onImportComplete?: () => void } -interface ImportStatus { - lastImportAt?: string - availableDocuments: number - newDocuments: number - updatedDocuments: number - importEnabled: boolean -} - export function ImportFromDOLCEButton({ - contractId, + allDocuments, 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 [importStatusMap, setImportStatusMap] = React.useState<Map<number, ImportStatus>>(new Map()) const [statusLoading, setStatusLoading] = React.useState(false) - // DOLCE 상태 조회 - const fetchImportStatus = async () => { + // 문서들에서 contractId들 추출 + const contractIds = React.useMemo(() => { + const uniqueIds = [...new Set(allDocuments.map(doc => doc.contractId).filter(Boolean))] + return uniqueIds.sort() + }, [allDocuments]) + + console.log(contractIds, "contractIds") + + // 주요 contractId (가장 많이 나타나는 것) + const primaryContractId = React.useMemo(() => { + if (contractIds.length === 1) return contractIds[0] + + const counts = allDocuments.reduce((acc, doc) => { + const id = doc.contractId || 0 + acc[id] = (acc[id] || 0) + 1 + return acc + }, {} as Record<number, number>) + + return Number(Object.entries(counts) + .sort(([,a], [,b]) => b - a)[0]?.[0] || contractIds[0] || 0) + }, [contractIds, allDocuments]) + + // 모든 contractId에 대한 상태 조회 + const fetchAllImportStatus = async () => { setStatusLoading(true) + const statusMap = new Map<number, ImportStatus>() + 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') - } + // 각 contractId별로 상태 조회 + const statusPromises = contractIds.map(async (contractId) => { + 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() + if (status.error) { + console.warn(`Status error for contract ${contractId}:`, status.error) + return { contractId, status: null } + } + + return { contractId, status } + } catch (error) { + console.error(`Failed to fetch status for contract ${contractId}:`, error) + return { contractId, status: null } + } + }) + + const results = await Promise.all(statusPromises) + + results.forEach(({ contractId, status }) => { + if (status) { + statusMap.set(contractId, status) + } + }) - const status = await response.json() - setImportStatus(status) + setImportStatusMap(statusMap) - // 프로젝트 코드가 없는 경우 에러 처리 - if (status.error) { - toast.error(`상태 확인 실패: ${status.error}`) - setImportStatus(null) - } } catch (error) { - console.error('Failed to fetch import status:', error) - toast.error('DOLCE 상태를 확인할 수 없습니다. 프로젝트 설정을 확인해주세요.') - setImportStatus(null) + console.error('Failed to fetch import statuses:', error) + toast.error('상태를 확인할 수 없습니다. 프로젝트 설정을 확인해주세요.') } finally { setStatusLoading(false) } @@ -73,11 +108,32 @@ export function ImportFromDOLCEButton({ // 컴포넌트 마운트 시 상태 조회 React.useEffect(() => { - fetchImportStatus() - }, [contractId]) + if (contractIds.length > 0) { + fetchAllImportStatus() + } + }, [contractIds]) + + // 주요 contractId의 상태 + const primaryImportStatus = importStatusMap.get(primaryContractId) + + // 전체 통계 계산 + const totalStats = React.useMemo(() => { + const statuses = Array.from(importStatusMap.values()) + return statuses.reduce((acc, status) => ({ + availableDocuments: acc.availableDocuments + (status.availableDocuments || 0), + newDocuments: acc.newDocuments + (status.newDocuments || 0), + updatedDocuments: acc.updatedDocuments + (status.updatedDocuments || 0), + importEnabled: acc.importEnabled || status.importEnabled + }), { + availableDocuments: 0, + newDocuments: 0, + updatedDocuments: 0, + importEnabled: false + }) + }, [importStatusMap]) const handleImport = async () => { - if (!contractId) return + if (contractIds.length === 0) return setImportProgress(0) setIsImporting(true) @@ -85,51 +141,68 @@ export function ImportFromDOLCEButton({ 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' + setImportProgress(prev => Math.min(prev + 10, 85)) + }, 500) + + // 여러 contractId에 대해 순차적으로 가져오기 실행 + const importPromises = contractIds.map(async (contractId) => { + 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') - } + if (!response.ok) { + const errorData = await response.json() + throw new Error(`Contract ${contractId}: ${errorData.message || 'Import failed'}`) + } + + return response.json() + }) - const result = await response.json() + const results = await Promise.all(importPromises) clearInterval(progressInterval) setImportProgress(100) + // 결과 집계 + const totalResult = results.reduce((acc, result) => ({ + newCount: acc.newCount + (result.newCount || 0), + updatedCount: acc.updatedCount + (result.updatedCount || 0), + skippedCount: acc.skippedCount + (result.skippedCount || 0), + success: acc.success && result.success + }), { + newCount: 0, + updatedCount: 0, + skippedCount: 0, + success: true + }) + setTimeout(() => { setImportProgress(0) setIsDialogOpen(false) setIsImporting(false) - if (result?.success) { - const { newCount = 0, updatedCount = 0, skippedCount = 0 } = result + if (totalResult.success) { toast.success( `DOLCE 가져오기 완료`, { - description: `신규 ${newCount}건, 업데이트 ${updatedCount}건, 건너뜀 ${skippedCount}건 (B3/B4/B5 포함)` + description: `신규 ${totalResult.newCount}건, 업데이트 ${totalResult.updatedCount}건, 건너뜀 ${totalResult.skippedCount}건 (${contractIds.length}개 계약)` } ) } else { toast.error( `DOLCE 가져오기 부분 실패`, { - description: result?.message || '일부 DrawingKind에서 가져오기에 실패했습니다.' + description: '일부 계약에서 가져오기에 실패했습니다.' } ) } - fetchImportStatus() // 상태 갱신 + fetchAllImportStatus() // 상태 갱신 onImportComplete?.() }, 500) @@ -148,19 +221,19 @@ export function ImportFromDOLCEButton({ return <Badge variant="secondary">DOLCE 연결 확인 중...</Badge> } - if (!importStatus) { + if (importStatusMap.size === 0) { return <Badge variant="destructive">DOLCE 연결 오류</Badge> } - if (!importStatus.importEnabled) { + if (!totalStats.importEnabled) { return <Badge variant="secondary">DOLCE 가져오기 비활성화</Badge> } - if (importStatus.newDocuments > 0 || importStatus.updatedDocuments > 0) { + if (totalStats.newDocuments > 0 || totalStats.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) + 업데이트 가능 ({contractIds.length}개 계약) </Badge> ) } @@ -173,8 +246,12 @@ export function ImportFromDOLCEButton({ ) } - const canImport = importStatus?.importEnabled && - (importStatus?.newDocuments > 0 || importStatus?.updatedDocuments > 0) + const canImport = totalStats.importEnabled && + (totalStats.newDocuments > 0 || totalStats.updatedDocuments > 0) + + if (contractIds.length === 0) { + return null // 계약이 없으면 버튼을 표시하지 않음 + } return ( <> @@ -193,19 +270,19 @@ export function ImportFromDOLCEButton({ <Download className="w-4 h-4" /> )} <span className="hidden sm:inline">DOLCE에서 가져오기</span> - {importStatus && (importStatus.newDocuments > 0 || importStatus.updatedDocuments > 0) && ( + {totalStats.newDocuments + totalStats.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} + {totalStats.newDocuments + totalStats.updatedDocuments} </Badge> )} </Button> </div> </PopoverTrigger> - <PopoverContent className="w-80"> + <PopoverContent className="w-96"> <div className="space-y-4"> <div className="space-y-2"> <h4 className="font-medium">DOLCE 가져오기 상태</h4> @@ -215,33 +292,61 @@ export function ImportFromDOLCEButton({ </div> </div> - {importStatus && ( + {/* 다중 계약 정보 표시 */} + {contractIds.length > 1 && ( + <div className="text-sm"> + <div className="text-muted-foreground">대상 계약</div> + <div className="font-medium">{contractIds.length}개 계약</div> + <div className="text-xs text-muted-foreground"> + Contract IDs: {contractIds.join(', ')} + </div> + </div> + )} + + {totalStats && ( <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 className="font-medium">{totalStats.newDocuments || 0}건</div> </div> <div> <div className="text-muted-foreground">업데이트</div> - <div className="font-medium">{importStatus.updatedDocuments || 0}건</div> + <div className="font-medium">{totalStats.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 className="font-medium">{totalStats.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()} + {/* 각 계약별 세부 정보 (펼치기/접기 가능) */} + {contractIds.length > 1 && ( + <details className="text-sm"> + <summary className="cursor-pointer text-muted-foreground hover:text-foreground"> + 계약별 세부 정보 + </summary> + <div className="mt-2 space-y-2 pl-2 border-l-2 border-muted"> + {contractIds.map(contractId => { + const status = importStatusMap.get(contractId) + return ( + <div key={contractId} className="text-xs"> + <div className="font-medium">Contract {contractId}</div> + {status ? ( + <div className="text-muted-foreground"> + 신규 {status.newDocuments}건, 업데이트 {status.updatedDocuments}건 + </div> + ) : ( + <div className="text-destructive">상태 확인 실패</div> + )} + </div> + ) + })} </div> - </div> + </details> )} </div> )} @@ -271,7 +376,7 @@ export function ImportFromDOLCEButton({ <Button variant="outline" size="sm" - onClick={fetchImportStatus} + onClick={fetchAllImportStatus} disabled={statusLoading} > {statusLoading ? ( @@ -292,16 +397,17 @@ export function ImportFromDOLCEButton({ <DialogTitle>DOLCE에서 문서 목록 가져오기</DialogTitle> <DialogDescription> 삼성중공업 DOLCE 시스템에서 최신 문서 목록을 가져옵니다. + {contractIds.length > 1 && ` (${contractIds.length}개 계약 대상)`} </DialogDescription> </DialogHeader> <div className="space-y-4"> - {importStatus && ( + {totalStats && ( <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)}건 + {totalStats.newDocuments + totalStats.updatedDocuments}건 </span> </div> @@ -309,6 +415,12 @@ export function ImportFromDOLCEButton({ 신규 문서와 업데이트된 문서가 포함됩니다. (B3, B4, B5) <br /> B4 문서의 경우 GTTPreDwg, GTTWorkingDwg 이슈 스테이지가 자동 생성됩니다. + {contractIds.length > 1 && ( + <> + <br /> + {contractIds.length}개 계약에서 순차적으로 가져옵니다. + </> + )} </div> {isImporting && ( |
