Files
spoolman2/client/src/pages/home/components/MaterialDistributionChart.tsx
tonym f38baa985e
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
feat: Rearrange dashboard - pie chart + stats on top, spools middle, analytics bottom
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-22 23:32:03 -06:00

106 lines
3.2 KiB
TypeScript

import { useList, useTranslate } from "@refinedev/core";
import { Card, theme } from "antd";
import { useMemo } from "react";
import { PieChart, Pie, Cell, ResponsiveContainer, Tooltip, Legend } from "recharts";
import { ISpool } from "../../spools/model";
const { useToken } = theme;
// Color palette for the pie chart
const COLORS = [
"#1890ff", // blue
"#52c41a", // green
"#faad14", // gold
"#f5222d", // red
"#722ed1", // purple
"#13c2c2", // cyan
"#eb2f96", // magenta
"#fa8c16", // orange
"#a0d911", // lime
"#2f54eb", // geekblue
];
interface MaterialDistributionChartProps {
square?: boolean;
}
export function MaterialDistributionChart({ square }: MaterialDistributionChartProps) {
const t = useTranslate();
const { token } = useToken();
// Fetch all spools for aggregation
const { result: spoolsResult, query: spoolsQuery } = useList<ISpool>({
resource: "spool",
pagination: { pageSize: 1000 },
meta: {
queryParams: {
allow_archived: false,
},
},
});
// Aggregate by material
const chartData = useMemo(() => {
const data = spoolsResult?.data || [];
const materialCounts: Record<string, number> = {};
data.forEach((spool) => {
const material = spool.filament.material || t("unknown");
materialCounts[material] = (materialCounts[material] || 0) + 1;
});
return Object.entries(materialCounts)
.map(([name, value]) => ({ name, value }))
.sort((a, b) => b.value - a.value)
.slice(0, 10); // Top 10 materials
}, [spoolsResult?.data, t]);
if (spoolsQuery.isLoading || chartData.length === 0) {
return null;
}
return (
<Card
title={t("home.charts.material_distribution")}
size="small"
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={square ? "100%" : 250}>
<PieChart>
<Pie
data={chartData}
cx="50%"
cy="50%"
innerRadius={square ? 40 : 50}
outerRadius={square ? 70 : 80}
paddingAngle={2}
dataKey="value"
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]} />
))}
</Pie>
<Tooltip
formatter={(value) => [`${value} spools`, t("spool.spool")]}
contentStyle={{
backgroundColor: token.colorBgElevated,
border: `1px solid ${token.colorBorder}`,
borderRadius: token.borderRadius,
}}
/>
{square && (
<Legend
layout="horizontal"
verticalAlign="bottom"
wrapperStyle={{ fontSize: 11 }}
/>
)}
</PieChart>
</ResponsiveContainer>
</Card>
);
}