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:
@ -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>
|
||||
|
18
src/frontend/src/components/forms/CreateApiForm.tsx
Normal file
18
src/frontend/src/components/forms/CreateApiForm.tsx
Normal 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} />;
|
||||
}
|
21
src/frontend/src/components/forms/DeleteApiForm.tsx
Normal file
21
src/frontend/src/components/forms/DeleteApiForm.tsx
Normal 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} />;
|
||||
}
|
19
src/frontend/src/components/forms/EditApiForm.tsx
Normal file
19
src/frontend/src/components/forms/EditApiForm.tsx
Normal 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} />;
|
||||
}
|
@ -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>
|
||||
</>
|
||||
);
|
||||
|
Reference in New Issue
Block a user