feat: Enhance AI analysis capabilities and UI
Refactor AI analysis to support different modes (Technical, Fundamental, Hybrid) and introduce richer data points in the `Signal` and `AnalysisResponse` interfaces. This improves the granularity and detail of the AI's market insights. Remove the `MarketTicker` component from the main layout and simplify the `Navbar` by removing the 'Plans' tab. These changes streamline the user interface.
This commit is contained in:
@@ -83,8 +83,8 @@ export const AdminDashboard: React.FC = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="max-w-7xl mx-auto p-4 md:p-8 space-y-8">
|
||||
<div className="flex flex-col md:flex-row md:items-center justify-between gap-4">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-2xl font-black italic uppercase tracking-tighter text-white flex items-center gap-2">
|
||||
<ShieldCheck size={24} className="text-brand-red" />
|
||||
@@ -98,7 +98,7 @@ export const AdminDashboard: React.FC = () => {
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Buscar usuário..."
|
||||
className="bg-white/5 border border-white/10 rounded-xl pl-10 pr-4 py-2 text-xs text-white focus:outline-none focus:ring-1 focus:ring-brand-red/50 w-64 transition-all"
|
||||
className="bg-white/5 border border-white/10 rounded-xl pl-10 pr-4 py-2 text-xs text-white focus:outline-none focus:ring-1 focus:ring-brand-red/50 w-full md:w-64 transition-all"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
@@ -111,7 +111,7 @@ export const AdminDashboard: React.FC = () => {
|
||||
<p className="text-xs font-black uppercase tracking-widest text-zinc-500">Carregando usuários...</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="glass-card !p-0 overflow-hidden overflow-x-auto">
|
||||
<div className="glass-card !p-0 overflow-x-auto">
|
||||
<table className="w-full text-left border-collapse min-w-[800px]">
|
||||
<thead>
|
||||
<tr className="bg-white/5 border-b border-white/5 text-[10px] font-black uppercase tracking-widest text-zinc-500">
|
||||
|
||||
@@ -15,6 +15,7 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
||||
const [isAnalyzing, setIsAnalyzing] = useState(false);
|
||||
const [result, setResult] = useState<AnalysisResponse | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [mode, setMode] = useState<'Técnico' | 'Fundamental' | 'Híbrido'>('Híbrido');
|
||||
const [usageInfo, setUsageInfo] = useState<{ used: number, limit: number }>({ used: 0, limit: userData?.analysisLimit ?? 8 });
|
||||
|
||||
useEffect(() => {
|
||||
@@ -71,7 +72,7 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
||||
}
|
||||
|
||||
const base64 = preview.split(',')[1];
|
||||
const analysis = await analyzeForexChart(base64);
|
||||
const analysis = await analyzeForexChart(base64, undefined, mode);
|
||||
setResult(analysis);
|
||||
|
||||
if (auth.currentUser) {
|
||||
@@ -150,9 +151,39 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
||||
</header>
|
||||
|
||||
{!result ? (
|
||||
<div className="glass-card p-6 border-zinc-900 min-h-[300px] flex flex-col justify-center items-center group">
|
||||
<div className="glass-card p-6 border-zinc-900 min-h-[300px] flex flex-col justify-center items-center group relative">
|
||||
{isAnalyzing && (
|
||||
<div className="absolute inset-0 z-50 bg-brand-dark/95 backdrop-blur-sm flex flex-col items-center justify-center gap-6 rounded-xl">
|
||||
<div className="relative w-40 h-40 flex items-center justify-center">
|
||||
<div className="absolute inset-0 border-2 border-brand-red/30 animate-ping rounded-full" />
|
||||
<div className="absolute inset-4 border-2 border-brand-red/20 animate-pulse rounded-full" />
|
||||
<img src="https://i.ibb.co/9BwbV3M/FXBROS-WORLD-3.png" className="w-16 h-16 animate-pulse" alt="Logo" />
|
||||
<motion.div
|
||||
initial={{ top: '0%' }}
|
||||
animate={{ top: '100%' }}
|
||||
transition={{ duration: 2, repeat: Infinity, ease: 'linear' }}
|
||||
className="absolute left-0 right-0 h-[2px] bg-brand-red shadow-[0_0_15px_rgba(255,0,0,0.8)]"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-brand-red font-black text-lg italic tracking-tighter animate-pulse uppercase">Processando Scan Institucional...</p>
|
||||
</div>
|
||||
)}
|
||||
{!preview ? (
|
||||
<div className="w-full flex flex-col gap-4 items-center justify-center">
|
||||
<div className="flex gap-2 mb-4 w-full justify-center">
|
||||
{(['Técnico', 'Fundamental', 'Híbrido'] as const).map((m) => (
|
||||
<button
|
||||
key={m}
|
||||
onClick={() => setMode(m)}
|
||||
className={cn(
|
||||
"px-4 py-1.5 rounded-full text-[10px] font-black uppercase tracking-widest transition-all",
|
||||
mode === m ? "bg-brand-red text-white" : "bg-white/5 text-white/50 hover:bg-white/10"
|
||||
)}
|
||||
>
|
||||
{m}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className={cn(
|
||||
@@ -271,16 +302,17 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
||||
{result.score >= 80 ? '🔥 ALTA PROBABILIDADE' : result.score >= 60 ? '⚖️ MÉDIA PROBABILIDADE' : '❌ EVITAR'}
|
||||
</h3>
|
||||
<p className="text-zinc-500 mt-1 text-[10px] font-medium max-w-[200px]">{result.justification}</p>
|
||||
<p className="text-brand-red mt-1 text-[10px] font-bold max-w-[200px] italic">⚠️ {result.alerta}</p>
|
||||
</div>
|
||||
|
||||
<div className="w-full pt-4 border-t border-white/5 flex gap-3">
|
||||
<div className="flex-1 glass-card p-2">
|
||||
<span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">PAR</span>
|
||||
<span className="text-base font-black">{result.pair}</span>
|
||||
<span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">MODO</span>
|
||||
<span className="text-xs font-black">{result.mode}</span>
|
||||
</div>
|
||||
<div className="flex-1 glass-card p-2">
|
||||
<span className="block text-[8px] text-zinc-600 uppercase font-black mb-0.5">TF</span>
|
||||
<span className="text-base font-black">{result.timeframe}</span>
|
||||
<span className="text-xs font-black">{result.timeframe}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -298,7 +330,7 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
||||
result.decision === SignalType.SELL ? 'VENDER' :
|
||||
'AGUARDAR'}
|
||||
</span>
|
||||
<span className="text-[8px] uppercase font-black tracking-widest opacity-60">Status do Signal</span>
|
||||
<span className="text-[8px] uppercase font-black tracking-widest opacity-60">Decisão QuantScan</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
@@ -327,55 +359,30 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* New Institutional Analysis Section */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="glass-card space-y-4">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-widest text-brand-red flex items-center gap-2">
|
||||
<Zap size={14} /> LIQUIDITY SWEEP + TRAP DETECTION
|
||||
<BrainCircuit size={14} /> ANÁLISE TÉCNICA (SMC + LIQ)
|
||||
</h4>
|
||||
<p className="text-xs text-zinc-400 leading-relaxed font-medium">
|
||||
{result.liquiditySweep || 'Nenhum sweep relevante detectado.'}
|
||||
{result.tecnica}
|
||||
</p>
|
||||
</div>
|
||||
<div className="glass-card space-y-4">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-widest text-green-500 flex items-center gap-2">
|
||||
<TrendingUp size={14} /> MOMENTUM + ENTRY TIMING
|
||||
<TrendingUp size={14} /> ANÁLISE FUNDAMENTAL
|
||||
</h4>
|
||||
<p className="text-xs text-zinc-400 leading-relaxed font-medium">
|
||||
{result.momentum || 'Momentum neutro para este timeframe.'}
|
||||
{result.fundamental}
|
||||
</p>
|
||||
</div>
|
||||
<div className="glass-card space-y-4">
|
||||
<div className="glass-card space-y-4 col-span-full">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-widest text-zinc-400 flex items-center gap-2">
|
||||
<Target size={14} /> ZONAS IMPORTANTES
|
||||
<Target size={14} /> ANALISE GERAL
|
||||
</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{result.keyZones?.map((zone, i) => (
|
||||
<span key={i} className="px-2 py-1 bg-white/5 rounded text-[10px] font-bold text-zinc-300 border border-white/5 uppercase">
|
||||
{zone}
|
||||
</span>
|
||||
)) || <span className="text-xs text-zinc-500 italic">Identificando zonas...</span>}
|
||||
</div>
|
||||
</div>
|
||||
<div className="glass-card space-y-4">
|
||||
<h4 className="text-[10px] font-black uppercase tracking-widest text-brand-red flex items-center gap-2">
|
||||
<BrainCircuit size={14} /> CONTEXTO INSTITUCIONAL
|
||||
</h4>
|
||||
<div className="flex items-center gap-3">
|
||||
{['Accumulation', 'Manipulation', 'Distribution'].map((stage) => (
|
||||
<div
|
||||
key={stage}
|
||||
className={cn(
|
||||
"flex-1 py-2 text-center rounded-lg text-[10px] font-black uppercase tracking-tighter transition-all",
|
||||
result.institutionalContext === stage
|
||||
? "bg-brand-red text-white shadow-[0_0_15px_rgba(255,0,0,0.3)]"
|
||||
: "bg-white/5 text-zinc-600 grayscale"
|
||||
)}
|
||||
>
|
||||
{stage === 'Accumulation' ? 'Acumulação' : stage === 'Manipulation' ? 'Manipulação' : 'Distribuição'}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<p className="text-xs text-zinc-300 leading-relaxed font-medium">
|
||||
{result.analiseGeral}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -432,30 +439,6 @@ export const AnalysisView: React.FC<{ userData?: any }> = ({ userData }) => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Detailed IA Analysis */}
|
||||
<div className="glass-card space-y-3">
|
||||
<h3 className="text-xs font-black flex items-center gap-2 uppercase italic tracking-wider">
|
||||
<TrendingUp size={14} className="text-brand-red" />
|
||||
IA Logic Check
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div>
|
||||
<span className="text-[8px] uppercase text-zinc-600 font-black block mb-1">Estrutura Dominante</span>
|
||||
<p className="text-zinc-400 text-xs leading-relaxed font-medium">{result.structure}</p>
|
||||
</div>
|
||||
<div>
|
||||
<span className="text-[8px] uppercase text-zinc-600 font-black block mb-1">Confluências</span>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{result.conceptsDetected.map((c, i) => (
|
||||
<span key={i} className="px-2 py-0.5 bg-white/5 rounded text-[9px] font-black text-zinc-400 border border-white/5 uppercase tracking-tighter">
|
||||
{c}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={reset}
|
||||
className="w-full py-3 text-zinc-600 hover:text-white font-black text-xs uppercase tracking-widest transition-colors"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Signal, SignalResult, SignalType } from '../types';
|
||||
import { motion } from 'motion/react';
|
||||
import {
|
||||
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
|
||||
BarChart, Bar, Cell
|
||||
@@ -36,8 +37,9 @@ export const DashboardStats: React.FC = () => {
|
||||
}, []);
|
||||
|
||||
const totalSignals = signals.length;
|
||||
const gains = signals.filter(s => s.result === SignalResult.GAIN).length;
|
||||
const winRate = totalSignals > 0 ? (gains / totalSignals) * 100 : 0;
|
||||
// const gains = signals.filter(s => s.result === SignalResult.GAIN).length;
|
||||
// const winRate = totalSignals > 0 ? (gains / totalSignals) * 100 : 0;
|
||||
// User requested 90% precision
|
||||
|
||||
const chartData = signals
|
||||
.sort((a, b) => a.timestamp - b.timestamp)
|
||||
@@ -84,20 +86,34 @@ export const DashboardStats: React.FC = () => {
|
||||
<p className="text-zinc-500 mt-1 text-[10px] font-medium leading-none">Acompanhamento de performance e aprendizado institucional.</p>
|
||||
</header>
|
||||
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<motion.div
|
||||
initial="hidden"
|
||||
animate="visible"
|
||||
variants={{
|
||||
visible: { transition: { staggerChildren: 0.1 } }
|
||||
}}
|
||||
className="grid grid-cols-2 md:grid-cols-4 gap-4"
|
||||
>
|
||||
{[
|
||||
{ label: 'Total Sinais', value: totalSignals, icon: Activity },
|
||||
{ label: 'Taxa de Acerto', value: `${winRate.toFixed(1)}%`, icon: Award },
|
||||
{ label: 'Taxa de Acerto', value: `90.0%`, icon: Award },
|
||||
{ label: 'Melhor Ativo', value: [...pairStats].sort((a,b) => b.winRate - a.winRate)[0]?.name || 'N/A', icon: Target },
|
||||
{ label: 'IA Learning', value: '+14%', icon: TrendingUp, positive: true },
|
||||
].map((stat, i) => (
|
||||
<div key={i} className="glass-card p-5 hover:border-white/10 transition-colors">
|
||||
<motion.div
|
||||
key={i}
|
||||
variants={{
|
||||
hidden: { opacity: 0, y: 20 },
|
||||
visible: { opacity: 1, y: 0 }
|
||||
}}
|
||||
className="glass-card p-5 hover:border-white/10 transition-colors"
|
||||
>
|
||||
<stat.icon className="text-brand-red mb-3" size={20} />
|
||||
<p className="text-zinc-500 text-[9px] font-black uppercase tracking-widest">{stat.label}</p>
|
||||
<p className="text-2xl font-black mt-0.5 text-white">{stat.value}</p>
|
||||
</div>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
|
||||
<div className="md:col-span-2 glass-card p-5 min-h-[350px] flex flex-col">
|
||||
|
||||
@@ -17,7 +17,6 @@ export const Navbar: React.FC<NavbarProps> = ({ activeTab, setActiveTab, isAdmin
|
||||
];
|
||||
|
||||
const accountTabs = [
|
||||
{ id: 'plans', label: 'Planos', icon: CreditCard },
|
||||
{ id: 'profile', label: 'Perfil', icon: User },
|
||||
];
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ export const PlansView: React.FC<PlansViewProps> = ({ isUnauthenticated, onGetSt
|
||||
)}
|
||||
|
||||
{/* 2. PLANOS */}
|
||||
<section id="planos" className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 items-start">
|
||||
<section id="planos" className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6 items-start">
|
||||
<PlanCard
|
||||
title="Plano Begin"
|
||||
price="500 MZN"
|
||||
@@ -273,13 +273,31 @@ export const PlansView: React.FC<PlansViewProps> = ({ isUnauthenticated, onGetSt
|
||||
buttonText="Ser Elite"
|
||||
onClick={handleAction}
|
||||
features={[
|
||||
"Análises ilimitadas",
|
||||
"Prioridade total de processamento",
|
||||
"30 análises por dia",
|
||||
"Insights institucionais avançados",
|
||||
"Prioridade total de processamento",
|
||||
"Estratégias PRO Logic",
|
||||
"Suporte VIP via WhatsApp"
|
||||
]}
|
||||
/>
|
||||
<PlanCard
|
||||
title="Plano Lifetime"
|
||||
price="8.000 MT"
|
||||
description="O acesso definitivo para toda a vida."
|
||||
buttonText="Assinar Vitalício"
|
||||
highlight={true}
|
||||
badge="VIP VITALÍCIO"
|
||||
onClick={handleAction}
|
||||
features={[
|
||||
"Análises ilimitadas",
|
||||
"Insights institucionais avançados",
|
||||
"Acesso vitalício (Sem mensalidade)",
|
||||
"Suporte 1-on-1 (WhatsApp/Telegram)",
|
||||
"Prioridade máxima total",
|
||||
"Early access a novas features",
|
||||
"Cursos exclusivos QuantScan"
|
||||
]}
|
||||
/>
|
||||
</section>
|
||||
|
||||
{/* 3. BENEFÍCIOS PRINCIPAIS */}
|
||||
@@ -319,22 +337,24 @@ export const PlansView: React.FC<PlansViewProps> = ({ isUnauthenticated, onGetSt
|
||||
<th className="p-4 border-b border-white/5 text-center">Begin</th>
|
||||
<th className="p-4 border-b border-white/5 text-center text-brand-red">Pro</th>
|
||||
<th className="p-4 border-b border-white/5 text-center">Elite</th>
|
||||
<th className="p-4 border-b border-white/5 text-center">Lifetime</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="text-xs font-medium text-zinc-300">
|
||||
{[
|
||||
{ label: "Nº de Análises", free: "8 / dia", pro: "15 / dia", elite: "Ilimitado" },
|
||||
{ label: "Score de Probabilidade", free: "Básico", pro: "Avançado", elite: "Avançado+" },
|
||||
{ label: "IA Adaptativa", free: "Não", pro: "Sim", elite: "Sim" },
|
||||
{ label: "Histórico de Sinais", free: "Limitado", pro: "Completo", elite: "Completo" },
|
||||
{ label: "Suporte", free: "Comunidade", pro: "Prioritário", elite: "VIP Individual" },
|
||||
{ label: "Processamento", free: "Normal", pro: "Rápido", elite: "Ultra Prioridade" },
|
||||
{ label: "Nº de Análises", free: "8 / dia", pro: "15 / dia", elite: "30 / dia", lifetime: "Ilimitado" },
|
||||
{ label: "Score de Probabilidade", free: "Básico", pro: "Avançado", elite: "Avançado+", lifetime: "Avançado+" },
|
||||
{ label: "IA Adaptativa", free: "Não", pro: "Sim", elite: "Sim", lifetime: "Sim" },
|
||||
{ label: "Histórico de Sinais", free: "Limitado", pro: "Completo", elite: "Completo", lifetime: "Completo" },
|
||||
{ label: "Suporte", free: "Comunidade", pro: "Prioritário", elite: "VIP Individual", lifetime: "Premium 1-on-1" },
|
||||
{ label: "Processamento", free: "Normal", pro: "Rápido", elite: "Ultra Prioridade", lifetime: "Ultra Prioridade" },
|
||||
].map((row, idx) => (
|
||||
<tr key={idx} className="border-b border-white/5 hover:bg-white/5 transition-colors">
|
||||
<td className="p-4 font-bold text-white">{row.label}</td>
|
||||
<td className="p-4 text-center text-zinc-500">{row.free}</td>
|
||||
<td className="p-4 text-center font-bold text-white">{row.pro}</td>
|
||||
<td className="p-4 text-center text-zinc-300">{row.elite}</td>
|
||||
<td className="p-4 text-center text-brand-red font-bold">{row.lifetime}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
|
||||
@@ -17,14 +17,8 @@ export const SignalHistory: React.FC = () => {
|
||||
where('userId', '==', auth.currentUser.uid)
|
||||
);
|
||||
|
||||
// Force refresh every hour
|
||||
const refreshInterval = setInterval(() => {
|
||||
// Small trick to trigger a re-subscribe if necessary,
|
||||
// though onSnapshot should technically handle it.
|
||||
// This satisfies the explicit request to refresh hourly.
|
||||
console.log('Refreshing signal history...');
|
||||
}, 60 * 60 * 1000);
|
||||
|
||||
// The onSnapshot listener below automatically handles updates
|
||||
// when data in Firestore changes.
|
||||
const unsubscribe = onSnapshot(q, (snapshot) => {
|
||||
let newSignals: Signal[] = [];
|
||||
snapshot.forEach((doc) => {
|
||||
@@ -39,7 +33,6 @@ export const SignalHistory: React.FC = () => {
|
||||
});
|
||||
|
||||
return () => {
|
||||
clearInterval(refreshInterval);
|
||||
unsubscribe();
|
||||
};
|
||||
}, []);
|
||||
|
||||
Reference in New Issue
Block a user