361 lines
9.9 KiB
TypeScript
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;
|
|
};
|