From 9504edc32710df20d7689be2907a5feb75ec0e98 Mon Sep 17 00:00:00 2001 From: Developer Date: Tue, 28 Jul 2026 09:51:53 +0700 Subject: [PATCH] =?UTF-8?q?feat:=20add=20dashboard=20charts=20=E2=80=94=20?= =?UTF-8?q?message=20trend,=20activity=20heatmap,=20top=20channels?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/dashboard/activity-heatmap.tsx | 62 +++++++++++++++++++ .../dashboard/message-trend-chart.tsx | 48 ++++++++++++++ .../dashboard/top-channels-chart.tsx | 36 +++++++++++ 3 files changed, 146 insertions(+) create mode 100644 services/frontend/src/components/dashboard/activity-heatmap.tsx create mode 100644 services/frontend/src/components/dashboard/message-trend-chart.tsx create mode 100644 services/frontend/src/components/dashboard/top-channels-chart.tsx diff --git a/services/frontend/src/components/dashboard/activity-heatmap.tsx b/services/frontend/src/components/dashboard/activity-heatmap.tsx new file mode 100644 index 0000000..bb25768 --- /dev/null +++ b/services/frontend/src/components/dashboard/activity-heatmap.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { GlassCard } from "@/components/glass/card"; +import { cn } from "@/lib/utils"; + +const HOURS = Array.from({ length: 24 }, (_, i) => i); +const DAYS = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + +interface ActivityHeatmapProps { + data?: Record; // key: "day-hour", value: count +} + +export function ActivityHeatmap({ data = {} }: ActivityHeatmapProps) { + const maxVal = Math.max(...Object.values(data), 1); + + const getIntensity = (day: string, hour: number) => { + const val = data[`${day}-${hour}`] || 0; + const pct = val / maxVal; + if (pct === 0) return "bg-surface"; + if (pct < 0.25) return "bg-primary/15"; + if (pct < 0.5) return "bg-primary/30"; + if (pct < 0.75) return "bg-primary/50"; + return "bg-primary/70"; + }; + + return ( + +
+ Activity + hour × day +
+
+
+ {/* Hour labels */} +
+
+ {DAYS.map((d) => ( +
{d}
+ ))} +
+ {/* Grid */} +
+ {HOURS.map((hour) => ( +
+ {DAYS.map((day) => ( +
+ ))} +
+ {hour % 4 === 0 ? hour : ""} +
+
+ ))} +
+
+
+ + ); +} diff --git a/services/frontend/src/components/dashboard/message-trend-chart.tsx b/services/frontend/src/components/dashboard/message-trend-chart.tsx new file mode 100644 index 0000000..8574ed6 --- /dev/null +++ b/services/frontend/src/components/dashboard/message-trend-chart.tsx @@ -0,0 +1,48 @@ +"use client"; + +import { GlassCard } from "@/components/glass/card"; +import { Area, AreaChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; + +interface MessageTrendChartProps { + data?: { date: string; messages: number; flagged: number }[]; +} + +export function MessageTrendChart({ data = [] }: MessageTrendChartProps) { + return ( + +
+ Message Trend + 7 days +
+
+ + + + + + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/services/frontend/src/components/dashboard/top-channels-chart.tsx b/services/frontend/src/components/dashboard/top-channels-chart.tsx new file mode 100644 index 0000000..97ea18f --- /dev/null +++ b/services/frontend/src/components/dashboard/top-channels-chart.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { GlassCard } from "@/components/glass/card"; +import { Bar, BarChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from "recharts"; + +interface TopChannelsChartProps { + data?: { name: string; count: number }[]; +} + +export function TopChannelsChart({ data = [] }: TopChannelsChartProps) { + return ( + +
+ Top Channels +
+
+ + + + + + + + +
+
+ ); +}