Show external filaments in spool create
This commit is contained in:
@@ -6,7 +6,7 @@ import dayjs from "dayjs";
|
|||||||
import TextArea from "antd/es/input/TextArea";
|
import TextArea from "antd/es/input/TextArea";
|
||||||
import { IFilament } from "../filaments/model";
|
import { IFilament } from "../filaments/model";
|
||||||
import { ISpool, ISpoolParsedExtras, WeightToEnter } from "./model";
|
import { ISpool, ISpoolParsedExtras, WeightToEnter } from "./model";
|
||||||
import { numberFormatter, numberParser } from "../../utils/parsing";
|
import { formatLength, formatWeight, numberFormatter, numberParser } from "../../utils/parsing";
|
||||||
import { useSpoolmanLocations } from "../../components/otherModels";
|
import { useSpoolmanLocations } from "../../components/otherModels";
|
||||||
import { MinusOutlined, PlusOutlined } from "@ant-design/icons";
|
import { MinusOutlined, PlusOutlined } from "@ant-design/icons";
|
||||||
import "../../utils/overrides.css";
|
import "../../utils/overrides.css";
|
||||||
@@ -14,7 +14,7 @@ import { EntityType, useGetFields } from "../../utils/queryFields";
|
|||||||
import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields";
|
import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields";
|
||||||
import utc from "dayjs/plugin/utc";
|
import utc from "dayjs/plugin/utc";
|
||||||
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
|
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
|
||||||
import { ValueType } from "rc-input-number";
|
import { useGetExternalDBFilaments } from "../../utils/queryExternalDB";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
@@ -22,10 +22,37 @@ interface CreateOrCloneProps {
|
|||||||
mode: "create" | "clone";
|
mode: "create" | "clone";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatFilamentLabel(name: string, diameter: number, vendorName?: string, material?: string, weight?: number) {
|
||||||
|
const portions = [];
|
||||||
|
if (vendorName) {
|
||||||
|
portions.push(vendorName);
|
||||||
|
}
|
||||||
|
portions.push(name);
|
||||||
|
const extras = [];
|
||||||
|
if (material) {
|
||||||
|
extras.push(material);
|
||||||
|
}
|
||||||
|
extras.push(formatLength(diameter));
|
||||||
|
if (weight) {
|
||||||
|
extras.push(formatWeight(weight));
|
||||||
|
}
|
||||||
|
return `${portions.join(" - ")} (${extras.join(" ")})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Performs a case-insensitive search for the given query in the given string.
|
||||||
|
* The query is broken down into words and the search is performed on each word.
|
||||||
|
*/
|
||||||
|
function selectSearchMatches(query: string, test: string): boolean {
|
||||||
|
const words = query.toLowerCase().split(" ");
|
||||||
|
return words.every((word) => test.toLowerCase().includes(word));
|
||||||
|
}
|
||||||
|
|
||||||
export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => {
|
export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps> = (props) => {
|
||||||
const t = useTranslate();
|
const t = useTranslate();
|
||||||
const extraFields = useGetFields(EntityType.spool);
|
const extraFields = useGetFields(EntityType.spool);
|
||||||
const currency = useCurrency();
|
const currency = useCurrency();
|
||||||
|
const externalFilaments = useGetExternalDBFilaments();
|
||||||
|
|
||||||
const { form, formProps, formLoading, onFinish, redirect } = useForm<
|
const { form, formProps, formLoading, onFinish, redirect } = useForm<
|
||||||
ISpool,
|
ISpool,
|
||||||
@@ -74,7 +101,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
|||||||
redirect(redirectTo, (values as ISpool).id);
|
redirect(redirectTo, (values as ISpool).id);
|
||||||
};
|
};
|
||||||
|
|
||||||
const { queryResult } = useSelect<IFilament>({
|
const { queryResult: filamentsQuery } = useSelect<IFilament>({
|
||||||
resource: "filament",
|
resource: "filament",
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -89,40 +116,55 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
|||||||
});
|
});
|
||||||
}, [form, extraFields.data, formProps.initialValues]);
|
}, [form, extraFields.data, formProps.initialValues]);
|
||||||
|
|
||||||
const filamentOptions = queryResult.data?.data.map((item) => {
|
const filamentSelectInternal = filamentsQuery.data?.data.map((item) => {
|
||||||
let vendorPrefix = "";
|
|
||||||
if (item.vendor) {
|
|
||||||
vendorPrefix = `${item.vendor.name} - `;
|
|
||||||
}
|
|
||||||
let name = item.name;
|
|
||||||
if (!name) {
|
|
||||||
name = `ID: ${item.id}`;
|
|
||||||
}
|
|
||||||
let material = "";
|
|
||||||
if (item.material) {
|
|
||||||
material = ` - ${item.material}`;
|
|
||||||
}
|
|
||||||
const label = `${vendorPrefix}${name}${material}`;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
label: label,
|
label: formatFilamentLabel(
|
||||||
|
item.name ?? `ID ${item.id}`,
|
||||||
|
item.diameter,
|
||||||
|
item.vendor?.name,
|
||||||
|
item.material,
|
||||||
|
item.weight
|
||||||
|
),
|
||||||
value: item.id,
|
value: item.id,
|
||||||
weight: item.weight,
|
weight: item.weight,
|
||||||
spool_weight: item.spool_weight,
|
spool_weight: item.spool_weight,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
filamentOptions?.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }));
|
filamentSelectInternal?.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }));
|
||||||
|
|
||||||
|
const filamentSelectExternal = externalFilaments.data?.map((item) => {
|
||||||
|
return {
|
||||||
|
label: formatFilamentLabel(item.name, item.diameter, item.manufacturer, item.material, item.weight),
|
||||||
|
value: item.id,
|
||||||
|
weight: item.weight,
|
||||||
|
spool_weight: item.spool_weight,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
filamentSelectExternal?.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }));
|
||||||
|
|
||||||
|
const filamentSelectOptions = [
|
||||||
|
{
|
||||||
|
title: "Internal",
|
||||||
|
label: <span>Internal</span>,
|
||||||
|
options: filamentSelectInternal ?? [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "External",
|
||||||
|
label: <span>External</span>,
|
||||||
|
options: filamentSelectExternal ?? [],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const [weightToEnter, setWeightToEnter] = useState(1);
|
const [weightToEnter, setWeightToEnter] = useState(1);
|
||||||
const [usedWeight, setUsedWeight] = useState(0);
|
const [usedWeight, setUsedWeight] = useState(0);
|
||||||
|
|
||||||
const selectedFilamentID = Form.useWatch("filament_id", form);
|
const selectedFilamentID = Form.useWatch("filament_id", form);
|
||||||
const selectedFilament = filamentOptions?.find((obj) => {
|
const selectedFilament = filamentSelectInternal?.find((obj) => {
|
||||||
return obj.value === selectedFilamentID;
|
return obj.value === selectedFilamentID;
|
||||||
});
|
});
|
||||||
|
|
||||||
const filamentChange = (newID: number) => {
|
const filamentChange = (newID: number) => {
|
||||||
const newSelectedFilament = filamentOptions?.find((obj) => {
|
const newSelectedFilament = filamentSelectInternal?.find((obj) => {
|
||||||
return obj.value === newID;
|
return obj.value === newID;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -296,10 +338,10 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
options={filamentOptions}
|
options={filamentSelectOptions}
|
||||||
showSearch
|
showSearch
|
||||||
filterOption={(input, option) =>
|
filterOption={(input, option) =>
|
||||||
typeof option?.label === "string" && option?.label.toLowerCase().includes(input.toLowerCase())
|
typeof option?.label === "string" && selectSearchMatches(input, option?.label)
|
||||||
}
|
}
|
||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
filamentChange(value);
|
filamentChange(value);
|
||||||
|
|||||||
@@ -84,3 +84,35 @@ export function enrichText(text: string | undefined) {
|
|||||||
|
|
||||||
return <>{elements}</>;
|
return <>{elements}</>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the weight in grams to either kilograms or grams based on the provided precision.
|
||||||
|
*
|
||||||
|
* @param {number} weightInGrams - The weight in grams to be formatted.
|
||||||
|
* @param {number} [precision=2] - The precision of the formatting (number of decimal places).
|
||||||
|
* @return {string} The formatted weight with the appropriate unit (kg or g).
|
||||||
|
*/
|
||||||
|
export function formatWeight(weightInGrams: number, precision: number = 2): string {
|
||||||
|
if (weightInGrams >= 1000) {
|
||||||
|
const kilograms = (weightInGrams / 1000).toFixed(precision).replace(/\.?0+$/, ""); // Remove trailing zeros
|
||||||
|
return `${kilograms} kg`;
|
||||||
|
} else {
|
||||||
|
return `${weightInGrams} g`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats the length in millimeters to either meters or millimeters based on the provided precision.
|
||||||
|
*
|
||||||
|
* @param {number} lengthInMillimeter - The length in millimeters to be formatted.
|
||||||
|
* @param {number} [precision=2] - The precision of the formatting (number of decimal places).
|
||||||
|
* @return {string} The formatted length with the appropriate unit (m or mm).
|
||||||
|
*/
|
||||||
|
export function formatLength(lengthInMillimeter: number, precision: number = 2): string {
|
||||||
|
if (lengthInMillimeter >= 1000) {
|
||||||
|
const meters = (lengthInMillimeter / 1000).toFixed(precision).replace(/\.?0+$/, ""); // Remove trailing zeros
|
||||||
|
return `${meters} m`;
|
||||||
|
} else {
|
||||||
|
return `${lengthInMillimeter} mm`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
43
client/src/utils/queryExternalDB.ts
Normal file
43
client/src/utils/queryExternalDB.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { getAPIURL } from "./url";
|
||||||
|
|
||||||
|
interface ExternalFilament {
|
||||||
|
id: string;
|
||||||
|
manufacturer: string;
|
||||||
|
name: string;
|
||||||
|
material: string;
|
||||||
|
density: number | null;
|
||||||
|
weight: number;
|
||||||
|
spool_weight: number | null;
|
||||||
|
diameter: number;
|
||||||
|
color_hex: string;
|
||||||
|
extruder_temp: number | null;
|
||||||
|
bed_temp: number | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExternalMaterial {
|
||||||
|
material: string;
|
||||||
|
density: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGetExternalDBFilaments() {
|
||||||
|
return useQuery<ExternalFilament[]>({
|
||||||
|
queryKey: ["external", "filaments"],
|
||||||
|
staleTime: 60,
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(`${getAPIURL()}/external/filament`);
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useGetExternalDBMaterials() {
|
||||||
|
return useQuery<ExternalMaterial[]>({
|
||||||
|
queryKey: ["external", "materials"],
|
||||||
|
staleTime: 60,
|
||||||
|
queryFn: async () => {
|
||||||
|
const response = await fetch(`${getAPIURL()}/external/material`);
|
||||||
|
return response.json();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user