From a404d5f7cc776f8f5d3350157c00ea3bb3017763 Mon Sep 17 00:00:00 2001 From: Divayang-2006 Date: Thu, 16 Oct 2025 23:32:55 +0530 Subject: [PATCH 1/3] Scope: Add CountryTrendChart component and integrate into Covid page -> Fetch /dayone/country/{slug}.(But i think API is not working properly) -> Loading state while fetching. -> Cumulative confirmed over time line. -> Abort or guard against stale updates on country change. -> Placeholder swapped for chart component. --- src/components/CountryTrendChart.jsx | 108 +++++++++++++++++++++++++++ src/pages/Covid.jsx | 3 +- 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 src/components/CountryTrendChart.jsx diff --git a/src/components/CountryTrendChart.jsx b/src/components/CountryTrendChart.jsx new file mode 100644 index 0000000..43eff72 --- /dev/null +++ b/src/components/CountryTrendChart.jsx @@ -0,0 +1,108 @@ +import { useEffect, useState, useRef } from 'react'; +import Loading from './Loading.jsx'; +import ChartPlaceholder from './ChartPlaceholder.jsx'; + +// Tiny, dependency-free line chart that accepts array of {date, value} +function SimpleLineChart({ data, width = 600, height = 200 }) { + if (!data || data.length === 0) return ; + + const padding = 20; + const dates = data.map(d => new Date(d.date)); + const values = data.map(d => d.value); + const minY = Math.min(...values); + const maxY = Math.max(...values); + + const x = i => { + const t = i / (data.length - 1 || 1); + return padding + t * (width - padding * 2); + }; + const y = v => { + const range = maxY - minY || 1; + const t = (v - minY) / range; + return height - padding - t * (height - padding * 2); + }; + + const path = data.map((d, i) => `${i === 0 ? 'M' : 'L'} ${x(i)} ${y(d.value)}`).join(' '); + + return ( + + + + + + + + + + {data.map((d, i) => ( + + ))} + + ); +} + +export default function CountryTrendChart({ slug }) { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [series, setSeries] = useState([]); + const abortRef = useRef(); + const lastSlugRef = useRef(slug); + + useEffect(() => { + if (!slug) { + lastSlugRef.current = slug; + setSeries([]); + setError(null); + setLoading(false); + if (abortRef.current) abortRef.current.abort(); + return; + } + + const controller = new AbortController(); + abortRef.current = controller; + let mounted = true; + + async function load() { + setLoading(true); + setError(null); + setSeries([]); + try { + const res = await fetch(`https://api.covid19api.com/dayone/country/${slug}`, { signal: controller.signal }); + if (!res.ok) throw new Error('Failed to fetch country trends'); + const json = await res.json(); + + if (!mounted || lastSlugRef.current !== slug) return; + + const seriesData = json.map(item => ({ date: item.Date, value: Number(item.Confirmed || 0) })); + + seriesData.sort((a, b) => new Date(a.date) - new Date(b.date)); + + setSeries(seriesData); + } catch (e) { + if (e.name === 'AbortError') return; + setError(e); + } finally { + if (mounted) setLoading(false); + } + } + + lastSlugRef.current = slug; + load(); + + return () => { + mounted = false; + controller.abort(); + }; + }, [slug]); + + if (!slug) return ; + if (loading && series.length === 0) return ; + if (error) return ; + + return ( +
+

Confirmed Cases — Cumulative

