mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Merge pull request #1154 from nowcommunity/codex/species-taxonomy-suggestions
[codex] Add species taxonomy suggestions
This commit is contained in:
@@ -7,14 +7,35 @@ import { SelectingTable } from '@/components/DetailView/common/SelectingTable'
|
||||
import { useGetAllSpeciesQuery } from '@/redux/speciesReducer'
|
||||
import { smallSpeciesTableColumns } from '@/common'
|
||||
import { SynonymsModal } from '../SynonymsModal'
|
||||
import { useState } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { taxonStatusOptions } from '@/shared/taxonStatusOptions'
|
||||
import { TaxonomySuggestionField } from '../TaxonomySuggestionField'
|
||||
import {
|
||||
buildTaxonomySuggestionOptions,
|
||||
TaxonomySuggestionField as TaxonomySuggestionFieldName,
|
||||
} from '../taxonomySuggestions'
|
||||
|
||||
export const TaxonomyTab = () => {
|
||||
const { textField, dropdown, bigTextField, editData, setEditData, mode } = useDetailContext<SpeciesDetailsType>()
|
||||
const { data: speciesQueryData, isError } = useGetAllSpeciesQuery()
|
||||
const [selectedSpecies, setSelectedSpecies] = useState<string | undefined>()
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false)
|
||||
const taxonomySuggestionOptions = useMemo(
|
||||
() => ({
|
||||
subclass_or_superorder_name: buildTaxonomySuggestionOptions(speciesQueryData, 'subclass_or_superorder_name'),
|
||||
order_name: buildTaxonomySuggestionOptions(speciesQueryData, 'order_name'),
|
||||
suborder_or_superfamily_name: buildTaxonomySuggestionOptions(speciesQueryData, 'suborder_or_superfamily_name'),
|
||||
family_name: buildTaxonomySuggestionOptions(speciesQueryData, 'family_name'),
|
||||
subfamily_name: buildTaxonomySuggestionOptions(speciesQueryData, 'subfamily_name'),
|
||||
genus_name: buildTaxonomySuggestionOptions(speciesQueryData, 'genus_name'),
|
||||
species_name: buildTaxonomySuggestionOptions(speciesQueryData, 'species_name'),
|
||||
}),
|
||||
[speciesQueryData]
|
||||
)
|
||||
|
||||
const taxonomySuggestionField = (field: TaxonomySuggestionFieldName) => (
|
||||
<TaxonomySuggestionField field={field} options={taxonomySuggestionOptions[field]} />
|
||||
)
|
||||
|
||||
const handleRowActionClick = (row: Species) => {
|
||||
setSelectedSpecies(row.species_id.toString())
|
||||
@@ -55,28 +76,18 @@ export const TaxonomyTab = () => {
|
||||
)
|
||||
|
||||
const classification = [
|
||||
[
|
||||
'Class',
|
||||
'Mammalia',
|
||||
'Subclass or Superorder',
|
||||
textField('subclass_or_superorder_name', { type: 'text', trim: true }),
|
||||
],
|
||||
['Class', 'Mammalia', 'Subclass or Superorder', taxonomySuggestionField('subclass_or_superorder_name')],
|
||||
[
|
||||
'Order',
|
||||
textField('order_name', { type: 'text', trim: true }),
|
||||
taxonomySuggestionField('order_name'),
|
||||
'Suborder or Superfamily',
|
||||
textField('suborder_or_superfamily_name', { type: 'text', trim: true }),
|
||||
taxonomySuggestionField('suborder_or_superfamily_name'),
|
||||
],
|
||||
[
|
||||
'Family',
|
||||
textField('family_name', { type: 'text', trim: true }),
|
||||
'Subfamily or Tribe',
|
||||
textField('subfamily_name', { type: 'text', trim: true }),
|
||||
],
|
||||
['Genus', textField('genus_name', { type: 'text', trim: true })],
|
||||
['Family', taxonomySuggestionField('family_name'), 'Subfamily or Tribe', taxonomySuggestionField('subfamily_name')],
|
||||
['Genus', taxonomySuggestionField('genus_name')],
|
||||
[
|
||||
'Species',
|
||||
textField('species_name', { type: 'text', trim: true }),
|
||||
taxonomySuggestionField('species_name'),
|
||||
'Unique Identifier',
|
||||
textField('unique_identifier', { type: 'text', trim: true }),
|
||||
],
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { TextField } from '@mui/material'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
import { checkFieldErrors } from '@/components/DetailView/common/checkFieldErrors'
|
||||
import { DataValue } from '@/components/DetailView/common/tabLayoutHelpers'
|
||||
import type { EditDataType, SpeciesDetailsType } from '@/shared/types'
|
||||
import type { TaxonomySuggestionField as TaxonomySuggestionFieldName } from './taxonomySuggestions'
|
||||
|
||||
const FIELD_WIDTH = '14em'
|
||||
|
||||
type TaxonomySuggestionFieldProps = {
|
||||
field: TaxonomySuggestionFieldName
|
||||
options: string[]
|
||||
}
|
||||
|
||||
export const TaxonomySuggestionField = ({ field, options }: TaxonomySuggestionFieldProps) => {
|
||||
const { editData, setEditData, validator, fieldsWithErrors, setFieldsWithErrors } =
|
||||
useDetailContext<SpeciesDetailsType>()
|
||||
const errorObject = validator(editData, field)
|
||||
const { error } = errorObject
|
||||
const suggestionListId = `${field}-taxonomy-suggestions`
|
||||
|
||||
const updateField = (value: string) => {
|
||||
const nextEditData: EditDataType<SpeciesDetailsType> = { ...editData, [field]: value }
|
||||
setEditData(nextEditData)
|
||||
const nextErrorObject = validator(nextEditData, field)
|
||||
checkFieldErrors(field, nextErrorObject, fieldsWithErrors, setFieldsWithErrors)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
checkFieldErrors(field, errorObject, fieldsWithErrors, setFieldsWithErrors)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [errorObject.error, errorObject.name])
|
||||
|
||||
const editingComponent = (
|
||||
<>
|
||||
<TextField
|
||||
id={`${field}-textfield`}
|
||||
inputProps={{ list: suggestionListId }}
|
||||
value={editData[field] ?? ''}
|
||||
onChange={event => updateField(event.currentTarget.value)}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
error={!!error}
|
||||
helperText={error ?? ''}
|
||||
onBlur={event => updateField(event.currentTarget.value.trim())}
|
||||
sx={{ width: FIELD_WIDTH }}
|
||||
/>
|
||||
<datalist id={suggestionListId}>
|
||||
{options.map(option => (
|
||||
<option key={option} value={option} />
|
||||
))}
|
||||
</datalist>
|
||||
</>
|
||||
)
|
||||
|
||||
return <DataValue<SpeciesDetailsType> field={field} EditElement={editingComponent} />
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { Species } from '@/shared/types'
|
||||
|
||||
export type TaxonomySuggestionField =
|
||||
| 'subclass_or_superorder_name'
|
||||
| 'order_name'
|
||||
| 'suborder_or_superfamily_name'
|
||||
| 'family_name'
|
||||
| 'subfamily_name'
|
||||
| 'genus_name'
|
||||
| 'species_name'
|
||||
|
||||
export const buildTaxonomySuggestionOptions = (
|
||||
speciesData: Species[] | undefined,
|
||||
field: TaxonomySuggestionField
|
||||
): string[] => {
|
||||
if (!speciesData) return []
|
||||
|
||||
return Array.from(
|
||||
speciesData.reduce<Set<string>>((options, species) => {
|
||||
const value = species[field]
|
||||
|
||||
if (typeof value === 'string') {
|
||||
const trimmedValue = value.trim()
|
||||
if (trimmedValue.length > 0) options.add(trimmedValue)
|
||||
}
|
||||
|
||||
return options
|
||||
}, new Set<string>())
|
||||
).sort((a, b) => a.localeCompare(b))
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { describe, expect, it } from '@jest/globals'
|
||||
|
||||
import { buildTaxonomySuggestionOptions } from '@/components/Species/taxonomySuggestions'
|
||||
import type { Species } from '@/shared/types'
|
||||
|
||||
const species = (overrides: Partial<Species>): Species => overrides as Species
|
||||
|
||||
describe('buildTaxonomySuggestionOptions', () => {
|
||||
it('returns sorted unique non-empty taxonomy values for a field', () => {
|
||||
const options = buildTaxonomySuggestionOptions(
|
||||
[
|
||||
species({ family_name: ' Felidae ' }),
|
||||
species({ family_name: 'Bovidae' }),
|
||||
species({ family_name: 'Felidae' }),
|
||||
species({ family_name: '' }),
|
||||
species({ family_name: null }),
|
||||
],
|
||||
'family_name'
|
||||
)
|
||||
|
||||
expect(options).toEqual(['Bovidae', 'Felidae'])
|
||||
})
|
||||
|
||||
it('returns an empty option list before species data is loaded', () => {
|
||||
expect(buildTaxonomySuggestionOptions(undefined, 'genus_name')).toEqual([])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user