SpendingAndStakeDistributionTable.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import React from 'react';
  2. import { Table, Popup, Icon } from 'semantic-ui-react';
  3. import styled from 'styled-components';
  4. import { useWindowDimensions } from '../../../joy-utils/src/react/hooks';
  5. import { TokenomicsData, StatusServerData } from '@polkadot/joy-utils/src/types/tokenomics';
  6. const round = (num: number): number => Math.round((num + Number.EPSILON) * 100) / 100;
  7. const applyCss = (columns: number[]): string => {
  8. let columnString = '';
  9. columns.forEach((column, index) => {
  10. if (index === 0) {
  11. columnString += `td:nth-of-type(${column}), th:nth-of-type(${column})`;
  12. } else {
  13. columnString += ` ,td:nth-of-type(${column}), th:nth-of-type(${column})`;
  14. }
  15. });
  16. return columnString;
  17. };
  18. const StyledTable = styled(({ divideColumnsAt, ...rest }) => <Table {...rest} />)`
  19. border: none !important;
  20. width: 70% !important;
  21. margin-bottom:1.5rem;
  22. @media (max-width: 1400px){
  23. width:100% !important;
  24. }
  25. & tr {
  26. td:nth-of-type(1),
  27. th:nth-of-type(1),
  28. ${(props: { divideColumnsAt: number[]}): string => applyCss(props.divideColumnsAt)} {
  29. border-left: 0.12rem solid rgba(20,20,20,0.3) !important;
  30. }
  31. td:nth-of-type(1){
  32. position: relative !important;
  33. }
  34. td:last-child, th:last-child{
  35. border-right: 0.12rem solid rgba(20,20,20,0.3) !important;
  36. }
  37. }
  38. & tr:last-child > td{
  39. border-bottom: 0.12rem solid rgba(20,20,20,0.3) !important;
  40. }
  41. & tr:last-child > td:nth-of-type(1){
  42. border-bottom-left-radius: 0.2rem !important;
  43. }
  44. & tr:last-child > td:last-child{
  45. border-bottom-right-radius: 0.2rem !important;
  46. }
  47. th{
  48. border-top: 0.12rem solid rgba(20,20,20,0.3) !important;
  49. }
  50. & .tableColorBlock{
  51. height: 1rem;
  52. width:1rem;
  53. margin: 0 auto;
  54. @media (max-width: 768px){
  55. margin: 0;
  56. }
  57. }
  58. `;
  59. const StyledTableRow = styled(Table.Row)`
  60. .help-icon{
  61. position: absolute !important;
  62. right: 0.5rem !important;
  63. top: 0.8rem !important;
  64. @media (max-width: 767px){
  65. top:0.8rem !important;
  66. }
  67. }
  68. `;
  69. const SpendingAndStakeTableRow: React.FC<{
  70. role: string;
  71. numberOfActors?: string;
  72. groupEarning?: string;
  73. groupEarningDollar?: string;
  74. earningShare?: string;
  75. groupStake?: string;
  76. groupStakeDollar?: string;
  77. stakeShare?: string;
  78. color?: string;
  79. active?: boolean;
  80. helpContent?: string;
  81. }> = ({ role, numberOfActors, groupEarning, groupEarningDollar, earningShare, groupStake, groupStakeDollar, stakeShare, color, active, helpContent }) => {
  82. const parseData = (data: string | undefined): string | JSX.Element => {
  83. if (data && active) {
  84. return <em>{data}</em>;
  85. } else if (data) {
  86. return data;
  87. } else {
  88. return 'Loading..';
  89. }
  90. };
  91. return (
  92. <StyledTableRow color={active && 'rgb(150, 150, 150)'}>
  93. <Table.Cell>
  94. {active ? <strong>{role}</strong> : role}
  95. {helpContent && <Popup
  96. trigger={<Icon className='help-icon' name='help circle' color='grey'/>}
  97. content={helpContent}
  98. position='right center'
  99. />}
  100. </Table.Cell>
  101. <Table.Cell>{parseData(numberOfActors)}</Table.Cell>
  102. <Table.Cell>{parseData(groupEarning)}</Table.Cell>
  103. <Table.Cell>{parseData(groupEarningDollar)}</Table.Cell>
  104. <Table.Cell>{parseData(earningShare)}</Table.Cell>
  105. <Table.Cell>{parseData(groupStake)}</Table.Cell>
  106. <Table.Cell>{parseData(groupStakeDollar)}</Table.Cell>
  107. <Table.Cell>{parseData(stakeShare)}</Table.Cell>
  108. <Table.Cell><div className='tableColorBlock' style={{ backgroundColor: color }}></div></Table.Cell>
  109. </StyledTableRow>
  110. );
  111. };
  112. const SpendingAndStakeDistributionTable: React.FC<{data?: TokenomicsData; statusData?: StatusServerData | null}> = ({ data, statusData }) => {
  113. const { width } = useWindowDimensions();
  114. const displayStatusData = (group: 'validators' | 'council' | 'storageProviders' | 'storageProviderLead' | 'contentCurators', action: 'rewardsPerWeek' | 'totalStake'): string | undefined => {
  115. if (group === 'storageProviderLead') {
  116. return statusData === null ? 'Data currently unavailable...' : (data && statusData) && `${(data.storageProviders.lead[action] * Number(statusData.price)).toFixed(2)}`;
  117. } else {
  118. return statusData === null ? 'Data currently unavailable...' : (data && statusData) && `${(data[group][action] * Number(statusData.price)).toFixed(2)}`;
  119. }
  120. };
  121. return (
  122. <StyledTable divideColumnsAt={[3, 6, 9]} celled>
  123. <Table.Header>
  124. <Table.Row>
  125. <Table.HeaderCell width={4}>Group/Role</Table.HeaderCell>
  126. <Table.HeaderCell><div>Actors</div>[Number]</Table.HeaderCell>
  127. <Table.HeaderCell><div>Group earning</div> [JOY/Week]</Table.HeaderCell>
  128. <Table.HeaderCell><div>Group earning</div> [USD/Week]</Table.HeaderCell>
  129. <Table.HeaderCell><div>Share</div> [%]</Table.HeaderCell>
  130. <Table.HeaderCell><div>Group Stake</div> [JOY]</Table.HeaderCell>
  131. <Table.HeaderCell><div>Group Stake</div> [USD]</Table.HeaderCell>
  132. <Table.HeaderCell><div>Share</div> [%]</Table.HeaderCell>
  133. <Table.HeaderCell width={1}>Color</Table.HeaderCell>
  134. </Table.Row>
  135. </Table.Header>
  136. <Table.Body>
  137. <SpendingAndStakeTableRow
  138. role={width <= 1050 ? 'Validators' : 'Validators (Nominators)'}
  139. helpContent='The current set of active Validators (and Nominators), and the sum of the sets projected rewards and total stakes (including Nominators).'
  140. numberOfActors={data && `${data.validators.number} (${data.validators.nominators.number})`}
  141. groupEarning={data && `${Math.round(data.validators.rewardsPerWeek)}`}
  142. groupEarningDollar={displayStatusData('validators', 'rewardsPerWeek')}
  143. earningShare={data && `${round(data.validators.rewardsShare * 100)}`}
  144. groupStake={data && `${data.validators.totalStake}`}
  145. groupStakeDollar={displayStatusData('validators', 'totalStake')}
  146. stakeShare={data && `${round(data.validators.stakeShare * 100)}`}
  147. color='rgb(246, 109, 68)'
  148. />
  149. <SpendingAndStakeTableRow
  150. role={width <= 1015 ? 'Council' : 'Council Members'}
  151. helpContent='The current Council Members, and the sum of their projected rewards and total stakes (including voters/backers).'
  152. numberOfActors={data && `${data.council.number}`}
  153. groupEarning={data && `${Math.round(data.council.rewardsPerWeek)}`}
  154. groupEarningDollar={displayStatusData('council', 'rewardsPerWeek')}
  155. earningShare={data && `${round(data.council.rewardsShare * 100)}`}
  156. groupStake={data && `${data.council.totalStake}`}
  157. groupStakeDollar={displayStatusData('council', 'totalStake')}
  158. stakeShare={data && `${round(data.council.stakeShare * 100)}`}
  159. color='rgb(254, 174, 101)'
  160. />
  161. <SpendingAndStakeTableRow
  162. role={width <= 1015 ? 'Storage' : 'Storage Providers'}
  163. helpContent='The current Storage Providers, and the sum of their projected rewards and stakes.'
  164. numberOfActors={data && `${data.storageProviders.number}`}
  165. groupEarning={data && `${Math.round(data.storageProviders.rewardsPerWeek)}`}
  166. groupEarningDollar={displayStatusData('storageProviders', 'rewardsPerWeek')}
  167. earningShare={data && `${round(data.storageProviders.rewardsShare * 100)}`}
  168. groupStake={data && `${data.storageProviders.totalStake}`}
  169. groupStakeDollar={displayStatusData('storageProviders', 'totalStake')}
  170. stakeShare={data && `${round(data.storageProviders.stakeShare * 100)}`}
  171. color='rgb(230, 246, 157)'
  172. />
  173. <SpendingAndStakeTableRow
  174. role={width <= 1015 ? 'S. Lead' : width <= 1050 ? 'Storage Lead' : 'Storage Provider Lead'}
  175. helpContent='Current Storage Provider Lead, and their projected reward and stake.'
  176. numberOfActors={data && `${data.storageProviders.lead.number}`}
  177. groupEarning={data && `${Math.round(data.storageProviders.lead.rewardsPerWeek)}`}
  178. groupEarningDollar={displayStatusData('storageProviderLead', 'rewardsPerWeek')}
  179. earningShare={data && `${round(data.storageProviders.lead.rewardsShare * 100)}`}
  180. groupStake={data && `${data.storageProviders.lead.totalStake}`}
  181. groupStakeDollar={displayStatusData('storageProviderLead', 'totalStake')}
  182. stakeShare={data && `${round(data.storageProviders.lead.stakeShare * 100)}`}
  183. color='rgb(170, 222, 167)'
  184. />
  185. <SpendingAndStakeTableRow
  186. role={width <= 1015 ? 'Content' : 'Content Curators'}
  187. helpContent='The current Content Curators (and their Lead), and the sum of their projected rewards and stakes.'
  188. numberOfActors={data && `${data.contentCurators.number} (${data.contentCurators.contentCuratorLead})`}
  189. groupEarning={data && `${Math.round(data.contentCurators.rewardsPerWeek)}`}
  190. groupEarningDollar={displayStatusData('contentCurators', 'rewardsPerWeek')}
  191. earningShare={data && `${round(data.contentCurators.rewardsShare * 100)}`}
  192. groupStake={data && `${data.contentCurators.totalStake}`}
  193. groupStakeDollar={displayStatusData('contentCurators', 'totalStake')}
  194. stakeShare={data && `${round(data.contentCurators.stakeShare * 100)}`}
  195. color='rgb(100, 194, 166)'
  196. />
  197. <SpendingAndStakeTableRow
  198. role='TOTAL'
  199. active={true}
  200. numberOfActors={data && `${data.totalNumberOfActors}`}
  201. groupEarning={data && `${Math.round(data.totalWeeklySpending)}`}
  202. groupEarningDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.totalWeeklySpending * Number(statusData.price))}`}
  203. earningShare={data && '100'}
  204. groupStake={data && `${data.currentlyStakedTokens}`}
  205. groupStakeDollar={statusData === null ? 'Data currently unavailable..' : (data && statusData) && `${round(data.currentlyStakedTokens * Number(statusData.price))}`}
  206. stakeShare={data && '100'}
  207. />
  208. </Table.Body>
  209. </StyledTable>
  210. );
  211. };
  212. export default SpendingAndStakeDistributionTable;