Skip to content

Commit d7e34f2

Browse files
committed
Add Date Range and Interval Selector on Connect Analytics page (#4988)
## Problem solved Short description of the bug fixed or feature added <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the date range selection functionality in the `PayAnalytics` and `ConnectAnalyticsDashboard` components by incorporating the `subDays` function and restructuring the date range handling. ### Detailed summary - Added `subDays` import from `date-fns` for date calculations. - Refactored `getLastNDaysRange` to use `subDays` for calculating past dates. - Moved and restructured the `presets` rendering logic in `Filters` for clarity. - Introduced `DateRangeSelector` and `IntervalSelector` components in `ConnectAnalyticsDashboard`. - Replaced `useMemo` with `useState` for managing date ranges in `ConnectAnalyticsDashboard`. - Simplified date range handling and interval type selection. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent e06cc9b commit d7e34f2

File tree

2 files changed

+208
-55
lines changed

2 files changed

+208
-55
lines changed
Lines changed: 177 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,201 @@
11
"use client";
2+
import { DatePickerWithRange } from "@/components/ui/DatePickerWithRange";
3+
import {
4+
Select,
5+
SelectContent,
6+
SelectItem,
7+
SelectTrigger,
8+
SelectValue,
9+
} from "@/components/ui/select";
210
import {
311
useWalletUsageAggregate,
412
useWalletUsagePeriod,
513
} from "@3rdweb-sdk/react/hooks/useApi";
6-
import { useMemo } from "react";
14+
import { differenceInDays, format, subDays } from "date-fns";
15+
import { useState } from "react";
716
import { ConnectAnalyticsDashboardUI } from "./ConnectAnalyticsDashboardUI";
817

918
export function ConnectAnalyticsDashboard(props: {
1019
clientId: string;
1120
}) {
12-
const allTimeFrom = useMemo(
13-
() => new Date(Date.now() - 135 * 24 * 60 * 60 * 1000),
14-
[],
21+
const [range, setRange] = useState<Range>(() =>
22+
getLastNDaysRange("last-120"),
1523
);
16-
const from = useMemo(
17-
() => new Date(Date.now() - 135 * 24 * 60 * 60 * 1000),
18-
[],
24+
25+
// use date-fns to calculate the number of days in the range
26+
const daysInRange = differenceInDays(range.to, range.from);
27+
28+
const [intervalType, setIntervalType] = useState<"day" | "week">(
29+
daysInRange > 30 ? "week" : "day",
1930
);
20-
const to = useMemo(() => new Date(), []);
31+
2132
const walletUsageQuery = useWalletUsagePeriod({
2233
clientId: props.clientId,
23-
from,
24-
to,
25-
period: "day",
34+
from: range.from,
35+
to: range.to,
36+
period: intervalType,
2637
});
38+
2739
const walletUsageAggregateQuery = useWalletUsageAggregate({
2840
clientId: props.clientId,
29-
from: allTimeFrom,
30-
to,
41+
from: range.from,
42+
to: range.to,
3143
});
3244

3345
return (
34-
<ConnectAnalyticsDashboardUI
35-
walletUsage={walletUsageQuery.data || []}
36-
aggregateWalletUsage={walletUsageAggregateQuery.data || []}
37-
isPending={
38-
walletUsageQuery.isPending || walletUsageAggregateQuery.isPending
46+
<div>
47+
<div className="flex gap-3">
48+
<DateRangeSelector
49+
range={range}
50+
setRange={(newRange) => {
51+
setRange(newRange);
52+
const days = differenceInDays(newRange.to, newRange.from);
53+
setIntervalType(days > 30 ? "week" : "day");
54+
}}
55+
/>
56+
<IntervalSelector
57+
intervalType={intervalType}
58+
setIntervalType={setIntervalType}
59+
/>
60+
</div>
61+
<div className="h-4" />
62+
<ConnectAnalyticsDashboardUI
63+
walletUsage={walletUsageQuery.data || []}
64+
aggregateWalletUsage={walletUsageAggregateQuery.data || []}
65+
isPending={
66+
walletUsageQuery.isPending || walletUsageAggregateQuery.isPending
67+
}
68+
/>
69+
</div>
70+
);
71+
}
72+
73+
const durationPresets = [
74+
{
75+
name: "Last 7 Days",
76+
id: "last-7",
77+
days: 7,
78+
},
79+
{
80+
name: "Last 30 Days",
81+
id: "last-30",
82+
days: 30,
83+
},
84+
{
85+
name: "Last 60 Days",
86+
id: "last-60",
87+
days: 60,
88+
},
89+
{
90+
name: "Last 120 Days",
91+
id: "last-120",
92+
days: 120,
93+
},
94+
] as const;
95+
96+
type DurationId = (typeof durationPresets)[number]["id"];
97+
98+
type Range = {
99+
type: DurationId | "custom";
100+
label?: string;
101+
from: Date;
102+
to: Date;
103+
};
104+
105+
function getLastNDaysRange(id: DurationId) {
106+
const durationInfo = durationPresets.find((preset) => preset.id === id);
107+
if (!durationInfo) {
108+
throw new Error("Invalid duration id");
109+
}
110+
111+
const todayDate = new Date();
112+
113+
const value: Range = {
114+
type: id,
115+
from: subDays(todayDate, durationInfo.days),
116+
to: todayDate,
117+
label: durationInfo.name,
118+
};
119+
120+
return value;
121+
}
122+
123+
function DateRangeSelector(props: {
124+
range: Range;
125+
setRange: (range: Range) => void;
126+
}) {
127+
const { range, setRange } = props;
128+
129+
return (
130+
<DatePickerWithRange
131+
from={range.from}
132+
to={range.to}
133+
setFrom={(from) =>
134+
setRange({
135+
from,
136+
to: range.to,
137+
type: "custom",
138+
})
139+
}
140+
setTo={(to) =>
141+
setRange({
142+
from: range.from,
143+
to,
144+
type: "custom",
145+
})
39146
}
147+
header={
148+
<div className="mb-2 border-border border-b p-4">
149+
<Select
150+
value={range.type}
151+
onValueChange={(id: DurationId) => {
152+
setRange(getLastNDaysRange(id));
153+
}}
154+
>
155+
<SelectTrigger className="flex bg-transparent">
156+
<SelectValue placeholder="Select" />
157+
</SelectTrigger>
158+
<SelectContent position="popper">
159+
{durationPresets.map((preset) => (
160+
<SelectItem key={preset.id} value={preset.id}>
161+
{preset.name}
162+
</SelectItem>
163+
))}
164+
165+
{range.type === "custom" && (
166+
<SelectItem value="custom">
167+
{format(range.from, "LLL dd, y")} -{" "}
168+
{format(range.to, "LLL dd, y")}
169+
</SelectItem>
170+
)}
171+
</SelectContent>
172+
</Select>
173+
</div>
174+
}
175+
labelOverride={range.label}
176+
className="w-auto"
40177
/>
41178
);
42179
}
180+
181+
function IntervalSelector(props: {
182+
intervalType: "day" | "week";
183+
setIntervalType: (intervalType: "day" | "week") => void;
184+
}) {
185+
return (
186+
<Select
187+
value={props.intervalType}
188+
onValueChange={(value: "day" | "week") => {
189+
props.setIntervalType(value);
190+
}}
191+
>
192+
<SelectTrigger className="w-auto hover:bg-muted">
193+
<SelectValue placeholder="Select" />
194+
</SelectTrigger>
195+
<SelectContent position="popper">
196+
<SelectItem value="week"> Weekly </SelectItem>
197+
<SelectItem value="day"> Daily</SelectItem>
198+
</SelectContent>
199+
</Select>
200+
);
201+
}

apps/dashboard/src/components/pay/PayAnalytics/PayAnalytics.tsx

Lines changed: 31 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
SelectTrigger,
99
SelectValue,
1010
} from "@/components/ui/select";
11-
import { format } from "date-fns";
11+
import { format, subDays } from "date-fns";
1212
import { useState } from "react";
1313
import { PayCustomersTable } from "./components/PayCustomersTable";
1414
import { PayNewCustomers } from "./components/PayNewCustomers";
@@ -125,19 +125,15 @@ export function PayAnalytics(props: { clientId: string }) {
125125
}
126126

127127
function getLastNDaysRange(id: DurationId) {
128-
const todayDate = new Date();
129-
const pastDate = new Date(todayDate);
130-
131128
const durationInfo = durationPresets.find((preset) => preset.id === id);
132129
if (!durationInfo) {
133130
throw new Error("Invalid duration id");
134131
}
135132

136-
pastDate.setDate(todayDate.getDate() - durationInfo.days);
137-
133+
const todayDate = new Date();
138134
const value: Range = {
139135
type: id,
140-
from: pastDate,
136+
from: subDays(todayDate, durationInfo.days),
141137
to: todayDate,
142138
label: durationInfo.name,
143139
};
@@ -148,35 +144,6 @@ function getLastNDaysRange(id: DurationId) {
148144
function Filters(props: { range: Range; setRange: (range: Range) => void }) {
149145
const { range, setRange } = props;
150146

151-
const presets = (
152-
<div className="mb-2 border-border border-b p-4">
153-
<Select
154-
value={range.type}
155-
onValueChange={(id: DurationId) => {
156-
setRange(getLastNDaysRange(id));
157-
}}
158-
>
159-
<SelectTrigger className="flex bg-transparent">
160-
<SelectValue placeholder="Select" />
161-
</SelectTrigger>
162-
<SelectContent position="popper">
163-
{durationPresets.map((preset) => (
164-
<SelectItem key={preset.id} value={preset.id}>
165-
{preset.name}
166-
</SelectItem>
167-
))}
168-
169-
{range.type === "custom" && (
170-
<SelectItem value="custom">
171-
{format(range.from, "LLL dd, y")} -{" "}
172-
{format(range.to, "LLL dd, y")}
173-
</SelectItem>
174-
)}
175-
</SelectContent>
176-
</Select>
177-
</div>
178-
);
179-
180147
return (
181148
<div className="flex gap-2">
182149
<DatePickerWithRange
@@ -196,7 +163,34 @@ function Filters(props: { range: Range; setRange: (range: Range) => void }) {
196163
type: "custom",
197164
})
198165
}
199-
header={presets}
166+
header={
167+
<div className="mb-2 border-border border-b p-4">
168+
<Select
169+
value={range.type}
170+
onValueChange={(id: DurationId) => {
171+
setRange(getLastNDaysRange(id));
172+
}}
173+
>
174+
<SelectTrigger className="flex bg-transparent">
175+
<SelectValue placeholder="Select" />
176+
</SelectTrigger>
177+
<SelectContent position="popper">
178+
{durationPresets.map((preset) => (
179+
<SelectItem key={preset.id} value={preset.id}>
180+
{preset.name}
181+
</SelectItem>
182+
))}
183+
184+
{range.type === "custom" && (
185+
<SelectItem value="custom">
186+
{format(range.from, "LLL dd, y")} -{" "}
187+
{format(range.to, "LLL dd, y")}
188+
</SelectItem>
189+
)}
190+
</SelectContent>
191+
</Select>
192+
</div>
193+
}
200194
labelOverride={range.label}
201195
className="w-auto border-none p-0"
202196
/>

0 commit comments

Comments
 (0)