refactor(v2): add common sidebar
This commit is contained in:
parent
68ace91321
commit
f9a51e4c79
@ -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';
|
||||||
|
22
src/client/components/CommonSidebar.tsx
Normal file
22
src/client/components/CommonSidebar.tsx
Normal 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';
|
@ -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';
|
||||||
|
@ -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]}>
|
||||||
|
@ -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>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -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,
|
||||||
},
|
},
|
||||||
|
@ -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}`,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user