import { useQuery, useMutation, useQueryClient, keepPreviousData } from "@tanstack/react-query"; import * as React from "react"; import { api } from "../api/client"; import { ResourceConfig } from "../types/config"; import { ConfigContext } from "../providers/ConfigContext"; import { FieldComponents, FieldComponentProps } from "../types/overrides"; import { defaultFieldComponents } from "../components/fields/DefaultFieldComponents"; import FormField from "../components/fields/FormField"; import GenericForm from "../components/GenericForm"; function wrapFormField(merged: FieldComponents) { return (props: Omit, 'components'>) => React.createElement(FormField, { ...props, components: merged }); } function wrapGenericForm(merged: FieldComponents) { return (props: Omit, 'fieldComponents'>) => React.createElement(GenericForm, { ...props, fieldComponents: merged }); } export function useResource(config: ResourceConfig | undefined, options?: { fieldComponents: FieldComponents }) { const queryClient = useQueryClient(); const { name = '', endpoint = '', primaryKey = 'id' } = config || {}; const mergedComponents = React.useMemo( () => options?.fieldComponents ? ({ ...defaultFieldComponents, ...options.fieldComponents }) : undefined, [options?.fieldComponents], ); // --- READ ALL --- const useList = (params?: any) => useQuery({ queryKey: [name, "list", params], queryFn: async () => { if (!endpoint) return { data: [], total: 0 }; const res = await api.get(endpoint, { params }); const total = res.headers ? parseInt(res.headers['x-total-count'] || res.headers['X-Total-Count']) : undefined; return { data: res.data, total: isNaN(total as any) ? undefined : total }; }, enabled: !!endpoint, placeholderData: keepPreviousData, }); // --- READ ONE --- const useRead = (id: string, params?: any | null) => useQuery({ queryKey: [name, "detail", id, params], queryFn: async () => { if (!id || !endpoint) return null; const res = await api.get(`${endpoint}/${id}`, params ? { params } : undefined); return res.data; }, enabled: !!id && !!endpoint, }); // --- CREATE --- const useCreate = () => useMutation({ mutationFn: async (data: Partial) => { if (!endpoint) throw new Error("Endpoint not defined"); const res = await api.post(endpoint, data); return res.data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [name, "list"] }); }, }); // --- UPDATE --- const useUpdate = () => useMutation({ mutationFn: async ({ id, data }: { id: string; data: Partial }) => { if (!endpoint) throw new Error("Endpoint not defined"); const res = await api.put(`${endpoint}/${id}`, data); return res.data; }, onSuccess: (updatedItem: any) => { const id = updatedItem[primaryKey]; queryClient.invalidateQueries({ queryKey: [name, "list"] }); queryClient.invalidateQueries({ queryKey: [name, "detail", id] }); }, }); // --- PATCH --- const usePatch = () => useMutation({ mutationFn: async ({ id, data }: { id: string; data: Partial }) => { if (!endpoint) throw new Error("Endpoint not defined"); const res = await api.patch(`${endpoint}/${id}`, data); return res.data; }, onSuccess: (updatedItem: any) => { const listId = updatedItem[primaryKey]; queryClient.invalidateQueries({ queryKey: [name, "list"] }); queryClient.invalidateQueries({ queryKey: [name, "detail", listId] }); }, }); // --- DELETE --- const useDelete = () => useMutation({ mutationFn: async (id: string) => { if (!endpoint) throw new Error("Endpoint not defined"); await api.delete(`${endpoint}/${id}`); return id; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [name, "list"] }); }, }); // --- HELPERS FOR useQueries --- const getListQueryOptions = (params?: any) => ({ queryKey: [name, "list", params], queryFn: async () => { if (!endpoint) return { data: [], total: 0 }; const res = await api.get(endpoint, { params }); const total = res.headers ? parseInt(res.headers['x-total-count'] || res.headers['X-Total-Count']) : undefined; return { data: res.data, total: isNaN(total as any) ? undefined : total }; }, enabled: !!endpoint, }); // --- READ ME --- const useMe = () => useQuery({ queryKey: [name, "me"], queryFn: async () => { if (!endpoint) return null; const res = await api.get(`${endpoint}/me`); return res.data; }, enabled: !!endpoint, }); // --- UPDATE ME --- const useUpdateMe = () => useMutation({ mutationFn: async (data: Partial) => { if (!endpoint) throw new Error("Endpoint not defined"); const res = await api.put(`${endpoint}/me`, data); return res.data; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: [name, "me"] }); queryClient.invalidateQueries({ queryKey: [name, "list"] }); }, }); const components = React.useMemo(() => { if (!mergedComponents) return undefined; return { ...mergedComponents, FormField: wrapFormField(mergedComponents), GenericForm: wrapGenericForm(mergedComponents), }; }, [mergedComponents]); return { useList, useRead, useMe, useCreate, useUpdate, usePatch, useUpdateMe, useDelete, getListQueryOptions, components, }; } export function useResourceByName(name: string, options?: { fieldComponents: FieldComponents }) { const config = React.useContext(ConfigContext); const resourceConfig = config?.resources.find((r) => r.name === name); return useResource(resourceConfig, options); }