From dd917cc39ec48fcfc43d499004b89c4d6a6c5d9f Mon Sep 17 00:00:00 2001 From: Dylan De Faoite Date: Sun, 8 Feb 2026 16:43:35 +0000 Subject: [PATCH] refactor: extract summary stats into separate component --- frontend/src/components/SummaryStats.tsx | 215 +++++++++++++++++++++++ frontend/src/pages/Stats.tsx | 203 +-------------------- 2 files changed, 223 insertions(+), 195 deletions(-) create mode 100644 frontend/src/components/SummaryStats.tsx diff --git a/frontend/src/components/SummaryStats.tsx b/frontend/src/components/SummaryStats.tsx new file mode 100644 index 0000000..3ca414a --- /dev/null +++ b/frontend/src/components/SummaryStats.tsx @@ -0,0 +1,215 @@ +import { useState } from "react"; +import { + LineChart, + Line, + XAxis, + YAxis, + Tooltip, + CartesianGrid, + ResponsiveContainer +} from "recharts"; + +import ActivityHeatmap from "../stats/ActivityHeatmap"; +import { ReactWordcloud } from '@cp949/react-wordcloud'; +import StatsStyling from "../styles/stats_styling"; +import Card from "../components/Card"; +import UserModal from "../components/UserModal"; + +import { + type SummaryResponse, + type FrequencyWord, + type UserAnalysisResponse, + type TimeAnalysisResponse, + type ContentAnalysisResponse, + type User +} from '../types/ApiTypes' + +const styles = StatsStyling; + +type SummaryStatsProps = { + userData: UserAnalysisResponse | null; + timeData: TimeAnalysisResponse | null; + contentData: ContentAnalysisResponse | null; + summary: SummaryResponse | null; +} + +function formatDateRange(startUnix: number, endUnix: number) { + const start = new Date(startUnix * 1000); + const end = new Date(endUnix * 1000); + + const fmt = (d: Date) => + d.toLocaleDateString(undefined, { + year: "numeric", + month: "short", + day: "2-digit", + }); + + return `${fmt(start)} → ${fmt(end)}`; +} + +function convertFrequencyData(data: FrequencyWord[]) { + return data.map((d: FrequencyWord) => ({ + text: d.word, + value: d.count, + })) +} + +const SummaryStats = ({userData, timeData, contentData, summary}: SummaryStatsProps) => { + const [selectedUser, setSelectedUser] = useState(null); + const selectedUserData: User | null = userData?.users.find((u) => u.author === selectedUser) ?? null; + + return ( +
+ + {/* main grid*/} +
+ + + + + + + + + 3 ? "…" : "") + : "—" + } + style={{ + gridColumn: "span 4" + }} + /> + + {/* events per day */} +
+

Events per Day

+

Trend of activity over time

+ +
+ + new Date(d.date) >= new Date('2026-01-10'))}> + + + + + + + +
+
+ + {/* Word Cloud */} +
+

Word Cloud

+

Most common terms across events

+ +
+ +
+
+ + {/* Top Users */} +
+

Top Users

+

Most active authors

+ +
+ {userData?.top_users.slice(0, 100).map((item) => ( +
setSelectedUser(item.author)} + > +
{item.author}
+
+ {item.source} • {item.count} events +
+
+ ))} +
+
+ + {/* Heatmap */} +
+

Heatmap

+

Activity density across time

+ +
+ +
+
+
+ + setSelectedUser(null)} + username={selectedUser ?? ""} + userData={selectedUserData} + /> +
+ ); +} + +export default SummaryStats; \ No newline at end of file diff --git a/frontend/src/pages/Stats.tsx b/frontend/src/pages/Stats.tsx index dcfd68a..d5943dc 100644 --- a/frontend/src/pages/Stats.tsx +++ b/frontend/src/pages/Stats.tsx @@ -1,55 +1,17 @@ import { useEffect, useState, useRef } from "react"; import axios from "axios"; -import { - LineChart, - Line, - XAxis, - YAxis, - Tooltip, - CartesianGrid, - ResponsiveContainer -} from "recharts"; - -import ActivityHeatmap from "../stats/ActivityHeatmap"; -import { ReactWordcloud } from '@cp949/react-wordcloud'; import StatsStyling from "../styles/stats_styling"; -import Card from "../components/Card"; -import UserModal from "../components/UserModal"; +import SummaryStats from "../components/SummaryStats"; import { - type TopUser, type SummaryResponse, - type FrequencyWord, type UserAnalysisResponse, type TimeAnalysisResponse, - type ContentAnalysisResponse, - type FilterResponse, - type User + type ContentAnalysisResponse } from '../types/ApiTypes' const styles = StatsStyling; -function formatDateRange(startUnix: number, endUnix: number) { - const start = new Date(startUnix * 1000); - const end = new Date(endUnix * 1000); - - const fmt = (d: Date) => - d.toLocaleDateString(undefined, { - year: "numeric", - month: "short", - day: "2-digit", - }); - - return `${fmt(start)} → ${fmt(end)}`; -} - -function convertFrequencyData(data: FrequencyWord[]) { - return data.map((d: FrequencyWord) => ({ - text: d.word, - value: d.count, - })) -} - const StatPage = () => { const [error, setError] = useState(''); const [loading, setLoading] = useState(false); @@ -59,9 +21,6 @@ const StatPage = () => { const [contentData, setContentData] = useState(null); const [summary, setSummary] = useState(null); - const [selectedUser, setSelectedUser] = useState(null); - const selectedUserData: User | null = userData?.users.find((u) => u.author === selectedUser) ?? null; - const searchInputRef = useRef(null); const beforeDateRef = useRef(null); @@ -89,17 +48,11 @@ const StatPage = () => { const onSubmitFilters = () => { const query = searchInputRef.current?.value ?? ""; - const startDate = beforeDateRef.current?.value; - const endDate = afterDateRef.current?.value; Promise.all([ axios.post("http://localhost:5000/filter/search", { query: query }), - //axios.post("http://localhost:5000/filter/time", { - //start: startDate, - //end: endDate - //}) ]) .then(() => { getStats(); @@ -165,153 +118,13 @@ return (
Analytics Dashboard
- {/* main grid*/} -
- - - - - - - - - 3 ? "…" : "") - : "—" - } - style={{ - gridColumn: "span 4" - }} - /> - - {/* events per day */} -
-

Events per Day

-

Trend of activity over time

- -
- - new Date(d.date) >= new Date('2026-01-10'))}> - - - - - - - -
-
- - {/* Word Cloud */} -
-

Word Cloud

-

Most common terms across events

- -
- -
-
- - {/* Top Users */} -
-

Top Users

-

Most active authors

- -
- {userData?.top_users.slice(0, 100).map((item) => ( -
setSelectedUser(item.author)} - > -
{item.author}
-
- {item.source} • {item.count} events -
-
- ))} -
-
- - {/* Heatmap */} -
-

Heatmap

-

Activity density across time

- -
- -
-
-
- - setSelectedUser(null)} - username={selectedUser ?? ""} - userData={selectedUserData} + + ); }