summaryrefslogtreecommitdiff
path: root/lib/basic-contract/status-detail/basic-contract-detail-table-toolbar-actions.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-11-19 06:15:43 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-11-19 06:15:43 +0000
commitc92bd1b8caa6ddabe6acee42018262febd5d91fb (patch)
tree833a62c9577894b0f77d3677d4d0274e1cb99385 /lib/basic-contract/status-detail/basic-contract-detail-table-toolbar-actions.tsx
parent9bf5b15734cdf87a02c68b2d2a25046a0678a037 (diff)
(임수민) 기본계약 코멘트, 법무검토 수정
Diffstat (limited to 'lib/basic-contract/status-detail/basic-contract-detail-table-toolbar-actions.tsx')
-rw-r--r--lib/basic-contract/status-detail/basic-contract-detail-table-toolbar-actions.tsx225
1 files changed, 221 insertions, 4 deletions
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">