diff options
Diffstat (limited to 'lib/legal-review/status/legal-works-toolbar-actions.tsx')
| -rw-r--r-- | lib/legal-review/status/legal-works-toolbar-actions.tsx | 286 |
1 files changed, 286 insertions, 0 deletions
diff --git a/lib/legal-review/status/legal-works-toolbar-actions.tsx b/lib/legal-review/status/legal-works-toolbar-actions.tsx new file mode 100644 index 00000000..82fbc80a --- /dev/null +++ b/lib/legal-review/status/legal-works-toolbar-actions.tsx @@ -0,0 +1,286 @@ +"use client" + +import * as React from "react" +import { type Table } from "@tanstack/react-table" +import { + Plus, + Send, + Download, + RefreshCw, + FileText, + MessageSquare +} 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 { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" +import { CreateLegalWorkDialog } from "./create-legal-work-dialog" +import { RequestReviewDialog } from "./request-review-dialog" +import { exportTableToExcel } from "@/lib/export" +import { getLegalWorks } from "../service" +import { LegalWorksDetailView } from "@/db/schema" +import { DeleteLegalWorksDialog } from "./delete-legal-works-dialog" + +type LegalWorkData = LegalWorksDetailView + +interface LegalWorksTableToolbarActionsProps { + table: Table<LegalWorkData> + onRefresh?: () => void +} + +export function LegalWorksTableToolbarActions({ + table, + onRefresh +}: LegalWorksTableToolbarActionsProps) { + const [isLoading, setIsLoading] = React.useState(false) + const [createDialogOpen, setCreateDialogOpen] = React.useState(false) + const [reviewDialogOpen, setReviewDialogOpen] = React.useState(false) + const router = useRouter() + const { data: session } = useSession() + + // 사용자 ID 가져오기 + const userId = React.useMemo(() => { + return session?.user?.id ? Number(session.user.id) : 1 + }, [session]) + + // 선택된 행들 - 단일 선택만 허용 + const selectedRows = table.getFilteredSelectedRowModel().rows + const hasSelection = selectedRows.length > 0 + const isSingleSelection = selectedRows.length === 1 + const isMultipleSelection = selectedRows.length > 1 + + // 선택된 단일 work + const selectedWork = isSingleSelection ? selectedRows[0].original : null + + // const canDeleateReview = selectedRows.filter(v=>v.status === '신규등록') + + + const deletableRows = React.useMemo(() => { + return selectedRows.filter(row => { + const status = row.original.status + return status ==="신규등록" + }) + }, [selectedRows]) + + const hasDeletableRows = deletableRows.length > 0 + + // 선택된 work의 상태 확인 + const canRequestReview = selectedWork?.status === "신규등록" + const canAssign = selectedWork?.status === "신규등록" + + // ---------------------------------------------------------------- + // 신규 생성 + // ---------------------------------------------------------------- + const handleCreateNew = React.useCallback(() => { + setCreateDialogOpen(true) + }, []) + + // ---------------------------------------------------------------- + // 검토 요청 (단일 선택만) + // ---------------------------------------------------------------- + const handleRequestReview = React.useCallback(() => { + if (!isSingleSelection) { + toast.error("검토요청은 한 건씩만 가능합니다. 하나의 항목만 선택해주세요.") + return + } + + if (!canRequestReview) { + toast.error("신규등록 상태인 항목만 검토요청이 가능합니다.") + return + } + + setReviewDialogOpen(true) + }, [isSingleSelection, canRequestReview]) + + // ---------------------------------------------------------------- + // 다이얼로그 성공 핸들러 + // ---------------------------------------------------------------- + const handleActionSuccess = React.useCallback(() => { + table.resetRowSelection() + onRefresh?.() + router.refresh() + }, [table, onRefresh, router]) + + // ---------------------------------------------------------------- + // 내보내기 핸들러 + // ---------------------------------------------------------------- + const handleExport = React.useCallback(() => { + exportTableToExcel(table, { + filename: "legal-works-list", + excludeColumns: ["select", "actions"], + }) + }, [table]) + + // ---------------------------------------------------------------- + // 새로고침 핸들러 + // ---------------------------------------------------------------- + const handleRefresh = React.useCallback(async () => { + setIsLoading(true) + try { + onRefresh?.() + toast.success("데이터를 새로고침했습니다.") + } catch (error) { + console.error("새로고침 오류:", error) + toast.error("새로고침 중 오류가 발생했습니다.") + } finally { + setIsLoading(false) + } + }, [onRefresh]) + + return ( + <> + <div className="flex items-center gap-2"> + + {hasDeletableRows&&( + <DeleteLegalWorksDialog + legalWorks={deletableRows.map(row => row.original)} + showTrigger={hasDeletableRows} + onSuccess={() => { + table.toggleAllRowsSelected(false) + // onRefresh?.() + }} + /> + )} + {/* 신규 생성 버튼 */} + <Button + variant="default" + size="sm" + className="gap-2" + onClick={handleCreateNew} + disabled={isLoading} + > + <Plus className="size-4" aria-hidden="true" /> + <span className="hidden sm:inline">신규 등록</span> + </Button> + + {/* 유틸리티 버튼들 */} + <div className="flex items-center gap-1 border-l pl-2 ml-2"> + <Button + variant="outline" + size="sm" + onClick={handleRefresh} + disabled={isLoading} + className="gap-2" + > + <RefreshCw className={`size-4 ${isLoading ? 'animate-spin' : ''}`} aria-hidden="true" /> + <span className="hidden sm:inline">새로고침</span> + </Button> + + <Button + variant="outline" + size="sm" + onClick={handleExport} + className="gap-2" + > + <Download 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"> + {/* 다중 선택 경고 메시지 */} + {isMultipleSelection && ( + <div className="text-xs text-amber-600 bg-amber-50 px-2 py-1 rounded border border-amber-200"> + 검토요청은 한 건씩만 가능합니다 + </div> + )} + + {/* 검토 요청 버튼 (단일 선택시만) */} + {isSingleSelection && ( + <Button + variant="default" + size="sm" + className="gap-2 bg-blue-600 hover:bg-blue-700" + onClick={handleRequestReview} + disabled={isLoading || !canRequestReview} + > + <Send className="size-4" aria-hidden="true" /> + <span className="hidden sm:inline"> + {canRequestReview ? "검토요청" : "검토불가"} + </span> + </Button> + )} + + {/* 추가 액션 드롭다운 */} + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button + variant="outline" + size="sm" + className="gap-2" + disabled={isLoading} + > + <MessageSquare className="size-4" aria-hidden="true" /> + <span className="hidden sm:inline">추가 작업</span> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + <DropdownMenuItem + onClick={() => toast.info("담당자 배정 기능을 준비 중입니다.")} + disabled={!isSingleSelection || !canAssign} + > + <FileText className="size-4 mr-2" /> + 담당자 배정 + </DropdownMenuItem> + <DropdownMenuSeparator /> + <DropdownMenuItem + onClick={() => toast.info("상태 변경 기능을 준비 중입니다.")} + disabled={!isSingleSelection} + > + <RefreshCw className="size-4 mr-2" /> + 상태 변경 + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + </div> + )} + + {/* 선택된 항목 정보 표시 */} + {hasSelection && ( + <div className="flex items-center gap-1 border-l pl-2 ml-2"> + <div className="text-xs text-muted-foreground"> + {isSingleSelection ? ( + <> + 선택: #{selectedWork?.id} ({selectedWork?.category}) + {selectedWork?.vendorName && ` | ${selectedWork.vendorName}`} + {selectedWork?.status && ` | ${selectedWork.status}`} + </> + ) : ( + `선택: ${selectedRows.length}건 (개별 처리 필요)` + )} + </div> + </div> + )} + </div> + + {/* 다이얼로그들 */} + {/* 신규 생성 다이얼로그 */} + <CreateLegalWorkDialog + open={createDialogOpen} + onOpenChange={setCreateDialogOpen} + onSuccess={handleActionSuccess} + onDataChange={onRefresh} + /> + + {/* 검토 요청 다이얼로그 - 단일 work 전달 */} + {selectedWork && ( + <RequestReviewDialog + open={reviewDialogOpen} + onOpenChange={setReviewDialogOpen} + work={selectedWork} // 단일 객체로 변경 + onSuccess={handleActionSuccess} + /> + )} + </> + ) +}
\ No newline at end of file |
