From 610d3bccf1cb640e2a21df28d8d2a954c2bf337e Mon Sep 17 00:00:00 2001 From: dujinkim Date: Thu, 5 Jun 2025 01:53:35 +0000 Subject: (대표님) 변경사항 0604 - OCR 관련 및 drizzle generated sqls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/swp-workflow-panel.tsx | 370 +++++++++++++++++++++ 1 file changed, 370 insertions(+) create mode 100644 lib/vendor-document-list/table/swp-workflow-panel.tsx (limited to 'lib/vendor-document-list/table/swp-workflow-panel.tsx') diff --git a/lib/vendor-document-list/table/swp-workflow-panel.tsx b/lib/vendor-document-list/table/swp-workflow-panel.tsx new file mode 100644 index 00000000..ded306e7 --- /dev/null +++ b/lib/vendor-document-list/table/swp-workflow-panel.tsx @@ -0,0 +1,370 @@ +"use client" + +import * as React from "react" +import { Send, Eye, CheckCircle, Clock, RefreshCw, AlertTriangle, Loader2 } from "lucide-react" +import { toast } from "sonner" + +import { Button } from "@/components/ui/button" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { Badge } from "@/components/ui/badge" +import { Separator } from "@/components/ui/separator" +import { Progress } from "@/components/ui/progress" +import type { EnhancedDocument } from "@/types/enhanced-documents" + +interface SWPWorkflowPanelProps { + contractId: number + documents: EnhancedDocument[] + onWorkflowUpdate?: () => void +} + +type WorkflowStatus = + | 'IDLE' // 대기 상태 + | 'SUBMITTED' // 목록 전송됨 + | 'UNDER_REVIEW' // 검토 중 + | 'CONFIRMED' // 컨펌됨 + | 'REVISION_REQUIRED' // 수정 요청됨 + | 'RESUBMITTED' // 재전송됨 + | 'APPROVED' // 최종 승인됨 + +interface WorkflowState { + status: WorkflowStatus + lastUpdatedAt?: string + pendingActions: string[] + confirmationData?: any + revisionComments?: string[] + approvalData?: any +} + +export function SWPWorkflowPanel({ + contractId, + documents, + onWorkflowUpdate +}: SWPWorkflowPanelProps) { + const [workflowState, setWorkflowState] = React.useState(null) + const [isLoading, setIsLoading] = React.useState(false) + const [actionProgress, setActionProgress] = React.useState(0) + + // 워크플로우 상태 조회 + const fetchWorkflowStatus = async () => { + setIsLoading(true) + try { + const response = await fetch(`/api/sync/workflow/status?contractId=${contractId}&targetSystem=SWP`) + if (!response.ok) throw new Error('Failed to fetch workflow status') + + const status = await response.json() + setWorkflowState(status) + } catch (error) { + console.error('Failed to fetch workflow status:', error) + toast.error('워크플로우 상태를 확인할 수 없습니다') + } finally { + setIsLoading(false) + } + } + + // 컴포넌트 마운트 시 상태 조회 + React.useEffect(() => { + fetchWorkflowStatus() + }, [contractId]) + + // 워크플로우 액션 실행 + const executeWorkflowAction = async (action: string) => { + setActionProgress(0) + setIsLoading(true) + + try { + // 진행률 시뮬레이션 + const progressInterval = setInterval(() => { + setActionProgress(prev => Math.min(prev + 20, 90)) + }, 200) + + const response = await fetch('/api/sync/workflow/action', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + contractId, + targetSystem: 'SWP', + action, + documents: documents.map(doc => ({ id: doc.id, documentNo: doc.documentNo })) + }) + }) + + if (!response.ok) { + const errorData = await response.json() + throw new Error(errorData.message || 'Workflow action failed') + } + + const result = await response.json() + + clearInterval(progressInterval) + setActionProgress(100) + + setTimeout(() => { + setActionProgress(0) + + if (result?.success) { + toast.success( + `${getActionLabel(action)} 완료`, + { description: result?.message || '워크플로우가 성공적으로 진행되었습니다.' } + ) + } else { + toast.error( + `${getActionLabel(action)} 실패`, + { description: result?.message || '워크플로우 실행에 실패했습니다.' } + ) + } + + fetchWorkflowStatus() // 상태 갱신 + onWorkflowUpdate?.() + }, 500) + + } catch (error) { + setActionProgress(0) + + toast.error(`${getActionLabel(action)} 실패`, { + description: error instanceof Error ? error.message : '알 수 없는 오류가 발생했습니다.' + }) + } finally { + setIsLoading(false) + } + } + + const getActionLabel = (action: string): string => { + switch (action) { + case 'SUBMIT_LIST': return '목록 전송' + case 'CHECK_CONFIRMATION': return '컨펌 확인' + case 'RESUBMIT_REVISED': return '수정본 재전송' + case 'CHECK_APPROVAL': return '승인 확인' + default: return action + } + } + + const getStatusBadge = () => { + if (isLoading) { + return 확인 중... + } + + if (!workflowState) { + return 오류 + } + + switch (workflowState.status) { + case 'IDLE': + return 대기 + case 'SUBMITTED': + return ( + + + 전송됨 + + ) + case 'UNDER_REVIEW': + return ( + + + 검토 중 + + ) + case 'CONFIRMED': + return ( + + + 컨펌됨 + + ) + case 'REVISION_REQUIRED': + return ( + + + 수정 요청 + + ) + case 'RESUBMITTED': + return ( + + + 재전송됨 + + ) + case 'APPROVED': + return ( + + + 승인 완료 + + ) + default: + return 알 수 없음 + } + } + + const getAvailableActions = (): string[] => { + if (!workflowState) return [] + + switch (workflowState.status) { + case 'IDLE': + return ['SUBMIT_LIST'] + case 'SUBMITTED': + return ['CHECK_CONFIRMATION'] + case 'UNDER_REVIEW': + return ['CHECK_CONFIRMATION'] + case 'CONFIRMED': + return [] // 컨펌되면 자동으로 다음 단계로 + case 'REVISION_REQUIRED': + return ['RESUBMIT_REVISED'] + case 'RESUBMITTED': + return ['CHECK_APPROVAL'] + case 'APPROVED': + return [] // 완료 상태 + default: + return [] + } + } + + const availableActions = getAvailableActions() + + return ( + + +
+ +
+
+ + +
+
+

