feat: Add batch operations, dashboard redesign, hierarchical locations
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

## Batch Operations (#13)
- Add POST /spool/bulk/archive, /bulk/delete, /bulk/update endpoints
- Add row selection with checkboxes to spool list
- Add floating BatchActionBar with archive/unarchive/delete/move actions
- Add confirmation modals for all batch operations

## Dashboard Redesign (#12)
- Install recharts for pie charts
- Add AlertCards (low stock, pending jobs, needs weighing)
- Add MaterialDistributionChart (pie chart by material)
- Add QuickStats row (total spools, filaments, vendors, weight)
- Refactor home page layout with new components

## Hierarchical Locations (#9)
- Add Location model with self-referential parent relationship
- Add migration for location table and spool.location_id FK
- Add location database operations (CRUD, tree, full path)
- Add location API endpoints (list, tree, CRUD)
- Add LocationTreeSelect component with nested tree display
- Add LocationCreateModal for inline location creation
- Keep legacy location string field for backward compatibility

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-21 21:40:01 -06:00
parent 27757bb949
commit 02da984b6e
18 changed files with 1683 additions and 90 deletions

View File

@@ -0,0 +1,93 @@
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
];
export function MaterialDistributionChart() {
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={{ marginBottom: 16 }}
>
<ResponsiveContainer width="100%" height={250}>
<PieChart>
<Pie
data={chartData}
cx="50%"
cy="50%"
innerRadius={50}
outerRadius={80}
paddingAngle={2}
dataKey="value"
label={({ name, percent }) => `${name} (${((percent ?? 0) * 100).toFixed(0)}%)`}
labelLine={{ 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,
}}
/>
</PieChart>
</ResponsiveContainer>
</Card>
);
}