From 502e78d1ad1916752758f274b52fc66cf9560d4a Mon Sep 17 00:00:00 2001 From: Oliver Walters Date: Thu, 27 Jul 2023 20:48:43 +1000 Subject: [PATCH] Split ApiFormField into separate file --- src/frontend/src/components/forms/ApiForm.tsx | 243 +----------------- .../src/components/forms/ApiFormField.tsx | 233 +++++++++++++++++ 2 files changed, 238 insertions(+), 238 deletions(-) create mode 100644 src/frontend/src/components/forms/ApiFormField.tsx diff --git a/src/frontend/src/components/forms/ApiForm.tsx b/src/frontend/src/components/forms/ApiForm.tsx index 4fc6e8b5f6..ae4ee5034c 100644 --- a/src/frontend/src/components/forms/ApiForm.tsx +++ b/src/frontend/src/components/forms/ApiForm.tsx @@ -1,253 +1,20 @@ -import { t } from '@lingui/macro'; import { Alert, - Checkbox, Divider, LoadingOverlay, Modal, - NumberInput, - ScrollArea, - Select, - TextInput + ScrollArea } from '@mantine/core'; -import { Button, Center, Group, Loader, Stack, Text } from '@mantine/core'; -import { DateInput } from '@mantine/dates'; -import { UseFormReturnType, useForm } from '@mantine/form'; -import { useDebouncedValue } from '@mantine/hooks'; +import { Button, Group, Loader, Stack } 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 { ReactNode, useEffect } from 'react'; +import { useEffect } from 'react'; import { useState } from 'react'; -import { useMemo } from 'react'; import { api } from '../../App'; - -/* Definition of the ApiForm field component. - * - The 'name' attribute *must* be provided - * - All other attributes are optional, and may be provided by the API - * - However, they can be overridden by the user - */ -export type ApiFormFieldType = { - name: string; - label?: string; - value?: any; - default?: any; - icon?: ReactNode; - fieldType?: string; - api_url?: string; - model?: string; - required?: boolean; - hidden?: boolean; - disabled?: boolean; - placeholder?: string; - description?: string; - errors?: string[]; - error?: any; -}; - -/* - * Build a complete field definition based on the provided data - */ -function constructField({ - form, - field, - definitions -}: { - form: UseFormReturnType>; - field: ApiFormFieldType; - definitions: ApiFormFieldType[]; -}) { - let def = definitions.find((def) => def.name == field.name) || field; - - def = { - ...def, - ...field - }; - - // Format the errors - if (def.errors?.length == 1) { - def.error = def.errors[0]; - } else if (def.errors?.length ?? 0 > 1) { - // TODO: Build a custom error stack? - } else { - def.error = null; - } - - // Retrieve the latest value from the form - let value = form.values[def.name]; - - if (value != undefined) { - def.value = value; - } - - // Change value to a date object if required - switch (def.fieldType) { - case 'date': - if (def.value) { - def.value = new Date(def.value); - } - break; - default: - break; - } - - return def; -} - -/** - * Render a 'select' field for searching the database against a particular model type - */ -function RelatedModelField({ - form, - field, - definitions -}: { - form: UseFormReturnType>; - field: ApiFormFieldType; - definitions: ApiFormFieldType[]; -}) { - // Extract field definition from provided data - // Where user has provided specific data, override the API definition - const definition: ApiFormFieldType = useMemo( - () => - constructField({ - form: form, - field: field, - definitions: definitions - }), - [form.values, field, definitions] - ); - - const [value, setValue] = useState(''); - const [searchText] = useDebouncedValue(value, 500); - - const selectQuery = useQuery({ - enabled: !definition.disabled && !!definition.api_url && !definition.hidden, - queryKey: [`related-field-${definition.name}`, searchText], - queryFn: async () => { - console.log('Searching for', searchText); - } - }); - - function onSearchChange(value: string) { - console.log('Search change:', value, definition.api_url, definition.model); - setValue(value); - } - - return ( - + ); +} + +/** + * Render an individual form field + */ +export function ApiFormField({ + form, + field, + definitions, + onValueChange +}: { + form: UseFormReturnType>; + field: ApiFormFieldType; + definitions: ApiFormFieldType[]; + onValueChange: (fieldName: string, value: any) => void; +}) { + // Extract field definition from provided data + // Where user has provided specific data, override the API definition + const definition: ApiFormFieldType = useMemo( + () => + constructField({ + form: form, + field: field, + definitions: definitions + }), + [form.values, field, definitions] + ); + + // Callback helper when form value changes + function onChange(value: any) { + // onValueChange(definition.name, value); + form.setValues({ [definition.name]: value }); + } + + switch (definition.fieldType) { + case 'related field': + return ( + + ); + case 'url': + return ( + onChange(event.currentTarget.value)} + /> + ); + case 'email': + return ( + onChange(event.currentTarget.value)} + /> + ); + case 'string': + return ( + onChange(event.currentTarget.value)} + /> + ); + case 'boolean': + return ( + onChange(event.currentTarget.checked)} + /> + ); + case 'date': + return ( + onChange(value)} + /> + ); + case 'integer': + case 'decimal': + case 'float': + case 'number': + return ( + onChange(value)} + /> + ); + default: + return ( + + Unknown field type for field '{definition.name}': ' + {definition.fieldType}' + + ); + } +}