Skip to content

Commit bab4ff5

Browse files
committed
Dashboard: Improved chart responsiveness, code cleanup (#7801)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing analytics components by introducing responsive time filters, updating chart components to support new props, and refactoring existing code for improved clarity and functionality. ### Detailed summary - Removed `range-selector.tsx` and refactored `AnalyticsHeader.tsx`. - Added `ResponsiveTimeFilters` for better time range handling. - Updated `EcosystemWalletsSummary` and `EcosystemAnalyticsPage` to use new props. - Refactored various chart components to accept `selectedChart` and `selectedChartQueryParam`. - Enhanced `AsyncTransactionsChartCard` and `AsyncTotalSponsoredCard` for better data handling. - Replaced `LoadingChartState` with responsive loading states across components. - Consolidated logic for handling time series data in analytics cards. - Improved type definitions for better clarity and consistency. > The following files were skipped due to too many changes: `apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/page.tsx` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Introduced responsive and modular time filter controls for analytics dashboards. * Added new highlights cards for team and project analytics, providing aggregated metrics in a combined bar chart format. * Added a new combined bar chart card for gas sponsorship data with dynamic chart selection. * Added components that map transaction and sponsorship data with chain metadata for improved analytics detail. * **Refactor** * Replaced multiple analytics and chart components with streamlined, asynchronous, and responsive variants. * Standardized date range and interval filtering using a centralized utility and responsive providers. * Updated chart selection to use in-app state and callbacks instead of URL-based navigation. * Simplified analytics page layouts by replacing Suspense with responsive suspense components. * Externalized data processing and state management from several chart card components to improve modularity. * Removed deprecated components and replaced them with updated responsive and async implementations. * **Bug Fixes** * Improved handling of date ranges and intervals, ensuring accurate and consistent filtering. * **Chores** * Removed unused or redundant components and consolidated stories for better maintainability. * Cleaned up imports and type definitions across analytics and project overview pages. * **Style** * Enhanced button and layout styling for chart selection and analytics cards. * **Documentation** * Updated interactive stories for analytics components to improve clarity and usability. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 621c1de commit bab4ff5

File tree

23 files changed

+1102
-898
lines changed

23 files changed

+1102
-898
lines changed

apps/dashboard/src/@/components/analytics/date-range-selector.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function DateRangeSelector(props: {
1818
}) {
1919
const { range, setRange } = props;
2020
const daysDiff = differenceInCalendarDays(range.to, range.from);
21+
2122
const matchingRange =
2223
normalizeTime(range.to).getTime() === normalizeTime(new Date()).getTime()
2324
? durationPresets.find((preset) => preset.days === daysDiff)
@@ -85,7 +86,7 @@ export function getLastNDaysRange(id: DurationId) {
8586
throw new Error(`Invalid duration id: ${id}`);
8687
}
8788

88-
const todayDate = new Date(Date.now() + 1000 * 60 * 60 * 24); // add 1 day to avoid timezone issues
89+
const todayDate = new Date(Date.now());
8990

9091
const value: Range = {
9192
from: subDays(todayDate, durationInfo.days),

apps/dashboard/src/@/components/analytics/range-selector.tsx

Lines changed: 0 additions & 104 deletions
This file was deleted.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"use client";
2+
3+
import {
4+
useResponsiveSearchParams,
5+
useSetResponsiveSearchParams,
6+
} from "responsive-rsc";
7+
import {
8+
DateRangeSelector,
9+
type DurationId,
10+
} from "@/components/analytics/date-range-selector";
11+
import { IntervalSelector } from "@/components/analytics/interval-selector";
12+
import { getFiltersFromSearchParams, normalizeTimeISOString } from "@/lib/time";
13+
14+
export function ResponsiveTimeFilters(props: { defaultRange: DurationId }) {
15+
const responsiveSearchParams = useResponsiveSearchParams();
16+
const setResponsiveSearchParams = useSetResponsiveSearchParams();
17+
const { range, interval } = getFiltersFromSearchParams({
18+
defaultRange: props.defaultRange,
19+
from: responsiveSearchParams.from,
20+
interval: responsiveSearchParams.interval,
21+
to: responsiveSearchParams.to,
22+
});
23+
24+
return (
25+
<div className="flex justify-end gap-3 flex-col lg:flex-row">
26+
<DateRangeSelector
27+
className="rounded-full"
28+
range={range}
29+
setRange={(newRange) => {
30+
setResponsiveSearchParams((v) => {
31+
const newParams = {
32+
...v,
33+
from: normalizeTimeISOString(newRange.from),
34+
to: normalizeTimeISOString(newRange.to),
35+
};
36+
return newParams;
37+
});
38+
}}
39+
/>
40+
<IntervalSelector
41+
className="bg-card rounded-full"
42+
intervalType={interval}
43+
setIntervalType={(newInterval) => {
44+
setResponsiveSearchParams((v) => {
45+
const newParams = {
46+
...v,
47+
interval: newInterval,
48+
};
49+
return newParams;
50+
});
51+
}}
52+
/>
53+
</div>
54+
);
55+
}

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/_components/TotalSponsoredCard.tsx

Lines changed: 44 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,53 @@
1-
import { defineChain } from "thirdweb";
2-
import { type ChainMetadata, getChainMetadata } from "thirdweb/chains";
1+
"use client";
2+
import { useSetResponsiveSearchParams } from "responsive-rsc";
3+
import type { ChainMetadata } from "thirdweb/chains";
34
import { cn } from "@/lib/utils";
45
import type { UserOpStats } from "@/types/analytics";
56
import { BarChart } from "../../../components/Analytics/BarChart";
67
import { CombinedBarChartCard } from "../../../components/Analytics/CombinedBarChartCard";
78
import { EmptyAccountAbstractionChartContent } from "../../[project_slug]/(sidebar)/account-abstraction/AccountAbstractionAnalytics/SponsoredTransactionsChartCard";
89

9-
export async function TotalSponsoredChartCardUI({
10-
data,
11-
aggregatedData,
12-
searchParams,
10+
const chartConfig = {
11+
mainnet: {
12+
color: "hsl(var(--chart-1))",
13+
label: "Mainnet Chains",
14+
},
15+
testnet: {
16+
color: "hsl(var(--chart-2))",
17+
label: "Testnet Chains",
18+
},
19+
total: {
20+
color: "hsl(var(--chart-3))",
21+
label: "All Chains",
22+
},
23+
};
24+
25+
export function TotalSponsoredChartCardUI({
26+
processedAggregatedData,
27+
selectedChart,
1328
className,
1429
onlyMainnet,
1530
title,
31+
chains,
32+
data,
1633
description,
34+
selectedChartQueryParam,
1735
}: {
1836
data: UserOpStats[];
19-
aggregatedData: UserOpStats[];
20-
searchParams?: { [key: string]: string | string[] | undefined };
37+
selectedChart: string | undefined;
2138
className?: string;
2239
onlyMainnet?: boolean;
2340
title?: string;
41+
selectedChartQueryParam: string;
2442
description?: string;
43+
chains: ChainMetadata[];
44+
processedAggregatedData: {
45+
mainnet: number;
46+
testnet: number;
47+
total: number;
48+
};
2549
}) {
26-
const chains = await Promise.all(
27-
data.map(
28-
(item) =>
29-
// eslint-disable-next-line no-restricted-syntax
30-
item.chainId && getChainMetadata(defineChain(Number(item.chainId))),
31-
),
32-
).then((chains) => chains.filter((c) => c) as ChainMetadata[]);
50+
const setResponsiveSearchParams = useSetResponsiveSearchParams();
3351

3452
// Process data to combine by date and chain type
3553
const dateMap = new Map<string, { mainnet: number; testnet: number }>();
@@ -55,35 +73,6 @@ export async function TotalSponsoredChartCardUI({
5573
}))
5674
.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());
5775

58-
const processedAggregatedData = {
59-
mainnet: aggregatedData
60-
.filter(
61-
(d) => !chains.find((c) => c.chainId === Number(d.chainId))?.testnet,
62-
)
63-
.reduce((acc, curr) => acc + curr.sponsoredUsd, 0),
64-
testnet: aggregatedData
65-
.filter(
66-
(d) => chains.find((c) => c.chainId === Number(d.chainId))?.testnet,
67-
)
68-
.reduce((acc, curr) => acc + curr.sponsoredUsd, 0),
69-
total: aggregatedData.reduce((acc, curr) => acc + curr.sponsoredUsd, 0),
70-
};
71-
72-
const chartConfig = {
73-
mainnet: {
74-
color: "hsl(var(--chart-1))",
75-
label: "Mainnet Chains",
76-
},
77-
testnet: {
78-
color: "hsl(var(--chart-2))",
79-
label: "Testnet Chains",
80-
},
81-
total: {
82-
color: "hsl(var(--chart-3))",
83-
label: "All Chains",
84-
},
85-
};
86-
8776
if (onlyMainnet) {
8877
const filteredData = timeSeriesData.filter((d) => d.mainnet > 0);
8978
return (
@@ -106,15 +95,23 @@ export async function TotalSponsoredChartCardUI({
10695
return (
10796
<CombinedBarChartCard
10897
activeChart={
109-
(searchParams?.totalSponsored as keyof typeof chartConfig) ?? "mainnet"
98+
selectedChart && selectedChart in chartConfig
99+
? (selectedChart as keyof typeof chartConfig)
100+
: "mainnet"
110101
}
102+
onSelect={(key) => {
103+
setResponsiveSearchParams((v) => {
104+
return {
105+
...v,
106+
[selectedChartQueryParam]: key,
107+
};
108+
});
109+
}}
111110
aggregateFn={(_data, key) => processedAggregatedData[key]}
112111
chartConfig={chartConfig}
113112
className={className}
114113
data={timeSeriesData}
115-
existingQueryParams={searchParams}
116114
isCurrency
117-
queryKey="totalSponsored"
118115
title={title || "Gas Sponsored"}
119116
// Get the trend from the last two COMPLETE periods
120117
trendFn={(data, key) =>

0 commit comments

Comments
 (0)