Skip to content

Commit b511a69

Browse files
committed
[TOOL-3056]: Add Engine transactions bar chart, Make shadcn table header sticky (#5963)
<!-- start pr-codex --> ## PR-Codex overview This PR focuses on enhancing the user interface and functionality of various components in the dashboard application, particularly around data visualization and table displays. ### Detailed summary - Updated `chartClassName` for better aspect ratio in multiple chart components. - Added `tableScrollableClassName` and `tableContainerClassName` to improve table layouts. - Modified text in `engine-overview.tsx` for clarity. - Introduced new props in `ThirdwebBarChart` for customizable tooltips and titles. - Implemented data processing logic in `TransactionsTable` for analytics display. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent ea34a97 commit b511a69

File tree

8 files changed

+115
-16
lines changed

8 files changed

+115
-16
lines changed

apps/dashboard/src/@/components/blocks/charts/bar-chart.tsx

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {
2323
EmptyChartState,
2424
LoadingChartState,
2525
} from "../../../../components/analytics/empty-chart-state";
26+
import { cn } from "../../../lib/utils";
2627

2728
type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
2829
// metadata
@@ -36,6 +37,9 @@ type ThirdwebBarChartProps<TConfig extends ChartConfig> = {
3637
// chart className
3738
chartClassName?: string;
3839
isPending: boolean;
40+
toolTipLabelFormatter?: (label: string, payload: unknown) => React.ReactNode;
41+
hideLabel?: boolean;
42+
titleClassName?: string;
3943
};
4044

4145
export function ThirdwebBarChart<TConfig extends ChartConfig>(
@@ -45,10 +49,13 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
4549
// if there are more than 4 keys then we should stack them by default
4650
const variant =
4751
props.variant || configKeys.length > 4 ? "stacked" : "grouped";
52+
4853
return (
4954
<Card>
5055
<CardHeader>
51-
<CardTitle className="mb-2">{props.title}</CardTitle>
56+
<CardTitle className={cn("mb-2", props.titleClassName)}>
57+
{props.title}
58+
</CardTitle>
5259
{props.description && (
5360
<CardDescription>{props.description}</CardDescription>
5461
)}
@@ -69,7 +76,16 @@ export function ThirdwebBarChart<TConfig extends ChartConfig>(
6976
tickMargin={10}
7077
tickFormatter={(value) => formatDate(new Date(value), "MMM d")}
7178
/>
72-
<ChartTooltip content={<ChartTooltipContent hideLabel />} />
79+
<ChartTooltip
80+
content={
81+
<ChartTooltipContent
82+
hideLabel={
83+
props.hideLabel !== undefined ? props.hideLabel : true
84+
}
85+
labelFormatter={props.toolTipLabelFormatter}
86+
/>
87+
}
88+
/>
7389
{props.showLegend && (
7490
<ChartLegend content={<ChartLegendContent />} />
7591
)}

apps/dashboard/src/@/components/ui/table.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ const TableHeader = React.forwardRef<
2525
<thead
2626
ref={ref}
2727
className={cn(
28-
"border-border border-b bg-background [&_tr]:border-b",
28+
"border-border border-b bg-background",
29+
"sticky top-0 z-20 border-none before:absolute before:inset-0 before:border-border before:border-b",
2930
className,
3031
)}
3132
{...props}
@@ -125,10 +126,20 @@ const TableCaption = React.forwardRef<
125126
));
126127
TableCaption.displayName = "TableCaption";
127128

128-
function TableContainer(props: { children: React.ReactNode }) {
129+
function TableContainer(props: {
130+
children: React.ReactNode;
131+
className?: string;
132+
scrollableContainerClassName?: string;
133+
}) {
129134
return (
130135
<ScrollShadow
131-
className="relative whitespace-nowrap rounded-lg border border-border bg-card"
136+
shadowClassName="z-30"
137+
disableTopShadow
138+
className={cn(
139+
"relative whitespace-nowrap rounded-lg border border-border bg-card",
140+
props.className,
141+
)}
142+
scrollableClassName={props.scrollableContainerClassName}
132143
shadowColor="hsl(var(--muted))"
133144
>
134145
{props.children}

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/analytics/ContractAnalyticsPage.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ function UniqueWalletsChart(props: ChartProps) {
136136
color: "hsl(var(--chart-1))",
137137
},
138138
}}
139-
chartClassName="aspect[2] lg:aspect-[4.5]"
139+
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
140140
/>
141141
);
142142
}
@@ -161,7 +161,7 @@ function TotalTransactionsChart(props: ChartProps) {
161161
color: "hsl(var(--chart-1))",
162162
},
163163
}}
164-
chartClassName="aspect[2] lg:aspect-[4.5]"
164+
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
165165
/>
166166
);
167167
}
@@ -181,7 +181,7 @@ function TotalEventsChart(props: ChartProps) {
181181
color: "hsl(var(--chart-1))",
182182
},
183183
}}
184-
chartClassName="aspect[2] lg:aspect-[4.5]"
184+
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
185185
/>
186186
);
187187
}
@@ -233,7 +233,7 @@ function FunctionBreakdownChart(
233233
},
234234
{} as Record<string, { label: string; color: string }>,
235235
)}
236-
chartClassName="aspect[2] lg:aspect-[4.5]"
236+
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
237237
showLegend
238238
/>
239239
);
@@ -286,7 +286,7 @@ function EventBreakdownChart(
286286
},
287287
{} as Record<string, { label: string; color: string }>,
288288
)}
289-
chartClassName="aspect[2] lg:aspect-[4.5]"
289+
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
290290
showLegend
291291
/>
292292
);

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/overview/components/Analytics.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ function OverviewAnalytics(props: ChartProps) {
135135
data={mergedData || []}
136136
isPending={isPending}
137137
showLegend
138-
chartClassName="aspect[2] lg:aspect-[4.5]"
138+
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
139139
/>
140140
);
141141
}

apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/backend-wallets-table.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ export const BackendWalletsTable: React.FC<BackendWalletsTableProps> = ({
181181
columns={columns as ColumnDef<BackendWallet, string>[]}
182182
isPending={isPending}
183183
isFetched={isFetched}
184+
tableScrollableClassName="max-h-[1000px]"
184185
onMenuClick={[
185186
{
186187
icon: <PencilIcon className="size-4" />,

apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/engine-overview.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,7 @@ function TransactionsSection(props: {
149149
Transactions
150150
</h2>
151151
<p className="text-muted-foreground">
152-
View transactions sent from your backend wallets in the past 24
153-
hours.
152+
View transactions sent from your backend wallets
154153
</p>
155154
</div>
156155

apps/dashboard/src/app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/overview/components/transactions-table.tsx

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { ThirdwebBarChart } from "@/components/blocks/charts/bar-chart";
12
import { WalletAddress } from "@/components/blocks/wallet-address";
23
import { CopyAddressButton } from "@/components/ui/CopyAddressButton";
34
import { CopyTextButton } from "@/components/ui/CopyTextButton";
45
import { Badge } from "@/components/ui/badge";
6+
import type { ChartConfig } from "@/components/ui/chart";
57
import {
68
Sheet,
79
SheetContent,
@@ -20,10 +22,10 @@ import {
2022
import { createColumnHelper } from "@tanstack/react-table";
2123
import { ChainIcon } from "components/icons/ChainIcon";
2224
import { formatDistanceToNowStrict } from "date-fns";
23-
import { format } from "date-fns/format";
25+
import { format, formatDate } from "date-fns/format";
2426
import { useAllChainsData } from "hooks/chains/allChains";
2527
import { InfoIcon, MoveLeftIcon, MoveRightIcon } from "lucide-react";
26-
import { type Dispatch, type SetStateAction, useState } from "react";
28+
import { type Dispatch, type SetStateAction, useMemo, useState } from "react";
2729
import { toTokens } from "thirdweb";
2830
import { Button, Card, FormLabel, LinkButton, Text } from "tw-components";
2931
import { TWTable } from "../../../../../../../../../../components/shared/TWTable";
@@ -256,14 +258,79 @@ export const TransactionsTable: React.FC<TransactionsTableProps> = ({
256258
? transactions.indexOf(selectedTransaction)
257259
: 0;
258260

261+
const { analyticsData, chartConfig } = useMemo(() => {
262+
const dayToTxCountMap: Map<number, Record<string, number>> = new Map();
263+
const uniqueStatuses = new Set<string>();
264+
265+
for (const tx of transactions) {
266+
if (!tx.queuedAt || !tx.status) {
267+
continue;
268+
}
269+
const normalizedDate = new Date(tx.queuedAt);
270+
normalizedDate.setHours(0, 0, 0, 0); // normalize time
271+
const time = normalizedDate.getTime();
272+
const entry = dayToTxCountMap.get(time) ?? {};
273+
entry[tx.status] = (entry[tx.status] ?? 0) + 1;
274+
uniqueStatuses.add(tx.status);
275+
dayToTxCountMap.set(time, entry);
276+
}
277+
278+
const analyticsData = Array.from(dayToTxCountMap.entries())
279+
.map(([day, record]) => ({
280+
time: new Date(day).getTime(),
281+
...record,
282+
}))
283+
.sort((a, b) => a.time - b.time);
284+
285+
const chartConfig: ChartConfig = {};
286+
Array.from(uniqueStatuses).forEach((status, i) => {
287+
chartConfig[status] = {
288+
label: status.charAt(0).toUpperCase() + status.slice(1), // first letter uppercase
289+
color:
290+
status === "errored"
291+
? "hsl(var(--destructive-text))"
292+
: status === "mined"
293+
? "hsl(var(--success-text))"
294+
: `hsl(var(--chart-${(i % 15) + 3}))`,
295+
};
296+
});
297+
298+
return {
299+
analyticsData,
300+
chartConfig,
301+
};
302+
}, [transactions]);
303+
259304
return (
260305
<>
306+
<ThirdwebBarChart
307+
title="Daily Transactions"
308+
description="Transactions sent from your backend wallets per day"
309+
config={chartConfig}
310+
data={analyticsData}
311+
isPending={isPending}
312+
chartClassName="aspect-[1.5] lg:aspect-[4.5]"
313+
titleClassName="text-xl mb-0"
314+
hideLabel={false}
315+
toolTipLabelFormatter={(_v, item) => {
316+
if (Array.isArray(item)) {
317+
const time = item[0].payload.time as number;
318+
return formatDate(new Date(time), "MMM d, yyyy");
319+
}
320+
return undefined;
321+
}}
322+
/>
323+
324+
<div className="h-8" />
325+
261326
<TWTable
262327
title="transactions"
263328
data={transactions}
264329
columns={columns}
265330
isPending={isPending}
266331
isFetched={isFetched}
332+
bodyRowClassName="hover:bg-accent/50"
333+
tableScrollableClassName="max-h-[1000px]"
267334
onRowClick={(row) => {
268335
setSelectedTransaction(row);
269336
transactionDisclosure.onOpen();

apps/dashboard/src/components/shared/TWTable.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ type TWTableProps<TRowData> = {
5656
title: string;
5757
bodyRowClassName?: string;
5858
bodyRowLinkBox?: boolean;
59+
tableContainerClassName?: string;
60+
tableScrollableClassName?: string;
5961
};
6062

6163
export function TWTable<TRowData>(tableProps: TWTableProps<TRowData>) {
@@ -124,7 +126,10 @@ export function TWTable<TRowData>(tableProps: TWTableProps<TRowData>) {
124126
});
125127

126128
return (
127-
<TableContainer>
129+
<TableContainer
130+
className={tableProps.tableContainerClassName}
131+
scrollableContainerClassName={tableProps.tableScrollableClassName}
132+
>
128133
<Table>
129134
<TableHeader>
130135
{table.getHeaderGroups().map((headerGroup) => (

0 commit comments

Comments
 (0)