Files
WonderQ-Project/WonderQ-Admin-UI/src/components/admin/AdminShell.tsx
T

138 lines
4.2 KiB
TypeScript

import { useMemo, useState } from "react";
import { ClipboardList, Headset, Home, List, LogOut } from "lucide-react";
import { clearToken, logoutAdmin } from "@/api";
import { ToastStack } from "@/components/admin/ToastStack";
import { Button } from "@/components/ui/button";
import { ConciergePage } from "@/pages/structure/ConciergePage";
import { HomePage } from "@/pages/structure/HomePage";
import { WanfaPage } from "@/pages/structure/WanfaPage";
import { LeadsPage } from "@/pages/leads/LeadsPage";
import type { Tab, Toast } from "@/types/admin";
const sidebarGroups: {
id: string;
label: string;
items: { id: Tab; label: string; icon: typeof Home; description: string }[];
}[] = [
{
id: "frontend",
label: "前台页面",
items: [
{ id: "home", label: "首页", icon: Home, description: "" },
{ id: "play", label: "玩法", icon: List, description: "" },
{ id: "butler", label: "管家", icon: Headset, description: "" },
{ id: "leads", label: "需求线索", icon: ClipboardList, description: "" },
],
},
];
const tabs = sidebarGroups.flatMap((group) => group.items);
export function AdminShell({ onLogout }: { onLogout: () => void }) {
const [activeTab, setActiveTab] = useState<Tab>("home");
const [toasts, setToasts] = useState<Toast[]>([]);
const activeMeta = useMemo(
() => tabs.find((tab) => tab.id === activeTab),
[activeTab],
);
const activeLabel = activeMeta?.label ?? "";
const activeDescription = activeMeta?.description ?? "";
const activeTopbarDescription = [
activeDescription,
"结构化维护",
"内存开发模式",
]
.filter(Boolean)
.join(" · ");
const notify = (toast: Omit<Toast, "id">) => {
const id = `toast-${Date.now()}-${Math.random().toString(16).slice(2)}`;
setToasts((items) => [...items, { ...toast, id }]);
window.setTimeout(() => {
setToasts((items) => items.filter((item) => item.id !== id));
}, 4200);
};
const logout = () => {
void logoutAdmin().finally(() => {
clearToken();
onLogout();
});
};
return (
<main className="admin-shell">
<aside className="sidebar">
<div className="brand-block">
<span className="brand-mark" aria-hidden="true">万</span>
<b>万趣管理端</b>
</div>
<nav>
{sidebarGroups.map((group) => (
<section
className="sidebar-group"
key={group.id}
aria-label={group.label}
>
<span>{group.label}</span>
{group.items.map((tab) => {
const Icon = tab.icon;
return (
<Button
variant="ghost"
aria-label={tab.label}
className={activeTab === tab.id ? "active" : ""}
key={tab.id}
onClick={() => setActiveTab(tab.id)}
title={tab.label}
>
<Icon size={18} />
<span>
<b>{tab.label}</b>
</span>
</Button>
);
})}
</section>
))}
</nav>
<Button
variant="ghost"
aria-label="退出登录"
className="logout-button"
onClick={logout}
title="退出登录"
>
<LogOut size={17} />
退出登录
</Button>
</aside>
<section className="main-panel">
<header className="topbar">
<div>
<span>{activeLabel}</span>
<small>{activeTopbarDescription}</small>
</div>
</header>
{activeTab === "home" ? (
<HomePage key="home-page" notify={notify} />
) : activeTab === "play" ? (
<WanfaPage key="wanfa-page" notify={notify} />
) : activeTab === "butler" ? (
<ConciergePage key="concierge-page" notify={notify} />
) : activeTab === "leads" ? (
<LeadsPage key="leads-page" notify={notify} />
) : null}
</section>
<ToastStack
toasts={toasts}
onClose={(id) =>
setToasts((items) => items.filter((item) => item.id !== id))
}
/>
</main>
);
}