mirror of
				https://github.com/inventree/InvenTree.git
				synced 2025-10-31 21:25:42 +00:00 
			
		
		
		
	Render basic field stack, and extract field data from API
This commit is contained in:
		| @@ -1,7 +1,8 @@ | ||||
| import { t } from '@lingui/macro'; | ||||
| import { Divider, Modal } from '@mantine/core'; | ||||
| import { Alert, Divider, Modal } from '@mantine/core'; | ||||
| import { Button, Center, Group, Loader, Stack, Text } from '@mantine/core'; | ||||
| import { useForm } from '@mantine/form'; | ||||
| import { IconAlertCircle } from '@tabler/icons-react'; | ||||
| import { useQuery } from '@tanstack/react-query'; | ||||
| import { AxiosResponse } from 'axios'; | ||||
| import { useEffect } from 'react'; | ||||
| @@ -15,12 +16,13 @@ import { api } from '../../App'; | ||||
|  * - All other attributes are optional, and may be provided by the API | ||||
|  * - However, they can be overridden by the user | ||||
|  */ | ||||
| export type ApiFormField = { | ||||
| export type ApiFormFieldType = { | ||||
|   name: string; | ||||
|   label?: string; | ||||
|   value?: any; | ||||
|   type?: string; | ||||
|   required?: boolean; | ||||
|   hidden?: boolean; | ||||
|   placeholder?: string; | ||||
|   help_text?: string; | ||||
|   icon?: string; | ||||
| @@ -28,13 +30,21 @@ export type ApiFormField = { | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * Extract a list of field definitions from an API response. | ||||
|  * @param response : The API response to extract the field definitions from. | ||||
|  * @returns A list of field definitions. | ||||
|  * Render an individual form field | ||||
|  */ | ||||
| function extractFieldDefinitions(response: AxiosResponse): ApiFormField[] { | ||||
|   // TODO: []; | ||||
|   return []; | ||||
| function ApiFormField({ | ||||
|   field, | ||||
|   definitions | ||||
| }: { | ||||
|   field: ApiFormFieldType; | ||||
|   definitions: ApiFormFieldType[]; | ||||
| }) { | ||||
|   useEffect(() => { | ||||
|     console.log('field:', field); | ||||
|     console.log('definitions:', definitions); | ||||
|   }, []); | ||||
|  | ||||
|   return <Text>{field.name}</Text>; | ||||
| } | ||||
|  | ||||
| /** | ||||
| @@ -58,15 +68,17 @@ export function ApiForm({ | ||||
|   onFormSuccess, | ||||
|   onFormError, | ||||
|   cancelText = t`Cancel`, | ||||
|   submitText = t`Submit` | ||||
|   submitText = t`Submit`, | ||||
|   method = 'PUT' | ||||
| }: { | ||||
|   name: string; | ||||
|   url: string; | ||||
|   pk?: number; | ||||
|   title: string; | ||||
|   fields: ApiFormField[]; | ||||
|   fields: ApiFormFieldType[]; | ||||
|   cancelText?: string; | ||||
|   submitText?: string; | ||||
|   method?: string; | ||||
|   opened: boolean; | ||||
|   onClose?: () => void; | ||||
|   onFormSuccess?: () => void; | ||||
| @@ -76,35 +88,96 @@ export function ApiForm({ | ||||
|   const form = useForm({}); | ||||
|  | ||||
|   // Form field definitions (via API) | ||||
|   const [fieldDefinitions, setFieldDefinitions] = useState<ApiFormField[]>([]); | ||||
|   const [fieldDefinitions, setFieldDefinitions] = useState<ApiFormFieldType[]>( | ||||
|     [] | ||||
|   ); | ||||
|  | ||||
|   // Error observed during form construction | ||||
|   const [error, setError] = useState<string>(''); | ||||
|  | ||||
|   const definitionQuery = useQuery({ | ||||
|     enabled: opened && !!url, | ||||
|     queryKey: ['form-definition', name, url, pk, fields], | ||||
|     queryFn: async () => { | ||||
|       let _url = url; | ||||
|  | ||||
|       if (pk && pk > 0) { | ||||
|         _url += `${pk}/`; | ||||
|       } | ||||
|       console.log('Fetching form definition from API:', _url); | ||||
|  | ||||
|       // Clear form construction error field | ||||
|       setError(''); | ||||
|       return api | ||||
|         .options(_url) | ||||
|         .options(getUrl()) | ||||
|         .then((response) => { | ||||
|           console.log('response:', response); | ||||
|           setFieldDefinitions(extractFieldDefinitions(response)); | ||||
|           return response; | ||||
|         }) | ||||
|         .catch((error) => { | ||||
|           console.error('error:', error); | ||||
|           console.error('Error fetching field definitions:', error); | ||||
|           setError(error.message); | ||||
|           setFieldDefinitions([]); | ||||
|         }); | ||||
|     } | ||||
|   }); | ||||
|  | ||||
|   // State variable to determine if the form can render the data | ||||
|   const [canRender, setCanRender] = useState<boolean>(false); | ||||
|  | ||||
|   // Update the canRender state variable on status change | ||||
|   useEffect(() => { | ||||
|     setCanRender( | ||||
|       !definitionQuery.isFetching && definitionQuery.isSuccess && !error | ||||
|     ); | ||||
|   }, [definitionQuery, error]); | ||||
|  | ||||
|   // Construct a fully-qualified URL based on the provided details | ||||
|   function getUrl(): string { | ||||
|     let u = url; | ||||
|  | ||||
|     if (pk && pk > 0) { | ||||
|       u += `${pk}/`; | ||||
|     } | ||||
|  | ||||
|     return u; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Extract a list of field definitions from an API response. | ||||
|    * @param response : The API response to extract the field definitions from. | ||||
|    * @returns A list of field definitions. | ||||
|    */ | ||||
|   function extractFieldDefinitions( | ||||
|     response: AxiosResponse | ||||
|   ): ApiFormFieldType[] { | ||||
|     let actions = response.data?.actions[method.toUpperCase()] || []; | ||||
|  | ||||
|     if (actions.length == 0) { | ||||
|       setError(`Permission denied for ${method} at ${url}`); | ||||
|       return []; | ||||
|     } | ||||
|  | ||||
|     let definitions: ApiFormFieldType[] = []; | ||||
|  | ||||
|     for (const fieldName in actions) { | ||||
|       const field = actions[fieldName]; | ||||
|       definitions.push({ | ||||
|         name: fieldName, | ||||
|         label: field.label, | ||||
|         help_text: field.help_text, | ||||
|         value: field.value, | ||||
|         type: field.type, | ||||
|         required: field.required, | ||||
|         placeholder: field.placeholder, | ||||
|         icon: field.icon | ||||
|       }); | ||||
|     } | ||||
|  | ||||
|     return definitions; | ||||
|   } | ||||
|  | ||||
|   return ( | ||||
|     <Modal opened={opened} onClose={onClose} title={title}> | ||||
|     <Modal | ||||
|       size="xl" | ||||
|       radius="sm" | ||||
|       opened={opened} | ||||
|       onClose={onClose} | ||||
|       title={title} | ||||
|     > | ||||
|       <Stack> | ||||
|         <Divider /> | ||||
|         {definitionQuery.isFetching && ( | ||||
| @@ -112,13 +185,38 @@ export function ApiForm({ | ||||
|             <Loader /> | ||||
|           </Center> | ||||
|         )} | ||||
|         {!definitionQuery.isFetching && <Text>Form definition fetched!</Text>} | ||||
|         {error && ( | ||||
|           <Alert | ||||
|             radius="sm" | ||||
|             color="red" | ||||
|             title={`Error`} | ||||
|             icon={<IconAlertCircle size="1rem" />} | ||||
|           > | ||||
|             {error} | ||||
|           </Alert> | ||||
|         )} | ||||
|         {canRender && ( | ||||
|           <Stack spacing="md"> | ||||
|             {fields.map((field) => ( | ||||
|               <ApiFormField | ||||
|                 key={field.name} | ||||
|                 field={field} | ||||
|                 definitions={fieldDefinitions} | ||||
|               /> | ||||
|             ))} | ||||
|           </Stack> | ||||
|         )} | ||||
|         <Divider /> | ||||
|         <Group position="right"> | ||||
|           <Button onClick={onClose} variant="outline" color="red"> | ||||
|           <Button onClick={onClose} variant="outline" radius="sm" color="red"> | ||||
|             {cancelText} | ||||
|           </Button> | ||||
|           <Button onClick={() => null} variant="outline" color="green"> | ||||
|           <Button | ||||
|             onClick={() => null} | ||||
|             variant="outline" | ||||
|             radius="sm" | ||||
|             color="green" | ||||
|           > | ||||
|             {submitText} | ||||
|           </Button> | ||||
|         </Group> | ||||
|   | ||||
| @@ -3,14 +3,14 @@ import { Group } from '@mantine/core'; | ||||
| import { Button } from '@mantine/core'; | ||||
| import { useState } from 'react'; | ||||
|  | ||||
| import { ApiForm, ApiFormField } from '../../components/forms/ApiForm'; | ||||
| import { ApiForm, ApiFormFieldType } from '../../components/forms/ApiForm'; | ||||
| import { PlaceholderPill } from '../../components/items/Placeholder'; | ||||
| import { StylishText } from '../../components/items/StylishText'; | ||||
|  | ||||
| export default function Home() { | ||||
|   const [formOpened, setFormOpened] = useState(false); | ||||
|  | ||||
|   const fields: ApiFormField[] = [ | ||||
|   const fields: ApiFormFieldType[] = [ | ||||
|     { | ||||
|       name: 'name' | ||||
|     }, | ||||
| @@ -31,6 +31,7 @@ export default function Home() { | ||||
|         name="part-edit" | ||||
|         url="/part/1/" | ||||
|         fields={fields} | ||||
|         method="PUT" | ||||
|         title="Edit Part" | ||||
|         opened={formOpened} | ||||
|         onClose={() => setFormOpened(false)} | ||||
|   | ||||
		Reference in New Issue
	
	Block a user