summaryrefslogtreecommitdiff
path: root/lib/rfq-last/table
diff options
context:
space:
mode:
Diffstat (limited to 'lib/rfq-last/table')
-rw-r--r--lib/rfq-last/table/delete-rfq-dialog.tsx254
-rw-r--r--lib/rfq-last/table/rfq-table-columns.tsx1
-rw-r--r--lib/rfq-last/table/rfq-table-toolbar-actions.tsx56
3 files changed, 310 insertions, 1 deletions
diff --git a/lib/rfq-last/table/delete-rfq-dialog.tsx b/lib/rfq-last/table/delete-rfq-dialog.tsx
new file mode 100644
index 00000000..01af5453
--- /dev/null
+++ b/lib/rfq-last/table/delete-rfq-dialog.tsx
@@ -0,0 +1,254 @@
+"use client";
+
+import * as React from "react";
+import {
+ AlertDialog,
+ AlertDialogAction,
+ AlertDialogCancel,
+ AlertDialogContent,
+ AlertDialogDescription,
+ AlertDialogFooter,
+ AlertDialogHeader,
+ AlertDialogTitle,
+} from "@/components/ui/alert-dialog";
+import { RfqsLastView } from "@/db/schema";
+import { deleteRfq } from "@/lib/rfq-last/delete-action";
+import { Loader2, AlertTriangle } from "lucide-react";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { toast } from "sonner";
+import { Textarea } from "@/components/ui/textarea";
+import { Label } from "@/components/ui/label";
+
+interface DeleteRfqDialogProps {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+ selectedRfqs: RfqsLastView[];
+ onSuccess?: () => void;
+}
+
+export function DeleteRfqDialog({
+ open,
+ onOpenChange,
+ selectedRfqs,
+ onSuccess,
+}: DeleteRfqDialogProps) {
+ const [isDeleting, setIsDeleting] = React.useState(false);
+ const [deleteReason, setDeleteReason] = React.useState("");
+
+ // ANFNR이 있는 RFQ만 필터링
+ const rfqsWithAnfnr = React.useMemo(() => {
+ return selectedRfqs.filter(rfq => rfq.ANFNR && rfq.ANFNR.trim() !== "");
+ }, [selectedRfqs]);
+
+ const handleDelete = async () => {
+ if (rfqsWithAnfnr.length === 0) {
+ toast.error("ANFNR이 있는 RFQ가 선택되지 않았습니다.");
+ return;
+ }
+
+ if (!deleteReason || deleteReason.trim() === "") {
+ toast.error("삭제 사유를 입력해주세요.");
+ return;
+ }
+
+ setIsDeleting(true);
+
+ try {
+ const rfqIds = rfqsWithAnfnr.map(rfq => rfq.id);
+ const result = await deleteRfq(rfqIds, deleteReason.trim());
+
+ if (result.results) {
+ const successCount = result.results.filter(r => r.success).length;
+ const failCount = result.results.length - successCount;
+
+ if (result.success) {
+ // 성공한 RFQ 목록
+ const successRfqs = result.results
+ .filter(r => r.success)
+ .map(r => {
+ const rfq = rfqsWithAnfnr.find(rf => rf.id === r.rfqId);
+ return rfq?.rfqCode || `RFQ ID: ${r.rfqId}`;
+ });
+
+ if (successCount > 0) {
+ toast.success(
+ `RFQ 삭제가 완료되었습니다. (${successCount}건)`,
+ {
+ description: successRfqs.length <= 3
+ ? successRfqs.join(", ")
+ : `${successRfqs.slice(0, 3).join(", ")} 외 ${successRfqs.length - 3}건`,
+ duration: 5000,
+ }
+ );
+ }
+
+ // 실패한 RFQ가 있는 경우
+ if (failCount > 0) {
+ const failRfqs = result.results
+ .filter(r => !r.success)
+ .map(r => {
+ const rfq = rfqsWithAnfnr.find(rf => rf.id === r.rfqId);
+ return `${rfq?.rfqCode || r.rfqId}: ${r.error || "알 수 없는 오류"}`;
+ });
+
+ toast.error(
+ `${failCount}건의 RFQ 삭제가 실패했습니다.`,
+ {
+ description: failRfqs.length <= 3
+ ? failRfqs.join(", ")
+ : `${failRfqs.slice(0, 3).join(", ")} 외 ${failRfqs.length - 3}건`,
+ duration: 7000,
+ }
+ );
+ }
+ } else {
+ // 전체 실패
+ toast.error(result.message || "RFQ 삭제에 실패했습니다.");
+ }
+ } else {
+ if (result.success) {
+ toast.success(result.message);
+ } else {
+ toast.error(result.message);
+ }
+ }
+
+ // 성공 여부와 관계없이 다이얼로그 닫기 및 콜백 호출
+ if (result.success) {
+ setDeleteReason(""); // 성공 시 입력 필드 초기화
+ onOpenChange(false);
+ onSuccess?.();
+ }
+ } catch (err) {
+ toast.error(err instanceof Error ? err.message : "RFQ 삭제 중 오류가 발생했습니다.");
+ } finally {
+ setIsDeleting(false);
+ }
+ };
+
+ const handleClose = () => {
+ if (!isDeleting) {
+ setDeleteReason(""); // 다이얼로그 닫을 때 입력 필드 초기화
+ onOpenChange(false);
+ }
+ };
+
+ // ANFNR이 없는 RFQ가 포함된 경우 경고 표시
+ const rfqsWithoutAnfnr = selectedRfqs.filter(rfq => !rfq.ANFNR || rfq.ANFNR.trim() === "");
+ const hasWarning = rfqsWithoutAnfnr.length > 0;
+
+ return (
+ <AlertDialog open={open} onOpenChange={handleClose}>
+ <AlertDialogContent className="max-w-2xl">
+ <AlertDialogHeader>
+ <AlertDialogTitle>RFQ 삭제</AlertDialogTitle>
+ <AlertDialogDescription className="space-y-4">
+ {isDeleting ? (
+ /* 로딩 중 상태 - 다른 내용 숨김 */
+ <div className="flex items-center justify-center gap-3 py-8">
+ <Loader2 className="h-6 w-6 animate-spin text-primary" />
+ <div className="flex flex-col">
+ <span className="text-base font-medium">RFQ 삭제 처리 중...</span>
+ <span className="text-sm text-muted-foreground mt-1">
+ ECC로 취소 요청을 전송하고 있습니다.
+ </span>
+ </div>
+ </div>
+ ) : (
+ <>
+ <div>
+ 선택된 RFQ 중 ANFNR이 있는 RFQ만 삭제됩니다.
+ </div>
+
+ {/* 삭제 대상 RFQ 목록 */}
+ {rfqsWithAnfnr.length > 0 && (
+ <div className="space-y-2">
+ <p className="font-medium text-sm">삭제 대상 RFQ ({rfqsWithAnfnr.length}건):</p>
+ <div className="max-h-40 overflow-y-auto border rounded-md p-3 space-y-1">
+ {rfqsWithAnfnr.map((rfq) => (
+ <div key={rfq.id} className="text-sm">
+ <span className="font-mono font-medium">{rfq.rfqCode}</span>
+ {rfq.rfqTitle && (
+ <span className="text-muted-foreground ml-2">
+ - {rfq.rfqTitle}
+ </span>
+ )}
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
+
+ {/* ANFNR이 없는 RFQ 경고 */}
+ {hasWarning && (
+ <Alert variant="destructive">
+ <AlertTriangle className="h-4 w-4" />
+ <AlertDescription>
+ <div className="space-y-2">
+ <p className="font-medium">ANFNR이 없는 RFQ는 삭제할 수 없습니다 ({rfqsWithoutAnfnr.length}건):</p>
+ <div className="max-h-32 overflow-y-auto space-y-1">
+ {rfqsWithoutAnfnr.map((rfq) => (
+ <div key={rfq.id} className="text-sm">
+ <span className="font-mono">{rfq.rfqCode}</span>
+ {rfq.rfqTitle && (
+ <span className="text-muted-foreground ml-2">
+ - {rfq.rfqTitle}
+ </span>
+ )}
+ </div>
+ ))}
+ </div>
+ </div>
+ </AlertDescription>
+ </Alert>
+ )}
+
+ {/* 삭제 사유 입력 */}
+ <div className="space-y-2">
+ <Label htmlFor="delete-reason" className="text-sm font-medium">
+ 삭제 사유 <span className="text-destructive">*</span>
+ </Label>
+ <Textarea
+ id="delete-reason"
+ placeholder="RFQ 삭제 사유를 입력해주세요..."
+ value={deleteReason}
+ onChange={(e) => setDeleteReason(e.target.value)}
+ disabled={isDeleting}
+ className="min-h-[100px] resize-none"
+ required
+ />
+ </div>
+
+ {/* 안내 메시지 */}
+ <div className="text-sm text-muted-foreground space-y-1">
+ <p>• ANFNR이 있는 RFQ만 삭제됩니다.</p>
+ <p>• ECC로 SOAP 취소 요청이 전송됩니다.</p>
+ <p>• 성공 시 RFQ 상태가 RFQ 삭제로 변경됩니다.</p>
+ <p>• 연결된 TBE 세션도 취소 상태로 변경됩니다.</p>
+ </div>
+ </>
+ )}
+ </AlertDialogDescription>
+ </AlertDialogHeader>
+ <AlertDialogFooter>
+ <AlertDialogCancel disabled={isDeleting}>취소</AlertDialogCancel>
+ <AlertDialogAction
+ onClick={handleDelete}
+ disabled={isDeleting || rfqsWithAnfnr.length === 0 || !deleteReason || deleteReason.trim() === ""}
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
+ >
+ {isDeleting ? (
+ <>
+ <Loader2 className="mr-2 h-4 w-4 animate-spin" />
+ 삭제 중...
+ </>
+ ) : (
+ "RFQ 삭제"
+ )}
+ </AlertDialogAction>
+ </AlertDialogFooter>
+ </AlertDialogContent>
+ </AlertDialog>
+ );
+}
+
diff --git a/lib/rfq-last/table/rfq-table-columns.tsx b/lib/rfq-last/table/rfq-table-columns.tsx
index d0a9ee1e..e8a5ba94 100644
--- a/lib/rfq-last/table/rfq-table-columns.tsx
+++ b/lib/rfq-last/table/rfq-table-columns.tsx
@@ -39,6 +39,7 @@ const getStatusBadgeVariant = (status: string) => {
case "RFQ 발송": return "default";
case "견적접수": return "default";
case "최종업체선정": return "default";
+ case "RFQ 삭제": return "destructive";
default: return "outline";
}
};
diff --git a/lib/rfq-last/table/rfq-table-toolbar-actions.tsx b/lib/rfq-last/table/rfq-table-toolbar-actions.tsx
index 148336fb..a6dc1ad4 100644
--- a/lib/rfq-last/table/rfq-table-toolbar-actions.tsx
+++ b/lib/rfq-last/table/rfq-table-toolbar-actions.tsx
@@ -3,11 +3,12 @@
import * as React from "react";
import { Table } from "@tanstack/react-table";
import { Button } from "@/components/ui/button";
-import { Users, RefreshCw, FileDown, Plus, Edit } from "lucide-react";
+import { Users, RefreshCw, FileDown, Plus, Edit, Trash2 } from "lucide-react";
import { RfqsLastView } from "@/db/schema";
import { RfqAssignPicDialog } from "./rfq-assign-pic-dialog";
import { CreateGeneralRfqDialog } from "./create-general-rfq-dialog"; // 추가
import { UpdateGeneralRfqDialog } from "./update-general-rfq-dialog"; // 수정용
+import { DeleteRfqDialog } from "./delete-rfq-dialog";
import { Badge } from "@/components/ui/badge";
import {
Tooltip,
@@ -29,6 +30,7 @@ export function RfqTableToolbarActions<TData>({
}: RfqTableToolbarActionsProps<TData>) {
const [showAssignDialog, setShowAssignDialog] = React.useState(false);
const [showUpdateDialog, setShowUpdateDialog] = React.useState(false);
+ const [showDeleteDialog, setShowDeleteDialog] = React.useState(false);
const [selectedRfqForUpdate, setSelectedRfqForUpdate] = React.useState<number | null>(null);
console.log(rfqCategory)
@@ -47,6 +49,9 @@ export function RfqTableToolbarActions<TData>({
// 수정 가능한 RFQ (general 카테고리에서 RFQ 생성 상태인 항목, 단일 선택만)
const updatableRfq = rfqCategory === "general" && rows.length === 1 && rows[0].status === "RFQ 생성" ? rows[0] : null;
+ // ANFNR이 있는 RFQ만 필터링 (삭제 가능한 RFQ)
+ const deletableRows = rows.filter(row => row.ANFNR && row.ANFNR.trim() !== "");
+
return {
ids: rows.map(row => row.id),
codes: rows.map(row => row.rfqCode || ""),
@@ -61,6 +66,10 @@ export function RfqTableToolbarActions<TData>({
// 수정 가능한 RFQ 정보
updatableRfq: updatableRfq,
canUpdate: updatableRfq !== null,
+ // 삭제 가능한 RFQ 정보
+ deletableRows: deletableRows,
+ deletableCount: deletableRows.length,
+ canDelete: deletableRows.length > 0,
};
}, [selectedRows, rfqCategory]);
@@ -92,6 +101,13 @@ export function RfqTableToolbarActions<TData>({
}
};
+ const handleDeleteSuccess = () => {
+ // 테이블 선택 초기화
+ table.toggleAllPageRowsSelected(false);
+ // 데이터 새로고침
+ onRefresh?.();
+ };
+
return (
<>
<div className="flex items-center gap-2">
@@ -125,6 +141,36 @@ export function RfqTableToolbarActions<TData>({
</TooltipProvider>
)}
+ {/* RFQ 삭제 버튼 - ANFNR이 있는 RFQ가 선택된 경우에만 활성화 */}
+ {selectedRfqData.totalCount > 0 && selectedRfqData.canDelete && (
+ <TooltipProvider>
+ <Tooltip>
+ <TooltipTrigger asChild>
+ <Button
+ variant="destructive"
+ size="sm"
+ onClick={() => setShowDeleteDialog(true)}
+ className="flex items-center gap-2"
+ >
+ <Trash2 className="h-4 w-4" />
+ RFQ 삭제
+ <Badge variant="secondary" className="ml-1">
+ {selectedRfqData.deletableCount}건
+ </Badge>
+ </Button>
+ </TooltipTrigger>
+ <TooltipContent>
+ <p>선택한 RFQ를 삭제합니다 (ANFNR이 있는 RFQ만 삭제 가능)</p>
+ {selectedRfqData.deletableCount !== selectedRfqData.totalCount && (
+ <p className="text-xs text-muted-foreground mt-1">
+ 전체 {selectedRfqData.totalCount}건 중 {selectedRfqData.deletableCount}건만 삭제 가능합니다
+ </p>
+ )}
+ </TooltipContent>
+ </Tooltip>
+ </TooltipProvider>
+ )}
+
{/* 선택된 항목 표시 */}
{selectedRfqData.totalCount > 0 && (
<div className="flex items-center gap-2 px-3 py-1.5 bg-muted rounded-md">
@@ -198,6 +244,14 @@ export function RfqTableToolbarActions<TData>({
rfqId={selectedRfqForUpdate || 0}
onSuccess={handleUpdateGeneralRfqSuccess}
/>
+
+ {/* RFQ 삭제 다이얼로그 */}
+ <DeleteRfqDialog
+ open={showDeleteDialog}
+ onOpenChange={setShowDeleteDialog}
+ selectedRfqs={selectedRfqData.deletableRows}
+ onSuccess={handleDeleteSuccess}
+ />
</>
);
} \ No newline at end of file