Skip to content
2 changes: 2 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 0 additions & 26 deletions src/components/AnchorsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import { useAsync } from "@/hooks/useAsync";
import { useToast } from "@/hooks/useToast";
import { useFocusShortcut } from "@/hooks/useFocusShortcut";
import { useQueryState } from "@/hooks/useQueryState";
import { useDebouncedValue } from "@/hooks/useDebouncedValue";
import { Card } from "./Card";
import { TableSkeleton } from "./TableSkeleton";
import { AnchorForm } from "./AnchorForm";
Expand Down Expand Up @@ -80,30 +79,6 @@ export function AnchorsPanel() {
const searchRef = useRef<HTMLInputElement>(null);
useFocusShortcut("/", searchRef);

// One ref per filter button for imperative focus management (roving tabindex).
const filterRefs = useRef<Array<HTMLButtonElement | null>>(
Array(FILTERS.length).fill(null),
);

/** Arrow-key / Home / End roving focus across the filter button group. */
function onFilterKeyDown(
event: React.KeyboardEvent<HTMLButtonElement>,
index: number,
) {
const last = FILTERS.length - 1;
let next: number | null = null;

if (event.key === "ArrowRight") next = index === last ? 0 : index + 1;
else if (event.key === "ArrowLeft") next = index === 0 ? last : index - 1;
else if (event.key === "Home") next = 0;
else if (event.key === "End") next = last;

if (next !== null) {
event.preventDefault();
filterRefs.current[next]?.focus();
}
}

// Sync status filter and search query to the URL querystring.
// Initial values are hydrated from the URL on first render.
const [rawStatus, setStatus] = useQueryState("status", "all");
Expand Down Expand Up @@ -247,7 +222,6 @@ export function AnchorsPanel() {
filterRefs.current[i] = el;
}}
onClick={() => setStatus(f.value)}
onKeyDown={(e) => onFilterKeyDown(e, i)}
aria-pressed={filter === f.value}
tabIndex={filter === f.value ? 0 : -1}
className={`rounded-full px-3 py-1 text-xs font-medium transition-colors ${
Expand Down
14 changes: 13 additions & 1 deletion src/components/SettlementsPanel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,19 @@ vi.mock("next/navigation", () => ({
usePathname: () => "/settlements",
}));

// ---------------------------------------------------------------------------
// Mock next/navigation so the panel can run in jsdom.
// ---------------------------------------------------------------------------

const mockReplace = vi.fn();
let mockSearchParamsString = "";

vi.mock("next/navigation", () => ({
useRouter: () => ({ replace: mockReplace }),
useSearchParams: () => new URLSearchParams(mockSearchParamsString),
usePathname: () => "/settlements",
}));

vi.mock("@/lib/settlementsApi", () => ({
fetchSettlements: vi.fn(),
openSettlement: vi.fn(),
Expand All @@ -50,7 +63,6 @@ vi.mock("@/lib/api", async (importOriginal) => {
beforeEach(() => {
vi.clearAllMocks();
mockSearchParamsString = "";
vi.mocked(fetchPools).mockResolvedValue([]);
});

function page(
Expand Down
9 changes: 3 additions & 6 deletions src/lib/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ export function matchesQuery(
fields: Array<string | number>,
query: string,
): boolean {
const trimmed = query.trim().replace(/\s+/g, " ").toLowerCase();
if (trimmed === "") return true;
const terms = trimmed.split(" ");
return terms.every((term) =>
fields.some((field) => String(field).toLowerCase().includes(term))
);
const needle = query.trim().replace(/\s+/g, " ").toLowerCase();
if (needle === "") return true;
return fields.some((field) => String(field).toLowerCase().includes(needle));
}
7 changes: 1 addition & 6 deletions src/lib/settlementsApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,7 @@ export async function exportSettlementsCsv(
options: FetchSettlementsOptions = {},
): Promise<string> {
const { anchor, page, pageSize, signal } = options;
const params = new URLSearchParams();
if (anchor) params.set("anchor", anchor);
if (page) params.set("page", String(page));
if (pageSize) params.set("pageSize", String(pageSize));
params.set("format", "csv");
const query = params.toString() ? `?${params.toString()}` : "";
const query = buildQueryParams({ anchor, page, pageSize, format: "csv" });
return apiTextRequest(`/api/v1/settlements${query}`, { signal });
}

Expand Down
Loading