Merge pull request #2 from ktds-dg0501/develop

Develop
This commit is contained in:
Cherry Kim 2025-10-27 16:20:12 +09:00 committed by GitHub
commit 9cbf89b9ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 3955 additions and 1661 deletions

25
package-lock.json generated
View File

@ -18,11 +18,11 @@
"@use-funnel/browser": "^0.0.12", "@use-funnel/browser": "^0.0.12",
"@use-funnel/next": "^0.0.12", "@use-funnel/next": "^0.0.12",
"axios": "^1.7.7", "axios": "^1.7.7",
"chart.js": "^4.4.6", "chart.js": "^4.5.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"next": "^14.2.15", "next": "^14.2.15",
"react": "^18.3.1", "react": "^18.3.1",
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.3.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.53.0", "react-hook-form": "^7.53.0",
"zod": "^3.23.8", "zod": "^3.23.8",
@ -30,6 +30,7 @@
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.48.0", "@playwright/test": "^1.48.0",
"@types/chart.js": "^2.9.41",
"@types/node": "^22.7.5", "@types/node": "^22.7.5",
"@types/react": "^18.3.11", "@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
@ -1215,6 +1216,16 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@types/chart.js": {
"version": "2.9.41",
"resolved": "https://registry.npmjs.org/@types/chart.js/-/chart.js-2.9.41.tgz",
"integrity": "sha512-3dvkDvueckY83UyUXtJMalYoH6faOLkWQoaTlJgB4Djde3oORmNP0Jw85HtzTuXyliUHcdp704s0mZFQKio/KQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"moment": "^2.10.2"
}
},
"node_modules/@types/json5": { "node_modules/@types/json5": {
"version": "0.0.29", "version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -4684,6 +4695,16 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/moment": {
"version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",

View File

@ -20,11 +20,11 @@
"@use-funnel/browser": "^0.0.12", "@use-funnel/browser": "^0.0.12",
"@use-funnel/next": "^0.0.12", "@use-funnel/next": "^0.0.12",
"axios": "^1.7.7", "axios": "^1.7.7",
"chart.js": "^4.4.6", "chart.js": "^4.5.1",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",
"next": "^14.2.15", "next": "^14.2.15",
"react": "^18.3.1", "react": "^18.3.1",
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.3.0",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-hook-form": "^7.53.0", "react-hook-form": "^7.53.0",
"zod": "^3.23.8", "zod": "^3.23.8",
@ -32,6 +32,7 @@
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.48.0", "@playwright/test": "^1.48.0",
"@types/chart.js": "^2.9.41",
"@types/node": "^22.7.5", "@types/node": "^22.7.5",
"@types/react": "^18.3.11", "@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",

View File

@ -10,12 +10,41 @@ import {
Grid, Grid,
} from '@mui/material'; } from '@mui/material';
import { import {
PieChart, PieChart as PieChartIcon,
ShowChart, ShowChart as ShowChartIcon,
Payments, Payments,
People, People,
} from '@mui/icons-material'; } 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 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 데이터 // Mock 데이터
const mockAnalyticsData = { const mockAnalyticsData = {
@ -28,9 +57,9 @@ const mockAnalyticsData = {
targetRoi: 300, targetRoi: 300,
}, },
channelPerformance: [ channelPerformance: [
{ channel: '우리동네TV', participants: 58, percentage: 45, color: '#E31E24' }, { channel: '우리동네TV', participants: 58, percentage: 45, color: '#F472B6' },
{ channel: '링고비즈', participants: 38, percentage: 30, color: '#0066FF' }, { channel: '링고비즈', participants: 38, percentage: 30, color: '#60A5FA' },
{ channel: 'SNS', participants: 32, percentage: 25, color: '#FFB800' }, { channel: 'SNS', participants: 32, percentage: 25, color: '#FB923C' },
], ],
timePerformance: { timePerformance: {
peakTime: '오후 2-4시', peakTime: '오후 2-4시',
@ -98,18 +127,25 @@ export default function AnalyticsPage() {
return ( return (
<> <>
<Header title="성과 분석" showBack={true} showMenu={false} showProfile={true} /> <Header title="성과 분석" showBack={true} showMenu={false} showProfile={true} />
<Box sx={{ pt: { xs: 7, sm: 8 }, minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}> <Box
<Container maxWidth="lg" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}> 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 */} {/* Title with Real-time Indicator */}
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
mb: 4, mb: 10,
}} }}
> >
<Typography variant="h5" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ ...responsiveText.h3 }}>
📊 () 📊 ()
</Typography> </Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
@ -118,7 +154,7 @@ export default function AnalyticsPage() {
width: 8, width: 8,
height: 8, height: 8,
borderRadius: '50%', borderRadius: '50%',
bgcolor: 'success.main', bgcolor: colors.mint,
animation: 'pulse 2s infinite', animation: 'pulse 2s infinite',
'@keyframes pulse': { '@keyframes pulse': {
'0%, 100%': { opacity: 1 }, '0%, 100%': { opacity: 1 },
@ -126,39 +162,75 @@ export default function AnalyticsPage() {
}, },
}} }}
/> />
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" sx={{ ...responsiveText.body2 }}>
{updateText} {updateText}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
{/* Summary KPI Cards */} {/* Summary KPI Cards */}
<Grid container spacing={3} sx={{ mb: 5 }}> <Grid container spacing={6} sx={{ mb: 10 }}>
<Grid item xs={6} md={3}> <Grid item xs={6} md={3}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card
<CardContent sx={{ textAlign: 'center', py: 3, px: 2 }}> elevation={0}
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 600 }}> 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>
<Typography variant="h4" sx={{ fontWeight: 700, my: 1 }}> <Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}>
{summary.participants} {summary.participants}
</Typography> </Typography>
<Typography variant="caption" color="success.main" sx={{ fontWeight: 600 }}> <Typography
variant="caption"
sx={{ color: 'rgba(255, 255, 255, 0.8)', fontWeight: 600, fontSize: '0.875rem' }}
>
{summary.participantsDelta} () {summary.participantsDelta} ()
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Grid>
<Grid item xs={6} md={3}> <Grid item xs={6} md={3}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card
<CardContent sx={{ textAlign: 'center', py: 3, px: 2 }}> elevation={0}
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 600 }}> 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>
<Typography variant="h4" sx={{ fontWeight: 700, my: 1 }}> <Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}>
{Math.floor(summary.totalCost / 10000)} {Math.floor(summary.totalCost / 10000)}
</Typography> </Typography>
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 600 }}> <Typography
variant="caption"
sx={{ color: 'rgba(255, 255, 255, 0.8)', fontWeight: 600, fontSize: '0.875rem' }}
>
{Math.floor(roiDetail.prizeCost / 10000)} + {' '} {Math.floor(roiDetail.prizeCost / 10000)} + {' '}
{Math.floor(roiDetail.channelCost / 10000)} {Math.floor(roiDetail.channelCost / 10000)}
</Typography> </Typography>
@ -166,31 +238,67 @@ export default function AnalyticsPage() {
</Card> </Card>
</Grid> </Grid>
<Grid item xs={6} md={3}> <Grid item xs={6} md={3}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card
<CardContent sx={{ textAlign: 'center', py: 3, px: 2 }}> elevation={0}
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 600 }}> 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>
<Typography variant="h4" sx={{ fontWeight: 700, my: 1, color: 'success.main' }}> <Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}>
{Math.floor(summary.expectedRevenue / 10000)} {Math.floor(summary.expectedRevenue / 10000)}
</Typography> </Typography>
<Typography variant="caption" color="success.main" sx={{ fontWeight: 600 }}> <Typography
{Math.floor(roiDetail.salesIncrease / 10000)} + LTV{' '} 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)} {Math.floor(roiDetail.newCustomerLTV / 10000)}
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Grid>
<Grid item xs={6} md={3}> <Grid item xs={6} md={3}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card
<CardContent sx={{ textAlign: 'center', py: 3, px: 2 }}> elevation={0}
<Typography variant="caption" color="text.secondary" sx={{ fontWeight: 600 }}> 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>
<Typography variant="h4" sx={{ fontWeight: 700, my: 1, color: 'success.main' }}> <Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}>
{summary.roi}% {summary.roi}%
</Typography> </Typography>
<Typography variant="caption" color="success.main" sx={{ fontWeight: 600 }}> <Typography
variant="caption"
sx={{ color: 'rgba(255, 255, 255, 0.8)', fontWeight: 600, fontSize: '0.875rem' }}
>
{summary.targetRoi}% {summary.targetRoi}%
</Typography> </Typography>
</CardContent> </CardContent>
@ -199,39 +307,76 @@ export default function AnalyticsPage() {
</Grid> </Grid>
{/* Charts Grid */} {/* Charts Grid */}
<Grid container spacing={4} sx={{ mb: 5 }}> <Grid container spacing={6} sx={{ mb: 10 }}>
{/* Channel Performance */} {/* Channel Performance */}
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card elevation={0} sx={{ ...cardStyles.default, height: '100%' }}>
<CardContent sx={{ p: 4 }}> <CardContent sx={{ p: { xs: 6, sm: 8 }, height: '100%', display: 'flex', flexDirection: 'column' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 6 }}>
<PieChart color="error" /> <Box
<Typography variant="h6" sx={{ fontWeight: 700 }}> 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> </Typography>
</Box> </Box>
{/* Chart Placeholder */} {/* Pie Chart */}
<Box <Box
sx={{ sx={{
width: '100%', width: '100%',
aspectRatio: '1 / 1', maxWidth: 300,
bgcolor: 'grey.100', mx: 'auto',
borderRadius: 3, mb: 3,
flex: 1,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
flexDirection: 'column',
p: 4,
mb: 3,
}} }}
> >
<span className="material-icons" style={{ fontSize: 64, color: '#bdbdbd' }}> <Pie
donut_large data={{
</span> labels: channelPerformance.map((item) => item.channel),
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}> datasets: [
{
</Typography> 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> </Box>
{/* Legend */} {/* Legend */}
@ -250,9 +395,11 @@ export default function AnalyticsPage() {
bgcolor: item.color, bgcolor: item.color,
}} }}
/> />
<Typography variant="body2">{item.channel}</Typography> <Typography variant="body2" sx={{ color: colors.gray[700] }}>
{item.channel}
</Typography>
</Box> </Box>
<Typography variant="body2" sx={{ fontWeight: 600 }}> <Typography variant="body2" sx={{ fontWeight: 600, color: colors.gray[900] }}>
{item.percentage}% ({item.participants}) {item.percentage}% ({item.participants})
</Typography> </Typography>
</Box> </Box>
@ -264,44 +411,113 @@ export default function AnalyticsPage() {
{/* Time Trend */} {/* Time Trend */}
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card elevation={0} sx={{ ...cardStyles.default, height: '100%' }}>
<CardContent sx={{ p: 4 }}> <CardContent sx={{ p: { xs: 6, sm: 8 }, height: '100%', display: 'flex', flexDirection: 'column' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 6 }}>
<ShowChart color="error" /> <Box
<Typography variant="h6" sx={{ fontWeight: 700 }}> 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> </Typography>
</Box> </Box>
{/* Chart Placeholder */} {/* Line Chart */}
<Box <Box
sx={{ sx={{
width: '100%', width: '100%',
aspectRatio: '16 / 9', mb: 3,
bgcolor: 'grey.100', flex: 1,
borderRadius: 3,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', minHeight: 200,
flexDirection: 'column',
p: 4,
mb: 3,
}} }}
> >
<span className="material-icons" style={{ fontSize: 64, color: '#bdbdbd' }}> <Line
trending_up data={{
</span> labels: [
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}> '00시',
'03시',
</Typography> '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> </Box>
{/* Stats */} {/* Stats */}
<Box> <Box>
<Typography variant="body2" color="text.secondary" sx={{ mb: 0.5 }}> <Typography variant="body2" sx={{ mb: 0.5, color: colors.gray[600] }}>
: {timePerformance.peakTime} ({timePerformance.peakParticipants}) : {timePerformance.peakTime} ({timePerformance.peakParticipants})
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" sx={{ color: colors.gray[600] }}>
: {timePerformance.avgPerHour} : {timePerformance.avgPerHour}
</Typography> </Typography>
</Box> </Box>
@ -311,37 +527,49 @@ export default function AnalyticsPage() {
</Grid> </Grid>
{/* ROI Detail & Participant Profile */} {/* ROI Detail & Participant Profile */}
<Grid container spacing={4} sx={{ mb: 5 }}> <Grid container spacing={6}>
{/* ROI Detail */} {/* ROI Detail */}
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card elevation={0} sx={{ ...cardStyles.default, height: '100%' }}>
<CardContent sx={{ p: 4 }}> <CardContent sx={{ p: { xs: 6, sm: 8 }, height: '100%', display: 'flex', flexDirection: 'column' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 6 }}>
<Payments color="error" /> <Box
<Typography variant="h6" sx={{ fontWeight: 700 }}> 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> </Typography>
</Box> </Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<Box> <Box>
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 1.5, color: colors.gray[900] }}>
: {Math.floor(roiDetail.totalCost / 10000)} : {Math.floor(roiDetail.totalCost / 10000)}
</Typography> </Typography>
<Box sx={{ pl: 2, display: 'flex', flexDirection: 'column', gap: 0.5 }}> <Box sx={{ pl: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" sx={{ color: colors.gray[600] }}>
</Typography> </Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}> <Typography variant="body2" sx={{ fontWeight: 600, color: colors.gray[900] }}>
{Math.floor(roiDetail.prizeCost / 10000)} {Math.floor(roiDetail.prizeCost / 10000)}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" sx={{ color: colors.gray[600] }}>
</Typography> </Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}> <Typography variant="body2" sx={{ fontWeight: 600, color: colors.gray[900] }}>
{Math.floor(roiDetail.channelCost / 10000)} {Math.floor(roiDetail.channelCost / 10000)}
</Typography> </Typography>
</Box> </Box>
@ -349,23 +577,23 @@ export default function AnalyticsPage() {
</Box> </Box>
<Box> <Box>
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 1.5, color: colors.gray[900] }}>
: {Math.floor(roiDetail.expectedRevenue / 10000)} : {Math.floor(roiDetail.expectedRevenue / 10000)}
</Typography> </Typography>
<Box sx={{ pl: 2, display: 'flex', flexDirection: 'column', gap: 0.5 }}> <Box sx={{ pl: 2, display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" sx={{ color: colors.gray[600] }}>
</Typography> </Typography>
<Typography variant="body2" sx={{ fontWeight: 600, color: 'success.main' }}> <Typography variant="body2" sx={{ fontWeight: 600, color: colors.mint }}>
{Math.floor(roiDetail.salesIncrease / 10000)} {Math.floor(roiDetail.salesIncrease / 10000)}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" sx={{ color: colors.gray[600] }}>
LTV LTV
</Typography> </Typography>
<Typography variant="body2" sx={{ fontWeight: 600, color: 'success.main' }}> <Typography variant="body2" sx={{ fontWeight: 600, color: colors.mint }}>
{Math.floor(roiDetail.newCustomerLTV / 10000)} {Math.floor(roiDetail.newCustomerLTV / 10000)}
</Typography> </Typography>
</Box> </Box>
@ -373,26 +601,26 @@ export default function AnalyticsPage() {
</Box> </Box>
<Box> <Box>
<Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 1, color: 'success.main' }}> <Typography variant="subtitle1" sx={{ fontWeight: 700, mb: 1.5, color: colors.mint }}>
</Typography> </Typography>
<Box <Box
sx={{ sx={{
p: 2, p: 2.5,
bgcolor: 'grey.100', bgcolor: colors.gray[100],
borderRadius: 2, borderRadius: 2,
textAlign: 'center', textAlign: 'center',
}} }}
> >
<Typography variant="body2" sx={{ mb: 0.5 }}> <Typography variant="body2" sx={{ mb: 0.5, color: colors.gray[700] }}>
( - ) ÷ × 100 ( - ) ÷ × 100
</Typography> </Typography>
<Typography variant="body2" sx={{ mb: 1 }}> <Typography variant="body2" sx={{ mb: 1.5, color: colors.gray[600] }}>
({Math.floor(roiDetail.expectedRevenue / 10000)} -{' '} ({Math.floor(roiDetail.expectedRevenue / 10000)} -{' '}
{Math.floor(roiDetail.totalCost / 10000)}) ÷{' '} {Math.floor(roiDetail.totalCost / 10000)}) ÷{' '}
{Math.floor(roiDetail.totalCost / 10000)} × 100 {Math.floor(roiDetail.totalCost / 10000)} × 100
</Typography> </Typography>
<Typography variant="h5" sx={{ fontWeight: 700, color: 'success.main' }}> <Typography variant="h5" sx={{ fontWeight: 700, color: colors.mint }}>
= {summary.roi}% = {summary.roi}%
</Typography> </Typography>
</Box> </Box>
@ -404,33 +632,45 @@ export default function AnalyticsPage() {
{/* Participant Profile */} {/* Participant Profile */}
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card elevation={0} sx={{ ...cardStyles.default, height: '100%' }}>
<CardContent sx={{ p: 4 }}> <CardContent sx={{ p: { xs: 6, sm: 8 }, height: '100%', display: 'flex', flexDirection: 'column' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 6 }}>
<People color="error" /> <Box
<Typography variant="h6" sx={{ fontWeight: 700 }}> 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> </Typography>
</Box> </Box>
{/* Age Distribution */} {/* Age Distribution */}
<Box sx={{ mb: 4 }}> <Box sx={{ mb: 4 }}>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2 }}> <Typography variant="body1" sx={{ fontWeight: 600, mb: 2, color: colors.gray[900] }}>
</Typography> </Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
{participantProfile.age.map((item) => ( {participantProfile.age.map((item) => (
<Box key={item.label}> <Box key={item.label}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Typography variant="body2" sx={{ minWidth: 60 }}> <Typography variant="body2" sx={{ minWidth: 60, color: colors.gray[700] }}>
{item.label} {item.label}
</Typography> </Typography>
<Box <Box
sx={{ sx={{
flex: 1, flex: 1,
height: 24, height: 28,
bgcolor: 'grey.200', bgcolor: colors.gray[200],
borderRadius: 1, borderRadius: 1.5,
overflow: 'hidden', overflow: 'hidden',
}} }}
> >
@ -438,11 +678,11 @@ export default function AnalyticsPage() {
sx={{ sx={{
width: `${item.percentage}%`, width: `${item.percentage}%`,
height: '100%', height: '100%',
bgcolor: 'info.main', background: `linear-gradient(135deg, ${colors.blue} 0%, ${colors.blueLight} 100%)`,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'flex-end', justifyContent: 'flex-end',
pr: 1, pr: 1.5,
}} }}
> >
<Typography <Typography
@ -461,22 +701,22 @@ export default function AnalyticsPage() {
{/* Gender Distribution */} {/* Gender Distribution */}
<Box> <Box>
<Typography variant="body1" sx={{ fontWeight: 600, mb: 2 }}> <Typography variant="body1" sx={{ fontWeight: 600, mb: 2, color: colors.gray[900] }}>
</Typography> </Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
{participantProfile.gender.map((item) => ( {participantProfile.gender.map((item) => (
<Box key={item.label}> <Box key={item.label}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Typography variant="body2" sx={{ minWidth: 60 }}> <Typography variant="body2" sx={{ minWidth: 60, color: colors.gray[700] }}>
{item.label} {item.label}
</Typography> </Typography>
<Box <Box
sx={{ sx={{
flex: 1, flex: 1,
height: 24, height: 28,
bgcolor: 'grey.200', bgcolor: colors.gray[200],
borderRadius: 1, borderRadius: 1.5,
overflow: 'hidden', overflow: 'hidden',
}} }}
> >
@ -484,11 +724,11 @@ export default function AnalyticsPage() {
sx={{ sx={{
width: `${item.percentage}%`, width: `${item.percentage}%`,
height: '100%', height: '100%',
bgcolor: 'error.main', background: `linear-gradient(135deg, ${colors.pink} 0%, ${colors.pinkLight} 100%)`,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'flex-end', justifyContent: 'flex-end',
pr: 1, pr: 1.5,
}} }}
> >
<Typography <Typography

View File

@ -29,8 +29,27 @@ import {
Add, Add,
Remove, Remove,
Info, Info,
People,
} from '@mui/icons-material'; } from '@mui/icons-material';
// 디자인 시스템 색상
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 데이터 // Mock 데이터
const mockEventData = { const mockEventData = {
name: '신규고객 유치 이벤트', name: '신규고객 유치 이벤트',
@ -163,55 +182,77 @@ export default function DrawPage() {
}; };
return ( return (
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 4 }}> <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 } }}>
{/* Setup View (Before Drawing) */} {/* Setup View (Before Drawing) */}
{!showResults && ( {!showResults && (
<> <>
{/* Event Info */} {/* Page Header */}
<Card sx={{ mb: 3, borderRadius: 3 }}> <Box sx={{ mb: 8 }}>
<CardContent> <Typography variant="h4" sx={{ fontWeight: 700, fontSize: '2rem', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}> 🎲
<EventNote color="error" /> </Typography>
<Typography variant="h6" sx={{ fontWeight: 700 }}> <Typography variant="body1" color="text.secondary">
</Typography> </Typography>
</Box> </Box>
<Box sx={{ mb: 2 }}>
<Typography variant="caption" color="text.secondary"> {/* Event Info Summary Cards */}
<Grid container spacing={6} sx={{ mb: 10 }}>
<Grid item xs={6} md={6}>
<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 }}>
<EventNote sx={{ fontSize: 40, mb: 2, color: 'white' }} />
<Typography variant="caption" display="block" sx={{ mb: 2, color: 'rgba(255,255,255,0.9)' }}>
</Typography> </Typography>
<Typography variant="body1">{mockEventData.name}</Typography> <Typography variant="h6" sx={{ fontWeight: 700, color: 'white' }}>
</Box> {mockEventData.name}
<Box sx={{ mb: 2 }}>
<Typography variant="caption" color="text.secondary">
</Typography> </Typography>
<Typography variant="h6" sx={{ fontWeight: 700 }}>
{mockEventData.totalParticipants}
</Typography>
</Box>
<Box>
<Typography variant="caption" color="text.secondary">
</Typography>
<Typography variant="body1"> </Typography>
</Box>
</CardContent> </CardContent>
</Card> </Card>
</Grid>
<Grid item xs={6} md={6}>
<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 }}>
<People 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="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
{mockEventData.totalParticipants}
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
{/* Drawing Settings */} {/* Drawing Settings */}
<Card sx={{ mb: 3, borderRadius: 3 }}> <Card elevation={0} sx={{ mb: 10, borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
<CardContent> <CardContent sx={{ p: 6 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 6 }}>
<Tune color="error" /> <Tune sx={{ fontSize: 32, color: colors.pink }} />
<Typography variant="h6" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ fontWeight: 700, fontSize: '1.5rem' }}>
</Typography> </Typography>
</Box> </Box>
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 6 }}>
<Typography variant="body2" sx={{ mb: 2 }}> <Typography variant="h6" sx={{ mb: 4, fontWeight: 600 }}>
</Typography> </Typography>
<Box <Box
@ -219,33 +260,44 @@ export default function DrawPage() {
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
gap: 2, gap: 4,
p: 4,
bgcolor: colors.gray[100],
borderRadius: 3,
}} }}
> >
<IconButton <IconButton
onClick={handleDecrease} onClick={handleDecrease}
sx={{ sx={{
width: 48, width: 60,
height: 48, height: 60,
border: '1px solid', border: '2px solid',
borderColor: 'divider', borderColor: colors.purple,
color: colors.purple,
'&:hover': {
bgcolor: colors.purpleLight,
},
}} }}
> >
<Remove /> <Remove sx={{ fontSize: 28 }} />
</IconButton> </IconButton>
<Typography variant="h3" sx={{ fontWeight: 600, width: 80, textAlign: 'center' }}> <Typography variant="h2" sx={{ fontWeight: 700, width: 120, textAlign: 'center', color: colors.purple }}>
{winnerCount} {winnerCount}
</Typography> </Typography>
<IconButton <IconButton
onClick={handleIncrease} onClick={handleIncrease}
sx={{ sx={{
width: 48, width: 60,
height: 48, height: 60,
border: '1px solid', border: '2px solid',
borderColor: 'divider', borderColor: colors.purple,
color: colors.purple,
'&:hover': {
bgcolor: colors.purpleLight,
},
}} }}
> >
<Add /> <Add sx={{ fontSize: 28 }} />
</IconButton> </IconButton>
</Box> </Box>
</Box> </Box>
@ -255,21 +307,42 @@ export default function DrawPage() {
<Checkbox <Checkbox
checked={storeBonus} checked={storeBonus}
onChange={(e) => setStoreBonus(e.target.checked)} onChange={(e) => setStoreBonus(e.target.checked)}
sx={{
color: colors.purple,
'&.Mui-checked': {
color: colors.purple,
},
}}
/> />
} }
label="매장 방문 고객 가산점 (가중치: 1.5배)" label={
sx={{ mb: 3 }} <Typography variant="body1" sx={{ fontWeight: 600 }}>
(가중치: 1.5배)
</Typography>
}
sx={{ mb: 6 }}
/> />
<Box sx={{ bgcolor: 'background.default', p: 2, borderRadius: 2 }}> <Box
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 1 }}> sx={{
<Info sx={{ fontSize: 16 }} color="action" /> bgcolor: colors.purpleLight,
<Typography variant="body2" color="text.secondary"> p: 4,
borderRadius: 3,
border: `1px solid ${colors.purple}40`,
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 3 }}>
<Info sx={{ fontSize: 24, color: colors.purple }} />
<Typography variant="h6" sx={{ fontWeight: 600, color: colors.purple }}>
</Typography> </Typography>
</Box> </Box>
<Typography variant="body2"> </Typography> <Typography variant="body1" sx={{ mb: 2, color: colors.gray[700] }}>
<Typography variant="body2"> </Typography>
</Typography>
<Typography variant="body1" sx={{ color: colors.gray[700] }}>
</Typography>
</Box> </Box>
</CardContent> </CardContent>
</Card> </Card>
@ -279,45 +352,75 @@ export default function DrawPage() {
fullWidth fullWidth
variant="contained" variant="contained"
size="large" size="large"
startIcon={<Casino />} startIcon={<Casino sx={{ fontSize: 28 }} />}
onClick={handleStartDrawing} onClick={handleStartDrawing}
sx={{ mb: 3, py: 1.5, borderRadius: 2, fontWeight: 700, fontSize: '1rem' }} sx={{
mb: 10,
py: 3,
borderRadius: 4,
fontWeight: 700,
fontSize: '1.25rem',
background: `linear-gradient(135deg, ${colors.pink} 0%, ${colors.purple} 100%)`,
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
'&:hover': {
background: `linear-gradient(135deg, ${colors.pink} 0%, ${colors.purple} 100%)`,
boxShadow: '0 6px 16px rgba(0, 0, 0, 0.2)',
transform: 'translateY(-2px)',
},
}}
> >
</Button> </Button>
{/* Drawing History */} {/* Drawing History */}
<Box> <Box>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 2 }}> <Typography variant="h5" sx={{ fontWeight: 700, mb: 6, fontSize: '1.5rem' }}>
📜 ( 3) 📜
</Typography> </Typography>
{mockDrawingHistory.length === 0 ? ( {mockDrawingHistory.length === 0 ? (
<Card sx={{ borderRadius: 3 }}> <Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
<CardContent sx={{ textAlign: 'center', py: 4 }}> <CardContent sx={{ textAlign: 'center', py: 8 }}>
<Typography variant="body1" color="text.secondary"> <ListIcon sx={{ fontSize: 64, color: colors.gray[300], mb: 2 }} />
<Typography variant="h6" color="text.secondary">
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
) : ( ) : (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{mockDrawingHistory.slice(0, 3).map((history, index) => ( {mockDrawingHistory.slice(0, 3).map((history, index) => (
<Card key={index} sx={{ borderRadius: 2 }}> <Card
<CardContent> key={index}
elevation={0}
sx={{
borderRadius: 4,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
}}
>
<CardContent sx={{ p: 5 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Box> <Box>
<Typography variant="body1" sx={{ mb: 0.5 }}> <Typography variant="h6" sx={{ mb: 2, fontWeight: 700 }}>
{history.date} {history.isRedraw && '(재추첨)'} {history.date} {history.isRedraw && '(재추첨)'}
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body1" color="text.secondary">
{history.winnerCount} {history.winnerCount}
</Typography> </Typography>
</Box> </Box>
<Button <Button
variant="text" variant="outlined"
size="small" size="medium"
onClick={() => handleHistoryDetail(history)} onClick={() => handleHistoryDetail(history)}
startIcon={<ListIcon />} startIcon={<ListIcon />}
sx={{
borderRadius: 3,
borderColor: colors.purple,
color: colors.purple,
'&:hover': {
borderColor: colors.purple,
bgcolor: colors.purpleLight,
},
}}
> >
</Button> </Button>
@ -335,51 +438,59 @@ export default function DrawPage() {
{showResults && ( {showResults && (
<> <>
{/* Results Header */} {/* Results Header */}
<Box sx={{ textAlign: 'center', mb: 4 }}> <Box sx={{ textAlign: 'center', mb: 10 }}>
<Typography variant="h4" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="h4" sx={{ fontWeight: 700, mb: 4, fontSize: '2rem' }}>
🎉 ! 🎉 !
</Typography> </Typography>
<Typography variant="h6"> <Typography variant="h6" sx={{ fontSize: '1.25rem' }}>
{mockEventData.totalParticipants} {winnerCount} {mockEventData.totalParticipants} {winnerCount}
</Typography> </Typography>
</Box> </Box>
{/* Winner List */} {/* Winner List */}
<Box sx={{ mb: 4 }}> <Box sx={{ mb: 10 }}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 2 }}> <Typography variant="h5" sx={{ fontWeight: 700, mb: 6, fontSize: '1.5rem' }}>
🏆 🏆
</Typography> </Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{winners.map((winner, index) => { {winners.map((winner, index) => {
const rank = index + 1; const rank = index + 1;
return ( return (
<Card key={winner.id} sx={{ borderRadius: 3 }}> <Card
<CardContent> key={winner.id}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}> elevation={0}
sx={{
borderRadius: 4,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
}}
>
<CardContent sx={{ p: 5 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 4 }}>
<Box <Box
sx={{ sx={{
width: 48, width: 64,
height: 48, height: 64,
borderRadius: '50%', borderRadius: '50%',
background: getRankClass(rank), background: getRankClass(rank),
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
color: 'white', color: 'white',
fontWeight: 600, fontWeight: 700,
fontSize: 18, fontSize: 20,
flexShrink: 0,
}} }}
> >
{rank} {rank}
</Box> </Box>
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1 }}>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
: #{winner.id} : #{winner.id}
</Typography> </Typography>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 0.5 }}> <Typography variant="h6" sx={{ fontWeight: 700, mb: 2, fontSize: '1.25rem' }}>
{winner.name} ({winner.phone}) {winner.name} ({winner.phone})
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1rem' }}>
: {winner.channel}{' '} : {winner.channel}{' '}
{winner.hasBonus && storeBonus && '🌟'} {winner.hasBonus && storeBonus && '🌟'}
</Typography> </Typography>
@ -391,14 +502,18 @@ export default function DrawPage() {
})} })}
</Box> </Box>
{storeBonus && ( {storeBonus && (
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', textAlign: 'center', mt: 2 }}> <Typography
variant="caption"
color="text.secondary"
sx={{ display: 'block', textAlign: 'center', mt: 6, fontSize: '0.875rem' }}
>
🌟 🌟
</Typography> </Typography>
)} )}
</Box> </Box>
{/* Action Buttons */} {/* Action Buttons */}
<Grid container spacing={2} sx={{ mb: 2 }}> <Grid container spacing={4} sx={{ mb: 4 }}>
<Grid item xs={6}> <Grid item xs={6}>
<Button <Button
fullWidth fullWidth
@ -406,7 +521,16 @@ export default function DrawPage() {
size="large" size="large"
startIcon={<Download />} startIcon={<Download />}
onClick={handleDownload} onClick={handleDownload}
sx={{ borderRadius: 2 }} sx={{
borderRadius: 3,
py: 3,
fontSize: '1rem',
fontWeight: 600,
borderWidth: 2,
'&:hover': {
borderWidth: 2,
},
}}
> >
</Button> </Button>
@ -418,7 +542,16 @@ export default function DrawPage() {
size="large" size="large"
startIcon={<Refresh />} startIcon={<Refresh />}
onClick={() => setRedrawDialogOpen(true)} onClick={() => setRedrawDialogOpen(true)}
sx={{ borderRadius: 2 }} sx={{
borderRadius: 3,
py: 3,
fontSize: '1rem',
fontWeight: 600,
borderWidth: 2,
'&:hover': {
borderWidth: 2,
},
}}
> >
</Button> </Button>
@ -431,7 +564,18 @@ export default function DrawPage() {
size="large" size="large"
startIcon={<Notifications />} startIcon={<Notifications />}
onClick={() => setNotifyDialogOpen(true)} onClick={() => setNotifyDialogOpen(true)}
sx={{ mb: 2, py: 1.5, borderRadius: 2, fontWeight: 700, fontSize: '1rem' }} sx={{
mb: 4,
py: 3,
borderRadius: 3,
fontWeight: 700,
fontSize: '1rem',
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
}}
> >
</Button> </Button>
@ -441,7 +585,12 @@ export default function DrawPage() {
variant="text" variant="text"
size="large" size="large"
onClick={handleBackToEvents} onClick={handleBackToEvents}
sx={{ borderRadius: 2 }} sx={{
borderRadius: 3,
py: 3,
fontSize: '1rem',
fontWeight: 600,
}}
> >
</Button> </Button>
@ -457,14 +606,15 @@ export default function DrawPage() {
sx: { sx: {
bgcolor: 'rgba(0, 0, 0, 0.9)', bgcolor: 'rgba(0, 0, 0, 0.9)',
color: 'white', color: 'white',
borderRadius: 4,
}, },
}} }}
> >
<DialogContent sx={{ textAlign: 'center', py: 8 }}> <DialogContent sx={{ textAlign: 'center', py: 16 }}>
<Casino <Casino
sx={{ sx={{
fontSize: 80, fontSize: 100,
mb: 3, mb: 6,
animation: 'spin 0.5s infinite', animation: 'spin 0.5s infinite',
'@keyframes spin': { '@keyframes spin': {
'0%, 100%': { transform: 'rotate(0deg)' }, '0%, 100%': { transform: 'rotate(0deg)' },
@ -472,95 +622,228 @@ export default function DrawPage() {
}, },
}} }}
/> />
<Typography variant="h4" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="h4" sx={{ fontWeight: 700, mb: 2, fontSize: '2rem' }}>
{animationText} {animationText}
</Typography> </Typography>
<Typography variant="body1" sx={{ color: 'rgba(255,255,255,0.7)' }}> <Typography variant="body1" sx={{ color: 'rgba(255,255,255,0.7)', fontSize: '1.125rem' }}>
{animationSubtext} {animationSubtext}
</Typography> </Typography>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
{/* Confirm Dialog */} {/* Confirm Dialog */}
<Dialog open={confirmDialogOpen} onClose={() => setConfirmDialogOpen(false)} maxWidth="xs" fullWidth> <Dialog
<DialogTitle> </DialogTitle> open={confirmDialogOpen}
<DialogContent> onClose={() => setConfirmDialogOpen(false)}
<Typography variant="body1" sx={{ textAlign: 'center' }}> maxWidth="xs"
fullWidth
PaperProps={{ sx: { borderRadius: 4 } }}
>
<DialogTitle sx={{ pt: 6, px: 6, pb: 4, fontSize: '1.5rem', fontWeight: 700 }}>
</DialogTitle>
<DialogContent sx={{ px: 6, pb: 4 }}>
<Typography variant="body1" sx={{ textAlign: 'center', fontSize: '1.125rem' }}>
{mockEventData.totalParticipants} {winnerCount} ? {mockEventData.totalParticipants} {winnerCount} ?
</Typography> </Typography>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions sx={{ px: 6, pb: 6, pt: 4, gap: 2 }}>
<Button onClick={() => setConfirmDialogOpen(false)}></Button> <Button
<Button onClick={executeDrawing} variant="contained"> onClick={() => setConfirmDialogOpen(false)}
sx={{
flex: 1,
py: 2,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
}}
>
</Button>
<Button
onClick={executeDrawing}
variant="contained"
sx={{
flex: 1,
py: 2,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
}}
>
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
{/* Redraw Dialog */} {/* Redraw Dialog */}
<Dialog open={redrawDialogOpen} onClose={() => setRedrawDialogOpen(false)} maxWidth="xs" fullWidth> <Dialog
<DialogTitle> </DialogTitle> open={redrawDialogOpen}
<DialogContent> onClose={() => setRedrawDialogOpen(false)}
<Typography variant="body1" sx={{ mb: 2, textAlign: 'center' }}> maxWidth="xs"
fullWidth
PaperProps={{ sx: { borderRadius: 4 } }}
>
<DialogTitle sx={{ pt: 6, px: 6, pb: 4, fontSize: '1.5rem', fontWeight: 700 }}>
</DialogTitle>
<DialogContent sx={{ px: 6, pb: 4 }}>
<Typography variant="body1" sx={{ mb: 4, textAlign: 'center', fontSize: '1.125rem' }}>
. .
</Typography> </Typography>
<Typography variant="body1" sx={{ mb: 2, textAlign: 'center' }}> <Typography variant="body1" sx={{ mb: 4, textAlign: 'center', fontSize: '1.125rem' }}>
? ?
</Typography> </Typography>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', textAlign: 'center' }}> <Typography
variant="caption"
color="text.secondary"
sx={{ display: 'block', textAlign: 'center', fontSize: '0.875rem' }}
>
</Typography> </Typography>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions sx={{ px: 6, pb: 6, pt: 4, gap: 2 }}>
<Button onClick={() => setRedrawDialogOpen(false)}></Button> <Button
<Button onClick={handleRedraw} variant="contained"> onClick={() => setRedrawDialogOpen(false)}
sx={{
flex: 1,
py: 2,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
}}
>
</Button>
<Button
onClick={handleRedraw}
variant="contained"
sx={{
flex: 1,
py: 2,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
}}
>
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
{/* Notify Dialog */} {/* Notify Dialog */}
<Dialog open={notifyDialogOpen} onClose={() => setNotifyDialogOpen(false)} maxWidth="xs" fullWidth> <Dialog
<DialogTitle> </DialogTitle> open={notifyDialogOpen}
<DialogContent> onClose={() => setNotifyDialogOpen(false)}
<Typography variant="body1" sx={{ mb: 2, textAlign: 'center' }}> maxWidth="xs"
fullWidth
PaperProps={{ sx: { borderRadius: 4 } }}
>
<DialogTitle sx={{ pt: 6, px: 6, pb: 4, fontSize: '1.5rem', fontWeight: 700 }}>
</DialogTitle>
<DialogContent sx={{ px: 6, pb: 4 }}>
<Typography variant="body1" sx={{ mb: 4, textAlign: 'center', fontSize: '1.125rem' }}>
{winnerCount} SMS ? {winnerCount} SMS ?
</Typography> </Typography>
<Typography variant="caption" color="text.secondary" sx={{ display: 'block', textAlign: 'center' }}> <Typography
variant="caption"
color="text.secondary"
sx={{ display: 'block', textAlign: 'center', fontSize: '0.875rem' }}
>
: {winnerCount * 100} (100/) : {winnerCount * 100} (100/)
</Typography> </Typography>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions sx={{ px: 6, pb: 6, pt: 4, gap: 2 }}>
<Button onClick={() => setNotifyDialogOpen(false)}></Button> <Button
<Button onClick={handleNotify} variant="contained"> onClick={() => setNotifyDialogOpen(false)}
sx={{
flex: 1,
py: 2,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
}}
>
</Button>
<Button
onClick={handleNotify}
variant="contained"
sx={{
flex: 1,
py: 2,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
}}
>
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
{/* History Detail Dialog */} {/* History Detail Dialog */}
<Dialog open={historyDetailOpen} onClose={() => setHistoryDetailOpen(false)} maxWidth="xs" fullWidth> <Dialog
<DialogTitle> </DialogTitle> open={historyDetailOpen}
<DialogContent> onClose={() => setHistoryDetailOpen(false)}
maxWidth="xs"
fullWidth
PaperProps={{ sx: { borderRadius: 4 } }}
>
<DialogTitle sx={{ pt: 6, px: 6, pb: 4, fontSize: '1.5rem', fontWeight: 700 }}>
</DialogTitle>
<DialogContent sx={{ px: 6, pb: 4 }}>
{selectedHistory && ( {selectedHistory && (
<Box sx={{ p: 2 }}> <Box sx={{ p: 4 }}>
<Typography variant="body1" sx={{ mb: 1 }}> <Typography variant="body1" sx={{ mb: 3, fontSize: '1.125rem' }}>
: {selectedHistory.date} : {selectedHistory.date}
</Typography> </Typography>
<Typography variant="body1" sx={{ mb: 1 }}> <Typography variant="body1" sx={{ mb: 3, fontSize: '1.125rem' }}>
: {selectedHistory.winnerCount} : {selectedHistory.winnerCount}
</Typography> </Typography>
<Typography variant="body1" sx={{ mb: 2 }}> <Typography variant="body1" sx={{ mb: 4, fontSize: '1.125rem' }}>
: {selectedHistory.isRedraw ? '예' : '아니오'} : {selectedHistory.isRedraw ? '예' : '아니오'}
</Typography> </Typography>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.875rem' }}>
</Typography> </Typography>
</Box> </Box>
)} )}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions sx={{ px: 6, pb: 6, pt: 4 }}>
<Button onClick={() => setHistoryDetailOpen(false)} variant="contained"> <Button
onClick={() => setHistoryDetailOpen(false)}
variant="contained"
fullWidth
sx={{
py: 2,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
}}
>
</Button> </Button>
</DialogActions> </DialogActions>

View File

@ -15,6 +15,7 @@ import {
Menu, Menu,
MenuItem, MenuItem,
Divider, Divider,
LinearProgress,
} from '@mui/material'; } from '@mui/material';
import { import {
MoreVert, MoreVert,
@ -23,13 +24,63 @@ import {
TrendingUp, TrendingUp,
Share, Share,
CardGiftcard, CardGiftcard,
HowToReg,
AttachMoney, AttachMoney,
People, People,
Edit, Edit,
Download, Download,
Person, Person,
Phone,
Email,
ShoppingCart,
Warning,
LocalFireDepartment,
Star,
NewReleases,
} from '@mui/icons-material'; } 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 데이터 // Mock 데이터
const mockEventData = { const mockEventData = {
@ -41,12 +92,17 @@ const mockEventData = {
prize: '커피 쿠폰', prize: '커피 쿠폰',
method: 'SNS 팔로우', method: 'SNS 팔로우',
cost: 250000, cost: 250000,
channels: ['홈페이지', '카카오톡', 'Instagram'], channels: ['우리동네TV', '링고비즈', 'SNS'],
participants: 128, participants: 128,
views: 456, views: 456,
roi: 450, roi: 450,
conversion: 28, conversion: 28,
targetParticipants: 200,
isAIRecommended: true, isAIRecommended: true,
isUrgent: false,
isPopular: true,
isHighROI: true,
isNew: false,
}; };
const recentParticipants = [ const recentParticipants = [
@ -57,6 +113,86 @@ const recentParticipants = [
{ name: '정*희', phone: '010-****-7890', time: '2시간 전' }, { 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() { export default function EventDetailPage() {
const router = useRouter(); const router = useRouter();
const params = useParams(); const params = useParams();
@ -119,11 +255,11 @@ export default function EventDetailPage() {
return ( return (
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}> <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 */} {/* Event Header */}
<Box sx={{ mb: 4 }}> <Box sx={{ mb: 8 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 4 }}>
<Typography variant="h4" sx={{ fontWeight: 700 }}> <Typography variant="h4" sx={{ fontWeight: 700, fontSize: '2rem' }}>
{event.title} {event.title}
</Typography> </Typography>
<IconButton onClick={handleMenuOpen}> <IconButton onClick={handleMenuOpen}>
@ -131,13 +267,13 @@ export default function EventDetailPage() {
</IconButton> </IconButton>
<Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}> <Menu anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={handleMenuClose}>
<MenuItem onClick={handleMenuClose}> <MenuItem onClick={handleMenuClose}>
<Edit sx={{ mr: 1 }} /> <Edit sx={{ mr: 2 }} />
</MenuItem> </MenuItem>
<MenuItem onClick={handleMenuClose}> <MenuItem onClick={handleMenuClose}>
<Share sx={{ mr: 1 }} /> <Share sx={{ mr: 2 }} />
</MenuItem> </MenuItem>
<MenuItem onClick={handleMenuClose}> <MenuItem onClick={handleMenuClose}>
<Download sx={{ mr: 1 }} /> <Download sx={{ mr: 2 }} />
</MenuItem> </MenuItem>
<Divider /> <Divider />
<MenuItem onClick={handleMenuClose} sx={{ color: 'error.main' }}> <MenuItem onClick={handleMenuClose} sx={{ color: 'error.main' }}>
@ -146,35 +282,99 @@ export default function EventDetailPage() {
</Menu> </Menu>
</Box> </Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap', mb: 4 }}>
<Chip label={getStatusText(event.status)} color={getStatusColor(event.status) as any} size="small" />
{event.isAIRecommended && (
<Chip <Chip
label="AI 추천" label={getStatusText(event.status)}
size="small" color={getStatusColor(event.status) as any}
sx={{ bgcolor: 'rgba(0, 102, 255, 0.1)', color: 'info.main' }} size="medium"
/>
{event.isAIRecommended && (
<Chip label="AI 추천" size="medium" sx={{ bgcolor: colors.purpleLight, color: colors.purple }} />
)}
{event.isUrgent && (
<Chip
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> </Box>
<Typography variant="body1" color="text.secondary"> <Typography variant="body1" color="text.secondary" sx={{ fontSize: '1rem', mb: 4 }}>
{event.startDate} ~ {event.endDate} 📅 {event.startDate} ~ {event.endDate}
</Typography> </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> </Box>
{/* Real-time KPIs */} {/* Real-time KPIs */}
<Box sx={{ mb: 5 }}> <Box sx={{ mb: 10 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 6 }}>
<Typography variant="h6" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ fontWeight: 700, fontSize: '1.5rem' }}>
</Typography> </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 <Box
sx={{ sx={{
width: 8, width: 10,
height: 8, height: 10,
borderRadius: '50%', borderRadius: '50%',
bgcolor: 'success.main', bgcolor: 'success.main',
animation: 'pulse 2s infinite',
'@keyframes pulse': {
'0%, 100%': { opacity: 1 },
'50%': { opacity: 0.5 },
},
}} }}
/> />
<Typography variant="body2" sx={{ fontWeight: 600 }}> <Typography variant="body2" sx={{ fontWeight: 600 }}>
@ -183,56 +383,86 @@ export default function EventDetailPage() {
</Box> </Box>
</Box> </Box>
<Grid container spacing={2}> <Grid container spacing={6}>
<Grid item xs={6} md={3}> <Grid item xs={6} md={3}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card
<CardContent sx={{ textAlign: 'center', py: 3 }}> elevation={0}
<Group color="primary" sx={{ fontSize: 32, mb: 1 }} /> sx={{
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}> 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>
<Typography variant="h5" sx={{ fontWeight: 700 }}> <Typography variant="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
{event.participants} {event.participants}
</Typography> </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> </CardContent>
</Card> </Card>
</Grid> </Grid>
<Grid item xs={6} md={3}> <Grid item xs={6} md={3}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card
<CardContent sx={{ textAlign: 'center', py: 3 }}> elevation={0}
<Visibility color="info" sx={{ fontSize: 32, mb: 1 }} /> sx={{
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}> 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>
<Typography variant="h5" sx={{ fontWeight: 700 }}> <Typography variant="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
{event.views} {event.views}
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Grid>
<Grid item xs={6} md={3}> <Grid item xs={6} md={3}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card
<CardContent sx={{ textAlign: 'center', py: 3 }}> elevation={0}
<TrendingUp color="success" sx={{ fontSize: 32, mb: 1 }} /> sx={{
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}> 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 ROI
</Typography> </Typography>
<Typography variant="h5" sx={{ fontWeight: 700, color: 'success.main' }}> <Typography variant="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
{event.roi}% {event.roi}%
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Grid>
<Grid item xs={6} md={3}> <Grid item xs={6} md={3}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card
<CardContent sx={{ textAlign: 'center', py: 3 }}> elevation={0}
<span className="material-icons" style={{ fontSize: 32, marginBottom: 8, color: '#1976d2' }}> sx={{
conversion_path borderRadius: 4,
</span> boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 1 }}> 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>
<Typography variant="h5" sx={{ fontWeight: 700 }}> <Typography variant="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
{event.conversion}% {event.conversion}%
</Typography> </Typography>
</CardContent> </CardContent>
@ -241,112 +471,236 @@ export default function EventDetailPage() {
</Grid> </Grid>
</Box> </Box>
{/* Chart Section */} {/* Chart Section - 참여 추이 */}
<Box sx={{ mb: 5 }}> <Box sx={{ mb: 10 }}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 3 }}> <Typography variant="h5" sx={{ fontWeight: 700, mb: 6, fontSize: '1.5rem' }}>
📈
</Typography> </Typography>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
<CardContent sx={{ p: 3 }}> <CardContent sx={{ p: 6 }}>
<Box sx={{ display: 'flex', gap: 1, mb: 3 }}> <Box sx={{ display: 'flex', gap: 2, mb: 6 }}>
<Button <Button
size="small" size="medium"
variant={chartPeriod === '7d' ? 'contained' : 'outlined'} variant={chartPeriod === '7d' ? 'contained' : 'outlined'}
onClick={() => setChartPeriod('7d')} onClick={() => setChartPeriod('7d')}
sx={{ borderRadius: 2 }}
> >
7 7
</Button> </Button>
<Button <Button
size="small" size="medium"
variant={chartPeriod === '30d' ? 'contained' : 'outlined'} variant={chartPeriod === '30d' ? 'contained' : 'outlined'}
onClick={() => setChartPeriod('30d')} onClick={() => setChartPeriod('30d')}
sx={{ borderRadius: 2 }}
> >
30 30
</Button> </Button>
<Button <Button
size="small" size="medium"
variant={chartPeriod === 'all' ? 'contained' : 'outlined'} variant={chartPeriod === 'all' ? 'contained' : 'outlined'}
onClick={() => setChartPeriod('all')} onClick={() => setChartPeriod('all')}
sx={{ borderRadius: 2 }}
> >
</Button> </Button>
</Box> </Box>
<Box <Box sx={{ height: 320 }}>
sx={{ <Line
height: 200, data={generateParticipationTrendData(chartPeriod)}
bgcolor: 'grey.100', options={{
borderRadius: 2, responsive: true,
display: 'flex', maintainAspectRatio: false,
alignItems: 'center', plugins: {
justifyContent: 'center', legend: {
flexDirection: 'column', 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,
},
},
},
}} }}
> />
<span className="material-icons" style={{ fontSize: 48, color: '#9e9e9e' }}>
show_chart
</span>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
</Typography>
</Box> </Box>
</CardContent> </CardContent>
</Card> </Card>
</Box> </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 */} {/* Event Details */}
<Box sx={{ mb: 5 }}> <Box sx={{ mb: 10 }}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 3 }}> <Typography variant="h5" sx={{ fontWeight: 700, mb: 6, fontSize: '1.5rem' }}>
🎯
</Typography> </Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
<CardContent sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}> <CardContent sx={{ display: 'flex', alignItems: 'flex-start', gap: 3, p: 4 }}>
<CardGiftcard color="error" /> <CardGiftcard sx={{ fontSize: 28, color: colors.pink }} />
<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={{ flex: 1 }}> <Box sx={{ flex: 1 }}>
<Typography variant="caption" color="text.secondary" display="block" sx={{ mb: 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> </Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}> <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2 }}>
{event.channels.map((channel) => ( {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>
</Box> </Box>
@ -356,29 +710,31 @@ export default function EventDetailPage() {
</Box> </Box>
{/* Quick Actions */} {/* Quick Actions */}
<Box sx={{ mb: 5 }}> <Box sx={{ mb: 10 }}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 3 }}> <Typography variant="h5" sx={{ fontWeight: 700, mb: 6, fontSize: '1.5rem' }}>
</Typography> </Typography>
<Grid container spacing={2}> <Grid container spacing={4}>
<Grid item xs={6} md={3}> <Grid item xs={6} md={3}>
<Card <Card
elevation={0} elevation={0}
sx={{ sx={{
cursor: 'pointer', cursor: 'pointer',
borderRadius: 3, borderRadius: 4,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
transition: 'all 0.2s', transition: 'all 0.2s',
'&:hover': { '&:hover': {
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
transform: 'translateY(-2px)', transform: 'translateY(-4px)',
}, },
}} }}
onClick={() => router.push(`/events/${eventId}/participants`)} onClick={() => router.push(`/events/${eventId}/participants`)}
> >
<CardContent sx={{ textAlign: 'center', py: 3 }}> <CardContent sx={{ textAlign: 'center', py: 5 }}>
<People color="error" sx={{ fontSize: 32, mb: 1 }} /> <People sx={{ fontSize: 40, mb: 2, color: colors.pink }} />
<Typography variant="body2"> </Typography> <Typography variant="body1" sx={{ fontWeight: 600 }}>
</Typography>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Grid>
@ -387,18 +743,20 @@ export default function EventDetailPage() {
elevation={0} elevation={0}
sx={{ sx={{
cursor: 'pointer', cursor: 'pointer',
borderRadius: 3, borderRadius: 4,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
transition: 'all 0.2s', transition: 'all 0.2s',
'&:hover': { '&:hover': {
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
transform: 'translateY(-2px)', transform: 'translateY(-4px)',
}, },
}} }}
> >
<CardContent sx={{ textAlign: 'center', py: 3 }}> <CardContent sx={{ textAlign: 'center', py: 5 }}>
<Edit color="info" sx={{ fontSize: 32, mb: 1 }} /> <Edit sx={{ fontSize: 40, mb: 2, color: colors.blue }} />
<Typography variant="body2"> </Typography> <Typography variant="body1" sx={{ fontWeight: 600 }}>
</Typography>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Grid>
@ -407,18 +765,20 @@ export default function EventDetailPage() {
elevation={0} elevation={0}
sx={{ sx={{
cursor: 'pointer', cursor: 'pointer',
borderRadius: 3, borderRadius: 4,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
transition: 'all 0.2s', transition: 'all 0.2s',
'&:hover': { '&:hover': {
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
transform: 'translateY(-2px)', transform: 'translateY(-4px)',
}, },
}} }}
> >
<CardContent sx={{ textAlign: 'center', py: 3 }}> <CardContent sx={{ textAlign: 'center', py: 5 }}>
<Share sx={{ fontSize: 32, mb: 1, color: 'text.secondary' }} /> <Share sx={{ fontSize: 40, mb: 2, color: colors.purple }} />
<Typography variant="body2"></Typography> <Typography variant="body1" sx={{ fontWeight: 600 }}>
</Typography>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Grid>
@ -427,18 +787,20 @@ export default function EventDetailPage() {
elevation={0} elevation={0}
sx={{ sx={{
cursor: 'pointer', cursor: 'pointer',
borderRadius: 3, borderRadius: 4,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
transition: 'all 0.2s', transition: 'all 0.2s',
'&:hover': { '&:hover': {
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
transform: 'translateY(-2px)', transform: 'translateY(-4px)',
}, },
}} }}
> >
<CardContent sx={{ textAlign: 'center', py: 3 }}> <CardContent sx={{ textAlign: 'center', py: 5 }}>
<Download sx={{ fontSize: 32, mb: 1, color: 'text.secondary' }} /> <Download sx={{ fontSize: 40, mb: 2, color: colors.mint }} />
<Typography variant="body2"> </Typography> <Typography variant="body1" sx={{ fontWeight: 600 }}>
</Typography>
</CardContent> </CardContent>
</Card> </Card>
</Grid> </Grid>
@ -446,51 +808,51 @@ export default function EventDetailPage() {
</Box> </Box>
{/* Recent Participants */} {/* Recent Participants */}
<Box sx={{ mb: 5 }}> <Box sx={{ mb: 10 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 3 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 6 }}>
<Typography variant="h6" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ fontWeight: 700, fontSize: '1.5rem' }}>
👥
</Typography> </Typography>
<Button <Button
size="small" size="medium"
endIcon={<span className="material-icons" style={{ fontSize: 16 }}>chevron_right</span>} endIcon={<span className="material-icons" style={{ fontSize: 18 }}>chevron_right</span>}
onClick={() => router.push(`/events/${eventId}/participants`)} onClick={() => router.push(`/events/${eventId}/participants`)}
sx={{ color: 'error.main', fontWeight: 600 }} sx={{ color: colors.pink, fontWeight: 600 }}
> >
</Button> </Button>
</Box> </Box>
<Card elevation={0} sx={{ borderRadius: 3, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)' }}> <Card elevation={0} sx={{ borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
<CardContent sx={{ p: 3 }}> <CardContent sx={{ p: 6 }}>
{recentParticipants.map((participant, index) => ( {recentParticipants.map((participant, index) => (
<Box key={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', justifyContent: 'space-between' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3 }}>
<Box <Box
sx={{ sx={{
width: 40, width: 48,
height: 40, height: 48,
borderRadius: '50%', borderRadius: '50%',
bgcolor: 'grey.200', bgcolor: colors.purpleLight,
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}} }}
> >
<Person sx={{ color: 'text.secondary' }} /> <Person sx={{ color: colors.purple, fontSize: 24 }} />
</Box> </Box>
<Box> <Box>
<Typography variant="body2" sx={{ fontWeight: 600 }}> <Typography variant="body1" sx={{ fontWeight: 600, mb: 0.5 }}>
{participant.name} {participant.name}
</Typography> </Typography>
<Typography variant="caption" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{participant.phone} {participant.phone}
</Typography> </Typography>
</Box> </Box>
</Box> </Box>
<Typography variant="caption" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{participant.time} {participant.time}
</Typography> </Typography>
</Box> </Box>

View File

@ -21,14 +21,37 @@ import {
DialogTitle, DialogTitle,
DialogContent, DialogContent,
DialogActions, DialogActions,
Grid,
} from '@mui/material'; } from '@mui/material';
import { import {
Search, Search,
FilterList, FilterList,
Casino, Casino,
Download, Download,
People,
TrendingUp,
Person,
AccessTime,
} from '@mui/icons-material'; } from '@mui/icons-material';
// 디자인 시스템 색상
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 데이터 // Mock 데이터
const mockParticipants = [ const mockParticipants = [
{ {
@ -150,11 +173,117 @@ export default function ParticipantsPage() {
alert('엑셀 다운로드 기능은 추후 구현됩니다'); alert('엑셀 다운로드 기능은 추후 구현됩니다');
}; };
// 통계 계산
const stats = {
total: mockParticipants.length,
waiting: mockParticipants.filter((p) => p.status === 'waiting').length,
winner: mockParticipants.filter((p) => p.status === 'winner').length,
channelDistribution: {
uriTV: mockParticipants.filter((p) => p.channelType === 'uriTV').length,
ringoBiz: mockParticipants.filter((p) => p.channelType === 'ringoBiz').length,
sns: mockParticipants.filter((p) => p.channelType === 'sns').length,
},
};
return ( return (
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}> <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 } }}>
{/* Page Header */}
<Box sx={{ mb: 8 }}>
<Typography variant="h4" sx={{ fontWeight: 700, fontSize: '2rem', mb: 2 }}>
👥
</Typography>
<Typography variant="body1" color="text.secondary">
</Typography>
</Box>
{/* Statistics Cards */}
<Grid container spacing={6} sx={{ mb: 10 }}>
<Grid item xs={6} md={3}>
<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 }}>
<People 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="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
{stats.total}
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={6} md={3}>
<Card
elevation={0}
sx={{
borderRadius: 4,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
background: `linear-gradient(135deg, ${colors.yellow} 0%, #FCD34D 100%)`,
}}
>
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
<AccessTime 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="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
{stats.waiting}
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={6} md={3}>
<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)' }}>
</Typography>
<Typography variant="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
{stats.winner}
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={6} md={3}>
<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 }}>
<Casino 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="h4" sx={{ fontWeight: 700, color: 'white', fontSize: '1.75rem' }}>
{stats.total > 0 ? Math.round((stats.winner / stats.total) * 100) : 0}%
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
{/* Search Section */} {/* Search Section */}
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 6 }}>
<TextField <TextField
fullWidth fullWidth
placeholder="이름 또는 전화번호 검색..." placeholder="이름 또는 전화번호 검색..."
@ -169,22 +298,24 @@ export default function ParticipantsPage() {
}} }}
sx={{ sx={{
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
borderRadius: 2, borderRadius: 3,
bgcolor: 'white',
}, },
}} }}
/> />
</Box> </Box>
{/* Filters */} {/* Filters */}
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 8 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3, flexWrap: 'wrap' }}>
<FilterList color="error" /> <FilterList sx={{ fontSize: 28, color: colors.pink }} />
<FormControl sx={{ flex: 1, minWidth: 140 }}> <FormControl sx={{ flex: 1, minWidth: 160 }}>
<InputLabel> </InputLabel> <InputLabel> </InputLabel>
<Select <Select
value={channelFilter} value={channelFilter}
label="참여 경로" label="참여 경로"
onChange={(e) => setChannelFilter(e.target.value as ChannelType)} onChange={(e) => setChannelFilter(e.target.value as ChannelType)}
sx={{ borderRadius: 2 }}
> >
<MenuItem value="all"> </MenuItem> <MenuItem value="all"> </MenuItem>
<MenuItem value="uriTV">TV</MenuItem> <MenuItem value="uriTV">TV</MenuItem>
@ -192,12 +323,13 @@ export default function ParticipantsPage() {
<MenuItem value="sns">SNS</MenuItem> <MenuItem value="sns">SNS</MenuItem>
</Select> </Select>
</FormControl> </FormControl>
<FormControl sx={{ flex: 1, minWidth: 120 }}> <FormControl sx={{ flex: 1, minWidth: 140 }}>
<InputLabel></InputLabel> <InputLabel></InputLabel>
<Select <Select
value={statusFilter} value={statusFilter}
label="상태" label="상태"
onChange={(e) => setStatusFilter(e.target.value as StatusType)} onChange={(e) => setStatusFilter(e.target.value as StatusType)}
sx={{ borderRadius: 2 }}
> >
<MenuItem value="all"></MenuItem> <MenuItem value="all"></MenuItem>
<MenuItem value="waiting"> </MenuItem> <MenuItem value="waiting"> </MenuItem>
@ -209,77 +341,122 @@ export default function ParticipantsPage() {
</Box> </Box>
{/* Total Count & Drawing Button */} {/* Total Count & Drawing Button */}
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 6 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexWrap: 'wrap', gap: 4 }}>
<Typography variant="h6" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ fontWeight: 700, fontSize: '1.5rem' }}>
<span style={{ color: '#e91e63' }}>{filteredParticipants.length}</span> <span style={{ color: colors.pink }}>{filteredParticipants.length}</span>
</Typography> </Typography>
<Box sx={{ display: 'flex', gap: 3 }}>
<Button
variant="outlined"
startIcon={<Download />}
onClick={handleDownloadClick}
sx={{
borderRadius: 3,
px: 4,
py: 1.5,
borderColor: colors.blue,
color: colors.blue,
'&:hover': {
borderColor: colors.blue,
bgcolor: `${colors.blue}10`,
},
}}
>
</Button>
<Button <Button
variant="contained" variant="contained"
startIcon={<Casino />} startIcon={<Casino />}
onClick={handleDrawClick} onClick={handleDrawClick}
sx={{ borderRadius: 2 }} sx={{
borderRadius: 3,
px: 4,
py: 1.5,
background: `linear-gradient(135deg, ${colors.pink} 0%, ${colors.purple} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.pink} 0%, ${colors.purple} 100%)`,
opacity: 0.9,
},
}}
> >
</Button> </Button>
</Box> </Box>
</Box> </Box>
</Box>
{/* Participant List */} {/* Participant List */}
<Box sx={{ mb: 5 }}> <Box sx={{ mb: 10 }}>
{pageParticipants.length === 0 ? ( {pageParticipants.length === 0 ? (
<Box sx={{ textAlign: 'center', py: 8 }}> <Box sx={{ textAlign: 'center', py: 16 }}>
<span className="material-icons" style={{ fontSize: 64, color: '#bdbdbd' }}> <Person sx={{ fontSize: 80, color: colors.gray[300], mb: 3 }} />
people_outline <Typography variant="h6" color="text.secondary" sx={{ fontWeight: 600 }}>
</span>
<Typography variant="body1" color="text.secondary" sx={{ mt: 2 }}>
</Typography> </Typography>
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}>
</Typography>
</Box> </Box>
) : ( ) : (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
{pageParticipants.map((participant) => ( {pageParticipants.map((participant) => (
<Card <Card
key={participant.id} key={participant.id}
elevation={0} elevation={0}
sx={{ sx={{
cursor: 'pointer', cursor: 'pointer',
borderRadius: 3, borderRadius: 4,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)', boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
transition: 'all 0.2s ease', transition: 'all 0.2s ease',
'&:hover': { '&:hover': {
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
transform: 'translateY(-2px)', transform: 'translateY(-4px)',
}, },
}} }}
onClick={() => handleParticipantClick(participant)} onClick={() => handleParticipantClick(participant)}
> >
<CardContent sx={{ p: 3 }}> <CardContent sx={{ p: 5 }}>
{/* Header */} {/* Header */}
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
alignItems: 'flex-start', alignItems: 'flex-start',
justifyContent: 'space-between', justifyContent: 'space-between',
mb: 2, mb: 4,
}} }}
> >
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1, display: 'flex', alignItems: 'center', gap: 3 }}>
<Typography variant="caption" color="text.secondary" sx={{ mb: 0.5 }}> <Box
sx={{
width: 56,
height: 56,
borderRadius: '50%',
bgcolor: colors.purpleLight,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Person sx={{ fontSize: 32, color: colors.purple }} />
</Box>
<Box>
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
#{participant.id} #{participant.id}
</Typography> </Typography>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 0.5 }}> <Typography variant="h6" sx={{ fontWeight: 700, mb: 1, fontSize: '1.25rem' }}>
{participant.name} {participant.name}
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body1" color="text.secondary">
{participant.phone} {participant.phone}
</Typography> </Typography>
</Box> </Box>
</Box>
<Chip <Chip
label={getStatusText(participant.status)} label={getStatusText(participant.status)}
color={getStatusColor(participant.status) as any} color={getStatusColor(participant.status) as any}
size="small" size="medium"
sx={{ fontWeight: 600 }} sx={{ fontWeight: 600, px: 2, py: 2.5 }}
/> />
</Box> </Box>
@ -287,26 +464,34 @@ export default function ParticipantsPage() {
<Box <Box
sx={{ sx={{
borderTop: '1px solid', borderTop: '1px solid',
borderColor: 'divider', borderColor: colors.gray[100],
pt: 2, pt: 4,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
gap: 0.5, gap: 2,
}} }}
> >
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body1" color="text.secondary">
</Typography> </Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}> <Chip
{participant.channel} label={participant.channel}
</Typography> size="small"
sx={{
bgcolor: colors.purpleLight,
color: colors.purple,
fontWeight: 600,
}}
/>
</Box> </Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body1" color="text.secondary">
</Typography> </Typography>
<Typography variant="body2">{participant.date}</Typography> <Typography variant="body1" sx={{ fontWeight: 600 }}>
{participant.date}
</Typography>
</Box> </Box>
</Box> </Box>
</CardContent> </CardContent>
@ -318,87 +503,145 @@ export default function ParticipantsPage() {
{/* Pagination */} {/* Pagination */}
{totalPages > 1 && ( {totalPages > 1 && (
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 5 }}> <Box sx={{ display: 'flex', justifyContent: 'center', mb: 10 }}>
<Pagination <Pagination
count={totalPages} count={totalPages}
page={currentPage} page={currentPage}
onChange={(_, page) => setCurrentPage(page)} onChange={(_, page) => setCurrentPage(page)}
color="primary" color="primary"
size="large" size="large"
sx={{
'& .MuiPaginationItem-root': {
fontSize: '1rem',
fontWeight: 600,
},
}}
/> />
</Box> </Box>
)} )}
{/* Excel Download Button (Desktop only) */}
<Box sx={{ display: { xs: 'none', md: 'block' } }}>
<Button
fullWidth
variant="outlined"
size="large"
startIcon={<Download />}
onClick={handleDownloadClick}
sx={{ borderRadius: 2 }}
>
</Button>
</Box>
{/* Participant Detail Dialog */} {/* Participant Detail Dialog */}
<Dialog <Dialog
open={detailDialogOpen} open={detailDialogOpen}
onClose={() => setDetailDialogOpen(false)} onClose={() => setDetailDialogOpen(false)}
maxWidth="sm" maxWidth="sm"
fullWidth fullWidth
PaperProps={{
sx: {
borderRadius: 4,
},
}}
> >
<DialogTitle> </DialogTitle> <DialogTitle sx={{ p: 5, pb: 3 }}>
<DialogContent dividers> <Typography variant="h5" sx={{ fontWeight: 700 }}>
{selectedParticipant && (
<Box sx={{ p: 2 }}>
<Box sx={{ mb: 3 }}>
<Typography variant="caption" color="text.secondary">
</Typography> </Typography>
<Typography variant="h6" sx={{ fontWeight: 700 }}> </DialogTitle>
<DialogContent dividers sx={{ p: 5 }}>
{selectedParticipant && (
<Box>
<Box sx={{ mb: 4, textAlign: 'center' }}>
<Box
sx={{
width: 80,
height: 80,
borderRadius: '50%',
bgcolor: colors.purpleLight,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
margin: '0 auto',
mb: 3,
}}
>
<Person sx={{ fontSize: 40, color: colors.purple }} />
</Box>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 1 }}>
{selectedParticipant.name}
</Typography>
<Typography variant="body2" color="text.secondary">
#{selectedParticipant.id} #{selectedParticipant.id}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ mb: 3 }}>
<Typography variant="caption" color="text.secondary"> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<Box
</Typography> sx={{
<Typography variant="body1">{selectedParticipant.name}</Typography> p: 3,
</Box> bgcolor: colors.gray[100],
<Box sx={{ mb: 3 }}> borderRadius: 3,
<Typography variant="caption" color="text.secondary"> }}
>
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
</Typography> </Typography>
<Typography variant="body1">{selectedParticipant.phone}</Typography> <Typography variant="body1" sx={{ fontWeight: 600 }}>
{selectedParticipant.phone}
</Typography>
</Box> </Box>
<Box sx={{ mb: 3 }}>
<Typography variant="caption" color="text.secondary"> <Box
sx={{
p: 3,
bgcolor: colors.gray[100],
borderRadius: 3,
}}
>
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
</Typography> </Typography>
<Typography variant="body1">{selectedParticipant.channel}</Typography> <Typography variant="body1" sx={{ fontWeight: 600 }}>
{selectedParticipant.channel}
</Typography>
</Box> </Box>
<Box sx={{ mb: 3 }}>
<Typography variant="caption" color="text.secondary"> <Box
sx={{
p: 3,
bgcolor: colors.gray[100],
borderRadius: 3,
}}
>
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
</Typography> </Typography>
<Typography variant="body1">{selectedParticipant.date}</Typography> <Typography variant="body1" sx={{ fontWeight: 600 }}>
{selectedParticipant.date}
</Typography>
</Box> </Box>
<Box>
<Typography variant="caption" color="text.secondary"> <Box
sx={{
p: 3,
bgcolor: colors.gray[100],
borderRadius: 3,
}}
>
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
</Typography> </Typography>
<Typography variant="body1"> <Chip
{getStatusText(selectedParticipant.status)} label={getStatusText(selectedParticipant.status)}
</Typography> color={getStatusColor(selectedParticipant.status) as any}
size="medium"
sx={{ fontWeight: 600 }}
/>
</Box>
</Box> </Box>
</Box> </Box>
)} )}
</DialogContent> </DialogContent>
<DialogActions> <DialogActions sx={{ p: 5, pt: 3 }}>
<Button onClick={() => setDetailDialogOpen(false)} variant="contained"> <Button
onClick={() => setDetailDialogOpen(false)}
variant="contained"
fullWidth
sx={{
borderRadius: 3,
py: 1.5,
background: `linear-gradient(135deg, ${colors.pink} 0%, ${colors.purple} 100%)`,
}}
>
</Button> </Button>
</DialogActions> </DialogActions>

View File

@ -17,8 +17,9 @@ import {
DialogActions, DialogActions,
Link, Link,
} from '@mui/material'; } from '@mui/material';
import { ArrowBack, CheckCircle, Edit, RocketLaunch, Save } from '@mui/icons-material'; import { ArrowBack, CheckCircle, Edit, RocketLaunch, Save, People, AttachMoney, TrendingUp } from '@mui/icons-material';
import { EventData } from '../page'; import { EventData } from '../page';
import { cardStyles, colors, responsiveText } from '@/shared/lib/button-styles';
interface ApprovalStepProps { interface ApprovalStepProps {
eventData: EventData; eventData: EventData;
@ -61,91 +62,214 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
}; };
return ( return (
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}> <Box sx={{ minHeight: '100vh', bgcolor: colors.gray[50], pt: { xs: 7, sm: 8 }, pb: 10 }}>
<Container maxWidth="md" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}> <Container maxWidth="lg" sx={{ pt: 8, pb: 6, px: { xs: 6, sm: 8, md: 10 } }}>
{/* Header */} {/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 4 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 8 }}>
<IconButton onClick={onBack}> <IconButton onClick={onBack}>
<ArrowBack /> <ArrowBack />
</IconButton> </IconButton>
<Typography variant="h5" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ ...responsiveText.h3, fontWeight: 700 }}>
</Typography> </Typography>
</Box> </Box>
{/* Title Section */} {/* Title Section */}
<Box sx={{ textAlign: 'center', mb: 5 }}> <Box sx={{ textAlign: 'center', mb: 10 }}>
<CheckCircle sx={{ fontSize: 64, color: 'success.main', mb: 2 }} /> <CheckCircle sx={{ fontSize: 64, color: colors.purple, mb: 2 }} />
<Typography variant="h5" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="h5" sx={{ ...responsiveText.h3, fontWeight: 700, mb: 2 }}>
</Typography> </Typography>
<Typography variant="body1" color="text.secondary"> <Typography variant="body1" color="text.secondary" sx={{ ...responsiveText.body1 }}>
</Typography> </Typography>
</Box> </Box>
{/* Event Summary Card */} {/* Event Summary Statistics */}
<Card elevation={0} sx={{ mb: 4, borderRadius: 3 }}> <Grid container spacing={4} sx={{ mb: 10 }}>
<CardContent sx={{ p: 4 }}> <Grid item xs={12} sm={6} md={3}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 2 }}> <Card
elevation={0}
sx={{
...cardStyles.default,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
borderColor: 'transparent',
}}
>
<CardContent sx={{ textAlign: 'center', py: 4, px: 3 }}>
<CheckCircle sx={{
fontSize: 32,
color: colors.gray[900],
mb: 1,
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
}} />
<Typography variant="body2" sx={{
color: colors.gray[700],
fontSize: '0.875rem',
mb: 1,
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
}}>
</Typography>
<Typography
variant="h6"
sx={{
fontWeight: 700,
color: colors.gray[900],
fontSize: '1rem',
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}
>
{eventData.recommendation?.title || '이벤트 제목'} {eventData.recommendation?.title || '이벤트 제목'}
</Typography> </Typography>
<Box sx={{ display: 'flex', gap: 1, mb: 3 }}>
<Chip label="배포 대기" color="warning" size="small" />
<Chip label="AI 추천" color="info" size="small" />
</Box>
<Grid container spacing={2} sx={{ pt: 2, borderTop: 1, borderColor: 'divider' }}>
<Grid item xs={6}>
<Typography variant="caption" color="text.secondary">
</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
2025.02.01 ~ 2025.02.28
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="caption" color="text.secondary">
</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{eventData.recommendation?.expectedParticipants || 0}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="caption" color="text.secondary">
</Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{eventData.recommendation?.estimatedCost.toLocaleString() || 0}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="caption" color="text.secondary">
ROI
</Typography>
<Typography variant="body2" sx={{ fontWeight: 600, color: 'error.main' }}>
{eventData.recommendation?.roi || 0}%
</Typography>
</Grid>
</Grid>
</CardContent> </CardContent>
</Card> </Card>
</Grid>
<Grid item xs={12} sm={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: 4, px: 3 }}>
<People sx={{
fontSize: 32,
color: colors.gray[900],
mb: 1,
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
}} />
<Typography variant="body2" sx={{
color: colors.gray[700],
fontSize: '0.875rem',
mb: 1,
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
}}>
</Typography>
<Typography
variant="h4"
sx={{
fontWeight: 700,
color: colors.gray[900],
fontSize: '1.75rem',
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}
>
{eventData.recommendation?.expectedParticipants || 0}
<Typography component="span" sx={{
fontSize: '1rem',
ml: 0.5,
color: colors.gray[900],
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}>
</Typography>
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} sm={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: 4, px: 3 }}>
<AttachMoney sx={{
fontSize: 32,
color: colors.gray[900],
mb: 1,
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
}} />
<Typography variant="body2" sx={{
color: colors.gray[700],
fontSize: '0.875rem',
mb: 1,
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
}}>
</Typography>
<Typography
variant="h4"
sx={{
fontWeight: 700,
color: colors.gray[900],
fontSize: '1.75rem',
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}
>
{((eventData.recommendation?.estimatedCost || 0) / 10000).toFixed(0)}
<Typography component="span" sx={{
fontSize: '1rem',
ml: 0.5,
color: colors.gray[900],
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}>
</Typography>
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} sm={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: 4, px: 3 }}>
<TrendingUp sx={{
fontSize: 32,
color: colors.gray[900],
mb: 1,
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
}} />
<Typography variant="body2" sx={{
color: colors.gray[700],
fontSize: '0.875rem',
mb: 1,
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
}}>
ROI
</Typography>
<Typography
variant="h4"
sx={{
fontWeight: 700,
color: colors.gray[900],
fontSize: '1.75rem',
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}
>
{eventData.recommendation?.roi || 0}%
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
{/* Event Details */} {/* Event Details */}
<Typography variant="h6" sx={{ fontWeight: 700, mb: 3 }}> <Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 6 }}>
</Typography> </Typography>
<Card elevation={0} sx={{ mb: 2, borderRadius: 3 }}> <Card elevation={0} sx={{ ...cardStyles.default, mb: 4 }}>
<CardContent sx={{ p: 3 }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}> <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1 }}>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary" sx={{ ...responsiveText.body2, fontSize: '0.875rem' }}>
</Typography> </Typography>
<Typography variant="body1" sx={{ fontWeight: 600 }}> <Typography variant="body1" sx={{ ...responsiveText.body1, fontWeight: 600, mt: 1 }}>
{eventData.recommendation?.title} {eventData.recommendation?.title}
</Typography> </Typography>
</Box> </Box>
@ -156,14 +280,14 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
</CardContent> </CardContent>
</Card> </Card>
<Card elevation={0} sx={{ mb: 2, borderRadius: 3 }}> <Card elevation={0} sx={{ ...cardStyles.default, mb: 4 }}>
<CardContent sx={{ p: 3 }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}> <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1 }}>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary" sx={{ ...responsiveText.body2, fontSize: '0.875rem' }}>
</Typography> </Typography>
<Typography variant="body1" sx={{ fontWeight: 600 }}> <Typography variant="body1" sx={{ ...responsiveText.body1, fontWeight: 600, mt: 1 }}>
{eventData.recommendation?.prize} {eventData.recommendation?.prize}
</Typography> </Typography>
</Box> </Box>
@ -174,14 +298,14 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
</CardContent> </CardContent>
</Card> </Card>
<Card elevation={0} sx={{ mb: 2, borderRadius: 3 }}> <Card elevation={0} sx={{ ...cardStyles.default, mb: 10 }}>
<CardContent sx={{ p: 3 }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}> <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2 }}>
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1 }}>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary" sx={{ ...responsiveText.body2, fontSize: '0.875rem' }}>
</Typography> </Typography>
<Typography variant="body1" sx={{ fontWeight: 600 }}> <Typography variant="body1" sx={{ ...responsiveText.body1, fontWeight: 600, mt: 1 }}>
{eventData.recommendation?.participationMethod} {eventData.recommendation?.participationMethod}
</Typography> </Typography>
</Box> </Box>
@ -190,21 +314,36 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
</Card> </Card>
{/* Distribution Channels */} {/* Distribution Channels */}
<Typography variant="h6" sx={{ fontWeight: 700, mb: 3, mt: 4 }}> <Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 6 }}>
</Typography> </Typography>
<Card elevation={0} sx={{ mb: 4, borderRadius: 3 }}> <Card elevation={0} sx={{ ...cardStyles.default, mb: 10 }}>
<CardContent sx={{ p: 3 }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 2 }}> <Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 2, mb: 4 }}>
{getChannelNames(eventData.channels).map((channel) => ( {getChannelNames(eventData.channels).map((channel) => (
<Chip key={channel} label={channel} color="primary" variant="outlined" /> <Chip
key={channel}
label={channel}
sx={{
bgcolor: colors.purple,
color: 'white',
fontWeight: 600,
fontSize: '0.875rem',
px: 2,
py: 2.5,
}}
/>
))} ))}
</Box> </Box>
<Button <Button
size="small" size="small"
startIcon={<Edit />} startIcon={<Edit />}
sx={{ color: 'primary.main' }} sx={{
...responsiveText.body2,
fontWeight: 600,
color: colors.purple,
}}
> >
</Button> </Button>
@ -212,19 +351,27 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
</Card> </Card>
{/* Terms Agreement */} {/* Terms Agreement */}
<Card elevation={0} sx={{ mb: 5, borderRadius: 3, bgcolor: 'grey.50' }}> <Card elevation={0} sx={{ ...cardStyles.default, bgcolor: colors.gray[50], mb: 10 }}>
<CardContent sx={{ p: 3 }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<FormControlLabel <FormControlLabel
control={ control={
<Checkbox <Checkbox
checked={agreeTerms} checked={agreeTerms}
onChange={(e) => setAgreeTerms(e.target.checked)} onChange={(e) => setAgreeTerms(e.target.checked)}
sx={{
color: colors.purple,
'&.Mui-checked': {
color: colors.purple,
},
}}
/> />
} }
label={ label={
<Typography variant="body2"> <Typography variant="body2" sx={{ ...responsiveText.body1 }}>
{' '} {' '}
<span style={{ color: 'red' }}>()</span> <Typography component="span" sx={{ color: colors.orange, fontWeight: 600 }}>
()
</Typography>
</Typography> </Typography>
} }
/> />
@ -232,7 +379,7 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
component="button" component="button"
variant="body2" variant="body2"
onClick={() => setTermsDialogOpen(true)} onClick={() => setTermsDialogOpen(true)}
sx={{ color: 'error.main', ml: 4, mt: 1 }} sx={{ ...responsiveText.body2, ml: 4, mt: 2, fontWeight: 600, color: colors.purple }}
> >
</Link> </Link>
@ -240,7 +387,7 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
</Card> </Card>
{/* Action Buttons */} {/* Action Buttons */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<Button <Button
fullWidth fullWidth
variant="contained" variant="contained"
@ -248,7 +395,20 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
disabled={!agreeTerms || isDeploying} disabled={!agreeTerms || isDeploying}
onClick={handleApprove} onClick={handleApprove}
startIcon={isDeploying ? null : <RocketLaunch />} startIcon={isDeploying ? null : <RocketLaunch />}
sx={{ py: 1.5 }} sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 700,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
'&:disabled': {
background: colors.gray[300],
},
}}
> >
{isDeploying ? '배포 중...' : '배포하기'} {isDeploying ? '배포 중...' : '배포하기'}
</Button> </Button>
@ -258,7 +418,20 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
size="large" size="large"
onClick={handleSaveDraft} onClick={handleSaveDraft}
startIcon={<Save />} startIcon={<Save />}
sx={{ py: 1.5 }} sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
borderWidth: 2,
borderColor: colors.gray[300],
color: colors.gray[700],
'&:hover': {
borderWidth: 2,
borderColor: colors.gray[400],
bgcolor: colors.gray[50],
},
}}
> >
</Button> </Button>
@ -271,39 +444,61 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
onClose={() => setTermsDialogOpen(false)} onClose={() => setTermsDialogOpen(false)}
maxWidth="sm" maxWidth="sm"
fullWidth fullWidth
PaperProps={{
sx: {
borderRadius: 4,
},
}}
> >
<DialogTitle> </DialogTitle> <DialogTitle sx={{ ...responsiveText.h3, fontWeight: 700, p: 6, pb: 4 }}>
<DialogContent>
<Typography variant="h6" sx={{ mb: 2 }}> </DialogTitle>
<DialogContent sx={{ px: 6, pb: 6 }}>
<Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 3 }}>
1 () 1 ()
</Typography> </Typography>
<Typography variant="body2" sx={{ mb: 3 }}> <Typography variant="body2" sx={{ ...responsiveText.body1, mb: 6, color: colors.gray[700] }}>
KT AI KT AI
. .
</Typography> </Typography>
<Typography variant="h6" sx={{ mb: 2 }}> <Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 3 }}>
2 ( ) 2 ( )
</Typography> </Typography>
<Typography variant="body2" sx={{ mb: 1 }}> <Typography variant="body2" sx={{ ...responsiveText.body1, mb: 2, color: colors.gray[700] }}>
항목: 이름, , 항목: 이름, ,
</Typography> </Typography>
<Typography variant="body2" sx={{ mb: 1 }}> <Typography variant="body2" sx={{ ...responsiveText.body1, mb: 2, color: colors.gray[700] }}>
목적: 이벤트 목적: 이벤트
</Typography> </Typography>
<Typography variant="body2" sx={{ mb: 3 }}> <Typography variant="body2" sx={{ ...responsiveText.body1, mb: 6, color: colors.gray[700] }}>
기간: 이벤트 6 기간: 이벤트 6
</Typography> </Typography>
<Typography variant="h6" sx={{ mb: 2 }}> <Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 3 }}>
3 ( ) 3 ( )
</Typography> </Typography>
<Typography variant="body2"> <Typography variant="body2" sx={{ ...responsiveText.body1, color: colors.gray[700] }}>
7 . 7 .
</Typography> </Typography>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions sx={{ p: 6, pt: 4 }}>
<Button onClick={() => setTermsDialogOpen(false)} variant="contained"> <Button
fullWidth
onClick={() => setTermsDialogOpen(false)}
variant="contained"
sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 700,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
}}
>
</Button> </Button>
</DialogActions> </DialogActions>
@ -316,18 +511,22 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
setSuccessDialogOpen(false); setSuccessDialogOpen(false);
onApprove(); onApprove();
}} }}
PaperProps={{
sx: {
borderRadius: 4,
},
}}
> >
<DialogContent sx={{ textAlign: 'center', py: 6, px: 4 }}> <DialogContent sx={{ textAlign: 'center', py: 10, px: 8 }}>
<CheckCircle sx={{ fontSize: 80, color: 'success.main', mb: 2 }} /> <CheckCircle sx={{ fontSize: 80, color: colors.purple, mb: 4 }} />
<Typography variant="h5" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="h5" sx={{ fontSize: '1.5rem', fontWeight: 700, mb: 3 }}>
! !
</Typography> </Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 4 }}> <Typography variant="body1" color="text.secondary" sx={{ fontSize: '1rem', mb: 8 }}>
. .
<br /> <br />
. .
</Typography> </Typography>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Button <Button
fullWidth fullWidth
variant="contained" variant="contained"
@ -336,10 +535,20 @@ export default function ApprovalStep({ eventData, onApprove, onBack }: ApprovalS
setSuccessDialogOpen(false); setSuccessDialogOpen(false);
onApprove(); onApprove();
}} }}
sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 700,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
}}
> >
</Button> </Button>
</Box>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</Box> </Box>

