Skip to content

Commit 7a4567d

Browse files
saagar210claude
andcommitted
refactor(components): lift AnalyticsTab data loading into hook
Extract the loadData promise fanout, period/loading/error state, data slices (summary/kbUsage/qualitySummary/qualityDrilldown/ lowRatingData/gapCandidates), and the KB-gap-status mutator into useAnalyticsLoader. Shell consumes the hook and keeps only activeSection, qualityThresholds, and selectedArticleId state. Wave 5.2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent f202777 commit 7a4567d

2 files changed

Lines changed: 143 additions & 90 deletions

File tree

src/components/Analytics/AnalyticsTab.tsx

Lines changed: 18 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,24 @@
1-
import { useState, useEffect, useCallback } from "react";
2-
import {
3-
useAnalytics,
4-
AnalyticsSummary,
5-
ArticleUsage,
6-
LowRatingAnalysis,
7-
ResponseQualityDrilldownExamples,
8-
ResponseQualitySummary,
9-
} from "../../hooks/useAnalytics";
10-
import { useInsightsOps } from "../../hooks/useInsightsOps";
1+
import { useState, useEffect } from "react";
112
import type { ResponseQualityThresholds } from "../../features/analytics/qualityThresholds";
123
import {
134
readCurrentThresholds,
145
subscribeToQualityThresholds,
156
} from "./qualityThresholdsState";
16-
import type { KbGapCandidate } from "../../types/insights";
177
import { ArticleDetailPanel } from "./ArticleDetailPanel";
188
import { PilotDiagnosticsSection } from "./PilotDiagnosticsSection";
199
import { RatingDistribution } from "./RatingDistribution";
2010
import { KbUsageTable } from "./KbUsageTable";
2111
import { ResponseQualityPanel } from "./ResponseQualityPanel";
12+
import { useAnalyticsLoader, type AnalyticsPeriod } from "./useAnalyticsLoader";
2213
import "./AnalyticsTab.css";
2314

24-
type Period = 7 | 30 | 90 | null; // null = all time
2515
type AnalyticsSection = "overview" | "pilot";
2616

2717
interface AnalyticsTabProps {
2818
initialSection?: AnalyticsSection;
2919
}
3020

