Files
prompts.chat/src/app/admin/page.tsx
T
2025-12-12 13:50:47 +03:00

201 lines
6.2 KiB
TypeScript

import { Metadata } from "next";
import { redirect } from "next/navigation";
import { getTranslations } from "next-intl/server";
import { auth } from "@/lib/auth";
import { db } from "@/lib/db";
import { Prisma } from "@prisma/client";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Users, FolderTree, Tags, FileText, Webhook } from "lucide-react";
import { UsersTable } from "@/components/admin/users-table";
import { CategoriesTable } from "@/components/admin/categories-table";
import { TagsTable } from "@/components/admin/tags-table";
import { WebhooksTable } from "@/components/admin/webhooks-table";
import { PromptsManagement } from "@/components/admin/prompts-management";
import { isAISearchEnabled } from "@/lib/ai/embeddings";
export const metadata: Metadata = {
title: "Admin Dashboard",
description: "Manage your application",
};
export default async function AdminPage() {
const session = await auth();
const t = await getTranslations("admin");
// Check if user is admin
if (!session?.user || session.user.role !== "ADMIN") {
redirect("/");
}
// Fetch stats and AI search status
const [userCount, promptCount, categoryCount, tagCount, aiSearchEnabled] = await Promise.all([
db.user.count(),
db.prompt.count(),
db.category.count(),
db.tag.count(),
isAISearchEnabled(),
]);
// Count prompts without embeddings (JSON null check requires separate query)
let promptsWithoutEmbeddings = 0;
if (aiSearchEnabled) {
promptsWithoutEmbeddings = await db.prompt.count({
where: {
isPrivate: false,
embedding: { equals: Prisma.DbNull },
},
});
}
// Fetch data for tables
const [users, categories, tags, webhooks] = await Promise.all([
db.user.findMany({
orderBy: { createdAt: "desc" },
select: {
id: true,
email: true,
username: true,
name: true,
avatar: true,
role: true,
verified: true,
createdAt: true,
_count: {
select: {
prompts: true,
},
},
},
}),
db.category.findMany({
orderBy: [{ parentId: "asc" }, { order: "asc" }],
include: {
_count: {
select: {
prompts: true,
children: true,
},
},
parent: {
select: {
id: true,
name: true,
},
},
},
}),
db.tag.findMany({
orderBy: { name: "asc" },
include: {
_count: {
select: {
prompts: true,
},
},
},
}),
db.webhookConfig.findMany({
orderBy: { createdAt: "desc" },
}),
]);
return (
<div className="container py-6">
<div className="mb-6">
<h1 className="text-2xl font-bold">{t("title")}</h1>
<p className="text-sm text-muted-foreground">{t("description")}</p>
</div>
{/* Stats Cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">{t("stats.users")}</CardTitle>
<Users className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{userCount}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">{t("stats.prompts")}</CardTitle>
<FileText className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{promptCount}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">{t("stats.categories")}</CardTitle>
<FolderTree className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{categoryCount}</div>
</CardContent>
</Card>
<Card>
<CardHeader className="flex flex-row items-center justify-between pb-2">
<CardTitle className="text-sm font-medium">{t("stats.tags")}</CardTitle>
<Tags className="h-4 w-4 text-muted-foreground" />
</CardHeader>
<CardContent>
<div className="text-2xl font-bold">{tagCount}</div>
</CardContent>
</Card>
</div>
{/* Management Tabs */}
<Tabs defaultValue="users" className="space-y-4">
<TabsList>
<TabsTrigger value="users" className="gap-2">
<Users className="h-4 w-4" />
{t("tabs.users")}
</TabsTrigger>
<TabsTrigger value="categories" className="gap-2">
<FolderTree className="h-4 w-4" />
{t("tabs.categories")}
</TabsTrigger>
<TabsTrigger value="tags" className="gap-2">
<Tags className="h-4 w-4" />
{t("tabs.tags")}
</TabsTrigger>
<TabsTrigger value="webhooks" className="gap-2">
<Webhook className="h-4 w-4" />
{t("tabs.webhooks")}
</TabsTrigger>
<TabsTrigger value="prompts" className="gap-2">
<FileText className="h-4 w-4" />
{t("tabs.prompts")}
</TabsTrigger>
</TabsList>
<TabsContent value="users">
<UsersTable users={users} />
</TabsContent>
<TabsContent value="categories">
<CategoriesTable categories={categories} />
</TabsContent>
<TabsContent value="tags">
<TagsTable tags={tags} />
</TabsContent>
<TabsContent value="webhooks">
<WebhooksTable webhooks={webhooks} />
</TabsContent>
<TabsContent value="prompts">
<PromptsManagement
aiSearchEnabled={aiSearchEnabled}
promptsWithoutEmbeddings={promptsWithoutEmbeddings}
/>
</TabsContent>
</Tabs>
</div>
);
}