diff options
| author | joonhoekim <26rote@gmail.com> | 2025-03-25 15:55:45 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-03-25 15:55:45 +0900 |
| commit | 1a2241c40e10193c5ff7008a7b7b36cc1d855d96 (patch) | |
| tree | 8a5587f10ca55b162d7e3254cb088b323a34c41b /lib/vendor-document-list/table/delete-docs-dialog.tsx | |
initial commit
Diffstat (limited to 'lib/vendor-document-list/table/delete-docs-dialog.tsx')
| -rw-r--r-- | lib/vendor-document-list/table/delete-docs-dialog.tsx | 231 |
1 files changed, 231 insertions, 0 deletions
diff --git a/lib/vendor-document-list/table/delete-docs-dialog.tsx b/lib/vendor-document-list/table/delete-docs-dialog.tsx new file mode 100644 index 00000000..8813c742 --- /dev/null +++ b/lib/vendor-document-list/table/delete-docs-dialog.tsx @@ -0,0 +1,231 @@ +"use client" + +import * as React from "react" +import { type Row } from "@tanstack/react-table" +import { Loader, Trash, AlertCircle } 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, AlertDescription } from "@/components/ui/alert" +import { DocumentStagesView } from "@/db/schema/vendorDocu" +import { removeDocuments } from "../service" + +interface DeleteDocumentsDialogProps + extends React.ComponentPropsWithoutRef<typeof Dialog> { + documents: Row<DocumentStagesView>["original"][] + showTrigger?: boolean + onSuccess?: () => void +} + +export function DeleteDocumentsDialog({ + documents, + showTrigger = true, + onSuccess, + ...props +}: DeleteDocumentsDialogProps) { + const [isDeletePending, startDeleteTransition] = React.useTransition() + const isDesktop = useMediaQuery("(min-width: 640px)") + + // "pending" 상태인 문서만 필터링 + const pendingDocuments = documents.filter(doc => doc.status === "pending") + const nonPendingDocuments = documents.filter(doc => doc.status !== "pending") + + const hasMixedStatus = pendingDocuments.length > 0 && nonPendingDocuments.length > 0 + const hasNoPendingDocuments = pendingDocuments.length === 0 + + function onDelete() { + // 삭제할 문서가 없으면 경고 + if (pendingDocuments.length === 0) { + toast.error("No pending documents to delete") + props.onOpenChange?.(false) + return + } + + startDeleteTransition(async () => { + // "pending" 상태인 문서 ID만 전달 + const { success, error } = await removeDocuments({ + ids: pendingDocuments.map((document) => document.documentId) + }) + + if (!success) { + toast.error(error || "Failed to delete documents") + return + } + + props.onOpenChange?.(false) + + // 적절한 성공 메시지 표시 + if (hasMixedStatus) { + toast.success(`${pendingDocuments.length} pending document(s) deleted successfully. ${nonPendingDocuments.length} non-pending document(s) were not affected.`) + } else { + toast.success(`${pendingDocuments.length} document(s) deleted successfully`) + } + + onSuccess?.() + }) + } + + // 선택된 문서 상태에 대한 알림 메시지 렌더링 + const renderStatusAlert = () => { + if (hasNoPendingDocuments) { + return ( + <Alert variant="destructive" className="mb-4"> + <AlertCircle className="h-4 w-4" /> + <AlertDescription> + None of the selected documents are in "pending" status. Only pending documents can be deleted. + </AlertDescription> + </Alert> + ) + } + + if (hasMixedStatus) { + return ( + <Alert className="mb-4"> + <AlertCircle className="h-4 w-4" /> + <AlertDescription> + Only the {pendingDocuments.length} document(s) with "pending" status will be deleted. + {nonPendingDocuments.length} document(s) cannot be deleted because they are not in pending status. + </AlertDescription> + </Alert> + ) + } + + return null + } + + if (isDesktop) { + return ( + <Dialog {...props}> + {showTrigger ? ( + <DialogTrigger asChild> + <Button variant="outline" size="sm"> + <Trash className="mr-2 size-4" aria-hidden="true" /> + Delete ({documents.length}) + </Button> + </DialogTrigger> + ) : null} + <DialogContent> + <DialogHeader> + <DialogTitle>Are you absolutely sure?</DialogTitle> + <DialogDescription> + This action cannot be undone. Only documents with "pending" status can be deleted. + </DialogDescription> + </DialogHeader> + + {renderStatusAlert()} + + <div> + {pendingDocuments.length > 0 && ( + <p className="text-sm text-muted-foreground mb-2"> + {pendingDocuments.length} pending document(s) will be deleted: + </p> + )} + {pendingDocuments.length > 0 && ( + <ul className="text-sm list-disc pl-5 mb-4 max-h-40 overflow-y-auto"> + {pendingDocuments.map(doc => ( + <li key={doc.documentId} className="text-muted-foreground">{doc.docNumber} - {doc.title}</li> + ))} + </ul> + )} + </div> + + <DialogFooter className="gap-2 sm:space-x-0"> + <DialogClose asChild> + <Button variant="outline">Cancel</Button> + </DialogClose> + <Button + aria-label="Delete selected rows" + variant="destructive" + onClick={onDelete} + disabled={isDeletePending || pendingDocuments.length === 0} + > + {isDeletePending && ( + <Loader + className="mr-2 size-4 animate-spin" + aria-hidden="true" + /> + )} + Delete {pendingDocuments.length > 0 ? `(${pendingDocuments.length})` : ""} + </Button> + </DialogFooter> + </DialogContent> + </Dialog> + ) + } + + return ( + <Drawer {...props}> + {showTrigger ? ( + <DrawerTrigger asChild> + <Button variant="outline" size="sm"> + <Trash className="mr-2 size-4" aria-hidden="true" /> + Delete ({documents.length}) + </Button> + </DrawerTrigger> + ) : null} + <DrawerContent> + <DrawerHeader> + <DrawerTitle>Are you absolutely sure?</DrawerTitle> + <DrawerDescription> + This action cannot be undone. Only documents with "pending" status can be deleted. + </DrawerDescription> + </DrawerHeader> + + {renderStatusAlert()} + + <div className="px-4"> + {pendingDocuments.length > 0 && ( + <p className="text-sm text-muted-foreground mb-2"> + {pendingDocuments.length} pending document(s) will be deleted: + </p> + )} + {pendingDocuments.length > 0 && ( + <ul className="text-sm list-disc pl-5 mb-4 max-h-40 overflow-y-auto"> + {pendingDocuments.map(doc => ( + <li key={doc.documentId} className="text-muted-foreground">{doc.docNumber} - {doc.title}</li> + ))} + </ul> + )} + </div> + + <DrawerFooter className="gap-2 sm:space-x-0"> + <DrawerClose asChild> + <Button variant="outline">Cancel</Button> + </DrawerClose> + <Button + aria-label="Delete selected rows" + variant="destructive" + onClick={onDelete} + disabled={isDeletePending || pendingDocuments.length === 0} + > + {isDeletePending && ( + <Loader className="mr-2 size-4 animate-spin" aria-hidden="true" /> + )} + Delete {pendingDocuments.length > 0 ? `(${pendingDocuments.length})` : ""} + </Button> + </DrawerFooter> + </DrawerContent> + </Drawer> + ) +}
\ No newline at end of file |
