index.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. import React, { useCallback } from 'react';
  2. import { View, Text, Image, TouchableOpacity, ScrollView } from 'react-native';
  3. import { Colors } from 'src/theme';
  4. import { styles } from './styles';
  5. import { Loading } from 'src/components';
  6. import CheckSvg from 'assets/icons/mark.svg';
  7. import CloseSVG from 'assets/icons/close.svg';
  8. import { API_HOST } from 'src/constants';
  9. import { FlashList } from '@shopify/flash-list';
  10. interface Mega {
  11. id: number;
  12. name: string;
  13. regions: {
  14. id: number;
  15. name: string;
  16. flag1: string;
  17. flag2: string | null;
  18. }[];
  19. transits: number[];
  20. visits: number[];
  21. }
  22. interface Region {
  23. id: number;
  24. name: string;
  25. flag1: string;
  26. flag2: string | null;
  27. country_id: number;
  28. }
  29. const RegionsRenderer = ({
  30. type,
  31. regions,
  32. setIsModalVisible
  33. }: {
  34. type: string;
  35. regions: any;
  36. setIsModalVisible: (value: boolean) => void;
  37. }) => {
  38. const flashlistConfig = {
  39. waitForInteraction: true,
  40. itemVisiblePercentThreshold: 50,
  41. minimumViewTime: 1000
  42. };
  43. const getOpacity = useCallback(
  44. (item: any, mega?: Mega) => {
  45. switch (type) {
  46. case 'nm':
  47. case 'mqp':
  48. return mega?.visits.includes(item.id) ? 1 : 0.4;
  49. case 'un':
  50. case 'unp':
  51. case 'tcc':
  52. return regions.data[1].includes(item.name) ? 1 : 0.4;
  53. case 'slow':
  54. return item.visited === 1 ? 1 : 0.4;
  55. case 'whs':
  56. return item.visited ? 1 : 0.4;
  57. default:
  58. return 1;
  59. }
  60. },
  61. [type, regions?.data]
  62. );
  63. const renderRegion = useCallback(
  64. (item: any, mega?: Mega) => {
  65. return (
  66. <View style={[styles.regionRow, { opacity: getOpacity(item, mega) }]}>
  67. <View
  68. style={[
  69. styles.flags,
  70. type === 'yes' ? { flex: 2 } : type === 'slow' ? { flex: 1 } : {}
  71. ]}
  72. >
  73. <Image
  74. source={{
  75. uri:
  76. API_HOST +
  77. (type === 'whs'
  78. ? item.flag
  79. : type === 'slow'
  80. ? '/img/flags_new/' + item?.flag
  81. : '/img/flags_new/' + item?.flag1)
  82. }}
  83. style={styles.regionsFlag}
  84. />
  85. {item?.flag2 ? (
  86. <Image
  87. source={{ uri: API_HOST + '/img/flags_new/' + item.flag2 }}
  88. style={[styles.regionsFlag, { marginLeft: -18 }]}
  89. />
  90. ) : null}
  91. <View style={styles.regionInfo}>
  92. <Text style={styles.regionName}>{type === 'slow' ? item.country : item?.name}</Text>
  93. </View>
  94. </View>
  95. {type === 'nm' && (
  96. <View style={{ flexDirection: 'row', flex: 2 }}>
  97. <View style={{ width: '56%', alignItems: 'center', marginRight: '2%' }}>
  98. {regions.data.firsts[item?.id] && regions.data.firsts[item?.id] !== 1 ? (
  99. <Text style={[styles.regionName, { fontSize: 12, fontWeight: '700' }]}>
  100. {regions.data.firsts[item?.id]}
  101. </Text>
  102. ) : null}
  103. </View>
  104. <View style={{ width: '42%', alignItems: 'center' }}>
  105. {regions.data.last[item?.id] && regions.data.last[item?.id] !== 1 ? (
  106. <Text style={[styles.regionName, { fontSize: 12, fontWeight: '700' }]}>
  107. {regions.data.last[item?.id]}
  108. </Text>
  109. ) : null}
  110. </View>
  111. </View>
  112. )}
  113. {type === 'yes' && (
  114. <View style={{ flexDirection: 'row', flex: 1 }}>
  115. <View style={{ width: '60%', alignItems: 'center' }}>
  116. {regions.data[1][item?.id] && regions.data[1][item?.id].year !== 1 ? (
  117. <Text style={[styles.regionName, { fontSize: 12, fontWeight: '700' }]}>
  118. {regions.data[1][item?.id].year}
  119. </Text>
  120. ) : null}
  121. </View>
  122. <View style={{ width: '40%', alignItems: 'center' }}>
  123. {regions.data[1][item?.id] ? (
  124. <Text style={[styles.regionName, { fontSize: 12, fontWeight: '700' }]}>
  125. {regions.data[1][item?.id].score}
  126. </Text>
  127. ) : null}
  128. </View>
  129. </View>
  130. )}
  131. {type === 'slow' && (
  132. <View style={{ flexDirection: 'row', flex: 1 }}>
  133. <View style={{ width: '33%', alignItems: 'center' }}>
  134. {item.slow11 === 1 ? (
  135. <CheckSvg fill={Colors.DARK_BLUE} width={14} height={14} />
  136. ) : null}
  137. </View>
  138. <View style={{ width: '33%', alignItems: 'center' }}>
  139. {item.slow31 === 1 ? (
  140. <CheckSvg fill={Colors.DARK_BLUE} width={14} height={14} />
  141. ) : null}
  142. </View>
  143. <View style={{ width: '33%', alignItems: 'center' }}>
  144. {item.slow101 === 1 ? (
  145. <CheckSvg fill={Colors.DARK_BLUE} width={14} height={14} />
  146. ) : null}
  147. </View>
  148. </View>
  149. )}
  150. </View>
  151. );
  152. },
  153. [getOpacity, regions?.data, type]
  154. );
  155. const renderMegaregion = useCallback(
  156. ({ item }: { item: Mega }) => {
  157. return (
  158. <View style={styles.megaregion}>
  159. <View style={styles.blockTitle}>
  160. <Text style={[styles.megaregionTitle, type === 'mqp' ? { textAlign: 'center' } : {}]}>
  161. {type === 'nm' ? '- ' : ''}
  162. {item.name}
  163. <Text style={{ fontWeight: '600' }}>
  164. {' '}
  165. - {item.visits.length}/{item.regions.length} (
  166. {((item.visits.length * 100) / item.regions.length).toFixed(2)}%)
  167. </Text>
  168. </Text>
  169. {type === 'nm' && (
  170. <View style={{ flexDirection: 'row', flex: 2 }}>
  171. <View style={{ width: '56%', alignItems: 'center', marginRight: '2%' }}>
  172. <Text style={styles.alignCenter}>
  173. <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>First</Text>
  174. {'\n'}
  175. <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>visited</Text>
  176. </Text>
  177. </View>
  178. <View style={{ width: '42%', alignItems: 'center' }}>
  179. <Text style={styles.alignCenter}>
  180. <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Last</Text>
  181. {'\n'}
  182. <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>visit</Text>
  183. </Text>
  184. </View>
  185. </View>
  186. )}
  187. </View>
  188. <View style={styles.megaSubList}>
  189. <ScrollView
  190. style={{ flex: 1 }}
  191. nestedScrollEnabled={true}
  192. showsVerticalScrollIndicator={false}
  193. horizontal={false}
  194. >
  195. {item.regions?.map((region: any) => (
  196. <View key={region.id}>{renderRegion(region, item)}</View>
  197. ))}
  198. </ScrollView>
  199. </View>
  200. </View>
  201. );
  202. },
  203. [flashlistConfig, renderRegion, type]
  204. );
  205. const renderContent = () => {
  206. const renderHeader = (headerText: string) => (
  207. <RegionsModalHeader textHeader={headerText} onRequestClose={() => setIsModalVisible(false)} />
  208. );
  209. switch (type) {
  210. case 'nm':
  211. case 'mqp':
  212. return (
  213. <>
  214. {renderHeader(type === 'nm' ? 'NM' : 'DARE')}
  215. <FlashList
  216. viewabilityConfig={flashlistConfig}
  217. estimatedItemSize={4000}
  218. data={regions.data.megaregions}
  219. renderItem={renderMegaregion}
  220. keyExtractor={(megaregion) => megaregion?.id?.toString()}
  221. showsVerticalScrollIndicator={false}
  222. nestedScrollEnabled={true}
  223. contentContainerStyle={{ paddingTop: 8 }}
  224. />
  225. </>
  226. );
  227. case 'un':
  228. case 'unp':
  229. case 'tcc':
  230. return (
  231. <>
  232. {renderHeader(type === 'un' ? 'UN' : type === 'unp' ? 'UN+' : 'TCC')}
  233. <FlashList
  234. viewabilityConfig={flashlistConfig}
  235. estimatedItemSize={50}
  236. data={regions.data[0]}
  237. renderItem={(region) => renderRegion(region.item)}
  238. keyExtractor={(region: Region) => region.name}
  239. showsVerticalScrollIndicator={false}
  240. contentContainerStyle={{ paddingTop: 8 }}
  241. ListHeaderComponent={() => (
  242. <View style={styles.blockTitle}>
  243. <Text style={[styles.megaregionTitle, { textAlign: 'center' }]}>
  244. {type === 'un' ? 'Countries' : 'Territories'}
  245. <Text style={{ fontWeight: '600' }}>
  246. {' '}
  247. - {regions.data[3]}/{regions.data[2]} (
  248. {((regions.data[3] * 100) / regions.data[2]).toFixed(2)}%)
  249. </Text>
  250. </Text>
  251. </View>
  252. )}
  253. />
  254. </>
  255. );
  256. case 'slow':
  257. return (
  258. <>
  259. {renderHeader('SLOW')}
  260. <FlashList
  261. viewabilityConfig={flashlistConfig}
  262. estimatedItemSize={50}
  263. data={regions.data[0]}
  264. renderItem={(region) => renderRegion(region.item)}
  265. keyExtractor={(region: Region) => region.country_id.toString()}
  266. showsVerticalScrollIndicator={false}
  267. contentContainerStyle={{ paddingTop: 8 }}
  268. ListHeaderComponent={() => {
  269. return (
  270. <View style={styles.blockTitle}>
  271. <Text style={[styles.megaregionTitle, { flex: 1 }]}>Countries</Text>
  272. <View style={{ flexDirection: 'row', flex: 1 }}>
  273. <View style={styles.slow}>
  274. <Text style={styles.alignCenter}>
  275. <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Slow11</Text>
  276. </Text>
  277. </View>
  278. <View style={styles.slow}>
  279. <Text style={styles.alignCenter}>
  280. <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Slow31</Text>
  281. </Text>
  282. </View>
  283. <View style={styles.slow}>
  284. <Text style={styles.alignCenter}>
  285. <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Slow101</Text>
  286. </Text>
  287. </View>
  288. </View>
  289. </View>
  290. );
  291. }}
  292. />
  293. </>
  294. );
  295. case 'yes':
  296. const calcTotalScore = (
  297. values: {
  298. year: number;
  299. score: number;
  300. country: string;
  301. }[]
  302. ) => {
  303. const totalScore = values.reduce((accumulator, item) => {
  304. return accumulator + item.score;
  305. }, 0);
  306. return totalScore;
  307. };
  308. return (
  309. <>
  310. {renderHeader('YES')}
  311. <FlashList
  312. viewabilityConfig={flashlistConfig}
  313. estimatedItemSize={50}
  314. data={regions.data[0]}
  315. renderItem={(region) => renderRegion(region.item)}
  316. keyExtractor={(region: Region) => region.id.toString()}
  317. showsVerticalScrollIndicator={false}
  318. contentContainerStyle={{ paddingTop: 8 }}
  319. ListHeaderComponent={() => {
  320. return (
  321. <View style={styles.blockTitle}>
  322. <Text style={[styles.megaregionTitle, { flex: 2 }]}>
  323. Countries
  324. <Text style={{ fontWeight: '600' }}>
  325. {' total score '}
  326. {calcTotalScore(Object.values(regions.data[1]))}
  327. </Text>
  328. </Text>
  329. <View style={{ flexDirection: 'row', flex: 1 }}>
  330. <View style={{ width: '60%', alignItems: 'center' }}>
  331. <Text style={styles.alignCenter}>
  332. <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Last visit</Text>
  333. </Text>
  334. </View>
  335. <View
  336. style={{ width: '40%', alignItems: 'center', justifyContent: 'center' }}
  337. >
  338. <Text style={styles.alignCenter}>
  339. <Text style={[styles.megaregionTitle, { fontSize: 12 }]}>Score</Text>
  340. </Text>
  341. </View>
  342. </View>
  343. </View>
  344. );
  345. }}
  346. />
  347. </>
  348. );
  349. case 'whs':
  350. return (
  351. <>
  352. {renderHeader('WHS')}
  353. <FlashList
  354. viewabilityConfig={flashlistConfig}
  355. estimatedItemSize={50}
  356. data={regions.data}
  357. renderItem={(region) => renderRegion(region.item)}
  358. keyExtractor={(region: Region) => region.name}
  359. showsVerticalScrollIndicator={false}
  360. contentContainerStyle={{ paddingTop: 8 }}
  361. />
  362. </>
  363. );
  364. }
  365. };
  366. return <View style={styles.modalContent}>{regions?.data ? renderContent() : <Loading />}</View>;
  367. };
  368. const RegionsModalHeader = ({
  369. textHeader,
  370. onRequestClose,
  371. rightElement
  372. }: {
  373. textHeader: string;
  374. onRequestClose: () => void;
  375. rightElement?: any;
  376. }) => {
  377. return (
  378. <View style={styles.header}>
  379. <TouchableOpacity onPress={onRequestClose} style={{ padding: 6 }}>
  380. <CloseSVG />
  381. </TouchableOpacity>
  382. <Text style={styles.headerText}>{textHeader}</Text>
  383. {rightElement ? rightElement : <View style={{ height: 30, width: 30 }} />}
  384. </View>
  385. );
  386. };
  387. export default RegionsRenderer;