feat: website detail basic page

This commit is contained in:
moonrailgun 2023-10-06 15:04:55 +08:00
parent 3d9f768a44
commit b5708b241a
8 changed files with 126 additions and 40 deletions

View File

@ -1,6 +1,7 @@
import { Alert } from 'antd';
import React from 'react'; import React from 'react';
export const ErrorTip: React.FC = React.memo(() => { export const ErrorTip: React.FC = React.memo(() => {
return <div>An unexpected error has occurred</div>; return <Alert message="An unexpected error has occurred" type="error" />;
}); });
ErrorTip.displayName = 'ErrorTip'; ErrorTip.displayName = 'ErrorTip';

View File

@ -0,0 +1,7 @@
import { Alert } from 'antd';
import React from 'react';
export const NotFoundTip: React.FC = React.memo(() => {
return <Alert message="Not found" type="error" />;
});
NotFoundTip.displayName = 'NotFoundTip';

View File

@ -119,7 +119,14 @@ const WebsiteListTable: React.FC<{ workspaceId: string }> = React.memo(
> >
Edit Edit
</Button> </Button>
<Button icon={<BarChartOutlined />}>View</Button> <Button
icon={<BarChartOutlined />}
onClick={() => {
navigate(`/website/${record.id}`);
}}
>
View
</Button>
</div> </div>
); );
}, },

View File

