External filament select for edit spool form

This commit is contained in:
Donkie
2024-05-12 22:08:47 +02:00
parent 4598d8f520
commit 55471062ab
3 changed files with 60 additions and 74 deletions

View File

@@ -30,6 +30,7 @@ import { getCurrencySymbol, useCurrency } from "../../utils/settings";
import { ExternalFilament, useGetExternalDBFilaments } from "../../utils/queryExternalDB"; import { ExternalFilament, useGetExternalDBFilaments } from "../../utils/queryExternalDB";
import { createFilamentFromExternal } from "../filaments/functions"; import { createFilamentFromExternal } from "../filaments/functions";
import { formatFilamentLabel, useGetFilamentSelectOptions } from "./functions"; import { formatFilamentLabel, useGetFilamentSelectOptions } from "./functions";
import { searchMatches } from "../../utils/filtering";
dayjs.extend(utc); dayjs.extend(utc);
@@ -41,15 +42,6 @@ type ISpoolRequest = Omit<ISpoolParsedExtras, "id" | "registered"> & {
filament_id: number | string; filament_id: number | string;
}; };
/**
* 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);
@@ -308,9 +300,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
<Select <Select
options={filamentOptions} options={filamentOptions}
showSearch showSearch
filterOption={(input, option) => filterOption={(input, option) => typeof option?.label === "string" && searchMatches(input, option?.label)}
typeof option?.label === "string" && selectSearchMatches(input, option?.label)
}
/> />
</Form.Item> </Form.Item>
{selectedFilament?.is_internal === false && ( {selectedFilament?.is_internal === false && (

View File

@@ -13,6 +13,9 @@ import { EntityType, useGetFields } from "../../utils/queryFields";
import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields"; import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields";
import { ParsedExtras } from "../../components/extraFields"; import { ParsedExtras } from "../../components/extraFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings"; import { getCurrencySymbol, useCurrency } from "../../utils/settings";
import { useGetFilamentSelectOptions } from "./functions";
import { searchMatches } from "../../utils/filtering";
import { createFilamentFromExternal } from "../filaments/functions";
/* /*
The API returns the extra fields as JSON values, but we need to parse them into their real types The API returns the extra fields as JSON values, but we need to parse them into their real types
@@ -21,6 +24,10 @@ We also need to stringify them again before sending them back to the API, which
the form's onFinish method. Form.Item's normalize should do this, but it doesn't seem to work. the form's onFinish method. Form.Item's normalize should do this, but it doesn't seem to work.
*/ */
type ISpoolRequest = ISpoolParsedExtras & {
filament_id: number | string;
};
export const SpoolEdit: React.FC<IResourceComponentsProps> = () => { export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
const t = useTranslate(); const t = useTranslate();
const [messageApi, contextHolder] = message.useMessage(); const [messageApi, contextHolder] = message.useMessage();
@@ -28,7 +35,7 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
const extraFields = useGetFields(EntityType.spool); const extraFields = useGetFields(EntityType.spool);
const currency = useCurrency(); const currency = useCurrency();
const { form, formProps, saveButtonProps } = useForm<ISpool, HttpError, ISpool, ISpool>({ const { form, formProps, saveButtonProps } = useForm<ISpool, HttpError, ISpoolRequest, ISpool>({
liveMode: "manual", liveMode: "manual",
onLiveEvent() { onLiveEvent() {
// Warn the user if the spool has been updated since the form was opened // Warn the user if the spool has been updated since the form was opened
@@ -52,71 +59,57 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
formProps.initialValues = ParsedExtras(formProps.initialValues); formProps.initialValues = ParsedExtras(formProps.initialValues);
} }
//
// Set up the filament selection options
//
const { options: filamentOptions, getById, allExternalFilaments } = useGetFilamentSelectOptions();
const selectedFilamentID = Form.useWatch("filament_id", form);
const selectedFilament = getById(selectedFilamentID);
// Override the form's onFinish method to stringify the extra fields // Override the form's onFinish method to stringify the extra fields
const originalOnFinish = formProps.onFinish; const originalOnFinish = formProps.onFinish;
formProps.onFinish = (allValues: ISpoolParsedExtras) => { formProps.onFinish = (allValues: ISpoolRequest) => {
if (allValues !== undefined && allValues !== null) { if (allValues !== undefined && allValues !== null) {
// Lot of stupidity here to make types work // Lot of stupidity here to make types work
const stringifiedAllValues = StringifiedExtras<ISpoolParsedExtras>(allValues); const values = StringifiedExtras<ISpoolRequest>(allValues);
originalOnFinish?.({ const selectOption = getById(values.filament_id);
extra: {}, if (selectOption?.is_internal === false) {
...stringifiedAllValues, // Filament ID being a string indicates its an external filament.
}); // If so, we should first create the internal filament version, then edit the spool
const externalFilament = allExternalFilaments?.find((f) => f.id === values.filament_id);
if (!externalFilament) {
throw new Error("Unknown external filament");
}
createFilamentFromExternal(externalFilament).then((internalFilament) => {
values.filament_id = internalFilament.id;
originalOnFinish?.({
extra: {},
...values,
});
});
} else {
originalOnFinish?.({
extra: {},
...values,
});
}
} }
}; };
const filamentOptions = queryResult.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 {
label: label,
value: item.id,
weight: item.weight,
spool_weight: item.spool_weight,
};
});
filamentOptions?.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }));
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); React.useEffect(() => {
const selectedFilament = filamentOptions?.find((obj) => { const newFilamentWeight = selectedFilament?.weight || 0;
return obj.value === selectedFilamentID; const newSpoolWeight = selectedFilament?.spool_weight || 0;
}); if (newFilamentWeight > 0) {
const filamentChange = (newID: number) => {
const newSelectedFilament = filamentOptions?.find((obj) => {
return obj.value === newID;
});
const initial_weight = initialWeightValue ?? 0;
const spool_weight = spoolWeightValue ?? 0;
const newFilamentWeight = newSelectedFilament?.weight || 0;
const newSpoolWeight = newSelectedFilament?.spool_weight || 0;
const currentCalculatedFilamentWeight = getTotalWeightFromFilament();
if ((initial_weight === 0 || initial_weight === currentCalculatedFilamentWeight) && newFilamentWeight > 0) {
form.setFieldValue("initial_weight", newFilamentWeight); form.setFieldValue("initial_weight", newFilamentWeight);
} }
if (newSpoolWeight > 0) {
if ((spool_weight === 0 || spool_weight === (selectedFilament?.spool_weight ?? 0)) && newSpoolWeight > 0) {
form.setFieldValue("spool_weight", newSpoolWeight); form.setFieldValue("spool_weight", newSpoolWeight);
} }
}; }, [selectedFilament]);
const weightChange = (weight: number) => { const weightChange = (weight: number) => {
setUsedWeight(weight); setUsedWeight(weight);
@@ -147,10 +140,6 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
return net_weight + spool_weight; return net_weight + spool_weight;
}; };
const getTotalWeightFromFilament = (): number => {
return (selectedFilament?.weight ?? 0) + (selectedFilament?.spool_weight ?? 0);
};
const getMeasuredWeight = (): number => { const getMeasuredWeight = (): number => {
const grossWeight = getGrossWeight(); const grossWeight = getGrossWeight();
@@ -274,14 +263,12 @@ export const SpoolEdit: React.FC<IResourceComponentsProps> = () => {
<Select <Select
options={filamentOptions} options={filamentOptions}
showSearch showSearch
filterOption={(input, option) => filterOption={(input, option) => typeof option?.label === "string" && searchMatches(input, option?.label)}
typeof option?.label === "string" && option?.label.toLowerCase().includes(input.toLowerCase())
}
onChange={(value) => {
filamentChange(value);
}}
/> />
</Form.Item> </Form.Item>
{selectedFilament?.is_internal === false && (
<Alert message={t("spool.fields_help.external_filament")} type="info" />
)}
<Form.Item <Form.Item
label={t("spool.fields.price")} label={t("spool.fields.price")}
help={t("spool.fields_help.price")} help={t("spool.fields_help.price")}

View File

@@ -35,3 +35,12 @@ export function getFiltersForField<Obj, Field extends keyof Obj>(
export function removeUndefined<T>(array: (T | undefined)[]): T[] { export function removeUndefined<T>(array: (T | undefined)[]): T[] {
return array.filter((value) => value !== undefined) as T[]; return array.filter((value) => value !== undefined) as T[];
} }
/**
* 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.
*/
export function searchMatches(query: string, test: string): boolean {
const words = query.toLowerCase().split(" ");
return words.every((word) => test.toLowerCase().includes(word));
}