SWP 워크플로우 상태

+
+ 현재 상태 + {getStatusBadge()} +
+
+ + {workflowState && ( +
+ + + {/* 대기 중인 액션들 */} + {workflowState.pendingActions && workflowState.pendingActions.length > 0 && ( +
+
대기 중인 작업
+ {workflowState.pendingActions.map((action, index) => ( + + {getActionLabel(action)} + + ))} +
+ )} + + {/* 수정 요청 사항 */} + {workflowState.revisionComments && workflowState.revisionComments.length > 0 && ( +
+
수정 요청 사항
+
+ {workflowState.revisionComments.map((comment, index) => ( +
+ {comment} +
+ ))} +
+
+ )} + + {/* 마지막 업데이트 시간 */} + {workflowState.lastUpdatedAt && ( +
+
마지막 업데이트
+
+ {new Date(workflowState.lastUpdatedAt).toLocaleString()} +
+
+ )} + + {/* 진행률 표시 */} + {isLoading && actionProgress > 0 && ( +
+
+ 진행률 + {actionProgress}% +
+ +
+ )} +
+ )} + + + + {/* 액션 버튼들 */} +
+ {availableActions.length > 0 ? ( + availableActions.map((action) => ( + + )) + ) : ( +
+ {workflowState?.status === 'APPROVED' + ? '워크플로우가 완료되었습니다.' + : '실행 가능한 작업이 없습니다.'} +
+ )} + + {/* 상태 새로고침 버튼 */} + +
+
+
+
+ ) +} \ No newline at end of file -- cgit v1.2.3