diff --git a/src/pages/Covid.jsx b/src/pages/Covid.jsx index 59695c1..3b65806 100644 --- a/src/pages/Covid.jsx +++ b/src/pages/Covid.jsx @@ -2,15 +2,15 @@ * COVID-19 DASHBOARD TODOs * ------------------------ * Easy: - * - [ ] Show date of summary data (API provides Date field) - * - [ ] Format numbers with abbreviation utility - * - [ ] Add refresh button - * - [ ] Add note/link about data source & potential delays + * - [x] Show date of summary data (API provides Date field) + * - [x] Format numbers with abbreviation utility + * - [x] Add refresh button + * - [x] Add note/link about data source & potential delays * Medium: - * - [ ] Country search input (filter dropdown) - * - [ ] Sort countries (Total Confirmed, New Confirmed, Deaths) + * - [x] Country search input (filter dropdown) + * - [x] Sort countries (Total Confirmed, New Confirmed, Deaths) * - [ ] Persist last selected country - * - [ ] Basic trend chart (cases over time) for selected country + * - [x] Basic trend chart (cases over time) for selected country * Advanced: * - [ ] Multi-country comparison chart * - [ ] Data normalization per million population (needs population API) @@ -18,6 +18,8 @@ * - [ ] Extract service + hook (useCovidSummary, useCountryTrends) */ import { useEffect, useState, useCallback, useRef } from 'react'; +import { LineChart, Line, BarChart, Bar, PieChart, Pie, Cell, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { Activity, Users, AlertCircle, TrendingUp, Globe, RefreshCw, Calendar } from 'lucide-react'; import Loading from '../components/Loading.jsx'; import ErrorMessage from '../components/ErrorMessage.jsx'; import Card from '../components/Card.jsx'; @@ -27,9 +29,12 @@ import Corona from '../Images/Corona.jpg'; export default function Covid() { const [summary, setSummary] = useState(null); + const [historical, setHistorical] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [country, setCountry] = useState(''); + const [searchTerm, setSearchTerm] = useState(''); + const [sortBy, setSortBy] = useState('cases'); const isFetchingRef = useRef(false); const fetchSummary = useCallback(async () => { @@ -51,11 +56,40 @@ export default function Covid() { } }, []); + const fetchHistorical = useCallback(async (countryName) => { + if (!countryName) return; + try { + const res = await fetch(`https://disease.sh/v3/covid-19/historical/${countryName}?lastdays=30`); + if (!res.ok) throw new Error('Failed to fetch historical data'); + const json = await res.json(); + setHistorical(json); + } catch (e) { + console.error('Historical data error:', e); + } + }, []); + useEffect(() => { fetchSummary(); }, [fetchSummary]); + useEffect(() => { + if (country) { + fetchHistorical(country); + } + }, [country, fetchHistorical]); + const countries = summary || []; + + // Filter and sort countries + const filteredCountries = countries + .filter(c => c.country.toLowerCase().includes(searchTerm.toLowerCase())) + .sort((a, b) => { + if (sortBy === 'cases') return b.cases - a.cases; + if (sortBy === 'todayCases') return b.todayCases - a.todayCases; + if (sortBy === 'deaths') return b.deaths - a.deaths; + return 0; + }); + const selected = countries.find(c => c.countryInfo.iso3 === country || c.country === country); // Compute simple global summary (sum across all countries) @@ -65,63 +99,284 @@ export default function Covid() { acc.todayCases += c.todayCases; acc.deaths += c.deaths; acc.todayDeaths += c.todayDeaths; + acc.recovered += c.recovered || 0; + acc.active += c.active || 0; return acc; }, - { cases: 0, todayCases: 0, deaths: 0, todayDeaths: 0 } + { cases: 0, todayCases: 0, deaths: 0, todayDeaths: 0, recovered: 0, active: 0 } ); + // Format numbers with abbreviation utility + const formatNumber = (num) => { + if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; + if (num >= 1000) return (num / 1000).toFixed(1) + 'K'; + return num.toLocaleString(); + }; + + const topCountries = [...countries] + .sort((a, b) => b.cases - a.cases) + .slice(0, 10) + .map(c => ({ + name: c.country, + cases: c.cases, + deaths: c.deaths, + recovered: c.recovered || 0 + })); + + const globalPieData = [ + { name: 'Active', value: global.active, color: '#f59e0b' }, + { name: 'Recovered', value: global.recovered, color: '#10b981' }, + { name: 'Deaths', value: global.deaths, color: '#ef4444' } + ]; + + const historicalChartData = historical?.timeline ? + Object.keys(historical.timeline.cases).map(date => ({ + date: new Date(date).toLocaleDateString('en-US', { month: 'short', day: 'numeric' }), + cases: historical.timeline.cases[date], + deaths: historical.timeline.deaths[date], + recovered: historical.timeline.recovered?.[date] || 0 + })) : []; + return ( -
- - COVID-19 Tracker - - } - subtitle="Stay informed with the latest COVID-19 stats worldwide." -/> -
-

COVID-19 Tracker

- -
+ + COVID-19 Tracker + + } + subtitle="Stay informed with the latest COVID-19 stats worldwide." + /> + +
+
+ {/* Header */} +
+
+

