Dashboards
Dashboards — StatisticCard, ProCard, ProList, charts
const { Statistic } = StatisticCard;
<ProCard direction="column" ghost gutter={[0, 16]}>
<StatisticCard.Group direction={screens.md ? "row" : "column"} loading={!s}>
<StatisticCard statistic={{ title: "Đang chờ duyệt", value: s.pending, description: <Statistic title="Giá trị" value={vnd(s.pendingAmount)} /> }} />
<StatisticCard.Divider type={screens.md ? "vertical" : "horizontal"} />
<StatisticCard statistic={{ title: "Đã duyệt tháng này", value: s.approved, formatter: (v) => vnd(Number(v)),
description: <Statistic title="So với tháng trước" value="12%" trend="up" /> }}
chart={<Tiny.Area height={46} data={series} />} />
</StatisticCard.Group>
<ProCard gutter={16} ghost wrap>
<ProCard title="Cần tôi duyệt" subTitle="gần hạn trước" headerBordered colSpan={{ xs: 24, lg: 15 }} extra={<a>Xem tất cả</a>}>
<ProList rowKey="id" dataSource={rows} split onItem={(r) => ({ onClick: () => open(r) })}
metas={{ avatar: { render: (_, r) => <Avatar>{r.creatorName[0]}</Avatar> }, title: { dataIndex: "title" },
description: { render: (_, r) => <Space>{r.creatorName}<span>{CATEGORY[r.category]}</span><Due r={r} /></Space> },
actions: { render: (_, r) => [<b key="a">{vnd(r.amount)}</b>] } }} />
</ProCard>
<ProCard title="Theo hạng mục" headerBordered colSpan={{ xs: 24, lg: 9 }}>
<Pie data={…} angleField="value" colorField="type" innerRadius={0.62} label={false} height={280}
annotations={[{ type: "text", style: { text: "12,1 tr", x: "50%", y: "50%", textAlign: "center", fontSize: 20, fontWeight: 600 } }]} />
</ProCard>
</ProCard>
</ProCard>- 3–4 KPIs max, each with a secondary line (
description: comparison, value, count). A bare number looks unfinished. trend="up"is red /"down"green (Chinese market convention). For Vietnamese business apps prefer plain text (“tăng 12%”) or color it yourself with tokens.- The to-do list is the hero of an approver’s dashboard; sort by urgency (due date), mark overdue in
danger. - Responsive:
Grid.useBreakpoint()to flipdirection/split(demos use rc-resize-observer — not needed). - Charts:
@ant-design/chartsv2 (Pie,Column,Line,Area,Tiny.*,DualAxes). Give every chart a titled ProCard and units in tooltip/axis formatters.
Dashboard — Dashboard người duyệt: StatisticCard.Group (mỗi số có dòng phụ) + danh sách việc cần làm (ProList) + biểu đồ donut. Mở trong Playground →
Xem code (60 dòng)
// Dashboard người duyệt: StatisticCard.Group (mỗi số có dòng phụ) + danh sách việc cần làm (ProList) + biểu đồ donut.
import { PageContainer, ProCard, ProList, StatisticCard } from "@ant-design/pro-components";
import { Avatar, Button, Grid, Space, Typography, theme } from "antd";
import { ClockCircleOutlined, RightOutlined } from "@ant-design/icons";
import { Pie } from "@ant-design/charts";
const { Statistic } = StatisticCard;
const vnd = (n: number) => `${n.toLocaleString("vi-VN")} ₫`;
const todo = [
{ id: 10, title: "Sửa điều hoà phòng họp tầng 3", who: "Trần Thị Lan · Hành chính", due: "08/11 · quá hạn", late: true, amount: 1500000 },
{ id: 9, title: "Gia hạn Microsoft 365 (12 tháng)", who: "Trần Thị Lan · Hành chính", due: "13/11", late: false, amount: 18600000 },
{ id: 8, title: "2 màn hình Dell P2423", who: "Lê Văn Minh · Kỹ thuật", due: "15/11", late: false, amount: 7800000 },
];
export default function Page() {
const { token } = theme.useToken();
const md = Grid.useBreakpoint().md;
return (
<PageContainer title="Chào buổi chiều, Nguyễn Thu Hà" subTitle="3 đề xuất đang chờ bạn duyệt"
extra={<Button type="primary">Mở hộp duyệt <RightOutlined /></Button>}>
<ProCard direction="column" ghost gutter={[0, 16]}>
<StatisticCard.Group direction={md ? "row" : "column"}>
<StatisticCard statistic={{ title: "Đang chờ duyệt", value: 3, description: <Statistic title="Giá trị" value={vnd(27900000)} /> }} />
<StatisticCard.Divider type={md ? "vertical" : "horizontal"} />
<StatisticCard statistic={{ title: "Đã duyệt tháng này", value: 12090000, formatter: (v) => vnd(Number(v)), description: <Statistic title="So với tháng trước" value="tăng 12%" /> }} />
<StatisticCard.Divider type={md ? "vertical" : "horizontal"} />
<StatisticCard statistic={{ title: "Từ chối tháng này", value: 1, description: <Statistic title="Tổng đề xuất" value={8} /> }} />
</StatisticCard.Group>
<ProCard gutter={16} ghost wrap>
<ProCard title="Cần tôi duyệt" subTitle="gần hạn trước" extra={<a>Xem tất cả</a>} headerBordered colSpan={{ xs: 24, lg: 15 }}>
<ProList
rowKey="id"
split
dataSource={todo}
metas={{
avatar: { render: (_, r) => <Avatar style={{ background: token.colorPrimary }}>{r.who[0]}</Avatar> },
title: { dataIndex: "title" },
description: { render: (_, r) => <Space size={12}><span>{r.who}</span><Typography.Text type={r.late ? "danger" : "secondary"}><ClockCircleOutlined /> {r.due}</Typography.Text></Space> },
actions: { render: (_, r) => [<b key="a">{vnd(r.amount)}</b>] },
}}
/>
</ProCard>
<ProCard title="Đã duyệt tháng này" subTitle="theo hạng mục" headerBordered colSpan={{ xs: 24, lg: 9 }}>
<Pie
height={260}
data={[{ type: "Thiết bị", value: 4990000 }, { type: "Văn phòng phẩm", value: 2400000 }, { type: "Dịch vụ", value: 4700000 }]}
angleField="value"
colorField="type"
innerRadius={0.62}
label={false}
legend={{ color: { position: "bottom", layout: { justifyContent: "center" } } }}
tooltip={{ items: [{ field: "value", valueFormatter: (v: number) => vnd(v) }] }}
/>
</ProCard>
</ProCard>
</ProCard>
</PageContainer>
);
}
Last updated on