mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
add validation and other checks for species synonym adding
This commit is contained in:
@@ -8,12 +8,13 @@ import { useDetailContext } from './Context/DetailContext'
|
||||
import { EditDataType, Editable, Reference, Species } from '@/shared/types'
|
||||
import { useState, useEffect, Fragment } from 'react'
|
||||
import { referenceValidator } from '@/shared/validators/validator'
|
||||
import { checkTaxonomy, convertTaxonomyFields } from '../../util/taxonomyUtilities'
|
||||
import { useLazyGetAllSpeciesQuery, useLazyGetAllSynonymsQuery } from '@/redux/speciesReducer'
|
||||
import { checkSpeciesTaxonomy, convertSpeciesTaxonomyFields } from '../../util/taxonomyUtilities'
|
||||
import { useGetAllSpeciesQuery, useGetAllSynonymsQuery } from '@/redux/speciesReducer'
|
||||
import { useNotify } from '@/hooks/notification'
|
||||
import { countryBoundingBoxes } from '@/country_data/countryBoundingBoxes'
|
||||
import { boundingBoxSplit, isPointInBoxes } from '@/util/isPointInBox'
|
||||
import { OutOfBoundsWarningModal, OutOfBoundsWarningModalState } from './OutOfBoundsWarningModal'
|
||||
import { skipToken } from '@reduxjs/toolkit/query'
|
||||
|
||||
export const WriteButton = <T,>({
|
||||
onWrite,
|
||||
@@ -28,8 +29,8 @@ export const WriteButton = <T,>({
|
||||
useDetailContext<T>()
|
||||
const [loading, setLoading] = useState(false)
|
||||
const { notify } = useNotify()
|
||||
const [getSpeciesData] = useLazyGetAllSpeciesQuery()
|
||||
const [getSynonyms] = useLazyGetAllSynonymsQuery()
|
||||
const { data: speciesData } = useGetAllSpeciesQuery(!taxonomy ? skipToken : undefined)
|
||||
const { data: synonymData } = useGetAllSynonymsQuery(!taxonomy ? skipToken : undefined)
|
||||
|
||||
// For coordinate out of bounds warning
|
||||
const [warningModalState, setWarningModalState] = useState<OutOfBoundsWarningModalState>({
|
||||
@@ -50,6 +51,11 @@ export const WriteButton = <T,>({
|
||||
return !isPointInBoxes(dec_lat, dec_long, boundingBoxSplit(countryBoundingBoxes[country]))
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (taxonomy && (!speciesData || !synonymData)) setLoading(true)
|
||||
else setLoading(false)
|
||||
}, [speciesData, synonymData])
|
||||
|
||||
/* validates all fields when entering new/editing mode
|
||||
to disable write button when unvisited tabs have
|
||||
validation errors (e.g. missing required fields) */
|
||||
@@ -107,23 +113,21 @@ export const WriteButton = <T,>({
|
||||
// @ts-expect-error Reason: Typescript doesn't recognise that references do exist. Unable to find a way around it. Fix if extra time
|
||||
}, [mode, editData.references])
|
||||
|
||||
const writeWithTaxonomyCheck = async () => {
|
||||
const writeWithTaxonomyCheck = () => {
|
||||
let speciesEditData: EditDataType<Species> | undefined = undefined
|
||||
|
||||
if (!mode.staging) {
|
||||
const { data: speciesData } = await getSpeciesData(undefined, true)
|
||||
const { data: synonyms } = await getSynonyms(undefined, true)
|
||||
if (!speciesData) {
|
||||
notify('Could not fetch species to check taxonomy data.', 'error')
|
||||
return
|
||||
}
|
||||
if (!synonyms) {
|
||||
if (!synonymData) {
|
||||
notify('Could not fetch synonyms to check taxonomy data.', 'error')
|
||||
return
|
||||
}
|
||||
// converts taxonomy fields to capitalized/lowercased
|
||||
speciesEditData = convertTaxonomyFields(editData as EditDataType<Species>)
|
||||
const errors = checkTaxonomy(speciesEditData, speciesData, synonyms)
|
||||
speciesEditData = convertSpeciesTaxonomyFields(editData as EditDataType<Species>)
|
||||
const errors = checkSpeciesTaxonomy(speciesEditData, speciesData, synonymData)
|
||||
if (errors.size > 0) {
|
||||
const errorMessage = [...errors].reduce((acc, currentError) => acc + `\n${currentError}`)
|
||||
notify(errorMessage, 'error', null)
|
||||
@@ -144,10 +148,10 @@ export const WriteButton = <T,>({
|
||||
})
|
||||
}
|
||||
|
||||
const handleWriteButtonClick = async () => {
|
||||
const handleWriteButtonClick = () => {
|
||||
if (taxonomy) {
|
||||
setLoading(true)
|
||||
await writeWithTaxonomyCheck()
|
||||
writeWithTaxonomyCheck()
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
@@ -186,13 +190,13 @@ export const WriteButton = <T,>({
|
||||
!('dec_long' in (editData as object)) ||
|
||||
!('country' in (editData as object))
|
||||
) {
|
||||
void handleWriteButtonClick()
|
||||
handleWriteButtonClick()
|
||||
return
|
||||
}
|
||||
|
||||
const localityObject = editData as unknown as { dec_lat: number; dec_long: number; country: string }
|
||||
if (!isCoordinateOutOfBounds(localityObject.dec_lat, localityObject.dec_long, localityObject.country)) {
|
||||
void handleWriteButtonClick()
|
||||
handleWriteButtonClick()
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -15,7 +15,11 @@ import { useGetAllSpeciesQuery } from '@/redux/speciesReducer'
|
||||
import { Box, CircularProgress } from '@mui/material'
|
||||
import { skipToken } from '@reduxjs/toolkit/query'
|
||||
import { MRT_ColumnDef } from 'material-react-table'
|
||||
import { checkTaxonomy, convertTaxonomyFields, fixNullValuesInTaxonomyFields } from '@/util/taxonomyUtilities'
|
||||
import {
|
||||
checkSpeciesTaxonomy,
|
||||
convertSpeciesTaxonomyFields,
|
||||
fixNullValuesInTaxonomyFields,
|
||||
} from '@/util/taxonomyUtilities'
|
||||
import { useNotify } from '@/hooks/notification'
|
||||
import { validateSpecies } from '@/shared/validators/species'
|
||||
import { smallSpeciesTableColumns } from '@/common'
|
||||
@@ -83,11 +87,11 @@ export const SpeciesTab = () => {
|
||||
return false
|
||||
}
|
||||
|
||||
const convertedSpecies = convertTaxonomyFields(newSpecies)
|
||||
const convertedSpecies = convertSpeciesTaxonomyFields(newSpecies)
|
||||
const everyLs = editData.now_ls.map(ls => ls.com_species) as unknown as Editable<Species>[]
|
||||
const filteredLs = everyLs.filter(species => species.rowState === 'new')
|
||||
|
||||
const taxonomyErrors = checkTaxonomy(convertedSpecies, speciesData!.concat(filteredLs), [])
|
||||
const taxonomyErrors = checkSpeciesTaxonomy(convertedSpecies, speciesData!.concat(filteredLs), [])
|
||||
if (taxonomyErrors.size > 0) {
|
||||
const errorMessage = [...taxonomyErrors].reduce((acc, currentError) => acc + `\n${currentError}`)
|
||||
notify(errorMessage, 'error', null)
|
||||
|
||||
@@ -4,9 +4,47 @@ import { Grouped } from '@/components/DetailView/common/tabLayoutHelpers'
|
||||
import { MRT_ColumnDef } from 'material-react-table'
|
||||
import { EditableTable } from '@/components/DetailView/common/EditableTable'
|
||||
import { EditingForm, EditingFormField } from '@/components/DetailView/common/EditingForm'
|
||||
import { useNotify } from '@/hooks/notification'
|
||||
import { validateSynonym } from '@/shared/validators/synonym'
|
||||
import { convertSynonymTaxonomyFields } from '@/util/taxonomyUtilities'
|
||||
|
||||
export const SynonymTab = () => {
|
||||
const { mode, setEditData, editData } = useDetailContext<SpeciesDetailsType>()
|
||||
const { notify } = useNotify()
|
||||
|
||||
const convertAndCheckNewSynonymTaxonomy = (newSynonym: EditDataType<SpeciesSynonym>) => {
|
||||
const errors = []
|
||||
for (const field in newSynonym) {
|
||||
const errorObject = validateSynonym(
|
||||
newSynonym as unknown as EditDataType<SpeciesSynonym>,
|
||||
field as unknown as keyof EditDataType<SpeciesSynonym>
|
||||
)
|
||||
const { error } = errorObject
|
||||
if (error) errors.push(errorObject)
|
||||
}
|
||||
if (errors.length > 0) {
|
||||
notify('Following validators failed: \n' + errors.map(e => `${e.name}: ${e.error}`).join('\n'), 'error')
|
||||
return false
|
||||
}
|
||||
|
||||
const convertedSynonym = convertSynonymTaxonomyFields(newSynonym)
|
||||
for (const existingSynonym of editData.com_taxa_synonym) {
|
||||
if (
|
||||
convertedSynonym.syn_genus_name === existingSynonym.syn_genus_name &&
|
||||
convertedSynonym.syn_species_name === existingSynonym.syn_species_name
|
||||
) {
|
||||
notify(
|
||||
`${convertedSynonym.syn_genus_name} ${convertedSynonym.syn_species_name} \
|
||||
has already been added as a synonym for this species.`,
|
||||
'error',
|
||||
null
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return convertedSynonym
|
||||
}
|
||||
|
||||
const columns: MRT_ColumnDef<SpeciesSynonym>[] = [
|
||||
{
|
||||
@@ -34,12 +72,14 @@ export const SynonymTab = () => {
|
||||
buttonText="Add new Species"
|
||||
formFields={formFields}
|
||||
editAction={(newSynonym: EditDataType<SpeciesSynonym>) => {
|
||||
const convertedSynonym = convertAndCheckNewSynonymTaxonomy(newSynonym)
|
||||
if (!convertedSynonym) return
|
||||
setEditData({
|
||||
...editData,
|
||||
com_taxa_synonym: [
|
||||
...editData.com_taxa_synonym,
|
||||
{
|
||||
...newSynonym,
|
||||
...convertedSynonym,
|
||||
rowState: 'new',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -57,6 +57,7 @@ export const api = createApi({
|
||||
'localities',
|
||||
'species',
|
||||
'specieslist',
|
||||
'speciessynonyms',
|
||||
'reference',
|
||||
'references',
|
||||
'timeunits',
|
||||
|
||||
@@ -7,18 +7,19 @@ const speciesApi = api.injectEndpoints({
|
||||
query: () => ({
|
||||
url: `/species/all`,
|
||||
}),
|
||||
providesTags: result => (result ? [{ type: 'specieslist' }] : []),
|
||||
providesTags: result => (result ? [{ type: 'specieslist' }, 'speciessynonyms'] : []),
|
||||
}),
|
||||
getAllSynonyms: builder.query<SpeciesSynonym[], void>({
|
||||
query: () => ({
|
||||
url: `/species/synonyms`,
|
||||
}),
|
||||
providesTags: result => (result ? ['speciessynonyms'] : []),
|
||||
}),
|
||||
getSpeciesDetails: builder.query<SpeciesDetailsType, string>({
|
||||
query: id => ({
|
||||
url: `/species/${id}`,
|
||||
}),
|
||||
providesTags: result => (result ? [{ type: 'species', id: result.species_id }] : []),
|
||||
providesTags: result => (result ? [{ type: 'species', id: result.species_id }, 'speciessynonyms'] : []),
|
||||
}),
|
||||
editSpecies: builder.mutation<SpeciesDetailsType, EditDataType<SpeciesDetailsType>>({
|
||||
query: species => ({
|
||||
@@ -27,7 +28,7 @@ const speciesApi = api.injectEndpoints({
|
||||
body: { species },
|
||||
}),
|
||||
invalidatesTags: (result, _error, { species_id }) =>
|
||||
result ? [{ type: 'species', id: species_id }, 'specieslist'] : [],
|
||||
result ? [{ type: 'species', id: species_id }, 'specieslist', 'speciessynonyms'] : [],
|
||||
}),
|
||||
deleteSpecies: builder.mutation<void, number>({
|
||||
query: id => ({
|
||||
@@ -36,15 +37,14 @@ const speciesApi = api.injectEndpoints({
|
||||
}),
|
||||
|
||||
invalidatesTags: (result, _error, species_id) =>
|
||||
typeof result !== 'undefined' ? [{ type: 'species', id: species_id }, 'specieslist'] : [],
|
||||
typeof result !== 'undefined' ? [{ type: 'species', id: species_id }, 'specieslist', 'speciessynonyms'] : [],
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
export const {
|
||||
useGetAllSpeciesQuery,
|
||||
useLazyGetAllSpeciesQuery,
|
||||
useLazyGetAllSynonymsQuery,
|
||||
useGetAllSynonymsQuery,
|
||||
useGetSpeciesDetailsQuery,
|
||||
useEditSpeciesMutation,
|
||||
useDeleteSpeciesMutation,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import { EditDataType, SpeciesSynonym } from '../types'
|
||||
import { Validators, validator } from './validator'
|
||||
|
||||
export const validateSynonym = (
|
||||
editData: EditDataType<SpeciesSynonym>,
|
||||
fieldName: keyof EditDataType<SpeciesSynonym>
|
||||
) => {
|
||||
const validators: Validators<Partial<EditDataType<SpeciesSynonym>>> = {
|
||||
syn_genus_name: {
|
||||
name: 'Synonym genus name',
|
||||
required: true,
|
||||
asString: (synGenusName: string) => {
|
||||
if (synGenusName.indexOf(' ') !== -1) return 'Genus must not contain any spaces.'
|
||||
return
|
||||
},
|
||||
},
|
||||
syn_species_name: {
|
||||
name: 'Synonym species name',
|
||||
required: true,
|
||||
asString: (synSpeciesName: string) => {
|
||||
if (synSpeciesName !== 'indet.' && editData.syn_genus_name === 'indet.')
|
||||
return 'when the Genus is indet., Species must also be indet.'
|
||||
if (synSpeciesName !== 'sp.' && editData.syn_genus_name === 'gen.')
|
||||
return 'when the Genus is gen., Species must be sp.'
|
||||
return
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return validator<EditDataType<SpeciesSynonym>>(validators, editData, fieldName)
|
||||
}
|
||||
@@ -146,7 +146,7 @@ const checkSubFamilyGenus = (newSpecies: EditDataType<Species>, existingSpecies:
|
||||
)
|
||||
}
|
||||
|
||||
export const checkTaxonomy = (
|
||||
export const checkSpeciesTaxonomy = (
|
||||
newSpecies: EditDataType<Species>,
|
||||
existingSpeciesArray: Species[],
|
||||
synonyms: SpeciesSynonym[]
|
||||
@@ -327,7 +327,7 @@ const convertTaxonomyField = (value: string | null | undefined) => {
|
||||
return convertedValue
|
||||
}
|
||||
|
||||
export const convertTaxonomyFields = (species: EditDataType<Species>) => {
|
||||
export const convertSpeciesTaxonomyFields = (species: EditDataType<Species>) => {
|
||||
const speciesName = species.species_name
|
||||
let lowercasedSpeciesName = ''
|
||||
if (speciesName) {
|
||||
@@ -358,3 +358,21 @@ export const fixNullValuesInTaxonomyFields = (species: Species | SpeciesDetailsT
|
||||
subfamily_name: species.subfamily_name ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
export const convertSynonymTaxonomyFields = (synonym: EditDataType<SpeciesSynonym>) => {
|
||||
const synSpeciesName = synonym.syn_species_name
|
||||
let lowercasedSynSpeciesName = ''
|
||||
if (synSpeciesName) {
|
||||
if (synSpeciesName !== 'indet.') {
|
||||
lowercasedSynSpeciesName = synSpeciesName.toLowerCase()
|
||||
} else {
|
||||
lowercasedSynSpeciesName = synSpeciesName
|
||||
}
|
||||
}
|
||||
const convertedSynonym = {
|
||||
...synonym,
|
||||
syn_genus_name: convertTaxonomyField(synonym.syn_genus_name),
|
||||
syn_species_name: lowercasedSynSpeciesName,
|
||||
}
|
||||
return convertedSynonym as EditDataType<SpeciesSynonym>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user