|
import React, { useState, useEffect, useCallback } from 'react'; |
|
import { |
|
Trophy, |
|
Target, |
|
RefreshCw, |
|
AlertCircle, |
|
Loader, |
|
BarChart3, |
|
Award |
|
} from 'lucide-react'; |
|
import { LotomaniaGame, LotomaniaResult } from '../types'; |
|
import { useLotomaniaAPI } from '../hooks/useLotomaniaAPI'; |
|
import { LotomaniaAlgorithm } from '../utils/lotomaniaAlgorithm'; |
|
import { Bar } from 'react-chartjs-2'; |
|
import '../utils/chartSetup'; |
|
|
|
interface ResultsAnalysisProps { |
|
verticalGames: LotomaniaGame[]; |
|
horizontalGames: LotomaniaGame[]; |
|
algorithm: LotomaniaAlgorithm; |
|
} |
|
|
|
interface GameAnalysis { |
|
game: LotomaniaGame; |
|
result: LotomaniaResult; |
|
matches: number; |
|
points: number; |
|
isWinning: boolean; |
|
matchedNumbers: number[]; |
|
prize: string; |
|
} |
|
|
|
const ResultsAnalysis: React.FC<ResultsAnalysisProps> = ({ |
|
verticalGames, |
|
horizontalGames, |
|
algorithm |
|
}) => { |
|
const [activeTab, setActiveTab] = useState<'vertical' | 'horizontal' | 'comparison'>('vertical'); |
|
const [analysisFilter, setAnalysisFilter] = useState<'all' | '15+' | '16+' | '17+' | '18+' | '19+' | '20'>('all'); |
|
const [analysisResults, setAnalysisResults] = useState<{ |
|
vertical: GameAnalysis[]; |
|
horizontal: GameAnalysis[]; |
|
}>({ vertical: [], horizontal: [] }); |
|
const [isAnalyzing, setIsAnalyzing] = useState(false); |
|
|
|
const { |
|
latestResult, |
|
loading: apiLoading, |
|
error: apiError, |
|
fetchLatestResult, |
|
analyzeGameResult |
|
} = useLotomaniaAPI(); |
|
|
|
const getPrizeLabel = (points: number): string => { |
|
switch (points) { |
|
case 20: return 'PRÊMIO MÁXIMO! 🎉'; |
|
case 19: return 'Prêmio Alto 🏆'; |
|
case 18: return 'Prêmio Médio Alto 🥈'; |
|
case 17: return 'Prêmio Médio 🥉'; |
|
case 16: return 'Prêmio Baixo 🏅'; |
|
case 15: return 'Prêmio Mínimo 🎯'; |
|
case 0: return 'PRÊMIO MÁXIMO! 🎉'; |
|
default: return 'Sem prêmio'; |
|
} |
|
}; |
|
|
|
const performAnalysis = useCallback(async () => { |
|
if (!latestResult) return; |
|
|
|
setIsAnalyzing(true); |
|
|
|
try { |
|
|
|
const verticalAnalysis: GameAnalysis[] = []; |
|
verticalGames.forEach(game => { |
|
const gameNumbers = algorithm.getNumbersFromGame(game); |
|
const analysis = analyzeGameResult(gameNumbers, latestResult); |
|
|
|
verticalAnalysis.push({ |
|
game, |
|
result: latestResult, |
|
matches: analysis.matches, |
|
points: analysis.points, |
|
isWinning: analysis.isWinning, |
|
matchedNumbers: analysis.matchedNumbers, |
|
prize: getPrizeLabel(analysis.points) |
|
}); |
|
}); |
|
|
|
|
|
const horizontalAnalysis: GameAnalysis[] = []; |
|
horizontalGames.forEach(game => { |
|
const gameNumbers = algorithm.getNumbersFromGame(game); |
|
const analysis = analyzeGameResult(gameNumbers, latestResult); |
|
|
|
horizontalAnalysis.push({ |
|
game, |
|
result: latestResult, |
|
matches: analysis.matches, |
|
points: analysis.points, |
|
isWinning: analysis.isWinning, |
|
matchedNumbers: analysis.matchedNumbers, |
|
prize: getPrizeLabel(analysis.points) |
|
}); |
|
}); |
|
|
|
setAnalysisResults({ |
|
vertical: verticalAnalysis, |
|
horizontal: horizontalAnalysis |
|
}); |
|
|
|
} catch (error) { |
|
console.error('Erro na análise:', error); |
|
} finally { |
|
setIsAnalyzing(false); |
|
} |
|
}, [latestResult, verticalGames, horizontalGames, algorithm, analyzeGameResult]); |
|
|
|
|
|
useEffect(() => { |
|
if (latestResult) { |
|
performAnalysis(); |
|
} |
|
}, [latestResult, performAnalysis]); |
|
|
|
const getFilteredAnalysis = (analysisData: GameAnalysis[]) => { |
|
let filtered = analysisData; |
|
|
|
|
|
switch (analysisFilter) { |
|
case '20': |
|
filtered = filtered.filter(a => a.points === 20 || a.points === 0); |
|
break; |
|
case '19+': |
|
filtered = filtered.filter(a => a.points >= 19 || a.points === 0); |
|
break; |
|
case '18+': |
|
filtered = filtered.filter(a => a.points >= 18 || a.points === 0); |
|
break; |
|
case '17+': |
|
filtered = filtered.filter(a => a.points >= 17 || a.points === 0); |
|
break; |
|
case '16+': |
|
filtered = filtered.filter(a => a.points >= 16 || a.points === 0); |
|
break; |
|
case '15+': |
|
filtered = filtered.filter(a => a.points >= 15 || a.points === 0); |
|
break; |
|
default: |
|
|
|
break; |
|
} |
|
|
|
return filtered; |
|
}; |
|
|
|
const renderStatistics = (analysisData: GameAnalysis[], type: 'vertical' | 'horizontal') => { |
|
const totalAnalyses = analysisData.length; |
|
const winningAnalyses = analysisData.filter(a => a.isWinning); |
|
|
|
const stats = { |
|
total: totalAnalyses, |
|
wins: winningAnalyses.length, |
|
winRate: totalAnalyses > 0 ? (winningAnalyses.length / totalAnalyses) * 100 : 0, |
|
points20: analysisData.filter(a => a.points === 20 || a.points === 0).length, |
|
points19: analysisData.filter(a => a.points === 19).length, |
|
points18: analysisData.filter(a => a.points === 18).length, |
|
points17: analysisData.filter(a => a.points === 17).length, |
|
points16: analysisData.filter(a => a.points === 16).length, |
|
points15: analysisData.filter(a => a.points === 15).length, |
|
averageMatches: totalAnalyses > 0 ? |
|
analysisData.reduce((sum, a) => sum + a.matches, 0) / totalAnalyses : 0 |
|
}; |
|
|
|
const chartData = { |
|
labels: ['15 pontos', '16 pontos', '17 pontos', '18 pontos', '19 pontos', '20 pontos'], |
|
datasets: [{ |
|
label: `Acertos ${type === 'vertical' ? 'Verticais' : 'Horizontais'}`, |
|
data: [stats.points15, stats.points16, stats.points17, stats.points18, stats.points19, stats.points20], |
|
backgroundColor: [ |
|
'rgba(156, 163, 175, 0.8)', |
|
'rgba(34, 197, 94, 0.8)', |
|
'rgba(59, 130, 246, 0.8)', |
|
'rgba(245, 158, 11, 0.8)', |
|
'rgba(239, 68, 68, 0.8)', |
|
'rgba(147, 51, 234, 0.8)' |
|
], |
|
borderColor: [ |
|
'rgb(156, 163, 175)', |
|
'rgb(34, 197, 94)', |
|
'rgb(59, 130, 246)', |
|
'rgb(245, 158, 11)', |
|
'rgb(239, 68, 68)', |
|
'rgb(147, 51, 234)' |
|
], |
|
borderWidth: 2 |
|
}] |
|
}; |
|
|
|
return ( |
|
<div className="space-y-6"> |
|
{/* Cards de estatísticas */} |
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> |
|
<div className="bg-white p-4 rounded-lg shadow border"> |
|
<div className="flex items-center justify-between"> |
|
<div> |
|
<p className="text-sm text-gray-600">Total Analisado</p> |
|
<p className="text-2xl font-bold text-gray-900">{stats.total.toLocaleString()}</p> |
|
</div> |
|
<BarChart3 className="w-8 h-8 text-blue-500" /> |
|
</div> |
|
</div> |
|
|
|
<div className="bg-white p-4 rounded-lg shadow border"> |
|
<div className="flex items-center justify-between"> |
|
<div> |
|
<p className="text-sm text-gray-600">Premiações</p> |
|
<p className="text-2xl font-bold text-green-600">{stats.wins}</p> |
|
<p className="text-xs text-gray-500">{stats.winRate.toFixed(2)}%</p> |
|
</div> |
|
<Trophy className="w-8 h-8 text-green-500" /> |
|
</div> |
|
</div> |
|
|
|
<div className="bg-white p-4 rounded-lg shadow border"> |
|
<div className="flex items-center justify-between"> |
|
<div> |
|
<p className="text-sm text-gray-600">Prêmios Máximos</p> |
|
<p className="text-2xl font-bold text-red-600">{stats.points20}</p> |
|
<p className="text-xs text-gray-500">20 ou 0 pontos</p> |
|
</div> |
|
<Award className="w-8 h-8 text-red-500" /> |
|
</div> |
|
</div> |
|
|
|
<div className="bg-white p-4 rounded-lg shadow border"> |
|
<div className="flex items-center justify-between"> |
|
<div> |
|
<p className="text-sm text-gray-600">Média Acertos</p> |
|
<p className="text-2xl font-bold text-purple-600">{stats.averageMatches.toFixed(1)}</p> |
|
<p className="text-xs text-gray-500">de 50 números</p> |
|
</div> |
|
<Target className="w-8 h-8 text-purple-500" /> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
{/* Gráfico de distribuição */} |
|
<div className="bg-white p-6 rounded-lg shadow border"> |
|
<h4 className="text-lg font-semibold mb-4">Distribuição de Prêmios</h4> |
|
<Bar |
|
data={chartData} |
|
options={{ |
|
responsive: true, |
|
plugins: { |
|
legend: { |
|
position: 'top', |
|
}, |
|
}, |
|
scales: { |
|
y: { |
|
beginAtZero: true, |
|
}, |
|
}, |
|
}} |
|
/> |
|
</div> |
|
</div> |
|
); |
|
}; |
|
|
|
const renderWinningGames = (analysisData: GameAnalysis[]) => { |
|
const winningGames = getFilteredAnalysis(analysisData).filter(a => a.isWinning); |
|
|
|
if (winningGames.length === 0) { |
|
return ( |
|
<div className="text-center py-8 text-gray-500"> |
|
<Trophy className="w-12 h-12 mx-auto mb-4 opacity-50" /> |
|
<p>Nenhum jogo premiado encontrado com os filtros aplicados</p> |
|
</div> |
|
); |
|
} |
|
|
|
return ( |
|
<div className="space-y-4"> |
|
{winningGames.slice(0, 50).map((analysis, index) => ( |
|
<div |
|
key={`${analysis.game.id}-${analysis.result.concurso}`} |
|
className={`p-4 rounded-lg border-l-4 ${ |
|
analysis.points === 20 || analysis.points === 0 |
|
? 'bg-red-50 border-red-500' |
|
: analysis.points === 19 |
|
? 'bg-orange-50 border-orange-500' |
|
: analysis.points === 18 |
|
? 'bg-yellow-50 border-yellow-500' |
|
: 'bg-green-50 border-green-500' |
|
}`} |
|
> |
|
<div className="flex items-start justify-between"> |
|
<div className="flex-1"> |
|
<div className="flex items-center space-x-4 mb-2"> |
|
<h4 className="font-semibold text-gray-900"> |
|
Jogo {analysis.game.type === 'vertical' ? 'Vertical' : 'Horizontal'} #{analysis.game.id} |
|
</h4> |
|
<span className={`px-2 py-1 rounded-full text-xs font-bold text-white ${ |
|
analysis.points === 20 || analysis.points === 0 |
|
? 'bg-red-600' |
|
: analysis.points === 19 |
|
? 'bg-orange-600' |
|
: analysis.points === 18 |
|
? 'bg-yellow-600' |
|
: 'bg-green-600' |
|
}`}> |
|
{analysis.points} pontos |
|
</span> |
|
<span className="text-sm text-gray-600"> |
|
Concurso {analysis.result.concurso} • {analysis.result.data} |
|
</span> |
|
</div> |
|
|
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 text-sm"> |
|
<div> |
|
<span className="font-medium text-gray-700">Posições marcadas:</span> |
|
<div className="flex flex-wrap gap-1 mt-1"> |
|
{(analysis.game.type === 'vertical' |
|
? analysis.game.markedColumns |
|
: analysis.game.markedRows! |
|
).map(pos => ( |
|
<span |
|
key={pos} |
|
className={`px-2 py-1 rounded text-xs font-medium text-white ${ |
|
analysis.game.type === 'vertical' ? 'bg-blue-500' : 'bg-green-500' |
|
}`} |
|
> |
|
{pos} |
|
</span> |
|
))} |
|
</div> |
|
</div> |
|
|
|
<div> |
|
<span className="font-medium text-gray-700">Acertos: {analysis.matches}/50</span> |
|
<p className="text-gray-600 mt-1">{analysis.prize}</p> |
|
</div> |
|
|
|
<div> |
|
<span className="font-medium text-gray-700">Fase {analysis.game.phase} • Ciclo {analysis.game.cycle}</span> |
|
<p className="text-gray-600 mt-1">Jogo {analysis.game.gameInPhase}</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
))} |
|
|
|
{winningGames.length > 50 && ( |
|
<div className="text-center py-4 text-gray-500"> |
|
<p>... e mais {winningGames.length - 50} jogos premiados</p> |
|
</div> |
|
)} |
|
</div> |
|
); |
|
}; |
|
|
|
return ( |
|
<div className="space-y-6"> |
|
{/* Header */} |
|
<div className="bg-gradient-to-r from-green-600 to-green-700 p-6 rounded-xl text-white"> |
|
<h2 className="text-2xl font-bold mb-2">Análise de Resultados</h2> |
|
<p className="text-green-100"> |
|
Comparação detalhada dos jogos com resultados oficiais da Lotomania |
|
</p> |
|
</div> |
|
|
|
{/* Informações do resultado atual */} |
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200"> |
|
{latestResult ? ( |
|
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> |
|
<div> |
|
<h3 className="text-sm font-medium text-gray-700 mb-2"> |
|
Último Resultado |
|
</h3> |
|
<div className="text-2xl font-bold text-green-600"> |
|
Concurso #{latestResult.concurso} |
|
</div> |
|
<div className="text-sm text-gray-500"> |
|
{latestResult.data} |
|
</div> |
|
</div> |
|
|
|
<div> |
|
<h3 className="text-sm font-medium text-gray-700 mb-2"> |
|
Filtro de análise: |
|
</h3> |
|
<select |
|
value={analysisFilter} |
|
onChange={(e) => setAnalysisFilter(e.target.value as any)} |
|
className="w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-green-500 focus:border-transparent" |
|
> |
|
<option value="all">Todos os resultados</option> |
|
<option value="15+">15+ pontos (Todos os prêmios)</option> |
|
<option value="16+">16+ pontos</option> |
|
<option value="17+">17+ pontos</option> |
|
<option value="18+">18+ pontos</option> |
|
<option value="19+">19+ pontos</option> |
|
<option value="20">Apenas 20 pontos (Máximo)</option> |
|
</select> |
|
</div> |
|
|
|
<div> |
|
<h3 className="text-sm font-medium text-gray-700 mb-2"> |
|
Controles |
|
</h3> |
|
<button |
|
onClick={fetchLatestResult} |
|
disabled={apiLoading || isAnalyzing} |
|
className="btn-primary flex items-center space-x-2 w-full justify-center" |
|
> |
|
{(apiLoading || isAnalyzing) ? <Loader className="w-4 h-4 animate-spin" /> : <RefreshCw className="w-4 h-4" />} |
|
<span>{apiLoading ? 'Buscando...' : isAnalyzing ? 'Analisando...' : 'Atualizar Resultado'}</span> |
|
</button> |
|
|
|
{analysisResults.vertical.length > 0 && ( |
|
<div className="text-xs text-gray-500 mt-2 text-center"> |
|
{analysisResults.vertical.length + analysisResults.horizontal.length} jogos analisados |
|
</div> |
|
)} |
|
</div> |
|
</div> |
|
) : ( |
|
<div className="text-center py-8"> |
|
<Loader className="w-8 h-8 animate-spin text-green-600 mx-auto mb-4" /> |
|
<p className="text-gray-600 mb-4"> |
|
Carregando resultado mais recente da Lotomania... |
|
</p> |
|
<button |
|
onClick={fetchLatestResult} |
|
disabled={apiLoading} |
|
className="btn-secondary" |
|
> |
|
Tentar novamente |
|
</button> |
|
</div> |
|
)} |
|
</div> |
|
|
|
{/* Tabs */} |
|
<div className="bg-white p-4 rounded-xl shadow-lg border border-gray-200"> |
|
<div className="flex space-x-2"> |
|
<button |
|
onClick={() => setActiveTab('vertical')} |
|
className={`px-4 py-2 rounded-lg font-medium transition-all duration-200 ${ |
|
activeTab === 'vertical' |
|
? 'bg-blue-600 text-white' |
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200' |
|
}`} |
|
> |
|
Jogos Verticais ({analysisResults.vertical.length}) |
|
</button> |
|
<button |
|
onClick={() => setActiveTab('horizontal')} |
|
className={`px-4 py-2 rounded-lg font-medium transition-all duration-200 ${ |
|
activeTab === 'horizontal' |
|
? 'bg-green-600 text-white' |
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200' |
|
}`} |
|
> |
|
Jogos Horizontais ({analysisResults.horizontal.length}) |
|
</button> |
|
<button |
|
onClick={() => setActiveTab('comparison')} |
|
className={`px-4 py-2 rounded-lg font-medium transition-all duration-200 ${ |
|
activeTab === 'comparison' |
|
? 'bg-purple-600 text-white' |
|
: 'bg-gray-100 text-gray-700 hover:bg-gray-200' |
|
}`} |
|
> |
|
Comparação |
|
</button> |
|
</div> |
|
</div> |
|
|
|
{/* Conteúdo baseado na tab ativa */} |
|
{apiError && ( |
|
<div className="bg-red-50 border border-red-200 p-4 rounded-lg"> |
|
<div className="flex items-center space-x-2"> |
|
<AlertCircle className="w-5 h-5 text-red-600" /> |
|
<p className="text-red-700">{apiError}</p> |
|
</div> |
|
</div> |
|
)} |
|
|
|
{activeTab === 'vertical' && ( |
|
<div className="space-y-6"> |
|
{renderStatistics(analysisResults.vertical, 'vertical')} |
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200"> |
|
<h3 className="text-lg font-semibold mb-4">Jogos Verticais Premiados</h3> |
|
{renderWinningGames(analysisResults.vertical)} |
|
</div> |
|
</div> |
|
)} |
|
|
|
{activeTab === 'horizontal' && ( |
|
<div className="space-y-6"> |
|
{renderStatistics(analysisResults.horizontal, 'horizontal')} |
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200"> |
|
<h3 className="text-lg font-semibold mb-4">Jogos Horizontais Premiados</h3> |
|
{renderWinningGames(analysisResults.horizontal)} |
|
</div> |
|
</div> |
|
)} |
|
|
|
{activeTab === 'comparison' && ( |
|
<div className="space-y-6"> |
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> |
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200"> |
|
<h3 className="text-lg font-semibold mb-4 text-blue-800">Estatísticas Verticais</h3> |
|
{renderStatistics(analysisResults.vertical, 'vertical')} |
|
</div> |
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200"> |
|
<h3 className="text-lg font-semibold mb-4 text-green-800">Estatísticas Horizontais</h3> |
|
{renderStatistics(analysisResults.horizontal, 'horizontal')} |
|
</div> |
|
</div> |
|
</div> |
|
)} |
|
</div> |
|
); |
|
}; |
|
|
|
export default ResultsAnalysis; |
|
|