diff options
Diffstat (limited to 'lib/qna/table/delete-qna-dialog.tsx')
| -rw-r--r-- | lib/qna/table/delete-qna-dialog.tsx | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/lib/qna/table/delete-qna-dialog.tsx b/lib/qna/table/delete-qna-dialog.tsx new file mode 100644 index 00000000..55dcd366 --- /dev/null +++ b/lib/qna/table/delete-qna-dialog.tsx @@ -0,0 +1,250 @@ +"use client" + +import * as React from "react" +import { toast } from "sonner" +import { Trash2 } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + Drawer, + DrawerContent, + DrawerDescription, + DrawerFooter, + DrawerHeader, + DrawerTitle, + DrawerTrigger, +} from "@/components/ui/drawer" +import { Badge } from "@/components/ui/badge" +import { ScrollArea } from "@/components/ui/scroll-area" + +import { QnaViewSelect } from "@/db/schema" +import { useMediaQuery } from "@/hooks/use-media-query" +import { deleteQna } from "../service" + +interface DeleteQnaDialogProps { + open: boolean + onOpenChange: (open: boolean) => void + qnas: QnaViewSelect[] + showTrigger?: boolean + onSuccess?: () => void +} + +export function DeleteQnaDialog({ + open, + onOpenChange, + qnas, + showTrigger = true, + onSuccess +}: DeleteQnaDialogProps) { + const [isDeletePending, startDeleteTransition] = React.useTransition() + const isDesktop = useMediaQuery("(min-width: 640px)") + + const qnaCount = qnas.length + const isMultiple = qnaCount > 1 + + async function handleDelete() { + startDeleteTransition(async () => { + try { + const promises = qnas.map(qna => deleteQna(qna.id)) + const results = await Promise.all(promises) + + const successCount = results.filter(result => result.success).length + const failCount = results.length - successCount + + if (successCount > 0) { + toast.success( + isMultiple + ? `${successCount}개의 질문이 삭제되었습니다.` + : "질문이 삭제되었습니다." + ) + onSuccess?.() + } + + if (failCount > 0) { + toast.error( + isMultiple + ? `${failCount}개의 질문 삭제에 실패했습니다.` + : "질문 삭제에 실패했습니다." + ) + } + + if (successCount > 0) { + onOpenChange(false) + } + } catch (error) { + toast.error("삭제 중 오류가 발생했습니다.") + console.error("질문 삭제 오류:", error) + } + }) + } + + const title = isMultiple ? `${qnaCount}개 질문 삭제` : "질문 삭제" + const description = isMultiple + ? "선택한 질문들을 정말로 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다." + : "이 질문을 정말로 삭제하시겠습니까? 이 작업은 되돌릴 수 없습니다." + + if (isDesktop) { + return ( + <Dialog open={open} onOpenChange={onOpenChange}> + {showTrigger && ( + <DialogTrigger asChild> + <Button variant="outline" size="sm"> + <Trash2 className="mr-2 h-4 w-4" /> + 삭제 + </Button> + </DialogTrigger> + )} + <DialogContent className="max-w-2xl"> + <DialogHeader> + <DialogTitle>{title}</DialogTitle> + <DialogDescription>{description}</DialogDescription> + </DialogHeader> + + {/* 삭제할 질문 목록 */} + <div className="max-h-[300px]"> + <div className="text-sm font-medium mb-2">삭제 대상:</div> + <ScrollArea className="max-h-[250px] border rounded-md p-3"> + <div className="space-y-3"> + {qnas.map((qna, index) => ( + <div key={qna.id} className="flex items-start gap-3 p-3 border rounded-md bg-muted/50"> + <div className="font-mono text-xs text-muted-foreground mt-1"> + {index + 1}. + </div> + <div className="flex-1 min-w-0"> + <div className="font-medium text-sm line-clamp-2 mb-1"> + {qna.title} + </div> + <div className="flex items-center gap-2 text-xs text-muted-foreground"> + <span>{qna.authorName}</span> + <span>•</span> + <span>{qna.companyName || "미지정"}</span> + <span>•</span> + <span>답변 {qna.totalAnswers}개</span> + </div> + <div className="flex items-center gap-1 mt-2"> + {qna.hasAnswers && ( + <Badge variant="secondary" className="text-xs"> + 답변있음 + </Badge> + )} + {qna.isPopular && ( + <Badge variant="default" className="text-xs"> + 인기질문 + </Badge> + )} + </div> + </div> + </div> + ))} + </div> + </ScrollArea> + </div> + + {/* 경고 메시지 */} + <div className="rounded-md border border-destructive/20 bg-destructive/5 p-3"> + <div className="text-sm text-destructive"> + <strong>주의:</strong> 질문을 삭제하면 해당 질문의 모든 답변과 댓글도 함께 삭제됩니다. + </div> + </div> + + <DialogFooter> + <Button + type="button" + variant="outline" + onClick={() => onOpenChange(false)} + disabled={isDeletePending} + > + 취소 + </Button> + <Button + type="button" + variant="destructive" + onClick={handleDelete} + disabled={isDeletePending} + > + {isDeletePending ? "삭제 중..." : "삭제"} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + ) + } + + return ( + <Drawer open={open} onOpenChange={onOpenChange}> + {showTrigger && ( + <DrawerTrigger asChild> + <Button variant="outline" size="sm"> + <Trash2 className="mr-2 h-4 w-4" /> + 삭제 + </Button> + </DrawerTrigger> + )} + <DrawerContent> + <DrawerHeader> + <DrawerTitle>{title}</DrawerTitle> + <DrawerDescription>{description}</DrawerDescription> + </DrawerHeader> + + <div className="px-4 pb-4"> + {/* 삭제할 질문 목록 */} + <div className="mb-4"> + <div className="text-sm font-medium mb-2">삭제 대상:</div> + <div className="max-h-[200px] space-y-2 overflow-y-auto"> + {qnas.map((qna, index) => ( + <div key={qna.id} className="flex items-start gap-2 p-2 border rounded-md bg-muted/50"> + <div className="font-mono text-xs text-muted-foreground mt-1"> + {index + 1}. + </div> + <div className="flex-1 min-w-0"> + <div className="font-medium text-sm line-clamp-1 mb-1"> + {qna.title} + </div> + <div className="text-xs text-muted-foreground"> + {qna.authorName} • 답변 {qna.totalAnswers}개 + </div> + </div> + </div> + ))} + </div> + </div> + + {/* 경고 메시지 */} + <div className="rounded-md border border-destructive/20 bg-destructive/5 p-3 mb-4"> + <div className="text-sm text-destructive"> + <strong>주의:</strong> 삭제된 질문과 관련 데이터는 복구할 수 없습니다. + </div> + </div> + </div> + + <DrawerFooter className="gap-2"> + <Button + type="button" + variant="destructive" + onClick={handleDelete} + disabled={isDeletePending} + > + {isDeletePending ? "삭제 중..." : "삭제"} + </Button> + <Button + type="button" + variant="outline" + onClick={() => onOpenChange(false)} + disabled={isDeletePending} + > + 취소 + </Button> + </DrawerFooter> + </DrawerContent> + </Drawer> + ) +}
\ No newline at end of file |
