Skip to Content
UI playbookDashboards

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 flip direction/split (demos use rc-resize-observer — not needed).
  • Charts: @ant-design/charts v2 (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