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

304 lines
9.9 KiB
TypeScript

"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 { cn } from "@/lib/utils";
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={cn(
"h-9 rounded-full border px-3.5 text-sm shadow-none",
hasActiveTab
? "border-foreground/10 bg-foreground text-background hover:bg-foreground/90 hover:text-background dark:border-white/20 dark:bg-white dark:text-black dark:hover:bg-white/90"
: "border-border/70 bg-background/90 text-muted-foreground hover:bg-background hover:text-foreground"
)}
size="sm"
variant="ghost"
>
<span>{group.title}</span>
<ChevronDown className="h-3.5 w-3.5 opacity-70" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start" className="w-80">
<DropdownMenuLabel className="space-y-1">
<div>{group.title}</div>
<div className="font-normal text-muted-foreground text-xs">
{group.description}
</div>
</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-foreground/10 bg-foreground text-background shadow-sm hover:bg-foreground/90 dark:border-white/20 dark:bg-white dark:text-black dark:hover:bg-white/90"
: "border-transparent bg-transparent text-muted-foreground hover:border-border/80 hover:bg-background hover:text-foreground";
if (item.href) {
return (
<Button
asChild
className={`h-9 shrink-0 gap-2 rounded-full border px-3.5 ${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-9 shrink-0 gap-2 rounded-full border px-3.5 ${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="rounded-2xl border bg-background px-4 py-3 shadow-sm">
<div className="flex flex-col gap-2.5">
<div className="flex flex-col gap-2 lg:flex-row lg:items-center lg:justify-between">
<div className="min-w-0 space-y-1.5">
<div className="flex flex-wrap items-center gap-2 text-[11px] text-muted-foreground uppercase tracking-[0.18em]">
<span className="font-semibold">Admin Console</span>
{activeGroup ? (
<>
<span className="h-1 w-1 rounded-full bg-border" />
<span className="tracking-normal normal-case text-[12px]">
{activeGroup.title}
</span>
</>
) : null}
</div>
<div className="flex flex-wrap items-center gap-x-3 gap-y-2">
<h1 className="font-semibold text-[26px] leading-none tracking-tight">
系统设置中心
</h1>
{activeItem && ActiveIcon ? (
<div className="inline-flex min-w-0 items-center gap-2 rounded-full border border-border/70 bg-muted/10 px-2.5 py-1.5">
<div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-background text-muted-foreground shadow-sm">
<ActiveIcon className="h-3.5 w-3.5" />
</div>
<div className="min-w-0">
<div className="flex items-center gap-2">
<span className="truncate font-medium text-[13px] text-foreground">
{activeItem.label}
</span>
<SettingStatusBadge tone="enabled">当前</SettingStatusBadge>
</div>
</div>
</div>
) : null}
</div>
</div>
<Button
asChild
className="h-8 rounded-full border-border/70 px-3 text-[13px]"
size="sm"
variant="outline"
>
<Link href="/">
<ArrowLeft className="h-3.5 w-3.5" />
返回工作台
</Link>
</Button>
</div>
<div className="border-border/70 border-t pt-2.5">
<div className="flex flex-col gap-2 md:hidden">
<Select onValueChange={onTabChange} value={activeTab}>
<SelectTrigger className="h-9 border-border/70 bg-background shadow-none">
<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>
{activeItem ? (
<p className="truncate text-muted-foreground text-xs">
{activeItem.description}
</p>
) : null}
<div className="flex flex-wrap gap-1">
{(activeGroup?.items ?? []).map((item) => (
<SettingsQuickAction
activeTab={activeTab}
item={item}
key={item.value}
onTabChange={onTabChange}
/>
))}
</div>
</div>
<div className="hidden gap-3 md:flex md:items-start">
<div className="flex shrink-0 items-center gap-2">
{ADMIN_SETTINGS_NAV_GROUPS.map((group) => (
<SettingsGroupDropdown
activeTab={activeTab}
group={group}
key={group.title}
onTabChange={onTabChange}
/>
))}
</div>
<Separator className="mt-1 h-6" orientation="vertical" />
<div className="min-w-0 flex-1">
<div className="flex min-w-0 items-center gap-1 overflow-x-auto pb-1">
{(activeGroup?.items ?? []).map((item) => (
<SettingsQuickAction
activeTab={activeTab}
item={item}
key={item.value}
onTabChange={onTabChange}
/>
))}
</div>
{activeItem ? (
<p className="truncate px-1 pt-1 text-muted-foreground text-xs">
{activeItem.description}
</p>
) : null}
</div>
</div>
</div>
</div>
</div>
);
}