From 02a287b98e0c20d3864502d94e4dc6dd5a22c14f Mon Sep 17 00:00:00 2001 From: Ben Senescu <44480372+bensenescu@users.noreply.github.com> Date: Tue, 26 May 2026 23:33:26 -0400 Subject: [PATCH] fix audit tables + back link columns (#224) --- .../results/AuditResultsTableFilterLogic.ts | 26 +++ .../results/AuditResultsTableFilters.tsx | 208 +++++++++++------- .../features/audit/results/ResultsTables.tsx | 67 +++--- .../backlinks/BacklinksTableColumns.tsx | 12 +- 4 files changed, 199 insertions(+), 114 deletions(-) diff --git a/src/client/features/audit/results/AuditResultsTableFilterLogic.ts b/src/client/features/audit/results/AuditResultsTableFilterLogic.ts index e58676b..8df925b 100644 --- a/src/client/features/audit/results/AuditResultsTableFilterLogic.ts +++ b/src/client/features/audit/results/AuditResultsTableFilterLogic.ts @@ -163,3 +163,29 @@ function parseFilterNumber(value: string) { const parsed = Number(trimmed); return Number.isFinite(parsed) ? parsed : null; } + +export function nullableNumberSort( + left: { getValue: (columnId: string) => number | null }, + right: { getValue: (columnId: string) => number | null }, + columnId: string, +) { + const a = left.getValue(columnId); + const b = right.getValue(columnId); + if (a == null && b == null) return 0; + if (a == null) return 1; + if (b == null) return -1; + return a - b; +} + +export function nullableStringSort( + left: { getValue: (columnId: string) => string | null }, + right: { getValue: (columnId: string) => string | null }, + columnId: string, +) { + const a = left.getValue(columnId); + const b = right.getValue(columnId); + if (!a && !b) return 0; + if (!a) return 1; + if (!b) return -1; + return a.localeCompare(b); +} diff --git a/src/client/features/audit/results/AuditResultsTableFilters.tsx b/src/client/features/audit/results/AuditResultsTableFilters.tsx index 039d84f..2e9b086 100644 --- a/src/client/features/audit/results/AuditResultsTableFilters.tsx +++ b/src/client/features/audit/results/AuditResultsTableFilters.tsx @@ -1,24 +1,24 @@ -import { - EMPTY_PAGES_FILTERS, - EMPTY_PERFORMANCE_FILTERS, - type PagesFilters, - type PerformanceFilters, +import { RotateCcw, SlidersHorizontal } from "lucide-react"; +import type { ReactNode } from "react"; +import type { + PagesFilters, + PerformanceFilters, } from "@/client/features/audit/results/AuditResultsTableFilterLogic"; export function PagesFilterBar({ filters, onChange, - resultCount, - totalCount, + activeFilterCount, + onReset, }: { filters: PagesFilters; onChange: (filters: PagesFilters) => void; - resultCount: number; - totalCount: number; + activeFilterCount: number; + onReset: () => void; }) { return ( -
-
+ +
+ onChange({ ...filters, missingAlt })} + options={[ + ["all", "All"], + ["yes", "Missing alt"], + ["no", "No missing alt"], + ]} + /> +
+
- onChange({ ...filters, missingAlt })} - options={[ - ["all", "All"], - ["yes", "Missing alt"], - ["no", "No missing alt"], - ]} - /> - onChange(EMPTY_PAGES_FILTERS)} - />
-
+ ); } export function PerformanceFilterBar({ filters, onChange, - resultCount, - totalCount, + activeFilterCount, + onReset, }: { filters: PerformanceFilters; onChange: (filters: PerformanceFilters) => void; - resultCount: number; - totalCount: number; + activeFilterCount: number; + onReset: () => void; }) { return ( -
-
+ +
+ onChange({ ...filters, maxLcpSeconds })} + /> +
+
onChange({ ...filters, minSeo })} onMaxChange={(maxSeo) => onChange({ ...filters, maxSeo })} /> - onChange({ ...filters, maxLcpSeconds })} - /> - onChange(EMPTY_PERFORMANCE_FILTERS)} - />
-
+ ); } @@ -150,6 +144,87 @@ export function EmptyTableMessage({ label }: { label: string }) { return
{label}
; } +export function TableFilterToggle({ + showFilters, + onToggle, + activeFilterCount, + resultCount, + totalCount, +}: { + showFilters: boolean; + onToggle: () => void; + activeFilterCount: number; + resultCount: number; + totalCount: number; +}) { + return ( +
+ + + {resultCount.toLocaleString()} of {totalCount.toLocaleString()} + +
+ ); +} + +export function countActiveFilters>( + filters: TFilters, + emptyFilters: TFilters, +) { + return Object.keys(filters).reduce((count, key) => { + const filterKey = key as keyof TFilters; + return filters[filterKey] !== emptyFilters[filterKey] ? count + 1 : count; + }, 0); +} + +function FilterPanel({ + activeFilterCount, + onReset, + children, +}: { + activeFilterCount: number; + onReset: () => void; + children: ReactNode; +}) { + return ( +
+
+
+

Refine results

+ {activeFilterCount > 0 ? ( + + {activeFilterCount} active + + ) : null} +
+ +
+ {children} +
+ ); +} + function TextFilter({ label, value, @@ -164,12 +239,12 @@ function TextFilter({ onChange: (value: string) => void; }) { return ( -