refactor(fe): align dashboard with backend & gateway data flow
- API/WS clients: same-origin by default, drop dead imphnen hardcode
- chatbot history: map BE rows {user_message,bot_response,created_at}
- message_deleted WS payload: object {id,deleted_at}, not bare string
- dashboard: wire top-channels chart + live mod queue from /api/review,
add Users & Channels tabs consuming /api/dashboard/users|channels
- recordings: live WS sync via voice_recording_uploaded; duration_bytes optional
- remove dead widgets with no BE data source (trend chart, heatmap)
This commit is contained in:
@@ -1,32 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { AlertCircle, Clock, Hash, Shield, Sparkles, Users } from "lucide-react";
|
||||
import {
|
||||
AlertCircle,
|
||||
Clock,
|
||||
Hash,
|
||||
Shield,
|
||||
Sparkles,
|
||||
Users,
|
||||
} from "lucide-react";
|
||||
import { useState } from "react";
|
||||
import { useStats } from "@/hooks";
|
||||
import { StatCard } from "@/components/dashboard/stat-card";
|
||||
import { ChannelsSection } from "@/components/dashboard/channels-section";
|
||||
import { LiveStream } from "@/components/dashboard/live-stream";
|
||||
import type { ModQueueItem } from "@/components/dashboard/mod-queue";
|
||||
import { ModQueue } from "@/components/dashboard/mod-queue";
|
||||
import { MessageTrendChart } from "@/components/dashboard/message-trend-chart";
|
||||
import { ActivityHeatmap } from "@/components/dashboard/activity-heatmap";
|
||||
import { StatCard } from "@/components/dashboard/stat-card";
|
||||
import { TopChannelsChart } from "@/components/dashboard/top-channels-chart";
|
||||
import { UsersSection } from "@/components/dashboard/users-section";
|
||||
import { SubNav } from "@/components/layout/sub-nav";
|
||||
import { ErrorState, LoadingSkeleton } from "@/components/shared";
|
||||
import { useReview, useStats } from "@/hooks";
|
||||
|
||||
type DashboardTab = "stats" | "live" | "activity";
|
||||
type DashboardTab = "stats" | "live" | "users" | "channels";
|
||||
|
||||
export default function DashboardPage() {
|
||||
const [tab, setTab] = useState<DashboardTab>("stats");
|
||||
const { data: stats, isLoading, error, refetch } = useStats();
|
||||
const { data: review = [] } = useReview();
|
||||
|
||||
const modQueueItems: ModQueueItem[] = review.slice(0, 10).map((msg) => ({
|
||||
id: msg.id,
|
||||
content: msg.content || msg.id,
|
||||
username: msg.username,
|
||||
severity:
|
||||
msg.ai_severity && msg.ai_severity !== "none"
|
||||
? (msg.ai_severity as ModQueueItem["severity"])
|
||||
: "medium",
|
||||
reason: msg.ai_analysis ?? "AI moderation flag",
|
||||
}));
|
||||
|
||||
const subNavTabs = [
|
||||
{ id: "stats", label: "Stats", icon: <Hash className="size-3" /> },
|
||||
{ id: "live", label: "Live", icon: <Sparkles className="size-3" /> },
|
||||
{ id: "activity", label: "Activity", icon: <Clock className="size-3" /> },
|
||||
{ id: "users", label: "Users", icon: <Users className="size-3" /> },
|
||||
{ id: "channels", label: "Channels", icon: <Hash className="size-3" /> },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="space-y-4 animate-fade-in-up">
|
||||
<SubNav tabs={subNavTabs} activeTab={tab} onTabChange={(t) => setTab(t as DashboardTab)} />
|
||||
<SubNav
|
||||
tabs={subNavTabs}
|
||||
activeTab={tab}
|
||||
onTabChange={(t) => setTab(t as DashboardTab)}
|
||||
/>
|
||||
|
||||
{tab === "stats" && (
|
||||
<div className="space-y-4">
|
||||
@@ -37,18 +62,46 @@ export default function DashboardPage() {
|
||||
) : (
|
||||
<>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
|
||||
<StatCard label="Total Messages" value={stats.total_messages} icon={Hash} />
|
||||
<StatCard label="Today" value={stats.today_messages} icon={Clock} />
|
||||
<StatCard label="Users" value={stats.total_users} icon={Users} />
|
||||
<StatCard label="Active 24h" value={stats.active_users_24h} icon={Sparkles} />
|
||||
<StatCard label="Flagged" value={stats.total_flagged} icon={AlertCircle} variant="danger" />
|
||||
<StatCard label="Clean" value={stats.total_clean} icon={Shield} variant="success" />
|
||||
<StatCard
|
||||
label="Total Messages"
|
||||
value={stats.total_messages}
|
||||
icon={Hash}
|
||||
/>
|
||||
<StatCard
|
||||
label="Today"
|
||||
value={stats.today_messages}
|
||||
icon={Clock}
|
||||
/>
|
||||
<StatCard
|
||||
label="Users"
|
||||
value={stats.total_users}
|
||||
icon={Users}
|
||||
/>
|
||||
<StatCard
|
||||
label="Active 24h"
|
||||
value={stats.active_users_24h}
|
||||
icon={Sparkles}
|
||||
/>
|
||||
<StatCard
|
||||
label="Flagged"
|
||||
value={stats.total_flagged}
|
||||
icon={AlertCircle}
|
||||
variant="danger"
|
||||
/>
|
||||
<StatCard
|
||||
label="Clean"
|
||||
value={stats.total_clean}
|
||||
icon={Shield}
|
||||
variant="success"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<MessageTrendChart />
|
||||
<TopChannelsChart />
|
||||
</div>
|
||||
<TopChannelsChart
|
||||
data={stats.top_channels.map((c) => ({
|
||||
name: c.channel_name ?? c.channel_id,
|
||||
count: c.message_count,
|
||||
}))}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -57,15 +110,13 @@ export default function DashboardPage() {
|
||||
{tab === "live" && (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<LiveStream />
|
||||
<ModQueue />
|
||||
<ModQueue items={modQueueItems} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{tab === "activity" && (
|
||||
<div className="grid grid-cols-1 gap-4">
|
||||
<ActivityHeatmap />
|
||||
</div>
|
||||
)}
|
||||
{tab === "users" && <UsersSection />}
|
||||
|
||||
{tab === "channels" && <ChannelsSection />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user