Loto / src /components /DetailedGameAnalysis.tsx
Raí Santos
oi
4c1e4ec
import React, { useState, useMemo } from 'react';
import { TrendingUp, Award, Grid3X3, DollarSign, Target, Filter } from 'lucide-react';
import { LotomaniaGame, LotomaniaResult } from '../types';
import { LotomaniaAlgorithm } from '../utils/lotomaniaAlgorithm';
interface DetailedGameAnalysisProps {
allGames: LotomaniaGame[];
algorithm: LotomaniaAlgorithm;
currentResult: LotomaniaResult;
analyzeGameResult: (numbers: number[], result: LotomaniaResult) => any;
}
interface DetailedGameResult {
game: LotomaniaGame;
numbers: number[];
matchedNumbers: number[];
nonMatchedNumbers: number[];
drawnButNotPlayed: number[];
points: number;
prizeValue: number;
prizeDescription: string;
netProfit: number;
isWinning: boolean;
}
export const DetailedGameAnalysis: React.FC<DetailedGameAnalysisProps> = ({
allGames,
algorithm,
currentResult,
analyzeGameResult
}) => {
const [filterType, setFilterType] = useState<'all' | 'vertical' | 'horizontal'>('all');
const [filterPoints, setFilterPoints] = useState<'all' | '15+' | '16+' | '17+' | '18+' | '19+' | '20'>('all');
const [currentPage, setCurrentPage] = useState(1);
const [showGrid, setShowGrid] = useState(true);
const GAMES_PER_PAGE = 5;
// Análise detalhada de todos os jogos
const detailedResults = useMemo(() => {
console.log('🔄 Analisando todos os jogos detalhadamente...');
const results: DetailedGameResult[] = allGames.map(game => {
const gameNumbers = algorithm.getNumbersFromGame(game);
const analysis = analyzeGameResult(gameNumbers, currentResult);
// Separar números
const matchedNumbers = gameNumbers.filter(num => currentResult.numeros.includes(num));
const nonMatchedNumbers = gameNumbers.filter(num => !currentResult.numeros.includes(num));
const drawnButNotPlayed = currentResult.numeros.filter(num => !gameNumbers.includes(num));
// Buscar prêmio oficial
let prizeValue = 0;
let prizeDescription = 'Sem prêmio';
const premio = currentResult.premiacoes?.find(p => p.acertos === analysis.points);
if (premio && analysis.points >= 15) {
prizeValue = premio.valorPremio;
prizeDescription = premio.descricao;
} else if (analysis.points === 0) {
const premioZero = currentResult.premiacoes?.find(p => p.acertos === 0);
if (premioZero) {
prizeValue = premioZero.valorPremio;
prizeDescription = premioZero.descricao;
}
}
const netProfit = prizeValue - 3.00; // Custo do jogo
return {
game,
numbers: gameNumbers,
matchedNumbers,
nonMatchedNumbers,
drawnButNotPlayed,
points: analysis.points,
prizeValue,
prizeDescription,
netProfit,
isWinning: prizeValue > 0
};
});
console.log(`✅ Análise detalhada concluída: ${results.length} jogos processados`);
return results;
}, [allGames, algorithm, currentResult, analyzeGameResult]);
// Filtrar resultados
const filteredResults = useMemo(() => {
let filtered = detailedResults;
// Filtro por tipo
if (filterType !== 'all') {
filtered = filtered.filter(r => r.game.type === filterType);
}
// Filtro por pontos
if (filterPoints === '20') {
filtered = filtered.filter(r => r.points === 20 || r.points === 0);
} else if (filterPoints !== 'all') {
const minPoints = parseInt(filterPoints.replace('+', ''));
filtered = filtered.filter(r => r.points >= minPoints);
}
return filtered;
}, [detailedResults, filterType, filterPoints]);
// Paginação
const totalPages = Math.ceil(filteredResults.length / GAMES_PER_PAGE);
const paginatedResults = filteredResults.slice(
(currentPage - 1) * GAMES_PER_PAGE,
currentPage * GAMES_PER_PAGE
);
// Estatísticas resumidas
const statistics = useMemo(() => {
const totalGames = filteredResults.length;
const winningGames = filteredResults.filter(r => r.isWinning).length;
const totalCost = totalGames * 3.00;
const totalPrizes = filteredResults.reduce((sum, r) => sum + r.prizeValue, 0);
const netResult = totalPrizes - totalCost;
// Distribuição por pontos
const pointsDistribution = filteredResults.reduce((acc, r) => {
acc[r.points] = (acc[r.points] || 0) + 1;
return acc;
}, {} as Record<number, number>);
// Distribuição por prêmios
const prizeDistribution = filteredResults
.filter(r => r.isWinning)
.reduce((acc, r) => {
if (!acc[r.points]) {
acc[r.points] = { count: 0, totalValue: 0 };
}
acc[r.points].count++;
acc[r.points].totalValue += r.prizeValue;
return acc;
}, {} as Record<number, { count: number; totalValue: number }>);
return {
totalGames,
winningGames,
totalCost,
totalPrizes,
netResult,
winRate: (winningGames / totalGames) * 100,
pointsDistribution,
prizeDistribution
};
}, [filteredResults]);
// Renderizar grid de um jogo
const renderGameGrid = (result: DetailedGameResult) => {
if (!showGrid) return null;
return (
<div className="bg-white p-4 rounded-lg border mb-4">
<h6 className="font-semibold mb-3 flex items-center gap-2">
<Grid3X3 className="w-4 h-4" />
Grid de Marcação - Jogo #{result.game.id}
</h6>
{/* Headers */}
<div className="flex gap-1 mb-2">
<div className="w-8"></div>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(col => (
<div key={col} className="w-8 h-6 text-xs font-bold text-center text-gray-600">
C{col}
</div>
))}
</div>
{/* Grid 10x10 */}
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(row => (
<div key={row} className="flex gap-1 mb-1">
<div className="w-8 h-8 text-xs font-bold text-center text-gray-600 flex items-center justify-center">
L{row}
</div>
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map(col => {
const number = (row - 1) * 10 + (col - 1); // 0-99
const isMarked = result.numbers.includes(number);
const isHit = result.matchedNumbers.includes(number);
const wasDrawn = currentResult.numeros.includes(number);
let cellClass = '';
let icon = '';
if (isMarked && isHit) {
cellClass = 'bg-green-600 text-white border-green-700'; // Marcado e acertou
icon = '✓';
} else if (isMarked && !isHit) {
cellClass = 'bg-blue-600 text-white border-blue-700'; // Marcado mas não acertou
} else if (!isMarked && wasDrawn) {
cellClass = 'bg-yellow-400 text-black border-yellow-500'; // Não marcado mas saiu
icon = '★';
} else {
cellClass = 'bg-gray-100 text-gray-600 border-gray-300'; // Normal
}
return (
<div
key={`${row}-${col}`}
className={`w-8 h-8 border text-xs font-bold flex flex-col items-center justify-center relative ${cellClass}`}
title={`Número ${number === 0 ? '00' : number} ${isMarked ? '(Marcado)' : ''} ${wasDrawn ? '(Sorteado)' : ''}`}
>
<span className="text-[10px] leading-none">
{number === 0 ? '00' : number.toString().padStart(2, '0')}
</span>
{icon && (
<span className="text-[8px] leading-none font-black">
{icon}
</span>
)}
</div>
);
})}
</div>
))}
{/* Legenda */}
<div className="flex flex-wrap gap-3 mt-3 text-xs">
<div className="flex items-center gap-1">
<div className="w-4 h-4 bg-green-600 rounded flex items-center justify-center text-white text-[8px]"></div>
<span>Marcado + Acertou ({result.matchedNumbers.length})</span>
</div>
<div className="flex items-center gap-1">
<div className="w-4 h-4 bg-blue-600 rounded"></div>
<span>Marcado + Não acertou ({result.numbers.length - result.matchedNumbers.length})</span>
</div>
<div className="flex items-center gap-1">
<div className="w-4 h-4 bg-yellow-400 rounded flex items-center justify-center text-black text-[8px]"></div>
<span>Não marcado + Saiu ({result.drawnButNotPlayed.length})</span>
</div>
<div className="flex items-center gap-1">
<div className="w-4 h-4 bg-gray-100 border border-gray-300 rounded"></div>
<span>Normal</span>
</div>
</div>
</div>
);
};
return (
<div className="space-y-6">
{/* Header */}
<div className="bg-gradient-to-r from-purple-600 to-blue-600 text-white p-6 rounded-lg">
<h2 className="text-2xl font-bold flex items-center gap-2">
<Target className="w-6 h-6" />
Análise Detalhada Jogo por Jogo
</h2>
<p className="text-purple-100 mt-2">
Resultado individual de cada jogo contra o concurso {currentResult.concurso}
</p>
</div>
{/* Estatísticas Resumidas */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
<div className="bg-blue-50 p-4 rounded-lg border-l-4 border-blue-500">
<span className="text-sm text-blue-600">Total de Jogos</span>
<p className="text-2xl font-bold text-blue-800">{statistics.totalGames}</p>
<p className="text-sm text-blue-600">
{filterType === 'all' ? 'Todos os tipos' : filterType}
</p>
</div>
<div className="bg-green-50 p-4 rounded-lg border-l-4 border-green-500">
<span className="text-sm text-green-600">Jogos Premiados</span>
<p className="text-2xl font-bold text-green-800">{statistics.winningGames}</p>
<p className="text-sm text-green-600">
{statistics.winRate.toFixed(1)}% de aproveitamento
</p>
</div>
<div className="bg-yellow-50 p-4 rounded-lg border-l-4 border-yellow-500">
<span className="text-sm text-yellow-600">Total de Prêmios</span>
<p className="text-2xl font-bold text-yellow-800">
R$ {statistics.totalPrizes.toLocaleString('pt-BR', { maximumFractionDigits: 0 })}
</p>
<p className="text-sm text-yellow-600">
Custo: R$ {statistics.totalCost.toLocaleString('pt-BR', { maximumFractionDigits: 0 })}
</p>
</div>
<div className={`p-4 rounded-lg border-l-4 ${
statistics.netResult >= 0 ? 'border-green-500 bg-green-50' : 'border-red-500 bg-red-50'
}`}>
<span className={`text-sm ${statistics.netResult >= 0 ? 'text-green-600' : 'text-red-600'}`}>
Resultado Líquido
</span>
<p className={`text-2xl font-bold ${statistics.netResult >= 0 ? 'text-green-800' : 'text-red-800'}`}>
{statistics.netResult >= 0 ? '+' : ''}R$ {statistics.netResult.toLocaleString('pt-BR', { maximumFractionDigits: 0 })}
</p>
<p className={`text-sm ${statistics.netResult >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{statistics.netResult >= 0 ? 'Lucro' : 'Prejuízo'}
</p>
</div>
</div>
{/* Filtros e Controles */}
<div className="bg-white p-4 rounded-lg shadow border">
<div className="flex flex-wrap gap-4 items-center">
<div className="flex items-center gap-2">
<Filter className="w-4 h-4 text-gray-500" />
<span className="text-sm font-medium">Filtros:</span>
</div>
<select
value={filterType}
onChange={(e) => setFilterType(e.target.value as any)}
className="px-3 py-1 border border-gray-300 rounded-md text-sm"
>
<option value="all">Todos os Tipos</option>
<option value="vertical">Apenas Verticais</option>
<option value="horizontal">Apenas Horizontais</option>
</select>
<select
value={filterPoints}
onChange={(e) => setFilterPoints(e.target.value as any)}
className="px-3 py-1 border border-gray-300 rounded-md text-sm"
>
<option value="all">Todas as Pontuações</option>
<option value="15+">15+ Pontos (Premiados)</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">20 Pontos (Máximo)</option>
</select>
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={showGrid}
onChange={(e) => setShowGrid(e.target.checked)}
className="rounded"
/>
Mostrar Grid Visual
</label>
<div className="text-sm text-gray-600">
{filteredResults.length} jogo(s) encontrado(s)
</div>
</div>
</div>
{/* Lista Detalhada de Jogos */}
<div className="space-y-6">
{paginatedResults.map((result, index) => (
<div
key={result.game.id}
className={`p-6 border-2 rounded-xl shadow-lg ${
result.isWinning ? 'border-green-400 bg-green-50' : 'border-gray-300 bg-white'
}`}
>
{/* Header do Jogo */}
<div className="flex justify-between items-center mb-4">
<div className="flex items-center gap-4">
<div className="bg-blue-600 text-white px-4 py-2 rounded-lg">
<span className="font-bold text-xl">Jogo #{result.game.id}</span>
</div>
<div className="text-sm text-gray-600">
<div>📍 Fase {result.game.phase}, Ciclo {result.game.cycle}</div>
<div>🎯 {result.game.type === 'vertical' ? 'Vertical (Colunas)' : 'Horizontal (Linhas)'}</div>
{result.game.type === 'vertical' ? (
<div>🔹 Colunas: {result.game.markedColumns.join(', ')}</div>
) : (
<div>🔸 Linhas: {result.game.markedRows?.join(', ')}</div>
)}
</div>
</div>
<div className="text-right">
<div className={`text-4xl font-bold mb-1 ${
result.isWinning ? 'text-green-600' : 'text-gray-600'
}`}>
{result.points} pts
</div>
<div className={`text-xl font-semibold ${
result.isWinning ? 'text-green-600' : 'text-red-600'
}`}>
{result.isWinning
? `+R$ ${result.prizeValue.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}`
: `-R$ 3,00`
}
</div>
<div className={`text-lg font-medium ${
result.netProfit >= 0 ? 'text-green-600' : 'text-red-600'
}`}>
Líquido: {result.netProfit >= 0 ? '+' : ''}R$ {result.netProfit.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}
</div>
<div className="text-sm text-gray-600 mt-1">
{result.isWinning ? `🏆 ${result.prizeDescription}` : '❌ Não premiado'}
</div>
</div>
</div>
{/* Grid Visual */}
{renderGameGrid(result)}
{/* Resumo de Números */}
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<div className="bg-white p-4 rounded-lg border">
<h6 className="font-semibold text-green-600 mb-2 flex items-center gap-2">
<Award className="w-4 h-4" />
Números Acertados ({result.matchedNumbers.length})
</h6>
<div className="flex flex-wrap gap-1">
{result.matchedNumbers.length > 0 ? result.matchedNumbers.map(num => (
<span
key={num}
className="bg-green-600 text-white px-2 py-1 rounded text-sm font-bold"
>
{num === 0 ? '00' : num.toString().padStart(2, '0')}
</span>
)) : (
<span className="text-gray-500 text-sm italic">Nenhum acerto</span>
)}
</div>
</div>
<div className="bg-white p-4 rounded-lg border">
<h6 className="font-semibold text-yellow-600 mb-2">
⭐ Números Sorteados Não Jogados ({result.drawnButNotPlayed.length})
</h6>
<div className="flex flex-wrap gap-1">
{result.drawnButNotPlayed.map(num => (
<span
key={num}
className="bg-yellow-400 text-black px-2 py-1 rounded text-sm font-bold"
>
{num === 0 ? '00' : num.toString().padStart(2, '0')}
</span>
))}
</div>
</div>
<div className={`p-4 rounded-lg border ${
result.isWinning ? 'bg-green-100 border-green-300' : 'bg-red-100 border-red-300'
}`}>
<h6 className={`font-semibold mb-2 flex items-center gap-2 ${
result.isWinning ? 'text-green-600' : 'text-red-600'
}`}>
<DollarSign className="w-4 h-4" />
Resultado Financeiro
</h6>
<div className="space-y-1 text-sm">
<div className="flex justify-between">
<span>Custo do jogo:</span>
<span className="text-red-600 font-semibold">-R$ 3,00</span>
</div>
<div className="flex justify-between">
<span>Prêmio obtido:</span>
<span className={`font-semibold ${
result.isWinning ? 'text-green-600' : 'text-gray-500'
}`}>
+R$ {result.prizeValue.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}
</span>
</div>
<div className="border-t pt-1 flex justify-between">
<span className="font-semibold">Saldo:</span>
<span className={`font-bold text-lg ${
result.netProfit >= 0 ? 'text-green-600' : 'text-red-600'
}`}>
{result.netProfit >= 0 ? '+' : ''}R$ {result.netProfit.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}
</span>
</div>
</div>
</div>
</div>
</div>
))}
</div>
{/* Paginação */}
{totalPages > 1 && (
<div className="flex justify-center items-center gap-2">
<button
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
disabled={currentPage === 1}
className="px-3 py-1 border rounded-md disabled:opacity-50"
>
Anterior
</button>
<div className="flex gap-1">
{Array.from({ length: Math.min(5, totalPages) }, (_, i) => {
const page = currentPage <= 3 ? i + 1 : currentPage - 2 + i;
if (page > totalPages) return null;
return (
<button
key={page}
onClick={() => setCurrentPage(page)}
className={`px-3 py-1 border rounded-md ${
currentPage === page ? 'bg-blue-600 text-white' : 'hover:bg-gray-100'
}`}
>
{page}
</button>
);
})}
</div>
<button
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
className="px-3 py-1 border rounded-md disabled:opacity-50"
>
Próximo
</button>
<span className="text-sm text-gray-600 ml-2">
Página {currentPage} de {totalPages}
</span>
</div>
)}
{/* Resumo de Distribuição por Pontos */}
<div className="bg-white p-6 rounded-lg shadow border">
<h3 className="text-lg font-semibold mb-4 flex items-center gap-2">
<TrendingUp className="w-5 h-5" />
Distribuição de Resultados
</h3>
<div className="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-3">
{Object.entries(statistics.pointsDistribution)
.sort(([a], [b]) => parseInt(b) - parseInt(a))
.map(([points, count]) => {
const prizeInfo = statistics.prizeDistribution[parseInt(points)];
const isWinning = prizeInfo && prizeInfo.count > 0;
return (
<div
key={points}
className={`p-3 rounded-lg text-center border ${
isWinning ? 'border-green-300 bg-green-50' : 'border-gray-200 bg-gray-50'
}`}
>
<div className={`font-bold text-xl ${
isWinning ? 'text-green-600' : 'text-gray-600'
}`}>
{points}
</div>
<div className="text-xs text-gray-600 mb-1">pontos</div>
<div className={`text-sm font-medium ${
isWinning ? 'text-green-700' : 'text-gray-700'
}`}>
{count} jogos
</div>
{isWinning && (
<div className="text-xs text-green-600 font-semibold mt-1">
R$ {prizeInfo.totalValue.toLocaleString('pt-BR', { maximumFractionDigits: 0 })}
</div>
)}
</div>
);
})}
</div>
</div>
</div>
);
};
export default DetailedGameAnalysis;