From 7b927bea1cd47e199aeef76950ac916c56c4e3fe Mon Sep 17 00:00:00 2001 From: Venisha Kalola Date: Wed, 22 Oct 2025 12:08:22 +0530 Subject: [PATCH 1/2] Improved UI of Covid Page --- src/pages/Covid.jsx | 355 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 295 insertions(+), 60 deletions(-) diff --git a/src/pages/Covid.jsx b/src/pages/Covid.jsx index 0072cd5..83851b6 100644 --- a/src/pages/Covid.jsx +++ b/src/pages/Covid.jsx @@ -1,75 +1,310 @@ -/** - * 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 - * Medium: - * - [ ] Country search input (filter dropdown) - * - [ ] Sort countries (Total Confirmed, New Confirmed, Deaths) - * - [ ] Persist last selected country - * - [ ] Basic trend chart (cases over time) for selected country - * Advanced: - * - [ ] Multi-country comparison chart - * - [ ] Data normalization per million population (needs population API) - * - [ ] Offline cache last fetch - * - [ ] Extract service + hook (useCovidSummary, useCountryTrends) - */ -import { useEffect, useState } from 'react'; -import Loading from '../components/Loading.jsx'; -import ErrorMessage from '../components/ErrorMessage.jsx'; -import Card from '../components/Card.jsx'; +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'; 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 isFetchingRef = useRef(false); - useEffect(() => { fetchSummary(); }, []); - - async function fetchSummary() { + const fetchSummary = useCallback(async () => { + if (isFetchingRef.current) return; + isFetchingRef.current = true; try { - setLoading(true); setError(null); - const res = await fetch('https://api.covid19api.com/summary'); - if (!res.ok) throw new Error('Failed to fetch'); + setLoading(true); + setError(null); + const res = await fetch('https://disease.sh/v3/covid-19/countries'); + if (!res.ok) throw new Error('Failed to fetch country data'); const json = await res.json(); setSummary(json); - } catch (e) { setError(e); } finally { setLoading(false); } - } + } catch (e) { + setError(e.message); + } finally { + setLoading(false); + isFetchingRef.current = false; + } + }, []); + + 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 || []; + const filteredCountries = countries.filter(c => + c.country.toLowerCase().includes(searchTerm.toLowerCase()) + ); + const selected = countries.find(c => c.country === country); + + const global = countries.reduce( + (acc, c) => { + acc.cases += c.cases; + acc.todayCases += c.todayCases; + acc.deaths += c.deaths; + acc.todayDeaths += c.todayDeaths; + acc.recovered += c.recovered; + acc.active += c.active; + return acc; + }, + { cases: 0, todayCases: 0, deaths: 0, todayDeaths: 0, recovered: 0, active: 0 } + ); + + 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 + })); + + 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 + })) : []; - const global = summary?.Global; - const countries = summary?.Countries || []; - const selected = countries.find(c => c.Slug === country); + const formatNumber = (num) => { + if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; + if (num >= 1000) return (num / 1000).toFixed(1) + 'K'; + return num.toLocaleString(); + }; return ( -
-

COVID-19 Tracker

- {loading && } - - {global && ( - -

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

-

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

-

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

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

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

-

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

-

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

-
- )} - {/* TODO: Add daily trends chart using ChartPlaceholder */} +
+
+ {/* Header */} +
+
+

+ + COVID-19 Dashboard +

+

Real-time global pandemic statistics

+
+ +
+ + {loading && !summary && ( +
+
+
+ )} + + {error && ( +
+ +

{error}

+
+ )} + + {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.recovered/global.cases)*100).toFixed(1)}% recovery rate

+
+ +
+
+

Deaths

+ +
+

{formatNumber(global.deaths)}

+

+{formatNumber(global.todayDeaths)} today

+
+
+ + {/* 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)}

+
+
+

Deaths

+

{formatNumber(selected.deaths)}

+

+{formatNumber(selected.todayDeaths)} today

+
+
+ +
+ + Last updated: {new Date(selected.updated).toLocaleString()} +
+
+ )} +
+ + {/* Historical Trend Chart */} + {historicalChartData.length > 0 && ( +
+

30-Day Trend for {selected?.country}

+ + + + + + formatNumber(value)} /> + + + + + + +
+ )} + + {/* Data Source */} +
+

Data source: disease.sh API

+

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