+ + COVID-19 Dashboard +

+

Real-time global pandemic statistics

+
+ +
- {loading && !summary && } - - - {countries.length > 0 && ( - -

New Confirmed: {global.todayCases.toLocaleString()}

-

Total Confirmed: {global.cases.toLocaleString()}

-

Total Deaths: {global.deaths.toLocaleString()}

-
- )} - - - - {selected && ( - -

New Confirmed: {selected.todayCases.toLocaleString()}

-

Total Confirmed: {selected.cases.toLocaleString()}

-

Total Deaths: {selected.deaths.toLocaleString()}

-

Updated: {new Date(selected.updated).toLocaleString()}

-
- )} - - + {loading && !summary && } + + + {countries.length > 0 && ( + <> + {/* Global Statistics Cards */} +
+
+
+

Total Cases

+ +
+

{formatNumber(global.cases)}

+

+{formatNumber(global.todayCases)} today

+
+ +
+
+

Active Cases

+ +
+

{formatNumber(global.active)}

+

Currently infected

+
+ +
+
+

Recovered

+ +
+

{formatNumber(global.recovered)}

+

+ {global.cases > 0 ? ((global.recovered/global.cases)*100).toFixed(1) : 0}% recovery rate +

+
+ +
+
+

Deaths

+ +
+

{formatNumber(global.deaths)}

+

+{formatNumber(global.todayDeaths)} today

+
+
+ + {/* Legacy Card for backwards compatibility */} +
+ +

New Confirmed: {global.todayCases.toLocaleString()}

+

Total Confirmed: {global.cases.toLocaleString()}

+

Total Deaths: {global.deaths.toLocaleString()}

+
+
+ + {/* Charts Row */} +
+ {/* Top 10 Countries Chart */} +
+

Top 10 Countries by Cases

+ + + + + + formatNumber(value)} /> + + + +
+ + {/* Global Distribution Pie Chart */} +
+

Global Case Distribution

+ + + `${name} ${(percent * 100).toFixed(1)}%`} + outerRadius={100} + fill="#8884d8" + dataKey="value" + > + {globalPieData.map((entry, index) => ( + + ))} + + formatNumber(value)} /> + + +
+
+ + {/* Country Selection */} +
+

Country Analysis

+
+ setSearchTerm(e.target.value)} + className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-indigo-500 focus:border-transparent" + /> + + +
+ + {selected && ( +
+
+ {selected.country} +

{selected.country}

+
+ +
+
+

Total Cases

+

{formatNumber(selected.cases)}

+

+{formatNumber(selected.todayCases)} today

+
+
+

Active

+

{formatNumber(selected.active)}

+
+
+

Recovered

+

{formatNumber(selected.recovered || 0)}

+
+
+

Deaths

+

{formatNumber(selected.deaths)}

+

+{formatNumber(selected.todayDeaths)} today

+
+
+ +
+ + Last updated: {new Date(selected.updated).toLocaleString()} +
+ + {/* Legacy Card for backwards compatibility */} +
+ +

New Confirmed: {selected.todayCases.toLocaleString()}

+

Total Confirmed: {selected.cases.toLocaleString()}

+

Total Deaths: {selected.deaths.toLocaleString()}

+

Updated: {new Date(selected.updated).toLocaleString()}

+
+
+
+ )} +
+ + {/* Historical Trend Chart */} + {historicalChartData.length > 0 && ( +
+

30-Day Trend for {selected?.country}

+ + + + + + formatNumber(value)} /> + + + + + + +
+ )} + + {/* Legacy CountryTrendChart for backwards compatibility */} + + + {/* Data Source Note */} +
+

Data source: disease.sh API

+

Data may have delays. For official information, consult WHO and local health authorities.

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