"use client" import * as React from "react" import { SelectTrigger } from "@radix-ui/react-select" import { type Table } from "@tanstack/react-table" import { ArrowUp, CheckCircle2, Download, Loader, Trash2, X, Mail, } from "lucide-react" import { toast } from "sonner" import { exportTableToExcel } from "@/lib/export" import { Button } from "@/components/ui/button" import { Portal } from "@/components/ui/portal" import { Select, SelectContent, SelectGroup, SelectItem, } from "@/components/ui/select" import { Separator } from "@/components/ui/separator" import { Tooltip, TooltipContent, TooltipTrigger, } from "@/components/ui/tooltip" import { Kbd } from "@/components/kbd" import { useSession } from "next-auth/react" // next-auth 세션 훅 import { ActionConfirmDialog } from "@/components/ui/action-dialog" import { VendorCandidatesWithVendorInfo, vendorCandidates } from "@/db/schema/vendors" import { bulkUpdateVendorCandidateStatus, removeCandidates, } from "../service" /** * 테이블 상단/하단에 고정되는 Floating Bar * 상태 일괄 변경, 초대, 삭제, Export 등을 수행 */ interface CandidatesTableFloatingBarProps { table: Table } export function VendorCandidateTableFloatingBar({ table, }: CandidatesTableFloatingBarProps) { const rows = table.getFilteredSelectedRowModel().rows const { data: session, status } = useSession() // React 18의 startTransition 사용 (isPending으로 트랜지션 상태 확인) const [isPending, startTransition] = React.useTransition() const [action, setAction] = React.useState< "update-status" | "export" | "delete" | "invite" >() const [popoverOpen, setPopoverOpen] = React.useState(false) // ESC 키로 selection 해제 React.useEffect(() => { function handleKeyDown(event: KeyboardEvent) { if (event.key === "Escape") { table.toggleAllRowsSelected(false) } } window.addEventListener("keydown", handleKeyDown) return () => window.removeEventListener("keydown", handleKeyDown) }, [table]) // 공용 Confirm Dialog (ActionConfirmDialog) 제어 const [confirmDialogOpen, setConfirmDialogOpen] = React.useState(false) const [confirmProps, setConfirmProps] = React.useState<{ title: string description?: string onConfirm: () => Promise | void }>({ title: "", description: "", onConfirm: () => {}, }) /** * 1) 삭제 버튼 클릭 시 Confirm Dialog 열기 */ function handleDeleteConfirm() { setAction("delete") setConfirmProps({ title: `Delete ${rows.length} candidate${ rows.length > 1 ? "s" : "" }?`, description: "This action cannot be undone.", onConfirm: async () => { startTransition(async () => { if (!session?.user?.id) { toast.error("인증 오류. 로그인 정보를 찾을 수 없습니다.") return } const userId = Number(session.user.id) // removeCandidates 호출 시 userId를 넘긴다고 가정 const { error } = await removeCandidates( { ids: rows.map((row) => row.original.id), }, userId ) if (error) { toast.error(error) return } toast.success("Candidates deleted successfully") table.toggleAllRowsSelected(false) setConfirmDialogOpen(false) }) }, }) setConfirmDialogOpen(true) } /** * 2) 선택된 후보들의 상태 일괄 업데이트 */ function handleSelectStatus(newStatus: VendorCandidatesWithVendorInfo["status"]) { setAction("update-status") setConfirmProps({ title: `Update ${rows.length} candidate${ rows.length > 1 ? "s" : "" } with status: ${newStatus}?`, description: "This action will override their current status.", onConfirm: async () => { startTransition(async () => { if (!session?.user?.id) { toast.error("인증 오류. 로그인 정보를 찾을 수 없습니다.") return } const userId = Number(session.user.id) const { error } = await bulkUpdateVendorCandidateStatus({ ids: rows.map((row) => row.original.id), status: newStatus, userId, comment: `Bulk status update to ${newStatus}`, }) if (error) { toast.error(error) return } toast.success("Candidates updated") setConfirmDialogOpen(false) table.toggleAllRowsSelected(false) }) }, }) setConfirmDialogOpen(true) } /** * 3) 초대하기 (status = "INVITED" + 이메일 발송) */ function handleInvite() { setAction("invite") setConfirmProps({ title: `Invite ${rows.length} candidate${ rows.length > 1 ? "s" : "" }?`, description: "This will change their status to INVITED and send invitation emails.", onConfirm: async () => { startTransition(async () => { if (!session?.user?.id) { toast.error("인증 오류. 로그인 정보를 찾을 수 없습니다.") return } const userId = Number(session.user.id) const { error } = await bulkUpdateVendorCandidateStatus({ ids: rows.map((row) => row.original.id), status: "INVITED", userId, comment: "Bulk invite action", }) if (error) { toast.error(error) return } toast.success("Invitation emails sent successfully") table.toggleAllRowsSelected(false) setConfirmDialogOpen(false) }) }, }) setConfirmDialogOpen(true) } return ( <> {/* 선택된 row가 있을 때 표시되는 Floating Bar */}
{/* 선택된 갯수 표시 + Clear selection 버튼 */}
{rows.length} selected

Clear selection

Esc
{/* 우측 액션들: 초대, 상태변경, Export, 삭제 */}
{/* 초대하기 */}

Send invitation emails

{/* 상태 업데이트 (Select) */} {/* Export 버튼 */}

Export candidates

{/* 삭제 버튼 */}

Delete candidates

{/* 공용 Confirm Dialog */} ) }