diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-28 11:44:16 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-28 11:44:16 +0000 |
| commit | c228a89c2834ee63b209bad608837c39643f350e (patch) | |
| tree | 39c9a121b556af872072dd80750dedf2d2d62335 /lib/gtc-contract/gtc-clauses/table/delete-gtc-clauses-dialog.tsx | |
| parent | 50ae0b8f02c034e60d4cbb504620dfa1575a836f (diff) | |
(대표님) 의존성 docx 추가, basicContract API, gtc(계약일반조건), 벤더평가 esg 평가데이터 내보내기 개선, S-EDP 피드백 대응(CLS_ID, ITEM NO 등)
Diffstat (limited to 'lib/gtc-contract/gtc-clauses/table/delete-gtc-clauses-dialog.tsx')
| -rw-r--r-- | lib/gtc-contract/gtc-clauses/table/delete-gtc-clauses-dialog.tsx | 175 |
1 files changed, 175 insertions, 0 deletions
diff --git a/lib/gtc-contract/gtc-clauses/table/delete-gtc-clauses-dialog.tsx b/lib/gtc-contract/gtc-clauses/table/delete-gtc-clauses-dialog.tsx new file mode 100644 index 00000000..29483c57 --- /dev/null +++ b/lib/gtc-contract/gtc-clauses/table/delete-gtc-clauses-dialog.tsx @@ -0,0 +1,175 @@ +"use client" + +import * as React from "react" +import { Loader, Trash2, AlertTriangle } from "lucide-react" +import { toast } from "sonner" + +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, + AlertDialogTrigger, +} from "@/components/ui/alert-dialog" +import { Button } from "@/components/ui/button" +import { Badge } from "@/components/ui/badge" + +import { type GtcClauseTreeView } from "@/db/schema/gtc" +import { useSession } from "next-auth/react" +import { deleteGtcClauses } from "../service" + +interface DeleteGtcClausesDialogProps + extends React.ComponentPropsWithRef<typeof AlertDialog> { + gtcClauses: GtcClauseTreeView[] + showTrigger?: boolean + onSuccess?: () => void +} + +export function DeleteGtcClausesDialog({ + gtcClauses, + showTrigger = true, + onSuccess, + ...props +}: DeleteGtcClausesDialogProps) { + const [isDeletePending, startDeleteTransition] = React.useTransition() + const { data: session } = useSession() + + const currentUserId = React.useMemo(() => { + return session?.user?.id ? Number(session.user.id) : null + }, [session]) + + function onDelete() { + startDeleteTransition(async () => { + if (!currentUserId) { + toast.error("로그인이 필요합니다") + return + } + + try { + // ✅ 한 번에 모든 조항 삭제 (배열로 전달) + const ids = gtcClauses.map(clause => clause.id) + const result = await deleteGtcClauses(ids) + + if (result.error) { + toast.error(`삭제 중 오류가 발생했습니다: ${result.error}`) + return + } + + props.onOpenChange?.(false) + toast.success( + `${result.deletedCount}개의 조항이 삭제되었습니다.` + ) + onSuccess?.() + } catch (error) { + console.error("Delete error:", error) + toast.error("조항 삭제 중 오류가 발생했습니다.") + } + }) + } + + const clausesWithChildren = gtcClauses.filter(clause => clause.childrenCount > 0) + const hasChildrenWarning = clausesWithChildren.length > 0 + + // 총 삭제될 하위 조항 수 계산 + const totalChildrenCount = clausesWithChildren.reduce((sum, clause) => sum + clause.childrenCount, 0) + + return ( + <AlertDialog {...props}> + {showTrigger && ( + <AlertDialogTrigger asChild> + <Button variant="outline" size="sm"> + <Trash2 className="mr-2 h-4 w-4" /> + 삭제 ({gtcClauses.length}) + </Button> + </AlertDialogTrigger> + )} + + <AlertDialogContent className="max-w-md"> + <AlertDialogHeader> + <div className="flex items-center gap-2"> + <AlertTriangle className="h-5 w-5 text-destructive" /> + <AlertDialogTitle>조항 삭제</AlertDialogTitle> + </div> + <AlertDialogDescription asChild> + <div className="space-y-3"> + <p> + 선택한 {gtcClauses.length}개의 조항을 <strong>완전히 삭제</strong>하시겠습니까? + 이 작업은 되돌릴 수 없습니다. + </p> + + {/* 삭제할 조항 목록 */} + <div className="space-y-2"> + <div className="text-sm font-medium">삭제할 조항:</div> + <div className="max-h-32 overflow-y-auto space-y-1"> + {gtcClauses.map((clause) => ( + <div key={clause.id} className="flex items-center gap-2 text-sm p-2 bg-muted/50 rounded"> + <Badge variant="outline" className="text-xs"> + {clause.itemNumber} + </Badge> + <span className="flex-1 truncate">{clause.subtitle}</span> + {clause.childrenCount > 0 && ( + <Badge variant="destructive" className="text-xs"> + 하위 {clause.childrenCount}개 + </Badge> + )} + </div> + ))} + </div> + </div> + + {/* 하위 조항 경고 */} + {hasChildrenWarning && ( + <div className="p-3 bg-destructive/10 border border-destructive/20 rounded-md"> + <div className="flex items-center gap-2 text-destructive text-sm font-medium mb-2"> + <AlertTriangle className="h-4 w-4" /> + 중요: 하위 조항 포함 삭제 + </div> + <div className="space-y-1 text-sm text-destructive/80"> + <p>하위 조항이 있는 조항을 삭제하면 모든 하위 조항도 함께 삭제됩니다.</p> + <p className="font-medium"> + 총 삭제될 조항: {gtcClauses.length + totalChildrenCount}개 + <span className="text-xs ml-1"> + (선택한 {gtcClauses.length}개 + 하위 {totalChildrenCount}개) + </span> + </p> + </div> + <div className="mt-2 text-xs text-destructive/70"> + 영향받는 조항: {clausesWithChildren.map(c => c.itemNumber).join(', ')} + </div> + </div> + )} + + <div className="p-2 bg-amber-50 border border-amber-200 rounded text-xs text-amber-800"> + ⚠️ <strong>실제 삭제</strong>: 데이터베이스에서 완전히 제거되며 복구할 수 없습니다. + </div> + </div> + </AlertDialogDescription> + </AlertDialogHeader> + + <AlertDialogFooter> + <AlertDialogCancel disabled={isDeletePending}> + Cancel + </AlertDialogCancel> + <AlertDialogAction + onClick={onDelete} + disabled={isDeletePending} + className="bg-destructive text-destructive-foreground hover:bg-destructive/90" + > + {isDeletePending && ( + <Loader + className="mr-2 size-4 animate-spin" + aria-hidden="true" + /> + )} + <Trash2 className="mr-2 h-4 w-4" /> + Delete Permanently + </AlertDialogAction> + </AlertDialogFooter> + </AlertDialogContent> + </AlertDialog> + ) +}
\ No newline at end of file |
