diff options
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.tsx | 191 |
1 files changed, 123 insertions, 68 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 e62a6cb7..42fb2b5f 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 @@ -22,11 +22,19 @@ import { prepareFinalApprovalAction, quickFinalApprovalAction, resendContractsAc import { BasicContractSignDialog } from "../vendor-table/basic-contract-sign-dialog" import { SSLVWPurInqReqDialog } from "@/components/common/legal/sslvw-pur-inq-req-dialog" import { requestRedFlagResolution } from "@/lib/compliance/red-flag-resolution" +import { useRouter } from "next/navigation" + +interface RedFlagResolutionState { + resolved: boolean + resolvedAt: Date | null + pendingApprovalId: string | null +} interface BasicContractDetailTableToolbarActionsProps { table: Table<BasicContractView> gtcData?: Record<number, { gtcDocumentId: number | null; hasComments: boolean }> redFlagData?: Record<number, boolean> + redFlagResolutionData?: Record<number, RedFlagResolutionState> isComplianceTemplate?: boolean } @@ -34,6 +42,7 @@ export function BasicContractDetailTableToolbarActions({ table, gtcData = {}, redFlagData = {}, + redFlagResolutionData = {}, isComplianceTemplate = false }: BasicContractDetailTableToolbarActionsProps) { // 선택된 행들 가져오기 @@ -47,6 +56,7 @@ export function BasicContractDetailTableToolbarActions({ const [loading, setLoading] = React.useState(false) const [buyerSignDialog, setBuyerSignDialog] = React.useState(false) const [contractsToSign, setContractsToSign] = React.useState<any[]>([]) + const router = useRouter() // 각 버튼별 활성화 조건 계산 const canBulkDownload = hasSelectedRows && selectedRows.some(row => @@ -339,6 +349,11 @@ export function BasicContractDetailTableToolbarActions({ return } + if (selectedRows.length !== 1) { + toast.error("계약서 한 건을 선택해주세요.") + return + } + try { setLoading(true) @@ -350,7 +365,7 @@ export function BasicContractDetailTableToolbarActions({ if (result.success) { toast.success(result.message) - // 테이블 데이터 갱신 + router.refresh() table.toggleAllPageRowsSelected(false) } else { toast.error(result.message) @@ -391,27 +406,48 @@ export function BasicContractDetailTableToolbarActions({ } } - // RED FLAG 해소요청 가능 여부 - const canRequestRedFlagResolution = hasSelectedRows && isComplianceTemplate && selectedRows.some(row => { - const contract = row.original - return redFlagData[contract.id] === true - }) + const hasPendingResolution = (contractId: number) => { + const state = redFlagResolutionData[contractId] + return Boolean(state?.pendingApprovalId && !state?.resolved) + } + + const redFlagEligibleContracts = selectedRows + .map(row => row.original) + .filter(contract => { + if (redFlagData[contract.id] !== true) return false + return !hasPendingResolution(contract.id) + }) - // RED FLAG 해소요청 가능한 계약서들 - const redFlagResolutionContracts = selectedRows + const redFlagPendingContracts = selectedRows .map(row => row.original) - .filter(contract => redFlagData[contract.id] === true) + .filter(contract => hasPendingResolution(contract.id)) + + const canRequestRedFlagResolution = + hasSelectedRows && isComplianceTemplate && redFlagEligibleContracts.length > 0 // RED FLAG 해소요청 const handleRequestRedFlagResolution = async () => { if (!canRequestRedFlagResolution) { - toast.error("RED FLAG가 있는 계약서를 선택해주세요") + toast.error("해소요청 가능한 RED FLAG 계약서를 선택해주세요") return } + if (redFlagPendingContracts.length > 0) { + const preview = redFlagPendingContracts + .map((contract) => contract.vendorName || `계약 ${contract.id}`) + .slice(0, 2) + .join(", ") + toast.info( + `${preview}${redFlagPendingContracts.length > 2 ? ` 외 ${redFlagPendingContracts.length - 2}건` : ""}은 해소요청이 이미 진행 중입니다.`, + { + description: "진행 중인 계약서는 자동으로 제외하고 요청합니다.", + } + ) + } + setLoading(true) try { - const contractIds = redFlagResolutionContracts.map(c => c.id) + const contractIds = redFlagEligibleContracts.map(c => c.id) const result = await requestRedFlagResolution(contractIds) toast.success("RED FLAG 해소요청 결재가 상신되었습니다.", { @@ -458,8 +494,14 @@ export function BasicContractDetailTableToolbarActions({ } ] - // 법무검토 요청 + const complianceInquiryUrl = 'http://60.101.207.55/Inquiry/Write/InquiryWrite.aspx' + + // 법무검토 요청 / 준법문의 const handleRequestLegalReview = () => { + if (isComplianceTemplate) { + window.open(complianceInquiryUrl, '_blank', 'noopener,noreferrer') + return + } setLegalReviewDialog(true) } @@ -503,13 +545,15 @@ export function BasicContractDetailTableToolbarActions({ title={!hasSelectedRows ? "계약서를 선택해주세요" : !canRequestRedFlagResolution - ? "RED FLAG가 있는 계약서를 선택해주세요" - : `${redFlagResolutionContracts.length}건 RED FLAG 해소요청` + ? redFlagPendingContracts.length > 0 + ? "이미 해소요청이 진행 중인 계약서만 선택되어 있습니다" + : "RED FLAG가 있는 계약서를 선택해주세요" + : `${redFlagEligibleContracts.length}건 RED FLAG 해소요청` } > <Flag className="size-4" aria-hidden="true" /> <span className="hidden sm:inline"> - RED FLAG 해소요청 {hasSelectedRows ? `(${redFlagResolutionContracts.length})` : ''} + RED FLAG 해소요청 {hasSelectedRows ? `(${redFlagEligibleContracts.length})` : ''} </span> </Button> )} @@ -530,19 +574,28 @@ export function BasicContractDetailTableToolbarActions({ </Button> {/* 법무검토 버튼 (SSLVW 데이터 조회) */} - <SSLVWPurInqReqDialog onConfirm={handleSSLVWConfirm} /> + <SSLVWPurInqReqDialog + onConfirm={handleSSLVWConfirm} + requireSingleSelection + triggerDisabled={selectedRows.length !== 1 || loading} + triggerTitle={ + selectedRows.length !== 1 + ? "계약서 한 건을 선택해주세요" + : undefined + } + /> - {/* 법무검토 요청 버튼 */} + {/* 법무검토 요청 / 준법문의 버튼 */} <Button variant="outline" size="sm" onClick={handleRequestLegalReview} className="gap-2" - title="법무검토 요청 링크 선택" + title={isComplianceTemplate ? "준법문의 링크로 이동" : "법무검토 요청 링크 선택"} > <FileText className="size-4" aria-hidden="true" /> <span className="hidden sm:inline"> - 법무검토 요청 + {isComplianceTemplate ? "준법문의" : "법무검토 요청"} </span> </Button> @@ -643,61 +696,63 @@ export function BasicContractDetailTableToolbarActions({ </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"> - 아래 링크 중 해당하는 유형을 선택하여 법무검토를 요청하세요. + {/* 법무검토 요청 다이얼로그 (준법 템플릿 제외) */} + {!isComplianceTemplate && ( + <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> - <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 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> - </div> - <ExternalLink className="size-5 text-gray-400 group-hover:text-blue-600 flex-shrink-0 ml-4" /> - </button> - ))} + <ExternalLink className="size-5 text-gray-400 group-hover:text-blue-600 flex-shrink-0 ml-4" /> + </button> + ))} + </div> </div> - </div> - <DialogFooter> - <Button - variant="outline" - onClick={() => setLegalReviewDialog(false)} - > - 닫기 - </Button> - </DialogFooter> - </DialogContent> - </Dialog> + <DialogFooter> + <Button + variant="outline" + onClick={() => setLegalReviewDialog(false)} + > + 닫기 + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + )} {/* 최종승인 다이얼로그 */} <Dialog open={finalApproveDialog} onOpenChange={setFinalApproveDialog}> |
