'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 (
);
}
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.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 => (
))}
)}
);
}