2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-15 19:45:46 +00:00

Render basic field stack, and extract field data from API

This commit is contained in:
Oliver Walters
2023-07-27 15:02:29 +10:00
parent f8412aa971
commit 256ef5792c
2 changed files with 126 additions and 27 deletions

View File

@ -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>

View File

@ -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)}