Create internal filament from external flow done
This commit is contained in:
38
client/src/pages/filaments/functions.ts
Normal file
38
client/src/pages/filaments/functions.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { ExternalFilament } from "../../utils/queryExternalDB";
|
||||
import { getAPIURL } from "../../utils/url";
|
||||
import { getOrCreateVendorFromExternal } from "../vendors/functions";
|
||||
import { IFilament } from "./model";
|
||||
|
||||
/**
|
||||
* Create a new internal filament given an external filament object.
|
||||
* Returns the created internal filament.
|
||||
*/
|
||||
export async function createFilamentFromExternal(externalFilament: ExternalFilament): Promise<IFilament> {
|
||||
const vendor = await getOrCreateVendorFromExternal(externalFilament.manufacturer);
|
||||
|
||||
const body: Omit<IFilament, "id" | "registered" | "extra"> & { vendor_id: number } = {
|
||||
name: externalFilament.name,
|
||||
material: externalFilament.material,
|
||||
vendor_id: vendor.id,
|
||||
density: externalFilament.density,
|
||||
diameter: externalFilament.diameter,
|
||||
weight: externalFilament.weight,
|
||||
spool_weight: externalFilament.spool_weight || undefined,
|
||||
color_hex: externalFilament.color_hex,
|
||||
settings_extruder_temp: externalFilament.extruder_temp || undefined,
|
||||
settings_bed_temp: externalFilament.bed_temp || undefined,
|
||||
external_id: externalFilament.id,
|
||||
};
|
||||
|
||||
const response = await fetch(getAPIURL() + "/filament", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
@@ -16,6 +16,7 @@ export interface IFilament {
|
||||
settings_extruder_temp?: number;
|
||||
settings_bed_temp?: number;
|
||||
color_hex?: string;
|
||||
external_id?: string;
|
||||
extra: { [key: string]: string };
|
||||
}
|
||||
|
||||
|
||||
@@ -143,6 +143,8 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
|
||||
)}
|
||||
<Title level={5}>{t("filament.fields.article_number")}</Title>
|
||||
<TextField value={record?.article_number} />
|
||||
<Title level={5}>{t("filament.fields.external_id")}</Title>
|
||||
<TextField value={record?.external_id} />
|
||||
<Title level={5}>{t("filament.fields.comment")}</Title>
|
||||
<TextField value={enrichText(record?.comment)} />
|
||||
<Title level={4}>{t("settings.extra_fields.tab")}</Title>
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
import React, { useState } from "react";
|
||||
import { HttpError, IResourceComponentsProps, useTranslate } from "@refinedev/core";
|
||||
import { Create, useForm, useSelect } from "@refinedev/antd";
|
||||
import { Form, Input, DatePicker, Select, InputNumber, Radio, Divider, Button, Typography } from "antd";
|
||||
import {
|
||||
Form,
|
||||
Input,
|
||||
DatePicker,
|
||||
Select,
|
||||
InputNumber,
|
||||
Radio,
|
||||
Divider,
|
||||
Button,
|
||||
Typography,
|
||||
ConfigProvider,
|
||||
Alert,
|
||||
Space,
|
||||
} from "antd";
|
||||
import dayjs from "dayjs";
|
||||
import TextArea from "antd/es/input/TextArea";
|
||||
import { IFilament } from "../filaments/model";
|
||||
@@ -14,7 +27,8 @@ import { EntityType, useGetFields } from "../../utils/queryFields";
|
||||
import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import { getCurrencySymbol, useCurrency } from "../../utils/settings";
|
||||
import { useGetExternalDBFilaments } from "../../utils/queryExternalDB";
|
||||
import { ExternalFilament, useGetExternalDBFilaments } from "../../utils/queryExternalDB";
|
||||
import { createFilamentFromExternal } from "../filaments/functions";
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
@@ -22,6 +36,10 @@ interface CreateOrCloneProps {
|
||||
mode: "create" | "clone";
|
||||
}
|
||||
|
||||
type ISpoolRequest = Omit<ISpoolParsedExtras, "id" | "registered"> & {
|
||||
filament_id: number | string;
|
||||
};
|
||||
|
||||
function formatFilamentLabel(name: string, diameter: number, vendorName?: string, material?: string, weight?: number) {
|
||||
const portions = [];
|
||||
if (vendorName) {
|
||||
@@ -36,7 +54,7 @@ function formatFilamentLabel(name: string, diameter: number, vendorName?: string
|
||||
if (weight) {
|
||||
extras.push(formatWeight(weight));
|
||||
}
|
||||
return `${portions.join(" - ")} (${extras.join(" ")})`;
|
||||
return `${portions.join(" - ")} (${extras.join(", ")})`;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,7 +75,7 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
||||
const { form, formProps, formLoading, onFinish, redirect } = useForm<
|
||||
ISpool,
|
||||
HttpError,
|
||||
ISpoolParsedExtras,
|
||||
ISpoolRequest,
|
||||
ISpoolParsedExtras
|
||||
>({
|
||||
redirect: false,
|
||||
@@ -91,6 +109,17 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
||||
|
||||
const handleSubmit = async (redirectTo: "list" | "edit" | "create") => {
|
||||
const values = StringifiedExtras(await form.validateFields());
|
||||
if (typeof values.filament_id === "string") {
|
||||
// Filament ID being a string indicates its an external filament.
|
||||
// If so, we should first create the internal filament version, then create the spool(s)
|
||||
const externalFilament = externalFilaments.data?.find((f) => f.id === values.filament_id);
|
||||
if (!externalFilament) {
|
||||
throw new Error("Unknown external filament");
|
||||
}
|
||||
const internalFilament = await createFilamentFromExternal(externalFilament);
|
||||
values.filament_id = internalFilament.id;
|
||||
}
|
||||
|
||||
if (quantity > 1) {
|
||||
const submit = Array(quantity).fill(values);
|
||||
// queue multiple creates this way for now Refine doesn't seem to map Arrays to createMany or multiple creates like it says it does
|
||||
@@ -98,7 +127,8 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
||||
} else {
|
||||
await onFinish(values);
|
||||
}
|
||||
redirect(redirectTo, (values as ISpool).id);
|
||||
|
||||
redirect(redirectTo);
|
||||
};
|
||||
|
||||
const { queryResult: filamentsQuery } = useSelect<IFilament>({
|
||||
@@ -116,7 +146,19 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
||||
});
|
||||
}, [form, extraFields.data, formProps.initialValues]);
|
||||
|
||||
const filamentSelectInternal = filamentsQuery.data?.data.map((item) => {
|
||||
//
|
||||
// Set up the filament selection options
|
||||
//
|
||||
|
||||
interface SelectOption {
|
||||
label: string;
|
||||
value: string | number;
|
||||
weight?: number;
|
||||
spool_weight?: number;
|
||||
is_internal: boolean;
|
||||
}
|
||||
|
||||
const filamentSelectInternal: SelectOption[] | undefined = filamentsQuery.data?.data.map((item) => {
|
||||
return {
|
||||
label: formatFilamentLabel(
|
||||
item.name ?? `ID ${item.id}`,
|
||||
@@ -128,61 +170,71 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
||||
value: item.id,
|
||||
weight: item.weight,
|
||||
spool_weight: item.spool_weight,
|
||||
is_internal: true,
|
||||
};
|
||||
});
|
||||
filamentSelectInternal?.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }));
|
||||
|
||||
const filamentSelectExternal = externalFilaments.data?.map((item) => {
|
||||
const filamentSelectExternal: SelectOption[] | undefined = 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,
|
||||
spool_weight: item.spool_weight || undefined,
|
||||
is_internal: false,
|
||||
};
|
||||
});
|
||||
filamentSelectExternal?.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" }));
|
||||
|
||||
const filamentSelectOptions = [
|
||||
{
|
||||
title: "Internal",
|
||||
label: <span>Internal</span>,
|
||||
label: <span>{t("spool.fields.filament_internal")}</span>,
|
||||
options: filamentSelectInternal ?? [],
|
||||
},
|
||||
{
|
||||
title: "External",
|
||||
label: <span>External</span>,
|
||||
label: <span>{t("spool.fields.filament_external")}</span>,
|
||||
options: filamentSelectExternal ?? [],
|
||||
},
|
||||
];
|
||||
|
||||
const selectedFilamentID: number | string | undefined = Form.useWatch("filament_id", form);
|
||||
let selectedFilament: SelectOption | null = null;
|
||||
// selectedFilamentID is a number of it's an internal filament, and a string of it's an external filament.
|
||||
if (typeof selectedFilamentID === "number") {
|
||||
selectedFilament =
|
||||
filamentSelectInternal?.find((obj) => {
|
||||
return obj.value === selectedFilamentID;
|
||||
}) ?? null;
|
||||
} else if (typeof selectedFilamentID === "string") {
|
||||
selectedFilament =
|
||||
filamentSelectExternal?.find((obj) => {
|
||||
return obj.value === selectedFilamentID;
|
||||
}) ?? null;
|
||||
}
|
||||
|
||||
//
|
||||
// Weight calculations
|
||||
//
|
||||
|
||||
const [weightToEnter, setWeightToEnter] = useState(1);
|
||||
const [usedWeight, setUsedWeight] = useState(0);
|
||||
|
||||
const selectedFilamentID = Form.useWatch("filament_id", form);
|
||||
const selectedFilament = filamentSelectInternal?.find((obj) => {
|
||||
return obj.value === selectedFilamentID;
|
||||
});
|
||||
|
||||
const filamentChange = (newID: number) => {
|
||||
const newSelectedFilament = filamentSelectInternal?.find((obj) => {
|
||||
return obj.value === newID;
|
||||
});
|
||||
|
||||
React.useEffect(() => {
|
||||
const initial_weight = initialWeightValue ?? 0;
|
||||
const spool_weight = spoolWeightValue ?? 0;
|
||||
|
||||
const newFilamentWeight = newSelectedFilament?.weight || 0;
|
||||
const newSpoolWeight = newSelectedFilament?.spool_weight || 0;
|
||||
const newFilamentWeight = selectedFilament?.weight || 0;
|
||||
const newSpoolWeight = selectedFilament?.spool_weight || 0;
|
||||
|
||||
const currentCalculatedFilamentWeight = getTotalWeightFromFilament();
|
||||
if ((initial_weight === 0 || initial_weight === currentCalculatedFilamentWeight) && newFilamentWeight > 0) {
|
||||
const currentCalculatedFilamentWeight = selectedFilament ? calcTotalWeight(selectedFilament) : 0;
|
||||
if (newFilamentWeight > 0) {
|
||||
form.setFieldValue("initial_weight", newFilamentWeight);
|
||||
}
|
||||
|
||||
if ((spool_weight === 0 || spool_weight === (selectedFilament?.spool_weight ?? 0)) && newSpoolWeight > 0) {
|
||||
if (newSpoolWeight > 0) {
|
||||
form.setFieldValue("spool_weight", newSpoolWeight);
|
||||
}
|
||||
};
|
||||
}, [selectedFilament]);
|
||||
|
||||
const weightChange = (weight: number) => {
|
||||
setUsedWeight(weight);
|
||||
@@ -222,9 +274,9 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
||||
return net_weight + spool_weight;
|
||||
};
|
||||
|
||||
const getTotalWeightFromFilament = (): number => {
|
||||
return (selectedFilament?.weight ?? 0) + (selectedFilament?.spool_weight ?? 0);
|
||||
};
|
||||
function calcTotalWeight(filament: SelectOption): number {
|
||||
return (filament.weight ?? 0) + (filament.spool_weight ?? 0);
|
||||
}
|
||||
|
||||
const getMeasuredWeight = (): number => {
|
||||
const grossWeight = getGrossWeight();
|
||||
@@ -343,11 +395,11 @@ export const SpoolCreate: React.FC<IResourceComponentsProps & CreateOrCloneProps
|
||||
filterOption={(input, option) =>
|
||||
typeof option?.label === "string" && selectSearchMatches(input, option?.label)
|
||||
}
|
||||
onChange={(value) => {
|
||||
filamentChange(value);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
{selectedFilament?.is_internal === false && (
|
||||
<Alert message={t("spool.fields_help.external_filament")} type="info" />
|
||||
)}
|
||||
<Form.Item
|
||||
label={t("spool.fields.price")}
|
||||
help={t("spool.fields_help.price")}
|
||||
|
||||
48
client/src/pages/vendors/functions.ts
vendored
Normal file
48
client/src/pages/vendors/functions.ts
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
import { getAPIURL } from "../../utils/url";
|
||||
import { IVendor } from "./model";
|
||||
|
||||
/**
|
||||
* Get a vendor given its external ID.
|
||||
*/
|
||||
export async function getVendorByExternalID(external_id: string): Promise<IVendor | null> {
|
||||
// Make a search using GET and query params
|
||||
const response = await fetch(`${getAPIURL()}/vendor?${new URLSearchParams({ external_id })}`);
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data: IVendor[] = await response.json();
|
||||
if (data.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return data[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new internal filament given an external filament object.
|
||||
* Returns the created internal filament.
|
||||
*/
|
||||
export async function getOrCreateVendorFromExternal(vendor_external_id: string): Promise<IVendor> {
|
||||
const existingVendor = await getVendorByExternalID(vendor_external_id);
|
||||
if (existingVendor) {
|
||||
return existingVendor;
|
||||
}
|
||||
|
||||
const body: Omit<IVendor, "id" | "registered" | "extra"> = {
|
||||
name: vendor_external_id,
|
||||
external_id: vendor_external_id,
|
||||
};
|
||||
|
||||
const response = await fetch(getAPIURL() + "/vendor", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Network response was not ok");
|
||||
}
|
||||
return response.json();
|
||||
}
|
||||
1
client/src/pages/vendors/model.tsx
vendored
1
client/src/pages/vendors/model.tsx
vendored
@@ -4,6 +4,7 @@ export interface IVendor {
|
||||
name: string;
|
||||
comment?: string;
|
||||
empty_spool_weight?: number;
|
||||
external_id?: string;
|
||||
extra: { [key: string]: string };
|
||||
}
|
||||
|
||||
|
||||
2
client/src/pages/vendors/show.tsx
vendored
2
client/src/pages/vendors/show.tsx
vendored
@@ -44,6 +44,8 @@ export const VendorShow: React.FC<IResourceComponentsProps> = () => {
|
||||
<TextField value={enrichText(record?.comment)} />
|
||||
<Title level={5}>{t("vendor.fields.empty_spool_weight")}</Title>
|
||||
<TextField value={record?.empty_spool_weight} />
|
||||
<Title level={5}>{t("vendor.fields.external_id")}</Title>
|
||||
<TextField value={record?.external_id} />
|
||||
<Title level={4}>{t("settings.extra_fields.tab")}</Title>
|
||||
{extraFields?.data?.map((field, index) => (
|
||||
<ExtraFieldDisplay key={index} field={field} value={record?.extra[field.key]} />
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||
import { getAPIURL } from "./url";
|
||||
|
||||
interface ExternalFilament {
|
||||
export interface ExternalFilament {
|
||||
id: string;
|
||||
manufacturer: string;
|
||||
name: string;
|
||||
material: string;
|
||||
density: number | null;
|
||||
density: number;
|
||||
weight: number;
|
||||
spool_weight: number | null;
|
||||
diameter: number;
|
||||
@@ -15,7 +15,7 @@ interface ExternalFilament {
|
||||
bed_temp: number | null;
|
||||
}
|
||||
|
||||
interface ExternalMaterial {
|
||||
export interface ExternalMaterial {
|
||||
material: string;
|
||||
density: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user