metatron-open-seo/src/client/features/backlinks/ReferringDomainsTable.tsx
Ben Senescu 44073498ed
Use TanStack links for internal table and shell navigation (#215)
* Use TanStack Link for audit issues and external RD links

* Use router link for billing menu

* Fix brand lookup tooltip clipping
2026-05-22 21:29:45 -04:00

193 lines
5.2 KiB
TypeScript

import {
createColumnHelper,
type SortingFn,
type SortingState,
} from "@tanstack/react-table";
import { useState } from "react";
import {
AppDataTable,
useAppTable,
} from "@/client/components/table/AppDataTable";
import { SortableHeader } from "@/client/components/table/SortableHeader";
import {
compareNumericNullsLast,
dateNullsLast,
isDescending,
numericNullsLast,
stringNullsLast,
} from "@/client/components/table/nullSafeSort";
import { EmptyTableState } from "./BacklinksPageEmptyTableState";
import type { BacklinksOverviewData } from "./backlinksPageTypes";
import {
formatCompactDate,
formatDecimal,
formatNumber,
} from "./backlinksPageUtils";
import { BacklinksExternalLink } from "./BacklinksPageLinks";
type ReferringDomainRow = BacklinksOverviewData["referringDomains"][number];
const columnHelper = createColumnHelper<ReferringDomainRow>();
// Nulls always to the bottom in both directions, same as the pre-TanStack
// implementation. Secondary compare on brokenPages must also keep nulls last —
// coercing to 0 would mix unknown values with real zeroes.
const sortByIssues: SortingFn<ReferringDomainRow> = (left, right, columnId) => {
const descending = isDescending(left, columnId);
const primary = compareNumericNullsLast(
left.original.brokenBacklinks,
right.original.brokenBacklinks,
descending,
);
if (primary !== 0) return primary;
return compareNumericNullsLast(
left.original.brokenPages,
right.original.brokenPages,
descending,
);
};
const columns = [
columnHelper.accessor("domain", {
header: ({ column }) => (
<SortableHeader
column={column}
label="Domain"
helpText="The referring site linking to your target."
/>
),
cell: ({ getValue }) => {
const domain = getValue();
if (!domain) return "-";
return (
<BacklinksExternalLink
url={getDomainWebsiteHref(domain)}
label={domain}
className="link link-primary link-hover break-all inline-flex items-center gap-1"
/>
);
},
sortingFn: stringNullsLast,
}),
columnHelper.accessor("backlinks", {
header: ({ column }) => (
<SortableHeader
column={column}
label="Backlinks"
helpText="Total backlinks found from this domain."
/>
),
cell: ({ getValue }) => formatNumber(getValue()),
sortingFn: numericNullsLast,
sortDescFirst: true,
}),
columnHelper.accessor("referringPages", {
header: ({ column }) => (
<SortableHeader
column={column}
label="Referring Pages"
helpText="Unique pages on this domain that link to your target."
/>
),
cell: ({ getValue }) => formatNumber(getValue()),
sortingFn: numericNullsLast,
sortDescFirst: true,
}),
columnHelper.accessor("rank", {
header: ({ column }) => (
<SortableHeader
column={column}
label="Rank"
helpText="Authority score for the referring domain."
/>
),
cell: ({ getValue }) => formatNumber(getValue()),
sortingFn: numericNullsLast,
sortDescFirst: true,
}),
columnHelper.accessor("spamScore", {
header: ({ column }) => (
<SortableHeader
column={column}
label="Spam"
helpText="Spam risk score for this referring domain."
/>
),
cell: ({ getValue }) => formatDecimal(getValue()),
sortingFn: numericNullsLast,
sortDescFirst: true,
}),
columnHelper.accessor("firstSeen", {
header: ({ column }) => (
<SortableHeader
column={column}
label="First Seen"
helpText="When this domain was first discovered linking to your target."
/>
),
cell: ({ getValue }) => formatCompactDate(getValue()),
sortingFn: dateNullsLast,
sortDescFirst: true,
}),
columnHelper.display({
id: "issues",
header: ({ column }) => (
<SortableHeader
column={column}
label="Issues"
helpText="Broken link and broken page counts tied to this domain."
/>
),
cell: ({ row }) => (
<div className="text-sm">
<div>Broken links: {formatNumber(row.original.brokenBacklinks)}</div>
<div className="text-base-content/55">
Broken pages: {formatNumber(row.original.brokenPages)}
</div>
</div>
),
enableSorting: true,
sortingFn: sortByIssues,
sortDescFirst: true,
}),
];
const DEFAULT_SORTING: SortingState = [{ id: "backlinks", desc: true }];
function getDomainWebsiteHref(domain: string) {
try {
return new URL(domain).toString();
} catch {
return `https://${domain}`;
}
}
export function ReferringDomainsTable({
rows,
}: {
rows: BacklinksOverviewData["referringDomains"];
}) {
const [sorting, setSorting] = useState<SortingState>(DEFAULT_SORTING);
const table = useAppTable({
data: rows,
columns,
state: { sorting },
onSortingChange: setSorting,
withSorting: true,
});
if (rows.length === 0) {
return <EmptyTableState label="No referring domains match this filter." />;
}
return (
<AppDataTable
table={table}
getCellClassName={(_, columnId) =>
columnId === "domain" ? "font-medium break-all" : undefined
}
/>
);
}