+
+ + )} +
); -} +} \ No newline at end of file From 139cdc2dbcfaa1fde780ddcc463f81315316ef18 Mon Sep 17 00:00:00 2001 From: Venisha Kalola Date: Wed, 22 Oct 2025 12:12:15 +0530 Subject: [PATCH 2/2] Improved UI of Covid Page --- src/pages/Covid.jsx | 450 +++++++++++++++++++++++++------------------- 1 file changed, 261 insertions(+), 189 deletions(-) diff --git a/src/pages/Covid.jsx b/src/pages/Covid.jsx index 83851b6..3b65806 100644 --- a/src/pages/Covid.jsx +++ b/src/pages/Covid.jsx @@ -1,6 +1,31 @@ +/** + * COVID-19 DASHBOARD TODOs + * ------------------------ + * Easy: + * - [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: + * - [x] Country search input (filter dropdown) + * - [x] Sort countries (Total Confirmed, New Confirmed, Deaths) + * - [ ] Persist last 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) + * - [ ] Offline cache last fetch + * - [ ] 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'; +import CountryTrendChart from '../components/CountryTrendChart.jsx'; +import HeroSection from '../components/HeroSection'; +import Corona from '../Images/Corona.jpg'; export default function Covid() { const [summary, setSummary] = useState(null); @@ -9,6 +34,7 @@ export default function Covid() { 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 () => { @@ -19,10 +45,11 @@ export default function Covid() { setError(null); const res = await fetch('https://disease.sh/v3/covid-19/countries'); if (!res.ok) throw new Error('Failed to fetch country data'); + const json = await res.json(); setSummary(json); } catch (e) { - setError(e.message); + setError(e); } finally { setLoading(false); isFetchingRef.current = false; @@ -52,24 +79,40 @@ export default function Covid() { }, [country, fetchHistorical]); const countries = summary || []; - const filteredCountries = countries.filter(c => - c.country.toLowerCase().includes(searchTerm.toLowerCase()) - ); - const selected = countries.find(c => c.country === country); + + // 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) const global = countries.reduce( (acc, c) => { acc.cases += c.cases; acc.todayCases += c.todayCases; acc.deaths += c.deaths; acc.todayDeaths += c.todayDeaths; - acc.recovered += c.recovered; - acc.active += c.active; + acc.recovered += c.recovered || 0; + acc.active += c.active || 0; return acc; }, { 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) @@ -77,7 +120,7 @@ export default function Covid() { name: c.country, cases: c.cases, deaths: c.deaths, - recovered: c.recovered + recovered: c.recovered || 0 })); const globalPieData = [ @@ -94,216 +137,245 @@ export default function Covid() { recovered: historical.timeline.recovered?.[date] || 0 })) : []; - const formatNumber = (num) => { - if (num >= 1000000) return (num / 1000000).toFixed(1) + 'M'; - if (num >= 1000) return (num / 1000).toFixed(1) + 'K'; - return num.toLocaleString(); - }; - return ( -
-
- {/* Header */} -
-
-

- - COVID-19 Dashboard -

-

Real-time global pandemic statistics

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

+ + COVID-19 Dashboard +

+

Real-time global pandemic statistics

+
+
- )} - {error && ( -
- -

{error}

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

Total Cases

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

Total Cases

+ +
+

{formatNumber(global.cases)}

+

+{formatNumber(global.todayCases)} today

-

{formatNumber(global.cases)}

-

+{formatNumber(global.todayCases)} today

-
-
-
-

Active Cases

- +
+
+

Active Cases

+ +
+

{formatNumber(global.active)}

+

Currently infected

-

{formatNumber(global.active)}

-

Currently infected

-
-
-
-

Recovered

- +
+
+

Recovered

+ +
+

{formatNumber(global.recovered)}

+

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

-

{formatNumber(global.recovered)}

-

{((global.recovered/global.cases)*100).toFixed(1)}% recovery rate

-
-
-
-

Deaths

- +
+
+

Deaths

+ +
+

{formatNumber(global.deaths)}

+

+{formatNumber(global.todayDeaths)} today

-

{formatNumber(global.deaths)}

-

+{formatNumber(global.todayDeaths)} today

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

Top 10 Countries by Cases

- - - - - - formatNumber(value)} /> - - - + {/* Legacy Card for backwards compatibility */} +
+ +

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

+

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

+

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

+
- {/* Global Distribution Pie Chart */} -
-

Global Case Distribution

- - - `${name} ${(percent * 100).toFixed(1)}%`} - outerRadius={100} - fill="#8884d8" - dataKey="value" - > - {globalPieData.map((entry, index) => ( - - ))} - - formatNumber(value)} /> - - -
-
+ {/* Charts Row */} +
+ {/* Top 10 Countries Chart */} +
+

Top 10 Countries by Cases

+ + + + + + 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" - /> - + {/* Global Distribution Pie Chart */} +
+

Global Case Distribution

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

{selected.country}

-
- -
-
-

Total Cases

-

{formatNumber(selected.cases)}

-

+{formatNumber(selected.todayCases)} today

+ {/* 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}

-
-

Active

-

{formatNumber(selected.active)}

+ +
+
+

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

+
-
-

Recovered

-

{formatNumber(selected.recovered)}

+ +
+ + Last updated: {new Date(selected.updated).toLocaleString()}
-
-

Deaths

-

{formatNumber(selected.deaths)}

-

+{formatNumber(selected.todayDeaths)} today

+ + {/* 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()}

+
+ )} +
-
- - Last updated: {new Date(selected.updated).toLocaleString()} -
+ {/* Historical Trend Chart */} + {historicalChartData.length > 0 && ( +
+

30-Day Trend for {selected?.country}

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

30-Day Trend for {selected?.country}

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

Data source: disease.sh API

-

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

-
- - )} + {/* Data Source Note */} +
+

Data source: disease.sh API

+

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

+
+ + )} +
);