mirror of
https://github.com/ktds-dg0501/kt-event-marketing-fe.git
synced 2025-12-06 14:56:23 +00:00
commit
9cbf89b9ec
25
package-lock.json
generated
25
package-lock.json
generated
@ -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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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 }}>
|
||||||
</Typography>
|
<Grid item xs={6} md={6}>
|
||||||
<Typography variant="body1">{mockEventData.name}</Typography>
|
<Card
|
||||||
</Box>
|
elevation={0}
|
||||||
<Box sx={{ mb: 2 }}>
|
sx={{
|
||||||
<Typography variant="caption" color="text.secondary">
|
borderRadius: 4,
|
||||||
총 참여자
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
||||||
</Typography>
|
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
|
||||||
<Typography variant="h6" sx={{ fontWeight: 700 }}>
|
}}
|
||||||
{mockEventData.totalParticipants}명
|
>
|
||||||
</Typography>
|
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||||||
</Box>
|
<EventNote sx={{ fontSize: 40, mb: 2, color: 'white' }} />
|
||||||
<Box>
|
<Typography variant="caption" display="block" sx={{ mb: 2, color: 'rgba(255,255,255,0.9)' }}>
|
||||||
<Typography variant="caption" color="text.secondary">
|
이벤트명
|
||||||
추첨 상태
|
</Typography>
|
||||||
</Typography>
|
<Typography variant="h6" sx={{ fontWeight: 700, color: 'white' }}>
|
||||||
<Typography variant="body1">추첨 전</Typography>
|
{mockEventData.name}
|
||||||
</Box>
|
</Typography>
|
||||||
</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>
|
||||||
|
|||||||
@ -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" />
|
<Chip
|
||||||
|
label={getStatusText(event.status)}
|
||||||
|
color={getStatusColor(event.status) as any}
|
||||||
|
size="medium"
|
||||||
|
/>
|
||||||
{event.isAIRecommended && (
|
{event.isAIRecommended && (
|
||||||
|
<Chip label="AI 추천" size="medium" sx={{ bgcolor: colors.purpleLight, color: colors.purple }} />
|
||||||
|
)}
|
||||||
|
{event.isUrgent && (
|
||||||
<Chip
|
<Chip
|
||||||
label="AI 추천"
|
icon={<Warning />}
|
||||||
size="small"
|
label="마감임박"
|
||||||
sx={{ bgcolor: 'rgba(0, 102, 255, 0.1)', color: 'info.main' }}
|
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,
|
||||||
>
|
},
|
||||||
<span className="material-icons" style={{ fontSize: 48, color: '#9e9e9e' }}>
|
tooltip: {
|
||||||
show_chart
|
backgroundColor: 'rgba(0, 0, 0, 0.8)',
|
||||||
</span>
|
padding: 12,
|
||||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
titleFont: { size: 14, weight: 'bold' },
|
||||||
참여자 추이 차트
|
bodyFont: { size: 13 },
|
||||||
</Typography>
|
},
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
grid: {
|
||||||
|
color: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
x: {
|
||||||
|
grid: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</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>
|
||||||
|
|||||||
@ -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>
|
||||||
<Button
|
<Box sx={{ display: 'flex', gap: 3 }}>
|
||||||
variant="contained"
|
<Button
|
||||||
startIcon={<Casino />}
|
variant="outlined"
|
||||||
onClick={handleDrawClick}
|
startIcon={<Download />}
|
||||||
sx={{ borderRadius: 2 }}
|
onClick={handleDownloadClick}
|
||||||
>
|
sx={{
|
||||||
당첨자 추첨
|
borderRadius: 3,
|
||||||
</Button>
|
px: 4,
|
||||||
|
py: 1.5,
|
||||||
|
borderColor: colors.blue,
|
||||||
|
color: colors.blue,
|
||||||
|
'&:hover': {
|
||||||
|
borderColor: colors.blue,
|
||||||
|
bgcolor: `${colors.blue}10`,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
엑셀 다운로드
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<Casino />}
|
||||||
|
onClick={handleDrawClick}
|
||||||
|
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>
|
||||||
|
</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
|
||||||
#{participant.id}
|
sx={{
|
||||||
</Typography>
|
width: 56,
|
||||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 0.5 }}>
|
height: 56,
|
||||||
{participant.name}
|
borderRadius: '50%',
|
||||||
</Typography>
|
bgcolor: colors.purpleLight,
|
||||||
<Typography variant="body2" color="text.secondary">
|
display: 'flex',
|
||||||
{participant.phone}
|
alignItems: 'center',
|
||||||
</Typography>
|
justifyContent: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Person sx={{ fontSize: 32, color: colors.purple }} />
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
||||||
|
#{participant.id}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h6" sx={{ fontWeight: 700, mb: 1, fontSize: '1.25rem' }}>
|
||||||
|
{participant.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" color="text.secondary">
|
||||||
|
{participant.phone}
|
||||||
|
</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 }}>
|
||||||
|
참여자 상세 정보
|
||||||
|
</Typography>
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogContent dividers sx={{ p: 5 }}>
|
||||||
{selectedParticipant && (
|
{selectedParticipant && (
|
||||||
<Box sx={{ p: 2 }}>
|
<Box>
|
||||||
<Box sx={{ mb: 3 }}>
|
<Box sx={{ mb: 4, textAlign: 'center' }}>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<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>
|
||||||
<Typography variant="h6" sx={{ fontWeight: 700 }}>
|
<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>
|
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
||||||
<Typography variant="body1">{selectedParticipant.phone}</Typography>
|
전화번호
|
||||||
</Box>
|
</Typography>
|
||||||
<Box sx={{ mb: 3 }}>
|
<Typography variant="body1" sx={{ fontWeight: 600 }}>
|
||||||
<Typography variant="caption" color="text.secondary">
|
{selectedParticipant.phone}
|
||||||
참여 경로
|
</Typography>
|
||||||
</Typography>
|
</Box>
|
||||||
<Typography variant="body1">{selectedParticipant.channel}</Typography>
|
|
||||||
</Box>
|
<Box
|
||||||
<Box sx={{ mb: 3 }}>
|
sx={{
|
||||||
<Typography variant="caption" color="text.secondary">
|
p: 3,
|
||||||
참여 일시
|
bgcolor: colors.gray[100],
|
||||||
</Typography>
|
borderRadius: 3,
|
||||||
<Typography variant="body1">{selectedParticipant.date}</Typography>
|
}}
|
||||||
</Box>
|
>
|
||||||
<Box>
|
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
||||||
<Typography variant="caption" color="text.secondary">
|
참여 경로
|
||||||
당첨 여부
|
</Typography>
|
||||||
</Typography>
|
<Typography variant="body1" sx={{ fontWeight: 600 }}>
|
||||||
<Typography variant="body1">
|
{selectedParticipant.channel}
|
||||||
{getStatusText(selectedParticipant.status)}
|
</Typography>
|
||||||
</Typography>
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: 3,
|
||||||
|
bgcolor: colors.gray[100],
|
||||||
|
borderRadius: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
||||||
|
참여 일시
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body1" sx={{ fontWeight: 600 }}>
|
||||||
|
{selectedParticipant.date}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
p: 3,
|
||||||
|
bgcolor: colors.gray[100],
|
||||||
|
borderRadius: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
||||||
|
당첨 여부
|
||||||
|
</Typography>
|
||||||
|
<Chip
|
||||||
|
label={getStatusText(selectedParticipant.status)}
|
||||||
|
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>
|
||||||
|
|||||||
@ -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
|
||||||
{eventData.recommendation?.title || '이벤트 제목'}
|
elevation={0}
|
||||||
</Typography>
|
sx={{
|
||||||
|
...cardStyles.default,
|
||||||
<Box sx={{ display: 'flex', gap: 1, mb: 3 }}>
|
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
|
||||||
<Chip label="배포 대기" color="warning" size="small" />
|
borderColor: 'transparent',
|
||||||
<Chip label="AI 추천" color="info" size="small" />
|
}}
|
||||||
</Box>
|
>
|
||||||
|
<CardContent sx={{ textAlign: 'center', py: 4, px: 3 }}>
|
||||||
<Grid container spacing={2} sx={{ pt: 2, borderTop: 1, borderColor: 'divider' }}>
|
<CheckCircle sx={{
|
||||||
<Grid item xs={6}>
|
fontSize: 32,
|
||||||
<Typography variant="caption" color="text.secondary">
|
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>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography
|
||||||
2025.02.01 ~ 2025.02.28
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 700,
|
||||||
|
color: colors.gray[900],
|
||||||
|
fontSize: '1rem',
|
||||||
|
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{eventData.recommendation?.title || '이벤트 제목'}
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</CardContent>
|
||||||
<Grid item xs={6}>
|
</Card>
|
||||||
<Typography variant="caption" color="text.secondary">
|
</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>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography
|
||||||
{eventData.recommendation?.expectedParticipants || 0}명
|
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>
|
</Typography>
|
||||||
</Grid>
|
</CardContent>
|
||||||
<Grid item xs={6}>
|
</Card>
|
||||||
<Typography variant="caption" color="text.secondary">
|
</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>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600 }}>
|
<Typography
|
||||||
{eventData.recommendation?.estimatedCost.toLocaleString() || 0}원
|
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>
|
</Typography>
|
||||||
</Grid>
|
</CardContent>
|
||||||
<Grid item xs={6}>
|
</Card>
|
||||||
<Typography variant="caption" color="text.secondary">
|
</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
|
예상 ROI
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body2" sx={{ fontWeight: 600, color: 'error.main' }}>
|
<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}%
|
{eventData.recommendation?.roi || 0}%
|
||||||
</Typography>
|
</Typography>
|
||||||
</Grid>
|
</CardContent>
|
||||||
</Grid>
|
</Card>
|
||||||
</CardContent>
|
</Grid>
|
||||||
</Card>
|
</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,30 +511,44 @@ 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"
|
size="large"
|
||||||
size="large"
|
onClick={() => {
|
||||||
onClick={() => {
|
setSuccessDialogOpen(false);
|
||||||
setSuccessDialogOpen(false);
|
onApprove();
|
||||||
onApprove();
|
}}
|
||||||
}}
|
sx={{
|
||||||
>
|
py: 3,
|
||||||
대시보드로 이동
|
borderRadius: 3,
|
||||||
</Button>
|
fontSize: '1rem',
|
||||||
</Box>
|
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>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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%' }}>
|
||||||
<Box
|
<CardContent sx={{ p: { xs: 6, sm: 8 } }}>
|
||||||
sx={{
|
<Box
|
||||||
width: '100%',
|
sx={{
|
||||||
aspectRatio: '1 / 1',
|
width: '100%',
|
||||||
bgcolor: 'grey.100',
|
aspectRatio: '1 / 1',
|
||||||
borderRadius: '12px 12px 0 0',
|
bgcolor: colors.gray[100],
|
||||||
display: 'flex',
|
borderRadius: 3,
|
||||||
flexDirection: 'column',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
flexDirection: 'column',
|
||||||
justifyContent: 'center',
|
alignItems: 'center',
|
||||||
p: 4,
|
justifyContent: 'center',
|
||||||
textAlign: 'center',
|
p: 6,
|
||||||
}}
|
textAlign: 'center',
|
||||||
>
|
}}
|
||||||
<span className="material-icons" style={{ fontSize: 48, marginBottom: 16 }}>
|
>
|
||||||
celebration
|
<span className="material-icons" style={{ fontSize: 64, marginBottom: 24, color: colors.purple }}>
|
||||||
</span>
|
celebration
|
||||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 1 }}>
|
</span>
|
||||||
{title || '제목을 입력하세요'}
|
<Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 2 }}>
|
||||||
</Typography>
|
{title || '제목을 입력하세요'}
|
||||||
<Typography variant="body1" color="text.secondary" sx={{ mb: 2 }}>
|
</Typography>
|
||||||
{prize || '경품을 입력하세요'}
|
<Typography variant="body1" color="text.secondary" sx={{ ...responsiveText.body1, mb: 2 }}>
|
||||||
</Typography>
|
{prize || '경품을 입력하세요'}
|
||||||
<Typography variant="body2" color="text.secondary">
|
</Typography>
|
||||||
{guide || '참여 안내를 입력하세요'}
|
<Typography variant="body2" color="text.secondary" sx={{ ...responsiveText.body2 }}>
|
||||||
</Typography>
|
{guide || '참여 안내를 입력하세요'}
|
||||||
</Box>
|
</Typography>
|
||||||
|
</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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ mb: 8, textAlign: 'center', fontSize: '1rem' }}>
|
||||||
|
이벤트에 어울리는 스타일을 선택하세요
|
||||||
|
</Typography>
|
||||||
|
|
||||||
<RadioGroup value={selectedStyle} onChange={(e) => handleStyleSelect(e.target.value)}>
|
<RadioGroup value={selectedStyle} onChange={(e) => handleStyleSelect(e.target.value)}>
|
||||||
{imageStyles.map((style) => (
|
<Grid container spacing={6} sx={{ mb: 10 }}>
|
||||||
<Box key={style.id} sx={{ mb: 4 }}>
|
{imageStyles.map((style) => (
|
||||||
<Typography variant="h6" sx={{ fontWeight: 700, mb: 2 }}>
|
<Grid item xs={12} md={4} key={style.id}>
|
||||||
{style.name}
|
<Card
|
||||||
</Typography>
|
elevation={0}
|
||||||
|
sx={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
borderRadius: 4,
|
||||||
|
border: selectedStyle === style.id ? 2 : 1,
|
||||||
|
borderColor: selectedStyle === style.id ? colors.purple : 'divider',
|
||||||
|
bgcolor: selectedStyle === style.id ? `${colors.purpleLight}40` : 'background.paper',
|
||||||
|
transition: 'all 0.3s',
|
||||||
|
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': {
|
||||||
|
borderColor: colors.purple,
|
||||||
|
transform: 'translateY(-2px)',
|
||||||
|
boxShadow: '0 8px 16px rgba(0, 0, 0, 0.15)',
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
onClick={() => handleStyleSelect(style.id)}
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
|
||||||
<Card
|
{/* 이미지 프리뷰 */}
|
||||||
elevation={0}
|
<Box
|
||||||
sx={{
|
|
||||||
cursor: 'pointer',
|
|
||||||
borderRadius: 3,
|
|
||||||
border: selectedStyle === style.id ? 3 : 1,
|
|
||||||
borderColor: selectedStyle === style.id ? 'error.main' : 'divider',
|
|
||||||
transition: 'all 0.3s',
|
|
||||||
position: 'relative',
|
|
||||||
'&:hover': {
|
|
||||||
transform: 'translateY(-2px)',
|
|
||||||
boxShadow: '0 8px 16px rgba(0, 0, 0, 0.1)',
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
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 }}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: '100%',
|
|
||||||
aspectRatio: '1 / 1',
|
|
||||||
background: style.gradient || '#f5f5f5',
|
|
||||||
borderRadius: '12px 12px 0 0',
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'column',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
p: 4,
|
|
||||||
textAlign: 'center',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="material-icons"
|
|
||||||
style={{
|
|
||||||
fontSize: 48,
|
|
||||||
marginBottom: 16,
|
|
||||||
color: style.textColor || 'inherit',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{style.icon}
|
|
||||||
</span>
|
|
||||||
<Typography
|
|
||||||
variant="body1"
|
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: 600,
|
width: '100%',
|
||||||
mb: 1,
|
aspectRatio: '1 / 1',
|
||||||
color: style.textColor || 'text.primary',
|
background: style.gradient || colors.gray[100],
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
p: 6,
|
||||||
|
textAlign: 'center',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{title}
|
<span
|
||||||
</Typography>
|
className="material-icons"
|
||||||
<Typography
|
style={{
|
||||||
variant="caption"
|
fontSize: 64,
|
||||||
sx={{
|
marginBottom: 24,
|
||||||
color: style.textColor || 'text.secondary',
|
color: style.textColor || colors.gray[700],
|
||||||
opacity: style.textColor ? 0.9 : 1,
|
}}
|
||||||
}}
|
>
|
||||||
>
|
{style.icon}
|
||||||
{prize}
|
</span>
|
||||||
</Typography>
|
<Typography
|
||||||
</Box>
|
variant="h6"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 700,
|
||||||
|
mb: 2,
|
||||||
|
color: style.textColor || 'text.primary',
|
||||||
|
fontSize: '1.25rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{
|
||||||
|
color: style.textColor || 'text.secondary',
|
||||||
|
opacity: style.textColor ? 0.9 : 1,
|
||||||
|
fontSize: '1rem',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{prize}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
|
||||||
<Box sx={{ p: 2, display: 'flex', justifyContent: 'flex-end' }}>
|
{/* 크게보기 버튼 */}
|
||||||
<Button
|
<Box sx={{ p: 4, display: 'flex', justifyContent: 'center' }}>
|
||||||
size="small"
|
<Button
|
||||||
startIcon={<ZoomIn />}
|
variant="outlined"
|
||||||
onClick={(e) => handlePreview(style, e)}
|
startIcon={<ZoomIn />}
|
||||||
>
|
onClick={(e) => handlePreview(style, e)}
|
||||||
크게보기
|
sx={{
|
||||||
</Button>
|
borderRadius: 2,
|
||||||
<FormControlLabel
|
py: 1.5,
|
||||||
value={style.id}
|
px: 4,
|
||||||
control={<Radio sx={{ display: 'none' }} />}
|
fontSize: '0.875rem',
|
||||||
label=""
|
fontWeight: 600,
|
||||||
sx={{ display: 'none' }}
|
}}
|
||||||
/>
|
>
|
||||||
</Box>
|
크게보기
|
||||||
</CardContent>
|
</Button>
|
||||||
</Card>
|
</Box>
|
||||||
</Box>
|
</CardContent>
|
||||||
))}
|
</Card>
|
||||||
|
</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',
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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}
|
||||||
event_busy
|
sx={{
|
||||||
</span>
|
...cardStyles.default,
|
||||||
<Typography variant="body1" color="text.secondary" sx={{ mt: 2 }}>
|
}}
|
||||||
검색 결과가 없습니다
|
>
|
||||||
</Typography>
|
<CardContent sx={{ textAlign: 'center', py: 20 }}>
|
||||||
</Box>
|
<Box sx={{ color: colors.gray[300], mb: 3 }}>
|
||||||
|
<span className="material-icons" style={{ fontSize: 72 }}>
|
||||||
|
event_busy
|
||||||
|
</span>
|
||||||
|
</Box>
|
||||||
|
<Typography variant="h6" sx={{ mb: 2, color: colors.gray[700] }}>
|
||||||
|
검색 결과가 없습니다
|
||||||
|
</Typography>
|
||||||
|
<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
|
<Box sx={{ mb: 4 }}>
|
||||||
sx={{
|
<Box
|
||||||
display: 'flex',
|
sx={{
|
||||||
alignItems: 'flex-start',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
justifyContent: 'space-between',
|
||||||
mb: 2,
|
alignItems: 'start',
|
||||||
}}
|
mb: 3,
|
||||||
>
|
}}
|
||||||
<Typography variant="h6" sx={{ fontWeight: 700, flex: 1 }}>
|
>
|
||||||
{event.title}
|
<Typography variant="h6" sx={{ fontWeight: 700, color: colors.gray[900], flex: 1 }}>
|
||||||
</Typography>
|
{event.title}
|
||||||
<Chip
|
</Typography>
|
||||||
label={`${getStatusText(event.status)}${
|
<Box
|
||||||
event.status === 'active'
|
sx={{
|
||||||
|
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}
|
</Box>
|
||||||
size="small"
|
|
||||||
sx={{ fontWeight: 600 }}
|
{/* Status Badges */}
|
||||||
/>
|
<Box sx={{ display: 'flex', gap: 1.5, flexWrap: 'wrap' }}>
|
||||||
|
{event.isUrgent && (
|
||||||
|
<Chip
|
||||||
|
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>
|
</Box>
|
||||||
|
|
||||||
{/* Stats */}
|
{/* Progress Bar for Active Events */}
|
||||||
<Grid container spacing={2} sx={{ mb: 2 }}>
|
{event.status === 'active' && (
|
||||||
<Grid item xs={6}>
|
<Box sx={{ mb: 4 }}>
|
||||||
<Box>
|
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<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 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>
|
||||||
|
</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>
|
<Box>
|
||||||
<Typography variant="caption" color="text.secondary">
|
<Typography
|
||||||
투자대비수익률
|
variant="body2"
|
||||||
|
sx={{ mb: 1, color: colors.gray[600], fontWeight: 500 }}
|
||||||
|
>
|
||||||
|
참여자
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography variant="body1" sx={{ fontWeight: 600, color: 'error.main' }}>
|
<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>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -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,9 +60,10 @@ 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 =
|
||||||
? Math.round(mockEvents.reduce((sum, e) => sum + e.roi, 0) / mockEvents.length)
|
mockEvents.length > 0
|
||||||
: 0;
|
? Math.round(mockEvents.reduce((sum, e) => sum + e.roi, 0) / mockEvents.length)
|
||||||
|
: 0;
|
||||||
|
|
||||||
const handleCreateEvent = () => {
|
const handleCreateEvent = () => {
|
||||||
router.push('/events/create');
|
router.push('/events/create');
|
||||||
@ -91,380 +88,470 @@ 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={{
|
|
||||||
...responsiveText.h2,
|
|
||||||
mb: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
안녕하세요, {mockUser.name}님! 👋
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="body1" sx={{ ...responsiveText.body1 }}>
|
|
||||||
이벤트 현황을 한눈에 확인하고 성과를 분석해보세요
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* KPI Cards */}
|
|
||||||
<Grid container spacing={4} sx={{ mb: 6 }}>
|
|
||||||
<Grid item xs={12} sm={4}>
|
|
||||||
<Card
|
|
||||||
elevation={0}
|
|
||||||
sx={{
|
sx={{
|
||||||
...cardStyles.default,
|
...responsiveText.h2,
|
||||||
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
|
mb: 4,
|
||||||
borderColor: 'transparent',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CardContent sx={{ textAlign: 'center', py: 4, px: 3 }}>
|
안녕하세요, {mockUser.name}님! 👋
|
||||||
<Box
|
</Typography>
|
||||||
sx={{
|
<Typography variant="body1" sx={{ ...responsiveText.body1 }}>
|
||||||
width: 64,
|
이벤트 현황을 한눈에 확인하고 성과를 분석해보세요
|
||||||
height: 64,
|
|
||||||
borderRadius: '50%',
|
|
||||||
bgcolor: 'rgba(255, 255, 255, 0.2)',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
margin: '0 auto',
|
|
||||||
mb: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Celebration sx={{ fontSize: 32, color: 'white' }} />
|
|
||||||
</Box>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ mb: 1, color: 'rgba(255, 255, 255, 0.9)', fontWeight: 500, fontSize: '0.875rem' }}
|
|
||||||
>
|
|
||||||
진행 중인 이벤트
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}>
|
|
||||||
{activeEvents.length}
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} sm={4}>
|
|
||||||
<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 }}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: 64,
|
|
||||||
height: 64,
|
|
||||||
borderRadius: '50%',
|
|
||||||
bgcolor: 'rgba(255, 255, 255, 0.2)',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
margin: '0 auto',
|
|
||||||
mb: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Group sx={{ fontSize: 32, color: 'white' }} />
|
|
||||||
</Box>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ mb: 1, color: 'rgba(255, 255, 255, 0.9)', fontWeight: 500, fontSize: '0.875rem' }}
|
|
||||||
>
|
|
||||||
총 참여자
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}>
|
|
||||||
{totalParticipants.toLocaleString()}
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={12} sm={4}>
|
|
||||||
<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 }}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: 64,
|
|
||||||
height: 64,
|
|
||||||
borderRadius: '50%',
|
|
||||||
bgcolor: 'rgba(255, 255, 255, 0.2)',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
margin: '0 auto',
|
|
||||||
mb: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<TrendingUp sx={{ fontSize: 32, color: 'white' }} />
|
|
||||||
</Box>
|
|
||||||
<Typography
|
|
||||||
variant="body2"
|
|
||||||
sx={{ mb: 1, color: 'rgba(255, 255, 255, 0.9)', fontWeight: 500, fontSize: '0.875rem' }}
|
|
||||||
>
|
|
||||||
평균 ROI
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="h3" sx={{ fontWeight: 700, color: 'white', fontSize: '2.5rem' }}>
|
|
||||||
{avgROI}%
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
{/* Quick Actions */}
|
|
||||||
<Box sx={{ mb: 6 }}>
|
|
||||||
<Typography variant="h5" sx={{ ...responsiveText.h3, mb: 4 }}>
|
|
||||||
빠른 시작
|
|
||||||
</Typography>
|
|
||||||
<Grid container spacing={4}>
|
|
||||||
<Grid item xs={6} sm={6}>
|
|
||||||
<Card
|
|
||||||
elevation={0}
|
|
||||||
sx={{
|
|
||||||
...cardStyles.clickable,
|
|
||||||
}}
|
|
||||||
onClick={handleCreateEvent}
|
|
||||||
>
|
|
||||||
<CardContent sx={{ textAlign: 'center', py: 5 }}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: 72,
|
|
||||||
height: 72,
|
|
||||||
borderRadius: '20px',
|
|
||||||
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.blue} 100%)`,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
margin: '0 auto',
|
|
||||||
mb: 2.5,
|
|
||||||
boxShadow: '0 4px 14px 0 rgba(167, 139, 250, 0.39)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Add sx={{ fontSize: 36, color: 'white' }} />
|
|
||||||
</Box>
|
|
||||||
<Typography variant="body1" sx={{ fontWeight: 600, color: colors.gray[900] }}>
|
|
||||||
새 이벤트
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
<Grid item xs={6} sm={6}>
|
|
||||||
<Card
|
|
||||||
elevation={0}
|
|
||||||
sx={{
|
|
||||||
...cardStyles.clickable,
|
|
||||||
}}
|
|
||||||
onClick={handleViewAnalytics}
|
|
||||||
>
|
|
||||||
<CardContent sx={{ textAlign: 'center', py: 5 }}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
width: 72,
|
|
||||||
height: 72,
|
|
||||||
borderRadius: '20px',
|
|
||||||
background: `linear-gradient(135deg, ${colors.blue} 0%, ${colors.mint} 100%)`,
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
margin: '0 auto',
|
|
||||||
mb: 2.5,
|
|
||||||
boxShadow: '0 4px 14px 0 rgba(96, 165, 250, 0.39)',
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Analytics sx={{ fontSize: 36, color: 'white' }} />
|
|
||||||
</Box>
|
|
||||||
<Typography variant="body1" sx={{ fontWeight: 600, color: colors.gray[900] }}>
|
|
||||||
성과 분석
|
|
||||||
</Typography>
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</Grid>
|
|
||||||
</Grid>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* Active Events */}
|
|
||||||
<Box sx={{ mb: 6 }}>
|
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 4 }}>
|
|
||||||
<Typography variant="h5" sx={{ ...responsiveText.h3 }}>
|
|
||||||
진행 중인 이벤트
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Button
|
|
||||||
size="medium"
|
|
||||||
endIcon={<span className="material-icons">chevron_right</span>}
|
|
||||||
onClick={() => router.push('/events')}
|
|
||||||
sx={{
|
|
||||||
color: colors.purple,
|
|
||||||
fontWeight: 600,
|
|
||||||
'&:hover': { bgcolor: 'rgba(167, 139, 250, 0.08)' },
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
전체보기
|
|
||||||
</Button>
|
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
{activeEvents.length === 0 ? (
|
{/* KPI Cards */}
|
||||||
<Card
|
<Grid container spacing={6} sx={{ mb: 10 }}>
|
||||||
elevation={0}
|
<Grid item xs={12} sm={4}>
|
||||||
sx={{
|
<Card
|
||||||
...cardStyles.default,
|
elevation={0}
|
||||||
}}
|
sx={{
|
||||||
>
|
...cardStyles.default,
|
||||||
<CardContent sx={{ textAlign: 'center', py: 10 }}>
|
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
|
||||||
<Box sx={{ color: colors.gray[300], mb: 3 }}>
|
borderColor: 'transparent',
|
||||||
<span className="material-icons" style={{ fontSize: 72 }}>
|
}}
|
||||||
event_busy
|
>
|
||||||
</span>
|
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||||||
</Box>
|
<Box
|
||||||
<Typography variant="h6" sx={{ mb: 2, color: colors.gray[700] }}>
|
sx={{
|
||||||
진행 중인 이벤트가 없습니다
|
width: 64,
|
||||||
</Typography>
|
height: 64,
|
||||||
<Typography variant="body2" sx={{ mb: 4, color: colors.gray[500] }}>
|
borderRadius: '50%',
|
||||||
새로운 이벤트를 만들어 고객과 소통해보세요
|
bgcolor: 'rgba(0, 0, 0, 0.05)',
|
||||||
</Typography>
|
display: 'flex',
|
||||||
<Button
|
alignItems: 'center',
|
||||||
variant="contained"
|
justifyContent: 'center',
|
||||||
startIcon={<Add />}
|
margin: '0 auto',
|
||||||
onClick={handleCreateEvent}
|
mb: 3,
|
||||||
sx={{
|
}}
|
||||||
py: { xs: 1.5, sm: 1.75 },
|
>
|
||||||
px: { xs: 3, sm: 4 },
|
<Celebration sx={{
|
||||||
fontSize: { xs: 15, sm: 16 },
|
fontSize: 32,
|
||||||
...getGradientButtonStyle('primary'),
|
color: colors.gray[900],
|
||||||
}}
|
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
|
||||||
>
|
}} />
|
||||||
새 이벤트 만들기
|
</Box>
|
||||||
</Button>
|
<Typography
|
||||||
</CardContent>
|
variant="body2"
|
||||||
</Card>
|
sx={{
|
||||||
) : (
|
mb: 1,
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
color: colors.gray[700],
|
||||||
{activeEvents.map((event) => (
|
fontWeight: 500,
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
진행 중인 이벤트
|
||||||
|
</Typography>
|
||||||
|
<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}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={4}>
|
||||||
|
<Card
|
||||||
|
elevation={0}
|
||||||
|
sx={{
|
||||||
|
...cardStyles.default,
|
||||||
|
background: `linear-gradient(135deg, ${colors.mint} 0%, ${colors.mintLight} 100%)`,
|
||||||
|
borderColor: 'transparent',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
borderRadius: '50%',
|
||||||
|
bgcolor: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: '0 auto',
|
||||||
|
mb: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Group sx={{
|
||||||
|
fontSize: 32,
|
||||||
|
color: colors.gray[900],
|
||||||
|
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
|
||||||
|
}} />
|
||||||
|
</Box>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
mb: 1,
|
||||||
|
color: colors.gray[700],
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
총 참여자
|
||||||
|
</Typography>
|
||||||
|
<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()}
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} sm={4}>
|
||||||
|
<Card
|
||||||
|
elevation={0}
|
||||||
|
sx={{
|
||||||
|
...cardStyles.default,
|
||||||
|
background: `linear-gradient(135deg, ${colors.blue} 0%, ${colors.blueLight} 100%)`,
|
||||||
|
borderColor: 'transparent',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 64,
|
||||||
|
height: 64,
|
||||||
|
borderRadius: '50%',
|
||||||
|
bgcolor: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: '0 auto',
|
||||||
|
mb: 3,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TrendingUp sx={{
|
||||||
|
fontSize: 32,
|
||||||
|
color: colors.gray[900],
|
||||||
|
filter: 'drop-shadow(0px 2px 4px rgba(0,0,0,0.2))',
|
||||||
|
}} />
|
||||||
|
</Box>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
mb: 1,
|
||||||
|
color: colors.gray[700],
|
||||||
|
fontWeight: 500,
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
textShadow: '0px 1px 2px rgba(0,0,0,0.1)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
평균 ROI
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="h3"
|
||||||
|
sx={{
|
||||||
|
fontWeight: 700,
|
||||||
|
color: colors.gray[900],
|
||||||
|
fontSize: '2.25rem',
|
||||||
|
textShadow: '0px 2px 4px rgba(0,0,0,0.15)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{avgROI}%
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{/* Quick Actions */}
|
||||||
|
<Box sx={{ mb: 10 }}>
|
||||||
|
<Typography variant="h5" sx={{ ...responsiveText.h3, mb: 6 }}>
|
||||||
|
빠른 시작
|
||||||
|
</Typography>
|
||||||
|
<Grid container spacing={6}>
|
||||||
|
<Grid item xs={6} sm={6}>
|
||||||
<Card
|
<Card
|
||||||
key={event.id}
|
|
||||||
elevation={0}
|
elevation={0}
|
||||||
sx={{
|
sx={{
|
||||||
...cardStyles.clickable,
|
...cardStyles.clickable,
|
||||||
}}
|
}}
|
||||||
onClick={() => handleEventClick(event.id)}
|
onClick={handleCreateEvent}
|
||||||
>
|
>
|
||||||
<CardContent sx={{ p: { xs: 3, sm: 4 } }}>
|
<CardContent sx={{ textAlign: 'center', py: 6 }}>
|
||||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'start', mb: 3 }}>
|
<Box
|
||||||
<Typography variant="h6" sx={{ fontWeight: 700, color: colors.gray[900] }}>
|
sx={{
|
||||||
{event.title}
|
width: 72,
|
||||||
</Typography>
|
height: 72,
|
||||||
<Box
|
borderRadius: '20px',
|
||||||
sx={{
|
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.blue} 100%)`,
|
||||||
px: 2.5,
|
display: 'flex',
|
||||||
py: 0.75,
|
alignItems: 'center',
|
||||||
bgcolor: colors.mint,
|
justifyContent: 'center',
|
||||||
color: 'white',
|
margin: '0 auto',
|
||||||
borderRadius: 2,
|
mb: 3,
|
||||||
fontSize: '0.875rem',
|
boxShadow: '0 4px 14px 0 rgba(167, 139, 250, 0.39)',
|
||||||
fontWeight: 600,
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Add sx={{ fontSize: 36, color: 'white' }} />
|
||||||
{event.status}
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
<Typography variant="body2" sx={{ mb: 3, color: colors.gray[600], display: 'flex', alignItems: 'center', gap: 1 }}>
|
<Typography variant="body1" sx={{ fontWeight: 600, color: colors.gray[900], fontSize: '1.125rem' }}>
|
||||||
<span>📅</span>
|
새 이벤트
|
||||||
<span>{event.startDate} ~ {event.endDate}</span>
|
|
||||||
</Typography>
|
</Typography>
|
||||||
<Box sx={{ display: 'flex', gap: 6 }}>
|
|
||||||
<Box>
|
|
||||||
<Typography variant="body2" sx={{ mb: 0.5, color: colors.gray[600], fontWeight: 500 }}>
|
|
||||||
참여자
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="h5" sx={{ fontWeight: 700, color: colors.gray[900] }}>
|
|
||||||
{event.participants.toLocaleString()}
|
|
||||||
<Typography component="span" variant="body2" sx={{ ml: 0.5, color: colors.gray[600] }}>
|
|
||||||
명
|
|
||||||
</Typography>
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box>
|
|
||||||
<Typography variant="body2" sx={{ mb: 0.5, color: colors.gray[600], fontWeight: 500 }}>
|
|
||||||
ROI
|
|
||||||
</Typography>
|
|
||||||
<Typography variant="h5" sx={{ fontWeight: 700, color: colors.mint }}>
|
|
||||||
{event.roi}%
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
</Grid>
|
||||||
</Box>
|
<Grid item xs={6} sm={6}>
|
||||||
)}
|
<Card
|
||||||
</Box>
|
elevation={0}
|
||||||
|
|
||||||
{/* Recent Activity */}
|
|
||||||
<Box sx={{ mb: 6 }}>
|
|
||||||
<Typography variant="h5" sx={{ ...responsiveText.h3, mb: 4 }}>
|
|
||||||
최근 활동
|
|
||||||
</Typography>
|
|
||||||
<Card
|
|
||||||
elevation={0}
|
|
||||||
sx={{
|
|
||||||
...cardStyles.default,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CardContent sx={{ p: { xs: 3, sm: 4 } }}>
|
|
||||||
{mockActivities.map((activity, index) => (
|
|
||||||
<Box
|
|
||||||
key={index}
|
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
...cardStyles.clickable,
|
||||||
gap: 3,
|
|
||||||
pt: index > 0 ? 3.5 : 0,
|
|
||||||
mt: index > 0 ? 3.5 : 0,
|
|
||||||
borderTop: index > 0 ? 1 : 0,
|
|
||||||
borderColor: colors.gray[200],
|
|
||||||
}}
|
}}
|
||||||
|
onClick={handleViewAnalytics}
|
||||||
>
|
>
|
||||||
<Box
|
<CardContent sx={{ textAlign: 'center', py: 6 }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 72,
|
||||||
|
height: 72,
|
||||||
|
borderRadius: '20px',
|
||||||
|
background: `linear-gradient(135deg, ${colors.blue} 0%, ${colors.mint} 100%)`,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
margin: '0 auto',
|
||||||
|
mb: 3,
|
||||||
|
boxShadow: '0 4px 14px 0 rgba(96, 165, 250, 0.39)',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Analytics sx={{ fontSize: 36, color: 'white' }} />
|
||||||
|
</Box>
|
||||||
|
<Typography variant="body1" sx={{ fontWeight: 600, color: colors.gray[900], fontSize: '1.125rem' }}>
|
||||||
|
성과분석
|
||||||
|
</Typography>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* Active Events */}
|
||||||
|
<Box sx={{ mb: 10 }}>
|
||||||
|
<Box
|
||||||
|
sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 6 }}
|
||||||
|
>
|
||||||
|
<Typography variant="h5" sx={{ ...responsiveText.h3 }}>
|
||||||
|
진행 중인 이벤트
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
size="medium"
|
||||||
|
endIcon={<span className="material-icons">chevron_right</span>}
|
||||||
|
onClick={() => router.push('/events')}
|
||||||
|
sx={{
|
||||||
|
color: colors.purple,
|
||||||
|
fontWeight: 600,
|
||||||
|
'&:hover': { bgcolor: 'rgba(167, 139, 250, 0.08)' },
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
전체보기
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{activeEvents.length === 0 ? (
|
||||||
|
<Card
|
||||||
|
elevation={0}
|
||||||
|
sx={{
|
||||||
|
...cardStyles.default,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent sx={{ textAlign: 'center', py: 10 }}>
|
||||||
|
<Box sx={{ color: colors.gray[300], mb: 3 }}>
|
||||||
|
<span className="material-icons" style={{ fontSize: 72 }}>
|
||||||
|
event_busy
|
||||||
|
</span>
|
||||||
|
</Box>
|
||||||
|
<Typography variant="h6" sx={{ mb: 2, color: colors.gray[700] }}>
|
||||||
|
진행 중인 이벤트가 없습니다
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ mb: 4, color: colors.gray[500] }}>
|
||||||
|
새로운 이벤트를 만들어 고객과 소통해보세요
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
startIcon={<Add />}
|
||||||
|
onClick={handleCreateEvent}
|
||||||
sx={{
|
sx={{
|
||||||
width: 48,
|
py: { xs: 1.5, sm: 1.75 },
|
||||||
height: 48,
|
px: { xs: 3, sm: 4 },
|
||||||
borderRadius: '14px',
|
fontSize: { xs: 15, sm: 16 },
|
||||||
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
|
...getGradientButtonStyle('primary'),
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
flexShrink: 0,
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<activity.icon sx={{ fontSize: 24, color: 'white' }} />
|
새 이벤트 만들기
|
||||||
</Box>
|
</Button>
|
||||||
<Box sx={{ flex: 1 }}>
|
</CardContent>
|
||||||
<Typography variant="body1" sx={{ fontWeight: 600, color: colors.gray[900], mb: 0.5 }}>
|
</Card>
|
||||||
{activity.text}
|
) : (
|
||||||
</Typography>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 6 }}>
|
||||||
<Typography variant="body2" sx={{ color: colors.gray[500] }}>
|
{activeEvents.map((event) => (
|
||||||
{activity.time}
|
<Card
|
||||||
</Typography>
|
key={event.id}
|
||||||
</Box>
|
elevation={0}
|
||||||
</Box>
|
sx={{
|
||||||
))}
|
...cardStyles.clickable,
|
||||||
</CardContent>
|
}}
|
||||||
</Card>
|
onClick={() => handleEventClick(event.id)}
|
||||||
</Box>
|
>
|
||||||
</Container>
|
<CardContent sx={{ p: { xs: 6, sm: 8 } }}>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'start',
|
||||||
|
mb: 6,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="h6" sx={{ fontWeight: 700, color: colors.gray[900] }}>
|
||||||
|
{event.title}
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
px: 2.5,
|
||||||
|
py: 0.75,
|
||||||
|
bgcolor: colors.mint,
|
||||||
|
color: 'white',
|
||||||
|
borderRadius: 2,
|
||||||
|
fontSize: '0.875rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{event.status}
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{
|
||||||
|
mb: 6,
|
||||||
|
color: colors.gray[600],
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span>📅</span>
|
||||||
|
<span>
|
||||||
|
{event.startDate} ~ {event.endDate}
|
||||||
|
</span>
|
||||||
|
</Typography>
|
||||||
|
<Box sx={{ display: 'flex', gap: 12 }}>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{ mb: 0.5, color: colors.gray[600], fontWeight: 500 }}
|
||||||
|
>
|
||||||
|
참여자
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="h5"
|
||||||
|
sx={{ fontWeight: 700, color: colors.gray[900] }}
|
||||||
|
>
|
||||||
|
{event.participants.toLocaleString()}
|
||||||
|
<Typography
|
||||||
|
component="span"
|
||||||
|
variant="body2"
|
||||||
|
sx={{ ml: 0.5, color: colors.gray[600] }}
|
||||||
|
>
|
||||||
|
명
|
||||||
|
</Typography>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box>
|
||||||
|
<Typography
|
||||||
|
variant="body2"
|
||||||
|
sx={{ mb: 0.5, color: colors.gray[600], fontWeight: 500 }}
|
||||||
|
>
|
||||||
|
ROI
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="h5" sx={{ fontWeight: 700, color: colors.mint }}>
|
||||||
|
{event.roi}%
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
{/* Floating Action Button */}
|
{/* Recent Activity */}
|
||||||
<Fab
|
<Box sx={{ mb: 10 }}>
|
||||||
|
<Typography variant="h5" sx={{ ...responsiveText.h3, mb: 6 }}>
|
||||||
|
최근 활동
|
||||||
|
</Typography>
|
||||||
|
<Card
|
||||||
|
elevation={0}
|
||||||
|
sx={{
|
||||||
|
...cardStyles.default,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CardContent sx={{ p: { xs: 6, sm: 8 } }}>
|
||||||
|
{mockActivities.map((activity, index) => (
|
||||||
|
<Box
|
||||||
|
key={index}
|
||||||
|
sx={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 4,
|
||||||
|
pt: index > 0 ? 6 : 0,
|
||||||
|
mt: index > 0 ? 6 : 0,
|
||||||
|
borderTop: index > 0 ? 1 : 0,
|
||||||
|
borderColor: colors.gray[200],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
width: 48,
|
||||||
|
height: 48,
|
||||||
|
borderRadius: '14px',
|
||||||
|
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<activity.icon sx={{ fontSize: 24, color: 'white' }} />
|
||||||
|
</Box>
|
||||||
|
<Box sx={{ flex: 1 }}>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
sx={{ fontWeight: 600, color: colors.gray[900], mb: 0.5 }}
|
||||||
|
>
|
||||||
|
{activity.text}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" sx={{ color: colors.gray[500] }}>
|
||||||
|
{activity.time}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
))}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
{/* Floating Action Button */}
|
||||||
|
<Fab
|
||||||
sx={{
|
sx={{
|
||||||
position: 'fixed',
|
position: 'fixed',
|
||||||
bottom: { xs: 80, sm: 90 },
|
bottom: { xs: 80, sm: 90 },
|
||||||
@ -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}
|
||||||
|
|||||||
@ -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,304 +189,341 @@ 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' }}>
|
sx={{
|
||||||
<Avatar
|
pt: { xs: 7, sm: 8 },
|
||||||
sx={{
|
pb: 10,
|
||||||
width: 80,
|
bgcolor: colors.gray[50],
|
||||||
height: 80,
|
minHeight: '100vh',
|
||||||
mx: 'auto',
|
}}
|
||||||
mb: 2,
|
>
|
||||||
bgcolor: 'grey.100',
|
<Container maxWidth="lg" sx={{ pt: 8, pb: 6, px: { xs: 6, sm: 8, md: 10 } }}>
|
||||||
color: 'grey.400',
|
{/* 사용자 정보 섹션 */}
|
||||||
}}
|
<Card elevation={0} sx={{ ...cardStyles.default, mb: 10, textAlign: 'center' }}>
|
||||||
>
|
<CardContent sx={{ p: { xs: 6, sm: 8 } }}>
|
||||||
<Person sx={{ fontSize: 48 }} />
|
<Avatar
|
||||||
</Avatar>
|
sx={{
|
||||||
<Typography variant="h5" sx={{ ...responsiveText.h3, mb: 0.5 }}>
|
width: 100,
|
||||||
{user?.name}
|
height: 100,
|
||||||
</Typography>
|
mx: 'auto',
|
||||||
<Typography variant="body2" color="text.secondary">
|
mb: 3,
|
||||||
{user?.email}
|
bgcolor: colors.purple,
|
||||||
</Typography>
|
color: 'white',
|
||||||
</Paper>
|
}}
|
||||||
|
>
|
||||||
|
<Person sx={{ fontSize: 56 }} />
|
||||||
|
</Avatar>
|
||||||
|
<Typography variant="h5" sx={{ ...responsiveText.h3, mb: 1 }}>
|
||||||
|
{user?.name}
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="body2" color="text.secondary" sx={{ ...responsiveText.body1 }}>
|
||||||
|
{user?.email}
|
||||||
|
</Typography>
|
||||||
|
</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>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
<Controller
|
<Controller
|
||||||
name="name"
|
name="name"
|
||||||
control={basicControl}
|
control={basicControl}
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<TextField
|
<TextField
|
||||||
{...field}
|
{...field}
|
||||||
fullWidth
|
fullWidth
|
||||||
label="이름"
|
label="이름"
|
||||||
error={!!basicErrors.name}
|
error={!!basicErrors.name}
|
||||||
helperText={basicErrors.name?.message}
|
helperText={basicErrors.name?.message}
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="phone"
|
|
||||||
control={basicControl}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
fullWidth
|
|
||||||
label="전화번호"
|
|
||||||
onChange={(e) => {
|
|
||||||
const formatted = formatPhoneNumber(e.target.value);
|
|
||||||
field.onChange(formatted);
|
|
||||||
}}
|
|
||||||
error={!!basicErrors.phone}
|
|
||||||
helperText={basicErrors.phone?.message}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="email"
|
|
||||||
control={basicControl}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
fullWidth
|
|
||||||
label="이메일"
|
|
||||||
type="email"
|
|
||||||
error={!!basicErrors.email}
|
|
||||||
helperText={basicErrors.email?.message}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* 매장 정보 */}
|
|
||||||
<Paper elevation={0} sx={{ p: 4, mb: 3 }}>
|
|
||||||
<Typography variant="h6" sx={{ ...responsiveText.h4, mb: 3 }}>
|
|
||||||
매장 정보
|
|
||||||
</Typography>
|
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
|
||||||
<Controller
|
|
||||||
name="businessName"
|
|
||||||
control={businessControl}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
fullWidth
|
|
||||||
label="매장명"
|
|
||||||
error={!!businessErrors.businessName}
|
|
||||||
helperText={businessErrors.businessName?.message}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="businessType"
|
|
||||||
control={businessControl}
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl fullWidth error={!!businessErrors.businessType}>
|
|
||||||
<InputLabel>업종</InputLabel>
|
|
||||||
<Select {...field} label="업종">
|
|
||||||
<MenuItem value="restaurant">음식점</MenuItem>
|
|
||||||
<MenuItem value="cafe">카페/베이커리</MenuItem>
|
|
||||||
<MenuItem value="retail">소매/편의점</MenuItem>
|
|
||||||
<MenuItem value="beauty">미용/뷰티</MenuItem>
|
|
||||||
<MenuItem value="fitness">헬스/피트니스</MenuItem>
|
|
||||||
<MenuItem value="education">학원/교육</MenuItem>
|
|
||||||
<MenuItem value="service">서비스업</MenuItem>
|
|
||||||
<MenuItem value="other">기타</MenuItem>
|
|
||||||
</Select>
|
|
||||||
{businessErrors.businessType && (
|
|
||||||
<Typography variant="caption" color="error" sx={{ mt: 0.5, ml: 2 }}>
|
|
||||||
{businessErrors.businessType.message}
|
|
||||||
</Typography>
|
|
||||||
)}
|
)}
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="businessLocation"
|
|
||||||
control={businessControl}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField {...field} fullWidth label="주소" />
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="businessHours"
|
|
||||||
control={businessControl}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
fullWidth
|
|
||||||
label="영업시간"
|
|
||||||
placeholder="예: 10:00 ~ 22:00"
|
|
||||||
/>
|
/>
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Paper>
|
|
||||||
|
|
||||||
{/* 비밀번호 변경 */}
|
<Controller
|
||||||
<Paper elevation={0} sx={{ p: 4, mb: 3 }}>
|
name="phone"
|
||||||
<Typography variant="h6" sx={{ ...responsiveText.h4, mb: 3 }}>
|
control={basicControl}
|
||||||
비밀번호 변경
|
render={({ field }) => (
|
||||||
</Typography>
|
<TextField
|
||||||
|
{...field}
|
||||||
|
fullWidth
|
||||||
|
label="전화번호"
|
||||||
|
onChange={(e) => {
|
||||||
|
const formatted = formatPhoneNumber(e.target.value);
|
||||||
|
field.onChange(formatted);
|
||||||
|
}}
|
||||||
|
error={!!basicErrors.phone}
|
||||||
|
helperText={basicErrors.phone?.message}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
<Controller
|
||||||
<Controller
|
name="email"
|
||||||
name="currentPassword"
|
control={basicControl}
|
||||||
control={passwordControl}
|
render={({ field }) => (
|
||||||
render={({ field }) => (
|
<TextField
|
||||||
<TextField
|
{...field}
|
||||||
{...field}
|
fullWidth
|
||||||
|
label="이메일"
|
||||||
|
type="email"
|
||||||
|
error={!!basicErrors.email}
|
||||||
|
helperText={basicErrors.email?.message}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 매장 정보 */}
|
||||||
|
<Card elevation={0} sx={{ ...cardStyles.default, mb: 10 }}>
|
||||||
|
<CardContent sx={{ p: { xs: 6, sm: 8 } }}>
|
||||||
|
<Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 6 }}>
|
||||||
|
매장 정보
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
|
<Controller
|
||||||
|
name="businessName"
|
||||||
|
control={businessControl}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
fullWidth
|
||||||
|
label="매장명"
|
||||||
|
error={!!businessErrors.businessName}
|
||||||
|
helperText={businessErrors.businessName?.message}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="businessType"
|
||||||
|
control={businessControl}
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl fullWidth error={!!businessErrors.businessType}>
|
||||||
|
<InputLabel>업종</InputLabel>
|
||||||
|
<Select {...field} label="업종">
|
||||||
|
<MenuItem value="restaurant">음식점</MenuItem>
|
||||||
|
<MenuItem value="cafe">카페/베이커리</MenuItem>
|
||||||
|
<MenuItem value="retail">소매/편의점</MenuItem>
|
||||||
|
<MenuItem value="beauty">미용/뷰티</MenuItem>
|
||||||
|
<MenuItem value="fitness">헬스/피트니스</MenuItem>
|
||||||
|
<MenuItem value="education">학원/교육</MenuItem>
|
||||||
|
<MenuItem value="service">서비스업</MenuItem>
|
||||||
|
<MenuItem value="other">기타</MenuItem>
|
||||||
|
</Select>
|
||||||
|
{businessErrors.businessType && (
|
||||||
|
<Typography variant="caption" color="error" sx={{ mt: 0.5, ml: 2 }}>
|
||||||
|
{businessErrors.businessType.message}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="businessLocation"
|
||||||
|
control={businessControl}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField {...field} fullWidth label="주소" />
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="businessHours"
|
||||||
|
control={businessControl}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
fullWidth
|
||||||
|
label="영업시간"
|
||||||
|
placeholder="예: 10:00 ~ 22:00"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 비밀번호 변경 */}
|
||||||
|
<Card elevation={0} sx={{ ...cardStyles.default, mb: 10 }}>
|
||||||
|
<CardContent sx={{ p: { xs: 6, sm: 8 } }}>
|
||||||
|
<Typography variant="h6" sx={{ ...responsiveText.h4, fontWeight: 700, mb: 6 }}>
|
||||||
|
비밀번호 변경
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
|
||||||
|
<Controller
|
||||||
|
name="currentPassword"
|
||||||
|
control={passwordControl}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
fullWidth
|
||||||
|
type={showCurrentPassword ? 'text' : 'password'}
|
||||||
|
label="현재 비밀번호"
|
||||||
|
placeholder="현재 비밀번호를 입력하세요"
|
||||||
|
error={!!passwordErrors.currentPassword}
|
||||||
|
helperText={passwordErrors.currentPassword?.message}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowCurrentPassword(!showCurrentPassword)}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showCurrentPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="newPassword"
|
||||||
|
control={passwordControl}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
fullWidth
|
||||||
|
type={showNewPassword ? 'text' : 'password'}
|
||||||
|
label="새 비밀번호"
|
||||||
|
placeholder="새 비밀번호를 입력하세요"
|
||||||
|
error={!!passwordErrors.newPassword}
|
||||||
|
helperText={passwordErrors.newPassword?.message || '8자 이상, 영문과 숫자를 포함해주세요'}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowNewPassword(!showNewPassword)}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showNewPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
name="confirmPassword"
|
||||||
|
control={passwordControl}
|
||||||
|
render={({ field }) => (
|
||||||
|
<TextField
|
||||||
|
{...field}
|
||||||
|
fullWidth
|
||||||
|
type={showConfirmPassword ? 'text' : 'password'}
|
||||||
|
label="비밀번호 확인"
|
||||||
|
placeholder="비밀번호를 다시 입력하세요"
|
||||||
|
error={!!passwordErrors.confirmPassword}
|
||||||
|
helperText={passwordErrors.confirmPassword?.message}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton
|
||||||
|
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
||||||
|
edge="end"
|
||||||
|
>
|
||||||
|
{showConfirmPassword ? <VisibilityOff /> : <Visibility />}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
type={showCurrentPassword ? 'text' : 'password'}
|
variant="outlined"
|
||||||
label="현재 비밀번호"
|
size="large"
|
||||||
placeholder="현재 비밀번호를 입력하세요"
|
onClick={handlePasswordSubmit(onChangePassword)}
|
||||||
error={!!passwordErrors.currentPassword}
|
sx={{
|
||||||
helperText={passwordErrors.currentPassword?.message}
|
mt: 1,
|
||||||
InputProps={{
|
py: 3,
|
||||||
endAdornment: (
|
borderRadius: 3,
|
||||||
<InputAdornment position="end">
|
fontSize: '1rem',
|
||||||
<IconButton
|
fontWeight: 600,
|
||||||
onClick={() => setShowCurrentPassword(!showCurrentPassword)}
|
borderWidth: 2,
|
||||||
edge="end"
|
'&:hover': {
|
||||||
>
|
borderWidth: 2,
|
||||||
{showCurrentPassword ? <VisibilityOff /> : <Visibility />}
|
},
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
}}
|
||||||
/>
|
>
|
||||||
)}
|
비밀번호 변경
|
||||||
/>
|
</Button>
|
||||||
|
</Box>
|
||||||
<Controller
|
</CardContent>
|
||||||
name="newPassword"
|
</Card>
|
||||||
control={passwordControl}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
fullWidth
|
|
||||||
type={showNewPassword ? 'text' : 'password'}
|
|
||||||
label="새 비밀번호"
|
|
||||||
placeholder="새 비밀번호를 입력하세요"
|
|
||||||
error={!!passwordErrors.newPassword}
|
|
||||||
helperText={passwordErrors.newPassword?.message || '8자 이상, 영문과 숫자를 포함해주세요'}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setShowNewPassword(!showNewPassword)}
|
|
||||||
edge="end"
|
|
||||||
>
|
|
||||||
{showNewPassword ? <VisibilityOff /> : <Visibility />}
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Controller
|
|
||||||
name="confirmPassword"
|
|
||||||
control={passwordControl}
|
|
||||||
render={({ field }) => (
|
|
||||||
<TextField
|
|
||||||
{...field}
|
|
||||||
fullWidth
|
|
||||||
type={showConfirmPassword ? 'text' : 'password'}
|
|
||||||
label="비밀번호 확인"
|
|
||||||
placeholder="비밀번호를 다시 입력하세요"
|
|
||||||
error={!!passwordErrors.confirmPassword}
|
|
||||||
helperText={passwordErrors.confirmPassword?.message}
|
|
||||||
InputProps={{
|
|
||||||
endAdornment: (
|
|
||||||
<InputAdornment position="end">
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
|
||||||
edge="end"
|
|
||||||
>
|
|
||||||
{showConfirmPassword ? <VisibilityOff /> : <Visibility />}
|
|
||||||
</IconButton>
|
|
||||||
</InputAdornment>
|
|
||||||
),
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
{/* 액션 버튼 */}
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||||
<Button
|
<Button
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="outlined"
|
variant="contained"
|
||||||
size="large"
|
size="large"
|
||||||
onClick={handlePasswordSubmit(onChangePassword)}
|
onClick={handleSave}
|
||||||
sx={{
|
sx={{
|
||||||
mt: 1,
|
py: 3,
|
||||||
py: { xs: 1.5, sm: 1.75 },
|
borderRadius: 3,
|
||||||
fontSize: { xs: 15, sm: 16 },
|
fontSize: '1rem',
|
||||||
fontWeight: 600,
|
fontWeight: 700,
|
||||||
borderWidth: 2,
|
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
|
||||||
|
fullWidth
|
||||||
|
variant="text"
|
||||||
|
size="large"
|
||||||
|
color="error"
|
||||||
|
onClick={() => setLogoutDialogOpen(true)}
|
||||||
|
sx={{
|
||||||
|
py: 3,
|
||||||
|
fontSize: '1rem',
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
로그아웃
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
</Paper>
|
</Container>
|
||||||
|
|
||||||
{/* 액션 버튼 */}
|
|
||||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
|
||||||
<Button
|
|
||||||
fullWidth
|
|
||||||
variant="contained"
|
|
||||||
size="large"
|
|
||||||
onClick={handleSave}
|
|
||||||
sx={{
|
|
||||||
py: { xs: 1.5, sm: 1.75 },
|
|
||||||
fontSize: { xs: 15, sm: 16 },
|
|
||||||
...getGradientButtonStyle('primary'),
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
저장하기
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
fullWidth
|
|
||||||
variant="text"
|
|
||||||
size="large"
|
|
||||||
color="error"
|
|
||||||
onClick={() => setLogoutDialogOpen(true)}
|
|
||||||
sx={{
|
|
||||||
py: { xs: 1.5, sm: 1.75 },
|
|
||||||
fontSize: { xs: 15, sm: 16 },
|
|
||||||
fontWeight: 600,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
로그아웃
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</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>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user