/ client / src / utils / queryFields.ts
queryFields.ts
  1  import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
  2  import dayjs from "dayjs";
  3  import { getAPIURL } from "./url";
  4  
  5  export enum FieldType {
  6    text = "text",
  7    integer = "integer",
  8    integer_range = "integer_range",
  9    float = "float",
 10    float_range = "float_range",
 11    datetime = "datetime",
 12    boolean = "boolean",
 13    choice = "choice",
 14  }
 15  
 16  export enum EntityType {
 17    vendor = "vendor",
 18    filament = "filament",
 19    spool = "spool",
 20  }
 21  
 22  export interface FieldParameters {
 23    name: string;
 24    order: number;
 25    unit?: string;
 26    field_type: FieldType;
 27    default_value?: string | (number | null)[] | boolean | dayjs.Dayjs;
 28    choices?: string[];
 29    multi_choice?: boolean;
 30  }
 31  
 32  export interface Field extends FieldParameters {
 33    key: string;
 34    entity_type: EntityType;
 35  }
 36  
 37  export function useGetFields(entity_type: EntityType) {
 38    return useQuery<Field[]>({
 39      queryKey: ["fields", entity_type],
 40      queryFn: async () => {
 41        const response = await fetch(`${getAPIURL()}/field/${entity_type}`);
 42        return response.json();
 43      },
 44    });
 45  }
 46  
 47  export function useSetField(entity_type: EntityType) {
 48    const queryClient = useQueryClient();
 49  
 50    return useMutation<Field[], unknown, { key: string; params: FieldParameters }, { previousFields?: Field[] }>({
 51      mutationFn: async ({ key, params }) => {
 52        const response = await fetch(`${getAPIURL()}/field/${entity_type}/${key}`, {
 53          method: "POST",
 54          headers: {
 55            "Content-Type": "application/json",
 56          },
 57          body: JSON.stringify(params),
 58        });
 59  
 60        // Throw error if response is not ok
 61        if (!response.ok) {
 62          throw new Error((await response.json()).message);
 63        }
 64  
 65        return response.json();
 66      },
 67      onMutate: async ({ key, params }) => {
 68        // Cancel any outgoing refetches (so they don't overwrite our optimistic update)
 69        await queryClient.cancelQueries({
 70          queryKey: ["fields", entity_type],
 71        });
 72  
 73        // Snapshot the previous value
 74        const previousFields = queryClient.getQueryData<Field[]>(["fields", entity_type]);
 75  
 76        // Optimistically update to the new value
 77        queryClient.setQueryData<Field[]>(["fields", entity_type], (old) => {
 78          if (!old) {
 79            return [
 80              {
 81                key: key,
 82                entity_type: entity_type,
 83                ...params,
 84              },
 85            ];
 86          }
 87          return old.map((field) => {
 88            if (field.key === key) {
 89              return { ...field, ...params };
 90            }
 91            return field;
 92          });
 93        });
 94  
 95        // Return a context object with the snapshotted value
 96        return { previousFields };
 97      },
 98      onError: (_err, _newFields, context) => {
 99        // Rollback to the previous value
100        if (context?.previousFields) {
101          queryClient.setQueryData(["fields", entity_type], context.previousFields);
102        }
103      },
104      onSettled: () => {
105        // Invalidate and refetch
106        queryClient.invalidateQueries({
107          queryKey: ["fields", entity_type],
108        });
109      },
110    });
111  }
112  
113  export function useDeleteField(entity_type: EntityType) {
114    const queryClient = useQueryClient();
115  
116    return useMutation<Field[], unknown, string>({
117      mutationFn: async (key) => {
118        const response = await fetch(`${getAPIURL()}/field/${entity_type}/${key}`, {
119          method: "DELETE",
120        });
121  
122        // Throw error if response is not ok
123        if (!response.ok) {
124          throw new Error((await response.json()).message);
125        }
126  
127        return response.json();
128      },
129      onSuccess: () => {
130        // Invalidate and refetch
131        queryClient.invalidateQueries({
132          queryKey: ["fields", entity_type],
133        });
134      },
135    });
136  }