Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,15 +65,20 @@ function App() {
let country = globalDataMap[d.iso3];
if (!country) {
globalDataMap[d.iso3] = pick(d, ['countryRegion', 'lastUpdate', 'confirmed', 'recovered', 'deaths', 'active']);
globalDataMap[d.iso3].provinces = { [d.provinceState]: pick(d, fieldsToFilter) };
globalDataMap[d.iso3].provinces = {};
if (d.provinceState) {
globalDataMap[d.iso3].provinces[d.provinceState] = pick(d, fieldsToFilter);
}
} else {
metrics.forEach(m => {
country[m] += d[m];
});
if (d.lastUpdate > country.lastUpdate) {
country.lastUpdate = d.lastUpdate;
}
country.provinces[d.provinceState] = pick(d, fieldsToFilter);
if (d.provinceState) {
country.provinces[d.provinceState] = pick(d, fieldsToFilter);
}
}
})
window.globalDataMap = globalDataMap; // for debugging purpose
Expand Down
37 changes: 30 additions & 7 deletions src/components/stats-panel.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
}

.stats-panel {
width: 450px;
width: 470px;
max-width: calc(100vw - 2rem);
margin: 1rem;
position: absolute;
Expand All @@ -26,12 +26,14 @@
display: flex;
justify-content: space-between;
overflow-x: auto;
margin: -1rem;
padding: 1rem;
}

.stats-group .ant-statistic {
cursor: pointer;
padding: 6px;
margin-right: 6px;
padding: 8px;
margin-right: 8px;
border-radius: 4px;
user-select: none;
}
Expand All @@ -49,21 +51,21 @@
}

.stats-list {
height: 40vh;
/* max-height: 40vh; */
margin: 1rem -1rem -1rem -1rem;
border-top: 1px solid #f0f0f0;
}

.stats-list .ant-spin-nested-loading {
/* .stats-list .ant-spin-nested-loading {
height: 100%;
}

.stats-list .ant-spin-container {
height: 100%;
}
} */

.stats-list .ant-list-items {
height: 100%;
max-height: 40vh;
overflow: auto;
}

Expand All @@ -73,3 +75,24 @@
display: flex;
justify-content: space-between;
}

.clickable {
cursor: pointer;
}

.clickable:hover {
background-color: rgb(244, 245, 248);
}


/* card title */
.stats-panel .ant-card-head-title {
padding: 0;
display: flex;
flex-direction: column;
}

.breadcrumb-item:hover {
color: #40a9ff;
cursor: pointer;
}
96 changes: 72 additions & 24 deletions src/components/stats-panel.js
Original file line number Diff line number Diff line change
@@ -1,42 +1,62 @@
import React, { useState } from 'react';
import { Statistic, Card, List, Button } from 'antd';
import { Statistic, Card, List, Button, Breadcrumb } from 'antd';
import { LineChartOutlined, CloseOutlined } from '@ant-design/icons';
import numeral from 'numeral'
import numeral from 'numeral';
import { isEmpty } from 'lodash';

import './stats-panel.css';


export default function StatsPanel ({ globalData }) {
const [minimized, setMinimized] = useState(false);
const [metric, setMetric] = useState('active');
const [countryCode, setCountry] = useState(); // iso3

const total = {
let total = {
active: 0,
confirmed: 0,
recovered: 0,
deaths: 0,
}

const countryList = Object.entries(globalData).map(([iso3, data]) => {
total.confirmed += data.confirmed;
total.active += data.active;
total.recovered += data.recovered;
total.deaths += data.deaths;
return { ...data, iso3 };
}).sort((c1, c2) => c2[metric] - c1[metric]);
const metricList = Object.keys(total);

let areaList = [];
if (countryCode && globalData[countryCode]) { // country
total = globalData[countryCode];
if (!isEmpty(globalData[countryCode].provinces)) {
areaList = Object.entries(globalData[countryCode].provinces).map(([areaCode, data]) => {
return { ...data, areaCode, name: data.provinceState || 'N/A' };
})
}
} else { // global
areaList = Object.entries(globalData).map(([areaCode, data]) => {
total.confirmed += data.confirmed;
total.active += data.active;
total.recovered += data.recovered;
total.deaths += data.deaths;
return { ...data, areaCode, name: data.countryRegion };
})
}
areaList.sort((c1, c2) => c2[metric] - c1[metric]);
console.log(areaList);

const resetPanel = () => {
setMinimized(false);
setCountry(null);
}

if (minimized) {
return (
<Button
onClick={() => setMinimized(false)}
className="minimized-button"
onClick={() => resetPanel()}
className='minimized-button'
icon={<LineChartOutlined />}
size='large'
/>
)
}

const listItemClasses = `stats-list-item ${countryCode ? '' : 'clickable'}`;
const collapseBtn = (
<Button
onClick={() => setMinimized(true)}
Expand All @@ -46,7 +66,13 @@ export default function StatsPanel ({ globalData }) {
</Button>
);
return (
<Card title="Global Stats" className='stats-panel' extra={collapseBtn}>
<Card
title={(
<CardTitle country={countryCode && globalData[countryCode].countryRegion} setCountry={setCountry} />
)}
className='stats-panel'
extra={collapseBtn}
>
<div className='stats-group'>
{metricList.map(m => (
<div className='stats-wrapper' onClick={() => setMetric(m)} key={m}>
Expand All @@ -58,16 +84,38 @@ export default function StatsPanel ({ globalData }) {
</div>
))}
</div>
<List
className='stats-list'
dataSource={countryList}
renderItem={country => (
<List.Item className="stats-list-item">
{country.countryRegion}
<span>{numeral(country[metric]).format('0,0')}</span>
</List.Item>
)}
/>
{areaList && areaList.length > 0 && (
<List
className='stats-list'
dataSource={areaList}
renderItem={area => (
<List.Item
className={listItemClasses} onClick={() => !countryCode && setCountry(area.areaCode)}>
{area.name}
<span>{numeral(area[metric]).format('0,0')}</span>
</List.Item>
)}
/>
)}
</Card>
)
}

const CardTitle = ({ country, setCountry }) => {
let title = 'Global Stats';
if (country) {
title = country;
}

return (
<div>
{ country && (
<Breadcrumb separator=''>
<Breadcrumb.Item className='breadcrumb-item' onClick={() => setCountry(null)}>Global Stats</Breadcrumb.Item>
<Breadcrumb.Separator>></Breadcrumb.Separator>
</Breadcrumb>
)}
<h3 style={{marginBottom: '0px'}}>{title}</h3>
</div>
)
}