feat: Add quick wins batch (#42, #43, #25, #35, #20, #36, #39)
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

- #42: Allow spools heavier than theoretical max (remove weight clamps)
- #43: Show filament custom fields on spool detail page
- #25: Sort spools by custom fields and remaining weight
- #35: Global search box for spool list
- #20: Gallery view for spools (color grid with progress bars)
- #36: Spool-level color override with ColorPicker
- #39: Cost analytics endpoint, total spent & avg cost/kg stats
- Fix: Filament select refresh after inline creation
- Fix: Parse temperatures from filament comments

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-22 23:09:42 -06:00
parent 5f66302e73
commit 801d3da02a
18 changed files with 524 additions and 45 deletions

View File

@@ -1,5 +1,6 @@
import { DollarOutlined, HighlightOutlined, InboxOutlined, DatabaseOutlined, UserOutlined } from "@ant-design/icons";
import { DollarOutlined, HighlightOutlined, InboxOutlined, DatabaseOutlined, UserOutlined, RiseOutlined } from "@ant-design/icons";
import { useList, useTranslate } from "@refinedev/core";
import { useQuery } from "@tanstack/react-query";
import { Card, Col, Row, Statistic, theme } from "antd";
import { useMemo } from "react";
import { Link } from "react-router";
@@ -7,6 +8,7 @@ import { IFilament } from "../../filaments/model";
import { ISpool } from "../../spools/model";
import { IVendor } from "../../vendors/model";
import { useCurrencyFormatter } from "../../../utils/settings";
import { getAPIURL } from "../../../utils/url";
const { useToken } = theme;
@@ -48,6 +50,19 @@ export function QuickStats() {
return data.reduce((sum, spool) => sum + (spool.remaining_value ?? 0), 0);
}, [spoolsResult?.data]);
// Fetch cost stats from backend
const { data: costStats, isLoading: costLoading } = useQuery<{
total_spent: number;
total_weight_purchased: number;
avg_cost_per_kg: number | null;
}>({
queryKey: ["analytics", "cost"],
queryFn: async () => {
const res = await fetch(`${getAPIURL()}/analytics/cost`);
return res.json();
},
});
const formatWeight = (grams: number) => {
if (grams >= 1000) {
return `${(grams / 1000).toFixed(1)} kg`;
@@ -113,6 +128,26 @@ export function QuickStats() {
/>
</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 />}
/>
</Card>
</Col>
</Row>
);
}