diff --git a/src/components/CountryTrendChart.jsx b/src/components/CountryTrendChart.jsx
new file mode 100644
index 0000000..d0cdd0a
--- /dev/null
+++ b/src/components/CountryTrendChart.jsx
@@ -0,0 +1,216 @@
+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';
+
+function SimpleLineChart({ data, width = 600, height = 250 }) {
+ const [hover, setHover] = useLocalState(null);
+ if (!data || data.length === 0) return ;
+
+ 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);
+
+ return (
+
+ );
+}
+
+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://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 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));
+
+ 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..f4959d4 100644
--- a/src/pages/Covid.jsx
+++ b/src/pages/Covid.jsx
@@ -17,72 +17,99 @@
* - [ ] 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';
+import CountryTrendChart from '../components/CountryTrendChart.jsx';
export default function Covid() {
const [summary, setSummary] = useState(null);
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()}
)}
-