summaryrefslogtreecommitdiff
path: root/lib/tech-vendor-candidates/table/invite-candidates-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tech-vendor-candidates/table/invite-candidates-dialog.tsx')
-rw-r--r--lib/tech-vendor-candidates/table/invite-candidates-dialog.tsx230
1 files changed, 0 insertions, 230 deletions
diff --git a/lib/tech-vendor-candidates/table/invite-candidates-dialog.tsx b/lib/tech-vendor-candidates/table/invite-candidates-dialog.tsx
deleted file mode 100644
index 570cf96a..00000000
--- a/lib/tech-vendor-candidates/table/invite-candidates-dialog.tsx
+++ /dev/null
@@ -1,230 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { type Row } from "@tanstack/react-table"
-import { Loader, Mail, AlertCircle, XCircle } from "lucide-react"
-import { toast } from "sonner"
-
-import { useMediaQuery } from "@/hooks/use-media-query"
-import { Button } from "@/components/ui/button"
-import {
- Dialog,
- DialogClose,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog"
-import {
- Drawer,
- DrawerClose,
- DrawerContent,
- DrawerDescription,
- DrawerFooter,
- DrawerHeader,
- DrawerTitle,
- DrawerTrigger,
-} from "@/components/ui/drawer"
-import {
- Alert,
- AlertTitle,
- AlertDescription
-} from "@/components/ui/alert"
-
-import { VendorCandidates } from "@/db/schema/vendors"
-import { bulkUpdateVendorCandidateStatus } from "../service"
-import { useSession } from "next-auth/react" // next-auth 세션 훅
-
-interface InviteCandidatesDialogProps
- extends React.ComponentPropsWithoutRef<typeof Dialog> {
- candidates: Row<VendorCandidates>["original"][]
- showTrigger?: boolean
- onSuccess?: () => void
-}
-
-export function InviteCandidatesDialog({
- candidates,
- showTrigger = true,
- onSuccess,
- ...props
-}: InviteCandidatesDialogProps) {
- const [isInvitePending, startInviteTransition] = React.useTransition()
- const isDesktop = useMediaQuery("(min-width: 640px)")
- const { data: session, status } = useSession()
-
- // 후보자를 상태별로 분류
- const discardedCandidates = candidates.filter(candidate => candidate.status === "DISCARDED")
- const nonDiscardedCandidates = candidates.filter(candidate => candidate.status !== "DISCARDED")
-
- // 이메일 유무에 따라 초대 가능한 후보자 분류 (DISCARDED가 아닌 후보자 중에서)
- const candidatesWithEmail = nonDiscardedCandidates.filter(candidate => candidate.contactEmail)
- const candidatesWithoutEmail = nonDiscardedCandidates.filter(candidate => !candidate.contactEmail)
-
- // 각 카테고리 수
- const invitableCount = candidatesWithEmail.length
- const hasUninvitableCandidates = candidatesWithoutEmail.length > 0
- const hasDiscardedCandidates = discardedCandidates.length > 0
-
- function onInvite() {
- startInviteTransition(async () => {
- // 이메일이 있고 DISCARDED가 아닌 후보자만 상태 업데이트
-
- if (!session?.user?.id) {
- toast.error("인증 오류. 로그인 정보를 찾을 수 없습니다.")
- return
- }
-
- const { error } = await bulkUpdateVendorCandidateStatus({
- ids: candidatesWithEmail.map((candidate) => candidate.id),
- status: "INVITED",
- })
-
- if (error) {
- toast.error(error)
- return
- }
-
- props.onOpenChange?.(false)
-
- if (invitableCount === 0) {
- toast.warning("No invitation sent - no eligible candidates with email addresses")
- } else {
- let skipMessage = ""
-
- if (hasUninvitableCandidates && hasDiscardedCandidates) {
- skipMessage = ` ${candidatesWithoutEmail.length} candidates without email and ${discardedCandidates.length} discarded candidates were skipped.`
- } else if (hasUninvitableCandidates) {
- skipMessage = ` ${candidatesWithoutEmail.length} candidates without email were skipped.`
- } else if (hasDiscardedCandidates) {
- skipMessage = ` ${discardedCandidates.length} discarded candidates were skipped.`
- }
-
- toast.success(`Invitation emails sent to ${invitableCount} candidates.${skipMessage}`)
- }
-
- onSuccess?.()
- })
- }
-
- // 초대 버튼 비활성화 조건
- const disableInviteButton = isInvitePending || invitableCount === 0
-
- const DialogComponent = (
- <>
- <div className="space-y-4">
- {/* 이메일 없는 후보자 알림 */}
- {hasUninvitableCandidates && (
- <Alert>
- <AlertCircle className="h-4 w-4" />
- <AlertTitle>Missing Email Addresses</AlertTitle>
- <AlertDescription>
- {candidatesWithoutEmail.length} candidate{candidatesWithoutEmail.length > 1 ? 's' : ''} {candidatesWithoutEmail.length > 1 ? 'don&apos;t' : 'doesn&apos;t'} have email addresses and won&apos;t receive invitations.
- </AlertDescription>
- </Alert>
- )}
-
- {/* 폐기된 후보자 알림 */}
- {hasDiscardedCandidates && (
- <Alert variant="destructive">
- <XCircle className="h-4 w-4" />
- <AlertTitle>Discarded Candidates</AlertTitle>
- <AlertDescription>
- {discardedCandidates.length} candidate{discardedCandidates.length > 1 ? 's have' : ' has'} been discarded and won&apos;t receive invitations.
- </AlertDescription>
- </Alert>
- )}
-
- <DialogDescription>
- {invitableCount > 0 ? (
- <>
- This will send invitation emails to{" "}
- <span className="font-medium">{invitableCount}</span>
- {invitableCount === 1 ? " candidate" : " candidates"} and change their status to INVITED.
- </>
- ) : (
- <>
- No candidates can be invited because none of the selected candidates have valid email addresses or they have been discarded.
- </>
- )}
- </DialogDescription>
- </div>
- </>
- )
-
- if (isDesktop) {
- return (
- <Dialog {...props}>
- {showTrigger ? (
- <DialogTrigger asChild>
- <Button variant="outline" size="sm" className="gap-2">
- <Mail className="size-4" aria-hidden="true" />
- Invite ({candidates.length})
- </Button>
- </DialogTrigger>
- ) : null}
- <DialogContent>
- <DialogHeader>
- <DialogTitle>Send invitations?</DialogTitle>
- </DialogHeader>
- {DialogComponent}
- <DialogFooter className="gap-2 sm:space-x-0">
- <DialogClose asChild>
- <Button variant="outline">Cancel</Button>
- </DialogClose>
- <Button
- aria-label="Invite selected vendors"
- variant="default"
- onClick={onInvite}
- disabled={disableInviteButton}
- >
- {isInvitePending && (
- <Loader
- className="mr-2 size-4 animate-spin"
- aria-hidden="true"
- />
- )}
- Send Invitations
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- )
- }
-
- return (
- <Drawer {...props}>
- {showTrigger ? (
- <DrawerTrigger asChild>
- <Button variant="outline" size="sm" className="gap-2">
- <Mail className="size-4" aria-hidden="true" />
- Invite ({candidates.length})
- </Button>
- </DrawerTrigger>
- ) : null}
- <DrawerContent>
- <DrawerHeader>
- <DrawerTitle>Send invitations?</DrawerTitle>
- </DrawerHeader>
- {DialogComponent}
- <DrawerFooter className="gap-2 sm:space-x-0">
- <DrawerClose asChild>
- <Button variant="outline">Cancel</Button>
- </DrawerClose>
- <Button
- aria-label="Invite selected vendors"
- variant="default"
- onClick={onInvite}
- disabled={disableInviteButton}
- >
- {isInvitePending && (
- <Loader className="mr-2 size-4 animate-spin" aria-hidden="true" />
- )}
- Send Invitations
- </Button>
- </DrawerFooter>
- </DrawerContent>
- </Drawer>
- )
-} \ No newline at end of file