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 { 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 { Button, Center, Group, Loader, Stack, Text } from '@mantine/core'; | ||||||
| import { useForm } from '@mantine/form'; | import { useForm } from '@mantine/form'; | ||||||
|  | import { IconAlertCircle } from '@tabler/icons-react'; | ||||||
| import { useQuery } from '@tanstack/react-query'; | import { useQuery } from '@tanstack/react-query'; | ||||||
| import { AxiosResponse } from 'axios'; | import { AxiosResponse } from 'axios'; | ||||||
| import { useEffect } from 'react'; | import { useEffect } from 'react'; | ||||||
| @@ -15,12 +16,13 @@ import { api } from '../../App'; | |||||||
|  * - All other attributes are optional, and may be provided by the API |  * - All other attributes are optional, and may be provided by the API | ||||||
|  * - However, they can be overridden by the user |  * - However, they can be overridden by the user | ||||||
|  */ |  */ | ||||||
| export type ApiFormField = { | export type ApiFormFieldType = { | ||||||
|   name: string; |   name: string; | ||||||
|   label?: string; |   label?: string; | ||||||
|   value?: any; |   value?: any; | ||||||
|   type?: string; |   type?: string; | ||||||
|   required?: boolean; |   required?: boolean; | ||||||
|  |   hidden?: boolean; | ||||||
|   placeholder?: string; |   placeholder?: string; | ||||||
|   help_text?: string; |   help_text?: string; | ||||||
|   icon?: string; |   icon?: string; | ||||||
| @@ -28,13 +30,21 @@ export type ApiFormField = { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Extract a list of field definitions from an API response. |  * Render an individual form field | ||||||
|  * @param response : The API response to extract the field definitions from. |  | ||||||
|  * @returns A list of field definitions. |  | ||||||
|  */ |  */ | ||||||
| function extractFieldDefinitions(response: AxiosResponse): ApiFormField[] { | function ApiFormField({ | ||||||
|   // TODO: []; |   field, | ||||||
|   return []; |   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, |   onFormSuccess, | ||||||
|   onFormError, |   onFormError, | ||||||
|   cancelText = t`Cancel`, |   cancelText = t`Cancel`, | ||||||
|   submitText = t`Submit` |   submitText = t`Submit`, | ||||||
|  |   method = 'PUT' | ||||||
| }: { | }: { | ||||||
|   name: string; |   name: string; | ||||||
|   url: string; |   url: string; | ||||||
|   pk?: number; |   pk?: number; | ||||||
|   title: string; |   title: string; | ||||||
|   fields: ApiFormField[]; |   fields: ApiFormFieldType[]; | ||||||
|   cancelText?: string; |   cancelText?: string; | ||||||
|   submitText?: string; |   submitText?: string; | ||||||
|  |   method?: string; | ||||||
|   opened: boolean; |   opened: boolean; | ||||||
|   onClose?: () => void; |   onClose?: () => void; | ||||||
|   onFormSuccess?: () => void; |   onFormSuccess?: () => void; | ||||||
| @@ -76,35 +88,96 @@ export function ApiForm({ | |||||||
|   const form = useForm({}); |   const form = useForm({}); | ||||||
|  |  | ||||||
|   // Form field definitions (via API) |   // 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({ |   const definitionQuery = useQuery({ | ||||||
|     enabled: opened && !!url, |     enabled: opened && !!url, | ||||||
|     queryKey: ['form-definition', name, url, pk, fields], |     queryKey: ['form-definition', name, url, pk, fields], | ||||||
|     queryFn: async () => { |     queryFn: async () => { | ||||||
|       let _url = url; |       // Clear form construction error field | ||||||
|  |       setError(''); | ||||||
|       if (pk && pk > 0) { |  | ||||||
|         _url += `${pk}/`; |  | ||||||
|       } |  | ||||||
|       console.log('Fetching form definition from API:', _url); |  | ||||||
|  |  | ||||||
|       return api |       return api | ||||||
|         .options(_url) |         .options(getUrl()) | ||||||
|         .then((response) => { |         .then((response) => { | ||||||
|           console.log('response:', response); |  | ||||||
|           setFieldDefinitions(extractFieldDefinitions(response)); |           setFieldDefinitions(extractFieldDefinitions(response)); | ||||||
|           return response; |           return response; | ||||||
|         }) |         }) | ||||||
|         .catch((error) => { |         .catch((error) => { | ||||||
|           console.error('error:', error); |           console.error('Error fetching field definitions:', error); | ||||||
|  |           setError(error.message); | ||||||
|           setFieldDefinitions([]); |           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 ( |   return ( | ||||||
|     <Modal opened={opened} onClose={onClose} title={title}> |     <Modal | ||||||
|  |       size="xl" | ||||||
|  |       radius="sm" | ||||||
|  |       opened={opened} | ||||||
|  |       onClose={onClose} | ||||||
|  |       title={title} | ||||||
|  |     > | ||||||
|       <Stack> |       <Stack> | ||||||
|         <Divider /> |         <Divider /> | ||||||
|         {definitionQuery.isFetching && ( |         {definitionQuery.isFetching && ( | ||||||
| @@ -112,13 +185,38 @@ export function ApiForm({ | |||||||
|             <Loader /> |             <Loader /> | ||||||
|           </Center> |           </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 /> |         <Divider /> | ||||||
|         <Group position="right"> |         <Group position="right"> | ||||||
|           <Button onClick={onClose} variant="outline" color="red"> |           <Button onClick={onClose} variant="outline" radius="sm" color="red"> | ||||||
|             {cancelText} |             {cancelText} | ||||||
|           </Button> |           </Button> | ||||||
|           <Button onClick={() => null} variant="outline" color="green"> |           <Button | ||||||
|  |             onClick={() => null} | ||||||
|  |             variant="outline" | ||||||
|  |             radius="sm" | ||||||
|  |             color="green" | ||||||
|  |           > | ||||||
|             {submitText} |             {submitText} | ||||||
|           </Button> |           </Button> | ||||||
|         </Group> |         </Group> | ||||||
|   | |||||||
| @@ -3,14 +3,14 @@ import { Group } from '@mantine/core'; | |||||||
| import { Button } from '@mantine/core'; | import { Button } from '@mantine/core'; | ||||||
| import { useState } from 'react'; | 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 { PlaceholderPill } from '../../components/items/Placeholder'; | ||||||
| import { StylishText } from '../../components/items/StylishText'; | import { StylishText } from '../../components/items/StylishText'; | ||||||
|  |  | ||||||
| export default function Home() { | export default function Home() { | ||||||
|   const [formOpened, setFormOpened] = useState(false); |   const [formOpened, setFormOpened] = useState(false); | ||||||
|  |  | ||||||
|   const fields: ApiFormField[] = [ |   const fields: ApiFormFieldType[] = [ | ||||||
|     { |     { | ||||||
|       name: 'name' |       name: 'name' | ||||||
|     }, |     }, | ||||||
| @@ -31,6 +31,7 @@ export default function Home() { | |||||||
|         name="part-edit" |         name="part-edit" | ||||||
|         url="/part/1/" |         url="/part/1/" | ||||||
|         fields={fields} |         fields={fields} | ||||||
|  |         method="PUT" | ||||||
|         title="Edit Part" |         title="Edit Part" | ||||||
|         opened={formOpened} |         opened={formOpened} | ||||||
|         onClose={() => setFormOpened(false)} |         onClose={() => setFormOpened(false)} | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user