Spaces:
Sleeping
Sleeping
| "use client" | |
| import React, { useEffect, useState } from 'react'; | |
| import { motion } from 'framer-motion'; | |
| import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; | |
| import { BarChart, X, Users, Activity, Loader2 } from 'lucide-react'; | |
| import { Button } from '@/components/ui/button'; | |
| interface DistributionChartProps { | |
| onClose: () => void; | |
| } | |
| interface StatsData { | |
| total_patients: number; | |
| avg_age: number; | |
| gender_distribution: { Male: number; Female: number }; | |
| age_bins?: { bin: string; count: number }[]; // We might need to fetch this or simulate | |
| } | |
| export default function DistributionChart({ onClose }: DistributionChartProps) { | |
| const [stats, setStats] = useState<StatsData | null>(null); | |
| const [loading, setLoading] = useState(true); | |
| useEffect(() => { | |
| const fetchStats = async () => { | |
| try { | |
| // In a real app we'd fetch histogram data | |
| // For now reusing /stats and simulating bins for demo | |
| const endpoint = process.env.NEXT_PUBLIC_API_URL || '/api'; | |
| const res = await fetch(`${endpoint}/stats`); | |
| if (res.ok) { | |
| const data = await res.json(); | |
| setStats(data); | |
| } | |
| } catch (error) { | |
| console.error(error); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| fetchStats(); | |
| }, []); | |
| const maxCount = stats ? Math.max(stats.gender_distribution.Male, stats.gender_distribution.Female) : 100; | |
| return ( | |
| <motion.div | |
| initial={{ opacity: 0, scale: 0.95 }} | |
| animate={{ opacity: 1, scale: 1 }} | |
| exit={{ opacity: 0, scale: 0.95 }} | |
| className="fixed inset-0 z-50 flex items-center justify-center bg-black/80 backdrop-blur-sm p-4" | |
| > | |
| <Card className="w-full max-w-4xl bg-slate-900 border-slate-700 shadow-2xl overflow-hidden"> | |
| <CardHeader className="flex flex-row items-center justify-between border-b border-slate-800 bg-slate-900/50"> | |
| <CardTitle className="text-xl font-bold text-white flex items-center gap-2"> | |
| <Users className="h-6 w-6 text-cyan-500" /> | |
| Patient Distribution Analysis | |
| </CardTitle> | |
| <Button variant="ghost" size="icon" onClick={onClose} className="hover:bg-slate-800 text-slate-400"> | |
| <X className="h-5 w-5" /> | |
| </Button> | |
| </CardHeader> | |
| <CardContent className="p-6"> | |
| {loading ? ( | |
| <div className="flex justify-center py-20"> | |
| <Loader2 className="h-12 w-12 animate-spin text-cyan-500" /> | |
| </div> | |
| ) : stats ? ( | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-8"> | |
| {/* Summary Stats */} | |
| <div className="space-y-6"> | |
| <div className="grid grid-cols-2 gap-4"> | |
| <div className="p-4 rounded-xl bg-slate-800/50 border border-slate-700"> | |
| <div className="text-sm text-slate-400 mb-1">Total Patients</div> | |
| <div className="text-3xl font-bold text-white">{stats.total_patients.toLocaleString()}</div> | |
| </div> | |
| <div className="p-4 rounded-xl bg-slate-800/50 border border-slate-700"> | |
| <div className="text-sm text-slate-400 mb-1">Average Age</div> | |
| <div className="text-3xl font-bold text-cyan-400">{stats.avg_age} <span className="text-sm font-normal text-slate-500">years</span></div> | |
| </div> | |
| </div> | |
| <div className="p-6 rounded-xl bg-slate-800/30 border border-slate-700/50"> | |
| <h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2"> | |
| <Activity className="h-5 w-5 text-purple-400" /> | |
| Data Overview | |
| </h3> | |
| <p className="text-sm text-slate-400 leading-relaxed"> | |
| The dataset covers {stats.total_patients.toLocaleString()} distinct ICU stays. | |
| Analysis shows a predominance of {stats.gender_distribution.Male > stats.gender_distribution.Female ? 'male' : 'female'} patients | |
| ({Math.round(stats.gender_distribution.Male / stats.total_patients * 100)}% vs {Math.round(stats.gender_distribution.Female / stats.total_patients * 100)}%). | |
| The cohort represents a diverse age range typical for sepsis studies. | |
| </p> | |
| </div> | |
| </div> | |
| {/* Visualization */} | |
| <div className="space-y-6"> | |
| <h3 className="text-sm font-medium text-slate-400 uppercase tracking-wider">Gender Distribution</h3> | |
| <div className="space-y-4"> | |
| {/* Male Bar */} | |
| <div className="space-y-2"> | |
| <div className="flex justify-between text-sm"> | |
| <span className="text-blue-400 font-medium">Male</span> | |
| <span className="text-slate-300">{stats.gender_distribution.Male.toLocaleString()}</span> | |
| </div> | |
| <div className="h-4 bg-slate-800 rounded-full overflow-hidden"> | |
| <motion.div | |
| className="h-full bg-blue-500" | |
| initial={{ width: 0 }} | |
| animate={{ width: `${(stats.gender_distribution.Male / maxCount) * 100}%` }} | |
| transition={{ duration: 1, ease: "easeOut" }} | |
| /> | |
| </div> | |
| </div> | |
| {/* Female Bar */} | |
| <div className="space-y-2"> | |
| <div className="flex justify-between text-sm"> | |
| <span className="text-pink-400 font-medium">Female</span> | |
| <span className="text-slate-300">{stats.gender_distribution.Female.toLocaleString()}</span> | |
| </div> | |
| <div className="h-4 bg-slate-800 rounded-full overflow-hidden"> | |
| <motion.div | |
| className="h-full bg-pink-500" | |
| initial={{ width: 0 }} | |
| animate={{ width: `${(stats.gender_distribution.Female / maxCount) * 100}%` }} | |
| transition={{ duration: 1, ease: "easeOut" }} | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| <div className="mt-8 pt-8 border-t border-slate-700"> | |
| <h3 className="text-sm font-medium text-slate-400 uppercase tracking-wider mb-4">Dataset Coverage</h3> | |
| <div className="relative h-48 bg-slate-800/30 rounded-xl overflow-hidden border border-slate-700/50 flex items-center justify-center"> | |
| <div className="text-center"> | |
| <BarChart className="h-16 w-16 text-slate-700 mx-auto mb-2" /> | |
| <p className="text-sm text-slate-500">Age histogram not available in this view</p> | |
| </div> | |
| {/* Placeholder for 3D effect or chart */} | |
| <div className="absolute inset-0 bg-gradient-to-t from-slate-900/80 to-transparent pointer-events-none" /> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| ) : ( | |
| <div className="text-center text-red-400">Failed to load statistics</div> | |
| )} | |
| </CardContent> | |
| </Card> | |
| </motion.div> | |
| ); | |
| } | |