Skip to content

Commit fd24864

Browse files
committed
feat: implement pool filtering functionality in dashboard with new PoolSection component and API integration
1 parent 372ffda commit fd24864

15 files changed

Lines changed: 381 additions & 9 deletions

File tree

components/dashboard/Dashboard.tsx

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ import type { TimelineBucket } from "./sections";
1616
import { SettingsPanel, type ExpandedSections } from "./SettingsPanel";
1717
import { TimelineBar } from "./TimelineBar";
1818

19-
import { useFieldReports } from "@/hooks/dashboard";
19+
import { useFieldReports, useImagePreloader } from "@/hooks/dashboard";
2020
import type { DataResponse } from "@/pages/api/data";
21-
import type { FieldReportsResponse } from "@/types";
21+
import type { FieldReportsResponse, Pool, PoolsResponse } from "@/types";
2222
import type { Voucher } from "@/types/voucher";
2323

2424
// Fetcher function for SWR
@@ -51,11 +51,24 @@ export function Dashboard() {
5151
}
5252
);
5353

54+
// Fetch pools
55+
const { data: poolsData, isLoading: poolsLoading } = useSWR<PoolsResponse>(
56+
"/api/pools",
57+
fetcher,
58+
{
59+
refreshInterval: 5 * 60 * 1000,
60+
revalidateOnFocus: false,
61+
}
62+
);
63+
5464
// Panel states
5565
const [optionsOpen, setOptionsOpen] = React.useState(false);
5666
const [graphType, setGraphType] = React.useState<"2D" | "3D">("3D");
5767
const [showTimelineBar, setShowTimelineBar] = React.useState(true);
5868

