"use client" import * as React from "react" import { type Table } from "@tanstack/react-table" import { Download, FileDown, Mail, Scale, CheckCircle, AlertTriangle, Send, Gavel, Check, FileSignature } from "lucide-react" import { exportTableToExcel } from "@/lib/export" import { downloadFile } from "@/lib/file-download" import { Button } from "@/components/ui/button" import { BasicContractView } from "@/db/schema" import { toast } from "sonner" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Badge } from "@/components/ui/badge" import { Textarea } from "@/components/ui/textarea" import { Label } from "@/components/ui/label" import { Separator } from "@/components/ui/separator" import { prepareFinalApprovalAction, quickFinalApprovalAction, requestLegalReviewAction, resendContractsAction } from "../service" import { BasicContractSignDialog } from "../vendor-table/basic-contract-sign-dialog" interface BasicContractDetailTableToolbarActionsProps { table: Table } export function BasicContractDetailTableToolbarActions({ table }: BasicContractDetailTableToolbarActionsProps) { // 선택된 행들 가져오기 const selectedRows = table.getSelectedRowModel().rows const hasSelectedRows = selectedRows.length > 0 // 다이얼로그 상태 const [resendDialog, setResendDialog] = React.useState(false) const [legalReviewDialog, setLegalReviewDialog] = React.useState(false) const [finalApproveDialog, setFinalApproveDialog] = React.useState(false) const [loading, setLoading] = React.useState(false) const [reviewNote, setReviewNote] = React.useState("") const [buyerSignDialog, setBuyerSignDialog] = React.useState(false) const [contractsToSign, setContractsToSign] = React.useState([]) // 각 버튼별 활성화 조건 계산 const canBulkDownload = hasSelectedRows && selectedRows.some(row => row.original.signedFilePath && row.original.signedFileName && row.original.vendorSignedAt ) const canBulkResend = hasSelectedRows const canRequestLegalReview = hasSelectedRows && selectedRows.some(row => row.original.legalReviewRequired && !row.original.legalReviewRequestedAt ) const canFinalApprove = hasSelectedRows && selectedRows.some(row => { const contract = row.original; if (contract.completedAt !== null || !contract.signedFilePath) { return false; } if (contract.legalReviewRequestedAt && !contract.legalReviewCompletedAt) { return false; } return true; }); // 필터링된 계약서들 계산 const resendContracts = selectedRows.map(row => row.original) const legalReviewContracts = selectedRows .map(row => row.original) .filter(contract => contract.legalReviewRequired && !contract.legalReviewRequestedAt) const finalApproveContracts = selectedRows .map(row => row.original) .filter(contract => { if (contract.completedAt !== null || !contract.signedFilePath) { return false; } if (contract.legalReviewRequestedAt && !contract.legalReviewCompletedAt) { return false; } return true; }); const contractsWithoutLegalReview = finalApproveContracts.filter(contract => !contract.legalReviewRequestedAt && !contract.legalReviewCompletedAt ); // 대량 재발송 const handleBulkResend = async () => { if (!hasSelectedRows) { toast.error("재발송할 계약서를 선택해주세요") return } setResendDialog(true) } // 선택된 계약서들 일괄 다운로드 const handleBulkDownload = async () => { if (!canBulkDownload) { toast.error("다운로드할 파일이 있는 계약서를 선택해주세요") return } const selectedContracts = selectedRows .map(row => row.original) .filter(contract => contract.signedFilePath && contract.signedFileName) if (selectedContracts.length === 0) { toast.error("다운로드할 파일이 없습니다") return } // 다운로드 시작 알림 toast.success(`${selectedContracts.length}건의 파일 다운로드를 시작합니다`) let successCount = 0 let failedCount = 0 const failedFiles: string[] = [] // 순차적으로 다운로드 (병렬 다운로드는 브라우저 제한으로 인해 문제가 될 수 있음) for (let i = 0; i < selectedContracts.length; i++) { const contract = selectedContracts[i] try { // 진행 상황 표시 if (selectedContracts.length > 3) { toast.loading(`다운로드 중... (${i + 1}/${selectedContracts.length})`, { id: 'bulk-download-progress' }) } const result = await downloadFile( contract.signedFilePath!, contract.signedFileName!, { action: 'download', showToast: false, // 개별 토스트는 비활성화 onError: (error) => { console.error(`다운로드 실패 - ${contract.signedFileName}:`, error) failedFiles.push(`${contract.vendorName || '업체명 없음'} (${contract.signedFileName})`) failedCount++ }, onSuccess: (fileName) => { console.log(`다운로드 성공 - ${fileName}`) successCount++ } } ) if (result.success) { successCount++ } else { failedCount++ failedFiles.push(`${contract.vendorName || '업체명 없음'} (${contract.signedFileName})`) } // 다운로드 간격 (브라우저 부하 방지) if (i < selectedContracts.length - 1) { await new Promise(resolve => setTimeout(resolve, 300)) } } catch (error) { console.error(`다운로드 에러 - ${contract.signedFileName}:`, error) failedCount++ failedFiles.push(`${contract.vendorName || '업체명 없음'} (${contract.signedFileName})`) } } // 진행 상황 토스트 제거 toast.dismiss('bulk-download-progress') // 최종 결과 표시 if (successCount === selectedContracts.length) { toast.success(`모든 파일 다운로드 완료 (${successCount}건)`) } else if (successCount > 0) { toast.warning( `일부 파일 다운로드 완료\n성공: ${successCount}건, 실패: ${failedCount}건`, { duration: 5000, description: failedFiles.length > 0 ? `실패한 파일: ${failedFiles.slice(0, 3).join(', ')}${failedFiles.length > 3 ? ` 외 ${failedFiles.length - 3}건` : ''}` : undefined } ) } else { toast.error( `모든 파일 다운로드 실패 (${failedCount}건)`, { duration: 5000, description: failedFiles.length > 0 ? `실패한 파일: ${failedFiles.slice(0, 3).join(', ')}${failedFiles.length > 3 ? ` 외 ${failedFiles.length - 3}건` : ''}` : undefined } ) } console.log("일괄 다운로드 완료:", { total: selectedContracts.length, success: successCount, failed: failedCount, failedFiles }) } // 법무검토 요청 const handleLegalReviewRequest = async () => { if (!canRequestLegalReview) { toast.error("법무검토 요청 가능한 계약서를 선택해주세요") return } setLegalReviewDialog(true) } // 최종승인 const handleFinalApprove = async () => { if (!canFinalApprove) { toast.error("최종승인 가능한 계약서를 선택해주세요") return } setFinalApproveDialog(true) } // 재요청 확인 const confirmResend = async () => { setLoading(true) try { // TODO: 서버액션 호출 await resendContractsAction(resendContracts.map(c => c.id)) console.log("대량 재발송:", resendContracts) toast.success(`${resendContracts.length}건의 계약서 재발송을 완료했습니다`) setResendDialog(false) table.toggleAllPageRowsSelected(false) // 선택 해제 } catch (error) { toast.error("재발송 중 오류가 발생했습니다") console.error(error) } finally { setLoading(false) } } // 법무검토 요청 확인 const confirmLegalReview = async () => { setLoading(true) try { // TODO: 서버액션 호출 await requestLegalReviewAction(legalReviewContracts.map(c => c.id), reviewNote) console.log("법무검토 요청:", legalReviewContracts, "메모:", reviewNote) toast.success(`${legalReviewContracts.length}건의 법무검토 요청을 완료했습니다`) setLegalReviewDialog(false) setReviewNote("") table.toggleAllPageRowsSelected(false) // 선택 해제 } catch (error) { toast.error("법무검토 요청 중 오류가 발생했습니다") console.error(error) } finally { setLoading(false) } } // 최종승인 확인 (수정됨) const confirmFinalApprove = async () => { setLoading(true) try { // 먼저 서명 가능한 계약서들을 준비 const prepareResult = await prepareFinalApprovalAction( finalApproveContracts.map(c => c.id) ) if (prepareResult.success && prepareResult.contracts) { // 서명이 필요한 경우 서명 다이얼로그 열기 setContractsToSign(prepareResult.contracts) setFinalApproveDialog(false) // 기존 다이얼로그는 닫기 // buyerSignDialog는 더 이상 필요 없으므로 제거 } else { toast.error(prepareResult.message) } } catch (error) { toast.error("최종승인 준비 중 오류가 발생했습니다") console.error(error) } finally { setLoading(false) } } // 구매자 서명 완료 콜백 const handleBuyerSignComplete = () => { setContractsToSign([]) // 계약서 목록 초기화하여 BasicContractSignDialog 언마운트 table.toggleAllPageRowsSelected(false) toast.success("모든 계약서의 최종승인이 완료되었습니다!") } // 빠른 승인 (서명 없이) const confirmQuickApproval = async () => { setLoading(true) try { const result = await quickFinalApprovalAction( finalApproveContracts.map(c => c.id) ) if (result.success) { toast.success(result.message) setFinalApproveDialog(false) table.toggleAllPageRowsSelected(false) } else { toast.error(result.message) } } catch (error) { toast.error("최종승인 중 오류가 발생했습니다") console.error(error) } finally { setLoading(false) } } return ( <>
{/* 일괄 다운로드 버튼 */} {/* 재요청 버튼 */} {/* 법무검토 요청 버튼 */} {/* 최종승인 버튼 */} {/* 실제 구매자 서명을 위한 BasicContractSignDialog */} {contractsToSign.length > 0 && ( 0} mode="buyer" // 구매자 모드 prop t={(key) => key} /> )} {/* Export 버튼 */}
{/* 재발송 다이얼로그 */} 계약서 재발송 확인 선택한 {resendContracts.length}건의 계약서를 재발송합니다.
{resendContracts.map((contract, index) => (
{contract.vendorName || '업체명 없음'}
{contract.vendorCode || '코드 없음'} | {contract.templateName || '템플릿명 없음'}
{contract.status}
))}
{/* 법무검토 요청 다이얼로그 */} 법무검토 요청 선택한 {legalReviewContracts.length}건의 계약서에 대한 법무검토를 요청합니다.
{legalReviewContracts.map((contract, index) => (
{contract.vendorName || '업체명 없음'}
{contract.vendorCode || '코드 없음'} | {contract.templateName || '템플릿명 없음'}
{contract.status}
))}