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/swp-workflow-panel.tsx | |
| parent | c72d0897f7b37843109c86f61d97eba05ba3ca0d (diff) | |
(대표님) 20250613 16시 10분 global css, b-rfq, document 등
Diffstat (limited to 'lib/vendor-document-list/ship/swp-workflow-panel.tsx')
| -rw-r--r-- | lib/vendor-document-list/ship/swp-workflow-panel.tsx | 370 |
1 files changed, 0 insertions, 370 deletions
diff --git a/lib/vendor-document-list/ship/swp-workflow-panel.tsx b/lib/vendor-document-list/ship/swp-workflow-panel.tsx deleted file mode 100644 index ded306e7..00000000 --- a/lib/vendor-document-list/ship/swp-workflow-panel.tsx +++ /dev/null @@ -1,370 +0,0 @@ -"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<WorkflowState | null>(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 <Badge variant="secondary">확인 중...</Badge> - } - - if (!workflowState) { - return <Badge variant="destructive">오류</Badge> - } - - switch (workflowState.status) { - case 'IDLE': - return <Badge variant="secondary">대기</Badge> - case 'SUBMITTED': - return ( - <Badge variant="default" className="gap-1 bg-blue-500"> - <Clock className="w-3 h-3" /> - 전송됨 - </Badge> - ) - case 'UNDER_REVIEW': - return ( - <Badge variant="default" className="gap-1 bg-yellow-500"> - <Eye className="w-3 h-3" /> - 검토 중 - </Badge> - ) - case 'CONFIRMED': - return ( - <Badge variant="default" className="gap-1 bg-green-500"> - <CheckCircle className="w-3 h-3" /> - 컨펌됨 - </Badge> - ) - case 'REVISION_REQUIRED': - return ( - <Badge variant="destructive" className="gap-1"> - <AlertTriangle className="w-3 h-3" /> - 수정 요청 - </Badge> - ) - case 'RESUBMITTED': - return ( - <Badge variant="default" className="gap-1 bg-orange-500"> - <RefreshCw className="w-3 h-3" /> - 재전송됨 - </Badge> - ) - case 'APPROVED': - return ( - <Badge variant="default" className="gap-1 bg-green-600"> - <CheckCircle className="w-3 h-3" /> - 승인 완료 - </Badge> - ) - default: - return <Badge variant="secondary">알 수 없음</Badge> - } - } - - 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 ( - <Popover> - <PopoverTrigger asChild> - <div className="flex items-center gap-3"> - <Button - variant="outline" - size="sm" - className="flex items-center border-orange-200 hover:bg-orange-50" - disabled={isLoading} - > - {isLoading ? ( - <Loader2 className="w-4 h-4 animate-spin" /> - ) : ( - <RefreshCw className="w-4 h-4" /> - )} - <span className="hidden sm:inline">SWP 워크플로우</span> - {workflowState?.pendingActions && workflowState.pendingActions.length > 0 && ( - <Badge - variant="destructive" - className="h-5 w-5 p-0 text-xs flex items-center justify-center" - > - {workflowState.pendingActions.length} - </Badge> - )} - </Button> - </div> - </PopoverTrigger> - - <PopoverContent className="w-80"> - <div className="space-y-4"> - <div className="space-y-2"> - <h4 className="font-medium">SWP 워크플로우 상태</h4> - <div className="flex items-center justify-between"> - <span className="text-sm text-muted-foreground">현재 상태</span> - {getStatusBadge()} - </div> - </div> - - {workflowState && ( - <div className="space-y-3"> - <Separator /> - - {/* 대기 중인 액션들 */} - {workflowState.pendingActions && workflowState.pendingActions.length > 0 && ( - <div className="space-y-2"> - <div className="text-sm font-medium">대기 중인 작업</div> - {workflowState.pendingActions.map((action, index) => ( - <Badge key={index} variant="outline" className="mr-1"> - {getActionLabel(action)} - </Badge> - ))} - </div> - )} - - {/* 수정 요청 사항 */} - {workflowState.revisionComments && workflowState.revisionComments.length > 0 && ( - <div className="space-y-2"> - <div className="text-sm font-medium text-red-600">수정 요청 사항</div> - <div className="text-xs text-muted-foreground space-y-1"> - {workflowState.revisionComments.map((comment, index) => ( - <div key={index} className="p-2 bg-red-50 rounded text-red-700"> - {comment} - </div> - ))} - </div> - </div> - )} - - {/* 마지막 업데이트 시간 */} - {workflowState.lastUpdatedAt && ( - <div className="text-sm"> - <div className="text-muted-foreground">마지막 업데이트</div> - <div className="font-medium"> - {new Date(workflowState.lastUpdatedAt).toLocaleString()} - </div> - </div> - )} - - {/* 진행률 표시 */} - {isLoading && actionProgress > 0 && ( - <div className="space-y-2"> - <div className="flex items-center justify-between text-sm"> - <span>진행률</span> - <span>{actionProgress}%</span> - </div> - <Progress value={actionProgress} className="h-2" /> - </div> - )} - </div> - )} - - <Separator /> - - {/* 액션 버튼들 */} - <div className="space-y-2"> - {availableActions.length > 0 ? ( - availableActions.map((action) => ( - <Button - key={action} - onClick={() => executeWorkflowAction(action)} - disabled={isLoading} - className="w-full justify-start" - size="sm" - variant={action.includes('SUBMIT') || action.includes('RESUBMIT') ? 'default' : 'outline'} - > - {isLoading ? ( - <Loader2 className="w-4 h-4 mr-2 animate-spin" /> - ) : ( - <Send className="w-4 h-4 mr-2" /> - )} - {getActionLabel(action)} - </Button> - )) - ) : ( - <div className="text-sm text-muted-foreground text-center py-2"> - {workflowState?.status === 'APPROVED' - ? '워크플로우가 완료되었습니다.' - : '실행 가능한 작업이 없습니다.'} - </div> - )} - - {/* 상태 새로고침 버튼 */} - <Button - variant="outline" - size="sm" - onClick={fetchWorkflowStatus} - disabled={isLoading} - className="w-full" - > - {isLoading ? ( - <Loader2 className="w-4 h-4 mr-2 animate-spin" /> - ) : ( - <RefreshCw className="w-4 h-4 mr-2" /> - )} - 상태 새로고침 - </Button> - </div> - </div> - </PopoverContent> - </Popover> - ) -}
\ No newline at end of file |
