From 90f79a7a691943a496f67f01c1e493256070e4de Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 7 Jul 2025 01:44:45 +0000 Subject: (대표님) 변경사항 20250707 10시 43분 - unstaged 변경사항 추가 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/evaluation-targets-toolbar-actions.tsx | 119 +++++++++++++++------ 1 file changed, 86 insertions(+), 33 deletions(-) (limited to 'lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx') diff --git a/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx b/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx index 82b7c97c..8bc5254c 100644 --- a/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx +++ b/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx @@ -51,16 +51,69 @@ export function EvaluationTargetsTableToolbarActions({ // 선택된 행들 const selectedRows = table.getFilteredSelectedRowModel().rows const hasSelection = selectedRows.length > 0 - const selectedTargets = selectedRows.map(row => row.original) - // 선택된 항목들의 상태 분석 + // ✅ selectedTargets를 useMemo로 안정화 (VendorsTable 방식과 동일) + const selectedTargets = React.useMemo(() => { + return selectedRows.map(row => row.original) + }, [selectedRows]) + + // ✅ 각 상태별 타겟들을 개별적으로 메모이제이션 (VendorsTable 방식과 동일) + const pendingTargets = React.useMemo(() => { + return table + .getFilteredSelectedRowModel() + .rows + .map(row => row.original) + .filter(t => t.status === "PENDING"); + }, [table.getFilteredSelectedRowModel().rows]); + + const confirmedTargets = React.useMemo(() => { + return table + .getFilteredSelectedRowModel() + .rows + .map(row => row.original) + .filter(t => t.status === "CONFIRMED"); + }, [table.getFilteredSelectedRowModel().rows]); + + const excludedTargets = React.useMemo(() => { + return table + .getFilteredSelectedRowModel() + .rows + .map(row => row.original) + .filter(t => t.status === "EXCLUDED"); + }, [table.getFilteredSelectedRowModel().rows]); + + const consensusTrueTargets = React.useMemo(() => { + return table + .getFilteredSelectedRowModel() + .rows + .map(row => row.original) + .filter(t => t.consensusStatus === true); + }, [table.getFilteredSelectedRowModel().rows]); + + const consensusFalseTargets = React.useMemo(() => { + return table + .getFilteredSelectedRowModel() + .rows + .map(row => row.original) + .filter(t => t.consensusStatus === false); + }, [table.getFilteredSelectedRowModel().rows]); + + const consensusNullTargets = React.useMemo(() => { + return table + .getFilteredSelectedRowModel() + .rows + .map(row => row.original) + .filter(t => t.consensusStatus === null); + }, [table.getFilteredSelectedRowModel().rows]); + + // ✅ 선택된 항목들의 상태 분석 - 안정화된 개별 배열들 사용 const selectedStats = React.useMemo(() => { - const pending = selectedTargets.filter(t => t.status === "PENDING").length - const confirmed = selectedTargets.filter(t => t.status === "CONFIRMED").length - const excluded = selectedTargets.filter(t => t.status === "EXCLUDED").length - const consensusTrue = selectedTargets.filter(t => t.consensusStatus === true).length - const consensusFalse = selectedTargets.filter(t => t.consensusStatus === false).length - const consensusNull = selectedTargets.filter(t => t.consensusStatus === null).length + 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, @@ -73,12 +126,19 @@ export function EvaluationTargetsTableToolbarActions({ canExclude: pending > 0, canRequestReview: pending > 0 } - }, [selectedTargets]) + }, [ + pendingTargets.length, + confirmedTargets.length, + excludedTargets.length, + consensusTrueTargets.length, + consensusFalseTargets.length, + consensusNullTargets.length + ]) // ---------------------------------------------------------------- // 신규 평가 대상 생성 (자동) // ---------------------------------------------------------------- - const handleAutoGenerate = async () => { + const handleAutoGenerate = React.useCallback(async () => { setIsLoading(true) try { // TODO: 발주실적에서 자동 추출 API 호출 @@ -90,23 +150,33 @@ export function EvaluationTargetsTableToolbarActions({ } finally { setIsLoading(false) } - } + }, [router]) // ---------------------------------------------------------------- // 신규 평가 대상 생성 (수동) // ---------------------------------------------------------------- - const handleManualCreate = () => { + const handleManualCreate = React.useCallback(() => { setManualCreateDialogOpen(true) - } + }, []) // ---------------------------------------------------------------- // 다이얼로그 성공 핸들러 // ---------------------------------------------------------------- - const handleActionSuccess = () => { + const handleActionSuccess = React.useCallback(() => { table.resetRowSelection() onRefresh?.() router.refresh() - } + }, [table, onRefresh, router]) + + // ---------------------------------------------------------------- + // 내보내기 핸들러 + // ---------------------------------------------------------------- + const handleExport = React.useCallback(() => { + exportTableToExcel(table, { + filename: "vendor-target-list", + excludeColumns: ["select", "actions"], + }) + }, [table]) return ( <> @@ -141,12 +211,7 @@ export function EvaluationTargetsTableToolbarActions({