AfriDataHubFrontend / src /components /DatasetAnalysis.jsx
rinogeek's picture
Update for deployment
62fe6d4
import { motion, AnimatePresence } from 'framer-motion'
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
BarChart, Bar, Legend
} from 'recharts'
import { Button } from '@/components/ui/button'
import {
Brain, TrendingUp, Lightbulb, Activity, Globe, Users, Zap, AlertTriangle
} from 'lucide-react'
// Mappage des icônes
const iconMap = {
trending: TrendingUp,
globe: Globe,
users: Users,
activity: Activity,
zap: Zap,
peak: Activity
}
const WidgetFactory = ({ widget, chartsData, themeColor }) => {
const Icon = iconMap[widget.icon] || Activity
switch (widget.type) {
case 'header_section':
return (
<div className="mb-12">
<h3 className="text-2xl md:text-3xl font-black text-foreground mb-6 tracking-tight">{widget.title}</h3>
<div className="prose prose-lg max-w-none text-muted-foreground leading-relaxed font-medium">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{widget.content}
</ReactMarkdown>
</div>
</div>
)
case 'kpi_grid':
return (
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mb-12">
{widget.items.map((item, idx) => {
const ItemIcon = iconMap[item.icon] || Activity
return (
<motion.div
key={idx}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.1 }}
className="glass p-6 md:p-8 rounded-[1.5rem] md:rounded-[2rem] shadow-xl flex items-center space-x-6 group hover:scale-105 transition-transform"
>
<div className={`p-5 rounded-2xl bg-muted shadow-inner group-hover:rotate-12 transition-transform`}>
<ItemIcon className={`h-8 w-8 ${item.color || 'text-primary'}`} />
</div>
<div>
<p className="text-xs text-muted-foreground font-black uppercase tracking-widest mb-1">{item.label}</p>
<p className="text-3xl font-black text-foreground">{item.value}</p>
</div>
</motion.div>
)
})}
</div>
)
case 'chart_section': {
const isEvolution = widget.chart_type === 'evolution'
const data = isEvolution ? chartsData.evolution : chartsData.top_countries
return (
<div className="glass p-6 md:p-10 rounded-[2rem] md:rounded-[3rem] shadow-xl mb-12 relative overflow-hidden">
<div className="relative z-10">
<h3 className="text-xl md:text-2xl font-black text-foreground mb-8 flex items-center">
<div className="p-3 rounded-xl bg-primary/10 mr-4">
{isEvolution ? <TrendingUp className="h-6 w-6 text-primary" /> : <Activity className="h-6 w-6 text-primary" />}
</div>
{widget.title}
</h3>
<div className="h-64 md:h-96 mb-8">
<ResponsiveContainer width="100%" height="100%">
{isEvolution ? (
<LineChart data={data}>
<defs>
<linearGradient id="lineGradient" x1="0" y1="0" x2="0" y2="1">
<stop offset="5%" stopColor={`var(--color-${themeColor})`} stopOpacity={0.3} />
<stop offset="95%" stopColor={`var(--color-${themeColor})`} stopOpacity={0} />
</linearGradient>
</defs>
<CartesianGrid strokeDasharray="3 3" stroke="currentColor" opacity={0.1} vertical={false} />
<XAxis dataKey="year" stroke="currentColor" opacity={0.5} fontSize={12} fontWeight="bold" />
<YAxis stroke="currentColor" opacity={0.5} fontSize={12} fontWeight="bold" />
<Tooltip
contentStyle={{ borderRadius: '20px', border: '1px solid var(--border)', backgroundColor: 'var(--card)', boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1)', padding: '15px' }}
itemStyle={{ fontWeight: 'bold' }}
labelStyle={{ color: 'var(--foreground)', fontWeight: 'black', marginBottom: '5px' }}
/>
<Line
type="monotone"
dataKey="value"
stroke={`var(--color-${themeColor})`}
strokeWidth={4}
dot={{ fill: `var(--color-${themeColor})`, strokeWidth: 3, r: 6 }}
activeDot={{ r: 10, strokeWidth: 0 }}
/>
</LineChart>
) : (
<BarChart data={data} layout="vertical">
<CartesianGrid strokeDasharray="3 3" stroke="currentColor" opacity={0.1} horizontal={false} />
<XAxis type="number" stroke="currentColor" opacity={0.5} fontSize={12} fontWeight="bold" />
<YAxis dataKey="country" type="category" width={120} stroke="currentColor" opacity={0.7} fontSize={12} fontWeight="bold" />
<Tooltip
contentStyle={{ borderRadius: '20px', border: '1px solid var(--border)', backgroundColor: 'var(--card)', boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1)', padding: '15px' }}
itemStyle={{ fontWeight: 'bold' }}
labelStyle={{ color: 'var(--foreground)', fontWeight: 'black', marginBottom: '5px' }}
/>
<Bar dataKey="value" fill={`var(--color-${themeColor})`} radius={[0, 10, 10, 0]} barSize={30} />
</BarChart>
)}
</ResponsiveContainer>
</div>
<div className="bg-muted/30 backdrop-blur-md p-6 rounded-2xl border border-border">
<p className="text-foreground font-medium italic leading-relaxed">
"{widget.commentary}"
</p>
</div>
</div>
</div>
)
}
case 'insight_cards':
return (
<div className="mb-12">
<h3 className="text-2xl font-black text-foreground mb-8">{widget.title}</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
{widget.items.map((insight, idx) => (
<motion.div
key={idx}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ delay: idx * 0.1 }}
className="glass p-6 md:p-8 rounded-[1.5rem] md:rounded-[2rem] shadow-lg border border-border group hover:bg-muted/40 transition-all"
>
<div className="flex flex-col space-y-4">
<div className="w-12 h-12 rounded-2xl bg-primary/10 flex items-center justify-center group-hover:scale-110 transition-transform">
<Lightbulb className={`h-6 w-6 text-primary`} />
</div>
<p className="text-foreground font-bold leading-relaxed">{insight}</p>
</div>
</motion.div>
))}
</div>
</div>
)
case 'text_section':
return (
<div className="relative overflow-hidden rounded-[2rem] md:rounded-[3rem] p-6 md:p-12 text-white shadow-2xl mb-12">
<div className="absolute inset-0 bg-[#166534] opacity-95"></div>
<div className="absolute inset-0 bg-mesh opacity-20"></div>
<div className="relative z-10">
<h3 className="text-xl md:text-3xl font-black mb-8 flex items-center">
<Brain className="h-8 w-8 mr-4 text-yellow-300" />
{widget.title}
</h3>
<div className="prose prose-invert max-w-none text-white/90 leading-relaxed text-lg md:text-xl font-medium">
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{widget.content}
</ReactMarkdown>
</div>
</div>
</div>
)
default:
return null
}
}
const DatasetAnalysis = ({ dataset, analysisData, onBack }) => {
const { analysis, charts_data } = analysisData
// Extraire la couleur du thème (par défaut green-700)
const themeColor = analysis.theme_color ? analysis.theme_color.split('-')[0] + '-600' : 'primary'
// Style CSS variable pour les graphiques
const style = {
[`--color-primary`]: '#166534',
[`--color-emerald-600`]: '#059669',
[`--color-blue-600`]: '#2563eb',
[`--color-red-600`]: '#dc2626',
[`--color-green-600`]: '#16a34a',
}
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
className="min-h-screen bg-background pb-20"
style={style}
>
{/* Header Premium avec Mesh Gradient */}
<div className="relative overflow-hidden pt-12 pb-16 md:pb-24 px-4 md:px-8">
<div className="absolute inset-0 bg-mesh opacity-30"></div>
<div className="absolute inset-0 bg-primary/5"></div>
<div className="max-w-7xl mx-auto relative z-10">
<Button
variant="ghost"
onClick={onBack}
className="rounded-xl hover:bg-primary/10 text-primary font-bold mb-12"
>
← Retour aux datasets
</Button>
<div className="flex flex-col md:flex-row md:items-end justify-between gap-12">
<div className="flex-1">
<div className="flex items-center space-x-3 mb-6">
<div className="px-4 py-2 bg-primary/10 rounded-full border border-primary/20 flex items-center">
<Brain className="h-4 w-4 text-primary mr-2" />
<span className="text-primary font-black uppercase tracking-widest text-xs">Intelligence Artificielle</span>
</div>
<div className="px-4 py-2 bg-secondary/10 rounded-full border border-secondary/20 flex items-center">
<Zap className="h-4 w-4 text-secondary mr-2" />
<span className="text-secondary font-black uppercase tracking-widest text-xs">Rapport Stratégique</span>
</div>
</div>
<h1 className="text-3xl md:text-6xl font-black text-foreground tracking-tight mb-6 leading-tight">
{analysis.report_title || dataset.title}
</h1>
<p className="text-lg md:text-2xl text-muted-foreground font-medium max-w-3xl leading-relaxed">
Analyse scientifique approfondie générée pour <span className="text-foreground font-black">{dataset.source_name}</span>.
</p>
</div>
<div className="hidden lg:block">
<div className="w-64 h-64 glass rounded-[3rem] flex items-center justify-center relative">
<div className="absolute inset-0 bg-primary/5 rounded-[3rem] animate-pulse"></div>
<Brain className="h-32 w-32 text-primary opacity-20" />
<div className="absolute -bottom-4 -right-4 bg-card shadow-2xl rounded-2xl p-4 border border-border">
<div className="flex items-center space-x-2">
<div className="w-3 h-3 rounded-full bg-emerald-500 animate-pulse"></div>
<span className="text-xs font-black uppercase tracking-tighter text-foreground">Gemini 2.5 Live</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div className="max-w-7xl mx-auto px-4 md:px-8 -mt-12">
{/* Rendu dynamique des widgets */}
{analysis.layout ? (
analysis.layout.map((widget, idx) => (
<WidgetFactory
key={idx}
widget={widget}
chartsData={charts_data}
themeColor={themeColor}
/>
))
) : (
<div className="glass rounded-[3rem] py-24 text-center">
<div className="w-20 h-20 bg-primary/10 rounded-full flex items-center justify-center mx-auto mb-6">
<AlertTriangle className="h-10 w-10 text-primary" />
</div>
<h3 className="text-2xl font-black text-foreground mb-2">Format non reconnu</h3>
<p className="text-muted-foreground font-medium">Veuillez régénérer l'analyse pour corriger ce problème.</p>
</div>
)}
</div>
</motion.div>
)
}
export default DatasetAnalysis