functions.tsx
1 import { useSelect, useTranslate } from "@refinedev/core"; 2 import { useQueries } from "@tanstack/react-query"; 3 import { Form, InputNumber, Modal, Radio } from "antd"; 4 import { useForm } from "antd/es/form/Form"; 5 import type { InputNumberRef } from "rc-input-number"; 6 import { useCallback, useMemo, useRef, useState } from "react"; 7 import { formatLength, formatWeight } from "../../utils/parsing"; 8 import { SpoolType, useGetExternalDBFilaments } from "../../utils/queryExternalDB"; 9 import { getAPIURL } from "../../utils/url"; 10 import { IFilament } from "../filaments/model"; 11 import { ISpool } from "./model"; 12 13 export async function setSpoolArchived(spool: ISpool, archived: boolean) { 14 const init: RequestInit = { 15 method: "PATCH", 16 headers: { 17 "Content-Type": "application/json", 18 }, 19 body: JSON.stringify({ 20 archived: archived, 21 }), 22 }; 23 const request = new Request(getAPIURL() + "/spool/" + spool.id); 24 await fetch(request, init); 25 } 26 27 /** 28 * Use some spool filament from this spool. Either specify length or weight. 29 * @param spool The spool 30 * @param length The length to add/subtract from the spool, in mm 31 * @param weight The weight to add/subtract from the spool, in g 32 */ 33 export async function useSpoolFilament(spool: ISpool, length?: number, weight?: number) { 34 const init: RequestInit = { 35 method: "PUT", 36 headers: { 37 "Content-Type": "application/json", 38 }, 39 body: JSON.stringify({ 40 use_length: length, 41 use_weight: weight, 42 }), 43 }; 44 const request = new Request(`${getAPIURL()}/spool/${spool.id}/use`); 45 await fetch(request, init); 46 } 47 48 /** 49 * Adjust usage based on the spool's current gross weight 50 * @param spool The spool 51 * @param weight The weight of the spool, in g 52 */ 53 export async function useSpoolFilamentMeasure(spool: ISpool, weight: number) { 54 const init: RequestInit = { 55 method: "PUT", 56 headers: { 57 "Content-Type": "application/json", 58 }, 59 body: JSON.stringify({ 60 weight: weight, 61 }), 62 }; 63 const request = new Request(`${getAPIURL()}/spool/${spool.id}/measure`); 64 await fetch(request, init); 65 } 66 67 /** 68 * Returns an array of queries using the useQueries hook from @tanstack/react-query. 69 * Each query fetches a spool by its ID from the server. 70 * 71 * @param {number[]} ids - An array of spool IDs to fetch. 72 * @return An array of query results, each containing the fetched spool data. 73 */ 74 export function useGetSpoolsByIds(ids: number[]) { 75 return useQueries({ 76 queries: ids.map((id) => { 77 return { 78 queryKey: ["spool", id], 79 queryFn: async () => { 80 const res = await fetch(getAPIURL() + "/spool/" + id); 81 return (await res.json()) as ISpool; 82 }, 83 }; 84 }), 85 }); 86 } 87 88 /** 89 * Formats a filament label with the given parameters. 90 */ 91 export function formatFilamentLabel( 92 name: string, 93 diameter: number, 94 vendorName?: string, 95 material?: string, 96 weight?: number, 97 spoolType?: SpoolType, 98 ): string { 99 const portions = []; 100 if (vendorName) { 101 portions.push(vendorName); 102 } 103 portions.push(name); 104 const extras = []; 105 if (material) { 106 extras.push(material); 107 } 108 extras.push(formatLength(diameter)); 109 if (weight) { 110 extras.push(formatWeight(weight)); 111 } 112 if (spoolType) { 113 extras.push(spoolType.charAt(0).toUpperCase() + spoolType.slice(1) + " spool"); 114 } 115 return `${portions.join(" - ")} (${extras.join(", ")})`; 116 } 117 118 interface SelectOption { 119 label: string; 120 value: string | number; 121 weight?: number; 122 spool_weight?: number; 123 is_internal: boolean; 124 } 125 126 export function useGetFilamentSelectOptions() { 127 // Setup hooks 128 const t = useTranslate(); 129 const { query: internalFilaments } = useSelect<IFilament>({ 130 resource: "filament", 131 pagination: { mode: "off" }, 132 }); 133 const externalFilaments = useGetExternalDBFilaments(); 134 135 // Format and sort internal filament options 136 const filamentSelectInternal: SelectOption[] = useMemo(() => { 137 const data = 138 internalFilaments.data?.data.map((item) => { 139 return { 140 label: formatFilamentLabel( 141 item.name ?? `ID ${item.id}`, 142 item.diameter, 143 item.vendor?.name, 144 item.material, 145 item.weight, 146 ), 147 value: item.id, 148 weight: item.weight, 149 spool_weight: item.spool_weight, 150 is_internal: true, 151 }; 152 }) ?? []; 153 data.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" })); 154 return data; 155 }, [internalFilaments.data?.data]); 156 157 // Format and sort external filament options 158 const filamentSelectExternal: SelectOption[] = useMemo(() => { 159 const data = 160 externalFilaments.data?.map((item) => { 161 return { 162 label: formatFilamentLabel( 163 item.name, 164 item.diameter, 165 item.manufacturer, 166 item.material, 167 item.weight, 168 item.spool_type, 169 ), 170 value: item.id, 171 weight: item.weight, 172 spool_weight: item.spool_weight || undefined, 173 is_internal: false, 174 }; 175 }) ?? []; 176 data.sort((a, b) => a.label.localeCompare(b.label, undefined, { sensitivity: "base" })); 177 return data; 178 }, [externalFilaments.data]); 179 180 return { 181 options: [ 182 { 183 label: <span>{t("spool.fields.filament_internal")}</span>, 184 options: filamentSelectInternal, 185 }, 186 { 187 label: <span>{t("spool.fields.filament_external")}</span>, 188 options: filamentSelectExternal, 189 }, 190 ], 191 internalSelectOptions: filamentSelectInternal, 192 externalSelectOptions: filamentSelectExternal, 193 allExternalFilaments: externalFilaments.data, 194 }; 195 } 196 197 type MeasurementType = "length" | "weight" | "measured_weight"; 198 199 export function useSpoolAdjustModal() { 200 const t = useTranslate(); 201 const [form] = useForm(); 202 203 const [curSpool, setCurSpool] = useState<ISpool | null>(null); 204 const [measurementType, setMeasurementType] = useState<MeasurementType>("length"); 205 const inputNumberRef = useRef<InputNumberRef | null>(null); 206 207 const openSpoolAdjustModal = useCallback((spool: ISpool) => { 208 setCurSpool(spool); 209 setTimeout(() => { 210 inputNumberRef.current?.focus(); 211 }, 0); 212 }, []); 213 214 const spoolAdjustModal = useMemo(() => { 215 if (curSpool === null) { 216 return null; 217 } 218 219 const onSubmit = async () => { 220 if (curSpool === null) { 221 return; 222 } 223 224 const value = form.getFieldValue("filament_value"); 225 if (value === undefined || value === null) { 226 return; 227 } 228 229 if (measurementType === "length") { 230 await useSpoolFilament(curSpool, value, undefined); 231 } else if (measurementType === "weight") { 232 await useSpoolFilament(curSpool, undefined, value); 233 } else { 234 await useSpoolFilamentMeasure(curSpool, value); 235 } 236 237 setCurSpool(null); 238 }; 239 240 return ( 241 <Modal title={t("spool.titles.adjust")} open onCancel={() => setCurSpool(null)} onOk={form.submit}> 242 <p>{t("spool.form.adjust_filament_help")}</p> 243 <Form form={form} initialValues={{ measurement_type: measurementType }} onFinish={onSubmit}> 244 <Form.Item label={t("spool.form.measurement_type_label")} name="measurement_type"> 245 <Radio.Group 246 value={measurementType} 247 onChange={({ target: { value } }) => setMeasurementType(value as MeasurementType)} 248 > 249 <Radio.Button value="length">{t("spool.form.measurement_type.length")}</Radio.Button> 250 <Radio.Button value="weight">{t("spool.form.measurement_type.weight")}</Radio.Button> 251 <Radio.Button value="measured_weight">{t("spool.fields.measured_weight")}</Radio.Button> 252 </Radio.Group> 253 </Form.Item> 254 <Form.Item label={t("spool.form.adjust_filament_value")} name="filament_value"> 255 <InputNumber ref={inputNumberRef} precision={1} addonAfter={measurementType === "length" ? "mm" : "g"} /> 256 </Form.Item> 257 </Form> 258 </Modal> 259 ); 260 }, [curSpool, measurementType, t]); 261 262 return { 263 openSpoolAdjustModal, 264 spoolAdjustModal, 265 }; 266 }