View File

@ -17,6 +17,24 @@ import {
} from '@mui/material'; } from '@mui/material';
import { ArrowBack } from '@mui/icons-material'; import { ArrowBack } from '@mui/icons-material';
// 디자인 시스템 색상
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',
},
};
interface Channel { interface Channel {
id: string; id: string;
name: string; name: string;
@ -89,19 +107,19 @@ export default function ChannelStep({ onNext, onBack }: ChannelStepProps) {
const selectedCount = channels.filter((ch) => ch.selected).length; const selectedCount = channels.filter((ch) => ch.selected).length;
return ( return (
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}> <Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 20 }}>
<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: 6, md: 8 } }}>
{/* Header */} {/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 4 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 8 }}>
<IconButton onClick={onBack}> <IconButton onClick={onBack} sx={{ width: 48, height: 48 }}>
<ArrowBack /> <ArrowBack />
</IconButton> </IconButton>
<Typography variant="h5" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ fontWeight: 700, fontSize: '1.5rem' }}>
</Typography> </Typography>
</Box> </Box>
<Typography variant="body2" color="text.secondary" sx={{ mb: 4, textAlign: 'center' }}> <Typography variant="body2" color="text.secondary" sx={{ mb: 8, textAlign: 'center', fontSize: '1rem' }}>
( 1 ) ( 1 )
</Typography> </Typography>
@ -109,21 +127,34 @@ export default function ChannelStep({ onNext, onBack }: ChannelStepProps) {
<Card <Card
elevation={0} elevation={0}
sx={{ sx={{
mb: 3, mb: 6,
borderRadius: 3, borderRadius: 4,
opacity: channels[0].selected ? 1 : 0.6, border: channels[0].selected ? 2 : 1,
transition: 'opacity 0.3s', borderColor: channels[0].selected ? colors.purple : 'divider',
bgcolor: channels[0].selected ? `${colors.purpleLight}40` : 'background.paper',
boxShadow: channels[0].selected ? '0 4px 12px rgba(0, 0, 0, 0.15)' : '0 2px 8px rgba(0, 0, 0, 0.08)',
transition: 'all 0.3s',
}} }}
> >
<CardContent sx={{ p: 4 }}> <CardContent sx={{ p: 6 }}>
<FormControlLabel <FormControlLabel
control={ control={
<Checkbox <Checkbox
checked={channels[0].selected} checked={channels[0].selected}
onChange={() => handleChannelToggle('uriTV')} onChange={() => handleChannelToggle('uriTV')}
sx={{
color: colors.purple,
'&.Mui-checked': {
color: colors.purple,
},
}}
/> />
} }
label="우리동네TV" label={
<Typography variant="body1" sx={{ fontWeight: channels[0].selected ? 700 : 600, fontSize: '1.125rem' }}>
TV
</Typography>
}
sx={{ mb: channels[0].selected ? 2 : 0 }} sx={{ mb: channels[0].selected ? 2 : 0 }}
/> />
@ -171,21 +202,34 @@ export default function ChannelStep({ onNext, onBack }: ChannelStepProps) {
<Card <Card
elevation={0} elevation={0}
sx={{ sx={{
mb: 3, mb: 6,
borderRadius: 3, borderRadius: 4,
opacity: channels[1].selected ? 1 : 0.6, border: channels[1].selected ? 2 : 1,
transition: 'opacity 0.3s', borderColor: channels[1].selected ? colors.purple : 'divider',
bgcolor: channels[1].selected ? `${colors.purpleLight}40` : 'background.paper',
boxShadow: channels[1].selected ? '0 4px 12px rgba(0, 0, 0, 0.15)' : '0 2px 8px rgba(0, 0, 0, 0.08)',
transition: 'all 0.3s',
}} }}
> >
<CardContent sx={{ p: 4 }}> <CardContent sx={{ p: 6 }}>
<FormControlLabel <FormControlLabel
control={ control={
<Checkbox <Checkbox
checked={channels[1].selected} checked={channels[1].selected}
onChange={() => handleChannelToggle('ringoBiz')} onChange={() => handleChannelToggle('ringoBiz')}
sx={{
color: colors.purple,
'&.Mui-checked': {
color: colors.purple,
},
}}
/> />
} }
label="링고비즈" label={
<Typography variant="body1" sx={{ fontWeight: channels[1].selected ? 700 : 600, fontSize: '1.125rem' }}>
</Typography>
}
sx={{ mb: channels[1].selected ? 2 : 0 }} sx={{ mb: channels[1].selected ? 2 : 0 }}
/> />
@ -217,21 +261,34 @@ export default function ChannelStep({ onNext, onBack }: ChannelStepProps) {
<Card <Card
elevation={0} elevation={0}
sx={{ sx={{
mb: 3, mb: 6,
borderRadius: 3, borderRadius: 4,
opacity: channels[2].selected ? 1 : 0.6, border: channels[2].selected ? 2 : 1,
transition: 'opacity 0.3s', borderColor: channels[2].selected ? colors.purple : 'divider',
bgcolor: channels[2].selected ? `${colors.purpleLight}40` : 'background.paper',
boxShadow: channels[2].selected ? '0 4px 12px rgba(0, 0, 0, 0.15)' : '0 2px 8px rgba(0, 0, 0, 0.08)',
transition: 'all 0.3s',
}} }}
> >
<CardContent sx={{ p: 4 }}> <CardContent sx={{ p: 6 }}>
<FormControlLabel <FormControlLabel
control={ control={
<Checkbox <Checkbox
checked={channels[2].selected} checked={channels[2].selected}
onChange={() => handleChannelToggle('genieTV')} onChange={() => handleChannelToggle('genieTV')}
sx={{
color: colors.purple,
'&.Mui-checked': {
color: colors.purple,
},
}}
/> />
} }
label="지니TV 광고" label={
<Typography variant="body1" sx={{ fontWeight: channels[2].selected ? 700 : 600, fontSize: '1.125rem' }}>
TV
</Typography>
}
sx={{ mb: channels[2].selected ? 2 : 0 }} sx={{ mb: channels[2].selected ? 2 : 0 }}
/> />
@ -290,21 +347,34 @@ export default function ChannelStep({ onNext, onBack }: ChannelStepProps) {
<Card <Card
elevation={0} elevation={0}
sx={{ sx={{
mb: 5, mb: 10,
borderRadius: 3, borderRadius: 4,
opacity: channels[3].selected ? 1 : 0.6, border: channels[3].selected ? 2 : 1,
transition: 'opacity 0.3s', borderColor: channels[3].selected ? colors.purple : 'divider',
bgcolor: channels[3].selected ? `${colors.purpleLight}40` : 'background.paper',
boxShadow: channels[3].selected ? '0 4px 12px rgba(0, 0, 0, 0.15)' : '0 2px 8px rgba(0, 0, 0, 0.08)',
transition: 'all 0.3s',
}} }}
> >
<CardContent sx={{ p: 4 }}> <CardContent sx={{ p: 6 }}>
<FormControlLabel <FormControlLabel
control={ control={
<Checkbox <Checkbox
checked={channels[3].selected} checked={channels[3].selected}
onChange={() => handleChannelToggle('sns')} onChange={() => handleChannelToggle('sns')}
sx={{
color: colors.purple,
'&.Mui-checked': {
color: colors.purple,
},
}}
/> />
} }
label="SNS" label={
<Typography variant="body1" sx={{ fontWeight: channels[3].selected ? 700 : 600, fontSize: '1.125rem' }}>
SNS
</Typography>
}
sx={{ mb: channels[3].selected ? 2 : 0 }} sx={{ mb: channels[3].selected ? 2 : 0 }}
/> />
@ -320,6 +390,12 @@ export default function ChannelStep({ onNext, onBack }: ChannelStepProps) {
onChange={(e) => onChange={(e) =>
handleConfigChange('sns', 'instagram', e.target.checked.toString()) handleConfigChange('sns', 'instagram', e.target.checked.toString())
} }
sx={{
color: colors.purple,
'&.Mui-checked': {
color: colors.purple,
},
}}
/> />
} }
label="Instagram" label="Instagram"
@ -332,6 +408,12 @@ export default function ChannelStep({ onNext, onBack }: ChannelStepProps) {
onChange={(e) => onChange={(e) =>
handleConfigChange('sns', 'naver', e.target.checked.toString()) handleConfigChange('sns', 'naver', e.target.checked.toString())
} }
sx={{
color: colors.purple,
'&.Mui-checked': {
color: colors.purple,
},
}}
/> />
} }
label="Naver Blog" label="Naver Blog"
@ -344,6 +426,12 @@ export default function ChannelStep({ onNext, onBack }: ChannelStepProps) {
onChange={(e) => onChange={(e) =>
handleConfigChange('sns', 'kakao', e.target.checked.toString()) handleConfigChange('sns', 'kakao', e.target.checked.toString())
} }
sx={{
color: colors.purple,
'&.Mui-checked': {
color: colors.purple,
},
}}
/> />
} }
label="Kakao Channel" label="Kakao Channel"
@ -374,17 +462,29 @@ export default function ChannelStep({ onNext, onBack }: ChannelStepProps) {
</Card> </Card>
{/* Summary */} {/* Summary */}
<Card elevation={0} sx={{ mb: 5, borderRadius: 3, bgcolor: 'grey.50' }}> <Card
<CardContent sx={{ p: 4 }}> elevation={0}
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}> sx={{
<Typography variant="h6"> </Typography> mb: 10,
<Typography variant="h6" color="error.main" sx={{ fontWeight: 700 }}> borderRadius: 4,
bgcolor: 'grey.50',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
}}
>
<CardContent sx={{ p: 8 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 4 }}>
<Typography variant="h6" sx={{ fontSize: '1.25rem' }}>
</Typography>
<Typography variant="h6" color="error.main" sx={{ fontWeight: 700, fontSize: '1.25rem' }}>
{totalCost.toLocaleString()} {totalCost.toLocaleString()}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ display: 'flex', justifyContent: 'space-between' }}> <Box sx={{ display: 'flex', justifyContent: 'space-between' }}>
<Typography variant="h6"> </Typography> <Typography variant="h6" sx={{ fontSize: '1.25rem' }}>
<Typography variant="h6" color="primary.main" sx={{ fontWeight: 700 }}>
</Typography>
<Typography variant="h6" sx={{ fontWeight: 700, fontSize: '1.25rem', color: colors.purple }}>
{totalExposure > 0 ? `${totalExposure.toLocaleString()}명+` : '0명'} {totalExposure > 0 ? `${totalExposure.toLocaleString()}명+` : '0명'}
</Typography> </Typography>
</Box> </Box>
@ -392,8 +492,23 @@ export default function ChannelStep({ onNext, onBack }: ChannelStepProps) {
</Card> </Card>
{/* Action Buttons */} {/* Action Buttons */}
<Box sx={{ display: 'flex', gap: 2 }}> <Box sx={{ display: 'flex', gap: 4 }}>
<Button fullWidth variant="outlined" size="large" onClick={onBack} sx={{ py: 1.5 }}> <Button
fullWidth
variant="outlined"
size="large"
onClick={onBack}
sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
borderWidth: 2,
'&:hover': {
borderWidth: 2,
},
}}
>
</Button> </Button>
<Button <Button
@ -402,7 +517,20 @@ export default function ChannelStep({ onNext, onBack }: ChannelStepProps) {
size="large" size="large"
disabled={selectedCount === 0} disabled={selectedCount === 0}
onClick={handleNext} onClick={handleNext}
sx={{ py: 1.5 }} sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 700,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
'&:disabled': {
background: colors.gray[300],
},
}}
> >
</Button> </Button>

