'use client'; /* IMPORT */ import { autoGenerateEvaluationTargets, generateEvalTargetTemplate, importEvalTargetExcel } from '../service'; import { Button } from '@/components/ui/button'; import { ConfirmTargetsDialog, ExcludeTargetsDialog, RequestReviewDialog, } from './evaluation-target-action-dialogs'; import { Check, Download, FileInput, LoaderCircle, MessageSquare, Plus, RefreshCw, Trash2, Upload, X, } from 'lucide-react'; import { DeleteTargetsDialog } from './delete-targets-dialog'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { exportTableToExcel } from '@/lib/export'; import { ManualCreateEvaluationTargetDialog } from './manual-create-evaluation-target-dialog'; import { toast } from 'sonner'; import { type ChangeEvent, useCallback, useMemo, useRef, useState } from 'react'; import { type EvaluationTargetWithDepartments } from '@/db/schema'; import { type Table } from '@tanstack/react-table'; import { useAuthRole } from '@/hooks/use-auth-role'; import { useRouter } from 'next/navigation'; import { useSession } from 'next-auth/react'; // ---------------------------------------------------------------------------------------------------- /* TYPES */ interface EvaluationTargetsTableToolbarActionsProps { table: Table; onRefresh?: () => void; } // ---------------------------------------------------------------------------------------------------- export function EvaluationTargetsTableToolbarActions(props: EvaluationTargetsTableToolbarActionsProps) { const { table, onRefresh } = props; const [isLoading, setIsLoading] = useState(false); const [manualCreateDialogOpen, setManualCreateDialogOpen] = useState(false); const [confirmDialogOpen, setConfirmDialogOpen] = useState(false); const [excludeDialogOpen, setExcludeDialogOpen] = useState(false); const [reviewDialogOpen, setReviewDialogOpen] = useState(false); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const router = useRouter(); const { data: session } = useSession(); const fileInputRef = useRef(null); // 권한 체크 const { hasRole, isLoading: roleLoading } = useAuthRole(); const canManageEvaluations = hasRole('정기평가') || hasRole('admin'); // 사용자 ID 가져오기 const userId = useMemo(() => { return session?.user?.id ? Number(session.user.id) : 1; }, [session]); // 선택된 행들 const selectedRows = table.getFilteredSelectedRowModel().rows; const hasSelection = selectedRows.length > 0; // ✅ selectedTargets를 useMemo로 안정화 (VendorsTable 방식과 동일) const selectedTargets = useMemo(() => { return selectedRows.map(row => row.original) }, [selectedRows]); // ✅ 각 상태별 타겟들을 개별적으로 메모이제이션 (VendorsTable 방식과 동일) const pendingTargets = useMemo(() => { return table .getFilteredSelectedRowModel() .rows .map(row => row.original) .filter(t => t.status === 'PENDING'); }, [table.getFilteredSelectedRowModel().rows]); const confirmedTargets = useMemo(() => { return table .getFilteredSelectedRowModel() .rows .map(row => row.original) .filter(t => t.status === 'CONFIRMED'); }, [table.getFilteredSelectedRowModel().rows]); const excludedTargets = useMemo(() => { return table .getFilteredSelectedRowModel() .rows .map(row => row.original) .filter(t => t.status === 'EXCLUDED'); }, [table.getFilteredSelectedRowModel().rows]); const consensusTrueTargets = useMemo(() => { return table .getFilteredSelectedRowModel() .rows .map(row => row.original) .filter(t => t.consensusStatus === true); }, [table.getFilteredSelectedRowModel().rows]); const consensusFalseTargets = useMemo(() => { return table .getFilteredSelectedRowModel() .rows .map(row => row.original) .filter(t => t.consensusStatus === false); }, [table.getFilteredSelectedRowModel().rows]); const consensusNullTargets = useMemo(() => { return table .getFilteredSelectedRowModel() .rows .map(row => row.original) .filter(t => t.consensusStatus === null); }, [table.getFilteredSelectedRowModel().rows]); // ✅ 선택된 항목들의 상태 분석 - 안정화된 개별 배열들 사용 const selectedStats = useMemo(() => { const pending = pendingTargets.length const confirmed = confirmedTargets.length const excluded = excludedTargets.length const consensusTrue = consensusTrueTargets.length const consensusFalse = consensusFalseTargets.length const consensusNull = consensusNullTargets.length return { pending, confirmed, excluded, consensusTrue, consensusFalse, consensusNull, canConfirm: pending > 0 && consensusTrue > 0, canExclude: pending > 0, canRequestReview: pending > 0, canDelete: pending > 0 // 삭제는 PENDING 상태인 것만 가능 } }, [ pendingTargets.length, confirmedTargets.length, excludedTargets.length, consensusTrueTargets.length, consensusFalseTargets.length, consensusNullTargets.length ]) // ---------------------------------------------------------------- // 신규 평가 대상 생성 (자동) // ---------------------------------------------------------------- const handleAutoGenerate = useCallback(async () => { setIsLoading(true); try { // 현재 년도를 기준으로 평가 대상 자동 생성 const currentYear = new Date().getFullYear(); const result = await autoGenerateEvaluationTargets(currentYear, userId); if (result.success) { if (result.generatedCount === 0) { toast.info(result.message, { description: result.skippedCount ? `이미 존재하는 평가 대상: ${result.skippedCount}개` : undefined }); } else { toast.success(result.message, { description: result.details ? `해양: ${result.details.shipTargets}개, 조선: ${result.details.plantTargets}개 생성${result.details.duplicateSkipped > 0 ? `, 중복 건너뜀: ${result.details.duplicateSkipped}개` : ''}` : undefined }); } onRefresh?.(); router.refresh(); } else { toast.error(result.error || '자동 생성 중 오류가 발생했습니다.'); } } catch (error) { console.error('Error auto generating targets:', error); toast.error("자동 생성 중 오류가 발생했습니다."); } finally { setIsLoading(false); } }, [router, onRefresh, userId]); // ---------------------------------------------------------------- // 신규 평가 대상 생성 (수동) // ---------------------------------------------------------------- const handleManualCreate = useCallback(() => { setManualCreateDialogOpen(true) }, []); // ---------------------------------------------------------------- // 다이얼로그 성공 핸들러 // ---------------------------------------------------------------- const handleActionSuccess = useCallback(() => { table.resetRowSelection() onRefresh?.() router.refresh() }, [table, onRefresh, router]); // EXCEL IMPORT const handleImport = useCallback(() => { fileInputRef.current?.click(); }, [table]); async function onFileChange(event: ChangeEvent) { const file = event.target.files?.[0]; if (!file) { toast.error('가져올 파일을 선택해주십시오.'); return; } if (!file.name.endsWith('.xlsx') && !file.name.endsWith('.xls')) { toast.error('.xlsx 또는 .xls 확장자인 Excel 파일만 업로드 가능합니다.'); return; } event.target.value = ''; try { const { errorFile, errorMessage, successMessage } = await importEvalTargetExcel(file); if (errorMessage) { toast.error(errorMessage); if (errorFile) { const url = URL.createObjectURL(errorFile); const link = document.createElement('a'); link.href = url; link.download = 'errors.xlsx'; link.click(); URL.revokeObjectURL(url); } } else { toast.success(successMessage || 'Excel 파일이 성공적으로 업로드되었습니다.'); } } catch (error) { toast.error('Excel 파일을 업로드하는 중 오류가 발생했습니다.'); console.error('Error in Excel File Upload: ', error); } finally { onRefresh?.(); } }; // EXCEL EXPORT const handleExport = useCallback(() => { exportTableToExcel(table, { filename: 'vendor-target-list', excludeColumns: ['select', 'actions'], }) }, [table]); // EXCEL TEMPLATE DOWNLOAD const handleTemplateDownload = useCallback(async () => { try { const buffer = await generateEvalTargetTemplate(); const blob = new Blob([buffer], { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = "협력업체_평가_대상_관리_템플릿.xlsx"; link.click(); URL.revokeObjectURL(url); toast.success('템플릿 파일이 다운로드되었습니다.'); } catch (error) { console.error('Error in Template Download: ', error); toast.error('템플릿 다운로드 중 오류가 발생했습니다.'); } }, [table]); // 권한이 없거나 로딩 중인 경우 내보내기 버튼만 표시 if (roleLoading) { return (
S
) } return ( <>
{/* 신규 생성 드롭다운 - 정기평가 권한이 있는 경우만 표시 */} {canManageEvaluations && ( 자동 생성 (발주실적 기반) 수동 생성 )} {/* 유틸리티 버튼들 */}
{/* 가져오기 버튼 */} {canManageEvaluations && ( <> )} {/* 내보내기 버튼 */} {/* 템플릿 다운로드 버튼 */} {canManageEvaluations && ( )}
{/* 선택된 항목 액션 버튼들 - 정기평가 권한이 있는 경우만 표시 */} {canManageEvaluations && hasSelection && (
{/* 확정 버튼 */} {selectedStats.canConfirm && ( )} {/* 제외 버튼 */} {selectedStats.canExclude && ( )} {/* 삭제 버튼 */} {selectedStats.canDelete && ( )} {/* 의견 요청 버튼 */} {selectedStats.canRequestReview && ( )}
)} {/* 권한이 없는 경우 안내 메시지 (선택사항) */} {!canManageEvaluations && hasSelection && (
평가 관리 권한이 필요합니다
)}
{/* 다이얼로그들 - 권한이 있는 경우만 렌더링 */} {canManageEvaluations && ( <> {/* 수동 생성 다이얼로그 */} {/* 확정 컨펌 다이얼로그 */} {/* 제외 컨펌 다이얼로그 */} {/* 의견 요청 다이얼로그 */} {/* 삭제 컨펌 다이얼로그 */} )} ) }