Refactor editing components into their own file

This commit is contained in:
arttu
2024-05-24 11:43:53 +03:00
committed by ShootingStar91
parent 1096d26280
commit 2851b96ea9
5 changed files with 137 additions and 117 deletions
@@ -1,5 +1,5 @@
import { ReactNode, createContext, useState, JSX } from 'react'
import { DropdownOption } from '../common/FormComponents'
import { DropdownOption } from '../common/editingComponents'
export type ModeType = 'read' | 'new' | 'edit'
@@ -6,8 +6,9 @@ import SaveIcon from '@mui/icons-material/Save'
import { useEffect, useState, JSX } from 'react'
import { DetailContextProvider, ModeType } from './Context/DetailContext'
import { cloneDeep } from 'lodash-es'
import { DropdownOption, DropdownSelector, EditableTextField, RadioSelector } from './common/FormComponents'
import { EditableTextField } from './common/FormComponents'
import { useDetailContext } from './hooks'
import { DropdownOption, DropdownSelector, RadioSelector } from './common/editingComponents'
export type TabType = {
title: string
@@ -1,24 +1,6 @@
import {
Card,
Typography,
Box,
Grid,
Divider,
Modal,
Button,
TextField,
FormControl,
InputLabel,
MenuItem,
Select,
SelectChangeEvent,
FormControlLabel,
Radio,
RadioGroup,
} from '@mui/material'
import { Card, Typography, Box, Grid, Divider, Modal, Button, TextField } from '@mui/material'
import { ReactNode, useState, ChangeEvent } from 'react'
import { useDetailContext } from '../hooks'
import { RegisterOptions, FieldValues, UseFormRegisterReturn, FieldErrors } from 'react-hook-form'
export const ArrayToTable = ({ array, half }: { array: Array<Array<ReactNode>>; half?: boolean }) => {
const maxRowLength = Math.max(...array.map(row => row.length))
@@ -149,101 +131,6 @@ export const EditableTextField = <T extends object>({
return <DataValue<T> field={field} EditElement={editingComponent} />
}
export type DropdownOption = { value: string; display: string }
export const DropdownSelector = <T extends object>({
options,
name,
field,
}: {
options: Array<DropdownOption | string>
name: string
field: keyof T
}) => {
const { data, setEditData, editData } = useDetailContext<T>()
const editingComponent = (
<FormControl size="small">
<InputLabel id={`${name}-multiselect-label`}>{name}</InputLabel>
<Select
labelId={`${name}-multiselect-label`}
label={name}
id={`${name}-multiselect`}
value={(editData[field] || '') as string}
onChange={(event: SelectChangeEvent) => setEditData({ ...editData, [field]: event.target.value })}
sx={{ width: '12em' }}
size="small"
>
{options.map(item => (
<MenuItem key={getValue(item)} value={getValue(item)}>
{getDisplay(item)}
</MenuItem>
))}
</Select>
</FormControl>
)
const option = options.find(option => getValue(option) === data[field])
const displayValue = option ? getDisplay(option) : null
return <DataValue<T> field={field} EditElement={editingComponent} displayValue={displayValue} />
}
export const FormTextField = <T extends string>({
register,
errors,
fieldName,
label,
required = false,
}: {
register: (name: T, options?: RegisterOptions<FieldValues, T> | undefined) => UseFormRegisterReturn<T>
errors: FieldErrors<FieldValues>
fieldName: T
label: string
required: boolean
}) => (
<TextField {...register(fieldName, { required: required })} error={!!errors[fieldName]} {...{ label, required }} />
)
const getValue = (item: DropdownOption | string) => (typeof item === 'string' ? item : item.value)
const getDisplay = (item: DropdownOption | string) => (typeof item === 'string' ? item : item.display)
export const RadioSelector = <T extends object>({
options,
name,
field,
}: {
options: Array<DropdownOption | string>
name: string
field: keyof T
}) => {
const { data, setEditData, editData } = useDetailContext<T>()
const editingComponent = (
<FormControl>
<RadioGroup
aria-labelledby={`${name}-radio-selection`}
name={name}
onChange={(event: ChangeEvent<HTMLInputElement>) =>
setEditData({ ...editData, [field]: event?.currentTarget?.value })
}
value={editData[field]}
sx={{ display: 'flex', flexDirection: 'row' }}
>
{options.map(option => (
<FormControlLabel
key={getValue(option)}
value={getValue(option)}
control={<Radio />}
label={getDisplay(option)}
/>
))}
</RadioGroup>
</FormControl>
)
const option = options.find(option => getValue(option) === data[field])
const displayValue = option ? getDisplay(option) : null
return <DataValue<T> field={field} EditElement={editingComponent} displayValue={displayValue} />
}
/*
buttonText = Text for the button that opens modal
children = Content of modal
@@ -0,0 +1,121 @@
import {
FormControl,
InputLabel,
Select,
SelectChangeEvent,
MenuItem,
TextField,
RadioGroup,
FormControlLabel,
Radio,
} from '@mui/material'
import { ChangeEvent, ReactNode } from 'react'
import { RegisterOptions, FieldValues, UseFormRegisterReturn, FieldErrors } from 'react-hook-form'
import { useDetailContext } from '../hooks'
import { DataValue } from './FormComponents'
export type DropdownOption = { value: string; display: string }
export const DropdownSelector = <T extends object>({
options,
name,
field,
}: {
options: Array<DropdownOption | string>
name: string
field: keyof T
}) => {
const { setEditData, editData } = useDetailContext<T>()
const editingComponent = (
<FormControl size="small">
<InputLabel id={`${name}-multiselect-label`}>{name}</InputLabel>
<Select
labelId={`${name}-multiselect-label`}
label={name}
id={`${name}-multiselect`}
value={(editData[field] || '') as string}
onChange={(event: SelectChangeEvent) => setEditData({ ...editData, [field]: event.target.value })}
sx={{ width: '12em' }}
size="small"
>
{options.map(item => (
<MenuItem key={getValue(item)} value={getValue(item)}>
{getDisplay(item)}
</MenuItem>
))}
</Select>
</FormControl>
)
return <MultiSelector<T> {...{ editingComponent, field, options }} />
}
export const FormTextField = <T extends string>({
register,
errors,
fieldName,
label,
required = false,
}: {
register: (name: T, options?: RegisterOptions<FieldValues, T> | undefined) => UseFormRegisterReturn<T>
errors: FieldErrors<FieldValues>
fieldName: T
label: string
required: boolean
}) => (
<TextField {...register(fieldName, { required: required })} error={!!errors[fieldName]} {...{ label, required }} />
)
const getValue = (item: DropdownOption | string) => (typeof item === 'string' ? item : item.value)
const getDisplay = (item: DropdownOption | string) => (typeof item === 'string' ? item : item.display)
export const RadioSelector = <T extends object>({
options,
name,
field,
}: {
options: Array<DropdownOption | string>
name: string
field: keyof T
}) => {
const { setEditData, editData } = useDetailContext<T>()
const editingComponent = (
<FormControl>
<RadioGroup
aria-labelledby={`${name}-radio-selection`}
name={name}
onChange={(event: ChangeEvent<HTMLInputElement>) =>
setEditData({ ...editData, [field]: event?.currentTarget?.value })
}
value={editData[field]}
sx={{ display: 'flex', flexDirection: 'row' }}
>
{options.map(option => (
<FormControlLabel
key={getValue(option)}
value={getValue(option)}
control={<Radio />}
label={getDisplay(option)}
/>
))}
</RadioGroup>
</FormControl>
)
return <MultiSelector<T> {...{ editingComponent, field, options }} />
}
const MultiSelector = <T extends object>({
options,
field,
editingComponent,
}: {
options: Array<DropdownOption | string>
field: keyof T
editingComponent: ReactNode
}) => {
const { data } = useDetailContext<T>()
const option = options.find(option => getValue(option) === data[field])
const displayValue = option ? getDisplay(option) : null
return <DataValue<T> field={field} EditElement={editingComponent} displayValue={displayValue} />
}
@@ -16,7 +16,18 @@ export const AgeTab = () => {
// TODO: The date_meth options should come from db: distinct(now_loc.date_meth)
const age = [
['Dating method', radioSelection('date_meth', ['time_unit', 'absolute', 'composite'], 'dating-method')],
[
'Dating method',
radioSelection(
'date_meth',
[
{ value: 'time_unit', display: 'Time unit' },
{ value: 'absolute', display: 'Absolute' },
{ value: 'composite', display: 'Composite' },
],
'dating-method'
),
],
[''],
['Age (Ma)', 'Basis for age (Absolute)', 'Basis for age (Time Unit)', 'Basis for age (Fraction)'],
[