feat: website detail basic page
This commit is contained in:
parent
3d9f768a44
commit
b5708b241a
@ -1,6 +1,7 @@
|
||||
import { Alert } from 'antd';
|
||||
import React from 'react';
|
||||
|
||||
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';
|
||||
|
7
src/client/components/NotFoundTip.tsx
Normal file
7
src/client/components/NotFoundTip.tsx
Normal 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';
|
@ -119,7 +119,14 @@ const WebsiteListTable: React.FC<{ workspaceId: string }> = React.memo(
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
<Button icon={<BarChartOutlined />}>View</Button>
|
||||
<Button
|
||||
icon={<BarChartOutlined />}
|
||||
onClick={() => {
|
||||
navigate(`/website/${record.id}`);
|
||||
}}
|
||||
>
|
||||
View
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
@ -1,14 +1,13 @@
|
||||
import { Button, message, Tag } from 'antd';
|
||||
import { Button, message } from 'antd';
|
||||
import React, { useMemo } from 'react';
|
||||
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 { HealthBar } from './HealthBar';
|
||||
import {
|
||||
StatsItemType,
|
||||
useWorkspaceWebsitePageview,
|
||||
useWorkspaceWebsiteStats,
|
||||
useWorspaceWebsites,
|
||||
WebsiteInfo,
|
||||
} from '../api/model/website';
|
||||
import { Loading } from './Loading';
|
||||
@ -25,30 +24,9 @@ import { formatNumber, formatShortTime } from '../utils/common';
|
||||
import { useTheme } from '../hooks/useTheme';
|
||||
import { WebsiteOnlineCount } from './WebsiteOnlineCount';
|
||||
|
||||
interface WebsiteOverviewProps {
|
||||
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<{
|
||||
export const WebsiteOverview: React.FC<{
|
||||
website: WebsiteInfo;
|
||||
actions?: React.ReactNode;
|
||||
}> = React.memo((props) => {
|
||||
const unit: DateUnit = 'hour';
|
||||
const startDate = dayjs().subtract(1, 'day').add(1, unit).startOf(unit);
|
||||
@ -99,7 +77,7 @@ const WebsiteOverviewItem: React.FC<{
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mb-10 pb-10 border-b">
|
||||
<div>
|
||||
<div className="flex">
|
||||
<div className="flex flex-1 text-2xl font-bold items-center">
|
||||
<span className="mr-2" title={props.website.domain ?? ''}>
|
||||
@ -120,11 +98,7 @@ const WebsiteOverviewItem: React.FC<{
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button type="primary" size="large">
|
||||
View Details <ArrowRightOutlined />
|
||||
</Button>
|
||||
</div>
|
||||
<div>{props.actions}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex mb-10 flex-wrap">
|
||||
@ -147,7 +121,7 @@ const WebsiteOverviewItem: React.FC<{
|
||||
</div>
|
||||
);
|
||||
});
|
||||
WebsiteOverviewItem.displayName = 'WebsiteOverviewItem';
|
||||
WebsiteOverview.displayName = 'WebsiteOverview';
|
||||
|
||||
export const MetricsBar: React.FC<{
|
||||
stats: {
|
||||
|
@ -1,14 +1,23 @@
|
||||
import React from 'react';
|
||||
import { EditOutlined } from '@ant-design/icons';
|
||||
import { Button } from 'antd';
|
||||
import React, { Fragment } from 'react';
|
||||
import { ArrowRightOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import { Button, Divider } from 'antd';
|
||||
import { WebsiteOverview } from '../components/WebsiteOverview';
|
||||
import { useCurrentWorkspaceId } from '../store/user';
|
||||
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(() => {
|
||||
const workspaceId = useCurrentWorkspaceId();
|
||||
const workspaceId = useCurrentWorkspaceId()!;
|
||||
const { isLoading, websites } = useWorspaceWebsites(workspaceId);
|
||||
const navigate = useNavigate();
|
||||
|
||||
if (!workspaceId) {
|
||||
return <NoWorkspaceTip />;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
@ -23,7 +32,28 @@ export const Dashboard: React.FC = React.memo(() => {
|
||||
</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>
|
||||
);
|
||||
|
47
src/client/pages/Website/Detail.tsx
Normal file
47
src/client/pages/Website/Detail.tsx
Normal 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';
|
@ -1,12 +1,14 @@
|
||||
import React from 'react';
|
||||
import { Route, Routes } from 'react-router';
|
||||
import { WebsiteList } from '../../components/WebsiteList';
|
||||
import { WebsiteDetail } from './Detail';
|
||||
|
||||
export const WebsitePage: React.FC = React.memo(() => {
|
||||
return (
|
||||
<div className="h-full">
|
||||
<Routes>
|
||||
<Route path="/" element={<WebsiteList />} />
|
||||
<Route path="/:websiteId" element={<WebsiteDetail />} />
|
||||
</Routes>
|
||||
</div>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { router, workspaceProcedure } from '../trpc';
|
||||
import { z } from 'zod';
|
||||
import { getWebsiteOnlineUserCount } from '../../model/website';
|
||||
import { prisma } from '../../model/_client';
|
||||
|
||||
export const websiteRouter = router({
|
||||
onlineCount: workspaceProcedure
|
||||
@ -16,4 +17,21 @@ export const websiteRouter = router({
|
||||
|
||||
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;
|
||||
}),
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user