row.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. import React, {useState, useEffect, useCallback} from 'react';
  2. import * as Icon from 'react-feather';
  3. import {
  4. formatDate,
  5. formatDateAbsolute,
  6. formatNumber,
  7. } from '../utils/common-functions';
  8. import {formatDistance} from 'date-fns';
  9. import {Tooltip} from 'react-lightweight-tooltip';
  10. function Row(props) {
  11. const [state, setState] = useState(props.state);
  12. const [districts, setDistricts] = useState(props.districts);
  13. const [sortedDistricts, setSortedDistricts] = useState(props.districts);
  14. const [sortData, setSortData] = useState({
  15. sortColumn: localStorage.getItem('district.sortColumn')
  16. ? localStorage.getItem('district.sortColumn')
  17. : 'confirmed',
  18. isAscending: localStorage.getItem('district.isAscending')
  19. ? localStorage.getItem('district.isAscending') === 'true'
  20. : false,
  21. });
  22. useEffect(() => {
  23. setState(props.state);
  24. }, [props.state]);
  25. useEffect(() => {
  26. setDistricts(props.districts);
  27. setSortedDistricts(props.districts);
  28. }, [props.districts]);
  29. const handleReveal = () => {
  30. props.handleReveal(props.state.state);
  31. };
  32. const sortDistricts = useCallback(
  33. (aDistricts) => {
  34. const sorted = {};
  35. if (aDistricts) {
  36. Object.keys(aDistricts)
  37. .sort((district1, district2) => {
  38. const sortColumn = sortData.sortColumn;
  39. const value1 =
  40. sortColumn === 'district'
  41. ? district1
  42. : parseInt(aDistricts[district1].confirmed);
  43. const value2 =
  44. sortColumn === 'district'
  45. ? district2
  46. : parseInt(aDistricts[district2].confirmed);
  47. const comparisonValue =
  48. value1 > value2
  49. ? 1
  50. : value1 === value2 && district1 > district2
  51. ? 1
  52. : -1;
  53. return sortData.isAscending
  54. ? comparisonValue
  55. : comparisonValue * -1;
  56. })
  57. .forEach((key) => {
  58. sorted[key] = aDistricts[key];
  59. });
  60. setSortedDistricts(sorted);
  61. }
  62. },
  63. [sortData.isAscending, sortData.sortColumn]
  64. );
  65. const handleSort = (column) => {
  66. const isAscending =
  67. sortData.sortColumn === column
  68. ? !sortData.isAscending
  69. : sortData.sortColumn === 'district';
  70. setSortData({
  71. sortColumn: column,
  72. isAscending: isAscending,
  73. });
  74. localStorage.setItem('district.sortColumn', column);
  75. localStorage.setItem('district.isAscending', isAscending);
  76. };
  77. useEffect(() => {
  78. sortDistricts(districts);
  79. }, [districts, sortData, sortDistricts]);
  80. return (
  81. <React.Fragment>
  82. <tr
  83. className={props.total ? 'state is-total' : 'state'}
  84. onMouseEnter={() => props.onHighlightState?.(state, props.index)}
  85. onMouseLeave={() => props.onHighlightState?.()}
  86. /* onTouchStart={() => props.onHighlightState?.(state, props.index)}*/
  87. /* onClick={!props.total ? handleReveal : null}*/
  88. style={{background: props.index % 2 === 0 ? '#f8f9fa' : ''}}
  89. >
  90. <td style={{fontWeight: 600}}>
  91. <div className="table__title-wrapper">
  92. <span
  93. className={`dropdown ${
  94. props.reveal ? 'rotateRightDown' : 'rotateDownRight'
  95. }`}
  96. style={{display: !props.total ? '' : 'none'}}
  97. onClick={() => {
  98. handleReveal();
  99. }}
  100. >
  101. <Icon.ChevronDown />
  102. </span>
  103. <span>
  104. {state.state}
  105. {state.statenotes && (
  106. <Tooltip
  107. content={[`${state.statenotes}`]}
  108. styles={{
  109. tooltip: {
  110. background: '#000',
  111. borderRadius: '10px',
  112. fontSize: '.8em',
  113. left: '250%',
  114. opacity: 0.65,
  115. },
  116. wrapper: {
  117. cursor: 'cursor',
  118. display: 'inline-block',
  119. position: 'relative',
  120. textAlign: 'center',
  121. },
  122. }}
  123. >
  124. <Icon.Info style={{height: '16px'}} />
  125. </Tooltip>
  126. )}
  127. </span>
  128. </div>
  129. </td>
  130. <td>
  131. <span className="deltas" style={{color: '#ff073a'}}>
  132. {state.deltaconfirmed > 0 && <Icon.ArrowUp />}
  133. {state.deltaconfirmed > 0 ? `${state.deltaconfirmed}` : ''}
  134. </span>
  135. <span className="table__count-text">
  136. {parseInt(state.confirmed) === 0
  137. ? '-'
  138. : formatNumber(state.confirmed)}
  139. </span>
  140. </td>
  141. <td
  142. style={{color: parseInt(state.active) === 0 ? '#B5B5B5' : 'inherit'}}
  143. >
  144. {/* <span className="deltas" style={{color: '#007bff'}}>
  145. {!state.delta.active==0 && <Icon.ArrowUp/>}
  146. {state.delta.active>0 ? `${state.delta.active}` : ''}
  147. </span>*/}
  148. {parseInt(state.active) === 0 ? '-' : formatNumber(state.active)}
  149. </td>
  150. <td
  151. style={{
  152. color: parseInt(state.recovered) === 0 ? '#B5B5B5' : 'inherit',
  153. }}
  154. >
  155. <span className="deltas" style={{color: '#28a745'}}>
  156. {state.deltarecovered > 0 && <Icon.ArrowUp />}
  157. {state.deltarecovered > 0 ? `${state.deltarecovered}` : ''}
  158. </span>
  159. <span className="table__count-text">
  160. {parseInt(state.recovered) === 0
  161. ? '-'
  162. : formatNumber(state.recovered)}
  163. </span>
  164. </td>
  165. <td
  166. style={{color: parseInt(state.deaths) === 0 ? '#B5B5B5' : 'inherit'}}
  167. >
  168. <span className="deltas" style={{color: '#6c757d'}}>
  169. {state.deltadeaths > 0 && <Icon.ArrowUp />}
  170. {state.deltadeaths > 0 ? `${state.deltadeaths}` : ''}
  171. </span>
  172. <span className="table__count-text">
  173. {parseInt(state.deaths) === 0 ? '-' : formatNumber(state.deaths)}
  174. </span>
  175. </td>
  176. </tr>
  177. <tr
  178. className={'state-last-update'}
  179. style={{display: props.reveal && !props.total ? '' : 'none'}}
  180. >
  181. <td colSpan={2}>
  182. <div className="last-update">
  183. <h6>Last updated&nbsp;</h6>
  184. <h6
  185. title={
  186. isNaN(Date.parse(formatDate(props.state.lastupdatedtime)))
  187. ? ''
  188. : formatDateAbsolute(props.state.lastupdatedtime)
  189. }
  190. >
  191. {isNaN(Date.parse(formatDate(props.state.lastupdatedtime)))
  192. ? ''
  193. : `${formatDistance(
  194. new Date(formatDate(props.state.lastupdatedtime)),
  195. new Date()
  196. )} ago`}
  197. </h6>
  198. </div>
  199. </td>
  200. </tr>
  201. <tr
  202. className={`district-heading`}
  203. style={{display: props.reveal && !props.total ? '' : 'none'}}
  204. >
  205. <td onClick={(e) => handleSort('district')}>
  206. <div className="heading-content">
  207. <abbr title="District">District</abbr>
  208. <div
  209. style={{
  210. display:
  211. sortData.sortColumn === 'district' ? 'initial' : 'none',
  212. }}
  213. >
  214. {sortData.isAscending ? (
  215. <div className="arrow-up" />
  216. ) : (
  217. <div className="arrow-down" />
  218. )}
  219. </div>
  220. </div>
  221. </td>
  222. <td onClick={(e) => handleSort('confirmed')}>
  223. <div className="heading-content">
  224. <abbr
  225. className={`${window.innerWidth <= 769 ? 'is-cherry' : ''}`}
  226. title="Confirmed"
  227. >
  228. {window.innerWidth <= 769
  229. ? window.innerWidth <= 375
  230. ? 'C'
  231. : 'Cnfmd'
  232. : 'Confirmed'}
  233. </abbr>
  234. <div
  235. style={{
  236. display:
  237. sortData.sortColumn === 'confirmed' ? 'initial' : 'none',
  238. }}
  239. >
  240. {sortData.isAscending ? (
  241. <div className="arrow-up" />
  242. ) : (
  243. <div className="arrow-down" />
  244. )}
  245. </div>
  246. </div>
  247. </td>
  248. </tr>
  249. {sortedDistricts &&
  250. Object.keys(sortedDistricts)
  251. .filter((district) => district.toLowerCase() !== 'unknown')
  252. .map((district, index) => {
  253. if (district.toLowerCase() !== 'unknown') {
  254. return (
  255. <tr
  256. key={index}
  257. className={`district`}
  258. style={{
  259. display: props.reveal && !props.total ? '' : 'none',
  260. background: index % 2 === 0 ? '#f8f9fa' : '',
  261. }}
  262. onMouseEnter={() =>
  263. props.onHighlightDistrict?.(district, state, props.index)
  264. }
  265. onMouseLeave={() => props.onHighlightDistrict?.()}
  266. /* onTouchStart={() =>
  267. props.onHighlightDistrict?.(district, state, props.index)
  268. }*/
  269. >
  270. <td style={{fontWeight: 600}}>{district}</td>
  271. <td>
  272. <span className="deltas" style={{color: '#ff073a'}}>
  273. {sortedDistricts[district].delta.confirmed > 0 && (
  274. <Icon.ArrowUp />
  275. )}
  276. {sortedDistricts[district].delta.confirmed > 0
  277. ? `${sortedDistricts[district].delta.confirmed}`
  278. : ''}
  279. </span>
  280. <span className="table__count-text">
  281. {formatNumber(sortedDistricts[district].confirmed)}
  282. </span>
  283. </td>
  284. </tr>
  285. );
  286. }
  287. return null;
  288. })}
  289. {sortedDistricts?.Unknown && (
  290. <React.Fragment>
  291. <tr
  292. className={`district`}
  293. style={{display: props.reveal && !props.total ? '' : 'none'}}
  294. >
  295. <td style={{fontWeight: 600}}>
  296. Unknown{' '}
  297. <span style={{fontSize: '0.75rem', color: '#201aa299'}}>#</span>
  298. </td>
  299. <td>
  300. <span className="deltas" style={{color: '#ff073a'}}>
  301. {sortedDistricts['Unknown'].delta.confirmed > 0 && (
  302. <Icon.ArrowUp />
  303. )}
  304. {sortedDistricts['Unknown'].delta.confirmed > 0
  305. ? `${sortedDistricts['Unknown'].delta.confirmed}`
  306. : ''}
  307. </span>
  308. <span className="table__count-text">
  309. {formatNumber(sortedDistricts['Unknown'].confirmed)}
  310. </span>
  311. </td>
  312. </tr>
  313. <span
  314. style={{
  315. display: props.reveal && !props.total ? '' : 'none',
  316. fontSize: '0.75rem',
  317. color: '#201aa299',
  318. }}
  319. >
  320. #
  321. </span>
  322. <div
  323. style={{
  324. display: props.reveal && !props.total ? '' : 'none',
  325. fontSize: '0.5rem',
  326. paddingLeft: '1rem',
  327. position: 'absolute',
  328. marginTop: '-0.85rem',
  329. color: '#201aa299',
  330. fontWeight: 600,
  331. }}
  332. >
  333. Awaiting patient-level details from State Bulletin
  334. </div>
  335. </React.Fragment>
  336. )}
  337. <tr
  338. className={`spacer`}
  339. style={{display: props.reveal && !props.total ? '' : 'none'}}
  340. >
  341. <td></td>
  342. <td></td>
  343. <td></td>
  344. </tr>
  345. </React.Fragment>
  346. );
  347. }
  348. export default Row;