summaryrefslogtreecommitdiff
path: root/lib/tbe-last/table/email-documents-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/tbe-last/table/email-documents-dialog.tsx')
-rw-r--r--lib/tbe-last/table/email-documents-dialog.tsx334
1 files changed, 334 insertions, 0 deletions
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