Spaces:
Sleeping
Sleeping
| 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 | |