69+
// Pool filtering
70+
const [selectedPools, setSelectedPools] = React.useState<Pool[]>([]);
71+
5972
// Token filtering
6073
const [selectedTokens, setSelectedTokens] = React.useState<Voucher[]>([]);
6174
const [filteredByToken, setFilteredByToken] = React.useState<
@@ -91,6 +104,7 @@ export function Dashboard() {
91104
// Collapsible sections state
92105
const [expandedSections, setExpandedSections] =
93106
React.useState<ExpandedSections>({
107+
pools: false,
94108
vouchers: true,
95109
animation: false,
96110
display: false,
@@ -107,6 +121,23 @@ export function Dashboard() {
107121
return () => clearTimeout(timer);
108122
}, [chargeStrengthInput, linkDistanceInput, centerGravityInput]);
109123

124+
// Compute available vouchers based on selected pools
125+
const availableVouchers = React.useMemo(() => {
126+
if (!data?.vouchers) return [];
127+
if (selectedPools.length === 0) return data.vouchers;
128+
129+
// Union of all allowed tokens from selected pools
130+
const allowedSet = new Set<string>();
131+
for (const pool of selectedPools) {
132+
for (const token of pool.allowed_tokens) {
133+
allowedSet.add(token.toLowerCase());
134+
}
135+
}
136+
return data.vouchers.filter((v) =>
137+
allowedSet.has(v.contract_address.toLowerCase())
138+
);
139+
}, [data?.vouchers, selectedPools]);
140+
110141
// Initialize selected tokens when data loads
111142
React.useEffect(() => {
112143
if (data) {
@@ -115,6 +146,26 @@ export function Dashboard() {
115146
}
116147
}, [data]);
117148

149+
// Auto-select pool tokens when pools change
150+
React.useEffect(() => {
151+
if (!data?.vouchers) return;
152+
153+
if (selectedPools.length > 0) {
154+
// Union of all allowed tokens from selected pools
155+
const allowedSet = new Set<string>();
156+
for (const pool of selectedPools) {
157+
for (const token of pool.allowed_tokens) {
158+
allowedSet.add(token.toLowerCase());
159+
}
160+
}
161+
const poolTokens = data.vouchers.filter((v) =>
162+
allowedSet.has(v.contract_address.toLowerCase())
163+
);
164+
setSelectedTokens(poolTokens);
165+
}
166+
// When no pools selected, keep current selection
167+
}, [selectedPools, data?.vouchers]);
168+
118169
// Filter graph data by selected tokens
119170
React.useEffect(() => {
120171
if (!data) return;
@@ -287,12 +338,20 @@ export function Dashboard() {
287338
maxVisible: 3,
288339
});
289340

290-
// Reset dismissed reports when animation restarts from beginning
341+
// Image preloading for field reports
342+
const { resetPreloaded } = useImagePreloader({
343+
reports: reportsData?.reports ?? [],
344+
currentDate: date,
345+
selectedVoucherAddresses,
346+
});
347+
348+
// Reset dismissed reports and preloaded images when animation restarts from beginning
291349
React.useEffect(() => {
292350
if (date === dateRange.start) {
293351
resetDismissed();
352+
resetPreloaded();
294353
}
295-
}, [date, dateRange.start, resetDismissed]);
354+
}, [date, dateRange.start, resetDismissed, resetPreloaded]);
296355

297356
// Callbacks
298357
const toggleSection = React.useCallback((section: keyof ExpandedSections) => {
@@ -410,8 +469,12 @@ export function Dashboard() {
410469
voucherCount={selectedTokens.length}
411470
expandedSections={expandedSections}
412471
toggleSection={toggleSection}
472+
pools={poolsData?.pools ?? []}
473+
poolsLoading={poolsLoading}
474+
selectedPools={selectedPools}
475+
onSelectPools={setSelectedPools}
413476
selectedTokens={selectedTokens}
414-
allVouchers={data.vouchers}
477+
allVouchers={availableVouchers}
415478
onSelectTokens={setSelectedTokens}
416479
date={date}
417480
setDate={setDate}

components/dashboard/SettingsPanel.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@ import React from "react";
66
import { CloseIcon } from "@components/icons";
77
import { StatsBar } from "./StatsBar";
88
import {
9+
PoolSection,
910
VoucherSection,
1011
AnimationSection,
1112
DisplaySection,
1213
PhysicsSection,
1314
} from "./sections";
1415
import type { TimelineBucket } from "./sections";
16+
import type { Pool } from "@/types";
1517
import type { Voucher } from "@/types/voucher";
1618
import type { DateRange, PhysicsInputs } from "@hooks/dashboard";
1719

1820
export interface ExpandedSections {
21+
pools: boolean;
1922
vouchers: boolean;
2023
animation: boolean;
2124
display: boolean;
@@ -31,9 +34,17 @@ export interface SettingsPanelProps {
3134
linkCount: number;
3235
voucherCount: number;
3336

34-
// Vouchers
37+
// Sections
3538
expandedSections: ExpandedSections;
3639
toggleSection: (section: keyof ExpandedSections) => void;
40+
41+
// Pools
42+
pools: Pool[];
43+
poolsLoading?: boolean;
44+
selectedPools: Pool[];
45+
onSelectPools: (pools: Pool[]) => void;
46+
47+
// Vouchers
3748
selectedTokens: Voucher[];
3849
allVouchers: Voucher[];
3950
onSelectTokens: (tokens: Voucher[]) => void;
@@ -76,6 +87,10 @@ export function SettingsPanel({
7687
voucherCount,
7788
expandedSections,
7889
toggleSection,
90+
pools,
91+
poolsLoading,
92+
selectedPools,
93+
onSelectPools,
7994
selectedTokens,
8095
allVouchers,
8196
onSelectTokens,
@@ -121,6 +136,16 @@ export function SettingsPanel({
121136
voucherCount={voucherCount}
122137
/>
123138

139+
{/* Pool Section */}
140+
<PoolSection
141+
expanded={expandedSections.pools}
142+
onToggle={() => toggleSection("pools")}
143+
pools={pools}
144+
selectedPools={selectedPools}
145+
onSelectPools={onSelectPools}
146+
isLoading={poolsLoading}
147+
/>
148+
124149
{/* Vouchers Section */}
125150
<VoucherSection
126151
expanded={expandedSections.vouchers}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* Pool filter section component
3+
*/
4+
5+
import React from "react";
6+
import { ChevronDownIcon } from "@components/icons";
7+
import { MultiSelect } from "@components/select";
8+
import type { Pool } from "@/types";
9+
10+
export interface PoolSectionProps {
11+
expanded: boolean;
12+
onToggle: () => void;
13+
pools: Pool[];
14+
selectedPools: Pool[];
15+
onSelectPools: (pools: Pool[]) => void;
16+
isLoading?: boolean;
17+
}
18+
19+
export function PoolSection({
20+
expanded,
21+
onToggle,
22+
pools,
23+
selectedPools,
24+
onSelectPools,
25+
isLoading,
26+
}: PoolSectionProps) {
27+
// Calculate total unique tokens across selected pools
28+
const totalTokens = React.useMemo(() => {
29+
if (selectedPools.length === 0) return 0;
30+
const tokenSet = new Set<string>();
31+
for (const pool of selectedPools) {
32+
for (const token of pool.allowed_tokens) {
33+
tokenSet.add(token.toLowerCase());
34+
}
35+
}
36+
return tokenSet.size;
37+
}, [selectedPools]);
38+
39+
return (
40+
<div className="border border-gray-200 rounded-lg">
41+
<button
42+
onClick={onToggle}
43+
className="w-full flex items-center justify-between px-4 py-3 bg-gray-50 hover:bg-gray-100 transition-colors"
44+
>
45+
<span className="font-medium text-gray-700">Filter by Pool</span>
46+
<ChevronDownIcon
47+
className={`w-4 h-4 text-gray-500 transition-transform ${
48+
expanded ? "rotate-180" : ""
49+
}`}
50+
/>
51+
</button>
52+
{expanded && (
53+
<div className="p-3 sm:p-4 space-y-3">
54+
<MultiSelect
55+
selected={selectedPools}
56+
options={pools}
57+
label="Select Pools"
58+
optionToKey={(p: Pool) => p.pool_address}
59+
optionToLabel={(p: Pool) => `${p.pool_name} (${p.pool_symbol})`}
60+
optionToSearchFields={(p: Pool) => [p.pool_name, p.pool_symbol]}
61+
onChange={onSelectPools}
62+
disabled={isLoading}
63+
/>
64+
<div className="flex gap-2">
65+
<button
66+
className="flex-1 px-3 py-2 sm:py-1.5 text-sm text-green-600 border border-green-200 rounded-md hover:bg-green-50 active:bg-green-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
67+
onClick={() => onSelectPools(pools)}
68+
disabled={isLoading}
69+
>
70+
Select All
71+
</button>
72+
<button
73+
className="flex-1 px-3 py-2 sm:py-1.5 text-sm text-gray-600 border border-gray-200 rounded-md hover:bg-gray-50 active:bg-gray-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
74+
onClick={() => onSelectPools([])}
75+
disabled={isLoading}
76+
>
77+
Clear
78+
</button>
79+
</div>
80+
{selectedPools.length > 0 && (
81+
<div className="text-sm text-gray-500">
82+
{totalTokens} token{totalTokens !== 1 ? "s" : ""} in{" "}
83+
{selectedPools.length} pool{selectedPools.length !== 1 ? "s" : ""}
84+
</div>
85+
)}
86+
</div>
87+
)}
88+
</div>
89+
);
90+
}

components/dashboard/sections/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
* Settings panel section components barrel export
33
*/
44

5+
export { PoolSection } from "./PoolSection";
6+
export type { PoolSectionProps } from "./PoolSection";
7+
58
export { VoucherSection } from "./VoucherSection";
69
export type { VoucherSectionProps } from "./VoucherSection";
710

components/ui/select/MultiSelect.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ interface MultiSelectProps<T> {
4444
optionToKey: (value: T) => string;
4545
optionToSearchFields?: (value: T) => string[];
4646
prioritizeSymbol?: string;
47+
disabled?: boolean;
4748
}
4849

4950
export function MultiSelect<T>(props: MultiSelectProps<T>) {
@@ -61,6 +62,7 @@ export function MultiSelect<T>(props: MultiSelectProps<T>) {
6162
optionToKey,
6263
optionToSearchFields,
6364
prioritizeSymbol,
65+
disabled,
6466
} = props;
6567

6668
// Close on click outside
@@ -153,8 +155,11 @@ export function MultiSelect<T>(props: MultiSelectProps<T>) {
153155
<span className="inline-block w-full rounded-md shadow-sm">
154156
<button
155157
type="button"
156-
onClick={() => setIsOpen(!isOpen)}
157-
className="cursor-default relative w-full rounded-md border border-gray-300 bg-white pl-3 pr-10 py-2 text-left transition ease-in-out duration-150"
158+
onClick={() => !disabled && setIsOpen(!isOpen)}
159+
disabled={disabled}
160+
className={`cursor-default relative w-full rounded-md border border-gray-300 bg-white pl-3 pr-10 py-2 text-left transition ease-in-out duration-150 ${
161+
disabled ? "bg-gray-100 cursor-not-allowed opacity-60" : ""
162+
}`}
158163
>
159164
<span className="block truncate text-black">
160165
{selected.length === options.length

config/cache.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,9 @@ export const CACHE_TTL_SECONDS = 86400 * 30; // 30 days
99
export const REPORTS_CACHE_KEY = "field-reports-1";
1010
export const REPORTS_CACHE_TTL_SECONDS = 3600; // 1 hour
1111

12+
// Pools cache
13+
export const POOLS_CACHE_KEY = "pools-1";
14+
export const POOLS_CACHE_TTL_SECONDS = 3600; // 1 hour
15+
1216
// SWR configuration
1317
export const SWR_REFRESH_INTERVAL = 5 * 60 * 1000; // 5 minutes

hooks/dashboard/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,9 @@ export type {
3333
UseFieldReportsOptions,
3434
UseFieldReportsReturn,
3535
} from "./useFieldReports";
36+
37+
export { useImagePreloader } from "./useImagePreloader";
38+
export type {
39+
UseImagePreloaderOptions,
40+
UseImagePreloaderReturn,
41+
} from "./useImagePreloader";

0 commit comments

Comments
 (0)