Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
car-crushers-portal/components/FormGenerator.tsx
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
264 lines (231 sloc)
6.47 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { | |
Checkbox, | |
CheckboxGroup, | |
FormControl, | |
FormErrorMessage, | |
Heading, | |
HStack, | |
Input, | |
NumberDecrementStepper, | |
NumberIncrementStepper, | |
NumberInput, | |
NumberInputField, | |
NumberInputStepper, | |
Radio, | |
RadioGroup, | |
Select, | |
Stack, | |
Textarea, | |
} from "@chakra-ui/react"; | |
import { type Dispatch, type SetStateAction, useState } from "react"; | |
interface component { | |
id: string; | |
max_length?: number; | |
options?: { default?: boolean; value: string }[]; | |
required: boolean; | |
title: string; | |
type: string; | |
value?: number | string | string[]; | |
} | |
export default function ({ | |
components, | |
read_only = true, | |
}: { | |
components: { [k: number]: component[] }; | |
read_only: boolean; | |
}) { | |
function isNumberElemInvalid(e: HTMLInputElement): boolean { | |
return !( | |
e.value || | |
isNaN(e.valueAsNumber) || | |
e.valueAsNumber <= Number.MAX_SAFE_INTEGER || | |
e.valueAsNumber >= Number.MIN_SAFE_INTEGER | |
); | |
} | |
function updateState( | |
state: { [k: string]: string | string[] }, | |
setState: Dispatch<SetStateAction<{}>>, | |
id: string, | |
value: string, | |
) { | |
const newState = { ...state }; | |
newState[id] = value; | |
setState(newState); | |
} | |
function renderCheckboxOptions( | |
c: component, | |
state: { [k: string]: string | string[] }, | |
setState: Dispatch<SetStateAction<{}>>, | |
) { | |
if (!c.options) throw new Error("Options for checkbox are undefined"); | |
const boxes = []; | |
const checkedBoxes = []; | |
for (const option of c.options) { | |
if ( | |
option.default || | |
(read_only && Array.isArray(c.value) && c.value.includes(option.value)) | |
) | |
checkedBoxes.push(option.value); | |
boxes.push( | |
<Checkbox | |
isReadOnly={read_only} | |
onChange={(e) => { | |
const newState = { ...state }; | |
const groupValues = newState[c.id] ?? []; | |
if (!Array.isArray(groupValues)) | |
throw new Error("Expected CheckboxGroup values to be an array"); | |
e.target.checked | |
? groupValues.push(e.target.value) | |
: groupValues.splice( | |
groupValues.findIndex((v) => v === e.target.value), | |
1, | |
); | |
newState[c.id] = groupValues; | |
setState(newState); | |
}} | |
value={option.value} | |
> | |
{option.value} | |
</Checkbox>, | |
); | |
} | |
return ( | |
<CheckboxGroup defaultValue={checkedBoxes}> | |
<HStack spacing={5}>{boxes}</HStack> | |
</CheckboxGroup> | |
); | |
} | |
function renderRadioElements( | |
c: component, | |
state: { [k: string]: string | string[] }, | |
setState: Dispatch<SetStateAction<{}>>, | |
) { | |
if (!c.options) throw new Error("Options for radio buttons are undefined!"); | |
const buttons = []; | |
for (const option of c.options) { | |
buttons.push( | |
<Radio checked={option.default} value={option.value}> | |
{option.value} | |
</Radio>, | |
); | |
} | |
return ( | |
<RadioGroup | |
id={c.id} | |
onChange={(e) => { | |
const newState = { ...state }; | |
newState[c.id] = e; | |
setState(newState); | |
}} | |
> | |
<Stack direction="row">{buttons}</Stack> | |
</RadioGroup> | |
); | |
} | |
function renderSelectElements( | |
c: component, | |
state: { [k: string]: string | string[] }, | |
setState: Dispatch<SetStateAction<{}>>, | |
) { | |
if (!c.options) throw new Error("Options for select are undefined!"); | |
const selectOptions = []; | |
for (const option of c.options) { | |
selectOptions.push(<option value={option.value}>{option.value}</option>); | |
} | |
return ( | |
<Select | |
onChange={(e) => { | |
const newState = { ...state }; | |
newState[c.id] = e.target.value; | |
setState(newState); | |
}} | |
placeholder="Select option" | |
> | |
{selectOptions} | |
</Select> | |
); | |
} | |
function generateReactComponents( | |
components: component[], | |
state: { [k: string]: string | string[] }, | |
setState: Dispatch<SetStateAction<{}>>, | |
): JSX.Element[] { | |
const fragmentsList = []; | |
for (const component of components) { | |
fragmentsList.push( | |
<Heading size="md">{component.title}</Heading>, | |
<br />, | |
); | |
switch (component.type) { | |
case "checkbox": | |
fragmentsList.push(renderCheckboxOptions(component, state, setState)); | |
break; | |
case "input": | |
fragmentsList.push( | |
<FormControl | |
isInvalid={ | |
!(document.getElementById(component.id) as HTMLInputElement) | |
.value.length | |
} | |
isReadOnly={read_only} | |
> | |
<Input | |
id={component.id} | |
maxLength={component.max_length} | |
onChange={(e) => | |
updateState(state, setState, component.id, e.target.value) | |
} | |
placeholder="Your response" | |
value={component.value} | |
/> | |
<FormErrorMessage>Field is required</FormErrorMessage> | |
</FormControl>, | |
); | |
break; | |
case "number": | |
fragmentsList.push( | |
<NumberInput | |
isInvalid={isNumberElemInvalid( | |
document.getElementById(component.id) as HTMLInputElement, | |
)} | |
isReadOnly={read_only} | |
> | |
<NumberInputField | |
id={component.id} | |
onChange={(e) => | |
updateState(state, setState, component.id, e.target.value) | |
} | |
value={component.value} | |
/> | |
<NumberInputStepper> | |
<NumberIncrementStepper /> | |
<NumberDecrementStepper /> | |
</NumberInputStepper> | |
</NumberInput>, | |
); | |
break; | |
case "radio": | |
fragmentsList.push(renderRadioElements(component, state, setState)); | |
break; | |
case "select": | |
fragmentsList.push(renderSelectElements(component, state, setState)); | |
break; | |
} | |
fragmentsList.push(<br />, <br />, <br />); | |
} | |
return fragmentsList; | |
} | |
const pages = []; | |
const [responses, setResponses] = useState({}); | |
for (const [page, componentList] of Object.entries(components)) { | |
pages.push( | |
<div | |
id={`form-page-${page}`} | |
style={{ display: page ? "none" : undefined }} | |
> | |
{generateReactComponents(componentList, responses, setResponses)} | |
</div>, | |
); | |
} | |
return <></>; | |
} |