Skip to content

Commit f80eed6

Browse files
committed
feat(indexd): host explorer filter sort
1 parent b578d00 commit f80eed6

11 files changed

Lines changed: 343 additions & 43 deletions

File tree

.changeset/shaky-houses-march.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'indexd': minor
3+
---
4+
5+
The location column now supports filtering by country.

.changeset/stale-dots-ring.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'indexd': minor
3+
---
4+
5+
The host explorer table can now be sorted by any column.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { Popover, Button } from '@siafoundation/design-system'
2+
import { Filter16 } from '@siafoundation/react-icons'
3+
import { Table } from '@tanstack/react-table'
4+
import { useState } from 'react'
5+
6+
interface ColumnFilterProps<T> {
7+
columnId: string
8+
table: Table<T>
9+
children: React.ReactNode
10+
}
11+
12+
export function ColumnFilter<T>({
13+
columnId,
14+
table,
15+
children,
16+
}: ColumnFilterProps<T>) {
17+
const [open, setOpen] = useState(false)
18+
const column = table.getColumn(columnId)
19+
const isFiltered = column?.getIsFiltered()
20+
21+
if (!column?.getCanFilter()) {
22+
return null
23+
}
24+
25+
return (
26+
<Popover
27+
trigger={
28+
<Button
29+
variant="ghost"
30+
size="none"
31+
icon={isFiltered ? 'contrast' : 'verySubtle'}
32+
>
33+
<Filter16 />
34+
</Button>
35+
}
36+
rootProps={{ open, onOpenChange: setOpen }}
37+
contentProps={{ align: 'end', className: 'w-64' }}
38+
>
39+
<div className="p-3 space-y-3">
40+
{children}
41+
{isFiltered && (
42+
<Button
43+
variant="ghost"
44+
size="small"
45+
onClick={() => {
46+
column.setFilterValue(undefined)
47+
setOpen(false)
48+
}}
49+
className="w-full"
50+
>
51+
Clear Filter
52+
</Button>
53+
)}
54+
</div>
55+
</Popover>
56+
)
57+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { Button } from '@siafoundation/design-system'
2+
import {
3+
ChevronSort16,
4+
ChevronSortDown16,
5+
ChevronSortUp16,
6+
} from '@siafoundation/react-icons'
7+
import { Table } from '@tanstack/react-table'
8+
9+
interface ColumnSortProps<T> {
10+
columnId: string
11+
table: Table<T>
12+
}
13+
14+
export function ColumnSort<T>({ columnId, table }: ColumnSortProps<T>) {
15+
const column = table.getColumn(columnId)
16+
const isSorted = column?.getIsSorted()
17+
18+
if (!column?.getCanSort()) {
19+
return null
20+
}
21+
22+
return (
23+
<Button
24+
variant="ghost"
25+
size="none"
26+
icon={isSorted ? 'contrast' : 'verySubtle'}
27+
onClick={() => {
28+
if (isSorted === 'asc') {
29+
column?.toggleSorting(true)
30+
} else if (isSorted === 'desc') {
31+
column?.clearSorting()
32+
} else {
33+
column?.toggleSorting(false)
34+
}
35+
}}
36+
>
37+
{isSorted === 'asc' ? (
38+
<ChevronSortUp16 />
39+
) : isSorted === 'desc' ? (
40+
<ChevronSortDown16 />
41+
) : (
42+
<ChevronSort16 />
43+
)}
44+
</Button>
45+
)
46+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { Select } from '@siafoundation/design-system'
2+
import { countryCodeEmoji, getCountryName } from '@siafoundation/units'
3+
import { Table } from '@tanstack/react-table'
4+
import { useMemo } from 'react'
5+
6+
interface CountryFilterProps<T extends { location?: { countryCode: string } }> {
7+
table: Table<T>
8+
}
9+
10+
export function CountryFilter<
11+
T extends { location?: { countryCode: string } },
12+
>({ table }: CountryFilterProps<T>) {
13+
const column = table.getColumn('location')
14+
const facets = useMemo(
15+
(): Map<string, number> => column?.getFacetedUniqueValues() || new Map(),
16+
[column],
17+
)
18+
const filterValue = column?.getFilterValue() as string | undefined
19+
20+
const countryOptions = useMemo(() => {
21+
const options: { value: string | undefined; label: string }[] = [
22+
{ value: '', label: 'All Countries' },
23+
]
24+
for (const [code] of facets) {
25+
if (code && code !== 'unknown') {
26+
options.push({
27+
value: code,
28+
label: `${countryCodeEmoji(code)} ${getCountryName(code)}`,
29+
})
30+
}
31+
}
32+
options.push({
33+
value: 'unknown',
34+
label: `❓ Unknown`,
35+
})
36+
return options
37+
}, [facets])
38+
39+
return (
40+
<div className="flex flex-col gap-1">
41+
<Select
42+
value={filterValue || ''}
43+
onChange={(e: React.ChangeEvent<HTMLSelectElement>) => {
44+
const value = e.target.value
45+
column?.setFilterValue(value === '' ? undefined : value)
46+
}}
47+
>
48+
{countryOptions.map((option) => (
49+
<option key={option.value} value={option.value}>
50+
{option.label}
51+
</option>
52+
))}
53+
</Select>
54+
</div>
55+
)
56+
}

apps/indexd/components/Data/Hosts/HostsTab.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ export function HostsTab() {
4545
data: hosts,
4646
columns,
4747
columnFilters,
48-
setColumnFilters,
4948
columnSorts,
5049
setColumnSorts,
50+
setColumnFilters,
5151
offset,
5252
limit,
5353
onRowClick,

apps/indexd/components/Data/Hosts/SidePanelHost.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export function SidePanelHost() {
2121
[selectedId, hosts],
2222
)
2323
const mapHost = useMemo(() => {
24-
if (!host || !host.location) return null
24+
if (!host || host.location.countryCode === 'unknown') return null
2525
const hostData: HostMapHost = {
2626
id: host.id,
2727
publicKey: host.id,

0 commit comments

Comments
 (0)