From c92bd1b8caa6ddabe6acee42018262febd5d91fb Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Wed, 19 Nov 2025 06:15:43 +0000
Subject: (임수민) 기본계약 코멘트, 법무검토 수정
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 ...basic-contract-detail-table-toolbar-actions.tsx | 225 ++++++++++++++++++++-
 1 file changed, 221 insertions(+), 4 deletions(-)

(limited to 'lib/basic-contract/status-detail/basic-contract-detail-table-toolbar-actions.tsx')

diff --git a/lib/basic-contract/status-detail/basic-contract-detail-table-toolbar-actions.tsx b/lib/basic-contract/status-detail/basic-contract-detail-table-toolbar-actions.tsx
index c71be9d1..3e965fac 100644
--- a/lib/basic-contract/status-detail/basic-contract-detail-table-toolbar-actions.tsx
+++ b/lib/basic-contract/status-detail/basic-contract-detail-table-toolbar-actions.tsx
@@ -2,7 +2,7 @@
 
 import * as React from "react"
 import { type Table } from "@tanstack/react-table"
-import { Download, FileDown, Mail, CheckCircle, AlertTriangle, Send, Check, FileSignature } from "lucide-react"
+import { Download, FileDown, Mail, CheckCircle, AlertTriangle, Send, Check, FileSignature, FileText, ExternalLink, Globe } from "lucide-react"
 
 import { exportTableToExcel } from "@/lib/export"
 import { downloadFile } from "@/lib/file-download"
@@ -18,15 +18,16 @@ import {
     DialogTitle,
 } from "@/components/ui/dialog"
 import { Badge } from "@/components/ui/badge"
-import { prepareFinalApprovalAction, quickFinalApprovalAction, resendContractsAction } from "../service"
+import { prepareFinalApprovalAction, quickFinalApprovalAction, resendContractsAction, updateLegalReviewStatusFromSSLVW } from "../service"
 import { BasicContractSignDialog } from "../vendor-table/basic-contract-sign-dialog"
 import { SSLVWPurInqReqDialog } from "@/components/common/legal/sslvw-pur-inq-req-dialog"
 
 interface BasicContractDetailTableToolbarActionsProps {
     table: Table<BasicContractView>
+    gtcData?: Record<number, { gtcDocumentId: number | null; hasComments: boolean }>
 }
 
