diff --git a/apps/web/app/(app)/[emailAccountId]/stats/BarListCard.tsx b/apps/web/app/(app)/[emailAccountId]/stats/BarListCard.tsx index 274defb077..293ad4b925 100644 --- a/apps/web/app/(app)/[emailAccountId]/stats/BarListCard.tsx +++ b/apps/web/app/(app)/[emailAccountId]/stats/BarListCard.tsx @@ -16,11 +16,19 @@ import { cn } from "@/utils"; interface BarListCardProps { tabs: { id: string; label: string; data: any }[]; // TODO: add type - icon: React.ReactNode; + icon?: React.ReactNode; title: string; + onItemClick?: (item: any) => void; + hideIcons?: boolean; } -export function BarListCard({ tabs, icon, title }: BarListCardProps) { +export function BarListCard({ + tabs, + icon, + title, + onItemClick, + hideIcons, +}: BarListCardProps) { const [selected, setSelected] = useState( tabs?.length > 0 ? tabs[0]?.id : null, ); @@ -35,7 +43,7 @@ export function BarListCard({ tabs, icon, title }: BarListCardProps) { selected={selected} />
- {icon} + {icon && icon}

{title.toUpperCase()}

@@ -49,6 +57,8 @@ export function BarListCard({ tabs, icon, title }: BarListCardProps) { /> d.id === selected)?.data || []} + onItemClick={onItemClick} + hideIcon={hideIcons} />
{tabs.find((d) => d.id === selected)?.data.length > 0 && ( @@ -71,6 +81,8 @@ export function BarListCard({ tabs, icon, title }: BarListCardProps) {
d.id === selected)?.data || []} + onItemClick={onItemClick} + hideIcon={hideIcons} />
diff --git a/apps/web/app/(app)/[emailAccountId]/stats/Stats.tsx b/apps/web/app/(app)/[emailAccountId]/stats/Stats.tsx index 886e589eb7..28a1947ed3 100644 --- a/apps/web/app/(app)/[emailAccountId]/stats/Stats.tsx +++ b/apps/web/app/(app)/[emailAccountId]/stats/Stats.tsx @@ -17,6 +17,7 @@ import { ActionBar } from "@/app/(app)/[emailAccountId]/stats/ActionBar"; import { DetailedStatsFilter } from "@/app/(app)/[emailAccountId]/stats/DetailedStatsFilter"; import { LayoutGrid } from "lucide-react"; import { DatePickerWithRange } from "@/components/DatePickerWithRange"; +import { TopicDistribution } from "@/app/(app)/[emailAccountId]/stats/TopicDistribution"; const selectOptions = [ { label: "Last week", value: "7" }, @@ -122,6 +123,9 @@ export function Stats() { dateRange={dateRange} refreshInterval={refreshInterval} /> +
+ +
= 0; i--) { + const date = subDays(now, i); + // Generate semi-realistic data with some variance + const baseCount = topicCount / 30; + const variance = Math.random() * baseCount * 0.8; + const count = Math.max( + 0, + Math.round(baseCount + variance - baseCount * 0.4), + ); + + data.push({ + date: format(date, "yyyy-MM-dd"), + count: count, + }); + } + + return data; +} + +export function TopicDistribution() { + const [selectedTopic, setSelectedTopic] = useState< + (typeof MOCK_TOPICS)[0] | null + >(null); + + const handleTopicClick = (item: { name: string; value: number }) => { + const topic = MOCK_TOPICS.find((t) => t.topic === item.name); + if (topic) { + setSelectedTopic(topic); + } + }; + + const tabs = [ + { + id: "common-topics", + label: "Common Topics", + data: MOCK_TOPICS.map((topic) => ({ + name: topic.topic, + value: topic.count, + icon: topic.emoji, + })), + }, + ]; + + return ( + <> + } + title="Topics" + onItemClick={handleTopicClick} + /> + + setSelectedTopic(null)} + > + + {selectedTopic && ( + <> + + {selectedTopic.topic} + +
+
+
+

Total (30 days)

+

+ {selectedTopic.count.toLocaleString()} +

+
+
+

Average per day

+

+ {Math.round(selectedTopic.count / 30).toLocaleString()} +

+
+
+ +
+ + )} +
+
+ + ); +} diff --git a/apps/web/components/charts/HorizontalBarChart.tsx b/apps/web/components/charts/HorizontalBarChart.tsx index bf13ce8a55..8d327fa161 100644 --- a/apps/web/components/charts/HorizontalBarChart.tsx +++ b/apps/web/components/charts/HorizontalBarChart.tsx @@ -3,19 +3,26 @@ import { DomainIcon } from "@/components/charts/DomainIcon"; import { cn } from "@/utils"; +export interface HorizontalBarChartItem { + name: string; + value: number; + href?: string; + target?: string; + icon?: string; +} + interface HorizontalBarChartProps { - data: Array<{ - name: string; - value: number; - href?: string; - target?: string; - }>; + data: Array; className?: string; + onItemClick?: (item: HorizontalBarChartItem) => void; + hideIcon?: boolean; } export function HorizontalBarChart({ data, className, + onItemClick, + hideIcon, }: HorizontalBarChartProps) { const maxValue = Math.max(...data.map((item) => item.value), 1); @@ -27,26 +34,39 @@ export function HorizontalBarChart({ ? item.name.split("@")[1] : item.name; - return ( -
+ const content = ( + <>
-
- - - {item.name} - +
+ {!hideIcon && + (item.icon ? ( + {item.icon} + ) : ( + + ))} + {item.href ? ( + + {item.name} + + ) : ( + + {item.name} + + )}
@@ -55,6 +75,28 @@ export function HorizontalBarChart({ {item.value.toLocaleString()}
+ + ); + + if (onItemClick) { + return ( + + ); + } + + return ( +
+ {content}
); })}