From c657ef972feeafff16ab0e07cb4771f7dd141ba0 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Thu, 10 Jul 2025 09:55:45 +0000
Subject: (대표님) 20250710 작업사항 - 평가 첨부, 로그인, SEDP 변경 요구사항
 반영
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../table/evaluation-targets-toolbar-actions.tsx   | 181 ++++++++++++++-------
 1 file changed, 125 insertions(+), 56 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 8bc5254c..d1c7e500 100644
--- a/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx
+++ b/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx
@@ -14,6 +14,7 @@ import {
 } from "lucide-react"
 import { toast } from "sonner"
 import { useRouter } from "next/navigation"
+import { useSession } from "next-auth/react"
 
 import { Button } from "@/components/ui/button"
 import {
@@ -31,6 +32,8 @@ import {
 } from "./evaluation-target-action-dialogs"
 import { EvaluationTargetWithDepartments } from "@/db/schema"
 import { exportTableToExcel } from "@/lib/export"
+import { autoGenerateEvaluationTargets } from "../service" // 서버 액션 import
+import { useAuthRole } from "@/hooks/use-auth-role"
 
 interface EvaluationTargetsTableToolbarActionsProps {
   table: Table<EvaluationTargetWithDepartments>
@@ -47,6 +50,16 @@ export function EvaluationTargetsTableToolbarActions({
   const [excludeDialogOpen, setExcludeDialogOpen] = React.useState(false)
   const [reviewDialogOpen, setReviewDialogOpen] = React.useState(false)
   const router = useRouter()
+  const { data: session } = useSession()
+
+  // 권한 체크
+  const { hasRole, isLoading: roleLoading } = useAuthRole()
+  const canManageEvaluations = hasRole('정기평가') || hasRole('admin')
+
+  // 사용자 ID 가져오기
+  const userId = React.useMemo(() => {
+    return session?.user?.id ? Number(session.user.id) : 1;
+  }, [session]);
 
   // 선택된 행들
   const selectedRows = table.getFilteredSelectedRowModel().rows
@@ -141,16 +154,36 @@ export function EvaluationTargetsTableToolbarActions({
   const handleAutoGenerate = React.useCallback(async () => {
     setIsLoading(true)
     try {
-      // TODO: 발주실적에서 자동 추출 API 호출
-      toast.success("평가 대상이 자동으로 생성되었습니다.")
-      router.refresh()
+      // 현재 년도를 기준으로 평가 대상 자동 생성
+      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])
+  }, [router, onRefresh, userId])
 
   // ----------------------------------------------------------------
   // 신규 평가 대상 생성 (수동)
@@ -178,33 +211,54 @@ export function EvaluationTargetsTableToolbarActions({
     })
   }, [table])
 
+  // 권한이 없거나 로딩 중인 경우 내보내기 버튼만 표시
+  if (roleLoading) {
+    return (
+      <div className="flex items-center gap-2">
+        <div className="flex items-center gap-1 border-l pl-2 ml-2">
+          <Button
+            variant="outline"
+            size="sm"
+            disabled
+            className="gap-2"
+          >
+            <Download className="size-4 animate-spin" aria-hidden="true" />
+            <span className="hidden sm:inline">로딩중...</span>
+          </Button>
+        </div>
+      </div>
+    )
+  }
+
   return (
     <>
       <div className="flex items-center gap-2">
-        {/* 신규 생성 드롭다운 */}
-        <DropdownMenu>
-          <DropdownMenuTrigger asChild>
-            <Button
-              variant="default"
-              size="sm"
-              className="gap-2"
-              disabled={isLoading}
-            >
-              <Plus className="size-4" aria-hidden="true" />
-              <span className="hidden sm:inline">신규 생성</span>
-            </Button>
-          </DropdownMenuTrigger>
-          <DropdownMenuContent align="start">
-            <DropdownMenuItem onClick={handleAutoGenerate} disabled={isLoading}>
-              <RefreshCw className="size-4 mr-2" />
-              자동 생성 (발주실적 기반)
-            </DropdownMenuItem>
-            <DropdownMenuItem onClick={handleManualCreate}>
-              <Plus className="size-4 mr-2" />
-              수동 생성
-            </DropdownMenuItem>
-          </DropdownMenuContent>
-        </DropdownMenu>
+        {/* 신규 생성 드롭다운 - 정기평가 권한이 있는 경우만 표시 */}
+        {canManageEvaluations && (
+          <DropdownMenu>
+            <DropdownMenuTrigger asChild>
+              <Button
+                variant="default"
+                size="sm"
+                className="gap-2"
+                disabled={isLoading}
+              >
+                <Plus className="size-4" aria-hidden="true" />
+                <span className="hidden sm:inline">신규 생성</span>
+              </Button>
+            </DropdownMenuTrigger>
+            <DropdownMenuContent align="start">
+              <DropdownMenuItem onClick={handleAutoGenerate} disabled={isLoading}>
+                <RefreshCw className={`size-4 mr-2 ${isLoading ? 'animate-spin' : ''}`} />
+                자동 생성 (발주실적 기반)
+              </DropdownMenuItem>
+              <DropdownMenuItem onClick={handleManualCreate}>
+                <Plus className="size-4 mr-2" />
+                수동 생성
+              </DropdownMenuItem>
+            </DropdownMenuContent>
+          </DropdownMenu>
+        )}
 
         {/* 유틸리티 버튼들 */}
         <div className="flex items-center gap-1 border-l pl-2 ml-2">
@@ -219,8 +273,8 @@ export function EvaluationTargetsTableToolbarActions({
           </Button>
         </div>
 
-        {/* 선택된 항목 액션 버튼들 */}
-        {hasSelection && (
+        {/* 선택된 항목 액션 버튼들 - 정기평가 권한이 있는 경우만 표시 */}
+        {canManageEvaluations && hasSelection && (
           <div className="flex items-center gap-1 border-l pl-2 ml-2">
             {/* 확정 버튼 */}
             {selectedStats.canConfirm && (
@@ -271,37 +325,52 @@ export function EvaluationTargetsTableToolbarActions({
             )}
           </div>
         )}
+
+        {/* 권한이 없는 경우 안내 메시지 (선택사항) */}
+        {!canManageEvaluations && hasSelection && (
+          <div className="flex items-center gap-1 border-l pl-2 ml-2">
+            <div className="text-xs text-muted-foreground px-2 py-1">
+              평가 관리 권한이 필요합니다
+            </div>
+          </div>
+        )}
       </div>
 
-      {/* 수동 생성 다이얼로그 */}
-      <ManualCreateEvaluationTargetDialog
-        open={manualCreateDialogOpen}
-        onOpenChange={setManualCreateDialogOpen}
-      />
+      {/* 다이얼로그들 - 권한이 있는 경우만 렌더링 */}
+      {canManageEvaluations && (
+        <>
+          {/* 수동 생성 다이얼로그 */}
+          <ManualCreateEvaluationTargetDialog
+            open={manualCreateDialogOpen}
+            onOpenChange={setManualCreateDialogOpen}
+            onSuccess={handleActionSuccess}
+          />
 
-      {/* 확정 컨펌 다이얼로그 */}
-      <ConfirmTargetsDialog
-        open={confirmDialogOpen}
-        onOpenChange={setConfirmDialogOpen}
-        targets={selectedTargets}
-        onSuccess={handleActionSuccess}
-      />
+          {/* 확정 컨펌 다이얼로그 */}
+          <ConfirmTargetsDialog
+            open={confirmDialogOpen}
+            onOpenChange={setConfirmDialogOpen}
+            targets={selectedTargets}
+            onSuccess={handleActionSuccess}
+          />
 
-      {/* 제외 컨펌 다이얼로그 */}
-      <ExcludeTargetsDialog
-        open={excludeDialogOpen}
-        onOpenChange={setExcludeDialogOpen}
-        targets={selectedTargets}
-        onSuccess={handleActionSuccess}
-      />
+          {/* 제외 컨펌 다이얼로그 */}
+          <ExcludeTargetsDialog
+            open={excludeDialogOpen}
+            onOpenChange={setExcludeDialogOpen}
+            targets={selectedTargets}
+            onSuccess={handleActionSuccess}
+          />
 
-      {/* 의견 요청 다이얼로그 */}
-      <RequestReviewDialog
-        open={reviewDialogOpen}
-        onOpenChange={setReviewDialogOpen}
-        targets={selectedTargets}
-        onSuccess={handleActionSuccess}
-      />
+          {/* 의견 요청 다이얼로그 */}
+          <RequestReviewDialog
+            open={reviewDialogOpen}
+            onOpenChange={setReviewDialogOpen}
+            targets={selectedTargets}
+            onSuccess={handleActionSuccess}
+          />
+        </>
+      )}
     </>
   )
 }
\ No newline at end of file
-- 
cgit v1.2.3