+ +
+ ); +} diff --git a/src/pages/Covid.jsx b/src/pages/Covid.jsx index 6155022..f395c6a 100644 --- a/src/pages/Covid.jsx +++ b/src/pages/Covid.jsx @@ -21,6 +21,7 @@ import { useEffect, useState, useCallback } from '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'; export default function Covid() { const [summary, setSummary] = useState(null); @@ -82,7 +83,7 @@ export default function Covid() {

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

)} - {/* TODO: Add daily trends chart using ChartPlaceholder */} + ); } From db644274ddc66bda7590c2db8c24a585f1c5613b Mon Sep 17 00:00:00 2001 From: Divayang-2006 Date: Fri, 17 Oct 2025 15:33:30 +0530 Subject: [PATCH 2/3] Scope: updating API and improving chart -> Updated API as the previous one was deprecated. -> Improved CountryTrendChart to enhance readability by adjusting the position of grid lines and labels. --- src/components/CountryTrendChart.jsx | 158 ++++++++++++++++++++++----- src/pages/Covid.jsx | 84 +++++++++----- 2 files changed, 188 insertions(+), 54 deletions(-) diff --git a/src/components/CountryTrendChart.jsx b/src/components/CountryTrendChart.jsx index 43eff72..0876887 100644 --- a/src/components/CountryTrendChart.jsx +++ b/src/components/CountryTrendChart.jsx @@ -1,42 +1,143 @@ -import { useEffect, useState, useRef } from 'react'; +import { useEffect, useState, useRef, useMemo, useState as useLocalState } from 'react'; import Loading from './Loading.jsx'; import ChartPlaceholder from './ChartPlaceholder.jsx'; +import ErrorMessage from './ErrorMessage.jsx'; -// Tiny, dependency-free line chart that accepts array of {date, value} -function SimpleLineChart({ data, width = 600, height = 200 }) { +function SimpleLineChart({ data, width = 600, height = 250 }) { + const [hover, setHover] = useLocalState(null); if (!data || data.length === 0) return ; - const padding = 20; + const padding = 40; const dates = data.map(d => new Date(d.date)); const values = data.map(d => d.value); + const minY = Math.min(...values); const maxY = Math.max(...values); + const yRange = maxY - minY || 1; + + const x = i => padding + (i / (data.length - 1)) * (width - padding * 2); + const y = v => height - padding - ((v - minY) / yRange) * (height - padding * 2); + + // Smooth path using quadratic curves + const path = data.reduce((acc, d, i, arr) => { + const px = x(i); + const py = y(d.value); + if (i === 0) return `M ${px} ${py}`; + const prevX = x(i - 1); + const prevY = y(arr[i - 1].value); + const midX = (px + prevX) / 2; + const midY = (py + prevY) / 2; + return acc + ` Q ${prevX} ${prevY}, ${midX} ${midY}`; + }, ''); + + const yTicks = 5; + const yStep = yRange / yTicks; + const yLabels = Array.from({ length: yTicks + 1 }, (_, i) => minY + i * yStep); - const x = i => { - const t = i / (data.length - 1 || 1); - return padding + t * (width - padding * 2); - }; - const y = v => { - const range = maxY - minY || 1; - const t = (v - minY) / range; - return height - padding - t * (height - padding * 2); - }; + return ( + setHover(null)} + > + {/* Grid lines */} + {yLabels.map((v, i) => ( + + ))} - const path = data.map((d, i) => `${i === 0 ? 'M' : 'L'} ${x(i)} ${y(d.value)}`).join(' '); + {/* Y-axis labels */} + {yLabels.map((v, i) => ( + + {Math.round(v).toLocaleString()} + + ))} - return ( - + {/* Area fill */} - - + + - - - {data.map((d, i) => ( - - ))} + + + + {/* Data points */} + {data.map((d, i) => { + const cx = x(i); + const cy = y(d.value); + return ( + setHover(i)} + /> + ); + })} + + {/* Tooltip */} + {hover !== null && ( + <> + + + {new Date(data[hover].date).toLocaleDateString()} + + + {data[hover].value.toLocaleString()} + + + )} ); } @@ -67,13 +168,20 @@ export default function CountryTrendChart({ slug }) { setError(null); setSeries([]); try { - const res = await fetch(`https://api.covid19api.com/dayone/country/${slug}`, { signal: controller.signal }); + const res = await fetch( + `https://disease.sh/v3/covid-19/historical/${slug}?lastdays=all`, + { signal: controller.signal } + ); if (!res.ok) throw new Error('Failed to fetch country trends'); const json = await res.json(); if (!mounted || lastSlugRef.current !== slug) return; - const seriesData = json.map(item => ({ date: item.Date, value: Number(item.Confirmed || 0) })); + const cases = json.timeline?.cases || {}; + const seriesData = Object.entries(cases).map(([date, value]) => ({ + date, + value: Number(value || 0), + })); seriesData.sort((a, b) => new Date(a.date) - new Date(b.date)); diff --git a/src/pages/Covid.jsx b/src/pages/Covid.jsx index f395c6a..f4959d4 100644 --- a/src/pages/Covid.jsx +++ b/src/pages/Covid.jsx @@ -17,7 +17,7 @@ * - [ ] Offline cache last fetch * - [ ] Extract service + hook (useCovidSummary, useCountryTrends) */ -import { useEffect, useState, useCallback } from 'react'; +import { useEffect, useState, useCallback, useRef } from 'react'; import Loading from '../components/Loading.jsx'; import ErrorMessage from '../components/ErrorMessage.jsx'; import Card from '../components/Card.jsx'; @@ -28,61 +28,87 @@ export default function Covid() { const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [country, setCountry] = useState(''); + const isFetchingRef = useRef(false); const fetchSummary = useCallback(async () => { - if (loading) return; + if (isFetchingRef.current) return; + isFetchingRef.current = true; try { - setLoading(true); + setLoading(true); setError(null); - const res = await fetch('https://api.covid19api.com/summary'); - if (!res.ok) throw new Error('Failed to fetch'); + 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); + } finally { + setLoading(false); + isFetchingRef.current = false; } - }, [loading]); + }, []); useEffect(() => { fetchSummary(); - }, []); + }, [fetchSummary]); - const global = summary?.Global; - const countries = summary?.Countries || []; - const selected = countries.find(c => c.Slug === country); + const countries = summary || []; + 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; + return acc; + }, + { cases: 0, todayCases: 0, deaths: 0, todayDeaths: 0 } + ); return (
-

COVID-19 Tracker

- -
+

COVID-19 Tracker

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

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

-

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

-

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

+

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

+

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

+

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

)} -