From 08b73d56c2d887931cecdf2b0af6b277381763e6 Mon Sep 17 00:00:00 2001
From: joonhoekim <26rote@gmail.com>
Date: Thu, 6 Nov 2025 17:44:59 +0900
Subject: (김준회) 결재 프리뷰 공통컴포넌트 작성 및 index.ts --> client.ts 분리
 (서버사이드 코드가 번들링되어 클라측에서 실행되는 문제 해결 목적)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/vendors/table/approve-vendor-dialog.tsx | 185 ++++++++++++++++++++++------
 1 file changed, 150 insertions(+), 35 deletions(-)

(limited to 'lib/vendors/table/approve-vendor-dialog.tsx')

diff --git a/lib/vendors/table/approve-vendor-dialog.tsx b/lib/vendors/table/approve-vendor-dialog.tsx
index 786399a4..fea5a006 100644
--- a/lib/vendors/table/approve-vendor-dialog.tsx
+++ b/lib/vendors/table/approve-vendor-dialog.tsx
@@ -29,8 +29,10 @@ import {
 } from "@/components/ui/drawer"
 import { Vendor } from "@/db/schema/vendors"
 import { rejectVendors } from "../service"
-import { approveVendorsWithApproval } from "../approval-actions"
+import { approveVendorsWithApproval, prepareVendorApprovalVariables } from "../approval-actions"
 import { useSession } from "next-auth/react"
