gs-port / src /components /Indicators.jsx
Scribbler310's picture
Upload folder using huggingface_hub
0c5252a verified
import React, { useState } from 'react';
import { Activity, ShieldAlert, Info, X, PieChart, DollarSign, Target } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
export default function Indicators({ portfolio, isFlattened = false }) {
const [activeInfo, setActiveInfo] = useState(null); // 'health' or 'risk'
// A simple gauge visualization using SVG
const dashArray = 283; // 2 * pi * r (r=45)
const dashOffset = dashArray - (dashArray * portfolio.healthScore) / 100;
const content = (
<>
{/* Portfolio Health Score */}
<div
onClick={() => setActiveInfo('health')}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 flex items-center justify-between cursor-pointer hover:border-gs-gold transition-all group h-full"
>
<div>
<h3 className="text-sm text-gs-slate uppercase tracking-wider mb-1 font-medium flex items-center">
<Activity size={16} className="mr-2 text-gs-gold" /> Health Score
</h3>
<p className="text-3xl font-light text-gs-navy">
{portfolio.healthScore} <span className="text-base text-gray-400">/ 100</span>
</p>
<p className="text-sm text-gray-500 mt-2 font-light flex items-center group-hover:text-gs-gold transition-colors">
<Info size={14} className="mr-1" /> Click to see how this is calculated
</p>
</div>
<div className="relative w-24 h-24">
<svg className="w-full h-full transform -rotate-90" viewBox="0 0 100 100">
<circle cx="50" cy="50" r="45" fill="none" stroke="#f3f4f6" strokeWidth="8" />
<circle
cx="50" cy="50" r="45" fill="none"
stroke="#0B233F" strokeWidth="8"
strokeDasharray={dashArray} strokeDashoffset={dashOffset}
strokeLinecap="round"
className="transition-all duration-1000 ease-out"
/>
</svg>
</div>
</div>
{/* Risk Meter */}
<div
onClick={() => setActiveInfo('risk')}
className="bg-white rounded-2xl p-6 shadow-sm border border-gray-100 flex flex-col justify-center cursor-pointer hover:border-gs-gold transition-all group h-full"
>
<h3 className="text-sm text-gs-slate uppercase tracking-wider mb-4 font-medium flex items-center">
<ShieldAlert size={16} className="mr-2 text-gs-gold" /> Risk Level
</h3>
<div className="flex w-full h-3 bg-gray-100 rounded-full overflow-hidden mb-3">
<div className={`h-full ${portfolio.riskLevel === 'Low' ? 'bg-gs-navy w-1/3' : portfolio.riskLevel === 'Medium' ? 'bg-gs-gold w-2/3' : 'bg-red-500 w-full'} transition-all duration-500`}></div>
</div>
<div className="flex justify-between text-xs text-gray-400 font-medium uppercase mb-2">
<span className={portfolio.riskLevel === 'Low' ? 'text-gs-navy font-bold' : ''}>Low</span>
<span className={portfolio.riskLevel === 'Medium' ? 'text-gs-gold font-bold' : ''}>Medium</span>
<span className={portfolio.riskLevel === 'High' ? 'text-red-500 font-bold' : ''}>High</span>
</div>
<p className="text-xs text-gray-400 font-light flex items-center group-hover:text-gs-gold transition-colors">
<Info size={12} className="mr-1" /> How we measure risk
</p>
</div>
{/* Calculation Modal */}
<AnimatePresence>
{activeInfo && (
<div className="fixed inset-0 z-[60] flex items-center justify-center p-4">
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setActiveInfo(null)}
className="absolute inset-0 bg-gs-navy/40 backdrop-blur-sm"
/>
<motion.div
initial={{ opacity: 0, scale: 0.9, y: 20 }}
animate={{ opacity: 1, scale: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.9, y: 20 }}
className="relative bg-white rounded-3xl shadow-2xl w-full max-w-md overflow-hidden"
>
<div className="p-8">
<div className="flex justify-between items-start mb-6">
<div className="flex items-center">
<div className="p-3 bg-gs-light rounded-xl mr-4 text-gs-gold">
{activeInfo === 'health' ? <Activity size={24} /> : <ShieldAlert size={24} />}
</div>
<div>
<h4 className="text-xl font-semibold text-gs-navy">
{activeInfo === 'health' ? 'Health Score Logic' : 'Risk Calculation'}
</h4>
<p className="text-sm text-gs-slate font-light">Radical Transparency Report</p>
</div>
</div>
<button onClick={() => setActiveInfo(null)} className="text-gray-400 hover:text-gs-navy transition-colors">
<X size={20} />
</button>
</div>
<div className="space-y-6">
{activeInfo === 'health' ? (
<>
<div className="flex gap-4">
<div className="mt-1 text-gs-gold"><DollarSign size={18} /></div>
<div>
<p className="font-medium text-gs-navy text-sm">Fee Efficiency</p>
<p className="text-xs text-gs-slate font-light leading-relaxed">We subtract points for high expense ratios. Every 0.1% in fees costs your portfolio health (Max -20 pts).</p>
</div>
</div>
<div className="flex gap-4">
<div className="mt-1 text-gs-gold"><PieChart size={18} /></div>
<div>
<p className="font-medium text-gs-navy text-sm">Diversification Bonus</p>
<p className="text-xs text-gs-slate font-light leading-relaxed">Holding 5+ different asset classes grants a +5 point bonus for reduced concentration risk.</p>
</div>
</div>
<div className="flex gap-4">
<div className="mt-1 text-gs-gold"><Target size={18} /></div>
<div>
<p className="font-medium text-gs-navy text-sm">Profile Alignment</p>
<p className="text-xs text-gs-slate font-light leading-relaxed">If your portfolio's actual volatility doesn't match your goal (e.g. Cautious vs Balanced), we subtract 15 points.</p>
</div>
</div>
</>
) : (
<div className="bg-gs-light p-5 rounded-2xl border border-gs-gold/20">
<p className="text-sm text-gs-navy font-medium mb-2">Market Sensitivity Logic</p>
<p className="text-xs text-gs-slate font-light leading-relaxed mb-4">
Risk isn't a guess. We measure how much your portfolio typically swings compared to the broader market to categorize your risk level.
</p>
<div className="bg-white/80 p-3 rounded-xl border border-gs-gold/10 mb-4 flex justify-between items-center shadow-sm">
<span className="text-[10px] uppercase tracking-widest text-gs-slate font-bold">Portfolio Beta</span>
<span className="text-xl font-bold text-gs-navy">{portfolio.avgBeta}</span>
</div>
<div className="space-y-2">
<div className="flex justify-between text-[10px] uppercase tracking-wider text-gs-slate font-bold">
<span>Low Risk</span>
<span className="text-gs-navy">Minimal Volatility</span>
</div>
<div className="flex justify-between text-[10px] uppercase tracking-wider text-gs-slate font-bold">
<span>Medium Risk</span>
<span className="text-gs-gold">Market Standard</span>
</div>
<div className="flex justify-between text-[10px] uppercase tracking-wider text-gs-slate font-bold">
<span>High Risk</span>
<span className="text-red-500">Aggressive Growth</span>
</div>
</div>
</div>
)}
</div>
<button
onClick={() => setActiveInfo(null)}
className="w-full mt-8 bg-gs-navy text-white py-4 rounded-xl font-medium hover:bg-gs-navy/90 transition-colors shadow-md"
>
Got it, thanks!
</button>
</div>
</motion.div>
</div>
)}
</AnimatePresence>
</>
);
return isFlattened ? content : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 relative">
{content}
</div>
);
}