"use client"; /** * 메일 발송 노드 속성 편집 * - 메일관리에서 등록한 계정을 선택하여 발송 * - 변수 태그 에디터로 본문 편집 */ import { useEffect, useState, useCallback, useMemo } from "react"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Switch } from "@/components/ui/switch"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Plus, Trash2, Mail, Server, FileText, Settings, RefreshCw, CheckCircle, AlertCircle, User } from "lucide-react"; import { useFlowEditorStore } from "@/lib/stores/flowEditorStore"; import { getMailAccounts, type MailAccount } from "@/lib/api/mail"; import type { EmailActionNodeData } from "@/types/node-editor"; import { VariableTagEditor, type VariableInfo } from "../../editors/VariableTagEditor"; interface EmailActionPropertiesProps { nodeId: string; data: EmailActionNodeData; } export function EmailActionProperties({ nodeId, data }: EmailActionPropertiesProps) { const { updateNode, nodes, edges } = useFlowEditorStore(); // 메일 계정 목록 const [mailAccounts, setMailAccounts] = useState([]); const [isLoadingAccounts, setIsLoadingAccounts] = useState(false); const [accountError, setAccountError] = useState(null); // 🆕 플로우에서 사용 가능한 변수 목록 계산 const availableVariables = useMemo(() => { const variables: VariableInfo[] = []; // 기본 시스템 변수 variables.push( { name: "timestamp", displayName: "현재 시간", description: "메일 발송 시점의 타임스탬프" }, { name: "sourceData", displayName: "소스 데이터", description: "전체 소스 데이터 (JSON)" } ); // 현재 노드에 연결된 소스 노드들에서 필드 정보 수집 const incomingEdges = edges.filter((e) => e.target === nodeId); for (const edge of incomingEdges) { const sourceNode = nodes.find((n) => n.id === edge.source); if (!sourceNode) continue; const nodeData = sourceNode.data as any; // 테이블 소스 노드인 경우 if (sourceNode.type === "tableSource" && nodeData.fields) { const tableName = nodeData.tableName || "테이블"; nodeData.fields.forEach((field: any) => { variables.push({ name: field.name, displayName: field.displayName || field.label || field.name, type: field.type, description: `${tableName} 테이블의 필드`, }); }); } // 외부 DB 소스 노드인 경우 if (sourceNode.type === "externalDBSource" && nodeData.fields) { const tableName = nodeData.tableName || "외부 테이블"; nodeData.fields.forEach((field: any) => { variables.push({ name: field.name, displayName: field.displayName || field.label || field.name, type: field.type, description: `${tableName} (외부 DB) 필드`, }); }); } // REST API 소스 노드인 경우 if (sourceNode.type === "restAPISource" && nodeData.responseFields) { nodeData.responseFields.forEach((field: any) => { variables.push({ name: field.name, displayName: field.displayName || field.label || field.name, type: field.type, description: "REST API 응답 필드", }); }); } // 데이터 변환 노드인 경우 - 출력 필드 추가 if (sourceNode.type === "dataTransform" && nodeData.transformations) { nodeData.transformations.forEach((transform: any) => { if (transform.targetField) { variables.push({ name: transform.targetField, displayName: transform.targetField, description: "데이터 변환 결과 필드", }); } }); } } // 중복 제거 const uniqueVariables = variables.filter( (v, index, self) => index === self.findIndex((t) => t.name === v.name) ); return uniqueVariables; }, [nodes, edges, nodeId]); // 로컬 상태 const [displayName, setDisplayName] = useState(data.displayName || "메일 발송"); // 계정 선택 const [selectedAccountId, setSelectedAccountId] = useState(data.accountId || ""); // 🆕 수신자 컴포넌트 사용 여부 const [useRecipientComponent, setUseRecipientComponent] = useState(data.useRecipientComponent ?? false); const [recipientToField, setRecipientToField] = useState(data.recipientToField || "mailTo"); const [recipientCcField, setRecipientCcField] = useState(data.recipientCcField || "mailCc"); // 메일 내용 const [to, setTo] = useState(data.to || ""); const [cc, setCc] = useState(data.cc || ""); const [bcc, setBcc] = useState(data.bcc || ""); const [subject, setSubject] = useState(data.subject || ""); const [body, setBody] = useState(data.body || ""); const [bodyType, setBodyType] = useState<"text" | "html">(data.bodyType || "text"); // 고급 설정 const [replyTo, setReplyTo] = useState(data.replyTo || ""); const [priority, setPriority] = useState<"high" | "normal" | "low">(data.priority || "normal"); const [timeout, setTimeout] = useState(data.options?.timeout?.toString() || "30000"); const [retryCount, setRetryCount] = useState(data.options?.retryCount?.toString() || "3"); // 메일 계정 목록 로드 const loadMailAccounts = useCallback(async () => { setIsLoadingAccounts(true); setAccountError(null); try { const accounts = await getMailAccounts(); setMailAccounts(accounts.filter(acc => acc.status === 'active')); } catch (error) { console.error("메일 계정 로드 실패:", error); setAccountError("메일 계정을 불러오는데 실패했습니다"); } finally { setIsLoadingAccounts(false); } }, []); // 컴포넌트 마운트 시 메일 계정 로드 useEffect(() => { loadMailAccounts(); }, [loadMailAccounts]); // 데이터 변경 시 로컬 상태 동기화 useEffect(() => { setDisplayName(data.displayName || "메일 발송"); setSelectedAccountId(data.accountId || ""); setUseRecipientComponent(data.useRecipientComponent ?? false); setRecipientToField(data.recipientToField || "mailTo"); setRecipientCcField(data.recipientCcField || "mailCc"); setTo(data.to || ""); setCc(data.cc || ""); setBcc(data.bcc || ""); setSubject(data.subject || ""); setBody(data.body || ""); setBodyType(data.bodyType || "text"); setReplyTo(data.replyTo || ""); setPriority(data.priority || "normal"); setTimeout(data.options?.timeout?.toString() || "30000"); setRetryCount(data.options?.retryCount?.toString() || "3"); }, [data]); // 선택된 계정 정보 const selectedAccount = mailAccounts.find(acc => acc.id === selectedAccountId); // 노드 업데이트 함수 const updateNodeData = useCallback( (updates: Partial) => { updateNode(nodeId, { ...data, ...updates, }); }, [nodeId, data, updateNode] ); // 표시명 변경 const handleDisplayNameChange = (value: string) => { setDisplayName(value); updateNodeData({ displayName: value }); }; // 계정 선택 변경 const handleAccountChange = (accountId: string) => { setSelectedAccountId(accountId); const account = mailAccounts.find(acc => acc.id === accountId); updateNodeData({ accountId, // 계정의 이메일을 발신자로 자동 설정 from: account?.email || "" }); }; // 메일 내용 업데이트 const updateMailContent = useCallback(() => { updateNodeData({ to, cc: cc || undefined, bcc: bcc || undefined, subject, body, bodyType, replyTo: replyTo || undefined, priority, }); }, [to, cc, bcc, subject, body, bodyType, replyTo, priority, updateNodeData]); // 옵션 업데이트 const updateOptions = useCallback(() => { updateNodeData({ options: { timeout: parseInt(timeout) || 30000, retryCount: parseInt(retryCount) || 3, }, }); }, [timeout, retryCount, updateNodeData]); return (
{/* 표시명 */}
handleDisplayNameChange(e.target.value)} placeholder="메일 발송" className="h-8 text-sm" />
계정 메일 본문 옵션 {/* 계정 선택 탭 */}
{accountError && (
{accountError}
)}
{/* 선택된 계정 정보 표시 */} {selectedAccount && (
선택된 계정
이름: {selectedAccount.name}
이메일: {selectedAccount.email}
SMTP: {selectedAccount.smtpHost}:{selectedAccount.smtpPort}
)} {!selectedAccount && mailAccounts.length > 0 && (
메일 발송을 위해 계정을 선택해주세요.
)} {mailAccounts.length === 0 && !isLoadingAccounts && (
메일 계정 등록 방법:
  1. 관리자 메뉴로 이동
  2. 메일관리 > 계정관리 선택
  3. 새 계정 추가 버튼 클릭
  4. SMTP 정보 입력 후 저장
)}
{/* 메일 설정 탭 */} {/* 발신자는 선택된 계정에서 자동으로 설정됨 */} {selectedAccount && (
{selectedAccount.email}

선택한 계정의 이메일 주소가 자동으로 사용됩니다.

)} {/* 🆕 수신자 컴포넌트 사용 옵션 */}

