Merge branch 'main' into chore/project4-iteration24-128-detail-tab-scroll

This commit is contained in:
Kari Lintulaakso
2026-04-16 13:58:41 +03:00
committed by GitHub
2 changed files with 103 additions and 40 deletions
@@ -1,8 +1,8 @@
import {
Editable,
EditDataType,
LocalityDetailsType,
LocalitySpecies,
LocalitySpeciesDetailsType,
RowState,
Species,
SpeciesDetailsType,
} from '@/shared/types'
@@ -44,11 +44,33 @@ export const SpeciesTab = () => {
return applyDefaultSpeciesOrdering(speciesData, { skip: hasUrlSorting })
}, [hasUrlSorting, speciesData])
const indexedLocalitySpeciesData = useMemo(() => {
return (editData.now_ls as unknown as LocalitySpecies[]).map((localitySpecies, index) => ({
...localitySpecies,
index,
}))
const toSpeciesDetailsDraft = (
input: Partial<EditDataType<Species>> & { class_name?: string | null }
): EditDataType<SpeciesDetailsType> => {
return {
now_ls: [],
com_taxa_synonym: [],
now_sau: [],
species_id: input.species_id,
class_name: input.class_name ?? undefined,
subclass_or_superorder_name: input.subclass_or_superorder_name ?? undefined,
order_name: input.order_name ?? undefined,
suborder_or_superfamily_name: input.suborder_or_superfamily_name ?? undefined,
family_name: input.family_name ?? undefined,
subfamily_name: input.subfamily_name ?? undefined,
genus_name: input.genus_name ?? undefined,
species_name: input.species_name ?? undefined,
unique_identifier: input.unique_identifier ?? undefined,
taxonomic_status: input.taxonomic_status ?? undefined,
sp_comment: input.sp_comment,
sp_author: input.sp_author,
}
}
type LocalitySpeciesRow = EditDataType<LocalitySpeciesDetailsType> & { rowState?: RowState; index: number }
const indexedLocalitySpeciesData = useMemo<LocalitySpeciesRow[]>(() => {
return editData.now_ls.map((localitySpecies, index) => ({ ...localitySpecies, index }))
}, [editData.now_ls])
const sortedLocalitySpeciesData = useMemo(() => {
@@ -98,25 +120,46 @@ export const SpeciesTab = () => {
)
const convertAndCheckNewSpeciesTaxonomy = (newSpecies: EditDataType<Species>) => {
const errors = []
for (const field in newSpecies) {
const errorObject = validateSpecies(
newSpecies as unknown as EditDataType<SpeciesDetailsType>,
field as unknown as keyof EditDataType<SpeciesDetailsType>
)
const { error } = errorObject
if (error) errors.push(errorObject)
}
const speciesForValidation = toSpeciesDetailsDraft(newSpecies)
const fieldsToValidate: Array<keyof EditDataType<SpeciesDetailsType>> = [
'subclass_or_superorder_name',
'order_name',
'suborder_or_superfamily_name',
'family_name',
'subfamily_name',
'genus_name',
'species_name',
'taxonomic_status',
'unique_identifier',
]
const errors = fieldsToValidate
.map(fieldName => validateSpecies(speciesForValidation, fieldName))
.filter(({ error }) => Boolean(error))
if (errors.length > 0) {
notify('Following validators failed: \n' + errors.map(e => `${e.name}: ${e.error}`).join('\n'), 'error')
return false
}
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 draftExistingTaxa = editData.now_ls
.filter(ls => ls.rowState === 'new')
.map(ls => ls.com_species)
.filter((species): species is EditDataType<SpeciesDetailsType> => Boolean(species))
.map(species => ({
species_id: species.species_id,
subclass_or_superorder_name: species.subclass_or_superorder_name,
order_name: species.order_name,
suborder_or_superfamily_name: species.suborder_or_superfamily_name,
family_name: species.family_name,
subfamily_name: species.subfamily_name,
genus_name: species.genus_name,
species_name: species.species_name,
unique_identifier: species.unique_identifier,
}))
const taxonomyErrors = checkSpeciesTaxonomy(convertedSpecies, speciesData!.concat(filteredLs), [])
const taxonomyErrors = checkSpeciesTaxonomy(convertedSpecies, [...(speciesData ?? []), ...draftExistingTaxa], [])
if (taxonomyErrors.size > 0) {
const errorMessage = [...taxonomyErrors].reduce((acc, currentError) => acc + `\n${currentError}`)
notify(errorMessage, 'error', null)
@@ -159,7 +202,7 @@ export const SpeciesTab = () => {
header: 'Taxon status',
},
]
const localitySpeciesColumns: MRT_ColumnDef<LocalitySpecies>[] = [
const localitySpeciesColumns: MRT_ColumnDef<LocalitySpeciesRow>[] = [
{
accessorKey: 'com_species.order_name',
header: 'Order',
@@ -237,7 +280,7 @@ export const SpeciesTab = () => {
{
lid: editData.lid,
species_id: undefined,
com_species: { ...(convertedSpecies as unknown as SpeciesDetailsType), class_name: 'Mammalia' },
com_species: toSpeciesDetailsDraft({ ...convertedSpecies, class_name: 'Mammalia' }),
rowState: 'new',
},
],
@@ -260,9 +303,7 @@ export const SpeciesTab = () => {
{
lid: editData.lid,
species_id: newSpecies.species_id,
com_species: {
...(fixNullValuesInTaxonomyFields(newSpecies) as SpeciesDetailsType),
},
com_species: toSpeciesDetailsDraft(fixNullValuesInTaxonomyFields(newSpecies)),
rowState: 'new',
},
],
@@ -271,7 +312,7 @@ export const SpeciesTab = () => {
/>
</Box>
)}
<EditableTable<LocalitySpecies, LocalityDetailsType>
<EditableTable<LocalitySpeciesRow, LocalityDetailsType>
columns={localitySpeciesColumns}
field="now_ls"
visible_data={sortedLocalitySpeciesData}
+37 -15
View File
@@ -1,15 +1,27 @@
import { EditDataType, Species, SpeciesDetailsType, SpeciesSynonym } from '@/shared/types'
import { EditDataType, Species, SpeciesSynonym } from '@/shared/types'
export type TaxonomySpecies = {
species_id: number | undefined
subclass_or_superorder_name: string | null | undefined
order_name: string | null | undefined
suborder_or_superfamily_name: string | null | undefined
family_name: string | null | undefined
subfamily_name: string | null | undefined
genus_name: string | null | undefined
species_name: string | null | undefined
unique_identifier: string | null | undefined
}
const generateMultipleParentsError = (
invalidField: string,
invalidValue1: string | null,
invalidValue2: string | null
invalidValue1: string | null | undefined,
invalidValue2: string | null | undefined
) => {
return `This taxon has several entries for ${invalidField}: ${invalidValue1}, ${invalidValue2}.
Please contact the NOW administration to fix this taxonomy.`
}
const isDuplicateTaxon = (newSpecies: EditDataType<Species>, existingSpecies: Species) => {
const isDuplicateTaxon = (newSpecies: EditDataType<Species>, existingSpecies: TaxonomySpecies) => {
if (
// the check for undefined species_id is here to make sure you cannot add two new,
// identical species at the same time (e.g. in Locality -> Species -> Add new species)
@@ -33,14 +45,14 @@ const isDuplicateSynonym = (newSpecies: EditDataType<Species>, existingSynonyms:
}
}
const speciesEqualsUniqueIdentifier = (newSpecies: EditDataType<Species>, existingSpecies: Species) => {
const speciesEqualsUniqueIdentifier = (newSpecies: EditDataType<Species>, existingSpecies: TaxonomySpecies) => {
return (
newSpecies.genus_name === existingSpecies.genus_name &&
newSpecies.species_name === existingSpecies.unique_identifier
)
}
const checkOrderFamily = (newSpecies: EditDataType<Species>, existingSpecies: Species) => {
const checkOrderFamily = (newSpecies: EditDataType<Species>, existingSpecies: TaxonomySpecies) => {
return (
newSpecies.subclass_or_superorder_name !== 'indet.' &&
newSpecies.order_name !== 'indet.' &&
@@ -54,7 +66,7 @@ const checkOrderFamily = (newSpecies: EditDataType<Species>, existingSpecies: Sp
)
}
const checkFamilyGenus = (newSpecies: EditDataType<Species>, existingSpecies: Species) => {
const checkFamilyGenus = (newSpecies: EditDataType<Species>, existingSpecies: TaxonomySpecies) => {
return (
newSpecies.subclass_or_superorder_name !== 'indet.' &&
newSpecies.order_name !== 'indet.' &&
@@ -70,7 +82,7 @@ const checkFamilyGenus = (newSpecies: EditDataType<Species>, existingSpecies: Sp
)
}
const checkSubClassOrder = (newSpecies: EditDataType<Species>, existingSpecies: Species) => {
const checkSubClassOrder = (newSpecies: EditDataType<Species>, existingSpecies: TaxonomySpecies) => {
return (
newSpecies.subclass_or_superorder_name &&
newSpecies.subclass_or_superorder_name !== 'indet.' &&
@@ -80,7 +92,7 @@ const checkSubClassOrder = (newSpecies: EditDataType<Species>, existingSpecies:
newSpecies.order_name === existingSpecies.order_name
)
}
const checkOrderSubOrder = (newSpecies: EditDataType<Species>, existingSpecies: Species) => {
const checkOrderSubOrder = (newSpecies: EditDataType<Species>, existingSpecies: TaxonomySpecies) => {
return (
newSpecies.subclass_or_superorder_name !== 'indet.' &&
newSpecies.order_name !== 'indet.' &&
@@ -91,7 +103,7 @@ const checkOrderSubOrder = (newSpecies: EditDataType<Species>, existingSpecies:
)
}
const checkSubOrderFamily = (newSpecies: EditDataType<Species>, existingSpecies: Species) => {
const checkSubOrderFamily = (newSpecies: EditDataType<Species>, existingSpecies: TaxonomySpecies) => {
return (
newSpecies.subclass_or_superorder_name !== 'indet.' &&
newSpecies.order_name !== 'indet.' &&
@@ -105,7 +117,7 @@ const checkSubOrderFamily = (newSpecies: EditDataType<Species>, existingSpecies:
)
}
const checkFamilySubFamily = (newSpecies: EditDataType<Species>, existingSpecies: Species) => {
const checkFamilySubFamily = (newSpecies: EditDataType<Species>, existingSpecies: TaxonomySpecies) => {
return (
newSpecies.subclass_or_superorder_name !== 'indet.' &&
newSpecies.order_name !== 'indet.' &&
@@ -119,7 +131,7 @@ const checkFamilySubFamily = (newSpecies: EditDataType<Species>, existingSpecies
)
}
const checkSubFamilyGenus = (newSpecies: EditDataType<Species>, existingSpecies: Species) => {
const checkSubFamilyGenus = (newSpecies: EditDataType<Species>, existingSpecies: TaxonomySpecies) => {
return (
newSpecies.subclass_or_superorder_name !== 'indet.' &&
newSpecies.order_name !== 'indet.' &&
@@ -138,7 +150,7 @@ const checkSubFamilyGenus = (newSpecies: EditDataType<Species>, existingSpecies:
export const checkSpeciesTaxonomy = (
newSpecies: EditDataType<Species>,
existingSpeciesArray: Species[],
existingSpeciesArray: TaxonomySpecies[],
synonyms: SpeciesSynonym[]
) => {
const {
@@ -164,7 +176,10 @@ export const checkSpeciesTaxonomy = (
return errors
}
const relatedSynonyms = synonyms.filter(syn => syn.species_id === existingSpecies.species_id)
const relatedSynonyms =
existingSpecies.species_id === undefined
? []
: synonyms.filter(syn => syn.species_id === existingSpecies.species_id)
if (isDuplicateSynonym(newSpecies, relatedSynonyms)) {
errors.add(
`${existingSpecies.genus_name} ${existingSpecies.species_name} already has ${genus} ${speciesName} as a synonym.`
@@ -356,7 +371,14 @@ export const hasTaxonomyChanges = (editedSpecies: EditDataType<Species>, origina
return TAXONOMY_FIELDS.some(field => (editedSpecies[field] ?? '') !== (originalSpecies[field] ?? ''))
}
export const fixNullValuesInTaxonomyFields = (species: Species | SpeciesDetailsType) => {
type TaxonomyNullFixable = {
subclass_or_superorder_name: string | null
suborder_or_superfamily_name: string | null
subfamily_name: string | null
taxonomic_status: string | null
}
export const fixNullValuesInTaxonomyFields = <T extends TaxonomyNullFixable>(species: T) => {
return {
...species,
subclass_or_superorder_name: species.subclass_or_superorder_name ?? '',