'use client'; import { useState, useEffect } from 'react'; // ─── Types ─────────────────────────────────────────────────────────────────── interface ReportData { target_drw_no: number; based_on_draw: number; generated_at: string; hot_numbers: number[]; cold_numbers: number[]; overdue_numbers: number[]; recent_pattern: { last3_numbers: number[]; triple_appear: number[]; recent_sum_avg: number; recent_odd_avg: number; }; recommended_sets: Array<{ strategy: string; numbers: number[]; description: string; }>; confidence_score: number; confidence_factors: { data_volume: number; pattern_consistency: number; recent_trend: number; }; } interface HistoryItem { drw_no: number; generated_at: string; } function getBallStyle(n: number) { if (n <= 10) return { bg: 'linear-gradient(145deg,#fde68a,#fbbf24,#d97706)', text: '#78350f' }; if (n <= 20) return { bg: 'linear-gradient(145deg,#93c5fd,#3b82f6,#1d4ed8)', text: '#fff' }; if (n <= 30) return { bg: 'linear-gradient(145deg,#fca5a5,#ef4444,#b91c1c)', text: '#fff' }; if (n <= 40) return { bg: 'linear-gradient(145deg,#d1d5db,#9ca3af,#4b5563)', text: '#fff' }; return { bg: 'linear-gradient(145deg,#86efac,#22c55e,#15803d)', text: '#fff' }; } function SmallBall({ n, size = 32 }: { n: number; size?: number }) { const { bg, text } = getBallStyle(n); return (
{n}
); } function ConfidenceBar({ label, value }: { label: string; value: number }) { const color = value >= 85 ? '#4ade80' : value >= 70 ? '#fbbf24' : '#f87171'; return (
{label} {value}
); } export default function ReportTab() { const [report, setReport] = useState(null); const [history, setHistory] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [copiedIdx, setCopiedIdx] = useState(null); useEffect(() => { Promise.all([ fetch('/api/lotto/report/latest').then(r => r.json()), fetch('/api/lotto/report/history?limit=10').then(r => r.json()), ]).then(([rep, hist]) => { setReport(rep); setHistory(hist.reports ?? []); }).catch(() => setError('리포트를 불러오지 못했습니다.')) .finally(() => setLoading(false)); }, []); const copyNumbers = (numbers: number[], idx: number) => { navigator.clipboard.writeText(numbers.join(', ')); setCopiedIdx(idx); setTimeout(() => setCopiedIdx(null), 1500); }; if (loading) return (
리포트 불러오는 중...
); if (error) return (
{error}
); if (!report) return null; const strategyColors = ['#fbbf24', '#60a5fa', '#a78bfa']; return (
{/* 헤더 */}
WEEKLY ATTACK REPORT

제{report.target_drw_no}회 공략 리포트

{report.based_on_draw}회까지 데이터 기반 · {new Date(report.generated_at).toLocaleDateString('ko-KR')} 생성
{/* 신뢰도 점수 */}
CONFIDENCE
{report.confidence_score}
/100
{/* 추천 번호 세트 */}
RECOMMENDED SETS
{report.recommended_sets.map((set, i) => (
{set.strategy}
{set.numbers.map(n => )}
{set.description}
))}
{/* 핫/콜드/미출현 */} {[ { label: '🔥 최근 과출현', numbers: report.hot_numbers, color: '#f87171', desc: '최근 10회 2회 이상 출현' }, { label: '❄️ 저빈도 번호', numbers: report.cold_numbers, color: '#60a5fa', desc: '역대 출현 빈도 하위' }, { label: '⏳ 장기 미출현', numbers: report.overdue_numbers, color: '#a78bfa', desc: '가장 오래 미출현 번호' }, ].map(({ label, numbers, color, desc }) => (
{label}
{desc}
{numbers.map(n => )}
))} {/* 최근 패턴 */}
📊 최근 패턴
{[ { label: '최근 10회 합계 평균', value: report.recent_pattern.recent_sum_avg.toFixed(1) }, { label: '최근 10회 홀수 평균', value: report.recent_pattern.recent_odd_avg.toFixed(1) + '개' }, ].map(({ label, value }) => (
{label} {value}
))} {report.recent_pattern.triple_appear.length > 0 && (
직전 3회 연속 출현
{report.recent_pattern.triple_appear.map(n => )}
)}
{/* 신뢰도 상세 */}
🎯 신뢰도 분석
{/* 이전 리포트 목록 */} {history.length > 0 && (
REPORT HISTORY
{history.map(h => ( ))}
)}
); }