diff --git a/services/frontend/src/app/(dashboard)/dashboard/view.tsx b/services/frontend/src/app/(dashboard)/dashboard/view.tsx index 7e98c8b7..fe3f4a82 100644 --- a/services/frontend/src/app/(dashboard)/dashboard/view.tsx +++ b/services/frontend/src/app/(dashboard)/dashboard/view.tsx @@ -258,6 +258,54 @@ export function DashboardView({ + {/* Hourly Flow — last-24h message volume by hour */} + {activity?.hourly && activity.hourly.length > 0 && ( + + + {activity.hourly.reduce((a, h) => a + h.messages, 0)} MSGS ·{" "} + {activity.hourly.reduce((a, h) => a + h.flagged, 0)} FLAGGED + + } + /> +
+ {activity.hourly.map((h) => { + const max = Math.max( + ...activity.hourly.map((x) => x.messages), + 1, + ); + const pct = Math.max(4, (h.messages / max) * 100); + return ( +
+
+
+ {String(h.hour).padStart(2, "0")} +
+
+ ); + })} +
+
+ 00:00 + 06:00 + 12:00 + 18:00 + 23:00 +
+ + )} + {/* Top Channels + Engagement Row */}
{/* Top Channels Ranking */} diff --git a/services/frontend/src/app/(dashboard)/recordings/view.tsx b/services/frontend/src/app/(dashboard)/recordings/view.tsx index 7cf7c7cd..85d4a5b1 100644 --- a/services/frontend/src/app/(dashboard)/recordings/view.tsx +++ b/services/frontend/src/app/(dashboard)/recordings/view.tsx @@ -799,6 +799,9 @@ function Leaderboard({
{s.transcribed > 0 ? `${s.words} words` : "no transcript"}
+
+ {s.last_at ? `active ${formatRelativeTime(s.last_at)}` : "—"} +
))} diff --git a/services/frontend/src/app/(dashboard)/voice/view.tsx b/services/frontend/src/app/(dashboard)/voice/view.tsx index 12319033..a7889b2c 100644 --- a/services/frontend/src/app/(dashboard)/voice/view.tsx +++ b/services/frontend/src/app/(dashboard)/voice/view.tsx @@ -1,6 +1,7 @@ "use client"; import { + Cable, Mic, PhoneOff, Radio, @@ -231,6 +232,99 @@ export function VoiceView({
+ {/* Connected Bridges — multi-guild voice bridge roster */} + + + {status?.connections?.length ?? (connected ? 1 : 0)} ACTIVE + + } + /> +
+ {status?.connections && status.connections.length > 0 ? ( +
+ {status.connections.map((conn) => { + const isActive = + conn.guildId === status.activeGuildId && + conn.channelId === status.activeChannelId; + return ( +
+ + + +
+
+ {conn.channelName || "#" + conn.channelId.slice(0, 8)} +
+
+ + G:{conn.guildId.slice(0, 8)} · C: + {conn.channelId.slice(0, 8)} + + {isActive && ( + + ACTIVE + + )} +
+
+ + {new Date(conn.connectedAt).toLocaleTimeString([], { + hour: "2-digit", + minute: "2-digit", + })} + +
+ ); + })} +
+ ) : connected ? ( +
+ + + +
+
+ {status?.activeChannelName || "Voice channel"} +
+
+ Active bridge · G: + {(status?.activeGuildId ?? guildId ?? "").slice(0, 8)} +
+
+ + ACTIVE + +
+ ) : ( +
+ + No active voice bridges + +
+ )} +
+
+ {/* Voice Stage Grid */}