From 4ee8b24cfadf47452807fa2af801385ed60ab47c Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 15 Sep 2025 14:41:01 +0000 Subject: (대표님) 작업사항 - rfqLast, tbeLast, pdfTron, userAuth MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/rfq-last/table/rfq-table-toolbar-actions.tsx | 222 +++++++++++++++++++---- 1 file changed, 184 insertions(+), 38 deletions(-) (limited to 'lib/rfq-last/table/rfq-table-toolbar-actions.tsx') diff --git a/lib/rfq-last/table/rfq-table-toolbar-actions.tsx b/lib/rfq-last/table/rfq-table-toolbar-actions.tsx index 9b696cbd..91b2798f 100644 --- a/lib/rfq-last/table/rfq-table-toolbar-actions.tsx +++ b/lib/rfq-last/table/rfq-table-toolbar-actions.tsx @@ -2,7 +2,7 @@ import * as React from "react"; import { type Table } from "@tanstack/react-table"; -import { Download, RefreshCw, Plus } from "lucide-react"; +import { Download, RefreshCw, Plus, Lock, LockOpen } from "lucide-react"; import { Button } from "@/components/ui/button"; import { @@ -12,8 +12,20 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import { toast } from "sonner"; import { RfqsLastView } from "@/db/schema"; import { CreateGeneralRfqDialog } from "./create-general-rfq-dialog"; +import { sealMultipleRfqs, unsealMultipleRfqs } from "../service"; interface RfqTableToolbarActionsProps { table: Table; @@ -27,6 +39,43 @@ export function RfqTableToolbarActions({ rfqCategory = "itb", }: RfqTableToolbarActionsProps) { const [isExporting, setIsExporting] = React.useState(false); + const [isSealing, setIsSealing] = React.useState(false); + const [sealDialogOpen, setSealDialogOpen] = React.useState(false); + const [sealAction, setSealAction] = React.useState<"seal" | "unseal">("seal"); + + const selectedRows = table.getFilteredSelectedRowModel().rows; + const selectedRfqIds = selectedRows.map(row => row.original.id); + + // 선택된 항목들의 밀봉 상태 확인 + const sealedCount = selectedRows.filter(row => row.original.rfqSealedYn).length; + const unsealedCount = selectedRows.filter(row => !row.original.rfqSealedYn).length; + + const handleSealAction = React.useCallback(async (action: "seal" | "unseal") => { + setSealAction(action); + setSealDialogOpen(true); + }, []); + + const confirmSealAction = React.useCallback(async () => { + setIsSealing(true); + try { + const result = sealAction === "seal" + ? await sealMultipleRfqs(selectedRfqIds) + : await unsealMultipleRfqs(selectedRfqIds); + + if (result.success) { + toast.success(result.message); + table.toggleAllRowsSelected(false); // 선택 해제 + onRefresh?.(); // 데이터 새로고침 + } else { + toast.error(result.error); + } + } catch (error) { + toast.error("작업 중 오류가 발생했습니다."); + } finally { + setIsSealing(false); + setSealDialogOpen(false); + } + }, [sealAction, selectedRfqIds, table, onRefresh]); const handleExportCSV = React.useCallback(async () => { setIsExporting(true); @@ -36,6 +85,7 @@ export function RfqTableToolbarActions({ return { "RFQ 코드": original.rfqCode || "", "상태": original.status || "", + "밀봉여부": original.rfqSealedYn ? "밀봉" : "미밀봉", "프로젝트 코드": original.projectCode || "", "프로젝트명": original.projectName || "", "자재코드": original.itemCode || "", @@ -89,6 +139,7 @@ export function RfqTableToolbarActions({ return { "RFQ 코드": original.rfqCode || "", "상태": original.status || "", + "밀봉여부": original.rfqSealedYn ? "밀봉" : "미밀봉", "프로젝트 코드": original.projectCode || "", "프로젝트명": original.projectName || "", "자재코드": original.itemCode || "", @@ -115,48 +166,143 @@ export function RfqTableToolbarActions({ }, [table]); return ( -
- {onRefresh && ( - - )} - - - + <> +
+ {onRefresh && ( - - - - 전체 데이터 내보내기 - - - 선택한 항목 내보내기 ({table.getFilteredSelectedRowModel().rows.length}개) - - - - - {/* rfqCategory가 'general'일 때만 일반견적 생성 다이얼로그 표시 */} - {rfqCategory === "general" && ( - - ) } -
+ )} + + {/* 견적 밀봉/해제 버튼 */} + {selectedRfqIds.length > 0 && ( + + + + + + handleSealAction("seal")} + disabled={unsealedCount === 0} + > + + 선택 항목 밀봉 ({unsealedCount}개) + + handleSealAction("unseal")} + disabled={sealedCount === 0} + > + + 선택 항목 밀봉 해제 ({sealedCount}개) + + +
+ 전체 {selectedRfqIds.length}개 선택됨 +
+
+
+ )} + + + + + + + + 전체 데이터 내보내기 + + + 선택한 항목 내보내기 ({table.getFilteredSelectedRowModel().rows.length}개) + + + + + {/* rfqCategory가 'general'일 때만 일반견적 생성 다이얼로그 표시 */} + {rfqCategory === "general" && ( + + )} +
+ + {/* 밀봉 확인 다이얼로그 */} + + + + + {sealAction === "seal" ? "견적 밀봉 확인" : "견적 밀봉 해제 확인"} + + + {sealAction === "seal" + ? `선택한 ${unsealedCount}개의 견적을 밀봉하시겠습니까? 밀봉된 견적은 업체에서 수정할 수 없습니다.` + : `선택한 ${sealedCount}개의 견적 밀봉을 해제하시겠습니까? 밀봉이 해제되면 업체에서 견적을 수정할 수 있습니다.`} + + + + 취소 + + {isSealing ? "처리 중..." : "확인"} + + + + + ); +} + +// CSV 내보내기 유틸리티 함수 +function exportTableToCSV({ data, filename }: { data: any[]; filename: string }) { + if (!data || data.length === 0) { + console.warn("No data to export"); + return; + } + + const headers = Object.keys(data[0]); + const csvContent = [ + headers.join(","), + ...data.map(row => + headers.map(header => { + const value = row[header]; + // 값에 쉼표, 줄바꿈, 따옴표가 있으면 따옴표로 감싸기 + if (typeof value === "string" && (value.includes(",") || value.includes("\n") || value.includes('"'))) { + return `"${value.replace(/"/g, '""')}"`; + } + return value; + }).join(",") + ) + ].join("\n"); + + const blob = new Blob(["\uFEFF" + csvContent], { type: "text/csv;charset=utf-8;" }); + const link = document.createElement("a"); + link.href = URL.createObjectURL(blob); + link.download = filename; + link.click(); + URL.revokeObjectURL(link.href); } \ No newline at end of file -- cgit v1.2.3