"use client" import React, { useState, useEffect, useCallback } from "react" import { useCriticalAlarm } from "@/lib/useCriticalAlarm" import { motion, AnimatePresence } from "framer-motion" import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" import { AlertTriangle, Activity, Users, TrendingUp, Clock, Heart, Thermometer, Droplets, Brain, ChevronRight, RefreshCw, Filter, Search } from "lucide-react" interface PatientSummary { stay_id: number subject_id: number age: number gender: string sepsis?: number prediction?: { sepsis: number respiration: number cardiovascular: number renal: number cns: number } } interface StatsData { total_patients: number avg_age: number gender_distribution: { Male: number; Female: number } sepsis_cases: { Sepsis: number; Normal: number } } // Export types for parent component export type { PatientSummary, StatsData } interface HospitalOverviewProps { onPatientSelect?: (stayId: number) => void // Cached data props cachedStats?: StatsData | null cachedPatients?: PatientSummary[] onDataLoaded?: (stats: StatsData | null, patients: PatientSummary[]) => void } export default function HospitalOverview({ onPatientSelect, cachedStats, cachedPatients, onDataLoaded }: HospitalOverviewProps) { const [emergencyPatients, setEmergencyPatients] = useState(cachedPatients || []) const [stats, setStats] = useState(cachedStats || null) const [loading, setLoading] = useState(!cachedPatients || cachedPatients.length === 0) const [refreshing, setRefreshing] = useState(false) const [searchQuery, setSearchQuery] = useState("") const [filterCritical, setFilterCritical] = useState(false) // ICU Alarm for critical patient interactions const { playAlarm, isMuted, toggleMute, isPlaying } = useCriticalAlarm() const handlePatientClick = useCallback((stayId: number, prediction?: PatientSummary['prediction']) => { // Play alarm when clicking on a critical patient if (prediction && prediction.sepsis >= 0.7) { playAlarm('critical') } onPatientSelect?.(stayId) }, [playAlarm, onPatientSelect]) const fetchData = useCallback(async (isRefresh = false) => { if (isRefresh) setRefreshing(true) else { try { // Try to load from localStorage cache first const cached = localStorage.getItem('sepsis_guard_overview_cache') if (cached) { const parsed = JSON.parse(cached) const now = new Date().getTime() // Cache valid for 24 hours (24 * 60 * 60 * 1000) if (now - parsed.timestamp < 86400000) { setStats(parsed.stats) setEmergencyPatients(parsed.patients) setLoading(false) onDataLoaded?.(parsed.stats, parsed.patients) return // Exit early since we used cache } } } catch (e) { console.error("Failed to read from cache:", e) } setLoading(true) } const endpoint = process.env.NEXT_PUBLIC_API_URL || '/api' try { // Fetch stats const statsRes = await fetch(`${endpoint}/stats`) let statsData: StatsData | null = null if (statsRes.ok) { statsData = await statsRes.json() setStats(statsData) } // Fetch emergency patients const emergencyRes = await fetch(`${endpoint}/patients/emergency?limit=50`) if (emergencyRes.ok) { const patients = await emergencyRes.json() // Fetch predictions for each patient const patientsWithPredictions = await Promise.all( patients.slice(0, 20).map(async (p: PatientSummary) => { try { const predRes = await fetch(`${endpoint}/predict/${p.stay_id}?window_hours=6`, { method: 'POST' }) if (predRes.ok) { const pred = await predRes.json() return { ...p, prediction: pred } } } catch (e) { console.error(`Failed to get prediction for ${p.stay_id}`) } return p }) ) setEmergencyPatients(patientsWithPredictions) try { // Save to localStorage for future reloads localStorage.setItem('sepsis_guard_overview_cache', JSON.stringify({ stats: statsData, patients: patientsWithPredictions, timestamp: new Date().getTime() })) } catch (e) { console.error("Failed to save cache:", e) } // Notify parent to cache the data onDataLoaded?.(statsData, patientsWithPredictions) } } catch (e) { console.error("Failed to fetch data:", e) } finally { setLoading(false) setRefreshing(false) } }, [onDataLoaded]) useEffect(() => { // Only fetch if no cached data if (!cachedPatients || cachedPatients.length === 0) { fetchData() } }, [cachedPatients, fetchData]) const handleRefresh = () => { fetchData(true) } const getCriticalityLevel = (prediction?: PatientSummary['prediction']) => { if (!prediction) return { level: 'unknown', color: 'slate', label: 'Unknown' } const sepsisRisk = prediction.sepsis if (sepsisRisk >= 0.7) return { level: 'critical', color: 'red', label: 'CRITICAL' } if (sepsisRisk >= 0.4) return { level: 'high', color: 'orange', label: 'HIGH RISK' } if (sepsisRisk >= 0.2) return { level: 'moderate', color: 'amber', label: 'MODERATE' } return { level: 'low', color: 'emerald', label: 'STABLE' } } const filteredPatients = emergencyPatients.filter(p => { const matchesSearch = searchQuery === "" || p.stay_id.toString().includes(searchQuery) || p.subject_id.toString().includes(searchQuery) const criticality = getCriticalityLevel(p.prediction) const matchesFilter = !filterCritical || criticality.level === 'critical' || criticality.level === 'high' return matchesSearch && matchesFilter }) // Sort by sepsis risk (highest first) const sortedPatients = [...filteredPatients].sort((a, b) => { const riskA = a.prediction?.sepsis ?? 0 const riskB = b.prediction?.sepsis ?? 0 return riskB - riskA }) if (loading) { return (

