2
0
mirror of https://github.com/inventree/InvenTree.git synced 2025-06-15 03:25:42 +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;
label?: string;
value?: any;
default?: any;
icon?: ReactNode;
fieldType?: string;
api_url?: string;
@ -249,8 +250,7 @@ function ApiFormField({
}
/**
* An ApiForm component is a modal form which is rendered dynamically,
* based on an API endpoint.
* Properties for the ApiForm component
* @param url : The API endpoint to fetch the form from.
* @param fields : The fields to render in the form.
* @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 onFormError : A callback function to call when the form is submitted with errors.
*/
export function ApiForm({
name,
url,
pk,
title,
fields,
opened,
onClose,
onFormSuccess,
onFormError,
cancelText = t`Cancel`,
submitText = t`Submit`,
method = 'PUT',
fetchInitialData = false
}: {
export interface ApiFormProps {
name: string;
url: string;
pk?: number;
@ -280,13 +266,22 @@ export function ApiForm({
fields: ApiFormFieldType[];
cancelText?: string;
submitText?: string;
submitColor?: string;
cancelColor?: string;
fetchInitialData?: boolean;
method?: string;
opened: boolean;
onClose?: () => void;
onFormSuccess?: () => 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
const form = useForm({});
@ -300,8 +295,8 @@ export function ApiForm({
// Query manager for retrieving form definition from the server
const definitionQuery = useQuery({
enabled: opened && !!url,
queryKey: ['form-definition', name, url, pk],
enabled: props.opened && !!props.url,
queryKey: ['form-definition', name, props.url, props.pk],
queryFn: async () => {
// Clear form construction error field
setError('');
@ -320,8 +315,12 @@ export function ApiForm({
// Query manager for retrieiving initial data from the server
const initialDataQuery = useQuery({
enabled: fetchInitialData && opened && !!url && fieldDefinitions.length > 0,
queryKey: ['form-initial-data', name, url, pk],
enabled:
props.fetchInitialData &&
props.opened &&
!!props.url &&
fieldDefinitions.length > 0,
queryKey: ['form-initial-data', name, props.url, props.pk],
queryFn: async () => {
return api
.get(getUrl())
@ -357,10 +356,14 @@ export function ApiForm({
// Construct a fully-qualified URL based on the provided details
function getUrl(): string {
let u = url;
if (!props.url) {
return '';
}
if (pk && pk > 0) {
u += `${pk}/`;
let u = props.url;
if (props.pk && props.pk > 0) {
u += `${props.pk}/`;
}
return u;
@ -374,10 +377,14 @@ export function ApiForm({
function extractFieldDefinitions(
response: AxiosResponse
): ApiFormFieldType[] {
let actions = response.data?.actions[method.toUpperCase()] || [];
if (!props.method) {
return [];
}
let actions = response.data?.actions[props.method.toUpperCase()] || [];
if (actions.length == 0) {
setError(`Permission denied for ${method} at ${url}`);
setError(`Permission denied for ${props.method} at ${props.url}`);
return [];
}
@ -389,7 +396,7 @@ export function ApiForm({
name: fieldName,
label: field.label,
description: field.help_text,
value: field.value,
value: field.value || field.default,
fieldType: field.type,
required: field.required,
placeholder: field.placeholder,
@ -405,11 +412,11 @@ export function ApiForm({
<Modal
size="xl"
radius="sm"
opened={opened}
opened={props.opened}
onClose={() => {
onClose ? onClose() : null;
props.onClose ? props.onClose() : null;
}}
title={title}
title={props.title}
>
<Stack>
<Divider />
@ -430,7 +437,7 @@ export function ApiForm({
{canRender && (
<ScrollArea>
<Stack spacing="md">
{fields.map((field) => (
{props.fields.map((field) => (
<ApiFormField
key={field.name}
field={field}
@ -447,19 +454,24 @@ export function ApiForm({
</Stack>
<Divider />
<Group position="right">
<Button onClick={onClose} variant="outline" radius="sm" color="red">
{cancelText}
<Button
onClick={props.onClose}
variant="outline"
radius="sm"
color={props.cancelColor ?? 'blue'}
>
{props.cancelText ?? `Cancel`}
</Button>
<Button
onClick={() => null}
variant="outline"
radius="sm"
color="green"
color={props.submitColor ?? 'green'}
disabled={!canSubmit}
>
<Group position="right" spacing={5} noWrap={true}>
<Loader size="xs" />
{submitText}
{props.submitText ?? `Submit`}
</Group>
</Button>
</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 { 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 { StylishText } from '../../components/items/StylishText';
@ -18,6 +21,8 @@ export default function Home() {
const [partFormOpened, setPartFormOpened] = useState(false);
const [poFormOpened, setPoFormOpened] = useState(false);
const [companyFormOpened, setCompanyFormOpened] = useState(false);
const [stockFormOpened, setStockFormOpened] = useState(false);
const [salesOrderFormOpened, setSalesOrderFormOpened] = useState(false);
const partFields: ApiFormFieldType[] = [
{
@ -59,6 +64,18 @@ export default function Home() {
}
];
const salesOrderFields: ApiFormFieldType[] = [
{
name: 'reference'
},
{
name: 'customer'
},
{
name: 'description'
}
];
const companyFields: ApiFormFieldType[] = [
{
name: 'name'
@ -91,39 +108,51 @@ export default function Home() {
</StylishText>
<PlaceholderPill />
</Group>
<ApiForm
<EditApiForm
name="part-edit"
url="/part/"
pk={1}
fields={partFields}
method="PUT"
title="Edit Part"
opened={partFormOpened}
onClose={() => setPartFormOpened(false)}
fetchInitialData={true}
/>
<ApiForm
<EditApiForm
name="po-edit"
url="/order/po/"
pk={1}
fields={poFields}
method="PUT"
title="Edit Purchase Order"
opened={poFormOpened}
onClose={() => setPoFormOpened(false)}
fetchInitialData={true}
/>
<ApiForm
<EditApiForm
name="company-edit"
url="/company/"
pk={1}
fields={companyFields}
method="PUT"
title="Edit Company"
opened={companyFormOpened}
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">
<Button
onClick={() => setPartFormOpened(true)}
@ -146,6 +175,20 @@ export default function Home() {
>
Edit Company Form
</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>
</>
);