diff --git a/frontend/src/components/HospitalAnalyticsPanel.tsx b/frontend/src/components/HospitalAnalyticsPanel.tsx index 00e5e94..a3b8c68 100644 --- a/frontend/src/components/HospitalAnalyticsPanel.tsx +++ b/frontend/src/components/HospitalAnalyticsPanel.tsx @@ -4,6 +4,7 @@ import { BarChart, CartesianGrid, Cell, + Legend, Pie, PieChart, ResponsiveContainer, @@ -16,6 +17,23 @@ import { getHospitalAnalytics, type HospitalAnalytics } from "../api/analytics"; const CHART_COLORS = ["#0284c7", "#16a34a", "#d97706", "#dc2626", "#7c3aed"]; +type OutcomeChartItem = { + name: string; + value: number; +}; + +type ProcedureChartItem = { + procedure: string; + totalCost: number; + avgCost: number; + count: number; +}; + +function truncateLabel(label: string, maxLength = 22) { + if (label.length <= maxLength) return label; + return `${label.slice(0, maxLength)}...`; +} + export default function HospitalAnalyticsPanel() { const [analytics, setAnalytics] = useState(null); const [loading, setLoading] = useState(true); @@ -48,24 +66,34 @@ export default function HospitalAnalyticsPanel() { }; }, []); - const outcomeData = useMemo(() => { + const outcomeData = useMemo(() => { const distribution = analytics?.outcome_distribution || {}; - return Object.entries(distribution).map(([name, value]) => ({ name, value })); + return Object.entries(distribution).map(([name, value]) => ({ + name, + value: Number(value), + })); }, [analytics]); - const procedureData = useMemo(() => { - return (analytics?.procedure_cost_analysis || []).slice(0, 6).map((item) => ({ - procedure: item.procedure, - avgCost: item.average_cost, - totalCost: item.total_cost, - count: item.count, - })); + const procedureData = useMemo(() => { + return [...(analytics?.procedure_cost_analysis || [])] + .sort((a, b) => b.total_cost - a.total_cost) + .slice(0, 5) + .map((item) => ({ + procedure: truncateLabel(item.procedure), + totalCost: item.total_cost, + avgCost: item.average_cost, + count: item.count, + })); }, [analytics]); + const totalOutcomes = useMemo(() => { + return outcomeData.reduce((sum, item) => sum + item.value, 0); + }, [outcomeData]); + if (loading) { return (
-

Loading hospital analytics...

+ Loading hospital analytics...
); } @@ -73,7 +101,7 @@ export default function HospitalAnalyticsPanel() { if (error) { return (
-

{error}

+ {error}
); } @@ -82,9 +110,14 @@ export default function HospitalAnalyticsPanel() { return (
-
-

Hospital Analytics

- Source: imported hospital CSV +
+
+

Hospital Analytics

+

+ Key outcome, quality, and procedure cost metrics from imported hospital data. +

+
+ Source: imported hospital CSV
@@ -95,36 +128,66 @@ export default function HospitalAnalyticsPanel() {
-
-

Outcome Distribution

+ {/* PIE CHART */} +
+

Outcome Distribution

+

+ Breakdown of patient outcome categories with counts and percentages. +

+ - + { + const percent = totalOutcomes > 0 ? (value / totalOutcomes) * 100 : 0; + return `${name}: ${percent.toFixed(1)}%`; + }} + > {outcomeData.map((entry, idx) => ( ))} + +
-
-

Procedure Cost Analysis

+ {/* BAR CHART */} +
+

Top Procedures by Total Cost

+

+ Highest-cost procedures ranked by total spend. +

+ - - - - - { - if (name === "avgCost") return [`$${value.toLocaleString()}`, "Average Cost"]; - if (name === "totalCost") return [`$${value.toLocaleString()}`, "Total Cost"]; - return [value, name]; - }} - /> - - + {procedureData.length <= 1 ? ( +
+ Not enough procedure data to display comparison. +
+ ) : ( + + + + `$${(value / 1000).toFixed(0)}k`} /> + [`$${value.toLocaleString()}`, "Total Cost"]} /> + + + )}
@@ -135,8 +198,8 @@ export default function HospitalAnalyticsPanel() { function MetricCard({ label, value }: { label: string; value: string }) { return (
-

{label}

+

{label}

{value}

); -} +} \ No newline at end of file