Skip to content

Commit f1ed9c2

Browse files
committed
feat: implement filter parameter synchronization and enhance workerpools query with ordering options
1 parent 082a348 commit f1ed9c2

File tree

5 files changed

+151
-16
lines changed

5 files changed

+151
-16
lines changed

src/hooks/useFilterParam.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useSearch, useNavigate } from '@tanstack/react-router';
2+
3+
/**
4+
* Synchronize a string filter value with the URL search params.
5+
* Ensures the value is part of the allowedValues list; otherwise falls back to defaultValue.
6+
*
7+
* @param paramName Query string key to store the filter value under.
8+
* @param allowedValues List of allowed string values for the filter.
9+
* @param defaultValue Default value if none present or invalid.
10+
* @returns [currentValue, setValue]
11+
*/
12+
export function useFilterParam(
13+
paramName: string,
14+
allowedValues: string[],
15+
defaultValue: string
16+
) {
17+
const search = useSearch({ strict: false });
18+
const navigate = useNavigate();
19+
20+
const rawValue = (search?.[paramName] as string | undefined) ?? defaultValue;
21+
const value = allowedValues.includes(rawValue) ? rawValue : defaultValue;
22+
23+
const setValue = (newValue: string) => {
24+
if (!allowedValues.includes(newValue)) return; // ignore invalid values
25+
if (newValue !== value) {
26+
const nav: any = navigate;
27+
nav({
28+
search: (prev: any) => ({
29+
...prev,
30+
[paramName]: newValue,
31+
}),
32+
replace: true,
33+
resetScroll: false,
34+
});
35+
}
36+
};
37+
38+
return [value, setValue] as const;
39+
}

src/modules/workerpools/WorkerpoolsPreviewTable.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PREVIEW_TABLE_LENGTH, PREVIEW_TABLE_REFETCH_INTERVAL } from '@/config';
22
import { execute } from '@/graphql/poco/execute';
3+
import { Workerpool_OrderBy, OrderDirection } from '@/graphql/poco/graphql';
34
import { cn } from '@/lib/utils';
45
import { useQuery } from '@tanstack/react-query';
56
import { LoaderCircle } from 'lucide-react';
@@ -8,25 +9,38 @@ import { DataTable } from '@/components/DataTable';
89
import WorkerpoolIcon from '@/components/icons/WorkerpoolIcon';
910
import { Button } from '@/components/ui/button';
1011
import useUserStore from '@/stores/useUser.store';
11-
import { createPlaceholderDataFnForQueryKey } from '@/utils/createPlaceholderDataFnForQueryKey';
12+
import { createPlaceholderDataFn } from '@/utils/createPlaceholderDataFnForQueryKey';
1213
import { ErrorAlert } from '../ErrorAlert';
1314
import { workerpoolsQuery } from './workerpoolsQuery';
1415
import { columns } from './workerpoolsTable/columns';
1516