+import { checkVendorsBlacklist } from "../blacklist-check"
+import { ApprovalPreviewDialog } from "@/lib/approval/client"
 
 interface VendorDecisionDialogProps
   extends React.ComponentPropsWithoutRef<typeof Dialog> {
@@ -50,6 +52,17 @@ export function VendorDecisionDialog({
   const isDesktop = useMediaQuery("(min-width: 640px)")
   const { data: session } = useSession()
 
+  // 결재 미리보기 다이얼로그 상태
+  const [showPreview, setShowPreview] = React.useState(false)
+  const [previewData, setPreviewData] = React.useState<{
+    variables: Record<string, string>
+    title: string
+    description: string
+  } | null>(null)
+
+  /**
+   * 승인 버튼 클릭 - 미리보기 다이얼로그 열기
+   */
   function onApprove() {
     if (!session?.user?.id) {
       toast.error("사용자 인증 정보를 찾을 수 없습니다.")
@@ -63,37 +76,97 @@ export function VendorDecisionDialog({
 
     startApproveTransition(async () => {
       try {
-        console.log("🔍 [DEBUG] 결재 상신 시작 - vendors:", vendors.map(v => ({ id: v.id, vendorName: v.vendorName, email: v.email })));
-        console.log("🔍 [DEBUG] 세션 정보:", { userId: session.user.id, epId: session.user.epId });
+        // 1. 블랙리스트 검사 (최우선 처리)
+        const vendorsToCheck = vendors.map(v => ({
+          id: String(v.id),
+          name: v.vendorName || '',
+          representativeName: v.representativeName,
+          representativeBirth: v.representativeBirth,
+        }));
+        
+        const blacklistCheckResult = await checkVendorsBlacklist(vendorsToCheck);
 
-        const result = await approveVendorsWithApproval({
-          vendorIds: vendors.map((vendor) => vendor.id),
-          currentUser: {
-            id: Number(session.user.id),
-            epId: session.user.epId as string, // 위에서 검증했으므로 타입 단언
-            email: session.user.email || undefined,
-          },
-          // TODO: 필요시 approvers 배열 추가
-          // approvers: ['EP001', 'EP002'],
-        })
-
-        if (!result.success) {
-          console.error("🚨 [DEBUG] 결재 상신 에러:", result.message);
-          toast.error(result.message || "결재 상신에 실패했습니다.")
-          return
+        if (!blacklistCheckResult.success) {
+          // 블랙리스트에 있는 벤더 목록 표시
+          const blacklistedNames = blacklistCheckResult.blacklistedVendors
+            .map(v => `• ${v.name}: ${v.message}`)
+            .join('\n');
+          
+          toast.error(
+            `문제가 있는 데이터가 있습니다:\n${blacklistedNames}`,
+            { duration: 10000 }
+          );
+          return;
         }
 
-        console.log("✅ [DEBUG] 결재 상신 성공:", result);
-        props.onOpenChange?.(false)
-        toast.success(`결재가 상신되었습니다. (결재ID: ${result.approvalId})`)
-        onSuccess?.()
+        // 2. 템플릿 변수 준비
+        const { variables, vendorRecords, vendorNames } = await prepareVendorApprovalVariables(
+          vendors.map(v => v.id),
+          session.user.email || undefined
+        );
+
+        // 3. 미리보기 데이터 설정
+        setPreviewData({
+          variables,
+          title: `벤더 가입 승인 요청 - ${vendorRecords.length}개 업체`,
+          description: `${vendorNames} 의 가입을 승인합니다.`,
+        });
+
+        // 4. 미리보기 다이얼로그 열기
+        setShowPreview(true);
+
       } catch (error) {
-        console.error("🚨 [DEBUG] 예상치 못한 에러:", error);
-        toast.error("예상치 못한 오류가 발생했습니다.")
+        console.error("🚨 [Vendor Decision] 미리보기 준비 실패:", error);
+        toast.error(error instanceof Error ? error.message : "미리보기를 준비하는 중 오류가 발생했습니다.")
       }
     })
   }
 
+  /**
+   * 결재 미리보기에서 확인 클릭 - 실제 결재 상신
+   */
+  async function handleApprovalConfirm(approvalData: {
+    approvers: string[]
+    title: string
+    description?: string
+  }) {
+    if (!session?.user?.id || !session?.user?.epId) {
+      toast.error("사용자 인증 정보가 없습니다.")
+      return
+    }
+
+    try {
+      const result = await approveVendorsWithApproval({
+        vendorIds: vendors.map((vendor) => vendor.id),
+        currentUser: {
+          id: Number(session.user.id),
+          epId: session.user.epId,
+          email: session.user.email || undefined,
+        },
+        approvers: approvalData.approvers, // 미리보기에서 설정한 결재선
+      })
+
+      if (!result.success) {
+        console.error("🚨 [Vendor Decision] 결재 상신 에러:", result.message);
+        toast.error(result.message || "결재 상신에 실패했습니다.")
+        return
+      }
+
+      console.log("✅ [Vendor Decision] 결재 상신 성공:", result);
+      
+      // 다이얼로그 모두 닫기
+      setShowPreview(false)
+      props.onOpenChange?.(false)
+      
+      toast.success(`결재가 상신되었습니다. (결재ID: ${result.approvalId})`)
+      onSuccess?.()
+      
+    } catch (error) {
+      console.error("🚨 [Vendor Decision] 예상치 못한 에러:", error);
+      toast.error("예상치 못한 오류가 발생했습니다.")
+    }
+  }
+
   function onReject() {
     if (!session?.user?.id) {
       toast.error("사용자 인증 정보를 찾을 수 없습니다.")
@@ -129,16 +202,17 @@ export function VendorDecisionDialog({
 
   if (isDesktop) {
     return (
-      <Dialog {...props}>
-        {showTrigger ? (
-          <DialogTrigger asChild>
-            <Button variant="outline" size="sm" className="gap-2">
-              <Check className="size-4" aria-hidden="true" />
-              가입 결정 ({vendors.length})
-            </Button>
-          </DialogTrigger>
-        ) : null}
-        <DialogContent className="max-w-2xl">
+      <>
+        <Dialog {...props}>
+          {showTrigger ? (
+            <DialogTrigger asChild>
+              <Button variant="outline" size="sm" className="gap-2">
+                <Check className="size-4" aria-hidden="true" />
+                가입 결정 ({vendors.length})
+              </Button>
+            </DialogTrigger>
+          ) : null}
+          <DialogContent className="max-w-2xl">
           <DialogHeader>
             <DialogTitle>협력업체 가입 결정</DialogTitle>
             <DialogDescription>
@@ -199,11 +273,32 @@ export function VendorDecisionDialog({
           </DialogFooter>
         </DialogContent>
       </Dialog>
+
+      {/* 결재 미리보기 다이얼로그 */}
+      {previewData && session?.user?.epId && (
+        <ApprovalPreviewDialog
+          open={showPreview}
+          onOpenChange={setShowPreview}
+          templateName="벤더 가입 승인 요청"
+          variables={previewData.variables}
+          title={previewData.title}
+          description={previewData.description}
+          currentUser={{
+            id: Number(session.user.id),
+            epId: session.user.epId,
+            name: session.user.name || undefined,
+            email: session.user.email || undefined,
+          }}
+          onConfirm={handleApprovalConfirm}
+        />
+      )}
+    </>
     )
   }
 
   return (
-    <Drawer {...props}>
+    <>
+      <Drawer {...props}>
       {showTrigger ? (
         <DrawerTrigger asChild>
           <Button variant="outline" size="sm" className="gap-2">
@@ -272,5 +367,25 @@ export function VendorDecisionDialog({
         </DrawerFooter>
       </DrawerContent>
     </Drawer>
+
+    {/* 결재 미리보기 다이얼로그 */}
+    {previewData && session?.user?.epId && (
+      <ApprovalPreviewDialog
+        open={showPreview}
+        onOpenChange={setShowPreview}
+        templateName="벤더 가입 승인 요청"
+        variables={previewData.variables}
+        title={previewData.title}
+        description={previewData.description}
+        currentUser={{
+          id: Number(session.user.id),
+          epId: session.user.epId,
+          name: session.user.name || undefined,
+          email: session.user.email || undefined,
+        }}
+        onConfirm={handleApprovalConfirm}
+      />
+    )}
+  </>
   )
 }
\ No newline at end of file
-- 
cgit v1.2.3