Loading hospital data...

) } return (
{/* Stats Cards */}

Total Patients

{stats?.total_patients?.toLocaleString() ?? '-'}

Sepsis Cases

{stats?.sepsis_cases?.Sepsis?.toLocaleString() ?? '-'}

Normal Cases

{stats?.sepsis_cases?.Normal?.toLocaleString() ?? '-'}

Avg. Age

{stats?.avg_age ?? '-'}

{/* Emergency Patients Section */}
Emergency Patients ({sortedPatients.length} patients)
{/* Search */}
{/* Search icon removed as requested */} setSearchQuery(e.target.value)} className="w-full px-4 py-2.5 rounded-lg bg-slate-800 border border-slate-600/50 text-base text-white placeholder-slate-400 focus:outline-none focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/30 shadow-none" />
{/* Filter */} {/* Refresh */}
{sortedPatients.map((patient, index) => { const criticality = getCriticalityLevel(patient.prediction) return ( handlePatientClick(patient.stay_id, patient.prediction)} > {/* Status */} {/* Patient ID */} {/* Age/Gender */} {/* Sepsis Risk */} {/* SOFA Scores */} {/* Action */} ) })}
STATUS PATIENT ID AGE/GENDER SEPSIS RISK SOFA SCORES ACTION
{criticality.label}
Stay: {patient.stay_id}
Subject: {patient.subject_id}
{patient.age} yrs {['M', 'Male', '0', 0].includes(patient.gender) ? '♂' : '♀'}
{patient.prediction ? (
= 0.7 ? 'bg-red-500' : patient.prediction.sepsis >= 0.4 ? 'bg-orange-500' : patient.prediction.sepsis >= 0.2 ? 'bg-amber-500' : 'bg-emerald-500' }`} style={{ width: `${Math.min(100, patient.prediction.sepsis * 100)}%` }} />
{(patient.prediction.sepsis * 100).toFixed(1)}%
) : ( - )}
{patient.prediction ? (
} value={patient.prediction.cardiovascular} /> } value={patient.prediction.renal} /> } value={patient.prediction.cns} />
) : ( - )}
{sortedPatients.length === 0 && (

No patients found

)}
) } function SOFABadge({ icon, value }: { icon: React.ReactNode; value: number }) { const severity = value >= 3 ? 'red' : value >= 2 ? 'amber' : value >= 1 ? 'yellow' : 'emerald' return (
{icon} {value.toFixed(1)}
) }