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

Create components for different form types

- Create
- Edit
- Delete
This commit is contained in:
Oliver Walters
2023-07-27 20:43:02 +10:00
parent 65a0a8ff4b
commit 3a24e7a27f
5 changed files with 158 additions and 45 deletions

View File

@ -32,6 +32,7 @@ export type ApiFormFieldType = {
name: string; name: string;
label?: string; label?: string;
value?: any; value?: any;
default?: any;
icon?: ReactNode; icon?: ReactNode;
fieldType?: string; fieldType?: string;
api_url?: string; api_url?: string;
@ -249,8 +250,7 @@ function ApiFormField({
} }
/** /**
* An ApiForm component is a modal form which is rendered dynamically, * Properties for the ApiForm component
* based on an API endpoint.
* @param url : The API endpoint to fetch the form from. * @param url : The API endpoint to fetch the form from.
* @param fields : The fields to render in the form. * @param fields : The fields to render in the form.
* @param opened : Whether the form is opened or not. * @param opened : Whether the form is opened or not.
@ -258,21 +258,7 @@ function ApiFormField({
* @param onFormSuccess : A callback function to call when the form is submitted successfully. * @param onFormSuccess : A callback function to call when the form is submitted successfully.
* @param onFormError : A callback function to call when the form is submitted with errors. * @param onFormError : A callback function to call when the form is submitted with errors.
*/ */
export function ApiForm({ export interface ApiFormProps {
name,
url,
pk,
title,
fields,
opened,
onClose,
onFormSuccess,
onFormError,
cancelText = t`Cancel`,
submitText = t`Submit`,
method = 'PUT',
fetchInitialData = false
}: {
name: string; name: string;
url: string; url: string;
pk?: number; pk?: number;
@ -280,13 +266,22 @@ export function ApiForm({
fields: ApiFormFieldType[]; fields: ApiFormFieldType[];
cancelText?: string; cancelText?: string;
submitText?: string; submitText?: string;
submitColor?: string;
cancelColor?: string;
fetchInitialData?: boolean; fetchInitialData?: boolean;
method?: string; method?: string;
opened: boolean; opened: boolean;
onClose?: () => void; onClose?: () => void;
onFormSuccess?: () => void; onFormSuccess?: () => void;
onFormError?: () => void; onFormError?: () => void;
}) { }
/**
* An ApiForm component is a modal form which is rendered dynamically,
* based on an API endpoint.
*/
export function ApiForm(props: ApiFormProps) {
// Form state // Form state
const form = useForm({}); const form = useForm({});
@ -300,8 +295,8 @@ export function ApiForm({
// Query manager for retrieving form definition from the server // Query manager for retrieving form definition from the server
const definitionQuery = useQuery({ const definitionQuery = useQuery({
enabled: opened && !!url, enabled: props.opened && !!props.url,
queryKey: ['form-definition', name, url, pk], queryKey: ['form-definition', name, props.url, props.pk],
queryFn: async () => { queryFn: async () => {
// Clear form construction error field // Clear form construction error field
setError(''); setError('');
@ -320,8 +315,12 @@ export function ApiForm({
// Query manager for retrieiving initial data from the server // Query manager for retrieiving initial data from the server
const initialDataQuery = useQuery({ const initialDataQuery = useQuery({
enabled: fetchInitialData && opened && !!url && fieldDefinitions.length > 0, enabled:
queryKey: ['form-initial-data', name, url, pk], props.fetchInitialData &&
props.opened &&
!!props.url &&
fieldDefinitions.length > 0,
queryKey: ['form-initial-data', name, props.url, props.pk],
queryFn: async () => { queryFn: async () => {
return api return api
.get(getUrl()) .get(getUrl())
@ -357,10 +356,14 @@ export function ApiForm({
// Construct a fully-qualified URL based on the provided details // Construct a fully-qualified URL based on the provided details
function getUrl(): string { function getUrl(): string {
let u = url; if (!props.url) {
return '';
}
if (pk && pk > 0) { let u = props.url;
u += `${pk}/`;
if (props.pk && props.pk > 0) {
u += `${props.pk}/`;
} }
return u; return u;
@ -374,10 +377,14 @@ export function ApiForm({
function extractFieldDefinitions( function extractFieldDefinitions(
response: AxiosResponse response: AxiosResponse
): ApiFormFieldType[] { ): ApiFormFieldType[] {
let actions = response.data?.actions[method.toUpperCase()] || []; if (!props.method) {
return [];
}
let actions = response.data?.actions[props.method.toUpperCase()] || [];
if (actions.length == 0) { if (actions.length == 0) {
setError(`Permission denied for ${method} at ${url}`); setError(`Permission denied for ${props.method} at ${props.url}`);
return []; return [];
} }
@ -389,7 +396,7 @@ export function ApiForm({
name: fieldName, name: fieldName,
label: field.label, label: field.label,
description: field.help_text, description: field.help_text,
value: field.value, value: field.value || field.default,
fieldType: field.type, fieldType: field.type,
required: field.required, required: field.required,
placeholder: field.placeholder, placeholder: field.placeholder,
@ -405,11 +412,11 @@ export function ApiForm({
<Modal <Modal
size="xl" size="xl"
radius="sm" radius="sm"
opened={opened} opened={props.opened}
onClose={() => { onClose={() => {
onClose ? onClose() : null; props.onClose ? props.onClose() : null;
}} }}
title={title} title={props.title}
> >
<Stack> <Stack>
<Divider /> <Divider />
@ -430,7 +437,7 @@ export function ApiForm({
{canRender && ( {canRender && (
<ScrollArea> <ScrollArea>
<Stack spacing="md"> <Stack spacing="md">
{fields.map((field) => ( {props.fields.map((field) => (
<ApiFormField <ApiFormField
key={field.name} key={field.name}
field={field} field={field}
@ -447,19 +454,24 @@ export function ApiForm({
</Stack> </Stack>
<Divider /> <Divider />
<Group position="right"> <Group position="right">
<Button onClick={onClose} variant="outline" radius="sm" color="red"> <Button
{cancelText} onClick={props.onClose}
variant="outline"
radius="sm"
color={props.cancelColor ?? 'blue'}
>
{props.cancelText ?? `Cancel`}
</Button> </Button>
<Button <Button
onClick={() => null} onClick={() => null}
variant="outline" variant="outline"
radius="sm" radius="sm"
color="green" color={props.submitColor ?? 'green'}
disabled={!canSubmit} disabled={!canSubmit}
> >
<Group position="right" spacing={5} noWrap={true}> <Group position="right" spacing={5} noWrap={true}>
<Loader size="xs" /> <Loader size="xs" />
{submitText} {props.submitText ?? `Submit`}
</Group> </Group>
</Button> </Button>
</Group> </Group>

View File

@ -0,0 +1,18 @@
import { useMemo } from 'react';
import { ApiForm, ApiFormProps } from './ApiForm';
/**
* A modal form for creating a new database object via the API
*/
export function CreateApiForm(props: ApiFormProps) {
const createProps: ApiFormProps = useMemo(() => {
return {
...props,
method: 'POST',
fetchInitialData: false
};
}, [props]);
return <ApiForm {...createProps} />;
}

View File

@ -0,0 +1,21 @@
import { t } from '@lingui/macro';
import { useMemo } from 'react';
import { ApiForm, ApiFormProps } from './ApiForm';
/**
* A modal form for deleting a single database object via the API.
*/
export function DeleteApiForm(props: ApiFormProps) {
const deleteProps: ApiFormProps = useMemo(() => {
return {
...props,
method: 'DELETE',
fetchInitialData: false,
submitText: props.submitText ? props.submitText : t`Delete`,
submitColor: 'red'
};
}, [props]);
return <ApiForm {...deleteProps} />;
}

View File

@ -0,0 +1,19 @@
import { useMemo } from 'react';
import { ApiForm, ApiFormProps } from './ApiForm';
/**
* A modal form for editing a single database object via the API.
*/
export function EditApiForm(props: ApiFormProps) {
const editProps: ApiFormProps = useMemo(() => {
return {
...props,
method: 'PUT',
fetchInitialData: true,
submitText: props.submitText ? props.submitText : 'Save'
};
}, [props]);
return <ApiForm {...editProps} />;
}

View File

@ -11,6 +11,9 @@ import {
import { useState } from 'react'; import { useState } from 'react';
import { ApiForm, ApiFormFieldType } from '../../components/forms/ApiForm'; import { ApiForm, ApiFormFieldType } from '../../components/forms/ApiForm';
import { CreateApiForm } from '../../components/forms/CreateApiForm';
import { DeleteApiForm } from '../../components/forms/DeleteApiForm';
import { EditApiForm } from '../../components/forms/EditApiForm';
import { PlaceholderPill } from '../../components/items/Placeholder'; import { PlaceholderPill } from '../../components/items/Placeholder';
import { StylishText } from '../../components/items/StylishText'; import { StylishText } from '../../components/items/StylishText';
@ -18,6 +21,8 @@ export default function Home() {
const [partFormOpened, setPartFormOpened] = useState(false); const [partFormOpened, setPartFormOpened] = useState(false);
const [poFormOpened, setPoFormOpened] = useState(false); const [poFormOpened, setPoFormOpened] = useState(false);
const [companyFormOpened, setCompanyFormOpened] = useState(false); const [companyFormOpened, setCompanyFormOpened] = useState(false);
const [stockFormOpened, setStockFormOpened] = useState(false);
const [salesOrderFormOpened, setSalesOrderFormOpened] = useState(false);
const partFields: ApiFormFieldType[] = [ const partFields: ApiFormFieldType[] = [
{ {
@ -59,6 +64,18 @@ export default function Home() {
} }
]; ];
const salesOrderFields: ApiFormFieldType[] = [
{
name: 'reference'
},
{
name: 'customer'
},
{
name: 'description'
}
];
const companyFields: ApiFormFieldType[] = [ const companyFields: ApiFormFieldType[] = [
{ {
name: 'name' name: 'name'
@ -91,39 +108,51 @@ export default function Home() {
</StylishText> </StylishText>
<PlaceholderPill /> <PlaceholderPill />
</Group> </Group>
<ApiForm <EditApiForm
name="part-edit" name="part-edit"
url="/part/" url="/part/"
pk={1} pk={1}
fields={partFields} fields={partFields}
method="PUT"
title="Edit Part" title="Edit Part"
opened={partFormOpened} opened={partFormOpened}
onClose={() => setPartFormOpened(false)} onClose={() => setPartFormOpened(false)}
fetchInitialData={true}
/> />
<ApiForm <EditApiForm
name="po-edit" name="po-edit"
url="/order/po/" url="/order/po/"
pk={1} pk={1}
fields={poFields} fields={poFields}
method="PUT"
title="Edit Purchase Order" title="Edit Purchase Order"
opened={poFormOpened} opened={poFormOpened}
onClose={() => setPoFormOpened(false)} onClose={() => setPoFormOpened(false)}
fetchInitialData={true}
/> />
<ApiForm <EditApiForm
name="company-edit" name="company-edit"
url="/company/" url="/company/"
pk={1} pk={1}
fields={companyFields} fields={companyFields}
method="PUT"
title="Edit Company" title="Edit Company"
opened={companyFormOpened} opened={companyFormOpened}
onClose={() => setCompanyFormOpened(false)} onClose={() => setCompanyFormOpened(false)}
fetchInitialData={true}
/> />
<DeleteApiForm
name="stock-delete"
url="/stock/"
title="Delete Stock Item"
pk={1}
fields={[]}
opened={stockFormOpened}
onClose={() => setStockFormOpened(false)}
/>
<CreateApiForm
name="sales-order-create"
url="/order/so/"
title="Create Sales Order"
fields={salesOrderFields}
opened={salesOrderFormOpened}
onClose={() => setSalesOrderFormOpened(false)}
/>
<Stack align="flex-start" spacing="xs"> <Stack align="flex-start" spacing="xs">
<Button <Button
onClick={() => setPartFormOpened(true)} onClick={() => setPartFormOpened(true)}
@ -146,6 +175,20 @@ export default function Home() {
> >
Edit Company Form Edit Company Form
</Button> </Button>
<Button
variant="outline"
color="green"
onClick={() => setSalesOrderFormOpened(true)}
>
Create Sales Order Form
</Button>
<Button
variant="outline"
color="red"
onClick={() => setStockFormOpened(true)}
>
Delete Stock Item Form
</Button>
</Stack> </Stack>
</> </>
); );