Skip to main content

Dashboard

Latest Issue

本节代码链接

/app/LatestIssue.tsx
import prisma from "@/prisma/client";
import { Avatar, Card, Flex, Heading, Table } from "@radix-ui/themes";
import Link from "next/link";
import { IssueStatusBadge } from "./components";

const LatestIssue = async () => {
const issues = await prisma.issue.findMany({
orderBy: { createdAt: "desc" },
take: 5,
include: {
assignedToUser: true,
},
});
return (
<Card>
<Heading size="4" mb="4">
Latest 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}></IssueStatusBadge>
</Flex>
<Flex align="center">
{issue.assignedToUserId && (
<Avatar
src={issue.assignedToUser?.image!}
fallback="?"
size="2"
radius="full"
/>
)}
</Flex>
</Flex>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
</Card>
);
};
export default LatestIssue;

效果如下

Latest Issue

Issue Summary

本节代码链接

/app/IssueSummary.tsx
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 key={container.label}>
<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;

效果如下

Issue Summary

Issue Charts

本节代码链接

Re-charts

npm i recharts
/app/IssueCharts
"use client";
import { Card } from "@radix-ui/themes";
import { Bar, BarChart, ResponsiveContainer, 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;

效果如下

Issue Chart

Dashboard Layout

本节代码链接

/app/page.tsx
import prisma from "@/prisma/client";
import { Flex, Grid } from "@radix-ui/themes";
import IssueChart from "./IssueChart";
import IssueSummary from "./IssueSummary";
import LatestIssue from "./LatestIssue";

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} closed={closed} inProgress={inProgress} />
<IssueChart open={open} closed={closed} inProgress={inProgress} />
</Flex>
<LatestIssue />
</Grid>
);
}

最终显示效果如下

Dashboard

Buy me a coffee ☕:
This article is licensed under CC 4.0 BY-SA