View File

@ -11,6 +11,7 @@ import {
IconButton, IconButton,
} from '@mui/material'; } from '@mui/material';
import { ArrowBack, Edit } from '@mui/icons-material'; import { ArrowBack, Edit } from '@mui/icons-material';
import { cardStyles, colors, responsiveText } from '@/shared/lib/button-styles';
interface ContentEditStepProps { interface ContentEditStepProps {
initialTitle: string; initialTitle: string;
@ -39,71 +40,73 @@ export default function ContentEditStep({
}; };
return ( return (
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}> <Box sx={{ minHeight: '100vh', bgcolor: colors.gray[50], pt: { xs: 7, sm: 8 }, pb: 10 }}>
<Container maxWidth="lg" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}> <Container maxWidth="lg" sx={{ pt: 8, pb: 6, px: { xs: 6, sm: 8, md: 10 } }}>
{/* Header */} {/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 4 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 10 }}>
<IconButton onClick={onBack}> <IconButton onClick={onBack} sx={{ width: 48, height: 48 }}>
<ArrowBack /> <ArrowBack />
</IconButton> </IconButton>
<Typography variant="h5" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ ...responsiveText.h3, fontWeight: 700 }}>
</Typography> </Typography>
</Box> </Box>
<Grid container spacing={4}> <Grid container spacing={6}>
{/* Preview Section */} {/* Preview Section */}
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 3 }}> <Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 6 }}>
</Typography> </Typography>
<Card elevation={0} sx={{ borderRadius: 3 }}> <Card elevation={0} sx={{ ...cardStyles.default, height: '100%' }}>
<CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<Box <Box
sx={{ sx={{
width: '100%', width: '100%',
aspectRatio: '1 / 1', aspectRatio: '1 / 1',
bgcolor: 'grey.100', bgcolor: colors.gray[100],
borderRadius: '12px 12px 0 0', borderRadius: 3,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
p: 4, p: 6,
textAlign: 'center', textAlign: 'center',
}} }}
> >
<span className="material-icons" style={{ fontSize: 48, marginBottom: 16 }}> <span className="material-icons" style={{ fontSize: 64, marginBottom: 24, color: colors.purple }}>
celebration celebration
</span> </span>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 2 }}>
{title || '제목을 입력하세요'} {title || '제목을 입력하세요'}
</Typography> </Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}> <Typography variant="body1" color="text.secondary" sx={{ ...responsiveText.body1, mb: 2 }}>
{prize || '경품을 입력하세요'} {prize || '경품을 입력하세요'}
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary" sx={{ ...responsiveText.body2 }}>
{guide || '참여 안내를 입력하세요'} {guide || '참여 안내를 입력하세요'}
</Typography> </Typography>
</Box> </Box>
</CardContent>
</Card> </Card>
</Grid> </Grid>
{/* Edit Section */} {/* Edit Section */}
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 3 }}> <Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 6 }}>
</Typography> </Typography>
<Card elevation={0} sx={{ borderRadius: 3, mb: 3 }}> <Card elevation={0} sx={{ ...cardStyles.default, height: '100%' }}>
<CardContent sx={{ p: 4 }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 6 }}>
<Edit color="primary" /> <Edit sx={{ color: colors.purple, fontSize: 28 }} />
<Typography variant="h6" sx={{ fontWeight: 700 }}> <Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700 }}>
</Typography> </Typography>
</Box> </Box>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<Box> <Box>
<TextField <TextField
fullWidth fullWidth
@ -133,7 +136,7 @@ export default function ContentEditStep({
value={guide} value={guide}
onChange={(e) => setGuide(e.target.value)} onChange={(e) => setGuide(e.target.value)}
multiline multiline
rows={3} rows={4}
inputProps={{ maxLength: 100 }} inputProps={{ maxLength: 100 }}
helperText={`${guide.length}/100자`} helperText={`${guide.length}/100자`}
/> />
@ -145,11 +148,42 @@ export default function ContentEditStep({
</Grid> </Grid>
{/* Action Buttons */} {/* Action Buttons */}
<Box sx={{ display: 'flex', gap: 2, mt: 5 }}> <Box sx={{ display: 'flex', gap: 4, mt: 10 }}>
<Button fullWidth variant="outlined" size="large" onClick={handleSave} sx={{ py: 1.5 }}> <Button
fullWidth
variant="outlined"
size="large"
onClick={handleSave}
sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
borderWidth: 2,
'&:hover': {
borderWidth: 2,
},
}}
>
</Button> </Button>
<Button fullWidth variant="contained" size="large" onClick={handleNext} sx={{ py: 1.5 }}> <Button
fullWidth
variant="contained"
size="large"
onClick={handleNext}
sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 700,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
}}
>
</Button> </Button>
</Box> </Box>

