diff --git a/src/frontend/src/components/forms/ApiForm.tsx b/src/frontend/src/components/forms/ApiForm.tsx
index c04b49d48c..79779c6cda 100644
--- a/src/frontend/src/components/forms/ApiForm.tsx
+++ b/src/frontend/src/components/forms/ApiForm.tsx
@@ -1,7 +1,8 @@
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 { useForm } from '@mantine/form';
+import { IconAlertCircle } from '@tabler/icons-react';
import { useQuery } from '@tanstack/react-query';
import { AxiosResponse } from 'axios';
import { useEffect } from 'react';
@@ -15,12 +16,13 @@ import { api } from '../../App';
* - All other attributes are optional, and may be provided by the API
* - However, they can be overridden by the user
*/
-export type ApiFormField = {
+export type ApiFormFieldType = {
name: string;
label?: string;
value?: any;
type?: string;
required?: boolean;
+ hidden?: boolean;
placeholder?: string;
help_text?: string;
icon?: string;
@@ -28,13 +30,21 @@ export type ApiFormField = {
};
/**
- * 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.
+ * Render an individual form field
*/
-function extractFieldDefinitions(response: AxiosResponse): ApiFormField[] {
- // TODO: [];
- return [];
+function ApiFormField({
+ field,
+ definitions
+}: {
+ field: ApiFormFieldType;
+ definitions: ApiFormFieldType[];
+}) {
+ useEffect(() => {
+ console.log('field:', field);
+ console.log('definitions:', definitions);
+ }, []);
+
+ return {field.name};
}
/**
@@ -58,15 +68,17 @@ export function ApiForm({
onFormSuccess,
onFormError,
cancelText = t`Cancel`,
- submitText = t`Submit`
+ submitText = t`Submit`,
+ method = 'PUT'
}: {
name: string;
url: string;
pk?: number;
title: string;
- fields: ApiFormField[];
+ fields: ApiFormFieldType[];
cancelText?: string;
submitText?: string;
+ method?: string;
opened: boolean;
onClose?: () => void;
onFormSuccess?: () => void;
@@ -76,35 +88,96 @@ export function ApiForm({
const form = useForm({});
// Form field definitions (via API)
- const [fieldDefinitions, setFieldDefinitions] = useState([]);
+ const [fieldDefinitions, setFieldDefinitions] = useState(
+ []
+ );
+
+ // Error observed during form construction
+ const [error, setError] = useState('');
const definitionQuery = useQuery({
enabled: opened && !!url,
queryKey: ['form-definition', name, url, pk, fields],
queryFn: async () => {
- let _url = url;
-
- if (pk && pk > 0) {
- _url += `${pk}/`;
- }
- console.log('Fetching form definition from API:', _url);
-
+ // Clear form construction error field
+ setError('');
return api
- .options(_url)
+ .options(getUrl())
.then((response) => {
- console.log('response:', response);
setFieldDefinitions(extractFieldDefinitions(response));
return response;
})
.catch((error) => {
- console.error('error:', error);
+ console.error('Error fetching field definitions:', error);
+ setError(error.message);
setFieldDefinitions([]);
});
}
});
+ // State variable to determine if the form can render the data
+ const [canRender, setCanRender] = useState(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 (
-
+
{definitionQuery.isFetching && (
@@ -112,13 +185,38 @@ export function ApiForm({
)}
- {!definitionQuery.isFetching && Form definition fetched!}
+ {error && (
+ }
+ >
+ {error}
+
+ )}
+ {canRender && (
+
+ {fields.map((field) => (
+
+ ))}
+
+ )}
-
diff --git a/src/frontend/src/pages/Index/Home.tsx b/src/frontend/src/pages/Index/Home.tsx
index 00e32b5ea4..6ba15cc198 100644
--- a/src/frontend/src/pages/Index/Home.tsx
+++ b/src/frontend/src/pages/Index/Home.tsx
@@ -3,14 +3,14 @@ import { Group } from '@mantine/core';
import { Button } from '@mantine/core';
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 { StylishText } from '../../components/items/StylishText';
export default function Home() {
const [formOpened, setFormOpened] = useState(false);
- const fields: ApiFormField[] = [
+ const fields: ApiFormFieldType[] = [
{
name: 'name'
},
@@ -31,6 +31,7 @@ export default function Home() {
name="part-edit"
url="/part/1/"
fields={fields}
+ method="PUT"
title="Edit Part"
opened={formOpened}
onClose={() => setFormOpened(false)}