hts/apps/migrant/app/[locale]/manage/desktop-sidebar.tsx

361 lines
9.9 KiB
TypeScript

"use client";
import { Badge } from "@/components/ui/badge";
import { ScrollArea } from "@/components/ui/scroll-area";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
// import type { Workspace } from "@/lib/db";
import { cn } from "@/lib/utils";
import {
Activity,
BookOpen,
Code,
Crown,
Loader2,
LucideIcon,
Settings,
ShieldHalf,
} from "lucide-react";
import Link from "next/link";
import { useSelectedLayoutSegments } from "next/navigation";
import { useRouter } from "next/navigation";
import React, { useEffect, useState, useTransition } from "react";
import { WorkspaceSwitcher } from "./team-switcher";
import { UserButton } from "./user-button";
import service from "@/lib/http/service";
import { StaffsInfo, StaffsInfoWithoutId, queryStaffList } from "@/lib/http/staff";
type Props = {
workspace: & {
apis: {
id: string;
name: string;
}[];
};
className?: string;
};
type NavItem = {
disabled?: boolean;
tooltip?: string;
icon: LucideIcon;
href: string;
external?: boolean;
label: string;
active?: boolean;
tag?: React.ReactNode;
};
const Tag: React.FC<{ label: string }> = ({ label }) => (
<div className="bg-background border text-content-subtle rounded text-xs px-1 py-0.5 font-mono ">
{label}
</div>
);
export const DesktopSidebar: React.FC<Props> = ({ workspace, className }) => {
const segments = useSelectedLayoutSegments();
const navigation: NavItem[] = [
{
icon: Code,
href: "/manage/overview",
label: "首页",
active: segments.length === 1 && segments.at(0) === "overview",
},
{
icon: Settings,
href: "/manage/staffs",
label: "我的员工",
active: segments.at(0) === "staffs",
},
{
icon: ShieldHalf,
label: "预览效果",
href: "/preview",
active: segments.some((s) => s === "preview"),
// tag: <Tag label="alpha" />,
},
{
icon: Activity,
href: "/manage/billing",
label: "账单",
active: segments.at(0) === "billing",
tag: <Tag label="beta" />,
},
];
const navigationDebug: NavItem[] = [
{
icon: Code,
href: "/manage/overview",
label: "首页",
active: segments.length === 1 && segments.at(0) === "overview",
},
{
icon: Code,
href: "/manage/new",
label: "创建员工",
active: segments.at(0) === "new",
},
{
icon: Code,
href: "/manage/plans",
label: "计费",
active: segments.at(0) === "plans",
},
{
icon: Code,
href: "/manage/repository",
label: "知识库列表",
active: segments.at(0) === "repository",
},
{
icon: Code,
href: "/manage/repository/new",
label: "知识库创建",
active: segments.at(0) === "repository",
},
// {
// icon: Code,
// href: "/manage/apis",
// label: "APIs",
// active: segments.at(0) === "apis",
// },
{
icon: Settings,
href: "/manage/staffs",
label: "我的员工",
active: segments.at(0) === "staffs",
},
{
icon: ShieldHalf,
label: "Authorization",
href: "/manage/authorization/roles",
active: segments.some((s) => s === "authorization"),
tag: <Tag label="alpha" />,
},
{
icon: ShieldHalf,
label: "预览效果",
href: "/preview",
active: segments.some((s) => s === "preview"),
// tag: <Tag label="alpha" />,
},
{
icon: ShieldHalf,
label: "数据分析",
href: "/manage/analysis",
active: segments.some((s) => s === "analysis"),
tag: <Tag label="alpha" />,
},
{
icon: Activity,
href: "/manage/billing",
label: "账单",
active: segments.at(0) === "billing",
tag: <Tag label="beta" />,
},
{
icon: Activity,
href: "/manage/billing/success",
label: "支付成功",
active: segments.at(0) === "billing",
},
];
const [clientReady, setClientReady] = useState<boolean>(false);
const [staffsList, setStaffsList] = useState<StaffsInfoWithoutId[]>([]);
// if (workspace.features.successPage) {
// navigation.push({
// icon: Crown,
// href: "/app/success",
// label: "Success",
// active: segments.at(0) === "success",
// tag: (
// <div className="bg-background border text-content-subtle rounded text-xs px-1 py-0.5 font-mono">
// internal
// </div>
// ),
// });
// }
const firstOfNextMonth = new Date();
firstOfNextMonth.setUTCMonth(firstOfNextMonth.getUTCMonth() + 1);
firstOfNextMonth.setDate(1);
useEffect(() => {
// console.log("------------email", infoRef.current, userData.auth_token)
async function initFunc() {
// await service.post('/api/v1/customer/list/staff', {
// // email: infoRef.current.email
// }, {
// headers: {
// // 'Authorization': token
// }
// }).then(function (result: any) {
// console.log("result:", result)
// if (result && result.header.code != 0) {
// // toast.error(result.header.message)
// return
// }
// }).catch((err: any) => {
// });
const data = await queryStaffList({})
setStaffsList(data.data.list)
}
initFunc()
setClientReady(true);
}, []);
return (
<aside className={cn("fixed inset-y-0 w-64 px-6 z-10", className)}>
<div className="flex min-w-full mt-4 -mx-2">
{/* <WorkspaceSwitcher /> */}
</div>
{/* {workspace.planDowngradeRequest ? (
<div className="flex justify-center w-full mt-2">
<Tooltip>
<TooltipTrigger>
<Badge size="sm">Subscription ending</Badge>
</TooltipTrigger>
<TooltipContent>
Your plan is schedueld to be downgraded to the {workspace.planDowngradeRequest} tier
on {firstOfNextMonth.toDateString()}
</TooltipContent>
</Tooltip>
</div>
) : null} */}
<nav className="flex flex-col flex-1 flex-grow mt-4">
<ul className="flex flex-col flex-1 gap-y-7">
<li>
<h2 className="text-xs font-semibold leading-6 text-content">StaffAI</h2>
<ul className="mt-2 -mx-2 space-y-1">
{navigation.map((item) => (
<li key={item.label}>
<NavLink item={item} />
</li>
))}
</ul>
</li>
<li>
<h2 className="text-xs font-semibold leading-6 text-content">Debug环境</h2>
<ul className="mt-2 -mx-2 space-y-1">
{navigationDebug.map((item) => (
<li key={item.label}>
<NavLink item={item} />
</li>
))}
</ul>
</li>
<li>
<h2 className="text-xs font-semibold leading-6 text-content"></h2>
{/* max-h-64 in combination with the h-8 on the <TooltipTrigger> will fit 8 apis nicely */}
<ScrollArea className="mt-2 -mx-2 space-y-1 overflow-auto max-h-64">
{/* {workspace.apis.map((api) => (
<Tooltip key={api.id}>
<TooltipTrigger className="w-full h-8 overflow-hidden text-ellipsis">
<NavLink
item={{
icon: Code,
href: `/manage/staffs/${api.id}`,
label: api.name,
active: segments.includes(api.id),
}}
/>
</TooltipTrigger>
<TooltipContent>{api.name}</TooltipContent>
</Tooltip>
))} */}
{staffsList.map((api) => (
<Tooltip key={api.id}>
<TooltipTrigger className="w-full h-8 overflow-hidden text-ellipsis">
<NavLink
item={{
icon: Code,
href: `/manage/staffs/${api.id}`,
label: api.name,
active: api.id ? segments.includes(api.id.toString()) : false,
}}
/>
</TooltipTrigger>
<TooltipContent>{api.name}</TooltipContent>
</Tooltip>
))}
</ScrollArea>
</li>
</ul>
</nav>
<UserButton />
</aside>
);
};
const NavLink: React.FC<{ item: NavItem }> = ({ item }) => {
const [isPending, startTransition] = useTransition();
const router = useRouter();
const link = (
<Link
prefetch
href={item.href}
onClick={() => {
if (!item.external) {
startTransition(() => {
router.push(item.href);
});
}
}}
target={item.external ? "_blank" : undefined}
className={cn(
"group flex gap-x-2 rounded-md px-2 py-1 text-sm font-medium leading-6 items-center hover:bg-gray-200 dark:hover:bg-gray-800 justify-between",
{
"bg-gray-200 dark:bg-gray-800": item.active,
"text-content-subtle pointer-events-none": item.disabled,
},
)}
>
<div className="flex group gap-x-2">
<span className="text-content-subtle border-border group-hover:shadow flex h-6 w-6 shrink-0 items-center justify-center rounded-lg border text-[0.625rem] font-medium bg-white">
{isPending ? (
<Loader2 className="w-4 h-4 shrink-0 animate-spin" />
) : (
<item.icon className="w-4 h-4 shrink-0" aria-hidden="true" />
)}
</span>
<p className="truncate whitespace-nowrap">{item.label}</p>
</div>
{item.tag}
</Link>
);
if (item.tooltip) {
return (
<Tooltip>
<TooltipTrigger className="w-full">
{link}
<TooltipContent>{item.tooltip}</TooltipContent>
</TooltipTrigger>
</Tooltip>
);
}
return link;
};