import { LeftOutlined, RightOutlined, ThunderboltOutlined } from "@ant-design/icons"; import { Edit, useForm, useSelect } from "@refinedev/antd"; import { HttpError, IResourceComponentsProps, useList, useTranslate } from "@refinedev/core"; import { Alert, Button, ColorPicker, DatePicker, Form, Input, InputNumber, message, Radio, Select, Space, Typography } from "antd"; import TextArea from "antd/es/input/TextArea"; import dayjs from "dayjs"; import React, { useEffect, useMemo, useState } from "react"; import { useNavigate } from "react-router"; import { ExtraFieldFormItem, ParsedExtras, StringifiedExtras } from "../../components/extraFields"; import { MultiColorPicker } from "../../components/multiColorPicker"; import { FilamentColorLookup } from "../../components/filamentColorLookup"; import { formatNumberOnUserInput, numberParser, numberParserAllowEmpty } from "../../utils/parsing"; import { EntityType, useGetFields } from "../../utils/queryFields"; import { getCurrencySymbol, useCurrency } from "../../utils/settings"; import { IVendor } from "../vendors/model"; import { IFilament, IFilamentParsedExtras } from "./model"; /** * Parse temperature ranges from a comment string. * Handles patterns like: * "extrude 190-230 / bed 50-70" * "nozzle: 200-220°C, bed: 60-70°C" * "hotend 200 bed 60" */ function parseTempsFromComment(comment: string): { extruderMin?: number; extruderMax?: number; bedMin?: number; bedMax?: number; } { const result: { extruderMin?: number; extruderMax?: number; bedMin?: number; bedMax?: number } = {}; // Patterns for extruder temperature const extruderPatterns = [ /(?:extrud(?:e|er)|nozzle|hotend|print\s*temp)[:\s]*(\d{2,3})\s*[-–~to]+\s*(\d{2,3})/i, /(?:extrud(?:e|er)|nozzle|hotend|print\s*temp)[:\s]*(\d{2,3})\s*°?C?/i, ]; // Patterns for bed temperature const bedPatterns = [ /(?:bed|plate|build\s*plate)[:\s]*(\d{2,3})\s*[-–~to]+\s*(\d{2,3})/i, /(?:bed|plate|build\s*plate)[:\s]*(\d{2,3})\s*°?C?/i, ]; for (const pattern of extruderPatterns) { const match = comment.match(pattern); if (match) { result.extruderMin = parseInt(match[1]); result.extruderMax = match[2] ? parseInt(match[2]) : parseInt(match[1]); break; } } for (const pattern of bedPatterns) { const match = comment.match(pattern); if (match) { result.bedMin = parseInt(match[1]); result.bedMax = match[2] ? parseInt(match[2]) : parseInt(match[1]); break; } } return result; } /* The API returns the extra fields as JSON values, but we need to parse them into their real types in order for Ant design's form to work properly. ParsedExtras does this for us. We also need to stringify them again before sending them back to the API, which is done by overriding the form's onFinish method. Form.Item's normalize should do this, but it doesn't seem to work. */ export const FilamentEdit: React.FC = () => { const t = useTranslate(); const navigate = useNavigate(); const [messageApi, contextHolder] = message.useMessage(); const [hasChanged, setHasChanged] = useState(false); const extraFields = useGetFields(EntityType.filament); const currency = useCurrency(); const [colorType, setColorType] = useState<"single" | "multi">("single"); // Get all filament IDs for prev/next navigation const { result: allFilaments } = useList({ resource: "filament", pagination: { mode: "off" }, sorters: [{ field: "id", order: "asc" }], }); const { formProps, saveButtonProps, id, form } = useForm({ liveMode: "manual", onLiveEvent() { // Warn the user if the filament has been updated since the form was opened messageApi.warning(t("filament.form.filament_updated")); setHasChanged(true); }, }); // Calculate prev/next IDs for navigation const { prevId, nextId } = useMemo(() => { if (!allFilaments?.data || !id) return { prevId: null, nextId: null }; const ids = allFilaments.data.map((f: IFilament) => f.id); const currentIndex = ids.indexOf(Number(id)); return { prevId: currentIndex > 0 ? ids[currentIndex - 1] : null, nextId: currentIndex < ids.length - 1 ? ids[currentIndex + 1] : null, }; }, [allFilaments, id]); // Get vendor selection options const { selectProps } = useSelect({ resource: "vendor", optionLabel: "name", }); // Add the vendor_id field to the form if (formProps.initialValues) { formProps.initialValues["vendor_id"] = formProps.initialValues["vendor"]?.id; // Parse the extra fields from string values into real types formProps.initialValues = ParsedExtras(formProps.initialValues); } // Update colorType state useEffect(() => { if (formProps.initialValues?.multi_color_hexes) { setColorType("multi"); } else { setColorType("single"); } }, [formProps.initialValues?.multi_color_hexes]); // Override the form's onFinish method to stringify the extra fields const originalOnFinish = formProps.onFinish; formProps.onFinish = (allValues: IFilamentParsedExtras) => { if (allValues !== undefined && allValues !== null) { if (colorType == "single") { allValues.multi_color_hexes = ""; } // Lot of stupidity here to make types work const stringifiedAllValues = StringifiedExtras(allValues); originalOnFinish?.({ extra: {}, ...stringifiedAllValues, }); } }; return ( ( <> {defaultButtons} )} > {contextHolder}
({ value: value ? dayjs(value) : undefined, })} > { if (value === undefined) { return null; } return value; }} >
- -