'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import { Box, Container, TextField, Select, MenuItem, FormControl, InputLabel, Card, CardContent, Typography, InputAdornment, Pagination, Grid, LinearProgress, Chip, } from '@mui/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 { cardStyles, colors, responsiveText } from '@/shared/lib/button-styles'; import { useEvents } from '@/entities/event/model/useEvents'; import type { EventStatus as ApiEventStatus } from '@/entities/event/model/types'; // ==================== API 연동 ==================== // Mock 데이터를 실제 API 호출로 교체 // 백업 파일: page.tsx.backup type EventStatus = 'all' | 'active' | 'scheduled' | 'ended'; type Period = '1month' | '3months' | '6months' | '1year' | 'all'; type SortBy = 'latest' | 'participants' | 'roi'; export default function EventsPage() { const router = useRouter(); const [searchTerm, setSearchTerm] = useState(''); const [statusFilter, setStatusFilter] = useState('all'); const [periodFilter, setPeriodFilter] = useState('1month'); const [sortBy, setSortBy] = useState('latest'); const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 20; // API 데이터 가져오기 const { events: apiEvents, loading, error, pageInfo, refetch } = useEvents({ page: currentPage - 1, size: itemsPerPage, sort: 'createdAt', order: 'desc' }); // API 상태를 UI 상태로 매핑 const mapApiStatus = (apiStatus: ApiEventStatus): EventStatus => { switch (apiStatus) { case 'PUBLISHED': return 'active'; case 'DRAFT': return 'scheduled'; case 'ENDED': return 'ended'; default: return 'all'; } }; // API 이벤트를 UI 형식으로 변환 const transformedEvents = apiEvents.map(event => ({ id: event.eventId, title: event.eventName || '제목 없음', status: mapApiStatus(event.status), startDate: event.startDate ? new Date(event.startDate).toLocaleDateString('ko-KR') : '-', endDate: event.endDate ? new Date(event.endDate).toLocaleDateString('ko-KR') : '-', prize: event.aiRecommendations[0]?.reward || '경품 정보 없음', method: event.aiRecommendations[0]?.participationMethod || '참여 방법 없음', participants: event.participants || 0, targetParticipants: event.targetParticipants || 0, roi: event.roi || 0, daysLeft: event.endDate ? Math.ceil((new Date(event.endDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : 0, isUrgent: event.endDate ? Math.ceil((new Date(event.endDate).getTime() - Date.now()) / (1000 * 60 * 60 * 24)) <= 3 : false, isPopular: event.participants && event.targetParticipants ? (event.participants / event.targetParticipants) >= 0.8 : false, isHighROI: event.roi ? event.roi >= 300 : false, isNew: event.createdAt ? (Date.now() - new Date(event.createdAt).getTime()) < (7 * 24 * 60 * 60 * 1000) : false, })); // 필터링 및 정렬 const filteredEvents = transformedEvents .filter((event) => { const matchesSearch = event.title.toLowerCase().includes(searchTerm.toLowerCase()); const matchesStatus = statusFilter === 'all' || event.status === statusFilter; return matchesSearch && matchesStatus; }) .sort((a, b) => { if (sortBy === 'latest') { return new Date(b.startDate).getTime() - new Date(a.startDate).getTime(); } else if (sortBy === 'participants') { return b.participants - a.participants; } else if (sortBy === 'roi') { return b.roi - a.roi; } return 0; }); // 페이지네이션 const totalPages = Math.ceil(filteredEvents.length / itemsPerPage); const startIndex = (currentPage - 1) * itemsPerPage; const endIndex = Math.min(startIndex + itemsPerPage, filteredEvents.length); const pageEvents = filteredEvents.slice(startIndex, endIndex); const handleEventClick = (eventId: string) => { router.push(`/events/${eventId}`); }; const getStatusStyle = (status: string) => { switch (status) { case 'active': return { bgcolor: colors.mint, color: 'white', }; case 'scheduled': return { bgcolor: colors.blue, color: 'white', }; case 'ended': return { bgcolor: colors.gray[300], color: colors.gray[700], }; default: return { bgcolor: colors.gray[200], color: colors.gray[600], }; } }; const getStatusText = (status: string) => { switch (status) { case 'active': return '진행중'; case 'scheduled': return '예정'; case 'ended': return '종료'; default: return status; } }; const getMethodIcon = (method: string) => { switch (method) { case '전화번호 입력': return ; case 'SNS 팔로우': return ; case '구매 인증': return ; case '이메일 등록': return ; default: return ; } }; const calculateProgress = (event: typeof transformedEvents[0]) => { if (event.status !== 'active') return 0; const startTime = new Date(event.startDate).getTime(); const endTime = new Date(event.endDate).getTime(); const total = endTime - startTime; const elapsed = Date.now() - startTime; return Math.min(Math.max((elapsed / total) * 100, 0), 100); }; // 통계 계산 const stats = { total: transformedEvents.length, active: transformedEvents.filter((e) => e.status === 'active').length, totalParticipants: transformedEvents.reduce((sum, e) => sum + e.participants, 0), avgROI: transformedEvents.filter((e) => e.roi > 0).length > 0 ? Math.round( transformedEvents.filter((e) => e.roi > 0).reduce((sum, e) => sum + e.roi, 0) / transformedEvents.filter((e) => e.roi > 0).length ) : 0, }; return ( <>
{/* Loading State */} {loading && ( 이벤트 목록을 불러오는 중... )} {/* Error State */} {error && ( 이벤트 목록을 불러오는데 실패했습니다 {error.message} refetch()} sx={{ px: 3, py: 1.5, borderRadius: 2, border: 'none', bgcolor: '#DC2626', color: 'white', fontSize: '0.875rem', fontWeight: 600, cursor: 'pointer', '&:hover': { bgcolor: '#B91C1C' }, }} > 다시 시도 )} {/* Summary Statistics */} {stats.total} 전체 이벤트 {stats.active} 진행중 {stats.totalParticipants} 총 참여자 {stats.avgROI}% 평균 ROI {/* Search Section */} setSearchTerm(e.target.value)} InputProps={{ startAdornment: ( ), }} sx={{ '& .MuiOutlinedInput-root': { borderRadius: 3, bgcolor: 'white', fontSize: { xs: '0.875rem', sm: '1rem' }, '& fieldset': { borderColor: colors.gray[200], }, '&:hover fieldset': { borderColor: colors.gray[300], }, '&.Mui-focused fieldset': { borderColor: colors.purple, }, }, }} /> {/* Filters */} 상태 기간 {/* Sorting */} 정렬 {/* Event List */} {pageEvents.length === 0 ? ( event_busy 검색 결과가 없습니다 다른 검색 조건으로 다시 시도해보세요 ) : ( {pageEvents.map((event) => ( handleEventClick(event.id)} > {/* Header with Badges */} {event.title} {getStatusText(event.status)} {event.status === 'active' ? ` | D-${event.daysLeft}` : event.status === 'scheduled' ? ` | D+${event.daysLeft}` : ''} {/* Status Badges */} {event.isUrgent && ( } label="마감임박" size="small" sx={{ bgcolor: '#FEF3C7', color: '#92400E', fontWeight: 600, fontSize: { xs: '0.6875rem', sm: '0.75rem' }, height: { xs: 24, sm: 28 }, '& .MuiChip-icon': { color: '#92400E' }, }} /> )} {event.isPopular && ( } label="인기" size="small" sx={{ bgcolor: '#FEE2E2', color: '#991B1B', fontWeight: 600, fontSize: { xs: '0.6875rem', sm: '0.75rem' }, height: { xs: 24, sm: 28 }, '& .MuiChip-icon': { color: '#991B1B' }, }} /> )} {event.isHighROI && ( } label="높은 ROI" size="small" sx={{ bgcolor: '#DCFCE7', color: '#166534', fontWeight: 600, fontSize: { xs: '0.6875rem', sm: '0.75rem' }, height: { xs: 24, sm: 28 }, '& .MuiChip-icon': { color: '#166534' }, }} /> )} {event.isNew && ( } label="신규" size="small" sx={{ bgcolor: '#DBEAFE', color: '#1E40AF', fontWeight: 600, fontSize: { xs: '0.6875rem', sm: '0.75rem' }, height: { xs: 24, sm: 28 }, '& .MuiChip-icon': { color: '#1E40AF' }, }} /> )} {/* Progress Bar for Active Events */} {event.status === 'active' && ( 이벤트 진행률 {Math.round(calculateProgress(event))}% )} {/* Event Info and Stats Container */} {/* Left: Event Info */} {event.prize} {getMethodIcon(event.method)} {event.method} {/* Date */} 📅 {event.startDate} ~ {event.endDate} {/* Right: Stats */} 참여자 {event.participants.toLocaleString()} {event.targetParticipants > 0 && ( 목표: {event.targetParticipants}명 ( {Math.round((event.participants / event.targetParticipants) * 100)} %) )} ROI = 400 ? colors.mint : event.roi >= 200 ? colors.orange : colors.gray[500], }} > {event.roi}% ))} )} {/* Pagination */} {totalPages > 1 && ( setCurrentPage(page)} 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], }, }, }} /> )} ); }