'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 */}
👥 최근 참여자
}
onClick={() => router.push(`/events/${eventId}/participants`)}
sx={{ color: colors.pink, fontWeight: 600, fontSize: { xs: '0.875rem', sm: '1rem' } }}
>
전체보기
{recentParticipants.map((participant, index) => (
{index > 0 && }
{participant.name}
{participant.phone}
{participant.time}
))}
);
}