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