Loto / src /components /GameByGameResults.tsx
Raí Santos
oi
dd60672
import React, { useState, useMemo } from 'react';
import { Target, TrendingUp, Filter, Eye } from 'lucide-react';
import { LotomaniaGame, LotomaniaResult } from '../types';
import { LotomaniaAlgorithm } from '../utils/lotomaniaAlgorithm';
interface GameByGameResultsProps {
allGames: LotomaniaGame[];
algorithm: LotomaniaAlgorithm;
currentResult: LotomaniaResult;
}
interface GameResultDetailed {
game: LotomaniaGame;
numbers: number[];
matchedNumbers: number[];
nonMatchedNumbers: number[];
points: number;
prizeValue: number;
prizeDescription: string;
netProfit: number;
isWinning: boolean;
}
export const GameByGameResults: React.FC<GameByGameResultsProps> = ({
allGames,
algorithm,
currentResult
}) => {
const [filterType, setFilterType] = useState<'all' | 'vertical' | 'horizontal'>('all');
const [filterPoints, setFilterPoints] = useState<'all' | 'winning' | '15+' | '16' | '17+' | '18' | '19+' | '20' | '0'>('all');
const [showGrid, setShowGrid] = useState(true);
const [currentPage, setCurrentPage] = useState(1);
const GAMES_PER_PAGE = 10;
// Analisar todos os jogos
const gameResults = useMemo(() => {
return allGames.map(game => {
const gameNumbers = algorithm.getNumbersFromGame(game);
const matchedNumbers = gameNumbers.filter(num => currentResult.numeros.includes(num));
const nonMatchedNumbers = gameNumbers.filter(num => !currentResult.numeros.includes(num));
let points = matchedNumbers.length;
let prizeValue = 0;
let prizeDescription = 'Sem prêmio';
// Verificar premiação oficial
const premio = currentResult.premiacoes?.find(p => p.acertos === points);
if (premio && points >= 15) {
prizeValue = premio.valorPremio;
prizeDescription = premio.descricao;
} else if (points === 0) {
const premioZero = currentResult.premiacoes?.find(p => p.acertos === 0);
if (premioZero) {
prizeValue = premioZero.valorPremio;
prizeDescription = premioZero.descricao;
}
}
const netProfit = prizeValue - 3.00;
return {
game,
numbers: gameNumbers,
matchedNumbers,
nonMatchedNumbers,
points,
prizeValue,
prizeDescription,
netProfit,
isWinning: prizeValue > 0
};
});
}, [allGames, algorithm, currentResult]);
// Filtrar resultados
const filteredResults = useMemo(() => {
let filtered = gameResults;
if (filterType !== 'all') {
filtered = filtered.filter(r => r.game.type === filterType);
}
if (filterPoints === 'winning') {
filtered = filtered.filter(r => r.isWinning);
} else if (filterPoints === '20') {
filtered = filtered.filter(r => r.points === 20);
} else if (filterPoints === '18') {
filtered = filtered.filter(r => r.points === 18);
} else if (filterPoints === '16') {
filtered = filtered.filter(r => r.points === 16);
} else if (filterPoints === '0') {
filtered = filtered.filter(r => r.points === 0);
} else if (filterPoints !== 'all') {
const minPoints = parseInt(filterPoints.replace('+', ''));
filtered = filtered.filter(r => r.points >= minPoints);
}
return filtered;
}, [gameResults, 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 stats = 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);
// Distribuição por pontos
const pointsDistribution = filteredResults.reduce((acc, r) => {
acc[r.points] = (acc[r.points] || 0) + 1;
return acc;
}, {} as Record<number, number>);
return {
totalGames,
winningGames,
totalCost,
totalPrizes,
netResult: totalPrizes - totalCost,
winRate: (winningGames / totalGames) * 100,
pointsDistribution
};
}, [filteredResults]);
// Renderizar grid de um jogo
const renderGameGrid = (result: GameResultDetailed) => {
if (!showGrid) return null;
return (
<div className="bg-gray-50 p-3 rounded-lg border">
<div className="text-xs font-semibold mb-2 text-gray-700">
🎯 Grid Visual - Jogo #{result.game.id}
</div>
{/* Grid compacto 10x10 */}
<div className="grid grid-cols-10 gap-0.5">
{Array.from({ length: 100 }, (_, i) => {
const number = i;
const isMarked = result.numbers.includes(number);
const isHit = result.matchedNumbers.includes(number);
const wasDrawn = currentResult.numeros.includes(number);
let cellClass = 'w-5 h-5 text-[8px] font-bold flex items-center justify-center border';
if (isMarked && isHit) {
cellClass += ' bg-green-600 text-white'; // Marcado e acertou
} else if (isMarked && !isHit) {
cellClass += ' bg-blue-600 text-white'; // Marcado mas não acertou
} else if (!isMarked && wasDrawn) {
cellClass += ' bg-yellow-400 text-black'; // Não marcado mas saiu
} else {
cellClass += ' bg-white text-gray-600'; // Normal
}
return (
<div
key={number}
className={cellClass}
title={`${number === 0 ? '00' : number} ${isMarked ? '(Marcado)' : ''} ${wasDrawn ? '(Sorteado)' : ''}`}
>
{number === 0 ? '00' : number.toString().padStart(2, '0')}
</div>
);
})}
</div>
{/* Legenda compacta */}
<div className="flex justify-between mt-2 text-[10px] text-gray-600">
<span className="flex items-center gap-1">
<div className="w-2 h-2 bg-green-600 rounded"></div>
Acertou ({result.matchedNumbers.length})
</span>
<span className="flex items-center gap-1">
<div className="w-2 h-2 bg-blue-600 rounded"></div>
Marcado ({result.numbers.length - result.matchedNumbers.length})
</span>
<span className="flex items-center gap-1">
<div className="w-2 h-2 bg-yellow-400 rounded"></div>
Perdeu ({currentResult.numeros.filter(n => !result.numbers.includes(n)).length})
</span>
</div>
</div>
);
};
return (
<div className="space-y-6">
{/* Header */}
<div className="bg-gradient-to-r from-green-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" />
Resultado Jogo por Jogo
</h2>
<p className="text-green-100 mt-2">
Análise individual de cada um dos {allGames.length} jogos contra o concurso {currentResult.concurso}
</p>
</div>
{/* Estatísticas Resumidas */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4">
<div className="bg-blue-50 p-4 rounded-lg border-l-4 border-blue-500">
<span className="text-sm text-blue-600">Jogos Analisados</span>
<p className="text-2xl font-bold text-blue-800">{stats.totalGames}</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">{stats.winningGames}</p>
<p className="text-xs text-green-600">{stats.winRate.toFixed(1)}%</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 Prêmios</span>
<p className="text-lg font-bold text-yellow-800">
R$ {stats.totalPrizes.toLocaleString('pt-BR', { maximumFractionDigits: 0 })}
</p>
</div>
<div className="bg-red-50 p-4 rounded-lg border-l-4 border-red-500">
<span className="text-sm text-red-600">Custo Total</span>
<p className="text-lg font-bold text-red-800">
R$ {stats.totalCost.toLocaleString('pt-BR', { maximumFractionDigits: 0 })}
</p>
</div>
<div className={`p-4 rounded-lg border-l-4 ${
stats.netResult >= 0 ? 'border-green-500 bg-green-50' : 'border-red-500 bg-red-50'
}`}>
<span className={`text-sm ${stats.netResult >= 0 ? 'text-green-600' : 'text-red-600'}`}>
Resultado
</span>
<p className={`text-lg font-bold ${stats.netResult >= 0 ? 'text-green-800' : 'text-red-800'}`}>
{stats.netResult >= 0 ? '+' : ''}R$ {stats.netResult.toLocaleString('pt-BR', { maximumFractionDigits: 0 })}
</p>
</div>
</div>
{/* Filtros */}
<div className="bg-white p-4 rounded-lg shadow border">
<div className="flex flex-wrap gap-4 items-center">
<Filter className="w-4 h-4 text-gray-500" />
<select
value={filterType}
onChange={(e) => setFilterType(e.target.value as any)}
className="px-3 py-1 border rounded text-sm"
>
<option value="all">Todos os Tipos</option>
<option value="vertical">Verticais ({gameResults.filter(r => r.game.type === 'vertical').length})</option>
<option value="horizontal">Horizontais ({gameResults.filter(r => r.game.type === 'horizontal').length})</option>
</select>
<select
value={filterPoints}
onChange={(e) => setFilterPoints(e.target.value as any)}
className="px-3 py-1 border rounded text-sm"
>
<option value="all">Todas Pontuações</option>
<option value="winning">Apenas Premiados</option>
<option value="15+">15+ Pontos</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</option>
</select>
<label className="flex items-center gap-2 text-sm">
<input
type="checkbox"
checked={showGrid}
onChange={(e) => setShowGrid(e.target.checked)}
/>
<Eye className="w-4 h-4" />
Grid Visual
</label>
<span className="text-sm text-gray-600">
{filteredResults.length} resultado(s)
</span>
</div>
</div>
{/* Lista de Resultados */}
<div className="space-y-4">
{paginatedResults.map((result) => (
<div
key={result.game.id}
className={`p-4 border rounded-lg ${
result.isWinning ? 'border-green-300 bg-green-50' : 'border-gray-200 bg-white'
}`}
>
<div className="grid grid-cols-1 lg:grid-cols-3 gap-4">
{/* Info do Jogo */}
<div>
<div className="flex items-center gap-3 mb-2">
<div className="bg-blue-600 text-white px-3 py-1 rounded font-bold">
#{result.game.id}
</div>
<div className="text-sm text-gray-600">
<div>{result.game.type === 'vertical' ? '🔹 Vertical' : '🔸 Horizontal'}</div>
<div>Fase {result.game.phase}, Ciclo {result.game.cycle}</div>
</div>
</div>
<div className="space-y-1 text-sm">
<div className="flex justify-between">
<span>Pontuação:</span>
<span className={`font-bold ${result.isWinning ? 'text-green-600' : 'text-gray-600'}`}>
{result.points} pontos
</span>
</div>
<div className="flex justify-between">
<span>Prêmio:</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="flex justify-between">
<span>Resultado:</span>
<span className={`font-bold ${result.netProfit >= 0 ? 'text-green-600' : 'text-red-600'}`}>
{result.netProfit >= 0 ? '+' : ''}R$ {result.netProfit.toLocaleString('pt-BR', { minimumFractionDigits: 2 })}
</span>
</div>
{result.isWinning && (
<div className="text-xs text-green-600 mt-1">
🏆 {result.prizeDescription}
</div>
)}
</div>
</div>
{/* Grid Visual */}
<div>
{renderGameGrid(result)}
</div>
{/* Números Detalhados */}
<div className="space-y-3">
<div>
<div className="text-sm font-semibold text-green-600 mb-1">
✅ Acertos ({result.matchedNumbers.length})
</div>
<div className="flex flex-wrap gap-1">
{result.matchedNumbers.map(num => (
<span
key={num}
className="bg-green-600 text-white px-1.5 py-0.5 rounded text-xs font-bold"
>
{num === 0 ? '00' : num.toString().padStart(2, '0')}
</span>
))}
</div>
</div>
<div>
<div className="text-sm font-semibold text-yellow-600 mb-1">
⭐ Perdidos ({currentResult.numeros.filter(n => !result.numbers.includes(n)).length})
</div>
<div className="flex flex-wrap gap-1">
{currentResult.numeros
.filter(n => !result.numbers.includes(n))
.slice(0, 8)
.map(num => (
<span
key={num}
className="bg-yellow-400 text-black px-1.5 py-0.5 rounded text-xs font-bold"
>
{num === 0 ? '00' : num.toString().padStart(2, '0')}
</span>
))}
{currentResult.numeros.filter(n => !result.numbers.includes(n)).length > 8 && (
<span className="text-xs text-gray-500">
+{currentResult.numeros.filter(n => !result.numbers.includes(n)).length - 8}
</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 disabled:opacity-50"
>
← Anterior
</button>
<span className="px-3 py-1 text-sm">
Página {currentPage} de {totalPages}
</span>
<button
onClick={() => setCurrentPage(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages}
className="px-3 py-1 border rounded disabled:opacity-50"
>
Próximo →
</button>
</div>
)}
{/* Distribuição de Pontos */}
<div className="bg-white p-4 rounded-lg shadow border">
<h3 className="font-semibold mb-3 flex items-center gap-2">
<TrendingUp className="w-4 h-4" />
Distribuição de Pontuação
</h3>
<div className="grid grid-cols-4 md:grid-cols-8 gap-2">
{Object.entries(stats.pointsDistribution)
.sort(([a], [b]) => parseInt(b) - parseInt(a))
.map(([points, count]) => {
const isWinning = parseInt(points) >= 15 || parseInt(points) === 0;
return (
<div
key={points}
className={`p-2 rounded text-center ${
isWinning ? 'bg-green-100 border border-green-300' : 'bg-gray-100'
}`}
>
<div className={`font-bold ${isWinning ? 'text-green-600' : 'text-gray-600'}`}>
{points}pts
</div>
<div className="text-xs text-gray-600">{count} jogos</div>
</div>
);
})}
</div>
</div>
</div>
);
};
export default GameByGameResults;