"use client"; /** * 메일 수신자 선택 컴포넌트 * InteractiveScreenViewer에서 사용하는 래퍼 컴포넌트 */ import React, { useState, useEffect, useCallback } from "react"; import { X, Plus, Users, Mail, Check } from "lucide-react"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Label } from "@/components/ui/label"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command"; import { cn } from "@/lib/utils"; import { getUserList } from "@/lib/api/user"; import type { MailRecipientSelectorConfig, Recipient, InternalUser, } from "./types"; interface MailRecipientSelectorComponentProps { // 컴포넌트 기본 Props id?: string; componentConfig?: MailRecipientSelectorConfig; // 폼 데이터 연동 formData?: Record; onFormDataChange?: (fieldName: string, value: any) => void; // 스타일 className?: string; style?: React.CSSProperties; // 모드 isPreviewMode?: boolean; isInteractive?: boolean; isDesignMode?: boolean; // 기타 Props (무시) [key: string]: any; } export const MailRecipientSelectorComponent: React.FC< MailRecipientSelectorComponentProps > = ({ id, componentConfig, formData = {}, onFormDataChange, className, style, isPreviewMode = false, isInteractive = true, isDesignMode = false, ...rest }) => { // config 기본값 const config = componentConfig || {}; const { toFieldName = "mailTo", ccFieldName = "mailCc", showCc = true, showInternalSelector = true, showExternalInput = true, toLabel = "수신자", ccLabel = "참조(CC)", maxRecipients, maxCcRecipients, required = true, } = config; // 상태 const [toRecipients, setToRecipients] = useState([]); const [ccRecipients, setCcRecipients] = useState([]); const [externalEmail, setExternalEmail] = useState(""); const [externalCcEmail, setExternalCcEmail] = useState(""); const [internalUsers, setInternalUsers] = useState([]); const [isLoadingUsers, setIsLoadingUsers] = useState(false); const [toPopoverOpen, setToPopoverOpen] = useState(false); const [ccPopoverOpen, setCcPopoverOpen] = useState(false); // 내부 사용자 목록 로드 const loadInternalUsers = useCallback(async () => { if (!showInternalSelector || isDesignMode) return; setIsLoadingUsers(true); try { const response = await getUserList({ status: "active", limit: 1000 }); if (response.success && response.data) { setInternalUsers(response.data); } } catch (error) { console.error("사용자 목록 로드 실패:", error); } finally { setIsLoadingUsers(false); } }, [showInternalSelector, isDesignMode]); // 컴포넌트 마운트 시 사용자 목록 로드 useEffect(() => { loadInternalUsers(); }, [loadInternalUsers]); // formData에서 초기값 로드 useEffect(() => { if (formData[toFieldName]) { const emails = formData[toFieldName].split(",").filter(Boolean); const recipients: Recipient[] = emails.map((email: string) => ({ id: `external-${email.trim()}`, email: email.trim(), type: "external" as const, })); setToRecipients(recipients); } if (formData[ccFieldName]) { const emails = formData[ccFieldName].split(",").filter(Boolean); const recipients: Recipient[] = emails.map((email: string) => ({ id: `external-${email.trim()}`, email: email.trim(), type: "external" as const, })); setCcRecipients(recipients); } }, []); // 수신자 변경 시 formData 업데이트 const updateFormData = useCallback( (recipients: Recipient[], fieldName: string) => { const emailString = recipients.map((r) => r.email).join(","); onFormDataChange?.(fieldName, emailString); }, [onFormDataChange] ); // 수신자 추가 (내부 사용자) const addInternalRecipient = useCallback( (user: InternalUser, type: "to" | "cc") => { const email = user.email || `${user.userId}@company.com`; const newRecipient: Recipient = { id: `internal-${user.userId}`, email, name: user.userName, type: "internal", userId: user.userId, }; if (type === "to") { // 중복 체크 if (toRecipients.some((r) => r.email === email)) return; // 최대 수신자 수 체크 if (maxRecipients && toRecipients.length >= maxRecipients) return; const updated = [...toRecipients, newRecipient]; setToRecipients(updated); updateFormData(updated, toFieldName); setToPopoverOpen(false); } else { if (ccRecipients.some((r) => r.email === email)) return; if (maxCcRecipients && ccRecipients.length >= maxCcRecipients) return; const updated = [...ccRecipients, newRecipient]; setCcRecipients(updated); updateFormData(updated, ccFieldName); setCcPopoverOpen(false); } }, [ toRecipients, ccRecipients, maxRecipients, maxCcRecipients, toFieldName, ccFieldName, updateFormData, ] ); // 외부 이메일 추가 const addExternalEmail = useCallback( (email: string, type: "to" | "cc") => { // 이메일 형식 검증 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) return; const newRecipient: Recipient = { id: `external-${email}-${type}`, email, type: "external", }; if (type === "to") { // 해당 필드 내에서만 중복 체크 if (toRecipients.some((r) => r.email === email)) return; if (maxRecipients && toRecipients.length >= maxRecipients) return; const updated = [...toRecipients, newRecipient]; setToRecipients(updated); updateFormData(updated, toFieldName); setExternalEmail(""); } else { // 해당 필드 내에서만 중복 체크 if (ccRecipients.some((r) => r.email === email)) return; if (maxCcRecipients && ccRecipients.length >= maxCcRecipients) return; const updated = [...ccRecipients, newRecipient]; setCcRecipients(updated); updateFormData(updated, ccFieldName); setExternalCcEmail(""); } }, [ toRecipients, ccRecipients, maxRecipients, maxCcRecipients, toFieldName, ccFieldName, updateFormData, ] ); // 수신자 제거 const removeRecipient = useCallback( (recipientId: string, type: "to" | "cc") => { if (type === "to") { const updated = toRecipients.filter((r) => r.id !== recipientId); setToRecipients(updated); updateFormData(updated, toFieldName); } else { const updated = ccRecipients.filter((r) => r.id !== recipientId); setCcRecipients(updated); updateFormData(updated, ccFieldName); } }, [toRecipients, ccRecipients, toFieldName, ccFieldName, updateFormData] ); // 이미 선택된 사용자인지 확인 (해당 필드 내에서만 중복 체크) const isUserSelected = useCallback( (user: InternalUser, type: "to" | "cc") => { const recipients = type === "to" ? toRecipients : ccRecipients; const userEmail = user.email || `${user.userId}@company.com`; return recipients.some((r) => r.email === userEmail); }, [toRecipients, ccRecipients] ); // 수신자 태그 렌더링 const renderRecipientTags = (recipients: Recipient[], type: "to" | "cc") => (
{recipients.map((recipient) => ( {recipient.type === "internal" ? ( ) : ( )} {recipient.name || recipient.email} {isInteractive && !isDesignMode && ( )} ))}
); // 내부 사용자 선택 팝오버 const renderInternalSelector = (type: "to" | "cc") => { const isOpen = type === "to" ? toPopoverOpen : ccPopoverOpen; const setIsOpen = type === "to" ? setToPopoverOpen : setCcPopoverOpen; return ( 검색 결과가 없습니다. {internalUsers.map((user, index) => { const userEmail = user.email || `${user.userId}@company.com`; const selected = isUserSelected(user, type); const uniqueKey = `${user.userId}-${index}`; return ( { if (!selected) { addInternalRecipient(user, type); } }} className={cn( "cursor-pointer", selected && "opacity-50 cursor-not-allowed" )} >
{user.userName} {userEmail} {user.deptName && ` | ${user.deptName}`}
{selected && }
); })}
); }; // 외부 이메일 입력 const renderExternalInput = (type: "to" | "cc") => { const value = type === "to" ? externalEmail : externalCcEmail; const setValue = type === "to" ? setExternalEmail : setExternalCcEmail; return (
setValue(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); addExternalEmail(value, type); } }} className="h-8 flex-1 text-sm" disabled={!isInteractive || isDesignMode} />
); }; // 디자인 모드 또는 프리뷰 모드 if (isDesignMode || isPreviewMode) { return (
메일 수신자 선택
내부 인원 선택
외부 이메일 입력
); } return (
{/* 수신자 (To) */}
{/* 선택된 수신자 태그 */} {toRecipients.length > 0 && (
{renderRecipientTags(toRecipients, "to")}
)} {/* 추가 버튼들 */}
{showInternalSelector && renderInternalSelector("to")} {showExternalInput && renderExternalInput("to")}
{/* 참조 (CC) */} {showCc && (
{/* 선택된 참조 수신자 태그 */} {ccRecipients.length > 0 && (
{renderRecipientTags(ccRecipients, "cc")}
)} {/* 추가 버튼들 */}
{showInternalSelector && renderInternalSelector("cc")} {showExternalInput && renderExternalInput("cc")}
)}
); }; export default MailRecipientSelectorComponent;