@ -1,14 +1,13 @@
import { Button, message, Tag } from 'antd'; import { Button, message } from 'antd';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import { Column, ColumnConfig } from '@ant-design/charts'; import { Column, ColumnConfig } from '@ant-design/charts';
import { ArrowRightOutlined, SyncOutlined } from '@ant-design/icons'; import { SyncOutlined } from '@ant-design/icons';
import { DateFilter } from './DateFilter'; import { DateFilter } from './DateFilter';
import { HealthBar } from './HealthBar'; import { HealthBar } from './HealthBar';
import { import {
StatsItemType, StatsItemType,
useWorkspaceWebsitePageview, useWorkspaceWebsitePageview,
useWorkspaceWebsiteStats, useWorkspaceWebsiteStats,
useWorspaceWebsites,
WebsiteInfo, WebsiteInfo,
} from '../api/model/website'; } from '../api/model/website';
import { Loading } from './Loading'; import { Loading } from './Loading';
@ -25,30 +24,9 @@ import { formatNumber, formatShortTime } from '../utils/common';
import { useTheme } from '../hooks/useTheme'; import { useTheme } from '../hooks/useTheme';
import { WebsiteOnlineCount } from './WebsiteOnlineCount'; import { WebsiteOnlineCount } from './WebsiteOnlineCount';
interface WebsiteOverviewProps { export const WebsiteOverview: React.FC<{
workspaceId: string;
}
export const WebsiteOverview: React.FC<WebsiteOverviewProps> = React.memo(
(props) => {
const { isLoading, websites } = useWorspaceWebsites(props.workspaceId);
if (isLoading) {
return <Loading />;
}
return (
<div>
{websites.map((website) => (
<WebsiteOverviewItem key={website.id} website={website} />
))}
</div>
);
}
);
WebsiteOverview.displayName = 'WebsiteOverview';
const WebsiteOverviewItem: React.FC<{
website: WebsiteInfo; website: WebsiteInfo;
actions?: React.ReactNode;
}> = React.memo((props) => { }> = React.memo((props) => {
const unit: DateUnit = 'hour'; const unit: DateUnit = 'hour';
const startDate = dayjs().subtract(1, 'day').add(1, unit).startOf(unit); const startDate = dayjs().subtract(1, 'day').add(1, unit).startOf(unit);
@ -99,7 +77,7 @@ const WebsiteOverviewItem: React.FC<{
} }
return ( return (
<div className="mb-10 pb-10 border-b"> <div>
<div className="flex"> <div className="flex">
<div className="flex flex-1 text-2xl font-bold items-center"> <div className="flex flex-1 text-2xl font-bold items-center">
<span className="mr-2" title={props.website.domain ?? ''}> <span className="mr-2" title={props.website.domain ?? ''}>
@ -120,11 +98,7 @@ const WebsiteOverviewItem: React.FC<{
</div> </div>
</div> </div>
<div> <div>{props.actions}</div>
<Button type="primary" size="large">
View Details <ArrowRightOutlined />
</Button>
</div>
</div> </div>
<div className="flex mb-10 flex-wrap"> <div className="flex mb-10 flex-wrap">
@ -147,7 +121,7 @@ const WebsiteOverviewItem: React.FC<{
</div> </div>
); );
}); });
WebsiteOverviewItem.displayName = 'WebsiteOverviewItem'; WebsiteOverview.displayName = 'WebsiteOverview';
export const MetricsBar: React.FC<{ export const MetricsBar: React.FC<{
stats: { stats: {

View File

@ -1,14 +1,23 @@
import React from 'react'; import React, { Fragment } from 'react';
import { EditOutlined } from '@ant-design/icons'; import { ArrowRightOutlined, EditOutlined } from '@ant-design/icons';
import { Button } from 'antd'; import { Button, Divider } from 'antd';
import { WebsiteOverview } from '../components/WebsiteOverview'; import { WebsiteOverview } from '../components/WebsiteOverview';
import { useCurrentWorkspaceId } from '../store/user'; import { useCurrentWorkspaceId } from '../store/user';
import { Loading } from '../components/Loading'; import { Loading } from '../components/Loading';
import { useWorspaceWebsites } from '../api/model/website';
import { NoWorkspaceTip } from '../components/NoWorkspaceTip';
import { useNavigate } from 'react-router';
export const Dashboard: React.FC = React.memo(() => { export const Dashboard: React.FC = React.memo(() => {
const workspaceId = useCurrentWorkspaceId(); const workspaceId = useCurrentWorkspaceId()!;
const { isLoading, websites } = useWorspaceWebsites(workspaceId);
const navigate = useNavigate();
if (!workspaceId) { if (!workspaceId) {
return <NoWorkspaceTip />;
}
if (isLoading) {
return <Loading />; return <Loading />;
} }
@ -23,7 +32,28 @@ export const Dashboard: React.FC = React.memo(() => {
</div> </div>
</div> </div>
<div> <div>
<WebsiteOverview workspaceId={workspaceId} /> {websites.map((website, i) => (
<Fragment key={website.id}>
{i !== 0 && <Divider />}
<WebsiteOverview
website={website}
actions={
<>
<Button
type="primary"
size="large"
onClick={() => {
navigate(`/website/${website.id}`);
}}
>
View Details <ArrowRightOutlined />
</Button>
</>
}
/>
</Fragment>
))}
</div> </div>
</div> </div>
); );

View File

@ -0,0 +1,47 @@
import { Divider } from 'antd';
import React from 'react';
import { useParams } from 'react-router';
import { trpc } from '../../api/trpc';
import { ErrorTip } from '../../components/ErrorTip';
import { Loading } from '../../components/Loading';
import { NotFoundTip } from '../../components/NotFoundTip';
import { WebsiteOverview } from '../../components/WebsiteOverview';
import { useCurrentWorkspaceId } from '../../store/user';
export const WebsiteDetail: React.FC = React.memo(() => {
const { websiteId } = useParams();
const workspaceId = useCurrentWorkspaceId();
const { data: website, isLoading } = trpc.website.info.useQuery({
workspaceId,
websiteId: websiteId!,
});
if (!websiteId) {
return <ErrorTip />;
}
if (isLoading) {
return <Loading />;
}
if (!website) {
return <NotFoundTip />;
}
return (
<div className="py-6">
<div>
<WebsiteOverview website={website} />
</div>
<Divider />
<div className="flex">
<div className="flex-1">left</div>
<Divider type="vertical" />
<div className="flex-1">right</div>
</div>
</div>
);
});
WebsiteDetail.displayName = 'WebsiteDetail';

View File

@ -1,12 +1,14 @@
import React from 'react'; import React from 'react';
import { Route, Routes } from 'react-router'; import { Route, Routes } from 'react-router';
import { WebsiteList } from '../../components/WebsiteList'; import { WebsiteList } from '../../components/WebsiteList';
import { WebsiteDetail } from './Detail';
export const WebsitePage: React.FC = React.memo(() => { export const WebsitePage: React.FC = React.memo(() => {
return ( return (
<div className="h-full"> <div className="h-full">
<Routes> <Routes>
<Route path="/" element={<WebsiteList />} /> <Route path="/" element={<WebsiteList />} />
<Route path="/:websiteId" element={<WebsiteDetail />} />
</Routes> </Routes>
</div> </div>
); );

View File

@ -1,6 +1,7 @@
import { router, workspaceProcedure } from '../trpc'; import { router, workspaceProcedure } from '../trpc';
import { z } from 'zod'; import { z } from 'zod';
import { getWebsiteOnlineUserCount } from '../../model/website'; import { getWebsiteOnlineUserCount } from '../../model/website';
import { prisma } from '../../model/_client';
export const websiteRouter = router({ export const websiteRouter = router({
onlineCount: workspaceProcedure onlineCount: workspaceProcedure
@ -16,4 +17,21 @@ export const websiteRouter = router({
return count; return count;
}), }),
info: workspaceProcedure
.input(
z.object({
websiteId: z.string(),
})
)
.query(async ({ input }) => {
const { websiteId, workspaceId } = input;
const website = await prisma.website.findUnique({
where: {
id: websiteId,
},
});
return website;
}),
}); });