1617
export function WorkerpoolsPreviewTable({ className }: { className?: string }) {
1718
const { chainId } = useUserStore();
1819

19-
const queryKey = [chainId, 'workerpools_preview'];
20+
// Pertinent ordering: usageCount desc + recent usage constraint (last 14 days)
21+
const recentFrom = Math.floor(Date.now() / 1000) - 14 * 24 * 60 * 60;
22+
const orderBy: Workerpool_OrderBy = Workerpool_OrderBy.UsageCount;
23+
const orderDirection: OrderDirection = OrderDirection.Desc;
24+
const queryKey = [
25+
chainId,
26+
'workerpools_preview',
27+
orderBy,
28+
orderDirection,
29+
recentFrom,
30+
];
2031
const workerpools = useQuery({
2132
queryKey,
2233
queryFn: () =>
2334
execute(workerpoolsQuery, chainId, {
2435
length: PREVIEW_TABLE_LENGTH,
2536
skip: 0,
37+
orderBy,
38+
orderDirection,
39+
recentFrom,
2640
}),
2741
refetchInterval: PREVIEW_TABLE_REFETCH_INTERVAL,
2842
enabled: !!chainId,
29-
placeholderData: createPlaceholderDataFnForQueryKey(queryKey),
43+
placeholderData: createPlaceholderDataFn(),
3044
});
3145

3246
const formattedData =
@@ -40,7 +54,7 @@ export function WorkerpoolsPreviewTable({ className }: { className?: string }) {
4054
<div className="flex items-center justify-between">
4155
<h2 className="flex items-center gap-2 font-sans">
4256
<WorkerpoolIcon size={20} className="text-foreground" />
43-
Latest workerpools deployed
57+
Most pertinent workerpools
4458
{workerpools.data && workerpools.isError && (
4559
<span className="text-muted-foreground text-sm font-light">
4660
(outdated)

src/modules/workerpools/workerpoolsQuery.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,33 @@
11
import { graphql } from '@/graphql/poco/gql';
22

3+
// @ts-expect-error Updated query; regenerate codegen
34
export const workerpoolsQuery = graphql(`
45
query Workerpools(
56
$length: Int = 20
67
$skip: Int = 0
78
$nextSkip: Int = 20
89
$nextNextSkip: Int = 40
10+
$orderBy: Workerpool_orderBy = timestamp
11+
$orderDirection: OrderDirection = desc
12+
$recentFrom: BigInt = 0
913
) {
1014
workerpools(
1115
first: $length
1216
skip: $skip
13-
orderBy: timestamp
14-
orderDirection: desc
17+
orderBy: $orderBy
18+
orderDirection: $orderDirection
19+
where: { lastUsageTimestamp_gte: $recentFrom }
1520
) {
1621
address: id
1722
owner {
1823
address: id
1924
}
2025
timestamp
26+
lastUsageTimestamp
2127
description
2228
workerStakeRatio
2329
schedulerRewardRatio
30+
usageCount
2431
transfers(orderBy: timestamp, orderDirection: desc) {
2532
transaction {
2633
txHash: id
@@ -32,16 +39,18 @@ export const workerpoolsQuery = graphql(`
3239
workerpoolsHasNext: workerpools(
3340
first: 1
3441
skip: $nextSkip
35-
orderBy: timestamp
36-
orderDirection: desc
42+
orderBy: $orderBy
43+
orderDirection: $orderDirection
44+
where: { lastUsageTimestamp_gte: $recentFrom }
3745
) {
3846
address: id
3947
}
4048
workerpoolsHasNextNext: workerpools(
4149
first: 1
4250
skip: $nextNextSkip
43-
orderBy: timestamp
44-
orderDirection: desc
51+
orderBy: $orderBy
52+
orderDirection: $orderDirection
53+
where: { lastUsageTimestamp_gte: $recentFrom }
4554
) {
4655
address: id
4756
}

src/routes/$chainSlug/_layout/workerpools.tsx

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,54 @@
11
import { TABLE_LENGTH, TABLE_REFETCH_INTERVAL } from '@/config';
22
import { execute } from '@/graphql/poco/execute';
3+
import { Workerpool_OrderBy, OrderDirection } from '@/graphql/poco/graphql';
34
import { useQuery } from '@tanstack/react-query';
45
import { createFileRoute } from '@tanstack/react-router';
56
import { LoaderCircle } from 'lucide-react';
67
import { DataTable } from '@/components/DataTable';
78
import { PaginatedNavigation } from '@/components/PaginatedNavigation';
89
import WorkerpoolIcon from '@/components/icons/WorkerpoolIcon';
910
import { BackButton } from '@/components/ui/BackButton';
11+
import {
12+
Select,
13+
SelectContent,
14+
SelectItem,
15+
SelectTrigger,
16+
} from '@/components/ui/select';
17+
import { useFilterParam } from '@/hooks/useFilterParam';
1018
import { usePageParam } from '@/hooks/usePageParam';
1119
import { ErrorAlert } from '@/modules/ErrorAlert';
1220
import { SearcherBar } from '@/modules/search/SearcherBar';
1321
import { WorkerpoolBreadcrumbsList } from '@/modules/workerpools/WorkerpoolBreadcrumbs';
1422
import { workerpoolsQuery } from '@/modules/workerpools/workerpoolsQuery';
1523
import { columns } from '@/modules/workerpools/workerpoolsTable/columns';
1624
import useUserStore from '@/stores/useUser.store';
17-
import { createPlaceholderDataFnForQueryKey } from '@/utils/createPlaceholderDataFnForQueryKey';
25+
import { createPlaceholderDataFn } from '@/utils/createPlaceholderDataFnForQueryKey';
1826
import { getAdditionalPages } from '@/utils/format';
1927

2028
export const Route = createFileRoute('/$chainSlug/_layout/workerpools')({
2129
component: WorkerpoolsRoute,
2230
});
2331

24-
function useWorkerpoolsData(currentPage: number) {
32+
function useWorkerpoolsData(currentPage: number, orderFilter: string) {
2533
const { chainId } = useUserStore();
2634
const skip = currentPage * TABLE_LENGTH;
2735
const nextSkip = skip + TABLE_LENGTH;
2836
const nextNextSkip = skip + 2 * TABLE_LENGTH;
37+
const orderBy = orderFilter === 'pertinent' ? 'usageCount' : 'timestamp';
38+
const orderDirection = orderFilter === 'oldest' ? 'asc' : 'desc';
39+
const recentFrom =
40+
orderFilter === 'pertinent'
41+
? Math.floor(Date.now() / 1000) - 14 * 24 * 60 * 60
42+
: 0;
2943

30-
const queryKey = [chainId, 'workerpools', currentPage];
44+
const queryKey = [
45+
chainId,
46+
'workerpools',
47+
currentPage,
48+
orderBy,
49+
orderDirection,
50+
recentFrom,
51+
];
3152
const { data, isLoading, isRefetching, isError, errorUpdateCount } = useQuery(
3253
{
3354
queryKey,
@@ -37,10 +58,13 @@ function useWorkerpoolsData(currentPage: number) {
3758
skip,
3859
nextSkip,
3960
nextNextSkip,
61+
orderBy: orderBy as Workerpool_OrderBy,
62+
orderDirection: orderDirection as OrderDirection,
63+
recentFrom,
4064
}),
4165
refetchInterval: TABLE_REFETCH_INTERVAL,
4266
enabled: !!chainId,
43-
placeholderData: createPlaceholderDataFnForQueryKey(queryKey),
67+
placeholderData: createPlaceholderDataFn(),
4468
}
4569
);
4670

@@ -68,20 +92,54 @@ function useWorkerpoolsData(currentPage: number) {
6892
}
6993

7094
function WorkerpoolsRoute() {
95+
const orders = [
96+
{ id: 1, value: 'recent', name: 'Recently deployed' },
97+
{ id: 2, value: 'oldest', name: 'Oldest deployed' },
98+
{ id: 3, value: 'pertinent', name: 'Most pertinent' },
99+
];
100+
const allowedOrderValues = orders.map((o) => o.value);
71101
const [currentPage, setCurrentPage] = usePageParam('workerpoolsPage');
102+
const [orderByFilter, setOrderByFilter] = useFilterParam(
103+
'workerpoolsOrderBy',
104+
allowedOrderValues,
105+
'pertinent'
106+
);
72107
const {
73108
data,
74109
isLoading,
75110
isRefetching,
76111
isError,
77112
hasPastError,
78113
additionalPages,
79-
} = useWorkerpoolsData(currentPage - 1);
114+
} = useWorkerpoolsData(currentPage - 1, orderByFilter);
115+
116+
function handleOrderChange(value: string) {
117+
setOrderByFilter(value);
118+
setCurrentPage(1);
119+
}
80120

81121
return (
82122
<div className="mt-8 grid gap-6">
83123
<div className="mt-6 flex flex-col justify-between lg:flex-row">
84-
<SearcherBar className="py-6 lg:order-last lg:mr-0 lg:max-w-md lg:py-0 xl:max-w-xl" />
124+
<div className="flex flex-col items-stretch gap-4 py-6 sm:flex-row lg:order-last lg:mr-0 lg:py-0">
125+
<SearcherBar className="lg:max-w-md xl:max-w-xl" />
126+
<Select
127+
value={orderByFilter?.toString()}
128+
onValueChange={handleOrderChange}
129+
defaultValue="pertinent"
130+
>
131+
<SelectTrigger className="m-auto box-content h-9! rounded-2xl">
132+
Order by
133+
</SelectTrigger>
134+
<SelectContent>
135+
{orders.map((order) => (
136+
<SelectItem key={order.id} value={order.value}>
137+
{order.name}
138+
</SelectItem>
139+
))}
140+
</SelectContent>
141+
</Select>
142+
</div>
85143
<div className="space-y-2">
86144
<h1 className="flex items-center gap-2 font-sans text-2xl font-extrabold">
87145
<WorkerpoolIcon size={24} />
@@ -116,6 +174,7 @@ function WorkerpoolsRoute() {
116174
currentPage={currentPage}
117175
totalPages={currentPage + additionalPages}
118176
onPageChange={setCurrentPage}
177+
filterKey={orderByFilter}
119178
/>
120179
</div>
121180
);

src/utils/createPlaceholderDataFnForQueryKey.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,17 @@ export const createPlaceholderDataFnForQueryKey =
3333
return previousData;
3434
}
3535
};
36+
37+
/**
38+
* Always reuse previous data (even if key changes) until fresh data arrives.
39+
* Useful for aggressively smoothing rapid key churn (rare). Opt-in separately.
40+
*/
41+
export const createPlaceholderDataFn =
42+
<TQueryFnData, TQueryData, TError>(): PlaceholderDataFunction<
43+
TQueryFnData,
44+
TError,
45+
TQueryData,
46+
QueryKey
47+
> =>
48+
(previousData) =>
49+
previousData;

0 commit comments

Comments
 (0)