View File

@ -11,9 +11,28 @@ import {
FormControlLabel, FormControlLabel,
IconButton, IconButton,
Dialog, Dialog,
Grid,
} from '@mui/material'; } from '@mui/material';
import { ArrowBack, ZoomIn, Psychology } from '@mui/icons-material'; import { ArrowBack, ZoomIn, Psychology } from '@mui/icons-material';
// 디자인 시스템 색상
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',
},
};
interface ImageStyle { interface ImageStyle {
id: string; id: string;
name: string; name: string;
@ -91,23 +110,23 @@ export default function ContentPreviewStep({
if (loading) { if (loading) {
return ( return (
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}> <Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 20 }}>
<Container maxWidth="md" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}> <Container maxWidth="lg" sx={{ pt: 8, pb: 8, px: { xs: 6, sm: 6, md: 8 } }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 4 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 8 }}>
<IconButton onClick={onBack}> <IconButton onClick={onBack}>
<ArrowBack /> <ArrowBack />
</IconButton> </IconButton>
<Typography variant="h5" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ fontWeight: 700, fontSize: '1.5rem' }}>
SNS SNS
</Typography> </Typography>
</Box> </Box>
<Box sx={{ textAlign: 'center', mt: 10, mb: 10 }}> <Box sx={{ textAlign: 'center', mt: 15, mb: 15 }}>
<Psychology <Psychology
sx={{ sx={{
fontSize: 64, fontSize: 80,
color: 'info.main', color: colors.purple,
mb: 2, mb: 4,
animation: 'spin 2s linear infinite', animation: 'spin 2s linear infinite',
'@keyframes spin': { '@keyframes spin': {
'0%': { transform: 'rotate(0deg)' }, '0%': { transform: 'rotate(0deg)' },
@ -115,15 +134,15 @@ export default function ContentPreviewStep({
}, },
}} }}
/> />
<Typography variant="h6" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="h5" sx={{ fontWeight: 700, mb: 3, fontSize: '1.5rem' }}>
AI AI
</Typography> </Typography>
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}> <Typography variant="body1" color="text.secondary" sx={{ mb: 3, fontSize: '1.125rem' }}>
<br /> <br />
... ...
</Typography> </Typography>
<Typography variant="caption" color="text.secondary"> <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1rem' }}>
시간: 5초 시간: 5초
</Typography> </Typography>
</Box> </Box>
@ -133,134 +152,148 @@ export default function ContentPreviewStep({
} }
return ( return (
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}> <Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 20 }}>
<Container maxWidth="md" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}> <Container maxWidth="lg" sx={{ pt: 8, pb: 8, px: { xs: 6, sm: 6, md: 8 } }}>
{/* Header */} {/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 4 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 8 }}>
<IconButton onClick={onBack}> <IconButton onClick={onBack}>
<ArrowBack /> <ArrowBack />
</IconButton> </IconButton>
<Typography variant="h5" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ fontWeight: 700, fontSize: '1.5rem' }}>
SNS SNS
</Typography> </Typography>
</Box> </Box>
<RadioGroup value={selectedStyle} onChange={(e) => handleStyleSelect(e.target.value)}> <Typography variant="body2" color="text.secondary" sx={{ mb: 8, textAlign: 'center', fontSize: '1rem' }}>
{imageStyles.map((style) => (
<Box key={style.id} sx={{ mb: 4 }}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 2 }}>
{style.name}
</Typography> </Typography>
<RadioGroup value={selectedStyle} onChange={(e) => handleStyleSelect(e.target.value)}>
<Grid container spacing={6} sx={{ mb: 10 }}>
{imageStyles.map((style) => (
<Grid item xs={12} md={4} key={style.id}>
<Card <Card
elevation={0} elevation={0}
sx={{ sx={{
cursor: 'pointer', cursor: 'pointer',
borderRadius: 3, borderRadius: 4,
border: selectedStyle === style.id ? 3 : 1, border: selectedStyle === style.id ? 2 : 1,
borderColor: selectedStyle === style.id ? 'error.main' : 'divider', borderColor: selectedStyle === style.id ? colors.purple : 'divider',
bgcolor: selectedStyle === style.id ? `${colors.purpleLight}40` : 'background.paper',
transition: 'all 0.3s', transition: 'all 0.3s',
position: 'relative', position: 'relative',
boxShadow: selectedStyle === style.id ? '0 4px 12px rgba(0, 0, 0, 0.15)' : '0 2px 8px rgba(0, 0, 0, 0.08)',
'&:hover': { '&:hover': {
borderColor: colors.purple,
transform: 'translateY(-2px)', transform: 'translateY(-2px)',
boxShadow: '0 8px 16px rgba(0, 0, 0, 0.1)', boxShadow: '0 8px 16px rgba(0, 0, 0, 0.15)',
}, },
}} }}
onClick={() => handleStyleSelect(style.id)} onClick={() => handleStyleSelect(style.id)}
> >
{selectedStyle === style.id && (
<Box
sx={{
position: 'absolute',
top: 16,
right: 16,
width: 32,
height: 32,
borderRadius: '50%',
bgcolor: 'error.main',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
zIndex: 10,
}}
>
<span className="material-icons" style={{ fontSize: 20 }}>
check
</span>
</Box>
)}
<CardContent sx={{ p: 0 }}> <CardContent sx={{ p: 0 }}>
{/* 스타일 이름 */}
<Box sx={{ p: 4, borderBottom: 1, borderColor: 'divider', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h6" sx={{ fontWeight: 700, fontSize: '1.125rem' }}>
{style.name}
</Typography>
<FormControlLabel
value={style.id}
control={<Radio sx={{ color: colors.purple, '&.Mui-checked': { color: colors.purple } }} />}
label=""
sx={{ m: 0 }}
/>
</Box>
{/* 이미지 프리뷰 */}
<Box <Box
sx={{ sx={{
width: '100%', width: '100%',
aspectRatio: '1 / 1', aspectRatio: '1 / 1',
background: style.gradient || '#f5f5f5', background: style.gradient || colors.gray[100],
borderRadius: '12px 12px 0 0',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
p: 4, p: 6,
textAlign: 'center', textAlign: 'center',
}} }}
> >
<span <span
className="material-icons" className="material-icons"
style={{ style={{
fontSize: 48, fontSize: 64,
marginBottom: 16, marginBottom: 24,
color: style.textColor || 'inherit', color: style.textColor || colors.gray[700],
}} }}
> >
{style.icon} {style.icon}
</span> </span>
<Typography <Typography
variant="body1" variant="h6"
sx={{ sx={{
fontWeight: 600, fontWeight: 700,
mb: 1, mb: 2,
color: style.textColor || 'text.primary', color: style.textColor || 'text.primary',
fontSize: '1.25rem',
}} }}
> >
{title} {title}
</Typography> </Typography>
<Typography <Typography
variant="caption" variant="body1"
sx={{ sx={{
color: style.textColor || 'text.secondary', color: style.textColor || 'text.secondary',
opacity: style.textColor ? 0.9 : 1, opacity: style.textColor ? 0.9 : 1,
fontSize: '1rem',
}} }}
> >
{prize} {prize}
</Typography> </Typography>
</Box> </Box>
<Box sx={{ p: 2, display: 'flex', justifyContent: 'flex-end' }}> {/* 크게보기 버튼 */}
<Box sx={{ p: 4, display: 'flex', justifyContent: 'center' }}>
<Button <Button
size="small" variant="outlined"
startIcon={<ZoomIn />} startIcon={<ZoomIn />}
onClick={(e) => handlePreview(style, e)} onClick={(e) => handlePreview(style, e)}
sx={{
borderRadius: 2,
py: 1.5,
px: 4,
fontSize: '0.875rem',
fontWeight: 600,
}}
> >
</Button> </Button>
<FormControlLabel
value={style.id}
control={<Radio sx={{ display: 'none' }} />}
label=""
sx={{ display: 'none' }}
/>
</Box> </Box>
</CardContent> </CardContent>
</Card> </Card>
</Box> </Grid>
))} ))}
</Grid>
</RadioGroup> </RadioGroup>
{/* Action Buttons */} {/* Action Buttons */}
<Box sx={{ display: 'flex', gap: 2, mt: 5 }}> <Box sx={{ display: 'flex', gap: 4 }}>
<Button fullWidth variant="outlined" size="large" onClick={onSkip} sx={{ py: 1.5 }}> <Button
fullWidth
variant="outlined"
size="large"
onClick={onSkip}
sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
borderWidth: 2,
'&:hover': {
borderWidth: 2,
},
}}
>
</Button> </Button>
<Button <Button
@ -269,7 +302,20 @@ export default function ContentPreviewStep({
size="large" size="large"
disabled={!selectedStyle} disabled={!selectedStyle}
onClick={handleNext} onClick={handleNext}
sx={{ py: 1.5 }} sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 700,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
'&:disabled': {
background: colors.gray[300],
},
}}
> >
</Button> </Button>
@ -321,7 +367,7 @@ export default function ContentPreviewStep({
maxWidth: 600, maxWidth: 600,
aspectRatio: '1 / 1', aspectRatio: '1 / 1',
background: fullscreenStyle.gradient || '#f5f5f5', background: fullscreenStyle.gradient || '#f5f5f5',
borderRadius: 3, borderRadius: 4,
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',

View File

@ -14,6 +14,24 @@ import {
import { AutoAwesome, TrendingUp, Replay, Store, Campaign } from '@mui/icons-material'; import { AutoAwesome, TrendingUp, Replay, Store, Campaign } from '@mui/icons-material';
import { EventObjective } from '../page'; import { EventObjective } from '../page';
// 디자인 시스템 색상
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',
},
};
interface ObjectiveOption { interface ObjectiveOption {
id: EventObjective; id: EventObjective;
icon: React.ReactNode; icon: React.ReactNode;
@ -62,54 +80,56 @@ export default function ObjectiveStep({ onNext }: ObjectiveStepProps) {
}; };
return ( return (
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}> <Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 20 }}>
<Container maxWidth="md" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}> <Container maxWidth="md" sx={{ pt: 8, pb: 8, px: { xs: 6, sm: 6, md: 8 } }}>
{/* Title Section */} {/* Title Section */}
<Box sx={{ mb: 5, textAlign: 'center' }}> <Box sx={{ mb: 10, textAlign: 'center' }}>
<AutoAwesome sx={{ fontSize: 64, color: 'primary.main', mb: 2 }} /> <AutoAwesome sx={{ fontSize: 80, color: colors.purple, mb: 4 }} />
<Typography variant="h4" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="h4" sx={{ fontWeight: 700, mb: 4, fontSize: '2rem' }}>
</Typography> </Typography>
<Typography variant="body1" color="text.secondary"> <Typography variant="body1" color="text.secondary" sx={{ fontSize: '1.125rem' }}>
AI가 AI가
</Typography> </Typography>
</Box> </Box>
{/* Purpose Options */} {/* Purpose Options */}
<RadioGroup value={selected} onChange={(e) => setSelected(e.target.value as EventObjective)}> <RadioGroup value={selected} onChange={(e) => setSelected(e.target.value as EventObjective)}>
<Grid container spacing={3} sx={{ mb: 5 }}> <Grid container spacing={6} sx={{ mb: 10 }}>
{objectives.map((objective) => ( {objectives.map((objective) => (
<Grid item xs={12} sm={6} key={objective.id}> <Grid item xs={12} sm={6} key={objective.id}>
<Card <Card
elevation={0} elevation={0}
sx={{ sx={{
cursor: 'pointer', cursor: 'pointer',
borderRadius: 3, borderRadius: 4,
border: selected === objective.id ? 2 : 1, border: selected === objective.id ? 2 : 1,
borderColor: selected === objective.id ? 'primary.main' : 'divider', borderColor: selected === objective.id ? colors.purple : 'divider',
bgcolor: selected === objective.id ? 'action.selected' : 'background.paper', bgcolor: selected === objective.id ? `${colors.purpleLight}40` : 'background.paper',
transition: 'all 0.2s', transition: 'all 0.2s',
boxShadow: selected === objective.id ? '0 4px 12px rgba(0, 0, 0, 0.15)' : '0 2px 8px rgba(0, 0, 0, 0.08)',
'&:hover': { '&:hover': {
borderColor: 'primary.main', borderColor: colors.purple,
boxShadow: selected === objective.id ? '0 4px 12px rgba(0, 0, 0, 0.15)' : '0 2px 8px rgba(0, 0, 0, 0.1)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
transform: 'translateY(-2px)',
}, },
}} }}
onClick={() => setSelected(objective.id)} onClick={() => setSelected(objective.id)}
> >
<CardContent sx={{ p: 4 }}> <CardContent sx={{ p: 6 }}>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 2, mb: 2 }}> <Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 3, mb: 3 }}>
<Box sx={{ color: 'primary.main' }}>{objective.icon}</Box> <Box sx={{ color: colors.purple }}>{objective.icon}</Box>
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="h6" sx={{ fontWeight: 700, mb: 2, fontSize: '1.25rem' }}>
{objective.title} {objective.title}
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1rem' }}>
{objective.description} {objective.description}
</Typography> </Typography>
</Box> </Box>
<FormControlLabel <FormControlLabel
value={objective.id} value={objective.id}
control={<Radio />} control={<Radio sx={{ color: selected === objective.id ? colors.purple : undefined }} />}
label="" label=""
sx={{ m: 0 }} sx={{ m: 0 }}
/> />
@ -125,14 +145,15 @@ export default function ObjectiveStep({ onNext }: ObjectiveStepProps) {
<Card <Card
elevation={0} elevation={0}
sx={{ sx={{
mb: 5, mb: 10,
bgcolor: 'primary.light', background: `linear-gradient(135deg, ${colors.purpleLight} 0%, ${colors.blue}20 100%)`,
borderRadius: 3, borderRadius: 4,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
}} }}
> >
<CardContent sx={{ display: 'flex', gap: 2, p: 3 }}> <CardContent sx={{ display: 'flex', gap: 3, p: 6 }}>
<AutoAwesome sx={{ color: 'primary.main' }} /> <AutoAwesome sx={{ color: colors.purple, fontSize: 28 }} />
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" sx={{ fontSize: '1rem', lineHeight: 1.8, color: colors.gray[700] }}>
AI가 , , . AI가 , , .
</Typography> </Typography>
</CardContent> </CardContent>
@ -146,7 +167,20 @@ export default function ObjectiveStep({ onNext }: ObjectiveStepProps) {
size="large" size="large"
disabled={!selected} disabled={!selected}
onClick={handleNext} onClick={handleNext}
sx={{ py: 1.5 }} sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 700,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
'&:disabled': {
background: colors.gray[300],
},
}}
> >
</Button> </Button>

View File

@ -19,6 +19,24 @@ import {
import { ArrowBack, Edit, Insights } from '@mui/icons-material'; import { ArrowBack, Edit, Insights } from '@mui/icons-material';
import { EventObjective, BudgetLevel, EventMethod } from '../page'; import { EventObjective, BudgetLevel, EventMethod } from '../page';
// 디자인 시스템 색상
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',
},
};
interface Recommendation { interface Recommendation {
id: string; id: string;
budget: BudgetLevel; budget: BudgetLevel;
@ -144,49 +162,56 @@ export default function RecommendationStep({ onNext, onBack }: RecommendationSte
}; };
return ( return (
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}> <Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 20 }}>
<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: 6, md: 8 } }}>
{/* Header */} {/* Header */}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 4 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 3, mb: 8 }}>
<IconButton onClick={onBack}> <IconButton onClick={onBack} sx={{ width: 48, height: 48 }}>
<ArrowBack /> <ArrowBack />
</IconButton> </IconButton>
<Typography variant="h5" sx={{ fontWeight: 700 }}> <Typography variant="h5" sx={{ fontWeight: 700, fontSize: '1.5rem' }}>
AI AI
</Typography> </Typography>
</Box> </Box>
{/* Trends Analysis */} {/* Trends Analysis */}
<Card elevation={0} sx={{ mb: 5, borderRadius: 3 }}> <Card
<CardContent sx={{ p: 4 }}> elevation={0}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, mb: 3 }}> sx={{
<Insights color="primary" /> mb: 10,
<Typography variant="h6" sx={{ fontWeight: 700 }}> borderRadius: 4,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
}}
>
<CardContent sx={{ p: 8 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 6 }}>
<Insights sx={{ fontSize: 32, color: colors.purple }} />
<Typography variant="h6" sx={{ fontWeight: 700, fontSize: '1.25rem' }}>
AI AI
</Typography> </Typography>
</Box> </Box>
<Grid container spacing={3}> <Grid container spacing={6}>
<Grid item xs={12} md={4}> <Grid item xs={12} md={4}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}> <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 2, fontSize: '1rem' }}>
📍 📍
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1rem' }}>
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={12} md={4}> <Grid item xs={12} md={4}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}> <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 2, fontSize: '1rem' }}>
🗺 🗺
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1rem' }}>
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={12} md={4}> <Grid item xs={12} md={4}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}> <Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 2, fontSize: '1rem' }}>
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1rem' }}>
</Typography> </Typography>
</Grid> </Grid>
@ -195,52 +220,67 @@ export default function RecommendationStep({ onNext, onBack }: RecommendationSte
</Card> </Card>
{/* Budget Selection */} {/* Budget Selection */}
<Box sx={{ mb: 4 }}> <Box sx={{ mb: 8 }}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 2 }}> <Typography variant="h6" sx={{ fontWeight: 700, mb: 4, fontSize: '1.25rem' }}>
</Typography> </Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 3 }}> <Typography variant="body2" color="text.secondary" sx={{ mb: 6, fontSize: '1rem' }}>
2 ( 1, 1) 2 ( 1, 1)
</Typography> </Typography>
<Tabs <Tabs
value={selectedBudget} value={selectedBudget}
onChange={(_, value) => setSelectedBudget(value)} onChange={(_, value) => setSelectedBudget(value)}
variant="fullWidth" variant="fullWidth"
sx={{ mb: 4 }} sx={{ mb: 8 }}
> >
<Tab label="💰 저비용" value="low" /> <Tab
<Tab label="💰💰 중비용" value="medium" /> label="💰 저비용"
<Tab label="💰💰💰 고비용" value="high" /> value="low"
sx={{ py: 3, fontSize: '1rem', fontWeight: 600 }}
/>
<Tab
label="💰💰 중비용"
value="medium"
sx={{ py: 3, fontSize: '1rem', fontWeight: 600 }}
/>
<Tab
label="💰💰💰 고비용"
value="high"
sx={{ py: 3, fontSize: '1rem', fontWeight: 600 }}
/>
</Tabs> </Tabs>
</Box> </Box>
{/* Recommendations */} {/* Recommendations */}
<RadioGroup value={selected} onChange={(e) => setSelected(e.target.value)}> <RadioGroup value={selected} onChange={(e) => setSelected(e.target.value)}>
<Grid container spacing={3} sx={{ mb: 5 }}> <Grid container spacing={6} sx={{ mb: 10 }}>
{budgetRecommendations.map((rec) => ( {budgetRecommendations.map((rec) => (
<Grid item xs={12} md={6} key={rec.id}> <Grid item xs={12} md={6} key={rec.id}>
<Card <Card
elevation={0} elevation={0}
sx={{ sx={{
cursor: 'pointer', cursor: 'pointer',
borderRadius: 3, borderRadius: 4,
border: selected === rec.id ? 2 : 1, border: selected === rec.id ? 2 : 1,
borderColor: selected === rec.id ? 'primary.main' : 'divider', borderColor: selected === rec.id ? colors.purple : 'divider',
bgcolor: selected === rec.id ? 'action.selected' : 'background.paper', bgcolor: selected === rec.id ? `${colors.purpleLight}40` : 'background.paper',
transition: 'all 0.2s', transition: 'all 0.2s',
boxShadow: selected === rec.id ? '0 4px 12px rgba(0, 0, 0, 0.15)' : '0 2px 8px rgba(0, 0, 0, 0.08)',
'&:hover': { '&:hover': {
borderColor: 'primary.main', borderColor: colors.purple,
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)', boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
transform: 'translateY(-2px)',
}, },
}} }}
onClick={() => setSelected(rec.id)} onClick={() => setSelected(rec.id)}
> >
<CardContent sx={{ p: 4 }}> <CardContent sx={{ p: 6 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 2 }}> <Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', mb: 4 }}>
<Chip <Chip
label={rec.method === 'online' ? '🌐 온라인' : '🏪 오프라인'} label={rec.method === 'online' ? '🌐 온라인' : '🏪 오프라인'}
color={rec.method === 'online' ? 'primary' : 'secondary'} color={rec.method === 'online' ? 'primary' : 'secondary'}
size="small" size="medium"
sx={{ fontSize: '0.875rem', py: 2 }}
/> />
<FormControlLabel value={rec.id} control={<Radio />} label="" sx={{ m: 0 }} /> <FormControlLabel value={rec.id} control={<Radio />} label="" sx={{ m: 0 }} />
</Box> </Box>
@ -251,59 +291,61 @@ export default function RecommendationStep({ onNext, onBack }: RecommendationSte
value={editedData[rec.id]?.title || rec.title} value={editedData[rec.id]?.title || rec.title}
onChange={(e) => handleEditTitle(rec.id, e.target.value)} onChange={(e) => handleEditTitle(rec.id, e.target.value)}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
sx={{ mb: 2 }} sx={{ mb: 4 }}
InputProps={{ InputProps={{
endAdornment: <Edit fontSize="small" color="action" />, endAdornment: <Edit fontSize="small" color="action" />,
sx: { fontSize: '1rem', py: 2 },
}} }}
/> />
<Box sx={{ mb: 2 }}> <Box sx={{ mb: 4 }}>
<Typography variant="caption" color="text.secondary" sx={{ mb: 0.5, display: 'block' }}> <Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block', fontSize: '0.875rem' }}>
</Typography> </Typography>
<TextField <TextField
fullWidth fullWidth
size="small" size="medium"
variant="outlined" variant="outlined"
value={editedData[rec.id]?.prize || rec.prize} value={editedData[rec.id]?.prize || rec.prize}
onChange={(e) => handleEditPrize(rec.id, e.target.value)} onChange={(e) => handleEditPrize(rec.id, e.target.value)}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
InputProps={{ InputProps={{
endAdornment: <Edit fontSize="small" color="action" />, endAdornment: <Edit fontSize="small" color="action" />,
sx: { fontSize: '1rem' },
}} }}
/> />
</Box> </Box>
<Grid container spacing={2} sx={{ mt: 2 }}> <Grid container spacing={4} sx={{ mt: 4 }}>
<Grid item xs={6}> <Grid item xs={6}>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.875rem' }}>
</Typography> </Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}> <Typography variant="body2" sx={{ fontWeight: 600, fontSize: '1rem', mt: 1 }}>
{rec.participationMethod} {rec.participationMethod}
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.875rem' }}>
</Typography> </Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}> <Typography variant="body2" sx={{ fontWeight: 600, fontSize: '1rem', mt: 1 }}>
{rec.expectedParticipants} {rec.expectedParticipants}
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.875rem' }}>
</Typography> </Typography>
<Typography variant="body2" sx={{ fontWeight: 600 }}> <Typography variant="body2" sx={{ fontWeight: 600, fontSize: '1rem', mt: 1 }}>
{(rec.estimatedCost / 10000).toFixed(0)} {(rec.estimatedCost / 10000).toFixed(0)}
</Typography> </Typography>
</Grid> </Grid>
<Grid item xs={6}> <Grid item xs={6}>
<Typography variant="caption" color="text.secondary"> <Typography variant="caption" color="text.secondary" sx={{ fontSize: '0.875rem' }}>
</Typography> </Typography>
<Typography variant="body2" sx={{ fontWeight: 600, color: 'error.main' }}> <Typography variant="body2" sx={{ fontWeight: 600, color: 'error.main', fontSize: '1rem', mt: 1 }}>
{rec.roi}% {rec.roi}%
</Typography> </Typography>
</Grid> </Grid>
@ -316,11 +358,46 @@ export default function RecommendationStep({ onNext, onBack }: RecommendationSte
</RadioGroup> </RadioGroup>
{/* Action Buttons */} {/* Action Buttons */}
<Box sx={{ display: 'flex', gap: 2 }}> <Box sx={{ display: 'flex', gap: 4 }}>
<Button fullWidth variant="outlined" size="large" onClick={onBack} sx={{ py: 1.5 }}> <Button
fullWidth
variant="outlined"
size="large"
onClick={onBack}
sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 600,
borderWidth: 2,
'&:hover': {
borderWidth: 2,
},
}}
>
</Button> </Button>
<Button fullWidth variant="contained" size="large" disabled={!selected} onClick={handleNext} sx={{ py: 1.5 }}> <Button
fullWidth
variant="contained"
size="large"
disabled={!selected}
onClick={handleNext}
sx={{
py: 3,
borderRadius: 3,
fontSize: '1rem',
fontWeight: 700,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
'&:disabled': {
background: colors.gray[300],
},
}}
>
</Button> </Button>
</Box> </Box>

View File

@ -13,13 +13,34 @@ import {
Card, Card,
CardContent, CardContent,
Typography, Typography,
Chip,
InputAdornment, InputAdornment,
Pagination, Pagination,
Grid, Grid,
LinearProgress,
Chip,
} from '@mui/material'; } from '@mui/material';
import { Search, FilterList } from '@mui/icons-material'; import {
Search,
FilterList,
Event,
TrendingUp,
People,
CardGiftcard,
Phone,
Share,
ShoppingCart,
Email,
LocalFireDepartment,
NewReleases,
Warning,
Star,
} from '@mui/icons-material';
import Header from '@/shared/ui/Header'; import Header from '@/shared/ui/Header';
import {
cardStyles,
colors,
responsiveText,
} from '@/shared/lib/button-styles';
// Mock 데이터 // Mock 데이터
const mockEvents = [ const mockEvents = [
@ -29,11 +50,16 @@ const mockEvents = [
status: 'active' as const, status: 'active' as const,
daysLeft: 5, daysLeft: 5,
participants: 128, participants: 128,
targetParticipants: 200,
roi: 450, roi: 450,
startDate: '2025-11-01', startDate: '2025-11-01',
endDate: '2025-11-15', endDate: '2025-11-15',
prize: '커피 쿠폰', prize: '커피 쿠폰',
method: '전화번호 입력', method: '전화번호 입력',
isUrgent: true,
isPopular: false,
isHighROI: true,
isNew: false,
}, },
{ {
id: '2', id: '2',
@ -41,11 +67,16 @@ const mockEvents = [
status: 'active' as const, status: 'active' as const,
daysLeft: 12, daysLeft: 12,
participants: 56, participants: 56,
targetParticipants: 100,
roi: 320, roi: 320,
startDate: '2025-11-05', startDate: '2025-11-05',
endDate: '2025-11-20', endDate: '2025-11-20',
prize: '할인 쿠폰', prize: '할인 쿠폰',
method: 'SNS 팔로우', method: 'SNS 팔로우',
isUrgent: false,
isPopular: false,
isHighROI: false,
isNew: false,
}, },
{ {
id: '3', id: '3',
@ -53,11 +84,16 @@ const mockEvents = [
status: 'ended' as const, status: 'ended' as const,
daysLeft: 0, daysLeft: 0,
participants: 234, participants: 234,
targetParticipants: 150,
roi: 580, roi: 580,
startDate: '2025-10-15', startDate: '2025-10-15',
endDate: '2025-10-31', endDate: '2025-10-31',
prize: '상품권', prize: '상품권',
method: '구매 인증', method: '구매 인증',
isUrgent: false,
isPopular: true,
isHighROI: true,
isNew: false,
}, },
{ {
id: '4', id: '4',
@ -65,11 +101,16 @@ const mockEvents = [
status: 'scheduled' as const, status: 'scheduled' as const,
daysLeft: 30, daysLeft: 30,
participants: 0, participants: 0,
targetParticipants: 300,
roi: 0, roi: 0,
startDate: '2025-12-01', startDate: '2025-12-01',
endDate: '2025-12-15', endDate: '2025-12-15',
prize: '체험권', prize: '체험권',
method: '이메일 등록', method: '이메일 등록',
isUrgent: false,
isPopular: false,
isHighROI: false,
isNew: true,
}, },
]; ];
@ -114,16 +155,28 @@ export default function EventsPage() {
router.push(`/events/${eventId}`); router.push(`/events/${eventId}`);
}; };
const getStatusColor = (status: string) => { const getStatusStyle = (status: string) => {
switch (status) { switch (status) {
case 'active': case 'active':
return 'success'; return {
bgcolor: colors.mint,
color: 'white',
};
case 'scheduled': case 'scheduled':
return 'info'; return {
bgcolor: colors.blue,
color: 'white',
};
case 'ended': case 'ended':
return 'default'; return {
bgcolor: colors.gray[300],
color: colors.gray[700],
};
default: default:
return 'default'; return {
bgcolor: colors.gray[200],
color: colors.gray[600],
};
} }
}; };
@ -140,13 +193,209 @@ export default function EventsPage() {
} }
}; };
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 <Event sx={{ fontSize: 18 }} />;
}
};
const calculateProgress = (event: typeof mockEvents[0]) => {
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);
};
// 통계 계산
const stats = {
total: mockEvents.length,
active: mockEvents.filter((e) => e.status === 'active').length,
totalParticipants: mockEvents.reduce((sum, e) => sum + e.participants, 0),
avgROI: Math.round(
mockEvents.filter((e) => e.roi > 0).reduce((sum, e) => sum + e.roi, 0) /
mockEvents.filter((e) => e.roi > 0).length
),
};
return ( return (
<> <>
<Header title="이벤트 목록" showBack={true} showMenu={false} showProfile={true} /> <Header title="이벤트 목록" showBack={true} showMenu={false} showProfile={true} />
<Box sx={{ pt: { xs: 7, sm: 8 }, minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}> <Box
<Container maxWidth="lg" sx={{ pt: 4, pb: 4, px: { xs: 3, sm: 3, md: 4 } }}> 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 } }}>
{/* Summary Statistics */}
<Grid container spacing={4} sx={{ mb: 8 }}>
<Grid item xs={6} sm={3}>
<Card
elevation={0}
sx={{
...cardStyles.default,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
borderColor: 'transparent',
}}
>
<CardContent sx={{ textAlign: 'center', py: 4, px: 3 }}>
<Event sx={{
fontSize: 32,
color: colors.gray[900],
mb: 1,
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
}} />
<Typography
variant="h4"
sx={{
fontWeight: 700,
color: colors.gray[900],
fontSize: '1.75rem',
mb: 0.5,
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}
>
{stats.total}
</Typography>
<Typography variant="body2" sx={{
color: colors.gray[700],
fontSize: '0.875rem',
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
}}>
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={6} sm={3}>
<Card
elevation={0}
sx={{
...cardStyles.default,
background: `linear-gradient(135deg, ${colors.mint} 0%, ${colors.mintLight} 100%)`,
borderColor: 'transparent',
}}
>
<CardContent sx={{ textAlign: 'center', py: 4, px: 3 }}>
<LocalFireDepartment sx={{
fontSize: 32,
color: colors.gray[900],
mb: 1,
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
}} />
<Typography
variant="h4"
sx={{
fontWeight: 700,
color: colors.gray[900],
fontSize: '1.75rem',
mb: 0.5,
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}
>
{stats.active}
</Typography>
<Typography variant="body2" sx={{
color: colors.gray[700],
fontSize: '0.875rem',
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
}}>
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={6} sm={3}>
<Card
elevation={0}
sx={{
...cardStyles.default,
background: `linear-gradient(135deg, ${colors.blue} 0%, ${colors.blueLight} 100%)`,
borderColor: 'transparent',
}}
>
<CardContent sx={{ textAlign: 'center', py: 4, px: 3 }}>
<People sx={{
fontSize: 32,
color: colors.gray[900],
mb: 1,
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
}} />
<Typography
variant="h4"
sx={{
fontWeight: 700,
color: colors.gray[900],
fontSize: '1.75rem',
mb: 0.5,
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}
>
{stats.totalParticipants}
</Typography>
<Typography variant="body2" sx={{
color: colors.gray[700],
fontSize: '0.875rem',
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
}}>
</Typography>
</CardContent>
</Card>
</Grid>
<Grid item xs={6} sm={3}>
<Card
elevation={0}
sx={{
...cardStyles.default,
background: `linear-gradient(135deg, ${colors.orange} 0%, ${colors.orangeLight} 100%)`,
borderColor: 'transparent',
}}
>
<CardContent sx={{ textAlign: 'center', py: 4, px: 3 }}>
<TrendingUp sx={{
fontSize: 32,
color: colors.gray[900],
mb: 1,
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
}} />
<Typography
variant="h4"
sx={{
fontWeight: 700,
color: colors.gray[900],
fontSize: '1.75rem',
mb: 0.5,
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}
>
{stats.avgROI}%
</Typography>
<Typography variant="body2" sx={{
color: colors.gray[700],
fontSize: '0.875rem',
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
}}>
ROI
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
{/* Search Section */} {/* Search Section */}
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 8 }}>
<TextField <TextField
fullWidth fullWidth
placeholder="이벤트명 검색..." placeholder="이벤트명 검색..."
@ -155,28 +404,51 @@ export default function EventsPage() {
InputProps={{ InputProps={{
startAdornment: ( startAdornment: (
<InputAdornment position="start"> <InputAdornment position="start">
<Search /> <Search sx={{ color: colors.gray[400] }} />
</InputAdornment> </InputAdornment>
), ),
}} }}
sx={{ sx={{
'& .MuiOutlinedInput-root': { '& .MuiOutlinedInput-root': {
borderRadius: 2, borderRadius: 3,
bgcolor: 'white',
'& fieldset': {
borderColor: colors.gray[200],
},
'&:hover fieldset': {
borderColor: colors.gray[300],
},
'&.Mui-focused fieldset': {
borderColor: colors.purple,
},
}, },
}} }}
/> />
</Box> </Box>
{/* Filters */} {/* Filters */}
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 8 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, flexWrap: 'wrap' }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 4, flexWrap: 'wrap' }}>
<FilterList color="error" /> <FilterList sx={{ color: colors.purple }} />
<FormControl sx={{ flex: 1, minWidth: 120 }}> <FormControl sx={{ flex: 1, minWidth: 120 }}>
<InputLabel></InputLabel> <InputLabel></InputLabel>
<Select <Select
value={statusFilter} value={statusFilter}
label="상태" label="상태"
onChange={(e) => setStatusFilter(e.target.value as EventStatus)} onChange={(e) => setStatusFilter(e.target.value as EventStatus)}
sx={{
borderRadius: 2,
bgcolor: 'white',
'& .MuiOutlinedInput-notchedOutline': {
borderColor: colors.gray[200],
},
'&:hover .MuiOutlinedInput-notchedOutline': {
borderColor: colors.gray[300],
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: colors.purple,
},
}}
> >
<MenuItem value="all"></MenuItem> <MenuItem value="all"></MenuItem>
<MenuItem value="active"></MenuItem> <MenuItem value="active"></MenuItem>
@ -190,6 +462,19 @@ export default function EventsPage() {
value={periodFilter} value={periodFilter}
label="기간" label="기간"
onChange={(e) => setPeriodFilter(e.target.value as Period)} onChange={(e) => setPeriodFilter(e.target.value as Period)}
sx={{
borderRadius: 2,
bgcolor: 'white',
'& .MuiOutlinedInput-notchedOutline': {
borderColor: colors.gray[200],
},
'&:hover .MuiOutlinedInput-notchedOutline': {
borderColor: colors.gray[300],
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: colors.purple,
},
}}
> >
<MenuItem value="1month"> 1</MenuItem> <MenuItem value="1month"> 1</MenuItem>
<MenuItem value="3months"> 3</MenuItem> <MenuItem value="3months"> 3</MenuItem>
@ -202,16 +487,29 @@ export default function EventsPage() {
</Box> </Box>
{/* Sorting */} {/* Sorting */}
<Box sx={{ mb: 3 }}> <Box sx={{ mb: 8 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" sx={{ ...responsiveText.body2, fontWeight: 600 }}>
:
</Typography> </Typography>
<FormControl sx={{ width: 160 }}> <FormControl sx={{ width: 160 }}>
<Select <Select
value={sortBy} value={sortBy}
onChange={(e) => setSortBy(e.target.value as SortBy)} onChange={(e) => setSortBy(e.target.value as SortBy)}
size="small" size="small"
sx={{
borderRadius: 2,
bgcolor: 'white',
'& .MuiOutlinedInput-notchedOutline': {
borderColor: colors.gray[200],
},
'&:hover .MuiOutlinedInput-notchedOutline': {
borderColor: colors.gray[300],
},
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
borderColor: colors.purple,
},
}}
> >
<MenuItem value="latest"></MenuItem> <MenuItem value="latest"></MenuItem>
<MenuItem value="participants"></MenuItem> <MenuItem value="participants"></MenuItem>
@ -222,89 +520,234 @@ export default function EventsPage() {
</Box> </Box>
{/* Event List */} {/* Event List */}
<Box sx={{ mb: 5 }}> <Box sx={{ mb: 10 }}>
{pageEvents.length === 0 ? ( {pageEvents.length === 0 ? (
<Box sx={{ textAlign: 'center', py: 8 }}> <Card
<span className="material-icons" style={{ fontSize: 64, color: '#bdbdbd' }}> elevation={0}
sx={{
...cardStyles.default,
}}
>
<CardContent sx={{ textAlign: 'center', py: 20 }}>
<Box sx={{ color: colors.gray[300], mb: 3 }}>
<span className="material-icons" style={{ fontSize: 72 }}>
event_busy event_busy
</span> </span>
<Typography variant="body1" color="text.secondary" sx={{ mt: 2 }}> </Box>
<Typography variant="h6" sx={{ mb: 2, color: colors.gray[700] }}>
</Typography> </Typography>
</Box> <Typography variant="body2" sx={{ color: colors.gray[500] }}>
</Typography>
</CardContent>
</Card>
) : ( ) : (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{pageEvents.map((event) => ( {pageEvents.map((event) => (
<Card <Card
key={event.id} key={event.id}
elevation={0} elevation={0}
sx={{ sx={{
cursor: 'pointer', ...cardStyles.clickable,
borderRadius: 3,
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.1)',
transition: 'all 0.2s ease',
'&:hover': {
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
transform: 'translateY(-2px)',
},
}} }}
onClick={() => handleEventClick(event.id)} onClick={() => handleEventClick(event.id)}
> >
<CardContent sx={{ p: 3 }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
{/* Header */} {/* Header with Badges */}
<Box sx={{ mb: 4 }}>
<Box <Box
sx={{ sx={{
display: 'flex', display: 'flex',
alignItems: 'flex-start',
justifyContent: 'space-between', justifyContent: 'space-between',
mb: 2, alignItems: 'start',
mb: 3,
}} }}
> >
<Typography variant="h6" sx={{ fontWeight: 700, flex: 1 }}> <Typography variant="h6" sx={{ fontWeight: 700, color: colors.gray[900], flex: 1 }}>
{event.title} {event.title}
</Typography> </Typography>
<Chip <Box
label={`${getStatusText(event.status)}${ sx={{
event.status === 'active' px: 2.5,
py: 0.75,
borderRadius: 2,
fontSize: '0.875rem',
fontWeight: 600,
...getStatusStyle(event.status),
}}
>
{getStatusText(event.status)}
{event.status === 'active'
? ` | D-${event.daysLeft}` ? ` | D-${event.daysLeft}`
: event.status === 'scheduled' : event.status === 'scheduled'
? ` | D+${event.daysLeft}` ? ` | D+${event.daysLeft}`
: '' : ''}
}`} </Box>
color={getStatusColor(event.status) as any}
size="small"
sx={{ fontWeight: 600 }}
/>
</Box> </Box>
{/* Stats */} {/* Status Badges */}
<Grid container spacing={2} sx={{ mb: 2 }}> <Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap' }}>
<Grid item xs={6}> {event.isUrgent && (
<Box> <Chip
<Typography variant="caption" color="text.secondary"> icon={<Warning sx={{ fontSize: 16 }} />}
label="마감임박"
size="small"
sx={{
bgcolor: '#FEF3C7',
color: '#92400E',
fontWeight: 600,
fontSize: '0.75rem',
'& .MuiChip-icon': { color: '#92400E' },
}}
/>
)}
{event.isPopular && (
<Chip
icon={<LocalFireDepartment sx={{ fontSize: 16 }} />}
label="인기"
size="small"
sx={{
bgcolor: '#FEE2E2',
color: '#991B1B',
fontWeight: 600,
fontSize: '0.75rem',
'& .MuiChip-icon': { color: '#991B1B' },
}}
/>
)}
{event.isHighROI && (
<Chip
icon={<Star sx={{ fontSize: 16 }} />}
label="높은 ROI"
size="small"
sx={{
bgcolor: '#DCFCE7',
color: '#166534',
fontWeight: 600,
fontSize: '0.75rem',
'& .MuiChip-icon': { color: '#166534' },
}}
/>
)}
{event.isNew && (
<Chip
icon={<NewReleases sx={{ fontSize: 16 }} />}
label="신규"
size="small"
sx={{
bgcolor: '#DBEAFE',
color: '#1E40AF',
fontWeight: 600,
fontSize: '0.75rem',
'& .MuiChip-icon': { color: '#1E40AF' },
}}
/>
)}
</Box>
</Box>
{/* Progress Bar for Active Events */}
{event.status === 'active' && (
<Box sx={{ mb: 4 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
<Typography variant="caption" sx={{ color: colors.gray[600], fontSize: '0.75rem' }}>
</Typography> </Typography>
<Typography variant="body1" sx={{ fontWeight: 600 }}> <Typography variant="caption" sx={{ color: colors.gray[700], fontWeight: 600, fontSize: '0.75rem' }}>
{event.participants} {Math.round(calculateProgress(event))}%
</Typography> </Typography>
</Box> </Box>
</Grid> <LinearProgress
<Grid item xs={6}> variant="determinate"
value={calculateProgress(event)}
sx={{
height: 8,
borderRadius: 4,
bgcolor: colors.gray[200],
'& .MuiLinearProgress-bar': {
borderRadius: 4,
background: `linear-gradient(90deg, ${colors.mint} 0%, ${colors.blue} 100%)`,
},
}}
/>
</Box>
)}
{/* Event Info and Stats Container */}
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end' }}>
{/* Left: Event Info */}
<Box> <Box>
<Typography variant="caption" color="text.secondary"> <Box sx={{ mb: 4, display: 'flex', flexDirection: 'column', gap: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<CardGiftcard sx={{ fontSize: 18, color: colors.pink }} />
<Typography variant="body2" sx={{ color: colors.gray[700], fontSize: '0.875rem' }}>
{event.prize}
</Typography> </Typography>
<Typography variant="body1" sx={{ fontWeight: 600, color: 'error.main' }}> </Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{getMethodIcon(event.method)}
<Typography variant="body2" sx={{ color: colors.gray[700], fontSize: '0.875rem' }}>
{event.method}
</Typography>
</Box>
</Box>
{/* Date */}
<Typography
variant="body2"
sx={{
color: colors.gray[600],
display: 'flex',
alignItems: 'center',
gap: 2,
}}
>
<span>📅</span>
<span>
{event.startDate} ~ {event.endDate}
</span>
</Typography>
</Box>
{/* Right: Stats */}
<Box sx={{ display: 'flex', gap: 8, textAlign: 'right' }}>
<Box>
<Typography
variant="body2"
sx={{ mb: 1, color: colors.gray[600], fontWeight: 500 }}
>
</Typography>
<Typography variant="h5" sx={{ fontWeight: 700, color: colors.gray[900], mb: 0.5 }}>
{event.participants.toLocaleString()}
<Typography
component="span"
variant="body2"
sx={{ ml: 0.5, color: colors.gray[600] }}
>
</Typography>
</Typography>
{event.targetParticipants > 0 && (
<Typography variant="caption" sx={{ color: colors.gray[500], fontSize: '0.75rem' }}>
: {event.targetParticipants} ({Math.round((event.participants / event.targetParticipants) * 100)}%)
</Typography>
)}
</Box>
<Box>
<Typography
variant="body2"
sx={{ mb: 1, color: colors.gray[600], fontWeight: 500 }}
>
ROI
</Typography>
<Typography variant="h5" sx={{ fontWeight: 700, color: event.roi >= 400 ? colors.mint : event.roi >= 200 ? colors.orange : colors.gray[500] }}>
{event.roi}% {event.roi}%
</Typography> </Typography>
</Box> </Box>
</Grid> </Box>
</Grid> </Box>
{/* Date */}
<Typography variant="body2" color="text.secondary">
{event.startDate} ~ {event.endDate}
</Typography>
</CardContent> </CardContent>
</Card> </Card>
))} ))}
@ -314,13 +757,28 @@ export default function EventsPage() {
{/* Pagination */} {/* Pagination */}
{totalPages > 1 && ( {totalPages > 1 && (
<Box sx={{ display: 'flex', justifyContent: 'center', mb: 5 }}> <Box sx={{ display: 'flex', justifyContent: 'center' }}>
<Pagination <Pagination
count={totalPages} count={totalPages}
page={currentPage} page={currentPage}
onChange={(_, page) => setCurrentPage(page)} onChange={(_, page) => setCurrentPage(page)}
color="primary"
size="large" size="large"
sx={{
'& .MuiPaginationItem-root': {
color: colors.gray[700],
fontWeight: 600,
'&.Mui-selected': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.blue} 100%)`,
color: 'white',
'&:hover': {
background: `linear-gradient(135deg, ${colors.purpleLight} 0%, ${colors.blueLight} 100%)`,
},
},
'&:hover': {
bgcolor: colors.gray[100],
},
},
}}
/> />
</Box> </Box>
)} )}

View File

@ -1,16 +1,7 @@
'use client'; 'use client';
import { useRouter } from 'next/navigation'; import { useRouter } from 'next/navigation';
import { import { Box, Container, Typography, Grid, Card, CardContent, Button, Fab } from '@mui/material';
Box,
Container,
Typography,
Grid,
Card,
CardContent,
Button,
Fab,
} from '@mui/material';
import { import {
Add, Add,
Celebration, Celebration,
@ -22,7 +13,12 @@ import {
CheckCircle, CheckCircle,
} from '@mui/icons-material'; } from '@mui/icons-material';
import Header from '@/shared/ui/Header'; import Header from '@/shared/ui/Header';
import { getGradientButtonStyle, responsiveText, cardStyles, colors } from '@/shared/lib/button-styles'; import {
getGradientButtonStyle,
responsiveText,
cardStyles,
colors,
} from '@/shared/lib/button-styles';
// Mock 사용자 데이터 (API 연동 전까지 임시 사용) // Mock 사용자 데이터 (API 연동 전까지 임시 사용)
const mockUser = { const mockUser = {
@ -64,7 +60,8 @@ export default function HomePage() {
// KPI 계산 // KPI 계산
const activeEvents = mockEvents.filter((e) => e.status === '진행중'); const activeEvents = mockEvents.filter((e) => e.status === '진행중');
const totalParticipants = mockEvents.reduce((sum, e) => sum + e.participants, 0); const totalParticipants = mockEvents.reduce((sum, e) => sum + e.participants, 0);
const avgROI = mockEvents.length > 0 const avgROI =
mockEvents.length > 0
? Math.round(mockEvents.reduce((sum, e) => sum + e.roi, 0) / mockEvents.length) ? Math.round(mockEvents.reduce((sum, e) => sum + e.roi, 0) / mockEvents.length)
: 0; : 0;
@ -91,14 +88,14 @@ export default function HomePage() {
minHeight: '100vh', minHeight: '100vh',
}} }}
> >
<Container maxWidth="lg" sx={{ pt: 5, pb: 4, px: { xs: 3, sm: 4, md: 5 } }}> <Container maxWidth="lg" sx={{ pt: 8, pb: 6, px: { xs: 6, sm: 8, md: 10 } }}>
{/* Welcome Section */} {/* Welcome Section */}
<Box sx={{ mb: 6 }}> <Box sx={{ mb: 10 }}>
<Typography <Typography
variant="h3" variant="h3"
sx={{ sx={{
...responsiveText.h2, ...responsiveText.h2,
mb: 2, mb: 4,
}} }}
> >
, {mockUser.name}! 👋 , {mockUser.name}! 👋
@ -109,7 +106,7 @@ export default function HomePage() {
</Box> </Box>
{/* KPI Cards */} {/* KPI Cards */}
<Grid container spacing={4} sx={{ mb: 6 }}> <Grid container spacing={6} sx={{ mb: 10 }}>
<Grid item xs={12} sm={4}> <Grid item xs={12} sm={4}>
<Card <Card
elevation={0} elevation={0}
@ -119,29 +116,47 @@ export default function HomePage() {
borderColor: 'transparent', borderColor: 'transparent',
}} }}
> >
<CardContent sx={{ textAlign: 'center', py: 4, px: 3 }}> <CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
<Box <Box
sx={{ sx={{
width: 64, width: 64,
height: 64, height: 64,
borderRadius: '50%', borderRadius: '50%',
bgcolor: 'rgba(255, 255, 255, 0.2)', bgcolor: 'rgba(0, 0, 0, 0.05)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
margin: '0 auto', margin: '0 auto',
mb: 2, mb: 3,
}} }}
> >
<Celebration sx={{ fontSize: 32, color: 'white' }} /> <Celebration sx={{
fontSize: 32,
color: colors.gray[900],
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
}} />
</Box> </Box>
<Typography <Typography
variant="body2" variant="body2"
sx={{ mb: 1, color: 'rgba(255, 255, 255, 0.9)', fontWeight: 500, fontSize: '0.875rem' }} sx={{
mb: 1,
color: colors.gray[700],
fontWeight: 500,
fontSize: '0.875rem',
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
}}
> >
</Typography> </Typography>
<Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}> <Typography
variant="h3"
sx={{
fontWeight: 700,
color: colors.gray[900],
fontSize: '2.25rem',
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}
>
{activeEvents.length} {activeEvents.length}
</Typography> </Typography>
</CardContent> </CardContent>
@ -156,29 +171,47 @@ export default function HomePage() {
borderColor: 'transparent', borderColor: 'transparent',
}} }}
> >
<CardContent sx={{ textAlign: 'center', py: 4, px: 3 }}> <CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
<Box <Box
sx={{ sx={{
width: 64, width: 64,
height: 64, height: 64,
borderRadius: '50%', borderRadius: '50%',
bgcolor: 'rgba(255, 255, 255, 0.2)', bgcolor: 'rgba(0, 0, 0, 0.05)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
margin: '0 auto', margin: '0 auto',
mb: 2, mb: 3,
}} }}
> >
<Group sx={{ fontSize: 32, color: 'white' }} /> <Group sx={{
fontSize: 32,
color: colors.gray[900],
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
}} />
</Box> </Box>
<Typography <Typography
variant="body2" variant="body2"
sx={{ mb: 1, color: 'rgba(255, 255, 255, 0.9)', fontWeight: 500, fontSize: '0.875rem' }} sx={{
mb: 1,
color: colors.gray[700],
fontWeight: 500,
fontSize: '0.875rem',
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
}}
> >
</Typography> </Typography>
<Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}> <Typography
variant="h3"
sx={{
fontWeight: 700,
color: colors.gray[900],
fontSize: '2.25rem',
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}
>
{totalParticipants.toLocaleString()} {totalParticipants.toLocaleString()}
</Typography> </Typography>
</CardContent> </CardContent>
@ -193,29 +226,47 @@ export default function HomePage() {
borderColor: 'transparent', borderColor: 'transparent',
}} }}
> >
<CardContent sx={{ textAlign: 'center', py: 4, px: 3 }}> <CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
<Box <Box
sx={{ sx={{
width: 64, width: 64,
height: 64, height: 64,
borderRadius: '50%', borderRadius: '50%',
bgcolor: 'rgba(255, 255, 255, 0.2)', bgcolor: 'rgba(0, 0, 0, 0.05)',
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
margin: '0 auto', margin: '0 auto',
mb: 2, mb: 3,
}} }}
> >
<TrendingUp sx={{ fontSize: 32, color: 'white' }} /> <TrendingUp sx={{
fontSize: 32,
color: colors.gray[900],
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
}} />
</Box> </Box>
<Typography <Typography
variant="body2" variant="body2"
sx={{ mb: 1, color: 'rgba(255, 255, 255, 0.9)', fontWeight: 500, fontSize: '0.875rem' }} sx={{
mb: 1,
color: colors.gray[700],
fontWeight: 500,
fontSize: '0.875rem',
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
}}
> >
ROI ROI
</Typography> </Typography>
<Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}> <Typography
variant="h3"
sx={{
fontWeight: 700,
color: colors.gray[900],
fontSize: '2.25rem',
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
}}
>
{avgROI}% {avgROI}%
</Typography> </Typography>
</CardContent> </CardContent>
@ -224,11 +275,11 @@ export default function HomePage() {
</Grid> </Grid>
{/* Quick Actions */} {/* Quick Actions */}
<Box sx={{ mb: 6 }}> <Box sx={{ mb: 10 }}>
<Typography variant="h5" sx={{ ...responsiveText.h3, mb: 4 }}> <Typography variant="h5" sx={{ ...responsiveText.h3, mb: 6 }}>
</Typography> </Typography>
<Grid container spacing={4}> <Grid container spacing={6}>
<Grid item xs={6} sm={6}> <Grid item xs={6} sm={6}>
<Card <Card
elevation={0} elevation={0}
@ -237,7 +288,7 @@ export default function HomePage() {
}} }}
onClick={handleCreateEvent} onClick={handleCreateEvent}
> >
<CardContent sx={{ textAlign: 'center', py: 5 }}> <CardContent sx={{ textAlign: 'center', py: 6 }}>
<Box <Box
sx={{ sx={{
width: 72, width: 72,
@ -248,13 +299,13 @@ export default function HomePage() {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
margin: '0 auto', margin: '0 auto',
mb: 2.5, mb: 3,
boxShadow: '0 4px 14px 0 rgba(167, 139, 250, 0.39)', boxShadow: '0 4px 14px 0 rgba(167, 139, 250, 0.39)',
}} }}
> >
<Add sx={{ fontSize: 36, color: 'white' }} /> <Add sx={{ fontSize: 36, color: 'white' }} />
</Box> </Box>
<Typography variant="body1" sx={{ fontWeight: 600, color: colors.gray[900] }}> <Typography variant="body1" sx={{ fontWeight: 600, color: colors.gray[900], fontSize: '1.125rem' }}>
</Typography> </Typography>
</CardContent> </CardContent>
@ -268,7 +319,7 @@ export default function HomePage() {
}} }}
onClick={handleViewAnalytics} onClick={handleViewAnalytics}
> >
<CardContent sx={{ textAlign: 'center', py: 5 }}> <CardContent sx={{ textAlign: 'center', py: 6 }}>
<Box <Box
sx={{ sx={{
width: 72, width: 72,
@ -279,14 +330,14 @@ export default function HomePage() {
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
margin: '0 auto', margin: '0 auto',
mb: 2.5, mb: 3,
boxShadow: '0 4px 14px 0 rgba(96, 165, 250, 0.39)', boxShadow: '0 4px 14px 0 rgba(96, 165, 250, 0.39)',
}} }}
> >
<Analytics sx={{ fontSize: 36, color: 'white' }} /> <Analytics sx={{ fontSize: 36, color: 'white' }} />
</Box> </Box>
<Typography variant="body1" sx={{ fontWeight: 600, color: colors.gray[900] }}> <Typography variant="body1" sx={{ fontWeight: 600, color: colors.gray[900], fontSize: '1.125rem' }}>
</Typography> </Typography>
</CardContent> </CardContent>
</Card> </Card>
@ -295,8 +346,10 @@ export default function HomePage() {
</Box> </Box>
{/* Active Events */} {/* Active Events */}
<Box sx={{ mb: 6 }}> <Box sx={{ mb: 10 }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4 }}> <Box
sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 6 }}
>
<Typography variant="h5" sx={{ ...responsiveText.h3 }}> <Typography variant="h5" sx={{ ...responsiveText.h3 }}>
</Typography> </Typography>
@ -349,7 +402,7 @@ export default function HomePage() {
</CardContent> </CardContent>
</Card> </Card>
) : ( ) : (
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
{activeEvents.map((event) => ( {activeEvents.map((event) => (
<Card <Card
key={event.id} key={event.id}
@ -359,8 +412,15 @@ export default function HomePage() {
}} }}
onClick={() => handleEventClick(event.id)} onClick={() => handleEventClick(event.id)}
> >
<CardContent sx={{ p: { xs: 3, sm: 4 } }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', mb: 3 }}> <Box
sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'start',
mb: 6,
}}
>
<Typography variant="h6" sx={{ fontWeight: 700, color: colors.gray[900] }}> <Typography variant="h6" sx={{ fontWeight: 700, color: colors.gray[900] }}>
{event.title} {event.title}
</Typography> </Typography>
@ -378,24 +438,48 @@ export default function HomePage() {
{event.status} {event.status}
</Box> </Box>
</Box> </Box>
<Typography variant="body2" sx={{ mb: 3, color: colors.gray[600], display: 'flex', alignItems: 'center', gap: 1 }}> <Typography
variant="body2"
sx={{
mb: 6,
color: colors.gray[600],
display: 'flex',
alignItems: 'center',
gap: 2,
}}
>
<span>📅</span> <span>📅</span>
<span>{event.startDate} ~ {event.endDate}</span> <span>
{event.startDate} ~ {event.endDate}
</span>
</Typography> </Typography>
<Box sx={{ display: 'flex', gap: 6 }}> <Box sx={{ display: 'flex', gap: 12 }}>
<Box> <Box>
<Typography variant="body2" sx={{ mb: 0.5, color: colors.gray[600], fontWeight: 500 }}> <Typography
variant="body2"
sx={{ mb: 0.5, color: colors.gray[600], fontWeight: 500 }}
>
</Typography> </Typography>
<Typography variant="h5" sx={{ fontWeight: 700, color: colors.gray[900] }}> <Typography
variant="h5"
sx={{ fontWeight: 700, color: colors.gray[900] }}
>
{event.participants.toLocaleString()} {event.participants.toLocaleString()}
<Typography component="span" variant="body2" sx={{ ml: 0.5, color: colors.gray[600] }}> <Typography
component="span"
variant="body2"
sx={{ ml: 0.5, color: colors.gray[600] }}
>
</Typography> </Typography>
</Typography> </Typography>
</Box> </Box>
<Box> <Box>
<Typography variant="body2" sx={{ mb: 0.5, color: colors.gray[600], fontWeight: 500 }}> <Typography
variant="body2"
sx={{ mb: 0.5, color: colors.gray[600], fontWeight: 500 }}
>
ROI ROI
</Typography> </Typography>
<Typography variant="h5" sx={{ fontWeight: 700, color: colors.mint }}> <Typography variant="h5" sx={{ fontWeight: 700, color: colors.mint }}>
@ -411,8 +495,8 @@ export default function HomePage() {
</Box> </Box>
{/* Recent Activity */} {/* Recent Activity */}
<Box sx={{ mb: 6 }}> <Box sx={{ mb: 10 }}>
<Typography variant="h5" sx={{ ...responsiveText.h3, mb: 4 }}> <Typography variant="h5" sx={{ ...responsiveText.h3, mb: 6 }}>
</Typography> </Typography>
<Card <Card
@ -421,15 +505,15 @@ export default function HomePage() {
...cardStyles.default, ...cardStyles.default,
}} }}
> >
<CardContent sx={{ p: { xs: 3, sm: 4 } }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
{mockActivities.map((activity, index) => ( {mockActivities.map((activity, index) => (
<Box <Box
key={index} key={index}
sx={{ sx={{
display: 'flex', display: 'flex',
gap: 3, gap: 4,
pt: index > 0 ? 3.5 : 0, pt: index > 0 ? 6 : 0,
mt: index > 0 ? 3.5 : 0, mt: index > 0 ? 6 : 0,
borderTop: index > 0 ? 1 : 0, borderTop: index > 0 ? 1 : 0,
borderColor: colors.gray[200], borderColor: colors.gray[200],
}} }}
@ -449,7 +533,10 @@ export default function HomePage() {
<activity.icon sx={{ fontSize: 24, color: 'white' }} /> <activity.icon sx={{ fontSize: 24, color: 'white' }} />
</Box> </Box>
<Box sx={{ flex: 1 }}> <Box sx={{ flex: 1 }}>
<Typography variant="body1" sx={{ fontWeight: 600, color: colors.gray[900], mb: 0.5 }}> <Typography
variant="body1"
sx={{ fontWeight: 600, color: colors.gray[900], mb: 0.5 }}
>
{activity.text} {activity.text}
</Typography> </Typography>
<Typography variant="body2" sx={{ color: colors.gray[500] }}> <Typography variant="body2" sx={{ color: colors.gray[500] }}>
@ -472,9 +559,11 @@ export default function HomePage() {
width: { xs: 64, sm: 72 }, width: { xs: 64, sm: 72 },
height: { xs: 64, sm: 72 }, height: { xs: 64, sm: 72 },
...getGradientButtonStyle('primary'), ...getGradientButtonStyle('primary'),
boxShadow: '0 10px 25px -5px rgba(167, 139, 250, 0.5), 0 8px 10px -6px rgba(167, 139, 250, 0.5)', boxShadow:
'0 10px 25px -5px rgba(167, 139, 250, 0.5), 0 8px 10px -6px rgba(167, 139, 250, 0.5)',
'&:hover': { '&:hover': {
boxShadow: '0 20px 35px -5px rgba(167, 139, 250, 0.6), 0 12px 15px -6px rgba(167, 139, 250, 0.6)', boxShadow:
'0 20px 35px -5px rgba(167, 139, 250, 0.6), 0 12px 15px -6px rgba(167, 139, 250, 0.6)',
}, },
}} }}
onClick={handleCreateEvent} onClick={handleCreateEvent}

View File

@ -7,10 +7,12 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod'; import { z } from 'zod';
import { import {
Box, Box,
Container,
TextField, TextField,
Button, Button,
Typography, Typography,
Paper, Card,
CardContent,
Avatar, Avatar,
Select, Select,
MenuItem, MenuItem,
@ -26,7 +28,8 @@ import {
import { Person, Visibility, VisibilityOff, CheckCircle } from '@mui/icons-material'; import { Person, Visibility, VisibilityOff, CheckCircle } from '@mui/icons-material';
import { useAuthStore } from '@/stores/authStore'; import { useAuthStore } from '@/stores/authStore';
import { useUIStore } from '@/stores/uiStore'; import { useUIStore } from '@/stores/uiStore';
import { getGradientButtonStyle, responsiveText, containerStyles } from '@/shared/lib/button-styles'; import Header from '@/shared/ui/Header';
import { cardStyles, colors, responsiveText } from '@/shared/lib/button-styles';
// 기본 정보 스키마 // 기본 정보 스키마
const basicInfoSchema = z.object({ const basicInfoSchema = z.object({
@ -186,33 +189,45 @@ export default function ProfilePage() {
}; };
return ( return (
<Box sx={{ ...containerStyles.page }}> <>
<Box sx={{ maxWidth: 600, mx: 'auto', px: { xs: 3, sm: 4 }, py: { xs: 3, sm: 4 } }}> <Header title="프로필" showBack={true} showMenu={false} showProfile={false} />
{/* 사용자 정보 섹션 */} <Box
<Paper elevation={0} sx={{ p: 4, mb: 3, textAlign: 'center' }}>
<Avatar
sx={{ sx={{
width: 80, pt: { xs: 7, sm: 8 },
height: 80, pb: 10,
mx: 'auto', bgcolor: colors.gray[50],
mb: 2, minHeight: '100vh',
bgcolor: 'grey.100',
color: 'grey.400',
}} }}
> >
<Person sx={{ fontSize: 48 }} /> <Container maxWidth="lg" sx={{ pt: 8, pb: 6, px: { xs: 6, sm: 8, md: 10 } }}>
{/* 사용자 정보 섹션 */}
<Card elevation={0} sx={{ ...cardStyles.default, mb: 10, textAlign: 'center' }}>
<CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<Avatar
sx={{
width: 100,
height: 100,
mx: 'auto',
mb: 3,
bgcolor: colors.purple,
color: 'white',
}}
>
<Person sx={{ fontSize: 56 }} />
</Avatar> </Avatar>
<Typography variant="h5" sx={{ ...responsiveText.h3, mb: 0.5 }}> <Typography variant="h5" sx={{ ...responsiveText.h3, mb: 1 }}>
{user?.name} {user?.name}
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary" sx={{ ...responsiveText.body1 }}>
{user?.email} {user?.email}
</Typography> </Typography>
</Paper> </CardContent>
</Card>
{/* 기본 정보 */} {/* 기본 정보 */}
<Paper elevation={0} sx={{ p: 4, mb: 3 }}> <Card elevation={0} sx={{ ...cardStyles.default, mb: 10 }}>
<Typography variant="h6" sx={{ ...responsiveText.h4, mb: 3 }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 6 }}>
</Typography> </Typography>
@ -264,11 +279,13 @@ export default function ProfilePage() {
)} )}
/> />
</Box> </Box>
</Paper> </CardContent>
</Card>
{/* 매장 정보 */} {/* 매장 정보 */}
<Paper elevation={0} sx={{ p: 4, mb: 3 }}> <Card elevation={0} sx={{ ...cardStyles.default, mb: 10 }}>
<Typography variant="h6" sx={{ ...responsiveText.h4, mb: 3 }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 6 }}>
</Typography> </Typography>
@ -333,11 +350,13 @@ export default function ProfilePage() {
)} )}
/> />
</Box> </Box>
</Paper> </CardContent>
</Card>
{/* 비밀번호 변경 */} {/* 비밀번호 변경 */}
<Paper elevation={0} sx={{ p: 4, mb: 3 }}> <Card elevation={0} sx={{ ...cardStyles.default, mb: 10 }}>
<Typography variant="h6" sx={{ ...responsiveText.h4, mb: 3 }}> <CardContent sx={{ p: { xs: 6, sm: 8 } }}>
<Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 6 }}>
</Typography> </Typography>
@ -433,28 +452,39 @@ export default function ProfilePage() {
onClick={handlePasswordSubmit(onChangePassword)} onClick={handlePasswordSubmit(onChangePassword)}
sx={{ sx={{
mt: 1, mt: 1,
py: { xs: 1.5, sm: 1.75 }, py: 3,
fontSize: { xs: 15, sm: 16 }, borderRadius: 3,
fontSize: '1rem',
fontWeight: 600, fontWeight: 600,
borderWidth: 2, borderWidth: 2,
'&:hover': {
borderWidth: 2,
},
}} }}
> >
</Button> </Button>
</Box> </Box>
</Paper> </CardContent>
</Card>
{/* 액션 버튼 */} {/* 액션 버튼 */}
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}> <Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
<Button <Button
fullWidth fullWidth
variant="contained" variant="contained"
size="large" size="large"
onClick={handleSave} onClick={handleSave}
sx={{ sx={{
py: { xs: 1.5, sm: 1.75 }, py: 3,
fontSize: { xs: 15, sm: 16 }, borderRadius: 3,
...getGradientButtonStyle('primary'), fontSize: '1rem',
fontWeight: 700,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
}} }}
> >
@ -466,24 +496,34 @@ export default function ProfilePage() {
color="error" color="error"
onClick={() => setLogoutDialogOpen(true)} onClick={() => setLogoutDialogOpen(true)}
sx={{ sx={{
py: { xs: 1.5, sm: 1.75 }, py: 3,
fontSize: { xs: 15, sm: 16 }, fontSize: '1rem',
fontWeight: 600, fontWeight: 600,
}} }}
> >
</Button> </Button>
</Box> </Box>
</Container>
</Box> </Box>
{/* 저장 완료 다이얼로그 */} {/* 저장 완료 다이얼로그 */}
<Dialog open={successDialogOpen} onClose={() => setSuccessDialogOpen(false)}> <Dialog
open={successDialogOpen}
onClose={() => setSuccessDialogOpen(false)}
PaperProps={{
sx: {
borderRadius: 4,
minWidth: 300,
},
}}
>
<DialogContent sx={{ textAlign: 'center', py: 6, px: 4 }}> <DialogContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
<CheckCircle sx={{ fontSize: 64, color: 'success.main', mb: 2 }} /> <CheckCircle sx={{ fontSize: 64, color: 'success.main', mb: 2 }} />
<Typography variant="h6" sx={{ fontWeight: 700, mb: 1 }}> <Typography variant="h6" sx={{ fontWeight: 700, mb: 1, fontSize: '1.25rem' }}>
</Typography> </Typography>
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1rem' }}>
. .
</Typography> </Typography>
</DialogContent> </DialogContent>
@ -496,8 +536,15 @@ export default function ProfilePage() {
}} }}
sx={{ sx={{
minWidth: 120, minWidth: 120,
py: { xs: 1.25, sm: 1.5 }, py: 2,
...getGradientButtonStyle('success'), borderRadius: 2,
fontSize: '1rem',
fontWeight: 700,
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
'&:hover': {
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.pink} 100%)`,
opacity: 0.9,
},
}} }}
> >
@ -506,29 +553,51 @@ export default function ProfilePage() {
</Dialog> </Dialog>
{/* 로그아웃 확인 다이얼로그 */} {/* 로그아웃 확인 다이얼로그 */}
<Dialog open={logoutDialogOpen} onClose={() => setLogoutDialogOpen(false)}> <Dialog
<DialogTitle></DialogTitle> open={logoutDialogOpen}
onClose={() => setLogoutDialogOpen(false)}
PaperProps={{
sx: {
borderRadius: 4,
minWidth: 300,
},
}}
>
<DialogTitle sx={{ fontSize: '1.25rem', fontWeight: 700 }}></DialogTitle>
<DialogContent> <DialogContent>
<Typography variant="body1" sx={{ textAlign: 'center', py: 2 }}> <Typography variant="body1" sx={{ textAlign: 'center', py: 2, fontSize: '1rem' }}>
? ?
</Typography> </Typography>
</DialogContent> </DialogContent>
<DialogActions> <DialogActions sx={{ justifyContent: 'center', gap: 2, pb: 3 }}>
<Button <Button
onClick={() => setLogoutDialogOpen(false)} onClick={() => setLogoutDialogOpen(false)}
sx={{ fontWeight: 600 }} sx={{
fontWeight: 600,
fontSize: '1rem',
py: 2,
px: 4,
borderRadius: 2,
}}
> >
</Button> </Button>
<Button <Button
variant="contained" variant="contained"
onClick={handleLogout} onClick={handleLogout}
sx={{ ...getGradientButtonStyle('error') }} color="error"
sx={{
fontWeight: 700,
fontSize: '1rem',
py: 2,
px: 4,
borderRadius: 2,
}}
> >
</Button> </Button>
</DialogActions> </DialogActions>
</Dialog> </Dialog>
</Box> </>
); );
} }

View File

@ -24,13 +24,13 @@
--color-error: #D32F2F; --color-error: #D32F2F;
--color-info: #0288D1; --color-info: #0288D1;
/* Spacing (4px Grid) */ /* Spacing (4px Grid) - Doubled values */
--spacing-xs: 4px; --spacing-xs: 8px;
--spacing-s: 8px; --spacing-s: 16px;
--spacing-m: 16px; --spacing-m: 32px;
--spacing-l: 24px; --spacing-l: 48px;
--spacing-xl: 32px; --spacing-xl: 64px;
--spacing-2xl: 48px; --spacing-2xl: 96px;
/* Border Radius */ /* Border Radius */
--border-radius-sm: 4px; --border-radius-sm: 4px;
@ -73,8 +73,8 @@ body {
padding: 0; padding: 0;
font-family: "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI", font-family: "Pretendard", -apple-system, BlinkMacSystemFont, "Segoe UI",
"Roboto", "Helvetica Neue", system-ui, sans-serif; "Roboto", "Helvetica Neue", system-ui, sans-serif;
font-size: 14px; font-size: 16px;
line-height: 1.5; line-height: 1.6;
color: var(--color-gray-900); color: var(--color-gray-900);
background-color: var(--color-gray-50); background-color: var(--color-gray-50);
min-height: 100%; min-height: 100%;
@ -149,18 +149,18 @@ button {
width: 100%; width: 100%;
max-width: 1200px; max-width: 1200px;
margin: 0 auto; margin: 0 auto;
padding: 0 20px; padding: 0 40px;
} }
@media (min-width: 768px) { @media (min-width: 768px) {
.container { .container {
padding: 0 40px; padding: 0 80px;
} }
} }
@media (min-width: 1024px) { @media (min-width: 1024px) {
.container { .container {
padding: 0 80px; padding: 0 120px;
} }
} }