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:
Developer
2026-07-31 16:54:41 +07:00
parent 2addfb6492
commit 69213ebd75
17 changed files with 611 additions and 224 deletions
@@ -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>
);
}