Loto / src /components /ResultsAnalysis.tsx
Raí Santos
oi
4c1e4ec
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'; // Importar configuração do Chart.js
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! 🎉'; // 0 acertos também premia
default: return 'Sem prêmio';
}
};
const performAnalysis = useCallback(async () => {
if (!latestResult) return;
setIsAnalyzing(true);
try {
// Analisar jogos verticais contra o último resultado
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)
});
});
// Analisar jogos horizontais contra o último resultado
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]);
// Executar análise quando componente carrega ou quando resultado muda
useEffect(() => {
if (latestResult) {
performAnalysis();
}
}, [latestResult, performAnalysis]);
const getFilteredAnalysis = (analysisData: GameAnalysis[]) => {
let filtered = analysisData;
// Filtrar por pontuação
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:
// 'all' - não filtrar
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)', // 15 pontos - cinza
'rgba(34, 197, 94, 0.8)', // 16 pontos - verde
'rgba(59, 130, 246, 0.8)', // 17 pontos - azul
'rgba(245, 158, 11, 0.8)', // 18 pontos - amarelo
'rgba(239, 68, 68, 0.8)', // 19 pontos - vermelho
'rgba(147, 51, 234, 0.8)' // 20 pontos - roxo
],
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;