31-
const PERIODS: { label: string; value: Period }[] = [
21+
const PERIODS: { label: string; value: AnalyticsPeriod }[] = [
3222
{ label: "7 days", value: 7 },
3323
{ label: "30 days", value: 30 },
3424
{ label: "90 days", value: 90 },
@@ -39,30 +29,22 @@ export function AnalyticsTab({
3929
initialSection = "overview",
4030
}: AnalyticsTabProps) {
4131
const {
42-
getSummary,
43-
getKbUsage,
44-
getLowRatingAnalysis,
45-
getResponseQualitySummary,
46-
getResponseQualityDrilldownExamples,
47-
} = useAnalytics();
48-
const { getKbGapCandidates, updateKbGapStatus } = useInsightsOps();
32+
summary,
33+
qualitySummary,
34+
qualityDrilldown,
35+
kbUsage,
36+
lowRatingData,
37+
gapCandidates,
38+
loading,
39+
error,
40+
period,
41+
setPeriod,
42+
updateGapStatus,
43+
} = useAnalyticsLoader();
4944
const [activeSection, setActiveSection] =
5045
useState<AnalyticsSection>(initialSection);
51-
const [period, setPeriod] = useState<Period>(30);
52-
const [summary, setSummary] = useState<AnalyticsSummary | null>(null);
53-
const [qualitySummary, setQualitySummary] =
54-
useState<ResponseQualitySummary | null>(null);
55-
const [qualityDrilldown, setQualityDrilldown] =
56-
useState<ResponseQualityDrilldownExamples | null>(null);
5746
const [qualityThresholds, setQualityThresholds] =
5847
useState<ResponseQualityThresholds>(() => readCurrentThresholds());
59-
const [kbUsage, setKbUsage] = useState<ArticleUsage[]>([]);
60-
const [lowRatingData, setLowRatingData] = useState<LowRatingAnalysis | null>(
61-
null,
62-
);
63-
const [gapCandidates, setGapCandidates] = useState<KbGapCandidate[]>([]);
64-
const [loading, setLoading] = useState(true);
65-
const [error, setError] = useState<string | null>(null);
6648
const [selectedArticleId, setSelectedArticleId] = useState<string | null>(
6749
null,
6850
);
@@ -71,60 +53,6 @@ export function AnalyticsTab({
7153
setActiveSection(initialSection);
7254
}, [initialSection]);
7355

74-
const loadData = useCallback(async () => {
75-
setLoading(true);
76-
setError(null);
77-
try {
78-
const [
79-
summaryData,
80-
kbData,
81-
lowRating,
82-
qualityData,
83-
qualityDrilldownData,
84-
] = await Promise.all([
85-
getSummary(period ?? undefined),
86-
getKbUsage(period ?? undefined),
87-
getLowRatingAnalysis(period ?? undefined).catch(() => null),
88-
getResponseQualitySummary(period ?? undefined).catch(() => null),
89-
getResponseQualityDrilldownExamples(period ?? undefined, 6).catch(
90-
() => null,
91-
),
92-
]);
93-
const gaps = await getKbGapCandidates(12, "open").catch(() => []);
94-
setSummary(summaryData);
95-
setQualitySummary(qualityData);
96-
setQualityDrilldown(qualityDrilldownData);
97-
setKbUsage(kbData);
98-
setLowRatingData(lowRating);
99-
setGapCandidates(gaps);
100-
} catch (err) {
101-
console.error("Failed to load analytics:", err);
102-
setError(typeof err === "string" ? err : "Failed to load analytics data");
103-
} finally {
104-
setLoading(false);
105-
}
106-
}, [
107-
period,
108-
getSummary,
109-
getKbUsage,
110-
getLowRatingAnalysis,
111-
getResponseQualitySummary,
112-
getResponseQualityDrilldownExamples,
113-
getKbGapCandidates,
114-
]);
115-
116-
const handleGapStatus = useCallback(
117-
async (id: string, status: "accepted" | "resolved" | "ignored") => {
118-
await updateKbGapStatus(id, status);
119-
setGapCandidates((prev) => prev.filter((g) => g.id !== id));
120-
},
121-
[updateKbGapStatus],
122-
);
123-
124-
useEffect(() => {
125-
loadData();
126-
}, [loadData]);
127-
12856
useEffect(() => {
12957
setQualityThresholds(readCurrentThresholds());
13058
return subscribeToQualityThresholds(setQualityThresholds);
@@ -339,19 +267,19 @@ export function AnalyticsTab({
339267
<div className="kb-gap-actions">
340268
<button
341269
className="kb-gap-btn"
342-
onClick={() => handleGapStatus(gap.id, "accepted")}
270+
onClick={() => updateGapStatus(gap.id, "accepted")}
343271
>
344272
Accept
345273
</button>
346274
<button
347275
className="kb-gap-btn"
348-
onClick={() => handleGapStatus(gap.id, "resolved")}
276+
onClick={() => updateGapStatus(gap.id, "resolved")}
349277
>
350278
Resolve
351279
</button>
352280
<button
353281
className="kb-gap-btn kb-gap-btn-muted"
354-
onClick={() => handleGapStatus(gap.id, "ignored")}
282+
onClick={() => updateGapStatus(gap.id, "ignored")}
355283
>
356284
Ignore
357285
</button>
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { useCallback, useEffect, useState } from "react";
2+
import {
3+
useAnalytics,
4+
type AnalyticsSummary,
5+
type ArticleUsage,
6+
type LowRatingAnalysis,
7+
type ResponseQualityDrilldownExamples,
8+
type ResponseQualitySummary,
9+
} from "../../hooks/useAnalytics";
10+
import { useInsightsOps } from "../../hooks/useInsightsOps";
11+
import type { KbGapCandidate } from "../../types/insights";
12+
13+
export type AnalyticsPeriod = 7 | 30 | 90 | null;
14+
15+
export interface UseAnalyticsLoaderResult {
16+
summary: AnalyticsSummary | null;
17+
kbUsage: ArticleUsage[];
18+
qualitySummary: ResponseQualitySummary | null;
19+
qualityDrilldown: ResponseQualityDrilldownExamples | null;
20+
lowRatingData: LowRatingAnalysis | null;
21+
gapCandidates: KbGapCandidate[];
22+
loading: boolean;
23+
error: string | null;
24+
period: AnalyticsPeriod;
25+
setPeriod: (p: AnalyticsPeriod) => void;
26+
reload: () => Promise<void>;
27+
updateGapStatus: (
28+
id: string,
29+
status: "accepted" | "resolved" | "ignored",
30+
) => Promise<void>;
31+
}
32+
33+
export function useAnalyticsLoader(): UseAnalyticsLoaderResult {
34+
const {
35+
getSummary,
36+
getKbUsage,
37+
getLowRatingAnalysis,
38+
getResponseQualitySummary,
39+
getResponseQualityDrilldownExamples,
40+
} = useAnalytics();
41+
const { getKbGapCandidates, updateKbGapStatus } = useInsightsOps();
42+
43+
const [period, setPeriod] = useState<AnalyticsPeriod>(30);
44+
const [summary, setSummary] = useState<AnalyticsSummary | null>(null);
45+
const [qualitySummary, setQualitySummary] =
46+
useState<ResponseQualitySummary | null>(null);
47+
const [qualityDrilldown, setQualityDrilldown] =
48+
useState<ResponseQualityDrilldownExamples | null>(null);
49+
const [kbUsage, setKbUsage] = useState<ArticleUsage[]>([]);
50+
const [lowRatingData, setLowRatingData] = useState<LowRatingAnalysis | null>(
51+
null,
52+
);
53+
const [gapCandidates, setGapCandidates] = useState<KbGapCandidate[]>([]);
54+
const [loading, setLoading] = useState(true);
55+
const [error, setError] = useState<string | null>(null);
56+
57+
const reload = useCallback(async () => {
58+
setLoading(true);
59+
setError(null);
60+
try {
61+
const [
62+
summaryData,
63+
kbData,
64+
lowRating,
65+
qualityData,
66+
qualityDrilldownData,
67+
] = await Promise.all([
68+
getSummary(period ?? undefined),
69+
getKbUsage(period ?? undefined),
70+
getLowRatingAnalysis(period ?? undefined).catch(() => null),
71+
getResponseQualitySummary(period ?? undefined).catch(() => null),
72+
getResponseQualityDrilldownExamples(period ?? undefined, 6).catch(
73+
() => null,
74+
),
75+
]);
76+
const gaps = await getKbGapCandidates(12, "open").catch(() => []);
77+
setSummary(summaryData);
78+
setQualitySummary(qualityData);
79+
setQualityDrilldown(qualityDrilldownData);
80+
setKbUsage(kbData);
81+
setLowRatingData(lowRating);
82+
setGapCandidates(gaps);
83+
} catch (err) {
84+
console.error("Failed to load analytics:", err);
85+
setError(typeof err === "string" ? err : "Failed to load analytics data");
86+
} finally {
87+
setLoading(false);
88+
}
89+
}, [
90+
period,
91+
getSummary,
92+
getKbUsage,
93+
getLowRatingAnalysis,
94+
getResponseQualitySummary,
95+
getResponseQualityDrilldownExamples,
96+
getKbGapCandidates,
97+
]);
98+
99+
useEffect(() => {
100+
reload();
101+
}, [reload]);
102+
103+
const updateGapStatus = useCallback(
104+
async (id: string, status: "accepted" | "resolved" | "ignored") => {
105+
await updateKbGapStatus(id, status);
106+
setGapCandidates((prev) => prev.filter((g) => g.id !== id));
107+
},
108+
[updateKbGapStatus],
109+
);
110+
111+
return {
112+
summary,
113+
kbUsage,
114+
qualitySummary,
115+
qualityDrilldown,
116+
lowRatingData,
117+
gapCandidates,
118+
loading,
119+
error,
120+
period,
121+
setPeriod,
122+
reload,
123+
updateGapStatus,
124+
};
125+
}

0 commit comments

Comments
 (0)