2026-07-26 11:27:21 +07:00
|
|
|
"use client";
|
|
|
|
|
|
2026-07-31 16:54:41 +07:00
|
|
|
import {
|
|
|
|
|
AlertCircle,
|
|
|
|
|
Clock,
|
|
|
|
|
Hash,
|
|
|
|
|
Shield,
|
|
|
|
|
Sparkles,
|
|
|
|
|
Users,
|
|
|
|
|
} from "lucide-react";
|
2026-07-27 21:54:31 +07:00
|
|
|
import { useState } from "react";
|
2026-07-31 16:54:41 +07:00
|
|
|
import { ChannelsSection } from "@/components/dashboard/channels-section";
|
2026-07-28 10:05:08 +07:00
|
|
|
import { LiveStream } from "@/components/dashboard/live-stream";
|
2026-07-31 16:54:41 +07:00
|
|
|
import type { ModQueueItem } from "@/components/dashboard/mod-queue";
|
2026-07-28 10:05:08 +07:00
|
|
|
import { ModQueue } from "@/components/dashboard/mod-queue";
|
2026-07-31 16:54:41 +07:00
|
|
|
import { StatCard } from "@/components/dashboard/stat-card";
|
2026-07-28 10:05:08 +07:00
|
|
|
import { TopChannelsChart } from "@/components/dashboard/top-channels-chart";
|
2026-07-31 16:54:41 +07:00
|
|
|
import { UsersSection } from "@/components/dashboard/users-section";
|
2026-07-28 10:05:08 +07:00
|
|
|
import { SubNav } from "@/components/layout/sub-nav";
|
|
|
|
|
import { ErrorState, LoadingSkeleton } from "@/components/shared";
|
2026-07-31 16:54:41 +07:00
|
|
|
import { useReview, useStats } from "@/hooks";
|
2026-07-26 11:27:21 +07:00
|
|
|
|
2026-07-31 16:54:41 +07:00
|
|
|
type DashboardTab = "stats" | "live" | "users" | "channels";
|
2026-07-26 11:27:21 +07:00
|
|
|
|
2026-07-26 15:34:08 +07:00
|
|
|
export default function DashboardPage() {
|
2026-07-28 10:05:08 +07:00
|
|
|
const [tab, setTab] = useState<DashboardTab>("stats");
|
|
|
|
|
const { data: stats, isLoading, error, refetch } = useStats();
|
2026-07-31 16:54:41 +07:00
|
|
|
const { data: review = [] } = useReview();
|
|
|
|
|
|
|
|
|
|
const modQueueItems: ModQueueItem[] = review.slice(0, 10).map((msg) => ({
|
|
|
|
|
id: msg.id,
|
|
|
|
|
content: msg.content || msg.id,
|
|
|
|
|
username: msg.username,
|
2026-08-01 08:56:10 +07:00
|
|
|
metadata: msg.metadata ?? null,
|
2026-07-31 16:54:41 +07:00
|
|
|
severity:
|
|
|
|
|
msg.ai_severity && msg.ai_severity !== "none"
|
|
|
|
|
? (msg.ai_severity as ModQueueItem["severity"])
|
|
|
|
|
: "medium",
|
|
|
|
|
reason: msg.ai_analysis ?? "AI moderation flag",
|
|
|
|
|
}));
|
2026-07-28 10:05:08 +07:00
|
|
|
|
|
|
|
|
const subNavTabs = [
|
|
|
|
|
{ id: "stats", label: "Stats", icon: <Hash className="size-3" /> },
|
|
|
|
|
{ id: "live", label: "Live", icon: <Sparkles className="size-3" /> },
|
2026-07-31 16:54:41 +07:00
|
|
|
{ id: "users", label: "Users", icon: <Users className="size-3" /> },
|
|
|
|
|
{ id: "channels", label: "Channels", icon: <Hash className="size-3" /> },
|
2026-07-28 10:05:08 +07:00
|
|
|
];
|
2026-07-26 11:27:21 +07:00
|
|
|
|
|
|
|
|
return (
|
2026-07-28 10:05:08 +07:00
|
|
|
<div className="space-y-4 animate-fade-in-up">
|
2026-07-31 16:54:41 +07:00
|
|
|
<SubNav
|
|
|
|
|
tabs={subNavTabs}
|
|
|
|
|
activeTab={tab}
|
|
|
|
|
onTabChange={(t) => setTab(t as DashboardTab)}
|
|
|
|
|
/>
|
2026-07-26 15:34:08 +07:00
|
|
|
|
2026-07-28 10:05:08 +07:00
|
|
|
{tab === "stats" && (
|
|
|
|
|
<div className="space-y-4">
|
|
|
|
|
{error ? (
|
|
|
|
|
<ErrorState message={error.message} onRetry={refetch} />
|
|
|
|
|
) : isLoading || !stats ? (
|
|
|
|
|
<LoadingSkeleton count={6} height="h-28" columns={3} />
|
|
|
|
|
) : (
|
|
|
|
|
<>
|
|
|
|
|
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-3">
|
2026-07-31 16:54:41 +07:00
|
|
|
<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"
|
|
|
|
|
/>
|
2026-07-28 10:05:08 +07:00
|
|
|
</div>
|
2026-07-26 11:27:21 +07:00
|
|
|
|
2026-07-31 16:54:41 +07:00
|
|
|
<TopChannelsChart
|
|
|
|
|
data={stats.top_channels.map((c) => ({
|
|
|
|
|
name: c.channel_name ?? c.channel_id,
|
|
|
|
|
count: c.message_count,
|
|
|
|
|
}))}
|
|
|
|
|
/>
|
2026-07-28 10:05:08 +07:00
|
|
|
</>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
2026-07-26 17:15:08 +07:00
|
|
|
)}
|
2026-07-28 10:05:08 +07:00
|
|
|
|
|
|
|
|
{tab === "live" && (
|
|
|
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
|
|
|
<LiveStream />
|
2026-07-31 16:54:41 +07:00
|
|
|
<ModQueue items={modQueueItems} />
|
2026-07-28 10:05:08 +07:00
|
|
|
</div>
|
2026-07-26 17:15:08 +07:00
|
|
|
)}
|
2026-07-28 10:05:08 +07:00
|
|
|
|
2026-07-31 16:54:41 +07:00
|
|
|
{tab === "users" && <UsersSection />}
|
|
|
|
|
|
|
|
|
|
{tab === "channels" && <ChannelsSection />}
|
2026-07-26 11:27:21 +07:00
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|