refactor(v2): add common sidebar

This commit is contained in:
moonrailgun 2024-03-23 00:37:30 +08:00
parent 68ace91321
commit f9a51e4c79
7 changed files with 118 additions and 27 deletions

View File

@ -46,7 +46,7 @@ export const CommonList: React.FC<CommonListProps> = React.memo((props) => {
const finalList = searchResult ?? props.items; const finalList = searchResult ?? props.items;
return ( return (
<> <div className="flex flex-col h-full">
{props.hasSearch && ( {props.hasSearch && (
<div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60"> <div className="bg-background/95 p-4 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<form> <form>
@ -63,7 +63,7 @@ export const CommonList: React.FC<CommonListProps> = React.memo((props) => {
</div> </div>
)} )}
<ScrollArea className="h-screen"> <ScrollArea className="flex-1">
<div className="flex flex-col gap-2 p-4 pt-0"> <div className="flex flex-col gap-2 p-4 pt-0">
{finalList.map((item) => { {finalList.map((item) => {
const isSelected = item.href === location.pathname; const isSelected = item.href === location.pathname;
@ -105,7 +105,7 @@ export const CommonList: React.FC<CommonListProps> = React.memo((props) => {
})} })}
</div> </div>
</ScrollArea> </ScrollArea>
</> </div>
); );
}); });
CommonList.displayName = 'CommonList'; CommonList.displayName = 'CommonList';

View File

@ -0,0 +1,22 @@
import React from 'react';
import { Separator } from './ui/separator';
interface CommonSidebarProps extends React.PropsWithChildren {
header: React.ReactNode;
}
export const CommonSidebar: React.FC<CommonSidebarProps> = React.memo(
(props) => {
return (
<div className="h-full flex flex-col">
<div className="flex items-center px-4 py-2 h-[52px]">
{props.header}
</div>
<Separator />
<div className="flex-1 overflow-hidden">{props.children}</div>
</div>
);
}
);
CommonSidebar.displayName = 'CommonSidebar';

View File

@ -24,7 +24,7 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from '@/components/ui/form'; } from '@/components/ui/form';
import { useEvent, useEventWithLoading } from '@/hooks/useEvent'; import { useEvent } from '@/hooks/useEvent';
import { Input } from '../ui/input'; import { Input } from '../ui/input';
import { preventDefault } from '@/utils/dom'; import { preventDefault } from '@/utils/dom';
import { trpc } from '@/api/trpc'; import { trpc } from '@/api/trpc';

View File

@ -17,14 +17,10 @@ import { cn } from '@/utils/style';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { Nav } from './Layout/Nav'; import { Nav } from './Layout/Nav';
import { WorkspaceSwitcher } from '@/components/WorkspaceSwitcher'; import { WorkspaceSwitcher } from '@/components/WorkspaceSwitcher';
import { ColorSchemeSwitcher } from '@/components/ColorSchemeSwitcher';
import { LanguageSelector } from '@/components/LanguageSelector';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import { useUserInfo } from '@/store/user';
import { Button } from '@/components/ui/button';
import { UserConfig } from './Layout/UserConfig'; import { UserConfig } from './Layout/UserConfig';
import { Outlet } from '@tanstack/react-router'; import { Outlet } from '@tanstack/react-router';
import { CommonList, CommonListItem } from '@/components/CommonList'; import { trpc } from '@/api/trpc';
import { useCurrentWorkspaceId } from '@/store/user';
const defaultLayout: [number, number, number] = [265, 440, 655]; const defaultLayout: [number, number, number] = [265, 440, 655];
@ -41,6 +37,10 @@ export const LayoutV2: React.FC<{
defaultValue: false, defaultValue: false,
} }
); );
const workspaceId = useCurrentWorkspaceId();
const { data: serviceCount } = trpc.workspace.getServiceCount.useQuery({
workspaceId,
});
return ( return (
<TooltipProvider delayDuration={0}> <TooltipProvider delayDuration={0}>
@ -83,13 +83,13 @@ export const LayoutV2: React.FC<{
links={[ links={[
{ {
title: 'Website', title: 'Website',
label: '128', label: String(serviceCount?.website ?? ''),
icon: LuAreaChart, icon: LuAreaChart,
variant: 'default', variant: 'default',
}, },
{ {
title: 'Monitor', title: 'Monitor',
label: '9', label: String(serviceCount?.monitor ?? ''),
icon: LuMonitorDot, icon: LuMonitorDot,
variant: 'ghost', variant: 'ghost',
}, },
@ -101,13 +101,13 @@ export const LayoutV2: React.FC<{
}, },
{ {
title: 'Telemetry', title: 'Telemetry',
label: '', label: String(serviceCount?.telemetry ?? ''),
icon: LuWifi, icon: LuWifi,
variant: 'ghost', variant: 'ghost',
}, },
{ {
title: 'Pages', title: 'Pages',
label: '', label: String(serviceCount?.page ?? ''),
icon: LuFilePieChart, icon: LuFilePieChart,
variant: 'ghost', variant: 'ghost',
}, },
@ -121,7 +121,7 @@ export const LayoutV2: React.FC<{
</ResizablePanel> </ResizablePanel>
<ResizableHandle withHandle /> <ResizableHandle withHandle />
<ResizablePanel defaultSize={layout[1]} minSize={30}> <ResizablePanel defaultSize={layout[1]} minSize={30}>
<div>{props.list}</div> <div className="h-full overflow-hidden">{props.list}</div>
</ResizablePanel> </ResizablePanel>
<ResizableHandle withHandle /> <ResizableHandle withHandle />
<ResizablePanel defaultSize={layout[2]}> <ResizablePanel defaultSize={layout[2]}>

View File

@ -1,5 +1,6 @@
import { trpc } from '@/api/trpc'; import { trpc } from '@/api/trpc';
import { CommonList } from '@/components/CommonList'; import { CommonList } from '@/components/CommonList';
import { CommonSidebar } from '@/components/CommonSidebar';
import { Separator } from '@/components/ui/separator'; import { Separator } from '@/components/ui/separator';
import { AddWebsiteBtn } from '@/components/website/AddWebsiteBtn'; import { AddWebsiteBtn } from '@/components/website/AddWebsiteBtn';
import { useDataReady } from '@/hooks/useDataReady'; import { useDataReady } from '@/hooks/useDataReady';
@ -54,18 +55,19 @@ function WebsiteComponent() {
return ( return (
<LayoutV2 <LayoutV2
list={ list={
<div> <CommonSidebar
<div className="flex items-center px-4 py-2"> header={
<>
<h1 className="text-xl font-bold">{t('Website')}</h1> <h1 className="text-xl font-bold">{t('Website')}</h1>
<div className="ml-auto"> <div className="ml-auto">
<AddWebsiteBtn /> <AddWebsiteBtn />
</div> </div>
</div> </>
<Separator /> }
>
<CommonList hasSearch={true} items={items} /> <CommonList hasSearch={true} items={items} />
</div> </CommonSidebar>
} }
/> />
); );

View File

@ -64,7 +64,7 @@ export const websiteRouter = router({
.meta({ .meta({
openapi: { openapi: {
method: 'GET', method: 'GET',
path: `/workspace/{workspaceId}/website/all`, path: '/workspace/{workspaceId}/website/all',
tags: [OPENAPI_TAG.WEBSITE], tags: [OPENAPI_TAG.WEBSITE],
protect: true, protect: true,
}, },

View File

@ -1,8 +1,16 @@
import { publicProcedure, router, workspaceOwnerProcedure } from '../trpc'; import {
OpenApiMetaInfo,
publicProcedure,
router,
workspaceOwnerProcedure,
workspaceProcedure,
} from '../trpc';
import { z } from 'zod'; import { z } from 'zod';
import { prisma } from '../../model/_client'; import { prisma } from '../../model/_client';
import { workspaceDashboardLayoutSchema } from '../../model/_schema'; import { workspaceDashboardLayoutSchema } from '../../model/_schema';
import { Prisma } from '@prisma/client'; import { Prisma } from '@prisma/client';
import { OPENAPI_TAG } from '../../utils/const';
import { OpenApiMeta } from 'trpc-openapi';
export const workspaceRouter = router({ export const workspaceRouter = router({
getUserWorkspaceRole: publicProcedure getUserWorkspaceRole: publicProcedure
@ -27,6 +35,54 @@ export const workspaceRouter = router({
return relation?.role ?? null; return relation?.role ?? null;
}), }),
getServiceCount: workspaceProcedure
.meta(
buildWorkspaceOpenapi({
method: 'GET',
path: '/getServiceCount',
})
)
.output(
z.object({
website: z.number(),
monitor: z.number(),
telemetry: z.number(),
page: z.number(),
})
)
.query(async ({ input }) => {
const { workspaceId } = input;
const [website, monitor, telemetry, page] = await Promise.all([
prisma.website.count({
where: {
workspaceId,
},
}),
prisma.monitor.count({
where: {
workspaceId,
},
}),
prisma.telemetry.count({
where: {
workspaceId,
},
}),
prisma.monitorStatusPage.count({
where: {
workspaceId,
},
}),
]);
return {
website,
monitor,
telemetry,
page,
};
}),
updateDashboardOrder: workspaceOwnerProcedure updateDashboardOrder: workspaceOwnerProcedure
.input( .input(
z.object({ z.object({
@ -64,3 +120,14 @@ export const workspaceRouter = router({
}); });
}), }),
}); });
function buildWorkspaceOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
return {
openapi: {
tags: [OPENAPI_TAG.WORKSPACE],
protect: true,
...meta,
path: `/workspace/{workspaceId}${meta.path}`,
},
};
}