Fix backlink tab states (#218)
* Fix tab active state wiring across result views * Add spam filters to referring domains
This commit is contained in:
parent
a3721a0f53
commit
45234e6945
@ -277,6 +277,8 @@ function CitationTabsCard({ result }: { result: BrandLookupResult }) {
|
|||||||
const canExport = exportTable.rows.length > 0;
|
const canExport = exportTable.rows.length > 0;
|
||||||
|
|
||||||
const currentFilterCount = filters[activeTab].activeFilterCount;
|
const currentFilterCount = filters[activeTab].activeFilterCount;
|
||||||
|
const queriesActive = activeTab === "queries";
|
||||||
|
const pagesActive = activeTab === "pages";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="overflow-hidden rounded-xl border border-base-300 bg-base-100">
|
<section className="overflow-hidden rounded-xl border border-base-300 bg-base-100">
|
||||||
@ -285,7 +287,8 @@ function CitationTabsCard({ result }: { result: BrandLookupResult }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
className={`tab ${activeTab === "queries" ? "tab-active" : ""}`}
|
aria-selected={queriesActive}
|
||||||
|
className={`tab ${queriesActive ? "tab-active" : ""}`}
|
||||||
onClick={() => setActiveTab("queries")}
|
onClick={() => setActiveTab("queries")}
|
||||||
>
|
>
|
||||||
Queries
|
Queries
|
||||||
@ -293,7 +296,8 @@ function CitationTabsCard({ result }: { result: BrandLookupResult }) {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
className={`tab ${activeTab === "pages" ? "tab-active" : ""}`}
|
aria-selected={pagesActive}
|
||||||
|
className={`tab ${pagesActive ? "tab-active" : ""}`}
|
||||||
onClick={() => setActiveTab("pages")}
|
onClick={() => setActiveTab("pages")}
|
||||||
>
|
>
|
||||||
Related pages
|
Related pages
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { Link } from "@tanstack/react-router";
|
|
||||||
import { StatCard } from "@/client/features/audit/shared";
|
import { StatCard } from "@/client/features/audit/shared";
|
||||||
import {
|
import {
|
||||||
exportPages,
|
exportPages,
|
||||||
@ -13,14 +12,18 @@ import {
|
|||||||
PerformanceTable,
|
PerformanceTable,
|
||||||
} from "@/client/features/audit/results/ResultsTables";
|
} from "@/client/features/audit/results/ResultsTables";
|
||||||
|
|
||||||
|
type ResultsTab = "pages" | "performance";
|
||||||
|
|
||||||
export function ResultsView({
|
export function ResultsView({
|
||||||
projectId,
|
projectId,
|
||||||
data,
|
data,
|
||||||
|
onTabChange,
|
||||||
tab,
|
tab,
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
data: AuditResultsData;
|
data: AuditResultsData;
|
||||||
tab: string;
|
tab: string;
|
||||||
|
onTabChange: (tab: ResultsTab) => void;
|
||||||
}) {
|
}) {
|
||||||
const { audit, pages, lighthouse } = data;
|
const { audit, pages, lighthouse } = data;
|
||||||
const hasPerformanceTab = lighthouse.length > 0;
|
const hasPerformanceTab = lighthouse.length > 0;
|
||||||
@ -40,12 +43,11 @@ export function ResultsView({
|
|||||||
<div className="card bg-base-100 border border-base-300">
|
<div className="card bg-base-100 border border-base-300">
|
||||||
<div className="card-body gap-3">
|
<div className="card-body gap-3">
|
||||||
<ResultsHeader
|
<ResultsHeader
|
||||||
projectId={projectId}
|
|
||||||
auditId={audit.id}
|
|
||||||
pageCount={pages.length}
|
pageCount={pages.length}
|
||||||
lighthouseCount={lighthouse.length}
|
lighthouseCount={lighthouse.length}
|
||||||
hasPerformanceTab={hasPerformanceTab}
|
hasPerformanceTab={hasPerformanceTab}
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
|
onTabChange={onTabChange}
|
||||||
onExport={(format) => {
|
onExport={(format) => {
|
||||||
if (activeTab === "performance") {
|
if (activeTab === "performance") {
|
||||||
exportPerformance(lighthouse, pages, format);
|
exportPerformance(lighthouse, pages, format);
|
||||||
@ -115,46 +117,45 @@ function useResultStats(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ResultsHeader({
|
function ResultsHeader({
|
||||||
projectId,
|
|
||||||
auditId,
|
|
||||||
pageCount,
|
pageCount,
|
||||||
lighthouseCount,
|
lighthouseCount,
|
||||||
hasPerformanceTab,
|
hasPerformanceTab,
|
||||||
activeTab,
|
activeTab,
|
||||||
|
onTabChange,
|
||||||
onExport,
|
onExport,
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
|
||||||
auditId: string;
|
|
||||||
pageCount: number;
|
pageCount: number;
|
||||||
lighthouseCount: number;
|
lighthouseCount: number;
|
||||||
hasPerformanceTab: boolean;
|
hasPerformanceTab: boolean;
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
|
onTabChange: (tab: ResultsTab) => void;
|
||||||
onExport: (format: "csv" | "json" | "sheets") => void;
|
onExport: (format: "csv" | "json" | "sheets") => void;
|
||||||
}) {
|
}) {
|
||||||
|
const tabs: Array<{ tab: ResultsTab; label: string }> = [
|
||||||
|
{ tab: "pages", label: `Pages (${pageCount})` },
|
||||||
|
{ tab: "performance", label: `Performance (${lighthouseCount})` },
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-3">
|
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-3">
|
||||||
{hasPerformanceTab ? (
|
{hasPerformanceTab ? (
|
||||||
<div role="tablist" className="tabs tabs-box w-fit">
|
<div role="tablist" className="tabs tabs-box w-fit">
|
||||||
<Link
|
{tabs.map(({ label, tab }) => {
|
||||||
to="/p/$projectId/audit"
|
const isActive = activeTab === tab;
|
||||||
params={{ projectId }}
|
|
||||||
search={{ auditId, tab: "pages" }}
|
return (
|
||||||
replace
|
<button
|
||||||
|
key={tab}
|
||||||
|
type="button"
|
||||||
role="tab"
|
role="tab"
|
||||||
className={`tab ${activeTab === "pages" ? "tab-active" : ""}`}
|
aria-selected={isActive}
|
||||||
|
className={`tab ${isActive ? "tab-active" : ""}`}
|
||||||
|
onClick={() => onTabChange(tab)}
|
||||||
>
|
>
|
||||||
Pages ({pageCount})
|
{label}
|
||||||
</Link>
|
</button>
|
||||||
<Link
|
);
|
||||||
to="/p/$projectId/audit"
|
})}
|
||||||
params={{ projectId }}
|
|
||||||
search={{ auditId, tab: "performance" }}
|
|
||||||
replace
|
|
||||||
role="tab"
|
|
||||||
className={`tab ${activeTab === "performance" ? "tab-active" : ""}`}
|
|
||||||
>
|
|
||||||
Performance ({lighthouseCount})
|
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<h3 className="text-base font-medium">Pages ({pageCount})</h3>
|
<h3 className="text-base font-medium">Pages ({pageCount})</h3>
|
||||||
|
|||||||
@ -239,7 +239,7 @@ function ReferringDomainsFilters({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-2 lg:grid-cols-2">
|
<div className="grid grid-cols-1 gap-2 lg:grid-cols-3">
|
||||||
<FilterRangeInputs
|
<FilterRangeInputs
|
||||||
form={form}
|
form={form}
|
||||||
title="Backlinks"
|
title="Backlinks"
|
||||||
@ -252,6 +252,13 @@ function ReferringDomainsFilters({
|
|||||||
minName="minRank"
|
minName="minRank"
|
||||||
maxName="maxRank"
|
maxName="maxRank"
|
||||||
/>
|
/>
|
||||||
|
<FilterRangeInputs
|
||||||
|
form={form}
|
||||||
|
title="Spam Score"
|
||||||
|
minName="minSpamScore"
|
||||||
|
maxName="maxSpamScore"
|
||||||
|
step="0.1"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -65,6 +65,18 @@ export function BacklinksPage({
|
|||||||
},
|
},
|
||||||
[navigate],
|
[navigate],
|
||||||
);
|
);
|
||||||
|
const handleResultTabChange = useCallback(
|
||||||
|
(tab: BacklinksSearchState["tab"]) => {
|
||||||
|
navigate({
|
||||||
|
search: (prev) => ({
|
||||||
|
...prev,
|
||||||
|
tab: tab === "backlinks" ? undefined : tab,
|
||||||
|
}),
|
||||||
|
replace: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[navigate],
|
||||||
|
);
|
||||||
const searchTabs = useSearchTabNavigation({
|
const searchTabs = useSearchTabNavigation({
|
||||||
storageKey: `backlinks:${projectId}`,
|
storageKey: `backlinks:${projectId}`,
|
||||||
urlInput: urlTabInput,
|
urlInput: urlTabInput,
|
||||||
@ -134,6 +146,7 @@ export function BacklinksPage({
|
|||||||
topPages={topPagesQuery.data}
|
topPages={topPagesQuery.data}
|
||||||
onRemoveHistoryItem={removeHistoryItem}
|
onRemoveHistoryItem={removeHistoryItem}
|
||||||
onRetryOverview={() => void overviewQuery.refetch()}
|
onRetryOverview={() => void overviewQuery.refetch()}
|
||||||
|
onTabChange={handleResultTabChange}
|
||||||
searchTabs={
|
searchTabs={
|
||||||
searchState.target
|
searchState.target
|
||||||
? {
|
? {
|
||||||
|
|||||||
@ -47,6 +47,7 @@ type BacklinksBodyProps = {
|
|||||||
topPages: BacklinksTopPagesData | undefined;
|
topPages: BacklinksTopPagesData | undefined;
|
||||||
onRemoveHistoryItem: (timestamp: number) => void;
|
onRemoveHistoryItem: (timestamp: number) => void;
|
||||||
onRetryOverview: () => void;
|
onRetryOverview: () => void;
|
||||||
|
onTabChange: (tab: BacklinksSearchState["tab"]) => void;
|
||||||
searchTabs: {
|
searchTabs: {
|
||||||
activeTabId: string | null;
|
activeTabId: string | null;
|
||||||
tabs: SearchTab[];
|
tabs: SearchTab[];
|
||||||
@ -73,6 +74,7 @@ export function BacklinksBody({
|
|||||||
topPages,
|
topPages,
|
||||||
onRemoveHistoryItem,
|
onRemoveHistoryItem,
|
||||||
onRetryOverview,
|
onRetryOverview,
|
||||||
|
onTabChange,
|
||||||
searchTabs,
|
searchTabs,
|
||||||
}: BacklinksBodyProps) {
|
}: BacklinksBodyProps) {
|
||||||
const mergedData = useMemo(
|
const mergedData = useMemo(
|
||||||
@ -179,7 +181,6 @@ export function BacklinksBody({
|
|||||||
summaryStats={summaryStats}
|
summaryStats={summaryStats}
|
||||||
/>
|
/>
|
||||||
<BacklinksResultsCard
|
<BacklinksResultsCard
|
||||||
projectId={projectId}
|
|
||||||
activeTab={searchState.tab}
|
activeTab={searchState.tab}
|
||||||
filteredData={filteredData}
|
filteredData={filteredData}
|
||||||
filters={filters}
|
filters={filters}
|
||||||
@ -188,6 +189,7 @@ export function BacklinksBody({
|
|||||||
searchState.tab !== "backlinks" ? tabErrorMessage : null
|
searchState.tab !== "backlinks" ? tabErrorMessage : null
|
||||||
}
|
}
|
||||||
exportTarget={mergedData.displayTarget || searchState.target}
|
exportTarget={mergedData.displayTarget || searchState.target}
|
||||||
|
onTabChange={onTabChange}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -22,6 +22,15 @@ import {
|
|||||||
import { buildBacklinksTabExport, exportBacklinksTabCsv } from "./export";
|
import { buildBacklinksTabExport, exportBacklinksTabCsv } from "./export";
|
||||||
import type { BacklinksFiltersState } from "./useBacklinksFilters";
|
import type { BacklinksFiltersState } from "./useBacklinksFilters";
|
||||||
|
|
||||||
|
const BACKLINKS_RESULTS_TABS: Array<{
|
||||||
|
tab: BacklinksSearchState["tab"];
|
||||||
|
label: string;
|
||||||
|
}> = [
|
||||||
|
{ tab: "backlinks", label: "Backlinks" },
|
||||||
|
{ tab: "domains", label: "Referring Domains" },
|
||||||
|
{ tab: "pages", label: "Top Pages" },
|
||||||
|
];
|
||||||
|
|
||||||
export function BacklinksOverviewPanels({
|
export function BacklinksOverviewPanels({
|
||||||
projectId,
|
projectId,
|
||||||
data,
|
data,
|
||||||
@ -66,15 +75,14 @@ export function BacklinksOverviewPanels({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function BacklinksResultsCard({
|
export function BacklinksResultsCard({
|
||||||
projectId,
|
|
||||||
activeTab,
|
activeTab,
|
||||||
filteredData,
|
filteredData,
|
||||||
filters,
|
filters,
|
||||||
isTabLoading,
|
isTabLoading,
|
||||||
tabErrorMessage,
|
tabErrorMessage,
|
||||||
exportTarget,
|
exportTarget,
|
||||||
|
onTabChange,
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
|
||||||
activeTab: BacklinksSearchState["tab"];
|
activeTab: BacklinksSearchState["tab"];
|
||||||
filteredData: {
|
filteredData: {
|
||||||
backlinks: BacklinksOverviewData["backlinks"];
|
backlinks: BacklinksOverviewData["backlinks"];
|
||||||
@ -85,6 +93,7 @@ export function BacklinksResultsCard({
|
|||||||
isTabLoading: boolean;
|
isTabLoading: boolean;
|
||||||
tabErrorMessage: string | null;
|
tabErrorMessage: string | null;
|
||||||
exportTarget: string;
|
exportTarget: string;
|
||||||
|
onTabChange: (tab: BacklinksSearchState["tab"]) => void;
|
||||||
}) {
|
}) {
|
||||||
const currentFilterCount = filters[activeTab].activeFilterCount;
|
const currentFilterCount = filters[activeTab].activeFilterCount;
|
||||||
const exportTable = useMemo(
|
const exportTable = useMemo(
|
||||||
@ -97,19 +106,15 @@ export function BacklinksResultsCard({
|
|||||||
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-3 px-4 py-3 border-b border-base-300">
|
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-3 px-4 py-3 border-b border-base-300">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div role="tablist" className="tabs tabs-box w-fit">
|
<div role="tablist" className="tabs tabs-box w-fit">
|
||||||
|
{BACKLINKS_RESULTS_TABS.map(({ label, tab }) => (
|
||||||
<TabLink
|
<TabLink
|
||||||
projectId={projectId}
|
key={tab}
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
tab="backlinks"
|
label={label}
|
||||||
>
|
onSelect={onTabChange}
|
||||||
Backlinks
|
tab={tab}
|
||||||
</TabLink>
|
/>
|
||||||
<TabLink projectId={projectId} activeTab={activeTab} tab="domains">
|
))}
|
||||||
Referring Domains
|
|
||||||
</TabLink>
|
|
||||||
<TabLink projectId={projectId} activeTab={activeTab} tab="pages">
|
|
||||||
Top Pages
|
|
||||||
</TabLink>
|
|
||||||
</div>
|
</div>
|
||||||
<p className="max-w-xl text-sm text-base-content/60">
|
<p className="max-w-xl text-sm text-base-content/60">
|
||||||
{TAB_DESCRIPTIONS[activeTab]}
|
{TAB_DESCRIPTIONS[activeTab]}
|
||||||
@ -276,30 +281,28 @@ function TrendCard({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function TabLink({
|
function TabLink({
|
||||||
projectId,
|
|
||||||
activeTab,
|
activeTab,
|
||||||
children,
|
label,
|
||||||
|
onSelect,
|
||||||
tab,
|
tab,
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
|
||||||
activeTab: BacklinksSearchState["tab"];
|
activeTab: BacklinksSearchState["tab"];
|
||||||
children: string;
|
label: string;
|
||||||
|
onSelect: (tab: BacklinksSearchState["tab"]) => void;
|
||||||
tab: BacklinksSearchState["tab"];
|
tab: BacklinksSearchState["tab"];
|
||||||
}) {
|
}) {
|
||||||
|
const isActive = activeTab === tab;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link
|
<button
|
||||||
to="/p/$projectId/backlinks"
|
type="button"
|
||||||
params={{ projectId }}
|
|
||||||
search={(prev) => ({
|
|
||||||
...prev,
|
|
||||||
tab: tab === "backlinks" ? undefined : tab,
|
|
||||||
})}
|
|
||||||
replace
|
|
||||||
role="tab"
|
role="tab"
|
||||||
className={`tab ${activeTab === tab ? "tab-active" : ""}`}
|
aria-selected={isActive}
|
||||||
|
className={`tab ${isActive ? "tab-active" : ""}`}
|
||||||
|
onClick={() => onSelect(tab)}
|
||||||
>
|
>
|
||||||
{children}
|
{label}
|
||||||
</Link>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,8 @@ export type ReferringDomainsFilterValues = {
|
|||||||
maxBacklinks: string;
|
maxBacklinks: string;
|
||||||
minRank: string;
|
minRank: string;
|
||||||
maxRank: string;
|
maxRank: string;
|
||||||
|
minSpamScore: string;
|
||||||
|
maxSpamScore: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TopPagesFilterValues = {
|
export type TopPagesFilterValues = {
|
||||||
@ -53,6 +55,8 @@ export const EMPTY_REFERRING_DOMAINS_FILTERS: ReferringDomainsFilterValues = {
|
|||||||
maxBacklinks: "",
|
maxBacklinks: "",
|
||||||
minRank: "",
|
minRank: "",
|
||||||
maxRank: "",
|
maxRank: "",
|
||||||
|
minSpamScore: "",
|
||||||
|
maxSpamScore: "",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const EMPTY_TOP_PAGES_FILTERS: TopPagesFilterValues = {
|
export const EMPTY_TOP_PAGES_FILTERS: TopPagesFilterValues = {
|
||||||
|
|||||||
46
src/client/features/backlinks/backlinksFiltering.test.ts
Normal file
46
src/client/features/backlinks/backlinksFiltering.test.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { BacklinksOverviewData } from "./backlinksPageTypes";
|
||||||
|
import { EMPTY_REFERRING_DOMAINS_FILTERS } from "./backlinksFilterTypes";
|
||||||
|
import { filterReferringDomainRows } from "./backlinksFiltering";
|
||||||
|
|
||||||
|
type ReferringDomainRow = BacklinksOverviewData["referringDomains"][number];
|
||||||
|
|
||||||
|
function makeReferringDomainRow(
|
||||||
|
overrides: Partial<ReferringDomainRow> = {},
|
||||||
|
): ReferringDomainRow {
|
||||||
|
return {
|
||||||
|
domain: "example.com",
|
||||||
|
backlinks: 10,
|
||||||
|
referringPages: 5,
|
||||||
|
rank: 20,
|
||||||
|
spamScore: 2,
|
||||||
|
firstSeen: null,
|
||||||
|
brokenBacklinks: 0,
|
||||||
|
brokenPages: 0,
|
||||||
|
...overrides,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("filterReferringDomainRows", () => {
|
||||||
|
it("filters by spam score range", () => {
|
||||||
|
const rows = [
|
||||||
|
makeReferringDomainRow({ domain: "clean.example", spamScore: 1 }),
|
||||||
|
makeReferringDomainRow({ domain: "risky.example", spamScore: 7 }),
|
||||||
|
makeReferringDomainRow({ domain: "unknown.example", spamScore: null }),
|
||||||
|
];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
filterReferringDomainRows(rows, {
|
||||||
|
...EMPTY_REFERRING_DOMAINS_FILTERS,
|
||||||
|
maxSpamScore: "3",
|
||||||
|
}),
|
||||||
|
).toEqual([rows[0], rows[2]]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
filterReferringDomainRows(rows, {
|
||||||
|
...EMPTY_REFERRING_DOMAINS_FILTERS,
|
||||||
|
minSpamScore: "3",
|
||||||
|
}),
|
||||||
|
).toEqual([rows[1], rows[2]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -107,6 +107,14 @@ export function filterReferringDomainRows(
|
|||||||
return false;
|
return false;
|
||||||
if (!passesNumericFilter(row.rank, filters.minRank, filters.maxRank))
|
if (!passesNumericFilter(row.rank, filters.minRank, filters.maxRank))
|
||||||
return false;
|
return false;
|
||||||
|
if (
|
||||||
|
!passesNumericFilter(
|
||||||
|
row.spamScore,
|
||||||
|
filters.minSpamScore,
|
||||||
|
filters.maxSpamScore,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,6 +56,7 @@ function SiteAuditPage() {
|
|||||||
auditId={auditId}
|
auditId={auditId}
|
||||||
tab={tab}
|
tab={tab}
|
||||||
onBack={() => setSearchParams({ auditId: undefined })}
|
onBack={() => setSearchParams({ auditId: undefined })}
|
||||||
|
onTabChange={(nextTab) => setSearchParams({ tab: nextTab })}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -65,11 +66,13 @@ function AuditDetail({
|
|||||||
auditId,
|
auditId,
|
||||||
tab,
|
tab,
|
||||||
onBack,
|
onBack,
|
||||||
|
onTabChange,
|
||||||
}: {
|
}: {
|
||||||
projectId: string;
|
projectId: string;
|
||||||
auditId: string;
|
auditId: string;
|
||||||
tab: string;
|
tab: string;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
|
onTabChange: (tab: "pages" | "performance") => void;
|
||||||
}) {
|
}) {
|
||||||
const statusQuery = useQuery({
|
const statusQuery = useQuery({
|
||||||
queryKey: ["audit-status", projectId, auditId],
|
queryKey: ["audit-status", projectId, auditId],
|
||||||
@ -178,6 +181,7 @@ function AuditDetail({
|
|||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
data={resultsQuery.data}
|
data={resultsQuery.data}
|
||||||
tab={tab}
|
tab={tab}
|
||||||
|
onTabChange={onTabChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user