화면에 배치한 "메일 수신자 선택" 컴포넌트의 값을 자동으로 사용합니다.

{ setUseRecipientComponent(checked); if (checked) { // 체크 시 자동으로 변수 설정 updateNodeData({ useRecipientComponent: true, recipientToField, recipientCcField, to: `{{${recipientToField}}}`, cc: `{{${recipientCcField}}}`, }); setTo(`{{${recipientToField}}}`); setCc(`{{${recipientCcField}}}`); } else { updateNodeData({ useRecipientComponent: false, to: "", cc: "", }); setTo(""); setCc(""); } }} />
{/* 필드명 설정 (수신자 컴포넌트 사용 시) */} {useRecipientComponent && (
{ const newField = e.target.value; setRecipientToField(newField); setTo(`{{${newField}}}`); updateNodeData({ recipientToField: newField, to: `{{${newField}}}`, }); }} placeholder="mailTo" className="h-7 text-xs" />
{ const newField = e.target.value; setRecipientCcField(newField); setCc(`{{${newField}}}`); updateNodeData({ recipientCcField: newField, cc: `{{${newField}}}`, }); }} placeholder="mailCc" className="h-7 text-xs" />
자동 설정됨:
수신자: {`{{${recipientToField}}}`}
참조: {`{{${recipientCcField}}}`}
)}
{/* 수신자 직접 입력 (컴포넌트 미사용 시) */} {!useRecipientComponent && ( <>
setTo(e.target.value)} onBlur={updateMailContent} placeholder="recipient@example.com (쉼표로 구분, {{변수}} 사용 가능)" className="h-8 text-sm" />
setCc(e.target.value)} onBlur={updateMailContent} placeholder="cc@example.com" className="h-8 text-sm" />
)}
setBcc(e.target.value)} onBlur={updateMailContent} placeholder="bcc@example.com" className="h-8 text-sm" />
setReplyTo(e.target.value)} onBlur={updateMailContent} placeholder="reply@example.com" className="h-8 text-sm" />
{/* 본문 탭 */}
setSubject(e.target.value)} onBlur={updateMailContent} placeholder="메일 제목 ({{변수}} 사용 가능)" className="h-8 text-sm" />
{/* 텍스트 형식: 변수 태그 에디터 사용 */} {bodyType === "text" && ( { setBody(newBody); updateNodeData({ body: newBody }); }} variables={availableVariables} placeholder="메일 본문을 입력하세요. @ 또는 / 키로 변수를 삽입할 수 있습니다." minHeight="200px" /> )} {/* HTML 형식: 직접 입력 */} {bodyType === "html" && (