From 4ee8b24cfadf47452807fa2af801385ed60ab47c Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
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<RfqsLastView>;
@@ -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 (
-    <div className="flex items-center gap-2">
-      {onRefresh && (
-        <Button
-          variant="outline"
-          size="sm"
-          onClick={onRefresh}
-          className="h-8 px-2 lg:px-3"
-        >
-          <RefreshCw className="mr-2 h-4 w-4" />
-          새로고침
-        </Button>
-      )}
-
-      <DropdownMenu>
-        <DropdownMenuTrigger asChild>
+    <>
+      <div className="flex items-center gap-2">
+        {onRefresh && (
           <Button
             variant="outline"
             size="sm"
+            onClick={onRefresh}
             className="h-8 px-2 lg:px-3"
-            disabled={isExporting}
           >
-            <Download className="mr-2 h-4 w-4" />
-            {isExporting ? "내보내는 중..." : "내보내기"}
+            <RefreshCw className="mr-2 h-4 w-4" />
+            새로고침
           </Button>
-        </DropdownMenuTrigger>
-        <DropdownMenuContent align="end">
-          <DropdownMenuItem onClick={handleExportCSV}>
-            전체 데이터 내보내기
-          </DropdownMenuItem>
-          <DropdownMenuItem 
-            onClick={handleExportSelected}
-            disabled={table.getFilteredSelectedRowModel().rows.length === 0}
-          >
-            선택한 항목 내보내기 ({table.getFilteredSelectedRowModel().rows.length}개)
-          </DropdownMenuItem>
-        </DropdownMenuContent>
-      </DropdownMenu>
-
-      {/* rfqCategory가 'general'일 때만 일반견적 생성 다이얼로그 표시 */}
-      {rfqCategory === "general" && (
-        <CreateGeneralRfqDialog onSuccess={onRefresh} />
-      ) }
-    </div>
+        )}
+
+        {/* 견적 밀봉/해제 버튼 */}
+        {selectedRfqIds.length > 0 && (
+          <DropdownMenu>
+            <DropdownMenuTrigger asChild>
+              <Button
+                variant="outline"
+                size="sm"
+                className="h-8 px-2 lg:px-3"
+                disabled={isSealing}
+              >
+                <Lock className="mr-2 h-4 w-4" />
+                견적 밀봉
+              </Button>
+            </DropdownMenuTrigger>
+            <DropdownMenuContent align="end">
+              <DropdownMenuItem 
+                onClick={() => handleSealAction("seal")}
+                disabled={unsealedCount === 0}
+              >
+                <Lock className="mr-2 h-4 w-4" />
+                선택 항목 밀봉 ({unsealedCount}개)
+              </DropdownMenuItem>
+              <DropdownMenuItem 
+                onClick={() => handleSealAction("unseal")}
+                disabled={sealedCount === 0}
+              >
+                <LockOpen className="mr-2 h-4 w-4" />
+                선택 항목 밀봉 해제 ({sealedCount}개)
+              </DropdownMenuItem>
+              <DropdownMenuSeparator />
+              <div className="px-2 py-1.5 text-xs text-muted-foreground">
+                전체 {selectedRfqIds.length}개 선택됨
+              </div>
+            </DropdownMenuContent>
+          </DropdownMenu>
+        )}
+
+        <DropdownMenu>
+          <DropdownMenuTrigger asChild>
+            <Button
+              variant="outline"
+              size="sm"
+              className="h-8 px-2 lg:px-3"
+              disabled={isExporting}
+            >
+              <Download className="mr-2 h-4 w-4" />
+              {isExporting ? "내보내는 중..." : "내보내기"}
+            </Button>
+          </DropdownMenuTrigger>
+          <DropdownMenuContent align="end">
+            <DropdownMenuItem onClick={handleExportCSV}>
+              전체 데이터 내보내기
+            </DropdownMenuItem>
+            <DropdownMenuItem 
+              onClick={handleExportSelected}
+              disabled={table.getFilteredSelectedRowModel().rows.length === 0}
+            >
+              선택한 항목 내보내기 ({table.getFilteredSelectedRowModel().rows.length}개)
+            </DropdownMenuItem>
+          </DropdownMenuContent>
+        </DropdownMenu>
+
+        {/* rfqCategory가 'general'일 때만 일반견적 생성 다이얼로그 표시 */}
+        {rfqCategory === "general" && (
+          <CreateGeneralRfqDialog onSuccess={onRefresh} />
+        )}
+      </div>
+
+      {/* 밀봉 확인 다이얼로그 */}
+      <AlertDialog open={sealDialogOpen} onOpenChange={setSealDialogOpen}>
+        <AlertDialogContent>
+          <AlertDialogHeader>
+            <AlertDialogTitle>
+              {sealAction === "seal" ? "견적 밀봉 확인" : "견적 밀봉 해제 확인"}
+            </AlertDialogTitle>
+            <AlertDialogDescription>
+              {sealAction === "seal" 
+                ? `선택한 ${unsealedCount}개의 견적을 밀봉하시겠습니까? 밀봉된 견적은 업체에서 수정할 수 없습니다.`
+                : `선택한 ${sealedCount}개의 견적 밀봉을 해제하시겠습니까? 밀봉이 해제되면 업체에서 견적을 수정할 수 있습니다.`}
+            </AlertDialogDescription>
+          </AlertDialogHeader>
+          <AlertDialogFooter>
+            <AlertDialogCancel disabled={isSealing}>취소</AlertDialogCancel>
+            <AlertDialogAction 
+              onClick={confirmSealAction} 
+              disabled={isSealing}
+              className={sealAction === "seal" ? "bg-red-600 hover:bg-red-700" : ""}
+            >
+              {isSealing ? "처리 중..." : "확인"}
+            </AlertDialogAction>
+          </AlertDialogFooter>
+        </AlertDialogContent>
+      </AlertDialog>
+    </>
   );
+}
+
+// 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

