mirror of
https://github.com/ktds-dg0501/kt-event-marketing-fe.git
synced 2025-12-06 08:16:23 +00:00
- 전체 간격(spacing) 2배 증가 및 컨테이너 패딩 확대 - 기본 폰트 크기 14px→16px 증가 - 채널별 성과 차트 색상을 조화로운 색상으로 변경 (#F472B6, #60A5FA, #FB923C) - 이벤트 목록 페이지에 통계 요약 카드 추가 (전체/진행중/참여자/평균ROI) - 진행중인 이벤트에 진행률 바 추가 - 이벤트 상태 배지 추가 (마감임박/인기/높은ROI/신규) - 상품 및 참여방법에 아이콘 추가 - 이벤트 카드 레이아웃 개선: 참여자/ROI 정보를 오른쪽 하단으로 재배치 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
756 lines
28 KiB
TypeScript
756 lines
28 KiB
TypeScript
'use client';
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import {
|
||
Box,
|
||
Container,
|
||
Typography,
|
||
Card,
|
||
CardContent,
|
||
Grid,
|
||
} from '@mui/material';
|
||
import {
|
||
PieChart as PieChartIcon,
|
||
ShowChart as ShowChartIcon,
|
||
Payments,
|
||
People,
|
||
} from '@mui/icons-material';
|
||
import {
|
||
Chart as ChartJS,
|
||
ArcElement,
|
||
CategoryScale,
|
||
LinearScale,
|
||
PointElement,
|
||
LineElement,
|
||
Title,
|
||
Tooltip,
|
||
Legend,
|
||
} from 'chart.js';
|
||
import { Pie, Line } from 'react-chartjs-2';
|
||
import Header from '@/shared/ui/Header';
|
||
import {
|
||
cardStyles,
|
||
colors,
|
||
responsiveText,
|
||
} from '@/shared/lib/button-styles';
|
||
|
||
// Chart.js 등록
|
||
ChartJS.register(
|
||
ArcElement,
|
||
CategoryScale,
|
||
LinearScale,
|
||
PointElement,
|
||
LineElement,
|
||
Title,
|
||
Tooltip,
|
||
Legend
|
||
);
|
||
|
||
// Mock 데이터
|
||
const mockAnalyticsData = {
|
||
summary: {
|
||
participants: 128,
|
||
participantsDelta: 12,
|
||
totalCost: 300000,
|
||
expectedRevenue: 1350000,
|
||
roi: 450,
|
||
targetRoi: 300,
|
||
},
|
||
channelPerformance: [
|
||
{ channel: '우리동네TV', participants: 58, percentage: 45, color: '#F472B6' },
|
||
{ channel: '링고비즈', participants: 38, percentage: 30, color: '#60A5FA' },
|
||
{ channel: 'SNS', participants: 32, percentage: 25, color: '#FB923C' },
|
||
],
|
||
timePerformance: {
|
||
peakTime: '오후 2-4시',
|
||
peakParticipants: 35,
|
||
avgPerHour: 8,
|
||
},
|
||
roiDetail: {
|
||
totalCost: 300000,
|
||
prizeCost: 250000,
|
||
channelCost: 50000,
|
||
expectedRevenue: 1350000,
|
||
salesIncrease: 1000000,
|
||
newCustomerLTV: 350000,
|
||
},
|
||
participantProfile: {
|
||
age: [
|
||
{ label: '20대', percentage: 35 },
|
||
{ label: '30대', percentage: 40 },
|
||
{ label: '40대', percentage: 25 },
|
||
],
|
||
gender: [
|
||
{ label: '여성', percentage: 60 },
|
||
{ label: '남성', percentage: 40 },
|
||
],
|
||
},
|
||
};
|
||
|
||
export default function AnalyticsPage() {
|
||
const [lastUpdate, setLastUpdate] = useState<Date>(new Date());
|
||
const [updateText, setUpdateText] = useState('방금 전');
|
||
|
||
useEffect(() => {
|
||
// 업데이트 시간 표시 갱신
|
||
const updateInterval = setInterval(() => {
|
||
const now = new Date();
|
||
const diff = Math.floor((now.getTime() - lastUpdate.getTime()) / 1000);
|
||
|
||
let text;
|
||
if (diff < 60) {
|
||
text = '방금 전';
|
||
} else if (diff < 3600) {
|
||
text = `${Math.floor(diff / 60)}분 전`;
|
||
} else {
|
||
text = `${Math.floor(diff / 3600)}시간 전`;
|
||
}
|
||
|
||
setUpdateText(text);
|
||
}, 30000); // 30초마다 갱신
|
||
|
||
// 5분마다 데이터 업데이트 시뮬레이션
|
||
const dataUpdateInterval = setInterval(() => {
|
||
setLastUpdate(new Date());
|
||
setUpdateText('방금 전');
|
||
}, 300000); // 5분
|
||
|
||
return () => {
|
||
clearInterval(updateInterval);
|
||
clearInterval(dataUpdateInterval);
|
||
};
|
||
}, [lastUpdate]);
|
||
|
||
const { summary, channelPerformance, timePerformance, roiDetail, participantProfile } =
|
||
mockAnalyticsData;
|
||
|
||
return (
|
||
<>
|
||
<Header title="성과 분석" showBack={true} showMenu={false} showProfile={true} />
|
||
<Box
|
||
sx={{
|
||
pt: { xs: 7, sm: 8 },
|
||
pb: 10,
|
||
bgcolor: colors.gray[50],
|
||
minHeight: '100vh',
|
||
}}
|
||
>
|
||
<Container maxWidth="lg" sx={{ pt: 8, pb: 6, px: { xs: 6, sm: 8, md: 10 } }}>
|
||
{/* Title with Real-time Indicator */}
|
||
<Box
|
||
sx={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'space-between',
|
||
mb: 10,
|
||
}}
|
||
>
|
||
<Typography variant="h5" sx={{ ...responsiveText.h3 }}>
|
||
📊 요약 (실시간)
|
||
</Typography>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
<Box
|
||
sx={{
|
||
width: 8,
|
||
height: 8,
|
||
borderRadius: '50%',
|
||
bgcolor: colors.mint,
|
||
animation: 'pulse 2s infinite',
|
||
'@keyframes pulse': {
|
||
'0%, 100%': { opacity: 1 },
|
||
'50%': { opacity: 0.3 },
|
||
},
|
||
}}
|
||
/>
|
||
<Typography variant="caption" sx={{ ...responsiveText.body2 }}>
|
||
{updateText}
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
|
||
{/* Summary KPI Cards */}
|
||
<Grid container spacing={6} sx={{ mb: 10 }}>
|
||
<Grid item xs={6} md={3}>
|
||
<Card
|
||
elevation={0}
|
||
sx={{
|
||
...cardStyles.default,
|
||
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
|
||
borderColor: 'transparent',
|
||
}}
|
||
>
|
||
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||
<Typography
|
||
variant="body2"
|
||
sx={{
|
||
mb: 2,
|
||
color: 'rgba(255, 255, 255, 0.9)',
|
||
fontWeight: 500,
|
||
fontSize: '1rem',
|
||
}}
|
||
>
|
||
참여자 수
|
||
</Typography>
|
||
<Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}>
|
||
{summary.participants}
|
||
</Typography>
|
||
<Typography
|
||
variant="caption"
|
||
sx={{ color: 'rgba(255, 255, 255, 0.8)', fontWeight: 600, fontSize: '0.875rem' }}
|
||
>
|
||
↑ {summary.participantsDelta}명 (오늘)
|
||
</Typography>
|
||
</CardContent>
|
||
</Card>
|
||
</Grid>
|
||
<Grid item xs={6} md={3}>
|
||
<Card
|
||
elevation={0}
|
||
sx={{
|
||
...cardStyles.default,
|
||
background: `linear-gradient(135deg, ${colors.orange} 0%, ${colors.orangeLight} 100%)`,
|
||
borderColor: 'transparent',
|
||
}}
|
||
>
|
||
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||
<Typography
|
||
variant="body2"
|
||
sx={{
|
||
mb: 2,
|
||
color: 'rgba(255, 255, 255, 0.9)',
|
||
fontWeight: 500,
|
||
fontSize: '1rem',
|
||
}}
|
||
>
|
||
총 비용
|
||
</Typography>
|
||
<Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}>
|
||
{Math.floor(summary.totalCost / 10000)}만
|
||
</Typography>
|
||
<Typography
|
||
variant="caption"
|
||
sx={{ color: 'rgba(255, 255, 255, 0.8)', fontWeight: 600, fontSize: '0.875rem' }}
|
||
>
|
||
경품 {Math.floor(roiDetail.prizeCost / 10000)}만 + 채널{' '}
|
||
{Math.floor(roiDetail.channelCost / 10000)}만
|
||
</Typography>
|
||
</CardContent>
|
||
</Card>
|
||
</Grid>
|
||
<Grid item xs={6} md={3}>
|
||
<Card
|
||
elevation={0}
|
||
sx={{
|
||
...cardStyles.default,
|
||
background: `linear-gradient(135deg, ${colors.mint} 0%, ${colors.mintLight} 100%)`,
|
||
borderColor: 'transparent',
|
||
}}
|
||
>
|
||
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||
<Typography
|
||
variant="body2"
|
||
sx={{
|
||
mb: 2,
|
||
color: 'rgba(255, 255, 255, 0.9)',
|
||
fontWeight: 500,
|
||
fontSize: '1rem',
|
||
}}
|
||
>
|
||
예상 수익
|
||
</Typography>
|
||
<Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}>
|
||
{Math.floor(summary.expectedRevenue / 10000)}만
|
||
</Typography>
|
||
<Typography
|
||
variant="caption"
|
||
sx={{ color: 'rgba(255, 255, 255, 0.8)', fontWeight: 600, fontSize: '0.875rem' }}
|
||
>
|
||
매출 {Math.floor(roiDetail.salesIncrease / 10000)}만 + LTV{' '}
|
||
{Math.floor(roiDetail.newCustomerLTV / 10000)}만
|
||
</Typography>
|
||
</CardContent>
|
||
</Card>
|
||
</Grid>
|
||
<Grid item xs={6} md={3}>
|
||
<Card
|
||
elevation={0}
|
||
sx={{
|
||
...cardStyles.default,
|
||
background: `linear-gradient(135deg, ${colors.blue} 0%, ${colors.blueLight} 100%)`,
|
||
borderColor: 'transparent',
|
||
}}
|
||
>
|
||
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||
<Typography
|
||
variant="body2"
|
||
sx={{
|
||
mb: 2,
|
||
color: 'rgba(255, 255, 255, 0.9)',
|
||
fontWeight: 500,
|
||
fontSize: '1rem',
|
||
}}
|
||
>
|
||
투자대비수익률
|
||
</Typography>
|
||
<Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}>
|
||
{summary.roi}%
|
||
</Typography>
|
||
<Typography
|
||
variant="caption"
|
||
sx={{ color: 'rgba(255, 255, 255, 0.8)', fontWeight: 600, fontSize: '0.875rem' }}
|
||
>
|
||
목표 {summary.targetRoi}% 달성
|
||
</Typography>
|
||
</CardContent>
|
||
</Card>
|
||
</Grid>
|
||
</Grid>
|
||
|
||
{/* Charts Grid */}
|
||
<Grid container spacing={6} sx={{ mb: 10 }}>
|
||
{/* Channel Performance */}
|
||
<Grid item xs={12} md={6}>
|
||
<Card elevation={0} sx={{ ...cardStyles.default, height: '100%' }}>
|
||
<CardContent sx={{ p: { xs: 6, sm: 8 }, height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 6 }}>
|
||
<Box
|
||
sx={{
|
||
width: 40,
|
||
height: 40,
|
||
borderRadius: '12px',
|
||
background: `linear-gradient(135deg, ${colors.pink} 0%, ${colors.pinkLight} 100%)`,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
}}
|
||
>
|
||
<PieChartIcon sx={{ fontSize: 24, color: 'white' }} />
|
||
</Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 700, color: colors.gray[900] }}>
|
||
채널별 성과
|
||
</Typography>
|
||
</Box>
|
||
|
||
{/* Pie Chart */}
|
||
<Box
|
||
sx={{
|
||
width: '100%',
|
||
maxWidth: 300,
|
||
mx: 'auto',
|
||
mb: 3,
|
||
flex: 1,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
}}
|
||
>
|
||
<Pie
|
||
data={{
|
||
labels: channelPerformance.map((item) => item.channel),
|
||
datasets: [
|
||
{
|
||
data: channelPerformance.map((item) => item.participants),
|
||
backgroundColor: channelPerformance.map((item) => item.color),
|
||
borderColor: '#fff',
|
||
borderWidth: 2,
|
||
},
|
||
],
|
||
}}
|
||
options={{
|
||
responsive: true,
|
||
maintainAspectRatio: true,
|
||
plugins: {
|
||
legend: {
|
||
display: false,
|
||
},
|
||
tooltip: {
|
||
callbacks: {
|
||
label: function (context) {
|
||
const label = context.label || '';
|
||
const value = context.parsed || 0;
|
||
const total = context.dataset.data.reduce((a: number, b: number) => a + b, 0);
|
||
const percentage = ((value / total) * 100).toFixed(1);
|
||
return `${label}: ${value}명 (${percentage}%)`;
|
||
},
|
||
},
|
||
},
|
||
},
|
||
}}
|
||
/>
|
||
</Box>
|
||
|
||
{/* Legend */}
|
||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||
{channelPerformance.map((item) => (
|
||
<Box
|
||
key={item.channel}
|
||
sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
|
||
>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
|
||
<Box
|
||
sx={{
|
||
width: 12,
|
||
height: 12,
|
||
borderRadius: '50%',
|
||
bgcolor: item.color,
|
||
}}
|
||
/>
|
||
<Typography variant="body2" sx={{ color: colors.gray[700] }}>
|
||
{item.channel}
|
||
</Typography>
|
||
</Box>
|
||
<Typography variant="body2" sx={{ fontWeight: 600, color: colors.gray[900] }}>
|
||
{item.percentage}% ({item.participants}명)
|
||
</Typography>
|
||
</Box>
|
||
))}
|
||
</Box>
|
||
</CardContent>
|
||
</Card>
|
||
</Grid>
|
||
|
||
{/* Time Trend */}
|
||
<Grid item xs={12} md={6}>
|
||
<Card elevation={0} sx={{ ...cardStyles.default, height: '100%' }}>
|
||
<CardContent sx={{ p: { xs: 6, sm: 8 }, height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 6 }}>
|
||
<Box
|
||
sx={{
|
||
width: 40,
|
||
height: 40,
|
||
borderRadius: '12px',
|
||
background: `linear-gradient(135deg, ${colors.blue} 0%, ${colors.blueLight} 100%)`,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
}}
|
||
>
|
||
<ShowChartIcon sx={{ fontSize: 24, color: 'white' }} />
|
||
</Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 700, color: colors.gray[900] }}>
|
||
시간대별 참여 추이
|
||
</Typography>
|
||
</Box>
|
||
|
||
{/* Line Chart */}
|
||
<Box
|
||
sx={{
|
||
width: '100%',
|
||
mb: 3,
|
||
flex: 1,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
minHeight: 200,
|
||
}}
|
||
>
|
||
<Line
|
||
data={{
|
||
labels: [
|
||
'00시',
|
||
'03시',
|
||
'06시',
|
||
'09시',
|
||
'12시',
|
||
'15시',
|
||
'18시',
|
||
'21시',
|
||
],
|
||
datasets: [
|
||
{
|
||
label: '참여자 수',
|
||
data: [3, 2, 5, 12, 28, 35, 22, 15],
|
||
borderColor: colors.blue,
|
||
backgroundColor: `${colors.blue}33`,
|
||
fill: true,
|
||
tension: 0.4,
|
||
pointBackgroundColor: colors.blue,
|
||
pointBorderColor: '#fff',
|
||
pointBorderWidth: 2,
|
||
pointRadius: 4,
|
||
pointHoverRadius: 6,
|
||
},
|
||
],
|
||
}}
|
||
options={{
|
||
responsive: true,
|
||
maintainAspectRatio: false,
|
||
plugins: {
|
||
legend: {
|
||
display: false,
|
||
},
|
||
tooltip: {
|
||
backgroundColor: colors.gray[900],
|
||
padding: 12,
|
||
displayColors: false,
|
||
callbacks: {
|
||
label: function (context) {
|
||
return `${context.parsed.y}명`;
|
||
},
|
||
},
|
||
},
|
||
},
|
||
scales: {
|
||
y: {
|
||
beginAtZero: true,
|
||
ticks: {
|
||
color: colors.gray[600],
|
||
},
|
||
grid: {
|
||
color: colors.gray[200],
|
||
},
|
||
},
|
||
x: {
|
||
ticks: {
|
||
color: colors.gray[600],
|
||
},
|
||
grid: {
|
||
display: false,
|
||
},
|
||
},
|
||
},
|
||
}}
|
||
/>
|
||
</Box>
|
||
|
||
{/* Stats */}
|
||
<Box>
|
||
<Typography variant="body2" sx={{ mb: 0.5, color: colors.gray[600] }}>
|
||
피크 시간: {timePerformance.peakTime} ({timePerformance.peakParticipants}명)
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ color: colors.gray[600] }}>
|
||
평균 시간당: {timePerformance.avgPerHour}명
|
||
</Typography>
|
||
</Box>
|
||
</CardContent>
|
||
</Card>
|
||
</Grid>
|
||
</Grid>
|
||
|
||
{/* ROI Detail & Participant Profile */}
|
||
<Grid container spacing={6}>
|
||
{/* ROI Detail */}
|
||
<Grid item xs={12} md={6}>
|
||
<Card elevation={0} sx={{ ...cardStyles.default, height: '100%' }}>
|
||
<CardContent sx={{ p: { xs: 6, sm: 8 }, height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 6 }}>
|
||
<Box
|
||
sx={{
|
||
width: 40,
|
||
height: 40,
|
||
borderRadius: '12px',
|
||
background: `linear-gradient(135deg, ${colors.orange} 0%, ${colors.orangeLight} 100%)`,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
}}
|
||
>
|
||
<Payments sx={{ fontSize: 24, color: 'white' }} />
|
||
</Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 700, color: colors.gray[900] }}>
|
||
투자대비수익률 상세
|
||
</Typography>
|
||
</Box>
|
||
|
||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||
<Box>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 1.5, color: colors.gray[900] }}>
|
||
총 비용: {Math.floor(roiDetail.totalCost / 10000)}만원
|
||
</Typography>
|
||
<Box sx={{ pl: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||
<Typography variant="body2" sx={{ color: colors.gray[600] }}>
|
||
• 경품 비용
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600, color: colors.gray[900] }}>
|
||
{Math.floor(roiDetail.prizeCost / 10000)}만원
|
||
</Typography>
|
||
</Box>
|
||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||
<Typography variant="body2" sx={{ color: colors.gray[600] }}>
|
||
• 채널 비용
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600, color: colors.gray[900] }}>
|
||
{Math.floor(roiDetail.channelCost / 10000)}만원
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Box>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 1.5, color: colors.gray[900] }}>
|
||
예상 수익: {Math.floor(roiDetail.expectedRevenue / 10000)}만원
|
||
</Typography>
|
||
<Box sx={{ pl: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||
<Typography variant="body2" sx={{ color: colors.gray[600] }}>
|
||
• 매출 증가
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600, color: colors.mint }}>
|
||
{Math.floor(roiDetail.salesIncrease / 10000)}만원
|
||
</Typography>
|
||
</Box>
|
||
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
|
||
<Typography variant="body2" sx={{ color: colors.gray[600] }}>
|
||
• 신규 고객 LTV
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ fontWeight: 600, color: colors.mint }}>
|
||
{Math.floor(roiDetail.newCustomerLTV / 10000)}만원
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
</Box>
|
||
|
||
<Box>
|
||
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 1.5, color: colors.mint }}>
|
||
투자대비수익률
|
||
</Typography>
|
||
<Box
|
||
sx={{
|
||
p: 2.5,
|
||
bgcolor: colors.gray[100],
|
||
borderRadius: 2,
|
||
textAlign: 'center',
|
||
}}
|
||
>
|
||
<Typography variant="body2" sx={{ mb: 0.5, color: colors.gray[700] }}>
|
||
(수익 - 비용) ÷ 비용 × 100
|
||
</Typography>
|
||
<Typography variant="body2" sx={{ mb: 1.5, color: colors.gray[600] }}>
|
||
({Math.floor(roiDetail.expectedRevenue / 10000)}만 -{' '}
|
||
{Math.floor(roiDetail.totalCost / 10000)}만) ÷{' '}
|
||
{Math.floor(roiDetail.totalCost / 10000)}만 × 100
|
||
</Typography>
|
||
<Typography variant="h5" sx={{ fontWeight: 700, color: colors.mint }}>
|
||
= {summary.roi}%
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
</Box>
|
||
</CardContent>
|
||
</Card>
|
||
</Grid>
|
||
|
||
{/* Participant Profile */}
|
||
<Grid item xs={12} md={6}>
|
||
<Card elevation={0} sx={{ ...cardStyles.default, height: '100%' }}>
|
||
<CardContent sx={{ p: { xs: 6, sm: 8 }, height: '100%', display: 'flex', flexDirection: 'column' }}>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 6 }}>
|
||
<Box
|
||
sx={{
|
||
width: 40,
|
||
height: 40,
|
||
borderRadius: '12px',
|
||
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
}}
|
||
>
|
||
<People sx={{ fontSize: 24, color: 'white' }} />
|
||
</Box>
|
||
<Typography variant="h6" sx={{ fontWeight: 700, color: colors.gray[900] }}>
|
||
참여자 프로필
|
||
</Typography>
|
||
</Box>
|
||
|
||
{/* Age Distribution */}
|
||
<Box sx={{ mb: 4 }}>
|
||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2, color: colors.gray[900] }}>
|
||
연령별
|
||
</Typography>
|
||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||
{participantProfile.age.map((item) => (
|
||
<Box key={item.label}>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||
<Typography variant="body2" sx={{ minWidth: 60, color: colors.gray[700] }}>
|
||
{item.label}
|
||
</Typography>
|
||
<Box
|
||
sx={{
|
||
flex: 1,
|
||
height: 28,
|
||
bgcolor: colors.gray[200],
|
||
borderRadius: 1.5,
|
||
overflow: 'hidden',
|
||
}}
|
||
>
|
||
<Box
|
||
sx={{
|
||
width: `${item.percentage}%`,
|
||
height: '100%',
|
||
background: `linear-gradient(135deg, ${colors.blue} 0%, ${colors.blueLight} 100%)`,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'flex-end',
|
||
pr: 1.5,
|
||
}}
|
||
>
|
||
<Typography
|
||
variant="caption"
|
||
sx={{ color: 'white', fontWeight: 600, fontSize: 12 }}
|
||
>
|
||
{item.percentage}%
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
</Box>
|
||
</Box>
|
||
))}
|
||
</Box>
|
||
</Box>
|
||
|
||
{/* Gender Distribution */}
|
||
<Box>
|
||
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2, color: colors.gray[900] }}>
|
||
성별
|
||
</Typography>
|
||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
|
||
{participantProfile.gender.map((item) => (
|
||
<Box key={item.label}>
|
||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
|
||
<Typography variant="body2" sx={{ minWidth: 60, color: colors.gray[700] }}>
|
||
{item.label}
|
||
</Typography>
|
||
<Box
|
||
sx={{
|
||
flex: 1,
|
||
height: 28,
|
||
bgcolor: colors.gray[200],
|
||
borderRadius: 1.5,
|
||
overflow: 'hidden',
|
||
}}
|
||
>
|
||
<Box
|
||
sx={{
|
||
width: `${item.percentage}%`,
|
||
height: '100%',
|
||
background: `linear-gradient(135deg, ${colors.pink} 0%, ${colors.pinkLight} 100%)`,
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'flex-end',
|
||
pr: 1.5,
|
||
}}
|
||
>
|
||
<Typography
|
||
variant="caption"
|
||
sx={{ color: 'white', fontWeight: 600, fontSize: 12 }}
|
||
>
|
||
{item.percentage}%
|
||
</Typography>
|
||
</Box>
|
||
</Box>
|
||
</Box>
|
||
</Box>
|
||
))}
|
||
</Box>
|
||
</Box>
|
||
</CardContent>
|
||
</Card>
|
||
</Grid>
|
||
</Grid>
|
||
</Container>
|
||
</Box>
|
||
</>
|
||
);
|
||
}
|