From 3a24e7a27f58ea9d16d8b0cd7b00edd54e354188 Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 27 Jul 2023 20:43:02 +1000 Subject: [PATCH] Create components for different form types - Create - Edit - Delete --- src/frontend/src/components/forms/ApiForm.tsx | 84 +++++++++++-------- .../src/components/forms/CreateApiForm.tsx | 18 ++++ .../src/components/forms/DeleteApiForm.tsx | 21 +++++ .../src/components/forms/EditApiForm.tsx | 19 +++++ src/frontend/src/pages/Index/Home.tsx | 61 ++++++++++++-- 5 files changed, 158 insertions(+), 45 deletions(-) create mode 100644 src/frontend/src/components/forms/CreateApiForm.tsx create mode 100644 src/frontend/src/components/forms/DeleteApiForm.tsx create mode 100644 src/frontend/src/components/forms/EditApiForm.tsx diff --git a/src/frontend/src/components/forms/ApiForm.tsx b/src/frontend/src/components/forms/ApiForm.tsx index 81c8004ea0..4fc6e8b5f6 100644 --- a/src/frontend/src/components/forms/ApiForm.tsx +++ b/src/frontend/src/components/forms/ApiForm.tsx @@ -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({ { - onClose ? onClose() : null; + props.onClose ? props.onClose() : null; }} - title={title} + title={props.title} > @@ -430,7 +437,7 @@ export function ApiForm({ {canRender && ( - {fields.map((field) => ( + {props.fields.map((field) => ( - diff --git a/src/frontend/src/components/forms/CreateApiForm.tsx b/src/frontend/src/components/forms/CreateApiForm.tsx new file mode 100644 index 0000000000..c19d8f6318 --- /dev/null +++ b/src/frontend/src/components/forms/CreateApiForm.tsx @@ -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 ; +} diff --git a/src/frontend/src/components/forms/DeleteApiForm.tsx b/src/frontend/src/components/forms/DeleteApiForm.tsx new file mode 100644 index 0000000000..15d54168f9 --- /dev/null +++ b/src/frontend/src/components/forms/DeleteApiForm.tsx @@ -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 ; +} diff --git a/src/frontend/src/components/forms/EditApiForm.tsx b/src/frontend/src/components/forms/EditApiForm.tsx new file mode 100644 index 0000000000..cb1ca207e2 --- /dev/null +++ b/src/frontend/src/components/forms/EditApiForm.tsx @@ -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 ; +} diff --git a/src/frontend/src/pages/Index/Home.tsx b/src/frontend/src/pages/Index/Home.tsx index a2f0233e71..b2a1a2a659 100644 --- a/src/frontend/src/pages/Index/Home.tsx +++ b/src/frontend/src/pages/Index/Home.tsx @@ -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() { - setPartFormOpened(false)} - fetchInitialData={true} /> - setPoFormOpened(false)} - fetchInitialData={true} /> - setCompanyFormOpened(false)} - fetchInitialData={true} /> + setStockFormOpened(false)} + /> + setSalesOrderFormOpened(false)} + /> + + + );