feat: Rearrange dashboard - pie chart + stats on top, spools middle, analytics bottom
Some checks failed
CI / style (push) Has been cancelled
CI / build-client (push) Has been cancelled
CI / build-amd64 (push) Has been cancelled
CI / build-tester (push) Has been cancelled
CI / test (cockroachdb) (push) Has been cancelled
CI / test (mariadb) (push) Has been cancelled
CI / test (postgres) (push) Has been cancelled
CI / test (sqlite) (push) Has been cancelled
CI / build-arm64 (push) Has been cancelled
CI / build-armv7 (push) Has been cancelled
CI / publish-images (push) Has been cancelled
CI / publish-release (push) Has been cancelled
Some checks failed
CI / style (push) Has been cancelled
CI / build-client (push) Has been cancelled
CI / build-amd64 (push) Has been cancelled
CI / build-tester (push) Has been cancelled
CI / test (cockroachdb) (push) Has been cancelled
CI / test (mariadb) (push) Has been cancelled
CI / test (postgres) (push) Has been cancelled
CI / test (sqlite) (push) Has been cancelled
CI / build-arm64 (push) Has been cancelled
CI / build-armv7 (push) Has been cancelled
CI / publish-images (push) Has been cancelled
CI / publish-release (push) Has been cancelled
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -20,7 +20,11 @@ const COLORS = [
|
||||
"#2f54eb", // geekblue
|
||||
];
|
||||
|
||||
export function MaterialDistributionChart() {
|
||||
interface MaterialDistributionChartProps {
|
||||
square?: boolean;
|
||||
}
|
||||
|
||||
export function MaterialDistributionChart({ square }: MaterialDistributionChartProps) {
|
||||
const t = useTranslate();
|
||||
const { token } = useToken();
|
||||
|
||||
@@ -59,20 +63,21 @@ export function MaterialDistributionChart() {
|
||||
<Card
|
||||
title={t("home.charts.material_distribution")}
|
||||
size="small"
|
||||
style={{ marginBottom: 16 }}
|
||||
style={square ? { aspectRatio: "1", display: "flex", flexDirection: "column" } : { marginBottom: 16 }}
|
||||
styles={square ? { body: { flex: 1, padding: "8px 12px", display: "flex", flexDirection: "column" } } : undefined}
|
||||
>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
<ResponsiveContainer width="100%" height={square ? "100%" : 250}>
|
||||
<PieChart>
|
||||
<Pie
|
||||
data={chartData}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
innerRadius={50}
|
||||
outerRadius={80}
|
||||
innerRadius={square ? 40 : 50}
|
||||
outerRadius={square ? 70 : 80}
|
||||
paddingAngle={2}
|
||||
dataKey="value"
|
||||
label={({ name, percent }) => `${name} (${((percent ?? 0) * 100).toFixed(0)}%)`}
|
||||
labelLine={{ stroke: token.colorTextSecondary }}
|
||||
label={square ? undefined : ({ name, percent }) => `${name} (${((percent ?? 0) * 100).toFixed(0)}%)`}
|
||||
labelLine={square ? false : { stroke: token.colorTextSecondary }}
|
||||
>
|
||||
{chartData.map((_, index) => (
|
||||
<Cell key={`cell-${index}`} fill={COLORS[index % COLORS.length]} />
|
||||
@@ -86,6 +91,13 @@ export function MaterialDistributionChart() {
|
||||
borderRadius: token.borderRadius,
|
||||
}}
|
||||
/>
|
||||
{square && (
|
||||
<Legend
|
||||
layout="horizontal"
|
||||
verticalAlign="bottom"
|
||||
wrapperStyle={{ fontSize: 11 }}
|
||||
/>
|
||||
)}
|
||||
</PieChart>
|
||||
</ResponsiveContainer>
|
||||
</Card>
|
||||
|
||||
@@ -12,7 +12,11 @@ import { getAPIURL } from "../../../utils/url";
|
||||
|
||||
const { useToken } = theme;
|
||||
|
||||
export function QuickStats() {
|
||||
interface QuickStatsProps {
|
||||
compact?: boolean;
|
||||
}
|
||||
|
||||
export function QuickStats({ compact }: QuickStatsProps) {
|
||||
const t = useTranslate();
|
||||
const { token } = useToken();
|
||||
const currencyFormatter = useCurrencyFormatter();
|
||||
@@ -70,6 +74,67 @@ export function QuickStats() {
|
||||
return `${Math.round(grams)} g`;
|
||||
};
|
||||
|
||||
const stats = [
|
||||
{
|
||||
title: t("home.quick_stats.total_filaments"),
|
||||
value: filamentsResult?.total ?? 0,
|
||||
loading: filamentsQuery.isLoading,
|
||||
prefix: <HighlightOutlined />,
|
||||
to: "/filament",
|
||||
},
|
||||
{
|
||||
title: t("home.quick_stats.total_vendors"),
|
||||
value: vendorsResult?.total ?? 0,
|
||||
loading: vendorsQuery.isLoading,
|
||||
prefix: <UserOutlined />,
|
||||
to: "/vendor",
|
||||
},
|
||||
{
|
||||
title: t("home.quick_stats.total_weight"),
|
||||
value: formatWeight(totalWeight),
|
||||
loading: spoolsQuery.isLoading,
|
||||
prefix: <DatabaseOutlined />,
|
||||
},
|
||||
{
|
||||
title: t("home.quick_stats.total_value"),
|
||||
value: currencyFormatter.format(totalValue),
|
||||
loading: spoolsQuery.isLoading,
|
||||
prefix: <DollarOutlined />,
|
||||
},
|
||||
{
|
||||
title: t("home.quick_stats.total_spent"),
|
||||
value: currencyFormatter.format(costStats?.total_spent ?? 0),
|
||||
loading: costLoading,
|
||||
prefix: <DollarOutlined />,
|
||||
},
|
||||
{
|
||||
title: t("home.quick_stats.avg_cost_per_kg"),
|
||||
value: costStats?.avg_cost_per_kg != null ? currencyFormatter.format(costStats.avg_cost_per_kg) : "-",
|
||||
loading: costLoading,
|
||||
prefix: <RiseOutlined />,
|
||||
},
|
||||
];
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<Row gutter={[8, 8]}>
|
||||
{stats.map((stat, i) => (
|
||||
<Col xs={12} key={i}>
|
||||
<Card size="small" styles={{ body: { padding: "8px 12px" } }}>
|
||||
<Statistic
|
||||
title={stat.title}
|
||||
value={stat.value}
|
||||
loading={stat.loading}
|
||||
prefix={stat.prefix}
|
||||
valueStyle={{ fontSize: 16 }}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Row gutter={[12, 12]} style={{ marginTop: 16 }}>
|
||||
<Col xs={12} md={8} lg={4}>
|
||||
@@ -84,70 +149,21 @@ export function QuickStats() {
|
||||
</Card>
|
||||
</Link>
|
||||
</Col>
|
||||
<Col xs={12} md={8} lg={4}>
|
||||
<Link to="/filament">
|
||||
{stats.map((stat, i) => (
|
||||
<Col xs={12} md={8} lg={4} key={i}>
|
||||
{stat.to ? (
|
||||
<Link to={stat.to}>
|
||||
<Card hoverable size="small">
|
||||
<Statistic
|
||||
title={t("home.quick_stats.total_filaments")}
|
||||
value={filamentsResult?.total ?? 0}
|
||||
loading={filamentsQuery.isLoading}
|
||||
prefix={<HighlightOutlined />}
|
||||
/>
|
||||
<Statistic title={stat.title} value={stat.value} loading={stat.loading} prefix={stat.prefix} />
|
||||
</Card>
|
||||
</Link>
|
||||
</Col>
|
||||
<Col xs={12} md={8} lg={4}>
|
||||
<Link to="/vendor">
|
||||
<Card hoverable size="small">
|
||||
<Statistic
|
||||
title={t("home.quick_stats.total_vendors")}
|
||||
value={vendorsResult?.total ?? 0}
|
||||
loading={vendorsQuery.isLoading}
|
||||
prefix={<UserOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Link>
|
||||
</Col>
|
||||
<Col xs={12} md={12} lg={6}>
|
||||
) : (
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title={t("home.quick_stats.total_weight")}
|
||||
value={formatWeight(totalWeight)}
|
||||
loading={spoolsQuery.isLoading}
|
||||
prefix={<DatabaseOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} md={12} lg={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title={t("home.quick_stats.total_value")}
|
||||
value={currencyFormatter.format(totalValue)}
|
||||
loading={spoolsQuery.isLoading}
|
||||
prefix={<DollarOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} md={12} lg={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title={t("home.quick_stats.total_spent")}
|
||||
value={currencyFormatter.format(costStats?.total_spent ?? 0)}
|
||||
loading={costLoading}
|
||||
prefix={<DollarOutlined />}
|
||||
/>
|
||||
</Card>
|
||||
</Col>
|
||||
<Col xs={12} md={12} lg={6}>
|
||||
<Card size="small">
|
||||
<Statistic
|
||||
title={t("home.quick_stats.avg_cost_per_kg")}
|
||||
value={costStats?.avg_cost_per_kg != null ? currencyFormatter.format(costStats.avg_cost_per_kg) : "-"}
|
||||
loading={costLoading}
|
||||
prefix={<RiseOutlined />}
|
||||
/>
|
||||
<Statistic title={stat.title} value={stat.value} loading={stat.loading} prefix={stat.prefix} />
|
||||
</Card>
|
||||
)}
|
||||
</Col>
|
||||
))}
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,8 +112,13 @@ export const Home: React.FC<IResourceComponentsProps> = () => {
|
||||
fontFamily: token.fontFamily,
|
||||
}}
|
||||
>
|
||||
{/* Header with spool count and add button */}
|
||||
<Row justify="space-between" align="middle" style={{ marginBottom: 16 }}>
|
||||
{/* Top section: Pie chart + stats/alerts side by side */}
|
||||
<Row gutter={16} style={{ marginBottom: 16 }}>
|
||||
<Col xs={24} md={10}>
|
||||
<MaterialDistributionChart square />
|
||||
</Col>
|
||||
<Col xs={24} md={14}>
|
||||
<Row justify="space-between" align="middle" style={{ marginBottom: 12 }}>
|
||||
<Col>
|
||||
<Statistic
|
||||
title={t("spool.spool")}
|
||||
@@ -128,21 +133,17 @@ export const Home: React.FC<IResourceComponentsProps> = () => {
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* Alert badges */}
|
||||
<AlertCards />
|
||||
|
||||
{/* Material distribution chart */}
|
||||
<MaterialDistributionChart />
|
||||
|
||||
{/* Usage analytics */}
|
||||
<UsageAnalytics />
|
||||
<QuickStats compact />
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* Spool list */}
|
||||
<Card
|
||||
title={t("home.all_spools")}
|
||||
extra={<Link to="/spool">{t("buttons.viewAll")}</Link>}
|
||||
styles={{ body: { padding: 0 } }}
|
||||
style={{ marginBottom: 16 }}
|
||||
>
|
||||
<Table<ISpool>
|
||||
dataSource={spoolsResult?.data as ISpool[]}
|
||||
@@ -168,8 +169,8 @@ export const Home: React.FC<IResourceComponentsProps> = () => {
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{/* Quick stats */}
|
||||
<QuickStats />
|
||||
{/* Usage analytics & insights */}
|
||||
<UsageAnalytics />
|
||||
</Content>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user