"use client"; import React, { useState, useEffect } from "react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 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 { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Upload, Send, FileText, Users, AlertCircle, CheckCircle2, Loader2, Download, X, } from "lucide-react"; import { useRouter } from "next/navigation"; import Link from "next/link"; import { useToast } from "@/hooks/use-toast"; import { MailAccount, MailTemplate, getMailAccounts, getMailTemplates, sendBulkMail, } from "@/lib/api/mail"; interface RecipientData { email: string; variables: Record; } export default function BulkSendPage() { const router = useRouter(); const { toast } = useToast(); const [accounts, setAccounts] = useState([]); const [templates, setTemplates] = useState([]); const [selectedAccountId, setSelectedAccountId] = useState(""); const [selectedTemplateId, setSelectedTemplateId] = useState(""); const [useTemplate, setUseTemplate] = useState(true); // 템플릿 사용 여부 const [customHtml, setCustomHtml] = useState(""); // 직접 작성한 HTML const [subject, setSubject] = useState(""); const [recipients, setRecipients] = useState([]); const [csvFile, setCsvFile] = useState(null); const [loading, setLoading] = useState(false); const [sending, setSending] = useState(false); const [sendProgress, setSendProgress] = useState({ sent: 0, total: 0 }); useEffect(() => { loadAccounts(); loadTemplates(); }, []); const loadAccounts = async () => { try { const data = await getMailAccounts(); setAccounts(data.filter((acc) => acc.status === 'active')); } catch (error: unknown) { const err = error as Error; toast({ title: "계정 로드 실패", description: err.message, variant: "destructive", }); } }; const loadTemplates = async () => { try { const data = await getMailTemplates(); setTemplates(data); } catch (error: unknown) { const err = error as Error; toast({ title: "템플릿 로드 실패", description: err.message, variant: "destructive", }); } }; const handleFileUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (!file.name.endsWith(".csv")) { toast({ title: "파일 형식 오류", description: "CSV 파일만 업로드 가능합니다.", variant: "destructive", }); return; } setCsvFile(file); setLoading(true); try { const text = await file.text(); const lines = text.split("\n").filter((line) => line.trim()); if (lines.length < 2) { throw new Error("CSV 파일에 데이터가 없습니다."); } // 첫 줄은 헤더 const headers = lines[0].split(",").map((h) => h.trim()); if (!headers.includes("email")) { throw new Error("CSV 파일에 'email' 컬럼이 필요합니다."); } const emailIndex = headers.indexOf("email"); const variableHeaders = headers.filter((h) => h !== "email"); const parsedRecipients: RecipientData[] = lines.slice(1).map((line) => { const values = line.split(",").map((v) => v.trim()); const email = values[emailIndex]; const variables: Record = {}; variableHeaders.forEach((header, index) => { const valueIndex = headers.indexOf(header); variables[header] = values[valueIndex] || ""; }); return { email, variables }; }); setRecipients(parsedRecipients); toast({ title: "파일 업로드 성공", description: `${parsedRecipients.length}명의 수신자를 불러왔습니다.`, }); } catch (error: unknown) { const err = error as Error; toast({ title: "파일 파싱 실패", description: err.message, variant: "destructive", }); setCsvFile(null); setRecipients([]); } finally { setLoading(false); } }; const handleSend = async () => { if (!selectedAccountId) { toast({ title: "계정 선택 필요", description: "발송할 메일 계정을 선택해주세요.", variant: "destructive", }); return; } // 템플릿 또는 직접 작성 중 하나는 있어야 함 if (useTemplate && !selectedTemplateId) { toast({ title: "템플릿 선택 필요", description: "사용할 템플릿을 선택해주세요.", variant: "destructive", }); return; } if (!useTemplate && !customHtml.trim()) { toast({ title: "내용 입력 필요", description: "메일 내용을 입력해주세요.", variant: "destructive", }); return; } if (!subject.trim()) { toast({ title: "제목 입력 필요", description: "메일 제목을 입력해주세요.", variant: "destructive", }); return; } if (recipients.length === 0) { toast({ title: "수신자 없음", description: "CSV 파일을 업로드해주세요.", variant: "destructive", }); return; } setSending(true); setSendProgress({ sent: 0, total: recipients.length }); try { await sendBulkMail({ accountId: selectedAccountId, templateId: useTemplate ? selectedTemplateId : undefined, customHtml: !useTemplate ? customHtml : undefined, subject, recipients, onProgress: (sent, total) => { setSendProgress({ sent, total }); }, }); toast({ title: "대량 발송 완료", description: `${recipients.length}명에게 메일을 발송했습니다.`, }); // 초기화 setSelectedAccountId(""); setSelectedTemplateId(""); setSubject(""); setRecipients([]); setCsvFile(null); } catch (error: unknown) { const err = error as Error; toast({ title: "발송 실패", description: err.message, variant: "destructive", }); } finally { setSending(false); } }; const downloadSampleCsv = () => { const sample = `email,name,company example1@example.com,홍길동,ABC회사 example2@example.com,김철수,XYZ회사`; const blob = new Blob([sample], { type: "text/csv;charset=utf-8;" }); const link = document.createElement("a"); link.href = URL.createObjectURL(blob); link.download = "sample.csv"; link.click(); }; return (
{/* 헤더 */}

대량 메일 발송

CSV 파일로 여러 수신자에게 메일을 발송하세요

{/* 왼쪽: 설정 */}
{/* 계정 선택 */} 발송 설정
{useTemplate ? (
) : (