本文实现仪表板功能,包括三个组件:顶部是问题的概要,下面是图表,右侧是最新的问题。
构建最新问题组件
先构建最新问题组件,在app目录下新建LastestIssues.tsx。
使用prisma获取最新5个问题,并获取分配的用户,注意Prisma中使用的是include属性关联。
用Card包装Table,然后map分别显示各行,每行显示带链接的标题和分配的用户头像。Card显示一个标题信息Lastest Issues,并根据页面效果调整相应样式。
import prisma from "@/prisma/client";
import { Avatar, Card, Flex, Heading, Table } from "@radix-ui/themes";
import Link from "next/link";
import React from "react";
import { IssueStatusBadge } from "./components";
const LastestIssues = async () => {
const issues = await prisma.issue.findMany({
orderBy: { createAt: "desc" },
take: 5,
include: {
assignedToUser: true,
},
});
return (
<Card>
<Heading size="4" mb="4">
Lastest Issues
</Heading>
<Table.Root>
<Table.Body>
{issues.map((issue) => (
<Table.Row key={issue.id}>
<Table.Cell>
<Flex justify={"between"}>
<Flex direction={"column"} align="start" gap="2">
<Link href={`/issues/${issue.id}`}>{issue.title}</Link>
<IssueStatusBadge status={issue.status} />
</Flex>
{issue.assignedToUser && (
<Avatar
src={issue.assignedToUser.image!}
fallback="?"
size={"2"}
radius="full"
/>
)}
</Flex>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</Card>
);
};
export default LastestIssues;
将LastestIssues组件在首页(/app/page.tsx)调用并查看效果。
import LastestIssues from "./LastestIssues";
export default function Home() {
return <LastestIssues />;
}
提交Git,命名:Build the lastestIssue component.
构建问题概要组件
再来创建问题概要组件,在app目录下新建IssueSummary组件。
使用3个卡片水平排列显示各个状态的问题数量。组件需要3个传递数据:open, inProgress, closed。创建数组containers,元素是3个对象,各个对象的属性有label,value,status。通过调用containers的map方法显示各个卡片,并适当进行样式化。
import { Status } from "@prisma/client";
import { Card, Flex, Text } from "@radix-ui/themes";
import Link from "next/link";
interface Props {
open: number;
inProgress: number;
closed: number;
}
const IssueSummary = ({ open, inProgress, closed }: Props) => {
const containers: { label: string; value: number; status: Status }[] = [
{ label: "Open Issues", value: open, status: "OPEN" },
{ label: "In-progress Issues", value: inProgress, status: "IN_PROGRESS" },
{ label: "Closed Issues", value: closed, status: "CLOSED" },
];
return (
<Flex gap={"4"}>
{containers.map((container) => (
<Card>
<Flex direction={"column"} gap="1">
<Link
className="text-sm font-medium"
href={`/issues?status=${container.status}`}
>
{container.label}
</Link>
<Text size="5" className="font-bold">
{container.value}
</Text>
</Flex>
</Card>
))}
</Flex>
);
};
export default IssueSummary;
在主页app/page.tsx中查看。
从数据库获取各状态的问题的数量,再调用IssueSummary。
import prisma from "@/prisma/client";
import IssueSummary from "./IssueSummary";
export default async function Home() {
const open = await prisma.issue.count({ where: { status: "OPEN" } });
const inProgress = await prisma.issue.count({
where: { status: "IN_PROGRESS" },
});
const closed = await prisma.issue.count({ where: { status: "CLOSED" } });
return <IssueSummary open={open} inProgress={inProgress} closed={closed} />;
}
查看页面,还不错。
提交Git,命名:Build the IssueSummary component.
构建条形图组件
现在来创建图表组件,我们使用Recharts,官网是:
安装:npm i recharts@2.8.0
在app下新建IssueChart.tsx文件,我们要创建的是条形图,使用的组件是BarChart,使用ResponsiveContainer包裹。为保持统一,外层再用Card包装。
与IssueSummary组件一样,IssueChart组件接受open, inProgress, closed的数量,然后进行展示。根据官网的示例,非常简单。Bar的样式使用了style属性,同时调用fill为 var(–accent-9),这是为了与主题theme一致,当主题变换了之后,Bar的颜色也会变。 “–accent-9”可以通过谷歌浏览器开发工具的查看元素功能获取,这里不详述。
"use client";
import { Card } from "@radix-ui/themes";
import React from "react";
import { ResponsiveContainer, BarChart, Bar, XAxis, YAxis } from "recharts";
interface Props {
open: number;
inProgress: number;
closed: number;
}
const IssueChart = ({ open, inProgress, closed }: Props) => {
const data = [
{ label: "Open", value: open },
{ label: "In progress", value: inProgress },
{ label: "Closed", value: closed },
];
return (
<Card>
<ResponsiveContainer width="100%" height={300}>
<BarChart data={data}>
<XAxis dataKey="label" />
<YAxis />
<Bar
dataKey="value"
barSize={60}
style={{ fill: "var(--accent-9)" }}
/>
</BarChart>
</ResponsiveContainer>
</Card>
);
};
export default IssueChart;
同样的,app/page.tsx调用查看效果。
提交Git,命名:Build the IssueChart component.
布置仪表板
仪表板的组件已建好,我们来布置。
app/page.tsx
import prisma from "@/prisma/client";
import { Flex, Grid } from "@radix-ui/themes";
import IssueChart from "./IssueChart";
import IssueSummary from "./IssueSummary";
import LastestIssues from "./LastestIssues";
export default async function Home() {
const open = await prisma.issue.count({ where: { status: "OPEN" } });
const inProgress = await prisma.issue.count({
where: { status: "IN_PROGRESS" },
});
const closed = await prisma.issue.count({ where: { status: "CLOSED" } });
return (
<Grid columns={{ initial: "1", md: "2" }} gap={"5"}>
<Flex direction={"column"} gap={"5"}>
<IssueSummary open={open} inProgress={inProgress} closed={closed} />
<IssueChart open={open} inProgress={inProgress} closed={closed} />
</Flex>
<LastestIssues />
</Grid>
);
}
形成的响应式页面。大屏和小屏分别显示如下。
小结
本文实现了仪表板功能,分别创建三个组件:顶部的问题概要组件,下面的图表组件,右侧的最新问题组件,并将这些组件组合形成页面。
下一篇是本系列的最后一篇,部署Next.js的生产环境。