diff options
Diffstat (limited to 'lib/basic-contract/status-detail')
3 files changed, 268 insertions, 43 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"> diff --git a/lib/basic-contract/status-detail/basic-contracts-detail-columns.tsx b/lib/basic-contract/status-detail/basic-contracts-detail-columns.tsx index 0dd33bcb..5a875541 100644 --- a/lib/basic-contract/status-detail/basic-contracts-detail-columns.tsx +++ b/lib/basic-contract/status-detail/basic-contracts-detail-columns.tsx @@ -373,49 +373,57 @@ export function getDetailColumns({ minSize: 130, }, - // 법무검토 요청일 + // 법무검토 상태 { - accessorKey: "legalReviewRequestedAt", + accessorKey: "legalReviewStatus", header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="법무검토 요청" /> + <DataTableColumnHeaderSimple column={column} title="법무검토 상태" /> ), cell: ({ row }) => { - const date = row.getValue("legalReviewRequestedAt") as Date | null - return date ? ( - <div className="text-sm text-purple-600"> - <div className="font-medium">요청됨</div> - <div className="text-xs">{formatDateTime(date, "KR")}</div> - </div> - ) : ( - <div className="text-sm text-gray-400">-</div> - ) - }, - minSize: 140, - }, - - // 법무검토 완료일 - { - accessorKey: "legalReviewCompletedAt", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="법무검토 완료" /> - ), - cell: ({ row }) => { - const date = row.getValue("legalReviewCompletedAt") as Date | null + const status = row.getValue("legalReviewStatus") as string | null const requestedDate = row.getValue("legalReviewRequestedAt") as Date | null - - return date ? ( - <div className="text-sm text-indigo-600"> - <div className="font-medium">완료</div> - <div className="text-xs">{formatDateTime(date, "KR")}</div> - </div> - ) : requestedDate ? ( - <div className="text-sm text-orange-500"> - <div className="font-medium">진행중</div> - <div className="text-xs">검토 대기</div> - </div> - ) : ( - <div className="text-sm text-gray-400">-</div> - ) + const completedDate = row.getValue("legalReviewCompletedAt") as Date | null + + // 법무검토 상태 우선, 없으면 기존 로직으로 판단 + if (status) { + const statusColors: Record<string, string> = { + '신규등록': 'text-blue-600', + '검토요청': 'text-purple-600', + '담당자배정': 'text-orange-600', + '검토중': 'text-yellow-600', + '답변완료': 'text-green-600', + '재검토요청': 'text-red-600', + '보류': 'text-gray-500', + '취소': 'text-red-700' + } + + return ( + <div className={`text-sm ${statusColors[status] || 'text-gray-600'}`}> + <div className="font-medium">{status}</div> + </div> + ) + } + + // legalWorks에 데이터가 없는 경우 기존 로직 사용 + if (completedDate) { + return ( + <div className="text-sm text-green-600"> + <div className="font-medium">완료</div> + <div className="text-xs">{formatDateTime(completedDate, "KR")}</div> + </div> + ) + } else if (requestedDate) { + return ( + <div className="text-sm text-orange-600"> + <div className="font-medium">진행중</div> + <div className="text-xs">검토 대기</div> + </div> + ) + } else { + return ( + <div className="text-sm text-gray-400">-</div> + ) + } }, minSize: 140, }, diff --git a/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx b/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx index 407463e4..0df46066 100644 --- a/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx +++ b/lib/basic-contract/status-detail/basic-contracts-detail-table.tsx @@ -151,7 +151,7 @@ export function BasicContractsDetailTable({ templateId, promises }: BasicContrac table={table} filterFields={advancedFilterFields} > - <BasicContractDetailTableToolbarActions table={table} /> + <BasicContractDetailTableToolbarActions table={table} gtcData={gtcData} /> </DataTableAdvancedToolbar> </DataTable> ) |
