'use client'; import { useState, useEffect } from 'react'; import { useRouter, useParams } from 'next/navigation'; import { Box, Container, Typography, Card, CardContent, Chip, Button, IconButton, Grid, Menu, MenuItem, Divider, LinearProgress, CircularProgress, Alert, Tooltip as MuiTooltip, } from '@mui/material'; import { MoreVert, Group, Visibility, TrendingUp, Share, CardGiftcard, AttachMoney, People, Edit, Download, Person, Phone, Email, ShoppingCart, Warning, LocalFireDepartment, Star, NewReleases, Refresh as RefreshIcon, } from '@mui/icons-material'; import { Line, Bar } from 'react-chartjs-2'; import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, BarElement, Title, Tooltip as ChartTooltip, Legend, Filler, } from 'chart.js'; import { analyticsApi } from '@/entities/analytics/api/analyticsApi'; // Chart.js 등록 ChartJS.register( CategoryScale, LinearScale, PointElement, LineElement, BarElement, Title, ChartTooltip, Legend, Filler ); // 디자인 시스템 색상 const colors = { pink: '#F472B6', purple: '#C084FC', purpleLight: '#E9D5FF', blue: '#60A5FA', mint: '#34D399', orange: '#FB923C', yellow: '#FBBF24', gray: { 900: '#1A1A1A', 700: '#4A4A4A', 500: '#9E9E9E', 300: '#D9D9D9', 100: '#F5F5F5', }, }; // Mock 데이터 const mockEventData = { id: '1', title: 'SNS 팔로우 이벤트', status: 'active' as const, startDate: '2025-01-15', endDate: '2025-02-15', prize: '커피 쿠폰', method: 'SNS 팔로우', cost: 250000, channels: ['우리동네TV', '링고비즈', 'SNS'], participants: 128, views: 456, roi: 450, conversion: 28, targetParticipants: 200, isAIRecommended: true, isUrgent: false, isPopular: true, isHighROI: true, isNew: false, }; const recentParticipants = [ { name: '김*진', phone: '010-****-1234', time: '5분 전' }, { name: '이*수', phone: '010-****-5678', time: '12분 전' }, { name: '박*영', phone: '010-****-9012', time: '25분 전' }, { name: '최*민', phone: '010-****-3456', time: '1시간 전' }, { name: '정*희', phone: '010-****-7890', time: '2시간 전' }, ]; // 헬퍼 함수 const getMethodIcon = (method: string) => { switch (method) { case '전화번호 입력': return ; case 'SNS 팔로우': return ; case '구매 인증': return ; case '이메일 등록': return ; default: return ; } }; const calculateProgress = (event: typeof mockEventData) => { if (event.status !== 'active') return 0; const total = new Date(event.endDate).getTime() - new Date(event.startDate).getTime(); const elapsed = Date.now() - new Date(event.startDate).getTime(); return Math.min(Math.max((elapsed / total) * 100, 0), 100); }; export default function EventDetailPage() { const router = useRouter(); const params = useParams(); const eventId = params.eventId as string; const [event, setEvent] = useState(mockEventData); const [anchorEl, setAnchorEl] = useState(null); const [chartPeriod, setChartPeriod] = useState<'7d' | '30d' | 'all'>('7d'); const [loading, setLoading] = useState(true); const [refreshing, setRefreshing] = useState(false); const [error, setError] = useState(null); const [analyticsData, setAnalyticsData] = useState(null); // Analytics API 호출 const fetchAnalytics = async (forceRefresh = false) => { try { if (forceRefresh) { console.log('🔄 Analytics 데이터 새로고침...'); setRefreshing(true); } else { console.log('📊 Analytics 데이터 로딩...'); setLoading(true); } setError(null); // Event Analytics API 병렬 호출 const [dashboard, timeline, roi, channels] = await Promise.all([ analyticsApi.getEventAnalytics(eventId, { refresh: forceRefresh }), analyticsApi.getEventTimelineAnalytics(eventId, { interval: chartPeriod === '7d' ? 'daily' : chartPeriod === '30d' ? 'daily' : 'daily', }), analyticsApi.getEventRoiAnalytics(eventId, { includeProjection: true }), analyticsApi.getEventChannelAnalytics(eventId, {}), ]); console.log('✅ Dashboard 데이터:', dashboard); console.log('✅ Timeline 데이터:', timeline); console.log('✅ ROI 데이터:', roi); console.log('✅ Channels 데이터:', channels); // Analytics 데이터 저장 const formattedAnalyticsData = { dashboard, timeline, roi, channels, }; setAnalyticsData(formattedAnalyticsData); // Event 객체 업데이트 - Analytics 데이터 반영 setEvent(prev => ({ ...prev, participants: dashboard.summary.participants, views: dashboard.summary.totalViews, conversion: dashboard.summary.conversionRate * 100, roi: dashboard.roi.roi, title: dashboard.eventTitle, })); console.log('✅ Analytics 데이터 로딩 완료'); } catch (err: any) { console.error('❌ Analytics 데이터 로딩 실패:', err); // 404 또는 400 에러는 아직 Analytics 데이터가 없는 경우 if (err.response?.status === 404 || err.response?.status === 400) { console.log('ℹ️ Analytics 데이터가 아직 생성되지 않았습니다.'); setError('이벤트의 Analytics 데이터가 아직 생성되지 않았습니다. 참여자가 생기면 자동으로 생성됩니다.'); } else { setError(err.message || 'Analytics 데이터를 불러오는데 실패했습니다.'); } } finally { setLoading(false); setRefreshing(false); } }; // 초기 데이터 로드 useEffect(() => { fetchAnalytics(); }, [eventId]); // 차트 기간 변경 시 Timeline 데이터 다시 로드 useEffect(() => { if (analyticsData) { fetchAnalytics(); } }, [chartPeriod]); const handleMenuOpen = (event: React.MouseEvent) => { setAnchorEl(event.currentTarget); }; const handleMenuClose = () => { setAnchorEl(null); }; const handleRefresh = () => { fetchAnalytics(true); }; // 차트 데이터 생성 함수 const generateParticipationTrendData = () => { if (!analyticsData?.timeline) { return { labels: [], datasets: [{ label: '일별 참여자', data: [], borderColor: colors.blue, backgroundColor: `${colors.blue}40`, fill: true, tension: 0.4, }], }; } const timelineData = analyticsData.timeline; const dataPoints = timelineData.dataPoints || []; // 데이터 포인트를 날짜별로 그룹화 const dailyData = new Map(); dataPoints.forEach((point: any) => { const date = new Date(point.timestamp); const dateKey = `${date.getMonth() + 1}/${date.getDate()}`; dailyData.set(dateKey, (dailyData.get(dateKey) || 0) + point.participants); }); const labels = Array.from(dailyData.keys()); const data = Array.from(dailyData.values()); return { labels, datasets: [{ label: '일별 참여자', data, borderColor: colors.blue, backgroundColor: `${colors.blue}40`, fill: true, tension: 0.4, }], }; }; const generateChannelPerformanceData = () => { if (!analyticsData?.channels?.channels) { return { labels: [], datasets: [{ label: '참여자 수', data: [], backgroundColor: [], borderRadius: 8, }], }; } const channelColors = [colors.pink, colors.blue, colors.orange, colors.purple, colors.mint, colors.yellow]; const channels = analyticsData.channels.channels; const labels = channels.map((ch: any) => { let channelName = ch.channelName || ch.channelType || '알 수 없음'; // 채널명 디코딩 처리 if (channelName.includes('%')) { try { channelName = decodeURIComponent(channelName); } catch (e) { console.warn('⚠️ 채널명 디코딩 실패:', channelName); } } return channelName; }); const data = channels.map((ch: any) => ch.metrics?.participants || 0); const backgroundColor = channels.map((_: any, idx: number) => channelColors[idx % channelColors.length]); return { labels, datasets: [{ label: '참여자 수', data, backgroundColor, borderRadius: 8, }], }; }; const generateRoiTrendData = () => { // ROI는 현재 시점의 값만 있으므로 간단한 추이를 표시 if (!analyticsData?.roi) { return { labels: ['현재'], datasets: [{ label: 'ROI (%)', data: [0], borderColor: colors.mint, backgroundColor: `${colors.mint}40`, fill: true, tension: 0.4, }], }; } const currentRoi = analyticsData.roi.roi?.roiPercentage || 0; // 단순 추정: 초기 0에서 현재 ROI까지의 추이 const labels = ['시작', '1주차', '2주차', '3주차', '현재']; const data = [0, currentRoi * 0.3, currentRoi * 0.5, currentRoi * 0.75, currentRoi]; return { labels, datasets: [{ label: 'ROI (%)', data, borderColor: colors.mint, backgroundColor: `${colors.mint}40`, fill: true, tension: 0.4, }], }; }; const getStatusColor = (status: string) => { switch (status) { case 'active': return 'success'; case 'scheduled': return 'info'; case 'ended': return 'default'; default: return 'default'; } }; const getStatusText = (status: string) => { switch (status) { case 'active': return '진행중'; case 'scheduled': return '예정'; case 'ended': return '종료'; default: return status; } }; // 로딩 중 if (loading) { return ( ); } // 에러 발생 if (error) { return ( {error} ); } return ( {/* Event Header */} {event.title} 이벤트 수정 공유하기 데이터 다운로드 이벤트 삭제 {event.isAIRecommended && ( )} {event.isUrgent && ( } label="마감임박" size="medium" sx={{ bgcolor: '#FEF3C7', color: '#92400E', fontSize: { xs: '0.75rem', sm: '0.875rem' }, height: { xs: 24, sm: 32 } }} /> )} {event.isPopular && ( } label="인기" size="medium" sx={{ bgcolor: '#FEE2E2', color: '#991B1B', fontSize: { xs: '0.75rem', sm: '0.875rem' }, height: { xs: 24, sm: 32 } }} /> )} {event.isHighROI && ( } label="높은 ROI" size="medium" sx={{ bgcolor: '#DCFCE7', color: '#166534', fontSize: { xs: '0.75rem', sm: '0.875rem' }, height: { xs: 24, sm: 32 } }} /> )} {event.isNew && ( } label="신규" size="medium" sx={{ bgcolor: '#DBEAFE', color: '#1E40AF', fontSize: { xs: '0.75rem', sm: '0.875rem' }, height: { xs: 24, sm: 32 } }} /> )} 📅 {event.startDate} ~ {event.endDate} {/* 진행률 바 (진행중인 이벤트만) */} {event.status === 'active' && ( 이벤트 진행률 {Math.round(calculateProgress(event))}% )} {/* Real-time KPIs */} 실시간 현황 실시간 업데이트 참여자 {event.participants}명 목표: {event.targetParticipants}명
({Math.round((event.participants / event.targetParticipants) * 100)}%)
조회수 {event.views} ROI {event.roi}% 전환율 {event.conversion}%
{/* Chart Section - 참여 추이 */} 📈 참여 추이 {/* Chart Section - 채널별 성과 & ROI 추이 */} 📊 채널별 참여자 💰 ROI 추이 {/* Event Details */} 🎯 이벤트 정보 경품 {event.prize} {getMethodIcon(event.method)} 참여 방법 {event.method} 예상 비용 {event.cost.toLocaleString()}원 배포 채널 {event.channels.map((channel) => ( ))} {/* Quick Actions */} ⚡ 빠른 작업 router.push(`/events/${eventId}/participants`)} > 참여자 목록 이벤트 수정 공유하기 데이터 다운 {/* Recent Participants */} 👥 최근 참여자 {recentParticipants.map((participant, index) => ( {index > 0 && } {participant.name} {participant.phone} {participant.time} ))}
); }