mirror of
https://github.com/ktds-dg0501/kt-event-marketing-fe.git
synced 2025-12-07 01:26:25 +00:00
- API 함수 추가: drawWinners, getWinners - 실제 백엔드 서버(localhost:8084)로 추첨 실행 - 당첨자 목록 실시간 조회 및 표시 - 에러 처리 및 로딩 상태 추가 - 재추첨 기능 API 연동 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
857 lines
29 KiB
TypeScript
857 lines
29 KiB
TypeScript
'use client';
|
|
|
|
import { useState, useEffect } from 'react';
|
|
import { useParams, useRouter } from 'next/navigation';
|
|
import {
|
|
Box,
|
|
Container,
|
|
Card,
|
|
CardContent,
|
|
Typography,
|
|
Button,
|
|
FormControlLabel,
|
|
Checkbox,
|
|
Dialog,
|
|
DialogTitle,
|
|
DialogContent,
|
|
DialogActions,
|
|
IconButton,
|
|
Grid,
|
|
Alert,
|
|
CircularProgress,
|
|
} from '@mui/material';
|
|
import {
|
|
EventNote,
|
|
Tune,
|
|
Casino,
|
|
Download,
|
|
Refresh,
|
|
Notifications,
|
|
Add,
|
|
Remove,
|
|
Info,
|
|
People,
|
|
} from '@mui/icons-material';
|
|
import { drawWinners, getWinners, getParticipants } from '@/shared/api/participation.api';
|
|
import type { DrawWinnersResponse } from '@/shared/types/api.types';
|
|
|
|
// 디자인 시스템 색상
|
|
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 Winner {
|
|
participantId: string;
|
|
name: string;
|
|
phoneNumber: string;
|
|
rank: number;
|
|
channel?: string;
|
|
storeVisited: boolean;
|
|
}
|
|
|
|
export default function DrawPage() {
|
|
const params = useParams();
|
|
const router = useRouter();
|
|
const eventId = params.eventId as string;
|
|
|
|
// State
|
|
const [winnerCount, setWinnerCount] = useState(5);
|
|
const [storeBonus, setStoreBonus] = useState(false);
|
|
const [isDrawing, setIsDrawing] = useState(false);
|
|
const [showResults, setShowResults] = useState(false);
|
|
const [winners, setWinners] = useState<Winner[]>([]);
|
|
const [animationText, setAnimationText] = useState('추첨 중...');
|
|
const [animationSubtext, setAnimationSubtext] = useState('난수 생성 중');
|
|
const [confirmDialogOpen, setConfirmDialogOpen] = useState(false);
|
|
const [redrawDialogOpen, setRedrawDialogOpen] = useState(false);
|
|
const [notifyDialogOpen, setNotifyDialogOpen] = useState(false);
|
|
|
|
// API 관련 상태
|
|
const [totalParticipants, setTotalParticipants] = useState(0);
|
|
const [eventName] = useState('이벤트');
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [drawResult, setDrawResult] = useState<DrawWinnersResponse | null>(null);
|
|
|
|
// 초기 데이터 로드
|
|
useEffect(() => {
|
|
const loadInitialData = async () => {
|
|
try {
|
|
setLoading(true);
|
|
setError(null);
|
|
|
|
// 참여자 총 수 조회
|
|
const participantsResponse = await getParticipants({
|
|
eventId,
|
|
page: 0,
|
|
size: 1,
|
|
});
|
|
|
|
setTotalParticipants(participantsResponse.data.totalElements);
|
|
|
|
// 기존 당첨자가 있는지 확인
|
|
try {
|
|
const winnersResponse = await getWinners(eventId, 0, 100);
|
|
if (winnersResponse.data.content.length > 0) {
|
|
// 당첨자가 있으면 결과 화면 표시
|
|
const winnerList: Winner[] = winnersResponse.data.content.map((p) => ({
|
|
participantId: p.participantId,
|
|
name: p.name,
|
|
phoneNumber: p.phoneNumber,
|
|
rank: 0, // rank는 순서대로
|
|
channel: p.channel,
|
|
storeVisited: p.storeVisited,
|
|
}));
|
|
setWinners(winnerList);
|
|
setShowResults(true);
|
|
}
|
|
} catch {
|
|
// 당첨자가 없으면 무시
|
|
console.log('No winners yet');
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to load initial data:', err);
|
|
setError('데이터를 불러오는데 실패했습니다.');
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
loadInitialData();
|
|
}, [eventId]);
|
|
|
|
const handleDecrease = () => {
|
|
if (winnerCount > 1) {
|
|
setWinnerCount(winnerCount - 1);
|
|
}
|
|
};
|
|
|
|
const handleIncrease = () => {
|
|
if (winnerCount < 100 && winnerCount < totalParticipants) {
|
|
setWinnerCount(winnerCount + 1);
|
|
}
|
|
};
|
|
|
|
const handleStartDrawing = () => {
|
|
setConfirmDialogOpen(true);
|
|
};
|
|
|
|
const executeDrawing = async () => {
|
|
setConfirmDialogOpen(false);
|
|
setIsDrawing(true);
|
|
setError(null);
|
|
|
|
try {
|
|
// Phase 1: 난수 생성 중 (1 second)
|
|
setTimeout(() => {
|
|
setAnimationText('당첨자 선정 중...');
|
|
setAnimationSubtext('공정한 추첨을 진행하고 있습니다');
|
|
}, 1000);
|
|
|
|
// 실제 API 호출
|
|
const response = await drawWinners(eventId, winnerCount, storeBonus);
|
|
setDrawResult(response.data);
|
|
|
|
// Phase 2: 완료 (2 seconds)
|
|
setTimeout(() => {
|
|
setAnimationText('완료!');
|
|
setAnimationSubtext('추첨이 완료되었습니다');
|
|
}, 2000);
|
|
|
|
// Phase 3: 당첨자 목록 변환 및 표시
|
|
setTimeout(() => {
|
|
const winnerList: Winner[] = response.data.winners.map((w) => ({
|
|
participantId: w.participantId,
|
|
name: w.name,
|
|
phoneNumber: w.phoneNumber,
|
|
rank: w.rank,
|
|
storeVisited: false, // API 응답에 포함되지 않음
|
|
}));
|
|
|
|
setWinners(winnerList);
|
|
setIsDrawing(false);
|
|
setShowResults(true);
|
|
}, 3000);
|
|
} catch (err) {
|
|
console.error('Draw failed:', err);
|
|
setIsDrawing(false);
|
|
const errorMessage =
|
|
err && typeof err === 'object' && 'response' in err
|
|
? (err as { response?: { data?: { message?: string } } }).response?.data?.message
|
|
: undefined;
|
|
setError(errorMessage || '추첨에 실패했습니다. 다시 시도해주세요.');
|
|
}
|
|
};
|
|
|
|
const handleRedraw = async () => {
|
|
setRedrawDialogOpen(false);
|
|
setShowResults(false);
|
|
setWinners([]);
|
|
|
|
setTimeout(() => {
|
|
executeDrawing();
|
|
}, 500);
|
|
};
|
|
|
|
const handleNotify = () => {
|
|
setNotifyDialogOpen(false);
|
|
setTimeout(() => {
|
|
alert('알림이 전송되었습니다');
|
|
}, 500);
|
|
};
|
|
|
|
const handleDownload = () => {
|
|
const now = new Date();
|
|
const dateStr = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}`;
|
|
const filename = `당첨자목록_${eventName}_${dateStr}.xlsx`;
|
|
alert(`${filename} 다운로드를 시작합니다`);
|
|
};
|
|
|
|
const handleBackToEvents = () => {
|
|
router.push('/events');
|
|
};
|
|
|
|
const getRankClass = (rank: number) => {
|
|
if (rank === 1) return 'linear-gradient(135deg, #FFD700 0%, #FFA500 100%)';
|
|
if (rank === 2) return 'linear-gradient(135deg, #C0C0C0 0%, #A8A8A8 100%)';
|
|
if (rank === 3) return 'linear-gradient(135deg, #CD7F32 0%, #B87333 100%)';
|
|
return '#e0e0e0';
|
|
};
|
|
|
|
// 로딩 상태
|
|
if (loading) {
|
|
return (
|
|
<Box
|
|
sx={{
|
|
minHeight: '100vh',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
}}
|
|
>
|
|
<CircularProgress size={60} />
|
|
</Box>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Box sx={{ minHeight: '100vh', bgcolor: 'background.default', pb: 10 }}>
|
|
<Container maxWidth="lg" sx={{ pt: 8, pb: 8, px: { xs: 6, sm: 8, md: 10 } }}>
|
|
{/* 에러 메시지 */}
|
|
{error && (
|
|
<Alert severity="error" sx={{ mb: 4, borderRadius: 3 }} onClose={() => setError(null)}>
|
|
{error}
|
|
</Alert>
|
|
)}
|
|
|
|
{/* Setup View (Before Drawing) */}
|
|
{!showResults && (
|
|
<>
|
|
{/* 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>
|
|
|
|
{/* Event Info Summary Cards */}
|
|
<Grid container spacing={6} sx={{ mb: 10 }}>
|
|
<Grid item xs={6} md={6}>
|
|
<Card
|
|
elevation={0}
|
|
sx={{
|
|
borderRadius: 4,
|
|
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)',
|
|
background: `linear-gradient(135deg, ${colors.purple} 0%, ${colors.purpleLight} 100%)`,
|
|
}}
|
|
>
|
|
<CardContent sx={{ textAlign: 'center', py: 6, px: 4 }}>
|
|
<EventNote sx={{ fontSize: 40, mb: 2, color: 'white' }} />
|
|
<Typography variant="caption" display="block" sx={{ mb: 2, color: 'rgba(255,255,255,0.9)' }}>
|
|
이벤트명
|
|
</Typography>
|
|
<Typography variant="h6" sx={{ fontWeight: 700, color: 'white' }}>
|
|
{eventName}
|
|
</Typography>
|
|
</CardContent>
|
|
</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' }}>
|
|
{totalParticipants}명
|
|
</Typography>
|
|
</CardContent>
|
|
</Card>
|
|
</Grid>
|
|
</Grid>
|
|
|
|
{/* Drawing Settings */}
|
|
<Card elevation={0} sx={{ mb: 10, borderRadius: 4, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.08)' }}>
|
|
<CardContent sx={{ p: 6 }}>
|
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 6 }}>
|
|
<Tune sx={{ fontSize: 32, color: colors.pink }} />
|
|
<Typography variant="h5" sx={{ fontWeight: 700, fontSize: '1.5rem' }}>
|
|
추첨 설정
|
|
</Typography>
|
|
</Box>
|
|
|
|
<Box sx={{ mb: 6 }}>
|
|
<Typography variant="h6" sx={{ mb: 4, fontWeight: 600 }}>
|
|
당첨 인원
|
|
</Typography>
|
|
<Box
|
|
sx={{
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
gap: 4,
|
|
p: 4,
|
|
bgcolor: colors.gray[100],
|
|
borderRadius: 3,
|
|
}}
|
|
>
|
|
<IconButton
|
|
onClick={handleDecrease}
|
|
sx={{
|
|
width: 60,
|
|
height: 60,
|
|
border: '2px solid',
|
|
borderColor: colors.purple,
|
|
color: colors.purple,
|
|
'&:hover': {
|
|
bgcolor: colors.purpleLight,
|
|
},
|
|
}}
|
|
>
|
|
<Remove sx={{ fontSize: 28 }} />
|
|
</IconButton>
|
|
<Typography variant="h2" sx={{ fontWeight: 700, width: 120, textAlign: 'center', color: colors.purple }}>
|
|
{winnerCount}
|
|
</Typography>
|
|
<IconButton
|
|
onClick={handleIncrease}
|
|
sx={{
|
|
width: 60,
|
|
height: 60,
|
|
border: '2px solid',
|
|
borderColor: colors.purple,
|
|
color: colors.purple,
|
|
'&:hover': {
|
|
bgcolor: colors.purpleLight,
|
|
},
|
|
}}
|
|
>
|
|
<Add sx={{ fontSize: 28 }} />
|
|
</IconButton>
|
|
</Box>
|
|
</Box>
|
|
|
|
<FormControlLabel
|
|
control={
|
|
<Checkbox
|
|
checked={storeBonus}
|
|
onChange={(e) => setStoreBonus(e.target.checked)}
|
|
sx={{
|
|
color: colors.purple,
|
|
'&.Mui-checked': {
|
|
color: colors.purple,
|
|
},
|
|
}}
|
|
/>
|
|
}
|
|
label={
|
|
<Typography variant="body1" sx={{ fontWeight: 600 }}>
|
|
매장 방문 고객 가산점 (가중치: 1.5배)
|
|
</Typography>
|
|
}
|
|
sx={{ mb: 6 }}
|
|
/>
|
|
|
|
<Box
|
|
sx={{
|
|
bgcolor: colors.purpleLight,
|
|
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>
|
|
</Box>
|
|
<Typography variant="body1" sx={{ mb: 2, color: colors.gray[700] }}>
|
|
• 난수 기반 무작위 추첨
|
|
</Typography>
|
|
<Typography variant="body1" sx={{ color: colors.gray[700] }}>
|
|
• 모든 추첨 과정은 자동 기록됩니다
|
|
</Typography>
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Drawing Start Button */}
|
|
<Button
|
|
fullWidth
|
|
variant="contained"
|
|
size="large"
|
|
startIcon={<Casino sx={{ fontSize: 28 }} />}
|
|
onClick={handleStartDrawing}
|
|
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>
|
|
|
|
</>
|
|
)}
|
|
|
|
{/* Results View (After Drawing) */}
|
|
{showResults && (
|
|
<>
|
|
{/* Results Header */}
|
|
<Box sx={{ textAlign: 'center', mb: 10 }}>
|
|
<Typography variant="h4" sx={{ fontWeight: 700, mb: 4, fontSize: '2rem' }}>
|
|
🎉 추첨 완료!
|
|
</Typography>
|
|
<Typography variant="h6" sx={{ fontSize: '1.25rem' }}>
|
|
총 {totalParticipants}명 중 {winners.length}명 당첨
|
|
</Typography>
|
|
{drawResult && (
|
|
<Typography variant="body2" color="text.secondary" sx={{ mt: 2 }}>
|
|
추첨 일시: {new Date(drawResult.drawnAt).toLocaleString('ko-KR')}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Winner List */}
|
|
<Box sx={{ mb: 10 }}>
|
|
<Typography variant="h5" sx={{ fontWeight: 700, mb: 6, fontSize: '1.5rem' }}>
|
|
🏆 당첨자 목록
|
|
</Typography>
|
|
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
|
{winners.map((winner) => {
|
|
return (
|
|
<Card
|
|
key={winner.participantId}
|
|
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
|
|
sx={{
|
|
width: 64,
|
|
height: 64,
|
|
borderRadius: '50%',
|
|
background: getRankClass(winner.rank),
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
color: 'white',
|
|
fontWeight: 700,
|
|
fontSize: 20,
|
|
flexShrink: 0,
|
|
}}
|
|
>
|
|
{winner.rank}위
|
|
</Box>
|
|
<Box sx={{ flex: 1 }}>
|
|
<Typography variant="caption" color="text.secondary" sx={{ mb: 1, display: 'block' }}>
|
|
참여자 ID: {winner.participantId}
|
|
</Typography>
|
|
<Typography variant="h6" sx={{ fontWeight: 700, mb: 2, fontSize: '1.25rem' }}>
|
|
{winner.name} ({winner.phoneNumber})
|
|
</Typography>
|
|
{winner.channel && (
|
|
<Typography variant="body2" color="text.secondary" sx={{ fontSize: '1rem' }}>
|
|
참여: {winner.channel}{' '}
|
|
{winner.storeVisited && storeBonus && '🌟'}
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
</Box>
|
|
</CardContent>
|
|
</Card>
|
|
);
|
|
})}
|
|
</Box>
|
|
{storeBonus && (
|
|
<Typography
|
|
variant="caption"
|
|
color="text.secondary"
|
|
sx={{ display: 'block', textAlign: 'center', mt: 6, fontSize: '0.875rem' }}
|
|
>
|
|
🌟 매장 방문 고객 가산점 적용
|
|
</Typography>
|
|
)}
|
|
</Box>
|
|
|
|
{/* Action Buttons */}
|
|
<Grid container spacing={4} sx={{ mb: 4 }}>
|
|
<Grid item xs={6}>
|
|
<Button
|
|
fullWidth
|
|
variant="outlined"
|
|
size="large"
|
|
startIcon={<Download />}
|
|
onClick={handleDownload}
|
|
sx={{
|
|
borderRadius: 3,
|
|
py: 3,
|
|
fontSize: '1rem',
|
|
fontWeight: 600,
|
|
borderWidth: 2,
|
|
'&:hover': {
|
|
borderWidth: 2,
|
|
},
|
|
}}
|
|
>
|
|
엑셀다운로드
|
|
</Button>
|
|
</Grid>
|
|
<Grid item xs={6}>
|
|
<Button
|
|
fullWidth
|
|
variant="outlined"
|
|
size="large"
|
|
startIcon={<Refresh />}
|
|
onClick={() => setRedrawDialogOpen(true)}
|
|
sx={{
|
|
borderRadius: 3,
|
|
py: 3,
|
|
fontSize: '1rem',
|
|
fontWeight: 600,
|
|
borderWidth: 2,
|
|
'&:hover': {
|
|
borderWidth: 2,
|
|
},
|
|
}}
|
|
>
|
|
재추첨
|
|
</Button>
|
|
</Grid>
|
|
</Grid>
|
|
|
|
<Button
|
|
fullWidth
|
|
variant="contained"
|
|
size="large"
|
|
startIcon={<Notifications />}
|
|
onClick={() => setNotifyDialogOpen(true)}
|
|
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
|
|
fullWidth
|
|
variant="text"
|
|
size="large"
|
|
onClick={handleBackToEvents}
|
|
sx={{
|
|
borderRadius: 3,
|
|
py: 3,
|
|
fontSize: '1rem',
|
|
fontWeight: 600,
|
|
}}
|
|
>
|
|
이벤트 목록으로
|
|
</Button>
|
|
</>
|
|
)}
|
|
|
|
{/* Drawing Animation */}
|
|
<Dialog
|
|
open={isDrawing}
|
|
maxWidth="sm"
|
|
fullWidth
|
|
PaperProps={{
|
|
sx: {
|
|
bgcolor: 'background.paper',
|
|
borderRadius: 4,
|
|
},
|
|
}}
|
|
>
|
|
<DialogContent sx={{ textAlign: 'center', py: 16 }}>
|
|
{/* 그라데이션 스피너 */}
|
|
<Box
|
|
sx={{
|
|
width: 100,
|
|
height: 100,
|
|
margin: '0 auto 48px',
|
|
borderRadius: '50%',
|
|
background: `conic-gradient(from 0deg, ${colors.purple}, ${colors.pink}, ${colors.blue}, ${colors.purple})`,
|
|
animation: 'spin 1.5s linear infinite',
|
|
display: 'flex',
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
'@keyframes spin': {
|
|
'0%': { transform: 'rotate(0deg)' },
|
|
'100%': { transform: 'rotate(360deg)' },
|
|
},
|
|
'&::before': {
|
|
content: '""',
|
|
position: 'absolute',
|
|
width: 75,
|
|
height: 75,
|
|
borderRadius: '50%',
|
|
backgroundColor: 'background.paper',
|
|
},
|
|
}}
|
|
>
|
|
<Casino
|
|
sx={{
|
|
fontSize: 50,
|
|
color: colors.purple,
|
|
zIndex: 1,
|
|
animation: 'pulse 1.5s ease-in-out infinite',
|
|
'@keyframes pulse': {
|
|
'0%, 100%': { opacity: 1, transform: 'scale(1)' },
|
|
'50%': { opacity: 0.7, transform: 'scale(0.95)' },
|
|
},
|
|
}}
|
|
/>
|
|
</Box>
|
|
<Typography variant="h4" sx={{ fontWeight: 700, mb: 2, fontSize: '2rem' }}>
|
|
{animationText}
|
|
</Typography>
|
|
<Typography variant="body1" color="text.secondary" sx={{ fontSize: '1.125rem' }}>
|
|
{animationSubtext}
|
|
</Typography>
|
|
</DialogContent>
|
|
</Dialog>
|
|
|
|
{/* Confirm Dialog */}
|
|
<Dialog
|
|
open={confirmDialogOpen}
|
|
onClose={() => setConfirmDialogOpen(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 }}>
|
|
<Typography variant="body1" sx={{ textAlign: 'center', fontSize: '1.125rem' }}>
|
|
총 {totalParticipants}명 중 {winnerCount}명을 추첨하시겠습니까?
|
|
</Typography>
|
|
{storeBonus && (
|
|
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', mt: 2 }}>
|
|
매장 방문 고객에게 1.5배 가산점이 적용됩니다.
|
|
</Typography>
|
|
)}
|
|
</DialogContent>
|
|
<DialogActions sx={{ px: 6, pb: 6, pt: 4, gap: 2 }}>
|
|
<Button
|
|
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>
|
|
</DialogActions>
|
|
</Dialog>
|
|
|
|
{/* Redraw Dialog */}
|
|
<Dialog
|
|
open={redrawDialogOpen}
|
|
onClose={() => setRedrawDialogOpen(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 }}>
|
|
<Typography variant="body1" sx={{ mb: 4, textAlign: 'center', fontSize: '1.125rem' }}>
|
|
재추첨 시 현재 당첨자 정보가 변경됩니다.
|
|
</Typography>
|
|
<Typography variant="body1" sx={{ mb: 4, textAlign: 'center', fontSize: '1.125rem' }}>
|
|
계속하시겠습니까?
|
|
</Typography>
|
|
<Typography
|
|
variant="caption"
|
|
color="text.secondary"
|
|
sx={{ display: 'block', textAlign: 'center', fontSize: '0.875rem' }}
|
|
>
|
|
이전 추첨 이력은 보관됩니다
|
|
</Typography>
|
|
</DialogContent>
|
|
<DialogActions sx={{ px: 6, pb: 6, pt: 4, gap: 2 }}>
|
|
<Button
|
|
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>
|
|
</DialogActions>
|
|
</Dialog>
|
|
|
|
{/* Notify Dialog */}
|
|
<Dialog
|
|
open={notifyDialogOpen}
|
|
onClose={() => setNotifyDialogOpen(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 }}>
|
|
<Typography variant="body1" sx={{ mb: 4, textAlign: 'center', fontSize: '1.125rem' }}>
|
|
{winnerCount}명의 당첨자에게 SMS 알림을 전송하시겠습니까?
|
|
</Typography>
|
|
<Typography
|
|
variant="caption"
|
|
color="text.secondary"
|
|
sx={{ display: 'block', textAlign: 'center', fontSize: '0.875rem' }}
|
|
>
|
|
예상 비용: {winnerCount * 100}원 (100원/건)
|
|
</Typography>
|
|
</DialogContent>
|
|
<DialogActions sx={{ px: 6, pb: 6, pt: 4, gap: 2 }}>
|
|
<Button
|
|
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>
|
|
</DialogActions>
|
|
</Dialog>
|
|
|
|
</Container>
|
|
</Box>
|
|
);
|
|
}
|