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,16 +1,18 @@
import {
AppstoreOutlined,
EditOutlined,
EyeOutlined,
FilterOutlined,
InboxOutlined,
PlusSquareOutlined,
PrinterOutlined,
TableOutlined,
ToolOutlined,
ToTopOutlined,
} from "@ant-design/icons";
import { List, useTable } from "@refinedev/antd";
import { IResourceComponentsProps, useInvalidate, useNavigation, useTranslate } from "@refinedev/core";
import { Button, Dropdown, Modal, Table } from "antd";
import { Button, Dropdown, Input, Modal, Table } from "antd";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import { useCallback, useMemo, useState } from "react";
@@ -42,6 +44,7 @@ import { useCurrencyFormatter } from "../../utils/settings";
import { bulkArchiveSpools, bulkDeleteSpools, bulkUpdateSpools, setSpoolArchived, useSpoolAdjustModal } from "./functions";
import { ISpool } from "./model";
import { BatchActionBar } from "./components/BatchActionBar";
import { SpoolGallery } from "./components/SpoolGallery";
dayjs.extend(utc);
@@ -69,7 +72,7 @@ function collapseSpool(element: ISpool): ISpoolCollapsed {
"filament.combined_name": filament_name,
"filament.id": element.filament.id,
"filament.material": element.filament.material,
"filament.color_hex": element.filament.color_hex,
"filament.color_hex": element.color_hex ?? element.filament.color_hex,
};
}
@@ -122,6 +125,10 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
// State for the switch to show archived spools
const [showArchived, setShowArchived] = useSavedState("spoolList-showArchived", false);
// State for search and view mode
const [searchQuery, setSearchQuery] = useState("");
const [viewMode, setViewMode] = useState<"table" | "gallery">("table");
// State for batch selection
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
@@ -134,6 +141,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
meta: {
queryParams: {
["allow_archived"]: showArchived,
...(searchQuery ? { q: searchQuery } : {}),
},
},
syncWithLocation: false,
@@ -353,6 +361,12 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
>
{t("buttons.clearFilters")}
</Button>
<Button
icon={viewMode === "table" ? <AppstoreOutlined /> : <TableOutlined />}
onClick={() => setViewMode(viewMode === "table" ? "gallery" : "table")}
>
{viewMode === "table" ? t("buttons.galleryView") : t("buttons.tableView")}
</Button>
<Dropdown
trigger={["click"]}
menu={{
@@ -390,6 +404,18 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
)}
>
{spoolAdjustModal}
<Input.Search
placeholder={t("spool.search_placeholder")}
allowClear
onSearch={(value) => {
setSearchQuery(value);
setCurrentPage(1);
}}
style={{ marginBottom: 16, maxWidth: 400 }}
/>
{viewMode === "gallery" ? (
<SpoolGallery dataSource={dataSource} loading={tableProps.loading as boolean} />
) : (
<Table
{...tableProps}
sticky
@@ -427,12 +453,14 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
id: "filament.combined_name",
i18nkey: "spool.fields.filament_name",
color: (record: ISpoolCollapsed) =>
record.filament.multi_color_hexes
? {
colors: record.filament.multi_color_hexes.split(","),
vertical: record.filament.multi_color_direction === "longitudinal",
}
: record.filament.color_hex,
record.color_hex
? record.color_hex
: record.filament.multi_color_hexes
? {
colors: record.filament.multi_color_hexes.split(","),
vertical: record.filament.multi_color_direction === "longitudinal",
}
: record.filament.color_hex,
dataId: "filament.combined_name",
filterValueQuery: useSpoolmanFilamentFilter(),
}),
@@ -441,7 +469,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
id: "filament.color_hex",
i18nkey: "spool.fields.color",
filterValueQuery: useSpoolmanColors(),
colorGetter: (record: ISpoolCollapsed) => record.filament.color_hex,
colorGetter: (record: ISpoolCollapsed) => record.color_hex ?? record.filament.color_hex,
width: 60,
}),
FilteredQueryColumn({
@@ -485,6 +513,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
unit: "g",
maxDecimals: 0,
width: 110,
sorter: true,
}),
NumberColumn({
...commonProps,
@@ -494,6 +523,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
maxDecimals: 0,
defaultText: t("unknown"),
width: 110,
sorter: true,
}),
NumberColumn({
...commonProps,
@@ -502,6 +532,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
unit: "mm",
maxDecimals: 0,
width: 120,
sorter: true,
}),
NumberColumn({
...commonProps,
@@ -511,6 +542,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
maxDecimals: 0,
defaultText: t("unknown"),
width: 120,
sorter: true,
}),
FilteredQueryColumn({
...commonProps,
@@ -556,6 +588,7 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
ActionsColumn(t("table.actions"), actions),
])}
/>
)}
<BatchActionBar
selectedIds={selectedRowKeys as number[]}
onArchive={handleBatchArchive}