diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-07 01:44:45 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-07 01:44:45 +0000 |
| commit | 90f79a7a691943a496f67f01c1e493256070e4de (patch) | |
| tree | 37275fde3ae08c2bca384fbbc8eb378de7e39230 /lib/evaluation/table/periodic-evaluations-toolbar-actions.tsx | |
| parent | fbb3b7f05737f9571b04b0a8f4f15c0928de8545 (diff) | |
(대표님) 변경사항 20250707 10시 43분 - unstaged 변경사항 추가
Diffstat (limited to 'lib/evaluation/table/periodic-evaluations-toolbar-actions.tsx')
| -rw-r--r-- | lib/evaluation/table/periodic-evaluations-toolbar-actions.tsx | 179 |
1 files changed, 106 insertions, 73 deletions
diff --git a/lib/evaluation/table/periodic-evaluations-toolbar-actions.tsx b/lib/evaluation/table/periodic-evaluations-toolbar-actions.tsx index 2d2bebc1..bb63a1fd 100644 --- a/lib/evaluation/table/periodic-evaluations-toolbar-actions.tsx +++ b/lib/evaluation/table/periodic-evaluations-toolbar-actions.tsx @@ -1,5 +1,3 @@ -"use client" - import * as React from "react" import { type Table } from "@tanstack/react-table" import { @@ -9,7 +7,8 @@ import { Download, RefreshCw, FileText, - MessageSquare + MessageSquare, + CheckCircle2 } from "lucide-react" import { toast } from "sonner" import { useRouter } from "next/navigation" @@ -28,6 +27,7 @@ import { } from "./periodic-evaluation-action-dialogs" import { PeriodicEvaluationView } from "@/db/schema" import { exportTableToExcel } from "@/lib/export" +import { FinalizeEvaluationDialog } from "./periodic-evaluation-finalize-dialogs" interface PeriodicEvaluationsTableToolbarActionsProps { table: Table<PeriodicEvaluationView> @@ -42,20 +42,66 @@ export function PeriodicEvaluationsTableToolbarActions({ const [createEvaluationDialogOpen, setCreateEvaluationDialogOpen] = React.useState(false) const [requestDocumentsDialogOpen, setRequestDocumentsDialogOpen] = React.useState(false) const [requestEvaluationDialogOpen, setRequestEvaluationDialogOpen] = React.useState(false) + const [finalizeEvaluationDialogOpen, setFinalizeEvaluationDialogOpen] = React.useState(false) const router = useRouter() // 선택된 행들 const selectedRows = table.getFilteredSelectedRowModel().rows const hasSelection = selectedRows.length > 0 - const selectedEvaluations = selectedRows.map(row => row.original) - // 선택된 항목들의 상태 분석 + // ✅ selectedEvaluations를 useMemo로 안정화 (VendorsTable 방식과 동일) + const selectedEvaluations = React.useMemo(() => { + return selectedRows.map(row => row.original) + }, [selectedRows]) + + // ✅ 각 상태별 평가들을 개별적으로 메모이제이션 (VendorsTable 방식과 동일) + const pendingSubmissionEvaluations = React.useMemo(() => { + return table + .getFilteredSelectedRowModel() + .rows + .map(row => row.original) + .filter(e => e.status === "PENDING_SUBMISSION"); + }, [table.getFilteredSelectedRowModel().rows]); + + const submittedEvaluations = React.useMemo(() => { + return table + .getFilteredSelectedRowModel() + .rows + .map(row => row.original) + .filter(e => e.status === "SUBMITTED" || e.status === "PENDING_SUBMISSION"); + }, [table.getFilteredSelectedRowModel().rows]); + + const inReviewEvaluations = React.useMemo(() => { + return table + .getFilteredSelectedRowModel() + .rows + .map(row => row.original) + .filter(e => e.status === "IN_REVIEW"); + }, [table.getFilteredSelectedRowModel().rows]); + + const reviewCompletedEvaluations = React.useMemo(() => { + return table + .getFilteredSelectedRowModel() + .rows + .map(row => row.original) + .filter(e => e.status === "REVIEW_COMPLETED"); + }, [table.getFilteredSelectedRowModel().rows]); + + const finalizedEvaluations = React.useMemo(() => { + return table + .getFilteredSelectedRowModel() + .rows + .map(row => row.original) + .filter(e => e.status === "FINALIZED"); + }, [table.getFilteredSelectedRowModel().rows]); + + // ✅ 선택된 항목들의 상태 분석 - 안정화된 개별 배열들 사용 const selectedStats = React.useMemo(() => { - const pendingSubmission = selectedEvaluations.filter(e => e.status === "PENDING_SUBMISSION").length - const submitted = selectedEvaluations.filter(e => e.status === "SUBMITTED").length - const inReview = selectedEvaluations.filter(e => e.status === "IN_REVIEW").length - const reviewCompleted = selectedEvaluations.filter(e => e.status === "REVIEW_COMPLETED").length - const finalized = selectedEvaluations.filter(e => e.status === "FINALIZED").length + const pendingSubmission = pendingSubmissionEvaluations.length + const submitted = submittedEvaluations.length + const inReview = inReviewEvaluations.length + const reviewCompleted = reviewCompletedEvaluations.length + const finalized = finalizedEvaluations.length // 협력업체에게 자료 요청 가능: PENDING_SUBMISSION 상태 const canRequestDocuments = pendingSubmission > 0 @@ -63,6 +109,9 @@ export function PeriodicEvaluationsTableToolbarActions({ // 평가자에게 평가 요청 가능: SUBMITTED 상태 (제출됐지만 아직 평가 시작 안됨) const canRequestEvaluation = submitted > 0 + // 평가 확정 가능: REVIEW_COMPLETED 상태 + const canFinalizeEvaluation = reviewCompleted > 0 + return { pendingSubmission, submitted, @@ -71,42 +120,37 @@ export function PeriodicEvaluationsTableToolbarActions({ finalized, canRequestDocuments, canRequestEvaluation, + canFinalizeEvaluation, total: selectedEvaluations.length } - }, [selectedEvaluations]) - - // ---------------------------------------------------------------- - // 신규 정기평가 생성 (자동) - // ---------------------------------------------------------------- - const handleAutoGenerate = async () => { - setIsLoading(true) - try { - // TODO: 평가대상에서 자동 생성 API 호출 - toast.success("정기평가가 자동으로 생성되었습니다.") - router.refresh() - } catch (error) { - console.error('Error auto generating periodic evaluations:', error) - toast.error("자동 생성 중 오류가 발생했습니다.") - } finally { - setIsLoading(false) - } - } - - // ---------------------------------------------------------------- - // 신규 정기평가 생성 (수동) - // ---------------------------------------------------------------- - const handleManualCreate = () => { - setCreateEvaluationDialogOpen(true) - } - + }, [ + pendingSubmissionEvaluations.length, + submittedEvaluations.length, + inReviewEvaluations.length, + reviewCompletedEvaluations.length, + finalizedEvaluations.length, + selectedEvaluations.length + ]) + + // ---------------------------------------------------------------- // 다이얼로그 성공 핸들러 // ---------------------------------------------------------------- - const handleActionSuccess = () => { + const handleActionSuccess = React.useCallback(() => { table.resetRowSelection() onRefresh?.() router.refresh() - } + }, [table, onRefresh, router]) + + // ---------------------------------------------------------------- + // 내보내기 핸들러 + // ---------------------------------------------------------------- + const handleExport = React.useCallback(() => { + exportTableToExcel(table, { + filename: "periodic-evaluations", + excludeColumns: ["select", "actions"], + }) + }, [table]) return ( <> @@ -117,12 +161,7 @@ export function PeriodicEvaluationsTableToolbarActions({ <Button variant="outline" size="sm" - onClick={() => - exportTableToExcel(table, { - filename: "periodic-evaluations", - excludeColumns: ["select", "actions"], - }) - } + onClick={handleExport} className="gap-2" > <Download className="size-4" aria-hidden="true" /> @@ -165,27 +204,25 @@ export function PeriodicEvaluationsTableToolbarActions({ </Button> )} - {/* 알림 발송 버튼 (선택사항) */} - <Button - variant="outline" - size="sm" - className="gap-2" - onClick={() => { - // TODO: 선택된 평가에 대한 알림 발송 - toast.info("알림이 발송되었습니다.") - }} - disabled={isLoading} - > - <MessageSquare className="size-4" aria-hidden="true" /> - <span className="hidden sm:inline"> - 알림 발송 ({selectedStats.total}) - </span> - </Button> + {/* 평가 확정 버튼 */} + {selectedStats.canFinalizeEvaluation && ( + <Button + variant="outline" + size="sm" + className="gap-2 text-purple-600 border-purple-200 hover:bg-purple-50" + onClick={() => setFinalizeEvaluationDialogOpen(true)} + disabled={isLoading} + > + <CheckCircle2 className="size-4" aria-hidden="true" /> + <span className="hidden sm:inline"> + 평가 확정 ({selectedStats.reviewCompleted}) + </span> + </Button> + )} </div> )} </div> - {/* 협력업체 자료 요청 다이얼로그 */} <RequestDocumentsDialog open={requestDocumentsDialogOpen} @@ -202,17 +239,13 @@ export function PeriodicEvaluationsTableToolbarActions({ onSuccess={handleActionSuccess} /> - {/* 선택 정보 표시 (디버깅용 - 필요시 주석 해제) */} - {/* {hasSelection && ( - <div className="text-xs text-muted-foreground mt-2"> - 선택된 {selectedRows.length}개 항목: - 제출대기 {selectedStats.pendingSubmission}개, - 제출완료 {selectedStats.submitted}개, - 검토중 {selectedStats.inReview}개, - 검토완료 {selectedStats.reviewCompleted}개, - 최종확정 {selectedStats.finalized}개 - </div> - )} */} + {/* 평가 확정 다이얼로그 */} + <FinalizeEvaluationDialog + open={finalizeEvaluationDialogOpen} + onOpenChange={setFinalizeEvaluationDialogOpen} + evaluations={reviewCompletedEvaluations} + onSuccess={handleActionSuccess} + /> </> ) -}
\ No newline at end of file +} |
