summaryrefslogtreecommitdiff
path: root/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx
diff options
context:
space:
mode:
authordujinkim <dujin.kim@dtsolution.co.kr>2025-06-19 09:44:28 +0000
committerdujinkim <dujin.kim@dtsolution.co.kr>2025-06-19 09:44:28 +0000
commit95bbe9c583ff841220da1267630e7b2025fc36dc (patch)
tree5e3d5bb3302530bbaa7f7abbe8c9cf8193ccbd4c /lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx
parent0eb030580b5cbe5f03d570c3c9d8c519bac3b783 (diff)
(대표님) 20250619 1844 KST 작업사항
Diffstat (limited to 'lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx')
-rw-r--r--lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx298
1 files changed, 298 insertions, 0 deletions
diff --git a/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx b/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx
new file mode 100644
index 00000000..3fb47771
--- /dev/null
+++ b/lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx
@@ -0,0 +1,298 @@
+"use client"
+
+import * as React from "react"
+import { type Table } from "@tanstack/react-table"
+import {
+ Plus,
+ Check,
+ MessageSquare,
+ X,
+ Download,
+ Upload,
+ RefreshCw,
+ Settings
+} from "lucide-react"
+import { toast } from "sonner"
+import { useRouter } from "next/navigation"
+
+import { Button } from "@/components/ui/button"
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@/components/ui/dropdown-menu"
+import { ManualCreateEvaluationTargetDialog } from "./manual-create-evaluation-target-dialog"
+import { EvaluationTargetWithDepartments } from "@/db/schema"
+
+interface EvaluationTargetsTableToolbarActionsProps {
+ table: Table<EvaluationTargetWithDepartments>
+ onRefresh?: () => void
+}
+
+export function EvaluationTargetsTableToolbarActions({
+ table,
+ onRefresh
+}: EvaluationTargetsTableToolbarActionsProps) {
+ const [isLoading, setIsLoading] = React.useState(false)
+ const [manualCreateDialogOpen, setManualCreateDialogOpen] = React.useState(false)
+ const router = useRouter()
+
+ // 선택된 행들
+ const selectedRows = table.getFilteredSelectedRowModel().rows
+ const hasSelection = selectedRows.length > 0
+ const selectedTargets = selectedRows.map(row => row.original)
+
+ // 선택된 항목들의 상태 분석
+ 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
+
+ return {
+ pending,
+ confirmed,
+ excluded,
+ consensusTrue,
+ consensusFalse,
+ consensusNull,
+ canConfirm: pending > 0 && consensusTrue > 0,
+ canExclude: pending > 0,
+ canRequestReview: pending > 0
+ }
+ }, [selectedTargets])
+
+ // ----------------------------------------------------------------
+ // 신규 평가 대상 생성 (자동)
+ // ----------------------------------------------------------------
+ const handleAutoGenerate = async () => {
+ setIsLoading(true)
+ try {
+ // TODO: 발주실적에서 자동 추출 API 호출
+ toast.success("평가 대상이 자동으로 생성되었습니다.")
+ router.refresh()
+ } catch (error) {
+ console.error('Error auto generating targets:', error)
+ toast.error("자동 생성 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ // ----------------------------------------------------------------
+ // 신규 평가 대상 생성 (수동)
+ // ----------------------------------------------------------------
+ const handleManualCreate = () => {
+ setManualCreateDialogOpen(true)
+ }
+
+ // ----------------------------------------------------------------
+ // 선택된 항목들 확정
+ // ----------------------------------------------------------------
+ const handleConfirmSelected = async () => {
+ if (!hasSelection || !selectedStats.canConfirm) return
+
+ setIsLoading(true)
+ try {
+ // TODO: 확정 API 호출
+ const confirmableTargets = selectedTargets.filter(
+ t => t.status === "PENDING" && t.consensusStatus === true
+ )
+
+ toast.success(`${confirmableTargets.length}개 항목이 확정되었습니다.`)
+ table.resetRowSelection()
+ router.refresh()
+ } catch (error) {
+ console.error('Error confirming targets:', error)
+ toast.error("확정 처리 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ // ----------------------------------------------------------------
+ // 선택된 항목들 제외
+ // ----------------------------------------------------------------
+ const handleExcludeSelected = async () => {
+ if (!hasSelection || !selectedStats.canExclude) return
+
+ setIsLoading(true)
+ try {
+ // TODO: 제외 API 호출
+ const excludableTargets = selectedTargets.filter(t => t.status === "PENDING")
+
+ toast.success(`${excludableTargets.length}개 항목이 제외되었습니다.`)
+ table.resetRowSelection()
+ router.refresh()
+ } catch (error) {
+ console.error('Error excluding targets:', error)
+ toast.error("제외 처리 중 오류가 발생했습니다.")
+ } finally {
+ setIsLoading(false)
+ }
+ }
+
+ // ----------------------------------------------------------------
+ // 선택된 항목들 의견 요청
+ // ----------------------------------------------------------------
+ const handleRequestReview = async () => {
+ if (!hasSelection || !selectedStats.canRequestReview) return
+
+ // TODO: 의견 요청 다이얼로그 열기
+ toast.info("의견 요청 다이얼로그를 구현해주세요.")
+ }
+
+ // ----------------------------------------------------------------
+ // Excel 내보내기
+ // ----------------------------------------------------------------
+ const handleExport = () => {
+ try {
+ // TODO: Excel 내보내기 구현
+ toast.success("Excel 파일이 다운로드되었습니다.")
+ } catch (error) {
+ console.error('Error exporting to Excel:', error)
+ toast.error("Excel 내보내기 중 오류가 발생했습니다.")
+ }
+ }
+
+ // ----------------------------------------------------------------
+ // 새로고침
+ // ----------------------------------------------------------------
+ const handleRefresh = () => {
+ if (onRefresh) {
+ onRefresh()
+ } else {
+ router.refresh()
+ }
+ toast.success("데이터가 새로고침되었습니다.")
+ }
+
+ 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>
+
+ {/* 유틸리티 버튼들 */}
+ <div className="flex items-center gap-1 border-l pl-2 ml-2">
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleExport}
+ className="gap-2"
+ >
+ <Download className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">내보내기</span>
+ </Button>
+
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleRefresh}
+ className="gap-2"
+ >
+ <RefreshCw className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">새로고침</span>
+ </Button>
+ </div>
+
+ {/* 선택된 항목 액션 버튼들 */}
+ {hasSelection && (
+ <div className="flex items-center gap-1 border-l pl-2 ml-2">
+ {/* 확정 버튼 */}
+ {selectedStats.canConfirm && (
+ <Button
+ variant="default"
+ size="sm"
+ className="gap-2 bg-green-600 hover:bg-green-700"
+ onClick={handleConfirmSelected}
+ disabled={isLoading}
+ >
+ <Check className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">
+ 확정 ({selectedStats.consensusTrue})
+ </span>
+ </Button>
+ )}
+
+ {/* 제외 버튼 */}
+ {selectedStats.canExclude && (
+ <Button
+ variant="destructive"
+ size="sm"
+ className="gap-2"
+ onClick={handleExcludeSelected}
+ disabled={isLoading}
+ >
+ <X className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">
+ 제외 ({selectedStats.pending})
+ </span>
+ </Button>
+ )}
+
+ {/* 의견 요청 버튼 */}
+ {selectedStats.canRequestReview && (
+ <Button
+ variant="outline"
+ size="sm"
+ className="gap-2"
+ onClick={handleRequestReview}
+ disabled={isLoading}
+ >
+ <MessageSquare className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">
+ 의견 요청 ({selectedStats.pending})
+ </span>
+ </Button>
+ )}
+ </div>
+ )}
+ </div>
+
+ {/* 수동 생성 다이얼로그 */}
+ <ManualCreateEvaluationTargetDialog
+ open={manualCreateDialogOpen}
+ onOpenChange={setManualCreateDialogOpen}
+ />
+
+ {/* 선택 정보 표시 */}
+ {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