ai-chatbot/app/(admin)/settings/_components/admin-settings-navigation.tsx
2026-05-29 14:11:59 +08:00

273 lines
8.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import Link from "next/link";
import { ArrowLeft, ChevronDown } from "lucide-react";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectSeparator,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Separator } from "@/components/ui/separator";
import { ADMIN_SETTINGS_NAV_GROUPS } from "../_lib/settings-navigation";
import type { AdminSettingsNavGroup, AdminSettingsNavItem } from "../_lib/types";
import { SettingStatusBadge } from "./setting-status-badge";
function getActiveGroup(activeTab: string) {
return ADMIN_SETTINGS_NAV_GROUPS.find((group) =>
group.items.some((item) => item.value === activeTab)
);
}
function getActiveItem(activeTab: string) {
return ADMIN_SETTINGS_NAV_GROUPS.flatMap((group) => group.items).find(
(item) => item.value === activeTab
);
}
function SettingsGroupDropdown({
activeTab,
group,
onTabChange,
}: {
activeTab: string;
group: AdminSettingsNavGroup;
onTabChange: (value: string) => void;
}) {
const hasActiveTab = group.items.some((item) => item.value === activeTab);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
className="h-10 rounded-full px-4"
size="sm"
variant={hasActiveTab ? "default" : "outline"}
>
<span>{group.title}</span>
<ChevronDown className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-80">
<DropdownMenuLabel>{group.title}</DropdownMenuLabel>
<DropdownMenuSeparator />
{group.items.map((item) => {
const Icon = item.icon;
const itemContent = (
<>
<Icon className="h-4 w-4 text-muted-foreground" />
<div className="min-w-0 flex-1">
<div className="font-medium text-sm">{item.label}</div>
<div className="truncate text-muted-foreground text-xs">
{item.description}
</div>
</div>
{item.value === activeTab ? (
<SettingStatusBadge tone="enabled">当前</SettingStatusBadge>
) : null}
</>
);
if (item.href) {
return (
<DropdownMenuItem asChild key={item.value}>
<Link href={item.href}>{itemContent}</Link>
</DropdownMenuItem>
);
}
return (
<DropdownMenuItem
key={item.value}
onSelect={() => onTabChange(item.value)}
>
{itemContent}
</DropdownMenuItem>
);
})}
</DropdownMenuContent>
</DropdownMenu>
);
}
function SettingsQuickAction({
activeTab,
item,
onTabChange,
}: {
activeTab: string;
item: AdminSettingsNavItem;
onTabChange: (value: string) => void;
}) {
const Icon = item.icon;
const className =
item.value === activeTab
? "border-primary bg-primary text-primary-foreground hover:bg-primary/90"
: "border-border bg-background text-foreground hover:bg-accent hover:text-accent-foreground";
if (item.href) {
return (
<Button
asChild
className={`h-10 shrink-0 gap-2 rounded-full border px-4 ${className}`}
size="sm"
variant="ghost"
>
<Link href={item.href}>
<Icon className="h-4 w-4" />
<span>{item.label}</span>
</Link>
</Button>
);
}
return (
<Button
className={`h-10 shrink-0 gap-2 rounded-full border px-4 ${className}`}
onClick={() => onTabChange(item.value)}
size="sm"
variant="ghost"
>
<Icon className="h-4 w-4" />
<span>{item.label}</span>
</Button>
);
}
export function AdminSettingsNavigation({
activeTab,
onTabChange,
}: {
activeTab: string;
onTabChange: (value: string) => void;
}) {
const activeGroup = getActiveGroup(activeTab);
const activeItem = getActiveItem(activeTab);
const ActiveIcon = activeItem?.icon;
return (
<div className="space-y-4 rounded-2xl border bg-background px-5 py-4 shadow-sm">
<div className="flex flex-col gap-4 xl:flex-row xl:items-start xl:justify-between">
<div className="space-y-2">
<div className="font-semibold text-muted-foreground text-xs uppercase tracking-[0.16em]">
Admin Console
</div>
<div className="flex flex-wrap items-center gap-3">
<h1 className="font-semibold text-2xl tracking-tight">
系统设置中心
</h1>
{activeGroup ? (
<SettingStatusBadge tone="enabled">{activeGroup.title}</SettingStatusBadge>
) : null}
</div>
<p className="max-w-4xl text-muted-foreground text-sm leading-6">
页面已改成横向工作区:顶部收拢分类入口,正文区域尽量留给配置表单和数据内容。
</p>
</div>
<Button asChild className="h-10 rounded-full px-4" size="sm" variant="outline">
<Link href="/">
<ArrowLeft className="h-4 w-4" />
返回工作台
</Link>
</Button>
</div>
<div className="rounded-2xl border bg-muted/20 p-2">
<div className="flex flex-col gap-3 md:hidden">
<Select onValueChange={onTabChange} value={activeTab}>
<SelectTrigger className="h-11 border-0 bg-background shadow-sm">
<SelectValue placeholder="选择设置项" />
</SelectTrigger>
<SelectContent>
{ADMIN_SETTINGS_NAV_GROUPS.map((group, groupIndex) => (
<SelectGroup key={group.title}>
<SelectLabel>{group.title}</SelectLabel>
{group.items
.filter((item) => !item.href)
.map((item) => (
<SelectItem key={item.value} value={item.value}>
{item.label}
</SelectItem>
))}
{groupIndex < ADMIN_SETTINGS_NAV_GROUPS.length - 1 ? (
<SelectSeparator />
) : null}
</SelectGroup>
))}
</SelectContent>
</Select>
<div className="flex flex-wrap gap-2">
{(activeGroup?.items ?? []).map((item) => (
<SettingsQuickAction
activeTab={activeTab}
item={item}
key={item.value}
onTabChange={onTabChange}
/>
))}
</div>
</div>
<div className="hidden items-center gap-2 md:flex">
{ADMIN_SETTINGS_NAV_GROUPS.map((group) => (
<SettingsGroupDropdown
activeTab={activeTab}
group={group}
key={group.title}
onTabChange={onTabChange}
/>
))}
<Separator className="mx-1 h-7" orientation="vertical" />
<div className="flex min-w-0 flex-1 items-center gap-2 overflow-x-auto pb-1">
{(activeGroup?.items ?? []).map((item) => (
<SettingsQuickAction
activeTab={activeTab}
item={item}
key={item.value}
onTabChange={onTabChange}
/>
))}
</div>
</div>
</div>
{activeItem && ActiveIcon ? (
<div className="flex items-start gap-3 rounded-2xl border bg-muted/10 px-4 py-3">
<div className="flex h-11 w-11 shrink-0 items-center justify-center rounded-2xl bg-background shadow-sm">
<ActiveIcon className="h-5 w-5 text-muted-foreground" />
</div>
<div className="min-w-0 flex-1">
<div className="flex flex-wrap items-center gap-2">
<h2 className="font-semibold text-base">{activeItem.label}</h2>
{activeGroup ? (
<span className="text-muted-foreground text-xs">
{activeGroup.title}
</span>
) : null}
</div>
<p className="mt-1 text-muted-foreground text-sm">
{activeItem.description}
</p>
</div>
</div>
) : null}
</div>
);
}