-export function BasicContractDetailTableToolbarActions({ table }: BasicContractDetailTableToolbarActionsProps) {
+export function BasicContractDetailTableToolbarActions({ table, gtcData = {} }: BasicContractDetailTableToolbarActionsProps) {
     // 선택된 행들 가져오기
     const selectedRows = table.getSelectedRowModel().rows
     const hasSelectedRows = selectedRows.length > 0
@@ -34,6 +35,7 @@ export function BasicContractDetailTableToolbarActions({ table }: BasicContractD
     // 다이얼로그 상태
     const [resendDialog, setResendDialog] = React.useState(false)
     const [finalApproveDialog, setFinalApproveDialog] = React.useState(false)
+    const [legalReviewDialog, setLegalReviewDialog] = React.useState(false)
     const [loading, setLoading] = React.useState(false)
     const [buyerSignDialog, setBuyerSignDialog] = React.useState(false)
     const [contractsToSign, setContractsToSign] = React.useState<any[]>([])
@@ -56,6 +58,42 @@ export function BasicContractDetailTableToolbarActions({ table }: BasicContractD
         return true;
     });
 
+    // 법무검토 요청 가능 여부
+    // 1. 협의 완료됨 (negotiationCompletedAt 있음) OR
+    // 2. 협의 없음 (코멘트 없음, hasComments: false)
+    // 협의 중 (negotiationCompletedAt 없고 코멘트 있음)은 불가
+    const canRequestLegalReview = hasSelectedRows && selectedRows.some(row => {
+        const contract = row.original;
+        // 이미 법무검토 요청된 계약서는 제외
+        if (contract.legalReviewRequestedAt) {
+            return false;
+        }
+        // 이미 최종승인 완료된 계약서는 제외
+        if (contract.completedAt) {
+            return false;
+        }
+        
+        // 협의 완료된 경우 → 가능
+        if (contract.negotiationCompletedAt) {
+            return true;
+        }
+        
+        // 협의 완료되지 않은 경우
+        // GTC 템플릿인 경우 코멘트 존재 여부 확인
+        if (contract.templateName?.includes('GTC')) {
+            const contractGtcData = gtcData[contract.id];
+            // 코멘트가 없으면 가능 (협의 없음)
+            if (contractGtcData && !contractGtcData.hasComments) {
+                return true;
+            }
+            // 코멘트가 있으면 불가 (협의 중)
+            return false;
+        }
+        
+        // GTC가 아닌 경우는 협의 완료 여부만 확인
+        return false;
+    });
+
     // 필터링된 계약서들 계산
     const resendContracts = selectedRows.map(row => row.original)
 
@@ -75,6 +113,40 @@ export function BasicContractDetailTableToolbarActions({ table }: BasicContractD
         !contract.legalReviewRequestedAt && !contract.legalReviewCompletedAt
     );
 
+    // 법무검토 요청 가능한 계약서들
+    const legalReviewContracts = selectedRows
+        .map(row => row.original)
+        .filter(contract => {
+            // 이미 법무검토 요청됨
+            if (contract.legalReviewRequestedAt) {
+                return false;
+            }
+            // 이미 최종승인 완료됨
+            if (contract.completedAt) {
+                return false;
+            }
+            
+            // 협의 완료된 경우
+            if (contract.negotiationCompletedAt) {
+                return true;
+            }
+            
+            // 협의 완료되지 않은 경우
+            // GTC 템플릿인 경우 코멘트 없으면 가능
+            if (contract.templateName?.includes('GTC')) {
+                const contractGtcData = gtcData[contract.id];
+                // 코멘트가 없으면 가능 (협의 없음)
+                if (contractGtcData && !contractGtcData.hasComments) {
+                    return true;
+                }
+                // 코멘트가 있으면 불가 (협의 중)
+                return false;
+            }
+            
+            // GTC가 아닌 경우는 협의 완료 여부만 확인
+            return false;
+        });
+
     // 대량 재발송
     const handleBulkResend = async () => {
         if (!hasSelectedRows) {
@@ -252,6 +324,42 @@ export function BasicContractDetailTableToolbarActions({ table }: BasicContractD
         toast.success("모든 계약서의 최종승인이 완료되었습니다!")
     }
 
+    // SSLVW 데이터 선택 확인 핸들러
+    const handleSSLVWConfirm = async (selectedSSLVWData: any[]) => {
+        if (!selectedSSLVWData || selectedSSLVWData.length === 0) {
+            toast.error("선택된 데이터가 없습니다.")
+            return
+        }
+
+        try {
+            setLoading(true)
+
+            // 선택된 계약서 ID들 추출
+            const selectedContractIds = selectedRows.map(row => row.original.id)
+
+            // 서버 액션 호출
+            const result = await updateLegalReviewStatusFromSSLVW(selectedSSLVWData, selectedContractIds)
+
+            if (result.success) {
+                toast.success(result.message)
+                // 테이블 데이터 갱신
+                table.toggleAllPageRowsSelected(false)
+            } else {
+                toast.error(result.message)
+            }
+
+            if (result.errors && result.errors.length > 0) {
+                toast.warning(`일부 처리 실패: ${result.errors.join(', ')}`)
+            }
+
+        } catch (error) {
+            console.error('SSLVW 확인 처리 실패:', error)
+            toast.error('법무검토 상태 업데이트 중 오류가 발생했습니다.')
+        } finally {
+            setLoading(false)
+        }
+    }
+
     // 빠른 승인 (서명 없이)
     const confirmQuickApproval = async () => {
         setLoading(true)
@@ -275,6 +383,45 @@ export function BasicContractDetailTableToolbarActions({ table }: BasicContractD
         }
     }
 
+    // 법무검토 요청 링크 목록
+    const legalReviewLinks = [
+        {
+            id: 'domestic-contract',
+            label: '국내계약',
+            url: 'http://60.101.208.95:8080/#/pjt/register-inquiry/domestic-contract',
+            description: '삼성중공업 법무관리시스템 - 국내계약'
+        },
+        {
+            id: 'domestic-advice',
+            label: '국내자문',
+            url: 'http://60.101.208.95:8080/#/pjt/register-inquiry/domestic-advice',
+            description: '삼성중공업 법무관리시스템 - 국내자문'
+        },
+        {
+            id: 'overseas-contract',
+            label: '해외계약',
+            url: 'http://60.101.208.95:8080/#/pjt/register-inquiry/overseas-contract',
+            description: '삼성중공업 법무관리시스템 - 해외계약'
+        },
+        {
+            id: 'overseas-advice',
+            label: '해외자문',
+            url: 'http://60.101.208.95:8080/#/pjt/register-inquiry/overseas-advice',
+            description: '삼성중공업 법무관리시스템 - 해외자문'
+        }
+    ]
+
+    // 법무검토 요청
+    const handleRequestLegalReview = () => {
+        setLegalReviewDialog(true)
+    }
+
+    // 법무검토 링크 클릭 핸들러
+    const handleLegalReviewLinkClick = (url: string) => {
+        window.open(url, '_blank', 'noopener,noreferrer')
+        setLegalReviewDialog(false)
+    }
+
     return (
         <>
             <div className="flex items-center gap-2">
@@ -314,7 +461,21 @@ export function BasicContractDetailTableToolbarActions({ table }: BasicContractD
                 </Button>
 
                 {/* 법무검토 버튼 (SSLVW 데이터 조회) */}
-                <SSLVWPurInqReqDialog />
+                <SSLVWPurInqReqDialog onConfirm={handleSSLVWConfirm} />
+
+                {/* 법무검토 요청 버튼 */}
+                <Button
+                    variant="outline"
+                    size="sm"
+                    onClick={handleRequestLegalReview}
+                    className="gap-2"
+                    title="법무검토 요청 링크 선택"
+                >
+                    <FileText className="size-4" aria-hidden="true" />
+                    <span className="hidden sm:inline">
+                        법무검토 요청
+                    </span>
+                </Button>
 
                 {/* 최종승인 버튼 */}
                 <Button
@@ -413,6 +574,62 @@ export function BasicContractDetailTableToolbarActions({ table }: BasicContractD
                 </DialogContent>
             </Dialog>
 
+            {/* 법무검토 요청 다이얼로그 */}
+            <Dialog open={legalReviewDialog} onOpenChange={setLegalReviewDialog}>
+                <DialogContent className="max-w-2xl">
+                    <DialogHeader>
+                        <DialogTitle className="flex items-center gap-2">
+                            <FileText className="size-5" />
+                            법무검토 요청
+                        </DialogTitle>
+                        <DialogDescription>
+                            법무검토 요청 유형을 선택하세요. 선택한 링크가 새 창에서 열립니다.
+                        </DialogDescription>
+                    </DialogHeader>
+
+                    <div className="space-y-4">
+                        <div className="flex items-start gap-3 p-4 bg-blue-50 border border-blue-200 rounded-lg">
+                            <Globe className="size-5 text-blue-600 flex-shrink-0 mt-0.5" />
+                            <div>
+                                <div className="font-medium text-blue-800">삼성중공업 법무관리시스템</div>
+                                <div className="text-sm text-blue-700 mt-1">
+                                    아래 링크 중 해당하는 유형을 선택하여 법무검토를 요청하세요.
+                                </div>
+                            </div>
+                        </div>
+
+                        <div className="space-y-2">
+                            {legalReviewLinks.map((link) => (
+                                <button
+                                    key={link.id}
+                                    onClick={() => handleLegalReviewLinkClick(link.url)}
+                                    className="w-full flex items-center justify-between p-4 rounded-lg border border-gray-200 hover:border-blue-300 hover:bg-blue-50 transition-colors text-left group"
+                                >
+                                    <div className="flex-1">
+                                        <div className="font-medium text-gray-900 group-hover:text-blue-700">
+                                            {link.label}
+                                        </div>
+                                        <div className="text-sm text-gray-500 mt-1">
+                                            {link.description}
+                                        </div>
+                                    </div>
+                                    <ExternalLink className="size-5 text-gray-400 group-hover:text-blue-600 flex-shrink-0 ml-4" />
+                                </button>
+                            ))}
+                        </div>
+                    </div>
+
+                    <DialogFooter>
+                        <Button
+                            variant="outline"
+                            onClick={() => setLegalReviewDialog(false)}
+                        >
+                            닫기
+                        </Button>
+                    </DialogFooter>
+                </DialogContent>
+            </Dialog>
+
             {/* 최종승인 다이얼로그 */}
             <Dialog open={finalApproveDialog} onOpenChange={setFinalApproveDialog}>
                 <DialogContent className="max-w-2xl">
-- 
cgit v1.2.3

