Skip to content

Commit 007844f

Browse files
committed
[DDW-1012] Stake pool list performance
1 parent ce56d89 commit 007844f

File tree

13 files changed

+807
-662
lines changed

13 files changed

+807
-662
lines changed

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
"@types/qrcode.react": "^1.0.2",
104104
"@types/react": "^17.0.38",
105105
"@types/react-svg-inline": "^2.1.3",
106+
"@types/react-table": "^7.7.9",
106107
"@typescript-eslint/eslint-plugin": "^5.10.1",
107108
"@typescript-eslint/parser": "^5.10.1",
108109
"asar": "2.1.0",
@@ -260,6 +261,7 @@
260261
"react-router": "5.2.0",
261262
"react-router-dom": "5.2.0",
262263
"react-svg-inline": "2.1.1",
264+
"react-table": "7.7.0",
263265
"react-virtualized": "9.22.3",
264266
"recharts": "1.8.5",
265267
"rimraf": "3.0.2",
Lines changed: 324 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
import React, { useMemo } from 'react';
2+
import { orderBy } from 'lodash';
3+
import classNames from 'classnames';
4+
import BigNumber from 'bignumber.js';
5+
import moment from 'moment';
6+
import { FormattedHTMLMessage } from 'react-intl';
7+
import { PopOver } from 'react-polymorph/lib/components/PopOver';
8+
import { PoolPopOver } from '../widgets/PoolPopOver';
9+
import { Intl } from '../../../types/i18nTypes';
10+
11+
import styles from './StakePoolsTable.scss';
12+
import StakePool from '../../../domains/StakePool';
13+
import { getColorFromRange, getSaturationColor } from '../../../utils/colors';
14+
import {
15+
formattedWalletAmount,
16+
toFixedUserFormat,
17+
} from '../../../utils/formatters';
18+
import { messages } from './StakePoolsTable.messages';
19+
20+
export const defaultTableOrdering = {
21+
ranking: 'asc',
22+
ticker: 'asc',
23+
saturation: 'asc',
24+
cost: 'asc',
25+
profitMargin: 'asc',
26+
producedBlocks: 'desc',
27+
nonMyopicMemberRewards: 'desc',
28+
pledge: 'asc',
29+
retiring: 'asc',
30+
};
31+
32+
interface UseSortedStakePoolListArgs {
33+
stakePoolList: StakePool[];
34+
sortBy: string;
35+
order: 'asc' | 'desc';
36+
}
37+
38+
export const useSortedStakePoolList = ({
39+
stakePoolList,
40+
sortBy,
41+
order,
42+
}: UseSortedStakePoolListArgs) =>
43+
useMemo(
44+
() =>
45+
orderBy(
46+
stakePoolList.map((stakePool) => {
47+
let calculatedPledge;
48+
let calculatedCost;
49+
let formattedTicker;
50+
51+
if (sortBy === 'ticker') {
52+
formattedTicker = stakePool.ticker
53+
.replace(/[^\w\s]/gi, '')
54+
.toLowerCase();
55+
}
56+
57+
if (sortBy === 'pledge') {
58+
const formattedPledgeValue = stakePool.pledge.toFixed(2);
59+
calculatedPledge = Number(
60+
parseFloat(formattedPledgeValue).toFixed(2)
61+
);
62+
}
63+
64+
if (sortBy === 'cost') {
65+
const formattedCostValue = stakePool.cost.toFixed(2);
66+
calculatedCost = Number(parseFloat(formattedCostValue).toFixed(2));
67+
}
68+
69+
return {
70+
...stakePool,
71+
calculatedPledge,
72+
calculatedCost,
73+
formattedTicker,
74+
};
75+
}),
76+
['formattedTicker', 'calculatedPledge', 'calculatedCost', sortBy],
77+
[order, order, order, order]
78+
),
79+
[stakePoolList, order, sortBy]
80+
);
81+
82+
type UseCreateColumnsArgs = {
83+
currentTheme: string;
84+
showWithSelectButton?: boolean;
85+
onSelect?: (...args: Array<any>) => any;
86+
containerClassName: string;
87+
numberOfRankedStakePools: number;
88+
selectedPoolId?: string;
89+
onOpenExternalLink: (...args: Array<any>) => any;
90+
intl: Intl;
91+
};
92+
93+
export const useCreateColumns = ({
94+
numberOfRankedStakePools,
95+
intl,
96+
currentTheme,
97+
onOpenExternalLink,
98+
onSelect,
99+
selectedPoolId,
100+
containerClassName,
101+
showWithSelectButton,
102+
}: UseCreateColumnsArgs) =>
103+
useMemo(
104+
() => [
105+
{
106+
id: 'ranking',
107+
Header: (
108+
<PopOver
109+
key="ranking"
110+
placement="bottom"
111+
content={
112+
<div className={styles.tooltipWithHtmlContent}>
113+
<FormattedHTMLMessage {...messages.tableHeaderRankTooltip} />
114+
</div>
115+
}
116+
>
117+
{intl.formatMessage(messages.tableHeaderRank)}
118+
</PopOver>
119+
),
120+
accessor: 'ranking',
121+
Cell: ({ row }) => {
122+
const { potentialRewards, ranking }: StakePool = row.original;
123+
const memberRewards = new BigNumber(potentialRewards);
124+
125+
return (
126+
<>
127+
{!memberRewards.isZero() ? (
128+
ranking
129+
) : (
130+
<>
131+
{numberOfRankedStakePools + 1}
132+
<span className={styles.asterisk}>*</span>
133+
</>
134+
)}
135+
</>
136+
);
137+
},
138+
},
139+
140+
{
141+
id: 'ticker',
142+
Header: intl.formatMessage(messages.tableHeaderTicker),
143+
accessor: 'ticker',
144+
Cell: ({ row }) => {
145+
const stakePool: StakePool = row.original;
146+
const color = getColorFromRange(
147+
stakePool.ranking,
148+
numberOfRankedStakePools
149+
);
150+
151+
return (
152+
<PoolPopOver
153+
color={color}
154+
currentTheme={currentTheme}
155+
onOpenExternalLink={onOpenExternalLink}
156+
onSelect={onSelect}
157+
isSelected={selectedPoolId === stakePool.id}
158+
stakePool={stakePool}
159+
containerClassName={containerClassName}
160+
numberOfRankedStakePools={numberOfRankedStakePools}
161+
showWithSelectButton={showWithSelectButton}
162+
>
163+
<span className={styles.ticker} role="presentation">
164+
{stakePool.ticker}
165+
</span>
166+
</PoolPopOver>
167+
);
168+
},
169+
},
170+
{
171+
id: 'saturation',
172+
Header: (
173+
<PopOver
174+
key="saturation"
175+
placement="bottom"
176+
content={intl.formatMessage(messages.tableHeaderSaturationTooltip)}
177+
>
178+
{intl.formatMessage(messages.tableHeaderSaturation)}
179+
</PopOver>
180+
),
181+
accessor: 'saturation',
182+
Cell: ({ row }) => {
183+
const { saturation }: StakePool = row.original;
184+
const progressBarContentClassnames = classNames([
185+
styles.progressBarContent,
186+
styles[getSaturationColor(saturation)],
187+
]);
188+
189+
return (
190+
<div className={styles.saturation}>
191+
<div className={styles.progressBar}>
192+
<div className={styles.progressBarContainer}>
193+
<div
194+
className={progressBarContentClassnames}
195+
style={{
196+
width: `${saturation.toFixed(2)}%`,
197+
}}
198+
/>
199+
</div>
200+
</div>
201+
<div className={styles.saturationLabel}>
202+
{`${toFixedUserFormat(saturation, 2)}%`}
203+
</div>
204+
</div>
205+
);
206+
},
207+
},
208+
{
209+
id: 'cost',
210+
Header: (
211+
<PopOver
212+
key="cost"
213+
placement="bottom"
214+
content={intl.formatMessage(messages.tableHeaderCostTooltip)}
215+
>
216+
{intl.formatMessage(messages.tableHeaderCost)}
217+
</PopOver>
218+
),
219+
accessor: 'cost',
220+
Cell: ({ value }) => {
221+
const cost = new BigNumber(value);
222+
const costValue = formattedWalletAmount(cost, false, false);
223+
224+
return costValue;
225+
},
226+
},
227+
{
228+
id: 'profitMargin',
229+
Header: (
230+
<PopOver
231+
key="profitMargin"
232+
placement="bottom"
233+
content={intl.formatMessage(messages.tableHeaderMarginTooltip)}
234+
>
235+
{intl.formatMessage(messages.tableHeaderMargin)}
236+
</PopOver>
237+
),
238+
accessor: 'profitMargin',
239+
Cell: ({ value }) => {
240+
return `${toFixedUserFormat(value, 2)}%`;
241+
},
242+
},
243+
{
244+
id: 'producedBlocks',
245+
Header: (
246+
<PopOver
247+
key="producedBlocks"
248+
placement="bottom"
249+
content={intl.formatMessage(
250+
messages.tableHeaderProducedBlocksTooltip
251+
)}
252+
>
253+
{intl.formatMessage(messages.tableHeaderProducedBlocks)}
254+
</PopOver>
255+
),
256+
accessor: 'producedBlocks',
257+
Cell: ({ value }) => {
258+
return toFixedUserFormat(value, 0);
259+
},
260+
},
261+
{
262+
id: 'nonMyopicMemberRewards',
263+
Header: (
264+
<PopOver
265+
key="nonMyopicMemberRewards"
266+
placement="bottom"
267+
content={intl.formatMessage(
268+
messages.tableHeaderPotentialRewardsTooltip
269+
)}
270+
>
271+
{intl.formatMessage(messages.tableHeaderPotentialRewards)}
272+
</PopOver>
273+
),
274+
accessor: 'nonMyopicMemberRewards',
275+
Cell: ({ row }) => {
276+
const stakePool: StakePool = row.original;
277+
const memberRewards = new BigNumber(stakePool.potentialRewards);
278+
const potentialRewards = formattedWalletAmount(memberRewards);
279+
return potentialRewards;
280+
},
281+
},
282+
{
283+
id: 'pledge',
284+
Header: (
285+
<PopOver
286+
key="pledge"
287+
placement="bottom"
288+
content={intl.formatMessage(messages.tableHeaderPledgeTooltip)}
289+
>
290+
{intl.formatMessage(messages.tableHeaderPledge)}
291+
</PopOver>
292+
),
293+
accessor: 'pledge',
294+
Cell: ({ row }) => {
295+
const stakePool: StakePool = row.original;
296+
const pledge = new BigNumber(stakePool.pledge);
297+
const pledgeValue = formattedWalletAmount(pledge, false, false);
298+
return pledgeValue;
299+
},
300+
},
301+
{
302+
id: 'retiring',
303+
Header: intl.formatMessage(messages.tableHeaderRetiring),
304+
accessor: 'retiring',
305+
Cell: ({ row }) => {
306+
const stakePool: StakePool = row.original;
307+
const retirement =
308+
stakePool.retiring &&
309+
moment(stakePool.retiring).locale(intl.locale).fromNow(true);
310+
311+
return (
312+
<>
313+
{retirement ? (
314+
<span className={styles.retiring}>{retirement}</span>
315+
) : (
316+
'-'
317+
)}
318+
</>
319+
);
320+
},
321+
},
322+
],
323+
[]
324+
);

0 commit comments

Comments
 (0)