|
import React, { useState, useEffect, useMemo, Suspense } from 'react'; |
|
import Sidebar from './components/Sidebar'; |
|
import ErrorBoundary from './components/ErrorBoundary'; |
|
import { useOptimizedAlgorithm } from './hooks/useOptimizedAlgorithm'; |
|
import { useLotomaniaAPI } from './hooks/useLotomaniaAPI'; |
|
import { setupSecurityDefaults } from './utils/SecurityUtils'; |
|
import { setupBundleOptimizations } from './utils/BundleOptimizer'; |
|
import { conditionalPreload } from './utils/lazyComponents'; |
|
import { |
|
DualGameViewer, |
|
Statistics, |
|
ProbabilityCalculator, |
|
EnhancedLotomaniaGrid, |
|
LotomaniaFeatures, |
|
ComponentLoader |
|
} from './utils/lazyComponents'; |
|
import RealComparisonDemo from './components/RealComparisonDemo'; |
|
import GameByGameResults from './components/GameByGameResults'; |
|
import { |
|
AlertCircle, |
|
RefreshCw, |
|
Settings, |
|
PlayCircle, |
|
TrendingUp, |
|
Zap, |
|
Target, |
|
BarChart3, |
|
Calculator |
|
} from 'lucide-react'; |
|
|
|
const App: React.FC = () => { |
|
const [activeSection, setActiveSection] = useState('dual-visualizer'); |
|
const [currentVerticalGameId, setCurrentVerticalGameId] = useState(1); |
|
const [currentHorizontalGameId, setCurrentHorizontalGameId] = useState(253); |
|
|
|
|
|
useEffect(() => { |
|
|
|
setupSecurityDefaults(); |
|
|
|
|
|
setupBundleOptimizations(); |
|
|
|
|
|
conditionalPreload(); |
|
|
|
console.log('🛡️ Segurança e otimizações configuradas'); |
|
}, []); |
|
|
|
|
|
const { |
|
allGames, |
|
verticalGames, |
|
horizontalGames, |
|
gameStatistics, |
|
isLoading: gamesLoading, |
|
error: gamesError, |
|
algorithm, |
|
getGameById, |
|
getNumbersFromGame, |
|
performanceMetrics |
|
} = useOptimizedAlgorithm(); |
|
|
|
const { |
|
latestResult, |
|
loading: apiLoading, |
|
error: apiError, |
|
fetchLatestResult, |
|
analyzeGameResult |
|
} = useLotomaniaAPI(); |
|
|
|
|
|
useEffect(() => { |
|
if (allGames.length === 0) return; |
|
|
|
try { |
|
const userCurrentVerticalGame = algorithm.findGameByColumns([2, 4, 5, 6, 10]); |
|
const userCurrentHorizontalGame = algorithm.findGameByRows([2, 4, 5, 6, 10]); |
|
|
|
if (userCurrentVerticalGame) { |
|
setCurrentVerticalGameId(userCurrentVerticalGame.id); |
|
} |
|
if (userCurrentHorizontalGame) { |
|
setCurrentHorizontalGameId(userCurrentHorizontalGame.id); |
|
} |
|
} catch (error) { |
|
console.error('Erro ao encontrar jogos do usuário:', error); |
|
} |
|
}, [allGames, algorithm]); |
|
|
|
|
|
const gameStats = useMemo(() => { |
|
if (!gameStatistics) { |
|
return { |
|
currentGame: Math.min(currentVerticalGameId, currentHorizontalGameId), |
|
totalGames: allGames.length, |
|
verticalGames: verticalGames.length, |
|
horizontalGames: horizontalGames.length, |
|
remainingGames: 0, |
|
phases: 1, |
|
gamesPerPhase: {} |
|
}; |
|
} |
|
|
|
return { |
|
currentGame: Math.min(currentVerticalGameId, currentHorizontalGameId), |
|
totalGames: gameStatistics.totalGames, |
|
verticalGames: gameStatistics.verticalGames, |
|
horizontalGames: gameStatistics.horizontalGames, |
|
remainingGames: algorithm.getRemainingGames(currentVerticalGameId), |
|
phases: gameStatistics.phases, |
|
gamesPerPhase: gameStatistics.gamesPerPhase |
|
}; |
|
}, [gameStatistics, allGames, verticalGames, horizontalGames, currentVerticalGameId, currentHorizontalGameId, algorithm]); |
|
|
|
|
|
const currentGamesAnalysis = useMemo(() => { |
|
if (!latestResult) return null; |
|
|
|
try { |
|
const currentVerticalGame = getGameById(currentVerticalGameId); |
|
const currentHorizontalGame = getGameById(currentHorizontalGameId); |
|
|
|
if (!currentVerticalGame || !currentHorizontalGame || |
|
currentVerticalGame.type !== 'vertical' || currentHorizontalGame.type !== 'horizontal') { |
|
return null; |
|
} |
|
|
|
const verticalNumbers = getNumbersFromGame(currentVerticalGame); |
|
const horizontalNumbers = getNumbersFromGame(currentHorizontalGame); |
|
|
|
if (verticalNumbers.length === 0 || horizontalNumbers.length === 0) { |
|
return null; |
|
} |
|
|
|
const verticalAnalysis = analyzeGameResult(verticalNumbers, latestResult); |
|
const horizontalAnalysis = analyzeGameResult(horizontalNumbers, latestResult); |
|
|
|
return { |
|
vertical: verticalAnalysis, |
|
horizontal: horizontalAnalysis, |
|
matchedNumbers: { |
|
vertical: verticalAnalysis.matchedNumbers, |
|
horizontal: horizontalAnalysis.matchedNumbers |
|
} |
|
}; |
|
} catch (error) { |
|
console.error('Erro na análise dos jogos atuais:', error); |
|
return null; |
|
} |
|
}, [currentVerticalGameId, currentHorizontalGameId, latestResult, getGameById, getNumbersFromGame, analyzeGameResult]); |
|
|
|
const handleSectionChange = (section: string) => { |
|
setActiveSection(section); |
|
}; |
|
|
|
const handleVerticalGameChange = (gameId: number) => { |
|
setCurrentVerticalGameId(gameId); |
|
}; |
|
|
|
const handleHorizontalGameChange = (gameId: number) => { |
|
setCurrentHorizontalGameId(gameId); |
|
}; |
|
|
|
|
|
const renderPendingSection = (title: string, description: string, icon: React.ReactNode) => ( |
|
<div className="space-y-6"> |
|
<div className="bg-gradient-to-r from-gray-600 to-gray-700 p-6 rounded-xl text-white"> |
|
<div className="flex items-center space-x-3"> |
|
{icon} |
|
<div> |
|
<h2 className="text-2xl font-bold mb-1">{title}</h2> |
|
<p className="text-gray-100">{description}</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div className="bg-white p-8 rounded-xl shadow-lg border border-gray-200 text-center"> |
|
<div className="max-w-md mx-auto"> |
|
<Zap className="w-16 h-16 text-blue-500 mx-auto mb-4" /> |
|
<h3 className="text-xl font-semibold text-gray-800 mb-2"> |
|
Funcionalidade Avançada |
|
</h3> |
|
<p className="text-gray-600 mb-4"> |
|
Esta seção está sendo implementada com recursos avançados de análise e simulação. |
|
</p> |
|
<div className="bg-blue-50 p-4 rounded-lg"> |
|
<p className="text-blue-800 text-sm"> |
|
🚀 Em breve: análises preditivas, simulações Monte Carlo, |
|
otimização de estratégias e muito mais! |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
|
|
|
|
if (gamesLoading) { |
|
return ( |
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center"> |
|
<ComponentLoader message="Gerando jogos da estratégia..." /> |
|
</div> |
|
); |
|
} |
|
|
|
|
|
if (gamesError) { |
|
return ( |
|
<div className="min-h-screen bg-gray-50 flex items-center justify-center p-4"> |
|
<div className="max-w-md w-full bg-white rounded-xl shadow-lg border border-gray-200 p-6 text-center"> |
|
<AlertCircle className="w-12 h-12 text-red-500 mx-auto mb-4" /> |
|
<h2 className="text-xl font-bold text-gray-900 mb-2">Erro na Aplicação</h2> |
|
<p className="text-gray-600 mb-4">{gamesError}</p> |
|
<button |
|
onClick={() => window.location.reload()} |
|
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700" |
|
> |
|
Recarregar Aplicação |
|
</button> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|
|
const renderContent = () => { |
|
switch (activeSection) { |
|
case 'real-comparison': { |
|
const currentVerticalGame = verticalGames.find(g => g.id === currentVerticalGameId); |
|
const currentHorizontalGame = horizontalGames.find(g => g.id === currentHorizontalGameId); |
|
|
|
return ( |
|
<div className="space-y-6"> |
|
<div className="bg-gradient-to-r from-blue-600 to-green-600 text-white p-6 rounded-xl shadow-lg"> |
|
<h2 className="text-2xl font-bold mb-2">🔍 DEMONSTRAÇÃO DE COMPARAÇÃO REAL</h2> |
|
<p className="text-blue-100"> |
|
Veja exatamente como seus jogos são comparados com os resultados oficiais da Lotomania - |
|
100% dados reais, sem invenções! |
|
</p> |
|
</div> |
|
|
|
{currentVerticalGame && ( |
|
<div> |
|
<h3 className="text-xl font-bold text-gray-800 mb-3"> |
|
📊 Jogo Vertical Atual (#{currentVerticalGame.id}) |
|
</h3> |
|
<RealComparisonDemo |
|
game={currentVerticalGame} |
|
result={latestResult} |
|
algorithm={algorithm} |
|
/> |
|
</div> |
|
)} |
|
|
|
{currentHorizontalGame && ( |
|
<div> |
|
<h3 className="text-xl font-bold text-gray-800 mb-3"> |
|
📊 Jogo Horizontal Atual (#{currentHorizontalGame.id}) |
|
</h3> |
|
<RealComparisonDemo |
|
game={currentHorizontalGame} |
|
result={latestResult} |
|
algorithm={algorithm} |
|
/> |
|
</div> |
|
)} |
|
|
|
<div className="bg-yellow-50 p-4 rounded-lg border border-yellow-200"> |
|
<div className="text-center"> |
|
<p className="text-yellow-800 font-semibold mb-2"> |
|
💡 Use os controles na barra lateral para navegar entre os jogos e ver comparações diferentes! |
|
</p> |
|
<p className="text-yellow-700 text-sm"> |
|
Cada jogo mostra exatamente quais números de 1 a 100 estão marcados e como eles se comparam com o resultado oficial. |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
} |
|
|
|
case 'dual-visualizer': |
|
return ( |
|
<Suspense fallback={<ComponentLoader message="Carregando visualizador..." />}> |
|
<DualGameViewer |
|
verticalGames={verticalGames} |
|
horizontalGames={horizontalGames} |
|
currentVerticalGameId={currentVerticalGameId} |
|
currentHorizontalGameId={currentHorizontalGameId} |
|
onVerticalGameChange={handleVerticalGameChange} |
|
onHorizontalGameChange={handleHorizontalGameChange} |
|
matchedNumbers={currentGamesAnalysis?.matchedNumbers} |
|
/> |
|
</Suspense> |
|
); |
|
|
|
case 'grid': { |
|
const verticalGameForGrid = getGameById(currentVerticalGameId); |
|
const horizontalGameForGrid = getGameById(currentHorizontalGameId); |
|
|
|
return ( |
|
<Suspense fallback={<ComponentLoader message="Carregando grid interativo..." />}> |
|
<div className="space-y-6"> |
|
<div className="bg-gradient-to-r from-blue-600 to-blue-700 p-6 rounded-xl text-white"> |
|
<h2 className="text-2xl font-bold mb-2">Grid Interativo Avançado</h2> |
|
<p className="text-blue-100"> |
|
Visualização detalhada dos jogos vertical e horizontal com destaque dos acertos |
|
</p> |
|
</div> |
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> |
|
{verticalGameForGrid && verticalGameForGrid.type === 'vertical' && ( |
|
<div> |
|
<h3 className="text-lg font-semibold mb-3 text-blue-800"> |
|
Jogo Vertical #{verticalGameForGrid.id} |
|
</h3> |
|
<EnhancedLotomaniaGrid |
|
game={verticalGameForGrid} |
|
showNumbers={false} |
|
size="md" |
|
className="shadow-lg" |
|
highlightMatches={currentGamesAnalysis?.matchedNumbers?.vertical || []} |
|
/> |
|
</div> |
|
)} |
|
|
|
{horizontalGameForGrid && horizontalGameForGrid.type === 'horizontal' && ( |
|
<div> |
|
<h3 className="text-lg font-semibold mb-3 text-green-800"> |
|
Jogo Horizontal #{horizontalGameForGrid.id} |
|
</h3> |
|
<EnhancedLotomaniaGrid |
|
game={horizontalGameForGrid} |
|
showNumbers={false} |
|
size="md" |
|
className="shadow-lg" |
|
highlightMatches={currentGamesAnalysis?.matchedNumbers?.horizontal || []} |
|
/> |
|
</div> |
|
)} |
|
</div> |
|
|
|
{/* Grid com números visíveis */} |
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200"> |
|
<h3 className="text-lg font-semibold mb-4 text-gray-800"> |
|
Visualização com Números |
|
</h3> |
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> |
|
{verticalGameForGrid && verticalGameForGrid.type === 'vertical' && ( |
|
<EnhancedLotomaniaGrid |
|
game={verticalGameForGrid} |
|
showNumbers={true} |
|
size="sm" |
|
highlightMatches={currentGamesAnalysis?.matchedNumbers?.vertical || []} |
|
/> |
|
)} |
|
{horizontalGameForGrid && horizontalGameForGrid.type === 'horizontal' && ( |
|
<EnhancedLotomaniaGrid |
|
game={horizontalGameForGrid} |
|
showNumbers={true} |
|
size="sm" |
|
highlightMatches={currentGamesAnalysis?.matchedNumbers?.horizontal || []} |
|
/> |
|
)} |
|
</div> |
|
</div> |
|
</div> |
|
</Suspense> |
|
); |
|
} |
|
|
|
case 'statistics': |
|
return ( |
|
<Suspense fallback={<ComponentLoader message="Carregando estatísticas..." />}> |
|
<Statistics |
|
totalGames={gameStats.totalGames} |
|
currentGame={gameStats.currentGame} |
|
phases={gameStats.phases} |
|
gamesPerPhase={gameStats.gamesPerPhase} |
|
/> |
|
</Suspense> |
|
); |
|
|
|
case 'results-analysis': |
|
return latestResult ? ( |
|
<Suspense fallback={<ComponentLoader message="Carregando análise de resultados..." />}> |
|
<GameByGameResults |
|
allGames={allGames} |
|
algorithm={algorithm} |
|
currentResult={latestResult} |
|
/> |
|
</Suspense> |
|
) : ( |
|
<div className="flex items-center justify-center h-64"> |
|
<div className="text-center"> |
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-4"></div> |
|
<p className="text-gray-600">Carregando resultado da Lotomania...</p> |
|
</div> |
|
</div> |
|
); |
|
|
|
case 'probability': |
|
return ( |
|
<Suspense fallback={<ComponentLoader message="Carregando calculadora de probabilidades..." />}> |
|
<ProbabilityCalculator |
|
verticalGames={verticalGames} |
|
horizontalGames={horizontalGames} |
|
latestResult={latestResult} |
|
/> |
|
</Suspense> |
|
); |
|
|
|
case 'lotomania-features': |
|
return ( |
|
<Suspense fallback={<ComponentLoader message="Carregando funcionalidades da Lotomania..." />}> |
|
<LotomaniaFeatures /> |
|
</Suspense> |
|
); |
|
|
|
case 'calculator': |
|
return ( |
|
<div className="space-y-6"> |
|
<div className="bg-gradient-to-r from-purple-600 to-purple-700 p-6 rounded-xl text-white"> |
|
<h2 className="text-2xl font-bold mb-2">Calculadora da Estratégia</h2> |
|
<p className="text-purple-100"> |
|
Cálculos e informações detalhadas da estratégia dual |
|
</p> |
|
</div> |
|
|
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6"> |
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200"> |
|
<div className="flex items-center justify-between"> |
|
<div> |
|
<p className="text-sm font-medium text-gray-600 mb-1">Total de Jogos</p> |
|
<p className="text-2xl font-bold text-blue-600">{gameStats.totalGames.toLocaleString()}</p> |
|
<p className="text-xs text-gray-500 mt-1">Vertical + Horizontal</p> |
|
</div> |
|
<Calculator className="w-8 h-8 text-blue-500" /> |
|
</div> |
|
</div> |
|
|
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200"> |
|
<div className="flex items-center justify-between"> |
|
<div> |
|
<p className="text-sm font-medium text-gray-600 mb-1">Jogos Verticais</p> |
|
<p className="text-2xl font-bold text-blue-600">{gameStats.verticalGames.toLocaleString()}</p> |
|
<p className="text-xs text-gray-500 mt-1">Colunas marcadas</p> |
|
</div> |
|
<BarChart3 className="w-8 h-8 text-blue-500" /> |
|
</div> |
|
</div> |
|
|
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200"> |
|
<div className="flex items-center justify-between"> |
|
<div> |
|
<p className="text-sm font-medium text-gray-600 mb-1">Jogos Horizontais</p> |
|
<p className="text-2xl font-bold text-green-600">{gameStats.horizontalGames.toLocaleString()}</p> |
|
<p className="text-xs text-gray-500 mt-1">Linhas marcadas</p> |
|
</div> |
|
<BarChart3 className="w-8 h-8 text-green-500" /> |
|
</div> |
|
</div> |
|
|
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200"> |
|
<div className="flex items-center justify-between"> |
|
<div> |
|
<p className="text-sm font-medium text-gray-600 mb-1">Investimento Total</p> |
|
<p className="text-2xl font-bold text-purple-600">R$ {(gameStats.totalGames * 3.0).toLocaleString()}</p> |
|
<p className="text-xs text-gray-500 mt-1">R$ 3,00 por jogo</p> |
|
</div> |
|
<Target className="w-8 h-8 text-purple-500" /> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<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-gray-800">Análise por Tipo</h3> |
|
<div className="space-y-4"> |
|
<div className="flex justify-between items-center p-3 bg-blue-50 rounded-lg"> |
|
<span className="text-blue-800 font-medium">Jogos Verticais:</span> |
|
<span className="text-blue-900 font-bold">{gameStats.verticalGames}</span> |
|
</div> |
|
<div className="flex justify-between items-center p-3 bg-green-50 rounded-lg"> |
|
<span className="text-green-800 font-medium">Jogos Horizontais:</span> |
|
<span className="text-green-900 font-bold">{gameStats.horizontalGames}</span> |
|
</div> |
|
<div className="flex justify-between items-center p-3 bg-gray-50 rounded-lg"> |
|
<span className="text-gray-800 font-medium">Fases Total:</span> |
|
<span className="text-gray-900 font-bold">{gameStats.phases}</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-gray-200"> |
|
<h3 className="text-lg font-semibold mb-4 text-gray-800">Progresso Atual</h3> |
|
<div className="space-y-4"> |
|
<div> |
|
<div className="flex justify-between text-sm text-gray-600 mb-1"> |
|
<span>Jogo Vertical Atual</span> |
|
<span>{currentVerticalGameId} / {gameStats.verticalGames}</span> |
|
</div> |
|
<div className="w-full bg-blue-200 rounded-full h-2"> |
|
<div |
|
className="bg-blue-600 h-2 rounded-full transition-all duration-300" |
|
style={{ width: `${(currentVerticalGameId / gameStats.verticalGames) * 100}%` }} |
|
/> |
|
</div> |
|
</div> |
|
|
|
<div> |
|
<div className="flex justify-between text-sm text-gray-600 mb-1"> |
|
<span>Jogo Horizontal Atual</span> |
|
<span>{currentHorizontalGameId} / {gameStats.horizontalGames}</span> |
|
</div> |
|
<div className="w-full bg-green-200 rounded-full h-2"> |
|
<div |
|
className="bg-green-600 h-2 rounded-full transition-all duration-300" |
|
style={{ width: `${(currentHorizontalGameId / gameStats.horizontalGames) * 100}%` }} |
|
/> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
); |
|
|
|
case 'patterns': |
|
return renderPendingSection( |
|
'Análise de Padrões', |
|
'Identificação de padrões e tendências nos sorteios', |
|
<TrendingUp className="w-6 h-6" /> |
|
); |
|
|
|
case 'simulator': |
|
return renderPendingSection( |
|
'Simulador Avançado', |
|
'Simulação Monte Carlo e cenários otimizados', |
|
<PlayCircle className="w-6 h-6" /> |
|
); |
|
|
|
case 'settings': |
|
return renderPendingSection( |
|
'Configurações', |
|
'Personalização e preferências do sistema', |
|
<Settings className="w-6 h-6" /> |
|
); |
|
|
|
default: |
|
return ( |
|
<div className="text-center p-8"> |
|
<h2 className="text-2xl font-bold text-gray-800 mb-4"> |
|
Seção em Desenvolvimento |
|
</h2> |
|
<p className="text-gray-600"> |
|
Esta funcionalidade será implementada em breve. |
|
</p> |
|
</div> |
|
); |
|
} |
|
}; |
|
|
|
return ( |
|
<ErrorBoundary> |
|
<div className="min-h-screen bg-gray-50 flex"> |
|
<Sidebar |
|
activeSection={activeSection} |
|
onSectionChange={handleSectionChange} |
|
gameStats={gameStats} |
|
/> |
|
|
|
<main className="flex-1 p-6 overflow-auto will-change-scroll"> |
|
<div className="max-w-7xl mx-auto"> |
|
{/* Indicador de performance em desenvolvimento */} |
|
{process.env.NODE_ENV === 'development' && performanceMetrics && ( |
|
<div className="mb-4 p-3 bg-yellow-50 border border-yellow-200 rounded-lg text-sm"> |
|
<div className="flex items-center justify-between"> |
|
<span className="font-medium text-yellow-800"> |
|
Performance: {performanceMetrics.totalGames} jogos • |
|
Validação: {performanceMetrics.validationTime.toFixed(2)}ms • |
|
Memória: ~{(performanceMetrics.memoryUsage.estimated / 1024).toFixed(1)}KB |
|
</span> |
|
<span className={`text-xs px-2 py-1 rounded ${ |
|
performanceMetrics.isValid ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800' |
|
}`}> |
|
{performanceMetrics.isValid ? '✓ Válido' : '✗ Erro'} |
|
</span> |
|
</div> |
|
</div> |
|
)} |
|
|
|
{/* Indicador de análise de resultado atual */} |
|
{currentGamesAnalysis && latestResult && ( |
|
<div className="mb-6 bg-white p-4 rounded-xl shadow-lg border border-gray-200 will-change-transform"> |
|
<div className="flex items-center justify-between"> |
|
<div> |
|
<h4 className="font-semibold text-gray-800"> |
|
Resultado vs Jogos Atuais - Concurso #{latestResult.concurso} |
|
</h4> |
|
<div className="flex space-x-6 mt-2 text-sm"> |
|
<div className="flex items-center space-x-2"> |
|
<span className="w-3 h-3 bg-blue-500 rounded-full"></span> |
|
<span>Vertical: {currentGamesAnalysis.vertical.matches}/50 acertos</span> |
|
{currentGamesAnalysis.vertical.isWinning && ( |
|
<span className="text-green-600 font-bold">🏆 PREMIADO!</span> |
|
)} |
|
</div> |
|
<div className="flex items-center space-x-2"> |
|
<span className="w-3 h-3 bg-green-500 rounded-full"></span> |
|
<span>Horizontal: {currentGamesAnalysis.horizontal.matches}/50 acertos</span> |
|
{currentGamesAnalysis.horizontal.isWinning && ( |
|
<span className="text-green-600 font-bold">🏆 PREMIADO!</span> |
|
)} |
|
</div> |
|
</div> |
|
</div> |
|
<button |
|
onClick={fetchLatestResult} |
|
disabled={apiLoading} |
|
className={`p-2 rounded-lg transition-colors duration-200 ${ |
|
apiLoading ? 'text-gray-300' : 'text-gray-400 hover:text-gray-600 hover:bg-gray-100' |
|
}`} |
|
title="Atualizar resultado" |
|
> |
|
<RefreshCw className={`w-5 h-5 ${apiLoading ? 'animate-spin' : ''}`} /> |
|
</button> |
|
</div> |
|
</div> |
|
)} |
|
|
|
{/* Erros da API */} |
|
{(apiError || gamesError) && ( |
|
<div className="mb-6 bg-red-50 border border-red-200 p-4 rounded-xl"> |
|
<div className="flex items-center space-x-2"> |
|
<AlertCircle className="w-5 h-5 text-red-600" /> |
|
<div> |
|
<h4 className="font-semibold text-red-800">Aviso do Sistema</h4> |
|
<p className="text-red-700 text-sm mt-1"> |
|
{apiError && `API: ${apiError}`} |
|
{gamesError && `Jogos: ${gamesError}`} |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
)} |
|
|
|
{/* Conteúdo principal com ErrorBoundary interno */} |
|
<ErrorBoundary> |
|
{renderContent()} |
|
</ErrorBoundary> |
|
</div> |
|
</main> |
|
</div> |
|
</ErrorBoundary> |
|
); |
|
}; |
|
|
|
export default App; |
|
|
|
|
|
|
|
|
|
|