summaryrefslogtreecommitdiff
path: root/lib/legal-review/status/legal-works-toolbar-actions.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/legal-review/status/legal-works-toolbar-actions.tsx')
-rw-r--r--lib/legal-review/status/legal-works-toolbar-actions.tsx286
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