diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-19 09:44:28 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-06-19 09:44:28 +0000 |
| commit | 95bbe9c583ff841220da1267630e7b2025fc36dc (patch) | |
| tree | 5e3d5bb3302530bbaa7f7abbe8c9cf8193ccbd4c /lib/evaluation-target-list/table/evaluation-targets-toolbar-actions.tsx | |
| parent | 0eb030580b5cbe5f03d570c3c9d8c519bac3b783 (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.tsx | 298 |
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 |
