summaryrefslogtreecommitdiff
path: root/lib/tech-vendor-candidates/table/candidates-table-floating-bar.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tech-vendor-candidates/table/candidates-table-floating-bar.tsx')
-rw-r--r--lib/tech-vendor-candidates/table/candidates-table-floating-bar.tsx395
1 files changed, 0 insertions, 395 deletions
diff --git a/lib/tech-vendor-candidates/table/candidates-table-floating-bar.tsx b/lib/tech-vendor-candidates/table/candidates-table-floating-bar.tsx
deleted file mode 100644
index baf4a583..00000000
--- a/lib/tech-vendor-candidates/table/candidates-table-floating-bar.tsx
+++ /dev/null
@@ -1,395 +0,0 @@
-"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<VendorCandidatesWithVendorInfo>
-}
-
-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> | 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 */}
- <div className="flex justify-center w-full my-4">
- <div className="flex items-center gap-2 rounded-md border bg-background p-2 text-foreground shadow">
- {/* 선택된 갯수 표시 + Clear selection 버튼 */}
- <div className="flex h-7 items-center rounded-md border border-dashed pl-2.5 pr-1">
- <span className="whitespace-nowrap text-xs">
- {rows.length} selected
- </span>
- <Separator orientation="vertical" className="ml-2 mr-1" />
- <Tooltip>
- <TooltipTrigger asChild>
- <Button
- variant="ghost"
- size="icon"
- className="size-5 hover:border"
- onClick={() => table.toggleAllRowsSelected(false)}
- >
- <X className="size-3.5 shrink-0" aria-hidden="true" />
- </Button>
- </TooltipTrigger>
- <TooltipContent className="flex items-center border bg-accent px-2 py-1 font-semibold text-foreground dark:bg-zinc-900">
- <p className="mr-2">Clear selection</p>
- <Kbd abbrTitle="Escape" variant="outline">
- Esc
- </Kbd>
- </TooltipContent>
- </Tooltip>
- </div>
-
- <Separator orientation="vertical" className="hidden h-5 sm:block" />
-
- {/* 우측 액션들: 초대, 상태변경, Export, 삭제 */}
- <div className="flex items-center gap-1.5">
- {/* 초대하기 */}
- <Tooltip>
- <TooltipTrigger asChild>
- <Button
- variant="secondary"
- size="sm"
- className="h-7 border"
- onClick={handleInvite}
- disabled={isPending}
- >
- {isPending && action === "invite" ? (
- <Loader
- className="mr-1 size-3.5 animate-spin"
- aria-hidden="true"
- />
- ) : (
- <Mail className="mr-1 size-3.5" aria-hidden="true" />
- )}
- <span>Invite</span>
- </Button>
- </TooltipTrigger>
- <TooltipContent className="border bg-accent font-semibold text-foreground dark:bg-zinc-900">
- <p>Send invitation emails</p>
- </TooltipContent>
- </Tooltip>
-
- {/* 상태 업데이트 (Select) */}
- <Select
- onValueChange={(value: VendorCandidatesWithVendorInfo["status"]) => {
- handleSelectStatus(value)
- }}
- >
- <Tooltip>
- <SelectTrigger asChild>
- <TooltipTrigger asChild>
- <Button
- variant="secondary"
- size="icon"
- className="size-7 border data-[state=open]:bg-accent data-[state=open]:text-accent-foreground"
- disabled={isPending}
- >
- {isPending && action === "update-status" ? (
- <Loader
- className="size-3.5 animate-spin"
- aria-hidden="true"
- />
- ) : (
- <CheckCircle2 className="size-3.5" aria-hidden="true" />
- )}
- </Button>
- </TooltipTrigger>
- </SelectTrigger>
- <TooltipContent className="border bg-accent font-semibold text-foreground dark:bg-zinc-900">
- <p>Update status</p>
- </TooltipContent>
- </Tooltip>
- <SelectContent align="center">
- <SelectGroup>
- {vendorCandidates.status.enumValues.map((status) => (
- <SelectItem
- key={status}
- value={status}
- className="capitalize"
- >
- {status}
- </SelectItem>
- ))}
- </SelectGroup>
- </SelectContent>
- </Select>
-
- {/* Export 버튼 */}
- <Tooltip>
- <TooltipTrigger asChild>
- <Button
- variant="secondary"
- size="icon"
- className="size-7 border"
- onClick={() => {
- setAction("export")
- startTransition(() => {
- exportTableToExcel(table, {
- excludeColumns: ["select", "actions"],
- onlySelected: true,
- })
- })
- }}
- disabled={isPending}
- >
- {isPending && action === "export" ? (
- <Loader
- className="size-3.5 animate-spin"
- aria-hidden="true"
- />
- ) : (
- <Download className="size-3.5" aria-hidden="true" />
- )}
- </Button>
- </TooltipTrigger>
- <TooltipContent className="border bg-accent font-semibold text-foreground dark:bg-zinc-900">
- <p>Export candidates</p>
- </TooltipContent>
- </Tooltip>
-
- {/* 삭제 버튼 */}
- <Tooltip>
- <TooltipTrigger asChild>
- <Button
- variant="secondary"
- size="icon"
- className="size-7 border"
- onClick={handleDeleteConfirm}
- disabled={isPending}
- >
- {isPending && action === "delete" ? (
- <Loader
- className="size-3.5 animate-spin"
- aria-hidden="true"
- />
- ) : (
- <Trash2 className="size-3.5" aria-hidden="true" />
- )}
- </Button>
- </TooltipTrigger>
- <TooltipContent className="border bg-accent font-semibold text-foreground dark:bg-zinc-900">
- <p>Delete candidates</p>
- </TooltipContent>
- </Tooltip>
- </div>
- </div>
- </div>
-
- {/* 공용 Confirm Dialog */}
- <ActionConfirmDialog
- open={confirmDialogOpen}
- onOpenChange={setConfirmDialogOpen}
- title={confirmProps.title}
- description={confirmProps.description}
- onConfirm={confirmProps.onConfirm}
- isLoading={
- isPending &&
- (action === "delete" || action === "update-status" || action === "invite")
- }
- confirmLabel={
- action === "delete"
- ? "Delete"
- : action === "update-status"
- ? "Update"
- : action === "invite"
- ? "Invite"
- : "Confirm"
- }
- confirmVariant={action === "delete" ? "destructive" : "default"}
- />
- </>
- )
-}