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>
106 lines
3.2 KiB
TypeScript
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>
|
|
);
|
|
}
|