Skip to content

Commit b17f207

Browse files
committed
feat: change scheduled leader to big one row instead of multiple columns
1 parent f48d0b6 commit b17f207

File tree

8 files changed

+369
-345
lines changed

8 files changed

+369
-345
lines changed

solfees-fe/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
},
2525
"dependencies": {
2626
"@consta/icons": "^1.1.0",
27-
"@consta/table": "0.1.0-beta",
27+
"@consta/table": "^0.2.0",
2828
"@consta/uikit": "^5.9.2",
2929
"@tanstack/react-query": "^5.53.1",
3030
"@tanstack/react-router": "^1.51.6",

solfees-fe/pnpm-lock.yaml

+5-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

solfees-fe/src/components/layout/PlotLayer.tsx

+8-8
Original file line numberDiff line numberDiff line change
@@ -87,21 +87,21 @@ const CustomTooltip: ContentType<any, any> = ({ active, payload }) => {
8787
</p>
8888
<ul className="recharts-tooltip-item-list">
8989
<li className="recharts-tooltip-item">
90-
<span className="recharts-tooltip-item-name">Tracked Items</span>
91-
<span className="recharts-tooltip-item-separator"> : </span>
90+
<span className="recharts-tooltip-item-name">Compute Units</span>
91+
<span className="recharts-tooltip-item-separator">: </span>
9292
<span className="recharts-tooltip-item-value">{elt.y.toLocaleString("en-US")}</span>
9393
</li>
94-
<li className="recharts-tooltip-item">
94+
<li className="recharts-tooltip-item hidden">
9595
<span className="recharts-tooltip-item-name">DEBUG processed</span>
9696
<span className="recharts-tooltip-item-separator"> : </span>
9797
<span className="recharts-tooltip-item-value">{elt.value.processed}</span>
9898
</li>
99-
<li className="recharts-tooltip-item">
99+
<li className="recharts-tooltip-item hidden">
100100
<span className="recharts-tooltip-item-name">DEBUG confirmed</span>
101101
<span className="recharts-tooltip-item-separator"> : </span>
102102
<span className="recharts-tooltip-item-value">{elt.value.confirmed}</span>
103103
</li>
104-
<li className="recharts-tooltip-item">
104+
<li className="recharts-tooltip-item hidden">
105105
<span className="recharts-tooltip-item-name">DEBUG finalized</span>
106106
<span className="recharts-tooltip-item-separator"> : </span>
107107
<span className="recharts-tooltip-item-value">{elt.value.finalized}</span>
@@ -139,17 +139,17 @@ const CustomTooltip2: ContentType<any, any> = ({ active, payload }) => {
139139
<span className="recharts-tooltip-item-separator"> : </span>
140140
<span className="recharts-tooltip-item-value">{valueInTooltip}</span>
141141
</li>
142-
<li className="recharts-tooltip-item">
142+
<li className="recharts-tooltip-item hidden">
143143
<span className="recharts-tooltip-item-name">DEBUG processed</span>
144144
<span className="recharts-tooltip-item-separator"> : </span>
145145
<span className="recharts-tooltip-item-value">{elt.value.processed}</span>
146146
</li>
147-
<li className="recharts-tooltip-item">
147+
<li className="recharts-tooltip-item hidden">
148148
<span className="recharts-tooltip-item-name">DEBUG confirmed</span>
149149
<span className="recharts-tooltip-item-separator"> : </span>
150150
<span className="recharts-tooltip-item-value">{elt.value.confirmed}</span>
151151
</li>
152-
<li className="recharts-tooltip-item">
152+
<li className="recharts-tooltip-item hidden">
153153
<span className="recharts-tooltip-item-name">DEBUG finalized</span>
154154
<span className="recharts-tooltip-item-separator"> : </span>
155155
<span className="recharts-tooltip-item-value">{elt.value.finalized}</span>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,290 @@
1+
import { withTooltip } from "@consta/uikit/withTooltip";
2+
import { Button } from "@consta/uikit/Button";
3+
import { TooltipProps } from "@consta/uikit/__internal__/src/hocs/withTooltip/withTooltip";
4+
import { ReactNode, useCallback, useDeferredValue, useMemo } from "react";
5+
import { IconInfoCircle } from "@consta/icons/IconInfoCircle";
6+
import { SlotContent, useWebSocketStore } from "../../store/websocketStore.ts";
7+
import { useShallow } from "zustand/react/shallow";
8+
import { useScheduleStore } from "../../store/scheduleStore.ts";
9+
import {
10+
getFakeSlot,
11+
prepareSingeRow,
12+
prepareValidatorRow,
13+
} from "../../common/prepareValidatorRow.ts";
14+
import { Table, TableColumn } from "@consta/table/Table";
15+
import { Validator } from "./Validator.tsx";
16+
import { Slots } from "./Slots.tsx";
17+
import { HeaderDataCell } from "@consta/table/HeaderDataCell";
18+
import { TransactionsHeaderButton } from "./TransactionsHeaderButton.tsx";
19+
import { Transactions } from "./Transactions.tsx";
20+
import { ComputeUnits } from "./ComputeUnits.tsx";
21+
import { EarnedSol } from "./EarnedSol.tsx";
22+
import { SimpleCell } from "./SimpleCell.tsx";
23+
import { percentFromStore } from "../../common/utils.ts";
24+
import { IconEdit } from "@consta/icons/IconEdit";
25+
26+
const ButtonWithTooltip = withTooltip({ content: "Top Tooltip" })(Button);
27+
28+
const InfoButton = (props: TooltipProps): ReactNode => {
29+
return (
30+
<ButtonWithTooltip
31+
as="span"
32+
iconSize="s"
33+
onlyIcon={true}
34+
view="clear"
35+
iconRight={IconInfoCircle}
36+
tooltipProps={props}
37+
/>
38+
);
39+
};
40+
41+
type TableProps = {
42+
onEditFee: (idx: number) => void;
43+
onEditKeys: () => void;
44+
};
45+
46+
export const CustomTable = ({ onEditFee, onEditKeys }: TableProps) => {
47+
const slots2 = useWebSocketStore(useShallow((state) => state.slots2));
48+
const percents = useWebSocketStore(useShallow((state) => state.percents));
49+
const indices = useScheduleStore(useShallow((state) => state.indices));
50+
const leaders = useScheduleStore(useShallow((state) => state.leaders));
51+
52+
const memoFee0 = useCallback(() => onEditFee(0), [onEditFee]);
53+
const memoFee1 = useCallback(() => onEditFee(1), [onEditFee]);
54+
const memoFee2 = useCallback(() => onEditFee(2), [onEditFee]);
55+
56+
const rowsFromSocket2 = useMemo(() => {
57+
const unsorted = slots2 as Record<string, SlotContent[]>;
58+
const result = Object.entries(unsorted)
59+
.sort((a, b) => Number(a[0]) - Number(b[0]))
60+
.map(prepareValidatorRow);
61+
// we always add next leader, but pick only one slot for it
62+
const idx = Math.max(...Object.keys(slots2).map(Number));
63+
const lastSlot = slots2[idx]?.[0]?.slot || 0;
64+
if (lastSlot) {
65+
const nextSlotNumber = (((lastSlot / 4) | 0) + 1) * 4;
66+
const nextLeaderIndex = indices[nextSlotNumber % 432_000] || 0;
67+
const nextLeader = leaders[nextLeaderIndex] || "";
68+
69+
const nextSlotContent = getFakeSlot(nextLeader, nextSlotNumber);
70+
nextSlotContent.commitment = "next-leader";
71+
const nextRow = prepareSingeRow(`scheduled-${nextSlotNumber}`, nextLeader, [nextSlotContent]);
72+
result.push(nextRow);
73+
}
74+
return [...result].reverse();
75+
}, [slots2, indices, leaders]);
76+
77+
const columns: TableColumn<(typeof rowsFromSocket2)[number]>[] = useMemo(() => {
78+
return [
79+
{
80+
minWidth: 150,
81+
title: "Validator",
82+
accessor: "leader",
83+
renderCell: ({ row }) => <Validator slots={row.slots} leader={row.leader} />,
84+
colSpan: ({ row }) =>
85+
row.slots.some((elt) => elt.commitment === "next-leader") ? "end" : undefined,
86+
},
87+
{
88+
minWidth: 170,
89+
title: "Slots",
90+
accessor: "slots",
91+
renderCell: ({ row }) => <Slots items={row.slots} />,
92+
},
93+
{
94+
minWidth: 160,
95+
title: "Transactions",
96+
accessor: "transactions",
97+
renderHeaderCell: ({ title }) => (
98+
<HeaderDataCell controlRight={[<TransactionsHeaderButton onEditKeys={onEditKeys} />]}>
99+
{title}
100+
</HeaderDataCell>
101+
),
102+
renderCell: ({ row }) => <Transactions slots={row.slots} items={row.transactions} />,
103+
},
104+
{
105+
title: "Compute Units",
106+
minWidth: 216,
107+
accessor: "computeUnits",
108+
renderHeaderCell: ({ title }) => (
109+
<HeaderDataCell
110+
controlRight={[<InfoButton content={title as string} direction={"downCenter"} />]}
111+
>
112+
{title}
113+
</HeaderDataCell>
114+
),
115+
renderCell: ({ row }) => <ComputeUnits slots={row.slots} items={row.computeUnits} />,
116+
},
117+
{
118+
minWidth: 160,
119+
title: "Earned SOL",
120+
accessor: "earnedSol",
121+
renderHeaderCell: ({ title }) => (
122+
<HeaderDataCell
123+
controlRight={[<InfoButton content={title as string} direction={"downCenter"} />]}
124+
>
125+
{title}
126+
</HeaderDataCell>
127+
),
128+
renderCell: ({ row }) => <EarnedSol slots={row.slots} items={row.earnedSol} />,
129+
},
130+
{
131+
minWidth: 160,
132+
accessor: "averageFee",
133+
title: "Average Fee",
134+
135+
renderHeaderCell: ({ title }) => (
136+
<HeaderDataCell
137+
controlRight={[<InfoButton content={title as string} direction={"downCenter"} />]}
138+
>
139+
{title}
140+
</HeaderDataCell>
141+
),
142+
renderCell: ({ row }) => <SimpleCell slots={row.slots} items={row.averageFee} />,
143+
},
144+
{
145+
minWidth: 160,
146+
title: "Fee p" + percentFromStore(percents[0]),
147+
accessor: "fee0",
148+
renderHeaderCell: ({ title }) => (
149+
<HeaderDataCell
150+
controlRight={[
151+
<Button
152+
as="span"
153+
iconSize="s"
154+
onlyIcon={true}
155+
view="clear"
156+
iconRight={IconEdit}
157+
onClick={memoFee0}
158+
/>,
159+
]}
160+
>
161+
{title}
162+
</HeaderDataCell>
163+
),
164+
renderCell: ({ row }) => <SimpleCell slots={row.slots} items={row.fee0} />,
165+
},
166+
{
167+
minWidth: 160,
168+
title: "Fee p" + percentFromStore(percents[1]),
169+
accessor: "fee1",
170+
renderHeaderCell: ({ title }) => (
171+
<HeaderDataCell
172+
controlRight={[
173+
<Button
174+
as="span"
175+
iconSize="s"
176+
onlyIcon={true}
177+
view="clear"
178+
iconRight={IconEdit}
179+
onClick={memoFee1}
180+
/>,
181+
]}
182+
>
183+
{title}
184+
</HeaderDataCell>
185+
),
186+
renderCell: ({ row }) => <SimpleCell slots={row.slots} items={row.fee1} />,
187+
},
188+
{
189+
minWidth: 160,
190+
title: "Fee p" + percentFromStore(percents[2]),
191+
accessor: "fee2",
192+
renderHeaderCell: ({ title }) => (
193+
<HeaderDataCell
194+
controlRight={[
195+
<Button
196+
as="span"
197+
iconSize="s"
198+
onlyIcon={true}
199+
view="clear"
200+
iconRight={IconEdit}
201+
onClick={memoFee2}
202+
/>,
203+
]}
204+
>
205+
{title}
206+
</HeaderDataCell>
207+
),
208+
renderCell: ({ row }) => <SimpleCell slots={row.slots} items={row.fee2} />,
209+
},
210+
];
211+
}, [onEditKeys, percents, memoFee0, memoFee1, memoFee2]);
212+
213+
const deferredValue = useDeferredValue(rowsFromSocket2);
214+
215+
// For testing with simple table layout (no components, only HTML tags)
216+
const isSimple = false;
217+
if (isSimple)
218+
return (
219+
<>
220+
<div className="w-full overflow-x-auto">
221+
{rowsFromSocket2.map((row, _idx) => {
222+
const key = `${row.leader}-${((row.slots[0]?.slot || 0) / 4) | 0}`;
223+
return (
224+
<div className="border flex flex-row gap-1" key={key}>
225+
<div className="bg-red-100">{row.leader}</div>
226+
<div>
227+
{row.slots.map((slot) => (
228+
<div className="bg-green-100" key={slot.slot}>
229+
{slot.commitment}-{slot.slot}
230+
</div>
231+
))}
232+
</div>
233+
<div>
234+
{row.fee0.map((elt, idx) => (
235+
<div className="bg-blue-100" key={idx}>
236+
{elt}
237+
</div>
238+
))}
239+
</div>
240+
<div>
241+
{row.fee1.map((elt, idx) => (
242+
<div className="bg-red-100" key={idx}>
243+
{elt}
244+
</div>
245+
))}
246+
</div>
247+
<div>
248+
{row.averageFee.map((elt, idx) => (
249+
<div className="bg-green-100" key={idx}>
250+
{elt}
251+
</div>
252+
))}
253+
</div>
254+
<div>
255+
{row.earnedSol.map((elt, idx) => (
256+
<div className="bg-blue-100" key={idx}>
257+
{elt}
258+
</div>
259+
))}
260+
</div>
261+
<div>
262+
{row.computeUnits.map((elt, idx) => (
263+
<div className="bg-red-100" key={idx}>
264+
{elt.amount}-{elt.percent}
265+
</div>
266+
))}
267+
</div>
268+
</div>
269+
);
270+
})}
271+
</div>
272+
</>
273+
);
274+
275+
if (!rowsFromSocket2.length)
276+
return <span className="w-full h-full text-center text-xl font-bold">Loading...</span>;
277+
278+
return (
279+
<div className="w-full h-full overflow-x-auto">
280+
<Table
281+
className="overflow-scroll"
282+
columns={columns}
283+
rows={deferredValue}
284+
style={{ maxHeight: "100%" }}
285+
virtualScroll={false}
286+
resizable={undefined}
287+
/>
288+
</div>
289+
);
290+
};

0 commit comments

Comments
 (0)