From 6c11fccc84f4c84fa72ee01f9caad9f76f35cea2 Mon Sep 17 00:00:00 2001
From: dujinkim <dujin.kim@dtsolution.co.kr>
Date: Tue, 16 Sep 2025 09:20:58 +0000
Subject: (대표님, 최겸) 계약, 업로드 관련, 메뉴처리, 입찰, 프리쿼트,
 rfqLast관련, tbeLast관련
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 lib/tbe-last/table/email-documents-dialog.tsx | 334 ++++++++++++++++++++++++++
 1 file changed, 334 insertions(+)
 create mode 100644 lib/tbe-last/table/email-documents-dialog.tsx

(limited to 'lib/tbe-last/table/email-documents-dialog.tsx')

diff --git a/lib/tbe-last/table/email-documents-dialog.tsx b/lib/tbe-last/table/email-documents-dialog.tsx
new file mode 100644
index 00000000..415cd428
--- /dev/null
+++ b/lib/tbe-last/table/email-documents-dialog.tsx
@@ -0,0 +1,334 @@
+// lib/tbe-last/table/dialogs/email-documents-dialog.tsx
+
+"use client"
+
+import * as React from "react"
+import {
+  Dialog,
+  DialogContent,
+  DialogDescription,
+  DialogFooter,
+  DialogHeader,
+  DialogTitle,
+} from "@/components/ui/dialog"
+import { Button } from "@/components/ui/button"
+import { Input } from "@/components/ui/input"
+import { Label } from "@/components/ui/label"
+import { Textarea } from "@/components/ui/textarea"
+import { Badge } from "@/components/ui/badge"
+import { ScrollArea } from "@/components/ui/scroll-area"
+import {
+  FileText,
+  X,
+  Plus,
+  Mail,
+  Loader2,
+  AlertCircle,
+} from "lucide-react"
+import { toast } from "sonner"
+import { sendDocumentsEmail } from "../service"
+
+interface EmailDocumentsDialogProps {
+  open: boolean
+  onOpenChange: (open: boolean) => void
+  selectedDocuments: any[]
+  sessionDetail: any
+  onSuccess?: () => void
+}
+
+export function EmailDocumentsDialog({
+  open,
+  onOpenChange,
+  selectedDocuments,
+  sessionDetail,
+  onSuccess
+}: EmailDocumentsDialogProps) {
+  const [recipients, setRecipients] = React.useState<string[]>([])
+  const [currentEmail, setCurrentEmail] = React.useState("")
+  const [ccRecipients, setCcRecipients] = React.useState<string[]>([])
+  const [currentCc, setCurrentCc] = React.useState("")
+  const [comments, setComments] = React.useState("")
+  const [isLoading, setIsLoading] = React.useState(false)
+
+  // 이메일 유효성 검사
+  const validateEmail = (email: string) => {
+    const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+    return re.test(email)
+  }
+
+  // 수신자 추가
+  const handleAddRecipient = () => {
+    if (currentEmail && validateEmail(currentEmail)) {
+      if (!recipients.includes(currentEmail)) {
+        setRecipients([...recipients, currentEmail])
+        setCurrentEmail("")
+      } else {
+        toast.error("이미 추가된 이메일입니다")
+      }
+    } else {
+      toast.error("올바른 이메일 주소를 입력하세요")
+    }
+  }
+
+  // CC 수신자 추가
+  const handleAddCc = () => {
+    if (currentCc && validateEmail(currentCc)) {
+      if (!ccRecipients.includes(currentCc)) {
+        setCcRecipients([...ccRecipients, currentCc])
+        setCurrentCc("")
+      } else {
+        toast.error("이미 추가된 이메일입니다")
+      }
+    } else {
+      toast.error("올바른 이메일 주소를 입력하세요")
+    }
+  }
+
+  // 수신자 제거
+  const removeRecipient = (email: string) => {
+    setRecipients(recipients.filter(r => r !== email))
+  }
+
+  // CC 수신자 제거
+  const removeCc = (email: string) => {
+    setCcRecipients(ccRecipients.filter(r => r !== email))
+  }
+
+  // 이메일 전송
+  const handleSendEmail = async () => {
+    if (recipients.length === 0) {
+      toast.error("최소 한 명의 수신자를 추가하세요")
+      return
+    }
+
+    if (selectedDocuments.length === 0) {
+      toast.error("선택된 문서가 없습니다")
+      return
+    }
+
+    setIsLoading(true)
+
+    try {
+      const result = await sendDocumentsEmail({
+        to: recipients,
+        cc: ccRecipients.length > 0 ? ccRecipients : undefined,
+        documents: selectedDocuments.map(doc => ({
+          documentId: doc.documentId,
+          documentReviewId: doc.documentReviewId,
+          documentName: doc.documentName,
+          filePath: doc.filePath,
+          documentType: doc.documentType,
+          documentSource: doc.documentSource,
+          reviewStatus: doc.reviewStatus,
+        })),
+        comments,
+        sessionInfo: {
+          sessionId: sessionDetail?.session?.tbeSessionId,
+          sessionTitle: sessionDetail?.session?.title,
+          buyerName: sessionDetail?.session?.buyerName,
+          vendorName: sessionDetail?.session?.vendorName,
+        }
+      })
+
+      if (result.success) {
+        toast.success("이메일이 성공적으로 전송되었습니다")
+        onSuccess?.()
+        onOpenChange(false)
+        
+        // 초기화
+        setRecipients([])
+        setCcRecipients([])
+        setComments("")
+        setCurrentEmail("")
+        setCurrentCc("")
+      } else {
+        throw new Error(result.error || "이메일 전송 실패")
+      }
+    } catch (error) {
+      console.error("Email send error:", error)
+      toast.error(error instanceof Error ? error.message : "이메일 전송 중 오류가 발생했습니다")
+    } finally {
+      setIsLoading(false)
+    }
+  }
+
+  // 파일 크기 포맷
+  const formatFileSize = (bytes: number) => {
+    if (bytes === 0) return '0 Bytes'
+    const k = 1024
+    const sizes = ['Bytes', 'KB', 'MB', 'GB']
+    const i = Math.floor(Math.log(bytes) / Math.log(k))
+    return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
+  }
+
+  return (
+    <Dialog open={open} onOpenChange={onOpenChange}>
+      <DialogContent className="max-w-2xl">
+        <DialogHeader>
+          <DialogTitle>Send Documents via Email</DialogTitle>
+          <DialogDescription>
+            선택한 {selectedDocuments.length}개의 문서를 이메일로 전송합니다
+          </DialogDescription>
+        </DialogHeader>
+
+        <div className="grid gap-4 py-4">
+          {/* 수신자 입력 */}
+          <div className="grid gap-2">
+            <Label htmlFor="recipients">수신자 (To) *</Label>
+            <div className="flex gap-2">
+              <Input
+                id="recipients"
+                type="email"
+                placeholder="이메일 주소 입력"
+                value={currentEmail}
+                onChange={(e) => setCurrentEmail(e.target.value)}
+                onKeyPress={(e) => {
+                  if (e.key === 'Enter') {
+                    e.preventDefault()
+                    handleAddRecipient()
+                  }
+                }}
+              />
+              <Button
+                type="button"
+                size="sm"
+                onClick={handleAddRecipient}
+                variant="outline"
+              >
+                <Plus className="h-4 w-4" />
+              </Button>
+            </div>
+            <div className="flex flex-wrap gap-2 mt-2">
+              {recipients.map((email) => (
+                <Badge key={email} variant="secondary" className="gap-1">
+                  {email}
+                  <X
+                    className="h-3 w-3 cursor-pointer hover:text-destructive"
+                    onClick={() => removeRecipient(email)}
+                  />
+                </Badge>
+              ))}
+            </div>
+          </div>
+
+          {/* CC 입력 */}
+          <div className="grid gap-2">
+            <Label htmlFor="cc">참조 (CC)</Label>
+            <div className="flex gap-2">
+              <Input
+                id="cc"
+                type="email"
+                placeholder="이메일 주소 입력 (선택사항)"
+                value={currentCc}
+                onChange={(e) => setCurrentCc(e.target.value)}
+                onKeyPress={(e) => {
+                  if (e.key === 'Enter') {
+                    e.preventDefault()
+                    handleAddCc()
+                  }
+                }}
+              />
+              <Button
+                type="button"
+                size="sm"
+                onClick={handleAddCc}
+                variant="outline"
+              >
+                <Plus className="h-4 w-4" />
+              </Button>
+            </div>
+            <div className="flex flex-wrap gap-2 mt-2">
+              {ccRecipients.map((email) => (
+                <Badge key={email} variant="secondary" className="gap-1">
+                  {email}
+                  <X
+                    className="h-3 w-3 cursor-pointer hover:text-destructive"
+                    onClick={() => removeCc(email)}
+                  />
+                </Badge>
+              ))}
+            </div>
+          </div>
+
+          {/* 코멘트 입력 */}
+          <div className="grid gap-2">
+            <Label htmlFor="comments">메시지</Label>
+            <Textarea
+              id="comments"
+              placeholder="추가 메시지를 입력하세요 (선택사항)"
+              value={comments}
+              onChange={(e) => setComments(e.target.value)}
+              rows={4}
+            />
+          </div>
+
+          {/* 첨부 파일 목록 */}
+          <div className="grid gap-2">
+            <Label>첨부 파일 ({selectedDocuments.length}개)</Label>
+            <ScrollArea className="h-[200px] w-full rounded-md border p-4">
+              <div className="space-y-2">
+                {selectedDocuments.map((doc, index) => (
+                  <div key={doc.documentReviewId} className="flex items-center gap-2 p-2 rounded-md bg-muted/50">
+                    <FileText className="h-4 w-4 text-muted-foreground" />
+                    <div className="flex-1 min-w-0">
+                      <p className="text-sm font-medium truncate">{doc.documentName}</p>
+                      <div className="flex items-center gap-2 text-xs text-muted-foreground">
+                        <span>{doc.documentType}</span>
+                        <span>•</span>
+                        <Badge variant={doc.documentSource === "buyer" ? "default" : "secondary"} className="text-xs">
+                          {doc.documentSource}
+                        </Badge>
+                        {doc.reviewStatus && (
+                          <>
+                            <span>•</span>
+                            <span>{doc.reviewStatus}</span>
+                          </>
+                        )}
+                      </div>
+                    </div>
+                  </div>
+                ))}
+              </div>
+            </ScrollArea>
+          </div>
+
+          {/* 경고 메시지 */}
+          {selectedDocuments.some(doc => doc.reviewStatus === "반려") && (
+            <div className="flex items-start gap-2 p-3 rounded-md bg-destructive/10 text-destructive">
+              <AlertCircle className="h-4 w-4 mt-0.5" />
+              <p className="text-sm">
+                반려된 문서가 포함되어 있습니다. 계속 진행하시겠습니까?
+              </p>
+            </div>
+          )}
+        </div>
+
+        <DialogFooter>
+          <Button
+            variant="outline"
+            onClick={() => onOpenChange(false)}
+            disabled={isLoading}
+          >
+            취소
+          </Button>
+          <Button
+            onClick={handleSendEmail}
+            disabled={isLoading || recipients.length === 0}
+          >
+            {isLoading ? (
+              <>
+                <Loader2 className="mr-2 h-4 w-4 animate-spin" />
+                전송 중...
+              </>
+            ) : (
+              <>
+                <Mail className="mr-2 h-4 w-4" />
+                이메일 전송
+              </>
+            )}
+          </Button>
+        </DialogFooter>
+      </DialogContent>
+    </Dialog>
+  )
+}
\ No newline at end of file
-- 
cgit v1.2.3

