"use client"; import { AlertCircle, ArrowLeft, BarChart3, ChevronRight, Hash, RefreshCw, Search, Shield, Users, } from "lucide-react"; import Image from "next/image"; import { useCallback, useEffect, useState } from "react"; import { dashboardApi } from "@/lib/api"; import type { DashboardChannel, DashboardChannelDetail, DashboardStats, DashboardUser, DashboardUserDetail, } from "@/lib/types"; type View = "stats" | "users" | "channels" | "user-detail" | "channel-detail"; export function DashboardPanel({ guildId }: { guildId: string }) { const [view, setView] = useState("stats"); const [activeUser, setActiveUser] = useState( null, ); const [activeChannel, setActiveChannel] = useState(null); const renderView = () => { switch (view) { case "stats": return setView(v)} />; case "users": return ( { try { const detail = await dashboardApi.getUserDetail(userId); setActiveUser(detail); setView("user-detail"); } catch { // ignore } }} /> ); case "channels": return ( { try { const detail = await dashboardApi.getChannelDetail(channelId); setActiveChannel(detail); setView("channel-detail"); } catch { // ignore } }} /> ); case "user-detail": return activeUser ? ( setView("users")} /> ) : ( {}} /> ); case "channel-detail": return activeChannel ? ( setView("channels")} /> ) : ( {}} /> ); } }; return (
{/* Sub-navigation */}
{renderView()}
); } // ── Stats View ──────────────────────────────────────────── function StatsView(_props: { onNavigate: (view: View) => void }) { const [stats, setStats] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const fetchStats = useCallback(async () => { setLoading(true); setError(null); try { const data = await dashboardApi.getStats(); setStats(data); } catch (err) { setError(err instanceof Error ? err.message : "Failed to load stats"); } finally { setLoading(false); } }, []); useEffect(() => { fetchStats(); }, [fetchStats]); if (error) { return (

{error}

); } return (
{/* Metric cards */} {loading ? (
{Array.from({ length: 8 }, (_, i) => `stat-sk-${i}`).map((key) => (
))}
) : stats ? ( <>
{/* Top Channels + Moderation Queue */}

Top Channels

{stats.top_channels.length === 0 ? (

No channel data yet.

) : (
{stats.top_channels.map((ch) => (
#{ch.channel_name ?? ch.channel_id.slice(0, 8)} {formatNumber(ch.message_count)}
))}
)}

Moderation Queue

{stats.moderation_overview.pending}
Pending
{stats.moderation_overview.processing}
Processing
{stats.moderation_overview.error}
Errors
) : null}
); } function MetricCard({ label, value, variant, }: { label: string; value: number; variant?: "default" | "destructive" | "success"; }) { const colorMap = { default: "", destructive: "text-destructive", success: "text-green-600 dark:text-green-400", }; return (

{label}

{formatNumber(value)}

); } // ── Users View ──────────────────────────────────────────── function UsersView({ onSelectUser, }: { onSelectUser: (userId: string) => void; }) { const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [_cursor, setCursor] = useState(null); const [search, setSearch] = useState(""); const fetchUsers = useCallback(async (searchQuery?: string) => { setLoading(true); try { const result = await dashboardApi.listUsers(20, undefined, searchQuery); setUsers(result.data); setCursor(result.nextCursor); } catch { // ignore } finally { setLoading(false); } }, []); useEffect(() => { fetchUsers(); }, [fetchUsers]); useEffect(() => { const timer = setTimeout(() => { if (search) fetchUsers(search); else fetchUsers(); }, 300); return () => clearTimeout(timer); }, [search, fetchUsers]); return (
setSearch(e.target.value)} className="w-full h-9 rounded-lg border border-input bg-background pl-9 pr-3 text-sm" />
{loading ? (
{Array.from({ length: 6 }, (_, i) => `user-sk-${i}`).map((key) => (
))}
) : (
{users.map((user) => ( ))}
)}
); } // ── Channels View ───────────────────────────────────────── function ChannelsView({ onSelectChannel, guildId, }: { onSelectChannel: (channelId: string) => void; guildId: string; }) { const [channels, setChannels] = useState([]); const [loading, setLoading] = useState(true); const [search, setSearch] = useState(""); const fetchChannels = useCallback( async (searchQuery?: string) => { setLoading(true); try { const result = await dashboardApi.listChannels( 20, searchQuery, guildId || undefined, ); setChannels(result.data); } catch { // ignore } finally { setLoading(false); } }, [guildId], ); useEffect(() => { fetchChannels(); }, [fetchChannels]); useEffect(() => { const timer = setTimeout(() => { if (search) fetchChannels(search); else fetchChannels(); }, 300); return () => clearTimeout(timer); }, [search, fetchChannels]); return (
setSearch(e.target.value)} className="w-full h-9 rounded-lg border border-input bg-background pl-9 pr-3 text-sm" />
{loading ? (
{Array.from({ length: 6 }, (_, i) => `ch-sk-${i}`).map((key) => (
))}
) : (
{channels.map((ch) => ( ))}
)}
); } // ── User Detail View ────────────────────────────────────── function UserDetailView({ user, onBack, }: { user: DashboardUserDetail; onBack: () => void; }) { return (
{user.avatar_url ? ( ) : ( (user.username ?? "?").charAt(0).toUpperCase() )}

{user.username ?? "Unknown"}

#{user.user_id}

{user.profile_summary && (

AI Profile

{user.profile_summary}

)} {/* Recent messages */} {user.recent_messages.length > 0 && (

Recent Messages

{user.recent_messages.slice(0, 5).map((msg) => (

{new Date(msg.created_at).toLocaleString()}

{msg.content}

))}
)}
); } // ── Channel Detail View ─────────────────────────────────── function ChannelDetailView({ channel, onBack, }: { channel: DashboardChannelDetail; onBack: () => void; }) { return (

#{channel.channel_name ?? channel.channel_id.slice(0, 8)}

{channel.channel_id}

{channel.culture_summary && (

Channel Culture

{channel.culture_summary}

)} {channel.recent_messages.length > 0 && (

Recent Messages

{channel.recent_messages.slice(0, 5).map((msg) => (
{msg.username} {new Date(msg.created_at).toLocaleString()}

{msg.content}

))}
)}
); } // ── Shared Components ───────────────────────────────────── function DetailStat({ label, value, variant, suffix, }: { label: string; value: number; variant?: "default" | "destructive" | "success"; suffix?: string; }) { const colorMap = { default: "", destructive: "text-destructive", success: "text-green-600 dark:text-green-400", }; return (

{label}

{formatNumber(value)} {suffix}

); } // ── Helpers ─────────────────────────────────────────────── function formatNumber(n: number): string { return n.toLocaleString(); }