aibanking.dev / views /Overview.tsx
admin08077's picture
Upload 26 files
b8b3edf verified
import React, { useMemo, useState, useEffect } from 'react';
/* Fix: react-router-dom exports may be flaky in this environment, using standard v6 imports */
import { useNavigate } from 'react-router-dom';
import {
AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer
} from 'recharts';
import {
ArrowUpRight,
ArrowDownRight,
Wallet,
Zap,
Activity,
Globe,
ShieldCheck,
ChevronRight,
Terminal,
ShieldAlert,
Building2,
RefreshCw,
Sparkles,
TrendingUp,
Shield,
Loader2
} from 'lucide-react';
/* Fix: removed .ts extensions from imports */
import { getSystemIntelligenceFeed, getPortfolioSuggestions } from '../services/geminiService';
import { apiClient } from '../services/api';
import { cryptoService } from '../services/cryptoService';
import { InternalAccount, AIInsight } from '../types/index';
import { CardSkeleton, Skeleton } from '../components/Skeleton';
const MOCK_BALANCE_HISTORY = {
'24H': [
{ time: '00:00', balance: 1245000 },
{ time: '04:00', balance: 1247000 },
{ time: '08:00', balance: 1246000 },
{ time: '12:00', balance: 1248000 },
{ time: '16:00', balance: 1249103 },
{ time: '20:00', balance: 1251200 },
],
'7D': [
{ time: 'Mon', balance: 1100000 },
{ time: 'Tue', balance: 1150000 },
{ time: 'Wed', balance: 1240000 },
{ time: 'Thu', balance: 1220000 },
{ time: 'Fri', balance: 1250000 },
{ time: 'Sat', balance: 1260000 },
{ time: 'Sun', balance: 1251200 },
]
};
const StatCard = ({ label, value, trend, icon: Icon, color, subValue, onClick, loading }: any) => {
if (loading) return <CardSkeleton />;
return (
<div
onClick={onClick}
className="bg-zinc-950 border border-zinc-900 p-8 rounded-[2rem] hover:border-blue-500/30 transition-all group relative overflow-hidden shadow-2xl cursor-pointer"
>
<div className="absolute top-0 right-0 p-4 opacity-[0.03] group-hover:opacity-10 transition-opacity">
<Icon size={120} />
</div>
<div className="flex justify-between items-start mb-6 relative z-10">
<div className={`p-4 rounded-2xl bg-${color}-500/10 text-${color}-500 group-hover:scale-110 transition-transform duration-500`}>
<Icon size={24} />
</div>
<div className={`flex items-center space-x-1.5 px-3 py-1 rounded-full ${trend >= 0 ? 'bg-emerald-500/10 text-emerald-500' : 'bg-rose-500/10 text-rose-500'} text-[10px] font-black uppercase tracking-widest`}>
{trend >= 0 ? <ArrowUpRight size={12} /> : <ArrowDownRight size={12} />}
<span>{Math.abs(trend)}%</span>
</div>
</div>
<div className="relative z-10">
<p className="text-zinc-500 text-[10px] font-black uppercase tracking-[0.2em] mb-2">{label}</p>
<h3 className="text-3xl font-bold text-white mono tracking-tighter mb-1">{value}</h3>
{subValue && <p className="text-[10px] text-zinc-600 font-bold uppercase tracking-widest">{subValue}</p>}
</div>
</div>
);
};
const RecommendationCard = ({ suggestion, loading }: any) => {
if (loading) return <Skeleton className="h-40 rounded-[2.5rem]" />;
const icons = {
ALPHA: <TrendingUp size={20} className="text-blue-400" />,
RISK: <Shield size={20} className="text-rose-400" />,
LIQUIDITY: <Zap size={20} className="text-emerald-400" />
};
const borderColors = {
ALPHA: 'hover:border-blue-500/40',
RISK: 'hover:border-rose-500/40',
LIQUIDITY: 'hover:border-emerald-500/40'
};
return (
<div className={`bg-zinc-950 border border-zinc-900 p-8 rounded-[2.5rem] transition-all group relative overflow-hidden shadow-2xl ${borderColors[suggestion.type as keyof typeof borderColors] || 'hover:border-zinc-700'}`}>
<div className="flex items-center justify-between mb-6">
<div className="p-3 bg-zinc-900 rounded-xl">
{icons[suggestion.type as keyof typeof icons]}
</div>
<span className="text-[8px] font-black uppercase tracking-widest text-zinc-600 bg-black px-2 py-1 rounded border border-zinc-800">{suggestion.type}</span>
</div>
<h4 className="text-sm font-black text-white uppercase italic tracking-widest mb-2 truncate pr-4">{suggestion.title}</h4>
<p className="text-[11px] text-zinc-500 leading-relaxed font-medium group-hover:text-zinc-300 transition-colors">"{suggestion.description}"</p>
<div className="mt-6 flex justify-end">
<button className="text-[9px] font-black text-blue-500 uppercase tracking-widest hover:text-white transition-colors flex items-center gap-1.5">
Execute Node <ChevronRight size={10} />
</button>
</div>
</div>
);
};
const Overview: React.FC = () => {
const navigate = useNavigate();
const [activeRange, setActiveRange] = useState<keyof typeof MOCK_BALANCE_HISTORY>('24H');
const [intelFeed, setIntelFeed] = useState<AIInsight[]>([]);
const [bankingAccounts, setBankingAccounts] = useState<InternalAccount[]>([]);
const [suggestions, setSuggestions] = useState<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [loadingSuggestions, setLoadingSuggestions] = useState(true);
const [error, setError] = useState<string | null>(null);
const loadData = async () => {
setIsLoading(true);
setError(null);
try {
const [accounts, intel, globalMarket] = await Promise.all([
apiClient.getRegistryNodes(),
getSystemIntelligenceFeed(),
cryptoService.getGlobal()
]);
setBankingAccounts(accounts);
setIntelFeed(intel);
// Trigger proactive suggestions based on gathered context
refreshSuggestions(accounts, globalMarket);
} catch (e: any) {
console.error(e);
setError("Synchronous parity check failed. Using fallback node.");
setBankingAccounts([{
id: 'fallback_01',
productName: 'Emergency Parity Node',
displayAccountNumber: '•••• 0000',
currency: 'USD',
status: 'ACTIVE',
currentBalance: 0,
availableBalance: 0,
institutionName: 'Local Ledger',
connectionId: 'LOC-001'
}]);
} finally {
setIsLoading(false);
}
};
const refreshSuggestions = async (accounts: InternalAccount[], globalMarket: any) => {
setLoadingSuggestions(true);
try {
const context = {
totalLiquidity: accounts.reduce((s, a) => s + a.availableBalance, 0),
activeNodes: accounts.length,
globalMarketCap: globalMarket?.total_market_cap?.usd
};
const strategicRecs = await getPortfolioSuggestions(context);
setSuggestions(strategicRecs);
} catch (err) {
console.error("Neural strategist link failure.");
} finally {
setLoadingSuggestions(false);
}
};
useEffect(() => {
loadData();
}, []);
const totalLiquidity = useMemo(() => {
return bankingAccounts.reduce((sum, acc) => sum + acc.availableBalance, 0);
}, [bankingAccounts]);
return (
<div className="space-y-10 animate-in fade-in duration-700 pb-20">
{error && (
<div className="bg-rose-500/10 border border-rose-500/20 p-4 rounded-2xl flex items-center gap-4 text-rose-500 text-xs font-bold uppercase tracking-widest">
<ShieldAlert size={16} />
{error}
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
<StatCard
loading={isLoading}
label="Total Liquidity"
value={`$${totalLiquidity.toLocaleString()}`}
trend={12.4}
icon={Wallet}
color="blue"
subValue={`${bankingAccounts.length} Registry Nodes Active`}
onClick={() => navigate('/registry')}
/>
<StatCard loading={isLoading} label="Network Health" value="99.98%" trend={0.01} icon={ShieldCheck} color="emerald" subValue="Handshake Integrity: High" />
<StatCard loading={isLoading} label="Fabric Load" value="1.2 P/s" trend={0.5} icon={Zap} color="blue" subValue="Subspace Polling Active" />
<StatCard loading={isLoading} label="Deficit Offset" value="1,242 Kg" trend={-4.2} icon={Globe} color="rose" subValue="ESG Neutrality Level: AA" />
</div>
{/* Neural Strategy Center Section */}
<div className="bg-zinc-950/40 border border-zinc-900 rounded-[3rem] p-10 shadow-2xl relative overflow-hidden">
<div className="absolute top-0 right-0 p-10 opacity-[0.03]">
<Sparkles size={200} className="text-blue-500" />
</div>
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-10 gap-6 relative z-10">
<div>
<h2 className="text-2xl font-black text-white italic tracking-tighter uppercase mb-2">Neural <span className="text-blue-500 not-italic">Strategy Center</span></h2>
<p className="text-zinc-500 text-[10px] font-bold uppercase tracking-widest">Actionable alpha derived from treasury mesh telemetry</p>
</div>
<button
onClick={() => refreshSuggestions(bankingAccounts, null)}
disabled={loadingSuggestions}
className="flex items-center gap-3 px-6 py-3 bg-zinc-900 border border-zinc-800 hover:border-zinc-700 text-white rounded-2xl font-black text-[10px] uppercase tracking-widest transition-all"
>
{loadingSuggestions ? <Loader2 size={14} className="animate-spin" /> : <RefreshCw size={14} />}
<span>Recalculate Alpha</span>
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 relative z-10">
{loadingSuggestions ? (
Array.from({ length: 3 }).map((_, i) => <Skeleton key={i} className="h-44 rounded-[2.5rem]" />)
) : (
suggestions.map((s, idx) => (
<RecommendationCard key={idx} suggestion={s} loading={loadingSuggestions} />
))
)}
</div>
</div>
<div className="grid grid-cols-12 gap-8">
<div className="col-span-12 lg:col-span-8 space-y-8 min-w-0">
<div className="bg-zinc-950 border border-zinc-900 rounded-[2.5rem] p-10 relative overflow-hidden shadow-2xl">
<div className="flex flex-col md:flex-row justify-between items-start md:items-center mb-10 relative z-10 gap-6">
<div>
<h2 className="text-2xl font-black text-white italic tracking-tighter uppercase mb-2">Aggregate <span className="text-blue-500 not-italic">Cash Curve</span></h2>
<div className="flex items-center gap-3">
<span className={`w-2 h-2 rounded-full ${isLoading ? 'bg-zinc-800' : 'bg-emerald-500'} animate-pulse`}></span>
<p className="text-zinc-500 text-[10px] font-bold uppercase tracking-widest">Live Node Trace</p>
</div>
</div>
<div className="flex bg-black p-1.5 rounded-2xl border border-zinc-900">
{(Object.keys(MOCK_BALANCE_HISTORY) as Array<keyof typeof MOCK_BALANCE_HISTORY>).map(t => (
<button
key={t}
onClick={() => setActiveRange(t)}
className={`px-6 py-2 rounded-xl text-[10px] font-black transition-all uppercase tracking-widest ${t === activeRange ? 'bg-zinc-100 text-black shadow-lg' : 'text-zinc-500 hover:text-zinc-300'}`}
>
{t}
</button>
))}
</div>
</div>
<div className="w-full relative z-10 min-h-[400px] h-[400px]">
{isLoading ? <Skeleton className="w-full h-full rounded-2xl" /> : (
<ResponsiveContainer width="100%" height="100%">
<AreaChart data={MOCK_BALANCE_HISTORY[activeRange]}>
<defs>
<linearGradient id="colorBalance" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor="#3b82f6" stopOpacity={0.2}/>
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0}/>
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="#18181b" vertical={false} />
<XAxis dataKey="time" stroke="#3f3f46" fontSize={10} tickLine={false} axisLine={false} tick={{ fontWeight: 800 }} />
<YAxis stroke="#3f3f46" fontSize={10} tickLine={false} axisLine={false} tickFormatter={(v) => `$${v/1000}k`} tick={{ fontWeight: 800 }} />
<Tooltip
contentStyle={{ backgroundColor: '#000', border: '1px solid #27272a', borderRadius: '16px', padding: '12px' }}
itemStyle={{ color: '#3b82f6', fontWeight: 900, fontSize: '12px' }}
/>
<Area type="monotone" dataKey="balance" stroke="#3b82f6" strokeWidth={4} fillOpacity={1} fill="url(#colorBalance)" animationDuration={1000} />
</AreaChart>
</ResponsiveContainer>
)}
</div>
</div>
<div className="bg-zinc-950 border border-zinc-900 rounded-[2.5rem] p-10 shadow-2xl">
<div className="flex justify-between items-center mb-8">
<h3 className="text-xl font-black text-white italic tracking-tighter uppercase">Registry <span className="text-blue-500 not-italic">Nodes</span></h3>
<button onClick={loadData} className="p-3 bg-zinc-900 rounded-xl text-zinc-500 hover:text-white transition-all">
<RefreshCw size={16} className={isLoading ? 'animate-spin' : ''} />
</button>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{isLoading ? (
<>
<Skeleton className="h-32 rounded-[2rem]" />
<Skeleton className="h-32 rounded-[2rem]" />
</>
) : (
bankingAccounts.map(acc => (
<div key={acc.id} className="p-8 bg-black/40 border border-zinc-900 rounded-[2rem] hover:border-blue-500/20 transition-all group">
<div className="flex justify-between items-start mb-6">
<div className="p-3 bg-blue-600/10 text-blue-500 rounded-xl group-hover:scale-110 transition-transform">
<Building2 size={20} />
</div>
<span className="text-[9px] font-black uppercase text-emerald-500 bg-emerald-500/5 px-2.5 py-1 rounded-full border border-emerald-500/10">Active Node</span>
</div>
<h4 className="text-white font-black text-sm uppercase italic mb-1">{acc.productName}</h4>
<p className="text-[10px] text-zinc-600 font-bold uppercase tracking-widest mb-4">{acc.displayAccountNumber}</p>
<div className="flex justify-between items-baseline">
<p className="text-[9px] font-black text-zinc-500 uppercase tracking-widest">Liquidity</p>
<p className="text-xl font-black text-white mono tracking-tighter">${acc.availableBalance.toLocaleString()}</p>
</div>
</div>
))
)}
</div>
</div>
</div>
<div className="col-span-12 lg:col-span-4 space-y-8 min-w-0">
<div className="bg-zinc-950 border border-zinc-900 rounded-[2.5rem] p-10 flex flex-col h-full shadow-2xl">
<div className="flex items-center justify-between mb-8">
<div>
<h2 className="text-xl font-black text-white italic tracking-tighter uppercase">Quantum <span className="text-blue-500 not-italic">Intel</span></h2>
<p className="text-zinc-500 text-[10px] font-bold uppercase tracking-[0.2em]">Neural Feedback Stream</p>
</div>
<Terminal size={18} className="text-zinc-700" />
</div>
<div className="space-y-6 flex-1 overflow-y-auto pr-2 custom-scrollbar">
{isLoading ? (
Array.from({ length: 4 }).map((_, i) => <Skeleton key={i} className="h-20 rounded-2xl" />)
) : (
intelFeed.map((intel, idx) => (
<div key={idx} className="p-5 bg-black/40 border border-zinc-900 rounded-2xl hover:border-blue-500/30 transition-all group/item">
<div className="flex items-start gap-4">
{intel.severity === 'CRITICAL' ? <ShieldAlert size={16} className="text-rose-500 mt-1" /> : <Activity size={16} className="text-blue-500 mt-1" />}
<div>
<p className="text-[10px] font-black text-white uppercase tracking-widest mb-1">{intel.title}</p>
<p className="text-[11px] text-zinc-500 leading-relaxed group-hover/item:text-zinc-300 transition-colors">{intel.description}</p>
</div>
</div>
</div>
))
)}
</div>
<button onClick={() => navigate('/advisor')} className="mt-8 w-full py-5 bg-zinc-900 hover:bg-zinc-100 hover:text-black text-white rounded-[1.5rem] text-[10px] font-black uppercase tracking-[0.3em] transition-all border border-zinc-800 flex items-center justify-center gap-4">
<ChevronRight size={18} />
<span>Deep Advisory</span>
</button>
</div>
</div>
</div>
</div>
);
};
export default Overview;