From 90f79a7a691943a496f67f01c1e493256070e4de Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
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({
           <Button
             variant="outline"
             size="sm"
-            onClick={() =>
-              exportTableToExcel(table, {
-                filename: "vendor-target-list",
-                excludeColumns: ["select", "actions"],
-              })
-            }
+            onClick={handleExport}
             className="gap-2"
           >
             <Download className="size-4" aria-hidden="true" />
@@ -237,18 +302,6 @@ export function EvaluationTargetsTableToolbarActions({
         targets={selectedTargets}
         onSuccess={handleActionSuccess}
       />
-
-      {/* 선택 정보 표시 */}
-      {/* {hasSelection && (
-        <div className="text-xs text-muted-foreground">
-          선택된 {selectedRows.length}개 항목: 
-          대기중 {selectedStats.pending}개, 
-          확정 {selectedStats.confirmed}개, 
-          제외 {selectedStats.excluded}개
-          {selectedStats.consensusTrue > 0 && ` | 의견일치 ${selectedStats.consensusTrue}개`}
-          {selectedStats.consensusFalse > 0 && ` | 의견불일치 ${selectedStats.consensusFalse}개`}
-        </div>
-      )} */}
     </>
   )
 }
\ No newline at end of file
-- 
cgit v1.2.3

