mirror of
https://github.com/ktds-dg0501/kt-event-marketing-fe.git
synced 2025-12-06 05:36:23 +00:00
이벤트 상세 페이지 디자인 개선 및 차트 추가
- 전체 간격 2배 이상 확대하여 다른 페이지와 디자인 통일 - 실제 Chart.js 라이브러리로 3개 차트 구현 - 참여 추이 차트 (Line): 7일/30일/전체 기간 선택 가능 - 채널별 참여자 차트 (Bar): 우리동네TV, 링고비즈, SNS - ROI 추이 차트 (Line): 주차별 ROI 성장 추이 - 상태 배지 추가 (AI 추천, 마감임박, 인기, 높은 ROI, 신규) - 진행중인 이벤트에 진행률 바 추가 - KPI 카드에 그라데이션 배경 적용 및 목표 달성률 표시 - 이벤트 정보 섹션 디자인 개선 (아이콘 색상, 간격 확대) - Quick Actions 카드 hover 효과 개선 - 최근 참여자 아바타 디자인 개선 - 실시간 업데이트 인디케이터에 pulse 애니메이션 추가 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e8ea659c0b
commit
de7726ffad
@ -15,6 +15,7 @@ import {
|
||||
Menu,
|
||||
MenuItem,
|
||||
Divider,
|
||||
LinearProgress,
|
||||
} from '@mui/material';
|
||||
import {
|
||||
MoreVert,
|
||||
@ -23,13 +24,63 @@ import {
|
||||
TrendingUp,
|
||||
Share,
|
||||
CardGiftcard,
|
||||
HowToReg,
|
||||
AttachMoney,
|
||||
People,
|
||||
Edit,
|
||||
Download,
|
||||
Person,
|
||||
Phone,
|
||||
Email,
|
||||
ShoppingCart,
|
||||
Warning,
|
||||
LocalFireDepartment,
|
||||
Star,
|
||||
NewReleases,
|
||||
} from '@mui/icons-material';
|
||||
import { Line, Bar } from 'react-chartjs-2';
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend,
|
||||
Filler,
|
||||
} from 'chart.js';
|
||||
|
||||
// Chart.js 등록
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
PointElement,
|
||||
LineElement,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
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 = {
|
||||
@ -41,12 +92,17 @@ const mockEventData = {
|
||||
prize: '커피 쿠폰',
|
||||
method: 'SNS 팔로우',
|
||||
cost: 250000,
|
||||
channels: ['홈페이지', '카카오톡', 'Instagram'],
|
||||
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 = [
|
||||
@ -57,6 +113,86 @@ const recentParticipants = [
|
||||
{ name: '정*희', phone: '010-****-7890', time: '2시간 전' },
|
||||
];
|
||||
|
||||
// 차트 데이터 생성 함수
|
||||
const generateParticipationTrendData = (period: '7d' | '30d' | 'all') => {
|
||||
const labels =
|
||||
period === '7d'
|
||||
? ['1/20', '1/21', '1/22', '1/23', '1/24', '1/25', '1/26']
|
||||
: period === '30d'
|
||||
? Array.from({ length: 30 }, (_, i) => `1/${i + 1}`)
|
||||
: Array.from({ length: 31 }, (_, i) => `1/${i + 1}`);
|
||||
|
||||
const data =
|
||||
period === '7d'
|
||||
? [12, 19, 15, 25, 22, 30, 28]
|
||||
: period === '30d'
|
||||
? Array.from({ length: 30 }, () => Math.floor(Math.random() * 30) + 10)
|
||||
: Array.from({ length: 31 }, () => Math.floor(Math.random() * 30) + 10);
|
||||
|
||||
return {
|
||||
labels,
|
||||
datasets: [
|
||||
{
|
||||
label: '일별 참여자',
|
||||
data,
|
||||
borderColor: colors.blue,
|
||||
backgroundColor: `${colors.blue}40`,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
const channelPerformanceData = {
|
||||
labels: ['우리동네TV', '링고비즈', 'SNS'],
|
||||
datasets: [
|
||||
{
|
||||
label: '참여자 수',
|
||||
data: [58, 38, 32],
|
||||
backgroundColor: [colors.pink, colors.blue, colors.orange],
|
||||
borderRadius: 8,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const roiTrendData = {
|
||||
labels: ['1주차', '2주차', '3주차', '4주차'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'ROI (%)',
|
||||
data: [150, 280, 380, 450],
|
||||
borderColor: colors.mint,
|
||||
backgroundColor: `${colors.mint}40`,
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// 헬퍼 함수
|
||||
const getMethodIcon = (method: string) => {
|
||||
switch (method) {
|
||||
case '전화번호 입력':
|
||||
return <Phone sx={{ fontSize: 18 }} />;
|
||||
case 'SNS 팔로우':
|
||||
return <Share sx={{ fontSize: 18 }} />;
|
||||
case '구매 인증':
|
||||
return <ShoppingCart sx={{ fontSize: 18 }} />;
|
||||
case '이메일 등록':
|
||||
return <Email sx={{ fontSize: 18 }} />;
|
||||
default:
|
||||
return <Share sx={{ fontSize: 18 }} />;
|
||||
}
|
||||
};
|
||||
|
||||
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();
|
||||
@ -119,11 +255,11 @@ export default function EventDetailPage() {
|
||||
|
||||
return (
|
||||
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}>
|
||||
<Container maxWidth="lg" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}>
|
||||
<Container maxWidth="lg" sx={{ pt: 8, pb: 8, px: { xs: 6, sm: 8, md: 10 } }}>
|
||||
{/* Event Header */}
|
||||
<Box sx={{ mb: 4 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 700 }}>
|
||||
<Box sx={{ mb: 8 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 4 }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 700, fontSize: '2rem' }}>
|
||||
{event.title}
|
||||
</Typography>
|
||||
<IconButton onClick={handleMenuOpen}>
|
||||
@ -131,13 +267,13 @@ export default function EventDetailPage() {
|
||||
</IconButton>
|
||||
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
|
||||
<MenuItem onClick={handleMenuClose}>
|
||||
<Edit sx={{ mr: 1 }} /> 이벤트 수정
|
||||
<Edit sx={{ mr: 2 }} /> 이벤트 수정
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleMenuClose}>
|
||||
<Share sx={{ mr: 1 }} /> 공유하기
|
||||
<Share sx={{ mr: 2 }} /> 공유하기
|
||||
</MenuItem>
|
||||
<MenuItem onClick={handleMenuClose}>
|
||||
<Download sx={{ mr: 1 }} /> 데이터 다운로드
|
||||
<Download sx={{ mr: 2 }} /> 데이터 다운로드
|
||||
</MenuItem>
|
||||
<Divider />
|
||||
<MenuItem onClick={handleMenuClose} sx={{ color: 'error.main' }}>
|
||||
@ -146,35 +282,99 @@ export default function EventDetailPage() {
|
||||
</Menu>
|
||||
</Box>
|
||||
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}>
|
||||
<Chip label={getStatusText(event.status)} color={getStatusColor(event.status) as any} size="small" />
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap', mb: 4 }}>
|
||||
<Chip
|
||||
label={getStatusText(event.status)}
|
||||
color={getStatusColor(event.status) as any}
|
||||
size="medium"
|
||||
/>
|
||||
{event.isAIRecommended && (
|
||||
<Chip label="AI 추천" size="medium" sx={{ bgcolor: colors.purpleLight, color: colors.purple }} />
|
||||
)}
|
||||
{event.isUrgent && (
|
||||
<Chip
|
||||
label="AI 추천"
|
||||
size="small"
|
||||
sx={{ bgcolor: 'rgba(0, 102, 255, 0.1)', color: 'info.main' }}
|
||||
icon={<Warning />}
|
||||
label="마감임박"
|
||||
size="medium"
|
||||
sx={{ bgcolor: '#FEF3C7', color: '#92400E' }}
|
||||
/>
|
||||
)}
|
||||
{event.isPopular && (
|
||||
<Chip
|
||||
icon={<LocalFireDepartment />}
|
||||
label="인기"
|
||||
size="medium"
|
||||
sx={{ bgcolor: '#FEE2E2', color: '#991B1B' }}
|
||||
/>
|
||||
)}
|
||||
{event.isHighROI && (
|
||||
<Chip
|
||||
icon={<Star />}
|
||||
label="높은 ROI"
|
||||
size="medium"
|
||||
sx={{ bgcolor: '#DCFCE7', color: '#166534' }}
|
||||
/>
|
||||
)}
|
||||
{event.isNew && (
|
||||
<Chip
|
||||
icon={<NewReleases />}
|
||||
label="신규"
|
||||
size="medium"
|
||||
sx={{ bgcolor: '#DBEAFE', color: '#1E40AF' }}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
<Typography variant="body1" color="text.secondary">
|
||||
{event.startDate} ~ {event.endDate}
|
||||
<Typography variant="body1" color="text.secondary" sx={{ fontSize: '1rem', mb: 4 }}>
|
||||
📅 {event.startDate} ~ {event.endDate}
|
||||
</Typography>
|
||||
|
||||
{/* 진행률 바 (진행중인 이벤트만) */}
|
||||
{event.status === 'active' && (
|
||||
<Box>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||
이벤트 진행률
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||
{Math.round(calculateProgress(event))}%
|
||||
</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={calculateProgress(event)}
|
||||
sx={{
|
||||
height: 10,
|
||||
borderRadius: 5,
|
||||
bgcolor: colors.gray[100],
|
||||
'& .MuiLinearProgress-bar': {
|
||||
background: `linear-gradient(90deg, ${colors.mint} 0%, ${colors.blue} 100%)`,
|
||||
borderRadius: 5,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Real-time KPIs */}
|
||||
<Box sx={{ mb: 5 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700 }}>
|
||||
<Box sx={{ mb: 10 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 6 }}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700, fontSize: '1.5rem' }}>
|
||||
실시간 현황
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, color: 'success.main' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, color: 'success.main' }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: 8,
|
||||
height: 8,
|
||||
width: 10,
|
||||
height: 10,
|
||||
borderRadius: '50%',
|
||||
bgcolor: 'success.main',
|
||||
animation: 'pulse 2s infinite',
|
||||
'@keyframes pulse': {
|
||||
'0%, 100%': { opacity: 1 },
|
||||
'50%': { opacity: 0.5 },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||
@ -183,56 +383,86 @@ export default function EventDetailPage() {
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={2}>
|
||||
<Grid container spacing={6}>
|
||||
<Grid item xs={6} md={3}>
|
||||
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}>
|
||||
<CardContent sx={{ textAlign: 'center', py: 3 }}>
|
||||
<Group color="primary" sx={{ fontSize: 32, mb: 1 }} />
|
||||
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
|
||||
<Card
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderRadius: 4,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
||||
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
|
||||
}}
|
||||
>
|
||||
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||||
<Group sx={{ fontSize: 40, mb: 2, color: 'white' }} />
|
||||
<Typography variant="caption" display="block" sx={{ mb: 2, color: 'rgba(255,255,255,0.9)' }}>
|
||||
참여자
|
||||
</Typography>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700 }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
|
||||
{event.participants}명
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.8)', mt: 1, display: 'block' }}>
|
||||
목표: {event.targetParticipants}명 (
|
||||
{Math.round((event.participants / event.targetParticipants) * 100)}%)
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={6} md={3}>
|
||||
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}>
|
||||
<CardContent sx={{ textAlign: 'center', py: 3 }}>
|
||||
<Visibility color="info" sx={{ fontSize: 32, mb: 1 }} />
|
||||
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
|
||||
<Card
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderRadius: 4,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
||||
background: `linear-gradient(135deg, ${colors.blue} 0%, #93C5FD 100%)`,
|
||||
}}
|
||||
>
|
||||
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||||
<Visibility sx={{ fontSize: 40, mb: 2, color: 'white' }} />
|
||||
<Typography variant="caption" display="block" sx={{ mb: 2, color: 'rgba(255,255,255,0.9)' }}>
|
||||
조회수
|
||||
</Typography>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700 }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
|
||||
{event.views}
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={6} md={3}>
|
||||
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}>
|
||||
<CardContent sx={{ textAlign: 'center', py: 3 }}>
|
||||
<TrendingUp color="success" sx={{ fontSize: 32, mb: 1 }} />
|
||||
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
|
||||
<Card
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderRadius: 4,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
||||
background: `linear-gradient(135deg, ${colors.mint} 0%, #6EE7B7 100%)`,
|
||||
}}
|
||||
>
|
||||
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||||
<TrendingUp sx={{ fontSize: 40, mb: 2, color: 'white' }} />
|
||||
<Typography variant="caption" display="block" sx={{ mb: 2, color: 'rgba(255,255,255,0.9)' }}>
|
||||
ROI
|
||||
</Typography>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700, color: 'success.main' }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
|
||||
{event.roi}%
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
<Grid item xs={6} md={3}>
|
||||
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}>
|
||||
<CardContent sx={{ textAlign: 'center', py: 3 }}>
|
||||
<span className="material-icons" style={{ fontSize: 32, marginBottom: 8, color: '#1976d2' }}>
|
||||
conversion_path
|
||||
</span>
|
||||
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
|
||||
<Card
|
||||
elevation={0}
|
||||
sx={{
|
||||
borderRadius: 4,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
||||
background: `linear-gradient(135deg, ${colors.orange} 0%, #FCD34D 100%)`,
|
||||
}}
|
||||
>
|
||||
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||||
<TrendingUp sx={{ fontSize: 40, mb: 2, color: 'white' }} />
|
||||
<Typography variant="caption" display="block" sx={{ mb: 2, color: 'rgba(255,255,255,0.9)' }}>
|
||||
전환율
|
||||
</Typography>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700 }}>
|
||||
<Typography variant="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
|
||||
{event.conversion}%
|
||||
</Typography>
|
||||
</CardContent>
|
||||
@ -241,112 +471,236 @@ export default function EventDetailPage() {
|
||||
</Grid>
|
||||
</Box>
|
||||
|
||||
{/* Chart Section */}
|
||||
<Box sx={{ mb: 5 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 3 }}>
|
||||
참여 추이
|
||||
{/* Chart Section - 참여 추이 */}
|
||||
<Box sx={{ mb: 10 }}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700, mb: 6, fontSize: '1.5rem' }}>
|
||||
📈 참여 추이
|
||||
</Typography>
|
||||
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}>
|
||||
<CardContent sx={{ p: 3 }}>
|
||||
<Box sx={{ display: 'flex', gap: 1, mb: 3 }}>
|
||||
<Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
|
||||
<CardContent sx={{ p: 6 }}>
|
||||
<Box sx={{ display: 'flex', gap: 2, mb: 6 }}>
|
||||
<Button
|
||||
size="small"
|
||||
size="medium"
|
||||
variant={chartPeriod === '7d' ? 'contained' : 'outlined'}
|
||||
onClick={() => setChartPeriod('7d')}
|
||||
sx={{ borderRadius: 2 }}
|
||||
>
|
||||
7일
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
size="medium"
|
||||
variant={chartPeriod === '30d' ? 'contained' : 'outlined'}
|
||||
onClick={() => setChartPeriod('30d')}
|
||||
sx={{ borderRadius: 2 }}
|
||||
>
|
||||
30일
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
size="medium"
|
||||
variant={chartPeriod === 'all' ? 'contained' : 'outlined'}
|
||||
onClick={() => setChartPeriod('all')}
|
||||
sx={{ borderRadius: 2 }}
|
||||
>
|
||||
전체
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
sx={{
|
||||
height: 200,
|
||||
bgcolor: 'grey.100',
|
||||
borderRadius: 2,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexDirection: 'column',
|
||||
}}
|
||||
>
|
||||
<span className="material-icons" style={{ fontSize: 48, color: '#9e9e9e' }}>
|
||||
show_chart
|
||||
</span>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||
참여자 추이 차트
|
||||
</Typography>
|
||||
<Box sx={{ height: 320 }}>
|
||||
<Line
|
||||
data={generateParticipationTrendData(chartPeriod)}
|
||||
options={{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top' as const,
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
padding: 12,
|
||||
titleFont: { size: 14, weight: 'bold' },
|
||||
bodyFont: { size: 13 },
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.05)',
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Box>
|
||||
|
||||
{/* Chart Section - 채널별 성과 & ROI 추이 */}
|
||||
<Grid container spacing={6} sx={{ mb: 10 }}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700, mb: 6, fontSize: '1.5rem' }}>
|
||||
📊 채널별 참여자
|
||||
</Typography>
|
||||
<Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
|
||||
<CardContent sx={{ p: 6 }}>
|
||||
<Box sx={{ height: 320 }}>
|
||||
<Bar
|
||||
data={channelPerformanceData}
|
||||
options={{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
padding: 12,
|
||||
titleFont: { size: 14, weight: 'bold' },
|
||||
bodyFont: { size: 13 },
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.05)',
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700, mb: 6, fontSize: '1.5rem' }}>
|
||||
💰 ROI 추이
|
||||
</Typography>
|
||||
<Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
|
||||
<CardContent sx={{ p: 6 }}>
|
||||
<Box sx={{ height: 320 }}>
|
||||
<Line
|
||||
data={roiTrendData}
|
||||
options={{
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
display: true,
|
||||
position: 'top' as const,
|
||||
},
|
||||
tooltip: {
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||
padding: 12,
|
||||
titleFont: { size: 14, weight: 'bold' },
|
||||
bodyFont: { size: 13 },
|
||||
},
|
||||
},
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true,
|
||||
grid: {
|
||||
color: 'rgba(0, 0, 0, 0.05)',
|
||||
},
|
||||
},
|
||||
x: {
|
||||
grid: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Event Details */}
|
||||
<Box sx={{ mb: 5 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 3 }}>
|
||||
이벤트 정보
|
||||
<Box sx={{ mb: 10 }}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700, mb: 6, fontSize: '1.5rem' }}>
|
||||
🎯 이벤트 정보
|
||||
</Typography>
|
||||
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}>
|
||||
<CardContent sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
|
||||
<CardGiftcard color="error" />
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 0.5 }}>
|
||||
경품
|
||||
</Typography>
|
||||
<Typography variant="body1">{event.prize}</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}>
|
||||
<CardContent sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
|
||||
<HowToReg color="error" />
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 0.5 }}>
|
||||
참여 방법
|
||||
</Typography>
|
||||
<Typography variant="body1">{event.method}</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}>
|
||||
<CardContent sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
|
||||
<AttachMoney color="error" />
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 0.5 }}>
|
||||
예상 비용
|
||||
</Typography>
|
||||
<Typography variant="body1">{event.cost.toLocaleString()}원</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}>
|
||||
<CardContent sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
|
||||
<Share color="error" />
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
|
||||
<CardContent sx={{ display: 'flex', alignItems: 'flex-start', gap: 3, p: 4 }}>
|
||||
<CardGiftcard sx={{ fontSize: 28, color: colors.pink }} />
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
|
||||
경품
|
||||
</Typography>
|
||||
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
||||
{event.prize}
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
|
||||
<CardContent sx={{ display: 'flex', alignItems: 'flex-start', gap: 3, p: 4 }}>
|
||||
{getMethodIcon(event.method)}
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
|
||||
참여 방법
|
||||
</Typography>
|
||||
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
||||
{event.method}
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
|
||||
<CardContent sx={{ display: 'flex', alignItems: 'flex-start', gap: 3, p: 4 }}>
|
||||
<AttachMoney sx={{ fontSize: 28, color: colors.mint }} />
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}>
|
||||
예상 비용
|
||||
</Typography>
|
||||
<Typography variant="h6" sx={{ fontWeight: 600 }}>
|
||||
{event.cost.toLocaleString()}원
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
|
||||
<CardContent sx={{ display: 'flex', alignItems: 'flex-start', gap: 3, p: 4 }}>
|
||||
<Share sx={{ fontSize: 28, color: colors.blue }} />
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 2 }}>
|
||||
배포 채널
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
|
||||
{event.channels.map((channel) => (
|
||||
<Chip key={channel} label={channel} size="small" color="success" />
|
||||
<Chip
|
||||
key={channel}
|
||||
label={channel}
|
||||
size="medium"
|
||||
sx={{
|
||||
bgcolor: colors.purpleLight,
|
||||
color: colors.purple,
|
||||
fontWeight: 600,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
@ -356,29 +710,31 @@ export default function EventDetailPage() {
|
||||
</Box>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<Box sx={{ mb: 5 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 3 }}>
|
||||
빠른 작업
|
||||
<Box sx={{ mb: 10 }}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700, mb: 6, fontSize: '1.5rem' }}>
|
||||
⚡ 빠른 작업
|
||||
</Typography>
|
||||
<Grid container spacing={2}>
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={6} md={3}>
|
||||
<Card
|
||||
elevation={0}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
borderRadius: 3,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: 4,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
||||
transition: 'all 0.2s',
|
||||
'&:hover': {
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||
transform: 'translateY(-2px)',
|
||||
transform: 'translateY(-4px)',
|
||||
},
|
||||
}}
|
||||
onClick={() => router.push(`/events/${eventId}/participants`)}
|
||||
>
|
||||
<CardContent sx={{ textAlign: 'center', py: 3 }}>
|
||||
<People color="error" sx={{ fontSize: 32, mb: 1 }} />
|
||||
<Typography variant="body2">참여자 목록</Typography>
|
||||
<CardContent sx={{ textAlign: 'center', py: 5 }}>
|
||||
<People sx={{ fontSize: 40, mb: 2, color: colors.pink }} />
|
||||
<Typography variant="body1" sx={{ fontWeight: 600 }}>
|
||||
참여자 목록
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
@ -387,18 +743,20 @@ export default function EventDetailPage() {
|
||||
elevation={0}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
borderRadius: 3,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: 4,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
||||
transition: 'all 0.2s',
|
||||
'&:hover': {
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||
transform: 'translateY(-2px)',
|
||||
transform: 'translateY(-4px)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardContent sx={{ textAlign: 'center', py: 3 }}>
|
||||
<Edit color="info" sx={{ fontSize: 32, mb: 1 }} />
|
||||
<Typography variant="body2">이벤트 수정</Typography>
|
||||
<CardContent sx={{ textAlign: 'center', py: 5 }}>
|
||||
<Edit sx={{ fontSize: 40, mb: 2, color: colors.blue }} />
|
||||
<Typography variant="body1" sx={{ fontWeight: 600 }}>
|
||||
이벤트 수정
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
@ -407,18 +765,20 @@ export default function EventDetailPage() {
|
||||
elevation={0}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
borderRadius: 3,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: 4,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
||||
transition: 'all 0.2s',
|
||||
'&:hover': {
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||
transform: 'translateY(-2px)',
|
||||
transform: 'translateY(-4px)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardContent sx={{ textAlign: 'center', py: 3 }}>
|
||||
<Share sx={{ fontSize: 32, mb: 1, color: 'text.secondary' }} />
|
||||
<Typography variant="body2">공유하기</Typography>
|
||||
<CardContent sx={{ textAlign: 'center', py: 5 }}>
|
||||
<Share sx={{ fontSize: 40, mb: 2, color: colors.purple }} />
|
||||
<Typography variant="body1" sx={{ fontWeight: 600 }}>
|
||||
공유하기
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
@ -427,18 +787,20 @@ export default function EventDetailPage() {
|
||||
elevation={0}
|
||||
sx={{
|
||||
cursor: 'pointer',
|
||||
borderRadius: 3,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
|
||||
borderRadius: 4,
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
||||
transition: 'all 0.2s',
|
||||
'&:hover': {
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
|
||||
transform: 'translateY(-2px)',
|
||||
transform: 'translateY(-4px)',
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CardContent sx={{ textAlign: 'center', py: 3 }}>
|
||||
<Download sx={{ fontSize: 32, mb: 1, color: 'text.secondary' }} />
|
||||
<Typography variant="body2">데이터 다운</Typography>
|
||||
<CardContent sx={{ textAlign: 'center', py: 5 }}>
|
||||
<Download sx={{ fontSize: 40, mb: 2, color: colors.mint }} />
|
||||
<Typography variant="body1" sx={{ fontWeight: 600 }}>
|
||||
데이터 다운
|
||||
</Typography>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Grid>
|
||||
@ -446,51 +808,51 @@ export default function EventDetailPage() {
|
||||
</Box>
|
||||
|
||||
{/* Recent Participants */}
|
||||
<Box sx={{ mb: 5 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}>
|
||||
<Typography variant="h6" sx={{ fontWeight: 700 }}>
|
||||
최근 참여자
|
||||
<Box sx={{ mb: 10 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 6 }}>
|
||||
<Typography variant="h5" sx={{ fontWeight: 700, fontSize: '1.5rem' }}>
|
||||
👥 최근 참여자
|
||||
</Typography>
|
||||
<Button
|
||||
size="small"
|
||||
endIcon={<span className="material-icons" style={{ fontSize: 16 }}>chevron_right</span>}
|
||||
size="medium"
|
||||
endIcon={<span className="material-icons" style={{ fontSize: 18 }}>chevron_right</span>}
|
||||
onClick={() => router.push(`/events/${eventId}/participants`)}
|
||||
sx={{ color: 'error.main', fontWeight: 600 }}
|
||||
sx={{ color: colors.pink, fontWeight: 600 }}
|
||||
>
|
||||
전체보기
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}>
|
||||
<CardContent sx={{ p: 3 }}>
|
||||
<Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
|
||||
<CardContent sx={{ p: 6 }}>
|
||||
{recentParticipants.map((participant, index) => (
|
||||
<Box key={index}>
|
||||
{index > 0 && <Divider sx={{ my: 2 }} />}
|
||||
{index > 0 && <Divider sx={{ my: 4 }} />}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3 }}>
|
||||
<Box
|
||||
sx={{
|
||||
width: 40,
|
||||
height: 40,
|
||||
width: 48,
|
||||
height: 48,
|
||||
borderRadius: '50%',
|
||||
bgcolor: 'grey.200',
|
||||
bgcolor: colors.purpleLight,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Person sx={{ color: 'text.secondary' }} />
|
||||
<Person sx={{ color: colors.purple, fontSize: 24 }} />
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
||||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 0.5 }}>
|
||||
{participant.name}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{participant.phone}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
{participant.time}
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user