Files
digicraft-fe/components/NeuroScorecard.tsx
Fahri Can Seçer 6e3bee17ef
Some checks failed
Deploy Frontend / deploy (push) Has been cancelled
main
2026-02-05 01:34:13 +03:00

260 lines
12 KiB
TypeScript

import React, { useState } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Sparkles, Brain, Zap, Heart, Eye, TrendingUp, AlertTriangle, CheckCircle } from 'lucide-react';
interface NeuroScoreProps {
analysis: {
scores: {
dopamine: number;
serotonin: number;
cognitiveEase: number;
commercialFit: number;
};
feedback: string[];
improvements: {
dopamine: string[];
serotonin: string[];
cognitiveEase: string[];
commercialFit: string[];
};
prediction: string;
} | null;
loading?: boolean;
onApplyImprovement?: (suggestion: string) => void;
}
const ScoreBar = ({ label, score, icon: Icon, color, barColor, onClick, isSelected }: { label: string; score: number; icon: any; color: string; barColor: string, onClick?: () => void, isSelected?: boolean }) => (
<div
onClick={onClick}
className={`mb-4 transition-all duration-300 ${onClick ? 'cursor-pointer hover:bg-white/5 p-2 -mx-2 rounded-lg' : ''} ${isSelected ? 'bg-white/10 ring-1 ring-white/20' : ''}`}
>
<div className="flex justify-between items-center mb-1">
<div className="flex items-center gap-2 text-stone-300">
<Icon className={`w-4 h-4 ${color}`} />
<span className="text-sm font-medium">{label}</span>
</div>
<span className={`text-sm font-bold ${score >= 8 ? 'text-green-400' : score >= 5 ? 'text-yellow-400' : 'text-red-400'}`}>
{score}/10
</span>
</div>
<div className="h-2 bg-stone-800/50 rounded-full overflow-hidden backdrop-blur-sm border border-white/5">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${score * 10}%` }}
transition={{ duration: 1, ease: "easeOut" }}
className={`h-full ${barColor}`}
/>
</div>
{isSelected && (
<motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} className="text-[10px] text-stone-400 mt-1 italic text-center">
Viewing fixes for this metric...
</motion.div>
)}
</div>
);
const NeuroScorecard: React.FC<NeuroScoreProps> = ({ analysis, loading, onApplyImprovement }) => {
const [selectedMetric, setSelectedMetric] = useState<string | null>(null);
// Get fixes based on selection or aggregate all
const currentImprovements = React.useMemo(() => {
if (!analysis?.improvements) return [];
// Map UI labels to API keys
const keyMap: Record<string, keyof typeof analysis.improvements> = {
'Dopamine': 'dopamine',
'Serotonin': 'serotonin',
'Cognitive Ease': 'cognitiveEase',
'Commercial Fit': 'commercialFit'
};
if (selectedMetric && keyMap[selectedMetric]) {
return analysis.improvements[keyMap[selectedMetric]] || [];
}
// Return all improvements flattened if nothing selected
return [
...analysis.improvements.dopamine,
...analysis.improvements.serotonin,
...analysis.improvements.cognitiveEase,
...analysis.improvements.commercialFit
];
}, [analysis, selectedMetric]);
if (loading) {
return (
<div className="p-8 rounded-2xl bg-white/5 backdrop-blur-xl border border-white/10 flex flex-col items-center justify-center text-center min-h-[400px]">
<motion.div
animate={{ rotate: 360 }}
transition={{ duration: 2, repeat: Infinity, ease: "linear" }}
className="mb-6 relative"
>
<div className="absolute inset-0 bg-blue-500/20 blur-xl rounded-full" />
<Brain className="w-16 h-16 text-blue-400 relative z-10" />
</motion.div>
<h3 className="text-xl font-bold text-white mb-2">Analyzing Neuro-Triggers...</h3>
<p className="text-stone-400 max-w-xs">Connecting to Gemini Vision to evaluate Dopamine, Serotonin, and Market Fit.</p>
<div className="mt-8 flex gap-2">
{[0, 1, 2].map((i) => (
<motion.div
key={i}
animate={{ scale: [1, 1.5, 1], opacity: [0.3, 1, 0.3] }}
transition={{ duration: 1, repeat: Infinity, delay: i * 0.2 }}
className="w-2 h-2 rounded-full bg-blue-400"
/>
))}
</div>
</div>
);
}
if (!analysis) return null;
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
className="bg-stone-900/40 backdrop-blur-2xl border border-white/10 rounded-2xl overflow-hidden shadow-2xl"
>
{/* Header */}
<div className="p-6 border-b border-white/5 bg-gradient-to-r from-blue-500/10 to-purple-500/10">
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<div className="p-2 bg-blue-500/20 rounded-lg">
<Brain className="w-6 h-6 text-blue-400" />
</div>
<div>
<h2 className="text-xl font-bold text-white">Neuro-Scorecard</h2>
<p className="text-xs text-stone-400">AI-Predicted Market Performance</p>
</div>
</div>
<div className={`px-4 py-1.5 rounded-full border ${analysis.prediction.includes('High') ? 'bg-green-500/20 border-green-500/30 text-green-400' :
analysis.prediction.includes('Medium') ? 'bg-yellow-500/20 border-yellow-500/30 text-yellow-400' :
'bg-red-500/20 border-red-500/30 text-red-400'
} text-sm font-semibold flex items-center gap-2`}>
<TrendingUp className="w-4 h-4" />
{analysis.prediction}
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-0 divide-y md:divide-y-0 md:divide-x divide-white/10">
{/* Column 1: Scores */}
<div className="p-6 bg-black/20">
<h3 className="text-sm font-bold text-white/50 uppercase tracking-widest mb-6 flex justify-between items-center">
Vital Signs
{selectedMetric && (
<button onClick={() => setSelectedMetric(null)} className="text-[10px] text-blue-400 hover:underline">
Show All
</button>
)}
</h3>
<ScoreBar
label="Dopamine Hit (Excitement)"
score={analysis.scores.dopamine}
icon={Zap}
color="text-yellow-400"
barColor="bg-yellow-400"
onClick={() => setSelectedMetric('Dopamine')}
isSelected={selectedMetric === 'Dopamine'}
/>
<ScoreBar
label="Serotonin Flow (Trust/Calm)"
score={analysis.scores.serotonin}
icon={Heart}
color="text-pink-400"
barColor="bg-pink-400"
onClick={() => setSelectedMetric('Serotonin')}
isSelected={selectedMetric === 'Serotonin'}
/>
<ScoreBar
label="Cognitive Ease (Clarity)"
score={analysis.scores.cognitiveEase}
icon={Eye}
color="text-blue-400"
barColor="bg-blue-400"
onClick={() => setSelectedMetric('Cognitive Ease')}
isSelected={selectedMetric === 'Cognitive Ease'}
/>
<ScoreBar
label="Commercial Fit (Pro Quality)"
score={analysis.scores.commercialFit}
icon={Sparkles}
color="text-purple-400"
barColor="bg-purple-400"
onClick={() => setSelectedMetric('Commercial Fit')}
isSelected={selectedMetric === 'Commercial Fit'}
/>
</div>
{/* Column 2: Improvements */}
<div className="p-6 relative">
<h3 className="text-sm font-bold text-red-400/80 uppercase tracking-widest mb-6 flex items-center gap-2">
<AlertTriangle className="w-4 h-4" />
{selectedMetric ? `${selectedMetric} Fixes` : 'Critical Fixes'}
</h3>
<ul className="space-y-4 max-h-[300px] overflow-y-auto pr-2 scrollbar-thin scrollbar-thumb-white/10">
{currentImprovements.length > 0 ? (
currentImprovements.map((fix, idx) => (
<motion.li
key={idx}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: idx * 0.1 }}
className="flex items-start gap-3 text-stone-300 text-sm group"
>
<div className="w-1.5 h-1.5 rounded-full bg-red-400 mt-1.5 flex-shrink-0 group-hover:scale-150 transition-transform" />
<div className="flex-1">
<span className="leading-relaxed block mb-2">{fix}</span>
{onApplyImprovement && (
<button
onClick={() => onApplyImprovement(fix)}
className="text-[10px] bg-red-500/10 hover:bg-red-500/20 text-red-300 px-2 py-1 rounded border border-red-500/20 transition-colors uppercase tracking-wider font-bold"
>
+ Add to Refine
</button>
)}
</div>
</motion.li>
))
) : (
<div className="text-center py-8">
<p className="text-stone-500 text-xs italic mb-4">
No specific fixes found for this metric.
</p>
</div>
)}
</ul>
</div>
{/* Column 3: Feedback */}
<div className="p-6 bg-white/2">
<h3 className="text-sm font-bold text-green-400/80 uppercase tracking-widest mb-6 flex items-center gap-2">
<CheckCircle className="w-4 h-4" />
Winning Traits
</h3>
<ul className="space-y-4">
{analysis.feedback.map((point, idx) => (
<motion.li
key={idx}
initial={{ opacity: 0, x: -10 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: idx * 0.1 + 0.2 }}
className="flex items-start gap-3 text-stone-300 text-sm"
>
<div className="w-1.5 h-1.5 rounded-full bg-green-400 mt-1.5 flex-shrink-0" />
<span className="leading-relaxed">{point}</span>
</motion.li>
))}
</ul>
</div>
</div>
</motion.div>
);
};
export default NeuroScorecard;