import { Badge, Card, Center, Group, Progress, SimpleGrid, Stack, Text, ThemeIcon, Title, } from "@mantine/core"; import { IconSearch } from "@tabler/icons-react"; import { useMemo } from "react"; import { useSearchHistory } from "../../hooks/useSearchHistory"; import type { SearchEntry } from "../../modules/history"; import { formatRelativeTime } from "../../modules/stringFormatters"; interface SearchStatsProps { period?: "today" | "week" | "month" | "all"; compact?: boolean; } interface StatsData { totalSearches: number; avgPerDay: number; mostActiveHour: number; topSources: { source: string; count: number; percentage: number }[]; recentActivity: SearchEntry[]; searchTrends: { date: string; count: number }[]; } export default function SearchStats({ period = "week", compact = false, }: SearchStatsProps) { const { recentSearches, isLoading } = useSearchHistory({ limit: 1000 }); const stats = useMemo((): StatsData => { if (!recentSearches.length) { return { totalSearches: 0, avgPerDay: 0, mostActiveHour: 0, topSources: [], recentActivity: [], searchTrends: [], }; } const now = new Date(); const filterDate = new Date(); switch (period) { case "today": filterDate.setHours(0, 0, 0, 0); break; case "week": filterDate.setDate(now.getDate() - 7); break; case "month": filterDate.setDate(now.getDate() - 30); break; default: filterDate.setFullYear(2000); } const filteredSearches = recentSearches.filter( (search) => search.timestamp >= filterDate.getTime(), ); const totalSearches = filteredSearches.length; const daysDiff = Math.max( 1, Math.ceil((now.getTime() - filterDate.getTime()) / (1000 * 60 * 60 * 24)), ); const avgPerDay = Math.round(totalSearches / daysDiff); const hourCounts = new Array(24).fill(0); filteredSearches.forEach((search) => { const hour = new Date(search.timestamp).getHours(); hourCounts[hour]++; }); const mostActiveHour = hourCounts.indexOf(Math.max(...hourCounts)); const sourceCounts = filteredSearches.reduce( (acc, search) => { if (compact && search.source.toLowerCase() === "user") { return acc; } acc[search.source] = (acc[search.source] || 0) + 1; return acc; }, {} as Record, ); const sourcesTotal = Object.values(sourceCounts).reduce( (sum, n) => sum + n, 0, ); const topSources = Object.entries(sourceCounts) .map(([source, count]) => ({ source: source.charAt(0).toUpperCase() + source.slice(1), count, percentage: sourcesTotal > 0 ? Math.round((count / sourcesTotal) * 100) : 0, })) .sort((a, b) => b.count - a.count); const recentActivity = filteredSearches .sort((a, b) => b.timestamp - a.timestamp) .slice(0, 10); const trends = new Map(); filteredSearches.forEach((search) => { const date = new Date(search.timestamp).toISOString().split("T")[0]; trends.set(date, (trends.get(date) || 0) + 1); }); const searchTrends = Array.from(trends.entries()) .map(([date, count]) => ({ date, count })) .sort((a, b) => a.date.localeCompare(b.date)); return { totalSearches, avgPerDay, mostActiveHour, topSources, recentActivity, searchTrends, }; }, [recentSearches, period, compact]); const getSourceColor = (source: string) => { const colors = { User: "blue", Followup: "green", Suggestion: "orange", }; return colors[source as keyof typeof colors] || "gray"; }; const formatHour = (hour: number) => { const period = hour >= 12 ? "PM" : "AM"; const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour; return `${displayHour}:00 ${period}`; }; if (isLoading) { return (
Loading analytics...
); } if (stats.totalSearches === 0) { return (
No search data available Start searching to see analytics
); } function MetricCard({ title, value, }: { title: string; value: string | number; }) { return ( {title} {value} ); } return ( 0 ? formatRelativeTime(stats.recentActivity[0].timestamp) : "Never" } /> {!(compact && stats.topSources.length === 0) && ( Search Sources {stats.topSources.length > 0 ? ( {stats.topSources.map((source) => ( {source.source} {source.count} searches {source.percentage}% ))} ) : ( No data available )} )} {stats.searchTrends.length > 1 && ( Search Trends Daily search activity over the selected period {stats.searchTrends.slice(-7).map((trend) => { const maxCount = Math.max( ...stats.searchTrends.map((t) => t.count), ); const percentage = maxCount > 0 ? (trend.count / maxCount) * 100 : 0; return ( {new Date(trend.date).toLocaleDateString("en", { month: "short", day: "numeric", })} {trend.count} ); })} )} ); }