mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Merge branch 'main' into make-some-tables-unclickable
This commit is contained in:
@@ -40,3 +40,6 @@ The new version is not deployed yet.
|
||||
:mag_right: [Class diagram](documentation/class_diagram.md) See the relations of relevant tables. Notice that it omits some unimportant tables and does not show most columns.
|
||||
|
||||
:earth_africa: [Map features](documentation/frontend/components/maps/README.md) Maps' functionality and development notes
|
||||
|
||||
**DeepWiki documentation for the NOW database can be found here:**
|
||||
[](https://deepwiki.com/nowcommunity/nowdatabase)
|
||||
|
||||
@@ -45,7 +45,7 @@ export const newSpeciesBasis: EditDataType<SpeciesDetailsType & EditMetaData> =
|
||||
genus_name: 'Petenyia',
|
||||
species_name: 'dubiatest',
|
||||
unique_identifier: '-',
|
||||
taxonomic_status: null,
|
||||
taxonomic_status: '',
|
||||
common_name: null,
|
||||
sp_author: 'Bachmayer & Wilson, 1970',
|
||||
strain: null,
|
||||
|
||||
@@ -132,8 +132,8 @@ router.get(`/export/:columnfilters/:sorting`, async (req, res) => {
|
||||
})
|
||||
res.attachment(`cross_search${currentDateAsString()}.csv`) // filename will get overwritten in frontend when fetching data from this route
|
||||
|
||||
// quoteColumns is needed to make sure linebreaks do not mess up the data
|
||||
const stream = format({ headers: true, quoteColumns: true }).transform(
|
||||
// this should match the csvConfig in frontend exportRows() function as closely as possible
|
||||
const stream = format({ delimiter: ',', headers: true, quoteColumns: true }).transform(
|
||||
transformFunction as FormatterRowTransformFunction<FormatterRow, FormatterRow>
|
||||
)
|
||||
|
||||
|
||||
@@ -1,11 +1,20 @@
|
||||
import { EditDataType, EditMetaData, LocalityDetailsType, User, Role } from '../../../frontend/src/shared/types'
|
||||
import {
|
||||
EditDataType,
|
||||
EditMetaData,
|
||||
LocalityDetailsType,
|
||||
User,
|
||||
Role,
|
||||
LocalitySpeciesDetailsType,
|
||||
Editable,
|
||||
SpeciesDetailsType,
|
||||
} from '../../../frontend/src/shared/types'
|
||||
import { validateLocality } from '../../../frontend/src/shared/validators/locality'
|
||||
import { validateSpecies } from '../../../frontend/src/shared/validators/species'
|
||||
import { ValidationObject, referenceValidator } from '../../../frontend/src/shared/validators/validator'
|
||||
import Prisma from '../../prisma/generated/now_test_client'
|
||||
import { AccessError } from '../middlewares/authorizer'
|
||||
import { fixBigInt } from '../utils/common'
|
||||
import { logDb, nowDb } from '../utils/db'
|
||||
import { logger } from '../utils/logger'
|
||||
import { getReferenceDetails } from './reference'
|
||||
|
||||
const getIdsOfUsersProjects = async (user: User) => {
|
||||
@@ -200,6 +209,7 @@ export const getLocalityDetails = async (id: number, user: User | undefined) =>
|
||||
return JSON.parse(fixBigInt(result)!) as LocalityDetailsType
|
||||
}
|
||||
|
||||
// also validates possible new species that were added to this locality
|
||||
export const validateEntireLocality = async (editedFields: EditDataType<Prisma.now_loc> & EditMetaData) => {
|
||||
const keys = Object.keys(editedFields)
|
||||
const errors: ValidationObject[] = []
|
||||
@@ -207,6 +217,17 @@ export const validateEntireLocality = async (editedFields: EditDataType<Prisma.n
|
||||
const error = validateLocality(editedFields as EditDataType<LocalityDetailsType>, key as keyof LocalityDetailsType)
|
||||
if (error.error) errors.push(error)
|
||||
}
|
||||
if ('now_ls' in editedFields) {
|
||||
for (const species of editedFields.now_ls as Array<Editable<LocalitySpeciesDetailsType>>) {
|
||||
if (species.rowState === 'new') {
|
||||
const keys = Object.keys(species.com_species)
|
||||
for (const key of keys) {
|
||||
const newSpeciesError = validateSpecies(species.com_species, key as keyof SpeciesDetailsType)
|
||||
if (newSpeciesError.error) errors.push(newSpeciesError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let error = null
|
||||
if ('references' in editedFields && editedFields.references) {
|
||||
error = referenceValidator(editedFields.references)
|
||||
@@ -251,12 +272,10 @@ export const filterDuplicateLocalitySpecies = async (
|
||||
// Logic is flipped for filter as we want to skip the ones the check matches
|
||||
return !(localityDetailsSpeciesIds.includes(localitySpecies.species_id) && localitySpecies.rowState === 'new')
|
||||
}
|
||||
|
||||
// code shouldn't get here ever
|
||||
logger.info(
|
||||
`Filter already existing species: localitySpecies didn't have species_id. Skipping. ${JSON.stringify(localitySpecies)}`
|
||||
)
|
||||
return false
|
||||
// if localitySpecies doesn't have species_id (i.e. it was created with the "add new species" button in Species tab),
|
||||
// it will never be filtered out. However, due to the taxonomy check for the "add new species" button,
|
||||
// it should not let the user create a duplicate species in this way.
|
||||
return true
|
||||
})
|
||||
|
||||
return updatedLocalityNow_ls
|
||||
|
||||
@@ -94,14 +94,13 @@ export const getSpeciesDetails = async (id: number) => {
|
||||
now_sau: {
|
||||
include: {
|
||||
now_sr: {
|
||||
select: {
|
||||
include: {
|
||||
ref_ref: {
|
||||
select: {
|
||||
include: {
|
||||
ref_authors: true,
|
||||
ref_journal: true,
|
||||
},
|
||||
},
|
||||
rid: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -49,9 +49,9 @@ export const writeLocality = async (
|
||||
try {
|
||||
await writeHandler.start()
|
||||
|
||||
const filteredLocality = await filterDuplicateLocalitySpecies(locality, user)
|
||||
if (updateOrAdd === 'update' && filteredLocality) {
|
||||
locality.now_ls = filteredLocality
|
||||
const filteredLocalities = await filterDuplicateLocalitySpecies(locality, user)
|
||||
if (updateOrAdd === 'update' && filteredLocalities && filteredLocalities.length > 0) {
|
||||
locality.now_ls = filteredLocalities
|
||||
}
|
||||
|
||||
if (updateOrAdd === 'add') {
|
||||
|
||||
@@ -11,7 +11,7 @@ const getSpeciesWriteHandler = (type: ActionType) => {
|
||||
dbName: NOW_DB_NAME,
|
||||
table: 'com_species',
|
||||
idColumn: 'species_id',
|
||||
allowedColumns: getFieldsOfTables(['com_species', 'now_ls', 'now_sau', 'now_lau']),
|
||||
allowedColumns: getFieldsOfTables(['com_species', 'now_ls', 'now_sau', 'now_lau', 'com_taxa_synonym']),
|
||||
type,
|
||||
})
|
||||
}
|
||||
|
||||
+164
-20
@@ -1,3 +1,162 @@
|
||||
before('Reset database', () => {
|
||||
cy.request(Cypress.env('databaseResetUrl'))
|
||||
})
|
||||
|
||||
describe('Adding species in Locality -> Species tab for an existing locality', () => {
|
||||
beforeEach('Login as admin', () => {
|
||||
cy.login('testSu')
|
||||
})
|
||||
|
||||
it('works with valid, unique species', () => {
|
||||
cy.visit(`/locality/20920?tab=2`)
|
||||
cy.contains('Lantian-Shuijiazui')
|
||||
cy.get('[id=edit-button]').click()
|
||||
|
||||
cy.contains('Add new Species').click()
|
||||
cy.get('[name=order_name]').type('Rodentia')
|
||||
cy.get('[name=family_name]').type('Gliridae')
|
||||
cy.get('[name=genus_name]').type('Simplomys')
|
||||
cy.get('[name=species_name]').type('someSpecies')
|
||||
cy.get('[name=unique_identifier]').type('veryunique')
|
||||
cy.contains('Save').click()
|
||||
cy.contains('somespecies')
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('Edited item successfully.')
|
||||
cy.visit(`/locality/20920?tab=2`)
|
||||
cy.contains('somespecies')
|
||||
})
|
||||
|
||||
it('works through "copy species taxonomy" button', () => {
|
||||
cy.visit(`/locality/20920?tab=2`)
|
||||
cy.contains('Lantian-Shuijiazui')
|
||||
cy.get('[id=edit-button]').click()
|
||||
cy.contains('Add new Species').click()
|
||||
cy.get('[data-cy=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=add-button-21426]').click()
|
||||
cy.contains('Close').click()
|
||||
cy.get('[name=species_name]').clear()
|
||||
cy.get('[name=species_name]').type('Newspecies')
|
||||
cy.contains('Save').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('Edited item successfully.')
|
||||
cy.visit(`/locality/20920?tab=2`)
|
||||
cy.contains('newspecies')
|
||||
})
|
||||
|
||||
it('does not work with invalid species', () => {
|
||||
cy.visit(`/locality/20920?tab=2`)
|
||||
cy.contains('Lantian-Shuijiazui')
|
||||
cy.get('[id=edit-button]').click()
|
||||
|
||||
cy.contains('Add new Species').click()
|
||||
cy.clearNewSpeciesForm()
|
||||
cy.get('[name=order_name]').type('Order with spaces')
|
||||
cy.get('[name=family_name]').type('newfamily')
|
||||
cy.get('[name=genus_name]').type('newgenus')
|
||||
cy.get('[name=species_name]').type('newspecies')
|
||||
cy.get('[name=unique_identifier]').type('veryunique')
|
||||
cy.contains('Save').click()
|
||||
cy.contains('Order must not contain any spaces, unless the value is "incertae sedis".')
|
||||
})
|
||||
|
||||
it('does not work if the species has already been added', () => {
|
||||
cy.visit(`/locality/20920?tab=2`)
|
||||
cy.contains('Lantian-Shuijiazui')
|
||||
cy.get('[id=edit-button]').click()
|
||||
|
||||
// adding species that already exists in the database
|
||||
cy.contains('Add new Species').click()
|
||||
cy.clearNewSpeciesForm()
|
||||
cy.get('[name=order_name]').type('Rodentia')
|
||||
cy.get('[name=family_name]').type('Gliridae')
|
||||
cy.get('[name=genus_name]').type('Simplomys')
|
||||
cy.get('[name=species_name]').type('Simplicidens')
|
||||
cy.get('[name=unique_identifier]').type('-')
|
||||
cy.contains('Save').click()
|
||||
cy.contains('The taxon already exists in the database.')
|
||||
|
||||
// add new, unique species
|
||||
cy.contains('Add new Species').click()
|
||||
cy.clearNewSpeciesForm()
|
||||
cy.get('[name=order_name]').type('neworder')
|
||||
cy.get('[name=family_name]').type('newfamily')
|
||||
cy.get('[name=genus_name]').type('newgenus')
|
||||
cy.get('[name=species_name]').type('newspecies')
|
||||
cy.get('[name=unique_identifier]').type('-')
|
||||
cy.contains('Save').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('Edited item successfully.')
|
||||
cy.visit(`/locality/20920?tab=2`)
|
||||
cy.contains('Lantian-Shuijiazui')
|
||||
cy.get('[id=edit-button]').click()
|
||||
cy.contains('Neworder')
|
||||
|
||||
// adding species that has been added earlier through "add new species" button
|
||||
cy.contains('Add new Species').click()
|
||||
cy.clearNewSpeciesForm()
|
||||
cy.get('[name=order_name]').type('neworder')
|
||||
cy.get('[name=family_name]').type('newfamily')
|
||||
cy.get('[name=genus_name]').type('newgenus')
|
||||
cy.get('[name=species_name]').type('newspecies')
|
||||
cy.get('[name=unique_identifier]').type('-')
|
||||
cy.contains('Save').click()
|
||||
cy.contains('The taxon already exists in the database.')
|
||||
})
|
||||
|
||||
it('does not work with species with invalid taxonomic order', () => {
|
||||
cy.visit(`/locality/20920?tab=2`)
|
||||
cy.contains('Lantian-Shuijiazui')
|
||||
cy.get('[id=edit-button]').click()
|
||||
|
||||
// adding species that already exists in the database
|
||||
cy.contains('Add new Species').click()
|
||||
cy.clearNewSpeciesForm()
|
||||
cy.get('[name=order_name]').type('Rodentia')
|
||||
cy.get('[name=family_name]').type('Soricidae')
|
||||
cy.get('[name=genus_name]').type('Simplomys')
|
||||
cy.get('[name=species_name]').type('Simplicidens')
|
||||
cy.get('[name=unique_identifier]').type('unique')
|
||||
cy.contains('Save').click()
|
||||
cy.contains('Family Soricidae belongs to order Eulipotyphla, not Rodentia.')
|
||||
cy.contains('Genus Simplomys belongs to family Gliridae, not Soricidae.')
|
||||
|
||||
cy.contains('Add new Species').click()
|
||||
cy.clearNewSpeciesForm()
|
||||
cy.get('[name=order_name]').type('Carnivora')
|
||||
cy.get('[name=family_name]').type('Canidae')
|
||||
cy.get('[name=genus_name]').type('Simplomys')
|
||||
cy.get('[name=species_name]').type('Simplicidens')
|
||||
cy.get('[name=unique_identifier]').type('moreunique')
|
||||
cy.contains('Save').click()
|
||||
cy.contains('Genus Simplomys belongs to family Gliridae, not Canidae.')
|
||||
|
||||
cy.contains('Add new Species').click()
|
||||
cy.clearNewSpeciesForm()
|
||||
cy.get('[name=order_name]').type('Artiodactyla')
|
||||
cy.get('[name=family_name]').type('Bovidae')
|
||||
cy.get('[name=genus_name]').type('Amblycoptus')
|
||||
cy.get('[name=species_name]').type('indet.')
|
||||
cy.get('[name=unique_identifier]').type('uniquest')
|
||||
cy.contains('Save').click()
|
||||
cy.contains('Genus Amblycoptus belongs to family Soricidae, not Bovidae.')
|
||||
})
|
||||
|
||||
it('does not work through "copy species taxonomy" button if nothing is changed', () => {
|
||||
cy.visit(`/locality/20920?tab=2`)
|
||||
cy.contains('Lantian-Shuijiazui')
|
||||
cy.get('[id=edit-button]').click()
|
||||
cy.contains('Add new Species').click()
|
||||
cy.get('[data-cy=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=add-button-21052]').click()
|
||||
cy.contains('Close').click()
|
||||
cy.contains('Save').click()
|
||||
cy.contains('The taxon already exists in the database.')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Creating a new locality', () => {
|
||||
beforeEach('Login as admin', () => {
|
||||
cy.login('testSu')
|
||||
@@ -30,13 +189,8 @@ describe('Creating a new locality', () => {
|
||||
cy.get('[id=dec_long-textfield]').type('103.67')
|
||||
cy.get('[id=dms_lat-textfield]').should('have.value', '49 4 12 N')
|
||||
cy.get('[id=dms_long-textfield]').should('have.value', '103 40 12 E')
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('Edited item successfully.')
|
||||
cy.contains('Bugat')
|
||||
cy.get('[id=delete-button]').should('exist')
|
||||
@@ -150,15 +304,8 @@ describe('Creating a new locality', () => {
|
||||
cy.get('[id=dec_lat-textfield]').type('49.07')
|
||||
cy.get('[id=dec_long-textfield]').type('103.67')
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('Edited item successfully.')
|
||||
cy.contains('Bugat')
|
||||
cy.contains('8.676')
|
||||
@@ -314,11 +461,8 @@ describe("Locality's coordinate selection map works", () => {
|
||||
cy.contains('Central Finland').first().click() // first item on the list
|
||||
cy.contains('Latitude: 63.05484, Longitude: 24.75291')
|
||||
cy.contains('Save').click()
|
||||
cy.contains('Finalize entry').click()
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.visit(`/locality/20920?tab=1`)
|
||||
cy.contains('63.05484')
|
||||
cy.contains('24.75291')
|
||||
|
||||
+12
-38
@@ -19,13 +19,7 @@ describe('Creating a species', () => {
|
||||
cy.get('[id=species_name-textfield]').type('testSpecies')
|
||||
cy.get('[id=unique_identifier-textfield]').type('{backspace}testUniqueIdentifier')
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.addReferenceAndSave()
|
||||
|
||||
// tests that values are capitalised and trimmed properly
|
||||
cy.contains('Testsuperorder')
|
||||
@@ -66,12 +60,7 @@ describe('Editing a species', () => {
|
||||
cy.get('[id=subfamily_name-textfield]').type('heimo')
|
||||
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('Saved species entry successfully.')
|
||||
cy.contains('Modifiedgenus')
|
||||
cy.contains('modifiedspecies')
|
||||
@@ -101,14 +90,8 @@ describe('Editing a species', () => {
|
||||
|
||||
cy.get('[id=species_name-textfield]').type('newSpecies')
|
||||
cy.contains('Species: This field is required').should('not.exist')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('Saved species entry successfully.')
|
||||
cy.contains('Newgenus')
|
||||
cy.contains('newspecies')
|
||||
@@ -126,8 +109,7 @@ describe('Taxonomy checks work', () => {
|
||||
|
||||
it('Creating a species that already exists in database does not work', () => {
|
||||
cy.visit('/species/new')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').contains('Copy existing taxonomy')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=add-button-21052]').click()
|
||||
cy.contains('Close').click()
|
||||
cy.get('[id=write-button]').click()
|
||||
@@ -136,8 +118,7 @@ describe('Taxonomy checks work', () => {
|
||||
|
||||
it('Invalid main taxonomy values do not work', () => {
|
||||
cy.visit('/species/new')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').contains('Copy existing taxonomy')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=add-button-21052]').click()
|
||||
cy.contains('Close').click()
|
||||
cy.get('[id=unique_identifier-textfield]').type('{backspace}new identifier') // change identifier so it's not a duplicate species
|
||||
@@ -158,8 +139,7 @@ describe('Taxonomy checks work', () => {
|
||||
|
||||
it('Invalid superorder/order pair does not work', () => {
|
||||
cy.visit('/species/new')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').contains('Copy existing taxonomy')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=add-button-21052]').click()
|
||||
cy.contains('Close').click()
|
||||
cy.get('[id=unique_identifier-textfield]').type('{backspace}new identifier')
|
||||
@@ -172,8 +152,7 @@ describe('Taxonomy checks work', () => {
|
||||
|
||||
it('Invalid order/suborder/family trio does not work', () => {
|
||||
cy.visit('/species/new')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').contains('Copy existing taxonomy')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=add-button-84357]').click()
|
||||
cy.contains('Close').click()
|
||||
cy.get('[id=unique_identifier-textfield]').type('{backspace}new identifier')
|
||||
@@ -187,8 +166,7 @@ describe('Taxonomy checks work', () => {
|
||||
|
||||
it('Invalid suborder/family pair does not work', () => {
|
||||
cy.visit('/species/new')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').contains('Copy existing taxonomy')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=add-button-84357]').click()
|
||||
cy.contains('Close').click()
|
||||
cy.get('[id=unique_identifier-textfield]').type('{backspace}new identifier')
|
||||
@@ -201,8 +179,7 @@ describe('Taxonomy checks work', () => {
|
||||
|
||||
it('Invalid family/subfamily/genus trio does not work', () => {
|
||||
cy.visit('/species/new')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').contains('Copy existing taxonomy')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=add-button-23065]').click()
|
||||
cy.contains('Close').click()
|
||||
cy.get('[id=unique_identifier-textfield]').type('{backspace}new identifier')
|
||||
@@ -216,8 +193,7 @@ describe('Taxonomy checks work', () => {
|
||||
|
||||
it('Invalid subfamily/genus pair does not work', () => {
|
||||
cy.visit('/species/new')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').contains('Copy existing taxonomy')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=add-button-23065]').click()
|
||||
cy.contains('Close').click()
|
||||
cy.get('[id=unique_identifier-textfield]').type('{backspace}new identifier')
|
||||
@@ -230,8 +206,7 @@ describe('Taxonomy checks work', () => {
|
||||
|
||||
it('When genus is indet. species has to be indet.', () => {
|
||||
cy.visit('/species/new')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').contains('Copy existing taxonomy')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=add-button-21052]').click()
|
||||
cy.contains('Close').click()
|
||||
cy.get('[id=unique_identifier-textfield]').type('{backspace}new identifier')
|
||||
@@ -246,8 +221,7 @@ describe('Taxonomy checks work', () => {
|
||||
|
||||
it('When genus is gen. species has to be sp.', () => {
|
||||
cy.visit('/species/new')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').contains('Copy existing taxonomy')
|
||||
cy.get('[id=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=copy_existing_taxonomy_button]').click()
|
||||
cy.get('[data-cy=add-button-21052]').click()
|
||||
cy.contains('Close').click()
|
||||
cy.get('[id=unique_identifier-textfield]').type('{backspace}new identifier')
|
||||
|
||||
@@ -17,12 +17,8 @@ describe('Creating time bound', () => {
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.get('[id=age-textfield]').first().type('20')
|
||||
cy.contains('Age (Ma): This field is required').should('not.exist')
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('Edited item successfully.')
|
||||
})
|
||||
})
|
||||
@@ -38,13 +34,8 @@ describe('Editing time bound', () => {
|
||||
cy.get('[id=edit-button]').click()
|
||||
cy.get('[id=age-textfield]').first().type('{backspace}{backspace}{backspace}{backspace}{backspace}1.5')
|
||||
cy.contains('This field is required').should('not.exist')
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('Edited item successfully.')
|
||||
cy.contains('1.5')
|
||||
})
|
||||
|
||||
+16
-56
@@ -16,13 +16,8 @@ describe('Creating a time unit', () => {
|
||||
cy.get('[data-cy=add-button-11]').first().click()
|
||||
cy.get('[id=low_bnd-tableselection]').first().click() // Select Lower Bound Button
|
||||
cy.get('[data-cy=add-button-14]').first().click()
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('Testing Time Unit Creation')
|
||||
cy.contains('ALMAAsianlandmammalage')
|
||||
cy.contains('C2N-o')
|
||||
@@ -130,13 +125,8 @@ describe('Creating a time unit', () => {
|
||||
cy.get('[id=sequence-tableselection]').should('have.value', 'ALMAAsianlandmammalage')
|
||||
cy.contains('C2N-o')
|
||||
cy.contains('C2N-y')
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('C2N-o')
|
||||
cy.contains('C2N-y')
|
||||
cy.contains('Creating new time-unit').should('not.exist')
|
||||
@@ -151,13 +141,8 @@ describe('Creating a time unit', () => {
|
||||
cy.get('[data-cy=add-button-11]').first().click()
|
||||
cy.get('[id=low_bnd-tableselection]').first().click()
|
||||
cy.get('[data-cy=add-button-14]').first().click()
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('C2N-o')
|
||||
cy.contains('C2N-y')
|
||||
cy.get('[id=edit-button]').click()
|
||||
@@ -165,13 +150,8 @@ describe('Creating a time unit', () => {
|
||||
cy.get('[data-cy=add-button-CalatayudTeruellocalbiozone]').first().click()
|
||||
cy.get('[id=low_bnd-tableselection]').first().click()
|
||||
cy.get('[data-cy=add-button-49]').first().click()
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('CalatayudTeruellocalbiozone')
|
||||
cy.contains('Langhian/Serravallian')
|
||||
})
|
||||
@@ -189,13 +169,8 @@ describe('Editing a time unit', () => {
|
||||
cy.get('[id=tu_display_name-textfield]').should('be.disabled') // name field is disabled when editing
|
||||
cy.get('[id=sequence-tableselection]').first().click()
|
||||
cy.get('[data-cy=add-button-ALMAAsianlandmammalage]').first().click()
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('ALMAAsianlandmammalage')
|
||||
})
|
||||
|
||||
@@ -274,13 +249,8 @@ describe('Editing a time unit', () => {
|
||||
cy.get('[id=sequence-tableselection]').should('have.value', 'CalatayudTeruellocalbiozone')
|
||||
cy.contains('C2N-y')
|
||||
cy.contains('MioceneLate-low')
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('CalatayudTeruellocalbiozone')
|
||||
cy.contains('C2N-y')
|
||||
cy.contains('MioceneLate-low')
|
||||
@@ -292,13 +262,8 @@ describe('Editing a time unit', () => {
|
||||
cy.get('[id=edit-button]').click()
|
||||
cy.get('[id=sequence-tableselection]').first().click()
|
||||
cy.get('[data-cy=add-button-easternparatethys]').first().click()
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('Bahean')
|
||||
cy.contains('easternparatethys')
|
||||
cy.get('[id=edit-button]').click()
|
||||
@@ -307,13 +272,8 @@ describe('Editing a time unit', () => {
|
||||
cy.get('[data-cy=add-button-CalatayudTeruellocalbiozone]').first().click()
|
||||
cy.get('[id=up_bnd-tableselection]').first().click()
|
||||
cy.get('[data-cy=add-button-14]').first().click()
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
|
||||
cy.addReferenceAndSave()
|
||||
cy.contains('CalatayudTeruellocalbiozone')
|
||||
cy.contains('C2N-o')
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
Cypress.Commands.add('login', username => {
|
||||
cy.clearLocalStorage()
|
||||
cy.visit('/login')
|
||||
cy.contains('Login').click()
|
||||
cy.get('[data-cy="login-button"]', { timeout: 10000 }).should('be.visible').click()
|
||||
cy.get('[data-cy="username-basic"]').type(username)
|
||||
cy.get('[data-cy="password-basic"]').type('test')
|
||||
cy.get('[data-cy="login-button"]').click()
|
||||
@@ -42,3 +42,28 @@ Cypress.Commands.add('pageForbidden', url => {
|
||||
Cypress.Commands.add('resetDatabase', () => {
|
||||
cy.task('resetDatabase')
|
||||
})
|
||||
|
||||
// use this once you have edited some data and want to save it
|
||||
Cypress.Commands.add('addReferenceAndSave', () => {
|
||||
cy.get('[id=write-button]').click()
|
||||
cy.get('[id=write-button]').should('be.disabled')
|
||||
cy.contains('button', 'Add existing reference').click()
|
||||
cy.get('button[data-cy^="add-button"]').first().click()
|
||||
cy.contains('button', 'Close').click()
|
||||
cy.get('[id=write-button]').should('not.be.disabled')
|
||||
cy.get('[id=write-button]').click()
|
||||
})
|
||||
|
||||
Cypress.Commands.add('clearNewSpeciesForm', () => {
|
||||
cy.get('[name=order_name]').clear()
|
||||
cy.get('[name=family_name]').clear()
|
||||
cy.get('[name=genus_name]').clear()
|
||||
cy.get('[name=species_name]').clear()
|
||||
cy.get('[name=subclass_or_superorder_name]').clear()
|
||||
cy.get('[name=suborder_or_superfamily_name]').clear()
|
||||
cy.get('[name=subfamily_name]').clear()
|
||||
cy.get('[name=unique_identifier]').clear()
|
||||
cy.get('[name=taxonomic_status]').clear()
|
||||
cy.get('[name=sp_comment]').clear()
|
||||
cy.get('[name=sp_author]').clear()
|
||||
})
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
### Cross Search Export
|
||||
|
||||
Cross search has a [`/export` route](../../backend/src/routes/crossSearch.ts) which is used by the ["Export Table"](../../../frontend/src/components/CrossSearch/CrossSearchExportButton.tsx) button in a cross-search table view. This route uses the same parameter parsing and validation function as the `/all` route, and it also uses the same SQL query, but with LIMIT set to undefined. The resulting data is [streamed](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) to the user, where the browser will download it as a csv file. See the "Exporting" section in [TableView docs](../frontend/components/TableView.md) for more information.
|
||||
Cross search has a [`/export` route](../../backend/src/routes/crossSearch.ts) which is used by the ["Export Table"](../../frontend/src/components/CrossSearch/CrossSearchExportMenuItem.tsx) button in a cross-search table view. This route uses the same parameter parsing and validation function as the `/all` route, and it also uses the same SQL query, but with LIMIT set to undefined. The resulting data is [streamed](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API) to the user, where the browser will download it as a csv file. See the "Exporting" section in [TableView docs](../frontend/components/TableView.md) for more information.
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
Current reference types & their field labels / field names (29.09.2024)
|
||||
# Reference types
|
||||
|
||||
As of 29.09.2024 there is no UI to add more entries however you can do it by altering the db itself.
|
||||
Note: This file is old and should be checked and updated.
|
||||
|
||||
In the test_data/sqlfiles/now_test.sql code related to adding reference types is around rows 3665-3700 and the fields for the data types are added around rows 3175-3525.
|
||||
Current reference types & their field labels / field names
|
||||
|
||||
Do concider other options before you try to alter the data by hand.
|
||||
In the `test_data/sqlfiles/now_test.sql` file, code related to adding reference types is around rows 3665-3700 and the fields for the data types are added around rows 3175-3525.
|
||||
|
||||
Do consider other options before you try to alter the data by hand.
|
||||
|
||||
## Existing fields & their ids
|
||||
|
||||
title_primary 1
|
||||
authors_primary 2
|
||||
date_primary 3
|
||||
@@ -265,4 +268,5 @@ Mandatory fields: title_primary OR gen_notes, date_primary, authors_primary, exa
|
||||
exact_date / exact_date
|
||||
|
||||
Mandatory fields: title_primary OR title_secondary OR title_series OR gen_notes, date_primary, authors_primary OR authors_secondary OR authors_series, exact_date, journal
|
||||
* In old php test version instead of journal, journal_id had to be defined. That seems odd, since when creating a new journal, journal_id will not initally be defined. So that will not be done here.
|
||||
|
||||
- In old php test version instead of journal, journal_id had to be defined. That seems odd, since when creating a new journal, journal_id will not initially be defined. So that will not be done here.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Localities
|
||||
# Species
|
||||
|
||||
## Required fields
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
### Time Bound
|
||||
### Time Bounds
|
||||
|
||||
A Time Bound can be given the following information:
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
### Time Unit
|
||||
### Time Units
|
||||
|
||||
A Time Unit can be given the following information:
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export const CrossSearchExportMenuItem = ({ handleClose }: { handleClose: () =>
|
||||
const token = useUser().token
|
||||
const fetchOptions = token ? { headers: { Authorization: `Bearer ${token}` } } : {}
|
||||
|
||||
const filename = `cross_search${currentDateAsString()}.csv`
|
||||
const filename = `locality-species-${currentDateAsString()}.csv`
|
||||
|
||||
const fetchCSVFile = async () => {
|
||||
setLoading(true)
|
||||
|
||||
@@ -25,6 +25,7 @@ export const StagingView = <T extends MRT_RowData>() => {
|
||||
<SelectingTable<Reference, T>
|
||||
buttonText="Add existing reference"
|
||||
data={data}
|
||||
title="References"
|
||||
isError={isError}
|
||||
columns={referenceTableColumns}
|
||||
idFieldName="rid"
|
||||
|
||||
@@ -22,7 +22,6 @@ export const ContactModal = ({
|
||||
setOpen(false)
|
||||
}
|
||||
|
||||
return <></>
|
||||
return (
|
||||
<Box>
|
||||
<Button onClick={() => setOpen(true)} variant="outlined" startIcon={<EmailIcon />} className="button">
|
||||
|
||||
@@ -3,6 +3,7 @@ import { EditingModal } from './EditingModal'
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { Editable, EditDataType } from '@/shared/types'
|
||||
import { useDetailContext } from '../Context/DetailContext'
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export type EditingFormField = { name: string; label: string; required?: boolean; type?: 'number' | 'string' }
|
||||
|
||||
@@ -11,6 +12,10 @@ Renders a button, that will open EditingModal, which can be used
|
||||
to add a new entry to a list, or edit existing one.
|
||||
If using for adding new, provide arrayFieldName.
|
||||
For editing existing row, use existingObject and editAction.
|
||||
|
||||
If you want to autofill the form's fields once the user e.g. clicks a button,
|
||||
pass the updated values into the replacedValues prop. The form will be reset and uses those values.
|
||||
The copyTaxonomyButton prop is used only in Locality -> Species tab.
|
||||
*/
|
||||
export const EditingForm = <T extends object, ParentType extends object>({
|
||||
dataCy,
|
||||
@@ -19,6 +24,8 @@ export const EditingForm = <T extends object, ParentType extends object>({
|
||||
editAction,
|
||||
existingObject,
|
||||
arrayFieldName,
|
||||
replacedValues,
|
||||
copyTaxonomyButton,
|
||||
}: {
|
||||
dataCy?: string
|
||||
buttonText: string
|
||||
@@ -26,15 +33,22 @@ export const EditingForm = <T extends object, ParentType extends object>({
|
||||
arrayFieldName?: keyof ParentType
|
||||
editAction?: (newObject: T) => void
|
||||
existingObject?: T | undefined
|
||||
replacedValues?: T | undefined
|
||||
copyTaxonomyButton?: JSX.Element | undefined
|
||||
}) => {
|
||||
const getDefaultValues: () => { [x: string]: unknown } = () => {
|
||||
if (!existingObject) return {}
|
||||
return existingObject
|
||||
}
|
||||
const { register, trigger, formState, getValues } = useForm({ defaultValues: getDefaultValues() })
|
||||
const { register, trigger, formState, getValues, reset } = useForm({ defaultValues: getDefaultValues() })
|
||||
const { errors } = formState
|
||||
const { editData, setEditData } = useDetailContext<ParentType>()
|
||||
|
||||
useEffect(() => {
|
||||
if (replacedValues) reset(replacedValues)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [replacedValues])
|
||||
|
||||
const onSave = async () => {
|
||||
const result = await trigger()
|
||||
if (!result) return false
|
||||
@@ -52,9 +66,11 @@ export const EditingForm = <T extends object, ParentType extends object>({
|
||||
return (
|
||||
<EditingModal {...{ dataCy, buttonText, onSave }}>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: '1em' }}>
|
||||
{copyTaxonomyButton}
|
||||
{formFields.map(field => (
|
||||
<TextField
|
||||
key={field.name}
|
||||
slotProps={{ inputLabel: { shrink: true } }}
|
||||
{...register(field.name, {
|
||||
required: field.required ? 'This field is required' : false,
|
||||
...(field.type === 'number' && {
|
||||
|
||||
@@ -12,9 +12,11 @@ give an array in prop "selectedValues" that contains the string values
|
||||
that already exist in editdata
|
||||
*/
|
||||
export const SelectingTable = <T extends MRT_RowData, ParentType extends object>({
|
||||
dataCy,
|
||||
buttonText,
|
||||
data,
|
||||
columns,
|
||||
title,
|
||||
editingAction,
|
||||
tableRowAction,
|
||||
fieldName,
|
||||
@@ -23,9 +25,11 @@ export const SelectingTable = <T extends MRT_RowData, ParentType extends object>
|
||||
isError,
|
||||
useObject,
|
||||
}: {
|
||||
dataCy?: string
|
||||
buttonText: string
|
||||
data: T[] | undefined
|
||||
columns: MRT_ColumnDef<T>[]
|
||||
title: string
|
||||
editingAction?: (object: T) => void
|
||||
tableRowAction?: (row: T) => void
|
||||
fieldName: keyof ParentType
|
||||
@@ -69,11 +73,12 @@ export const SelectingTable = <T extends MRT_RowData, ParentType extends object>
|
||||
if (isError) return <Box>Error fetching data for the selecting table.</Box>
|
||||
if (!data) return <CircularProgress />
|
||||
return (
|
||||
<EditingModal buttonText={buttonText}>
|
||||
<EditingModal buttonText={buttonText} dataCy={dataCy}>
|
||||
{data ? (
|
||||
<TableView<T>
|
||||
data={filteredData}
|
||||
columns={columns}
|
||||
title={title}
|
||||
isFetching={false}
|
||||
visibleColumns={visibleColumns}
|
||||
selectorFn={editingAction ?? defaultEditingAction}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ReferenceOfUpdate, UpdateLog } from '@/shared/types'
|
||||
import { AnyReference, ReferenceOfUpdate, UpdateLog } from '@/shared/types'
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
import { EditingModal } from '@/components/DetailView/common/EditingModal'
|
||||
import { SimpleTable } from '@/components/DetailView/common/SimpleTable'
|
||||
@@ -18,9 +18,10 @@ const makeNameList = (names: Array<string | null>) => {
|
||||
return names[0] ?? ''
|
||||
}
|
||||
|
||||
const ReferenceList = ({ references, big }: { references: ReferenceOfUpdate[]; big: boolean }) => {
|
||||
const ReferenceList = ({ references, big }: { references: AnyReference[]; big: boolean }) => {
|
||||
const getReferenceText = (ref: ReferenceOfUpdate) => {
|
||||
// php version: html/include/database.php -> referenceCitation()
|
||||
//const ref = referenceOfUpdate.ref_ref
|
||||
const authors = makeNameList(ref.ref_authors.map(author => author.author_surname))
|
||||
const issue = ref.issue ? ` (${ref.issue}) ` : ''
|
||||
if (ref.ref_type_id === 1) {
|
||||
@@ -37,6 +38,7 @@ const ReferenceList = ({ references, big }: { references: ReferenceOfUpdate[]; b
|
||||
// All other types
|
||||
return `${authors} ${ref.date_primary ? `(${ref.date_primary}). ` : ''} ${ref.title_primary?.concat('. ') ?? ''}${ref.title_secondary?.concat('. ') ?? ''}${ref.title_series?.concat('. ') ?? ''}${ref.gen_notes?.concat('.') ?? ''}`
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
{references.map(ref => (
|
||||
@@ -44,7 +46,7 @@ const ReferenceList = ({ references, big }: { references: ReferenceOfUpdate[]; b
|
||||
key={ref.rid}
|
||||
sx={{ padding: '0.4em', margin: '0.5em', maxWidth: big ? '50em' : '30em', backgroundColor: 'lightblue' }}
|
||||
>
|
||||
<div style={{ wordWrap: 'break-word', whiteSpace: 'normal' }}>{getReferenceText(ref)}</div>
|
||||
<div style={{ wordWrap: 'break-word', whiteSpace: 'normal' }}>{getReferenceText(ref.ref_ref)}</div>
|
||||
{big && (
|
||||
<div>
|
||||
<Link to={`/reference/${ref.rid}`}>View</Link>
|
||||
@@ -90,10 +92,7 @@ export const UpdateTab = <T, UpdateType extends MRT_RowData & { updates: UpdateL
|
||||
accessorKey: refFieldName as string,
|
||||
header: 'Reference',
|
||||
Cell: ({ row }: { row: MRT_Row<UpdateType> }) => (
|
||||
<ReferenceList
|
||||
references={(row.original[refFieldName] as { ref_ref: ReferenceOfUpdate }[]).map(item => item.ref_ref)}
|
||||
big={false}
|
||||
/>
|
||||
<ReferenceList references={row.original[refFieldName]} big={false} />
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -105,7 +104,7 @@ export const UpdateTab = <T, UpdateType extends MRT_RowData & { updates: UpdateL
|
||||
coordinator={row.original[`${prefix}_coordinator`] as string}
|
||||
updates={row.original.updates}
|
||||
comment={row.original[`${prefix}_comment` as keyof UpdateType] as string}
|
||||
references={row.original[refFieldName] as { ref_ref: ReferenceOfUpdate }[]}
|
||||
references={row.original[refFieldName]}
|
||||
/>
|
||||
),
|
||||
},
|
||||
@@ -118,7 +117,7 @@ export const UpdateTab = <T, UpdateType extends MRT_RowData & { updates: UpdateL
|
||||
)
|
||||
}
|
||||
|
||||
const DetailsModal = <RefType extends { ref_ref: ReferenceOfUpdate }>({
|
||||
const DetailsModal = ({
|
||||
updates,
|
||||
references,
|
||||
comment,
|
||||
@@ -127,7 +126,7 @@ const DetailsModal = <RefType extends { ref_ref: ReferenceOfUpdate }>({
|
||||
coordinator,
|
||||
}: {
|
||||
updates: UpdateLog[]
|
||||
references: RefType[]
|
||||
references: AnyReference[]
|
||||
comment: string
|
||||
date: Date
|
||||
authorizer: string
|
||||
@@ -179,11 +178,7 @@ const DetailsModal = <RefType extends { ref_ref: ReferenceOfUpdate }>({
|
||||
</Card>
|
||||
<Divider />
|
||||
<h3>References</h3>
|
||||
{references.length === 0 ? (
|
||||
<Box>Update has no references.</Box>
|
||||
) : (
|
||||
<ReferenceList references={references.map(ref => ref.ref_ref)} big />
|
||||
)}
|
||||
{references.length === 0 ? <Box>Update has no references.</Box> : <ReferenceList references={references} big />}
|
||||
<Divider />
|
||||
<h3>Changed database values</h3>
|
||||
<SimpleTable columns={columns} data={updates} />
|
||||
|
||||
@@ -119,7 +119,7 @@ export const emptyLocality = {
|
||||
} as unknown as LocalityDetailsType
|
||||
|
||||
export const emptySpecies = {
|
||||
class_name: '',
|
||||
class_name: 'Mammalia',
|
||||
order_name: '',
|
||||
family_name: '',
|
||||
subclass_or_superorder_name: '',
|
||||
@@ -128,7 +128,7 @@ export const emptySpecies = {
|
||||
genus_name: '',
|
||||
species_name: '',
|
||||
unique_identifier: '-',
|
||||
taxonomic_status: null,
|
||||
taxonomic_status: '',
|
||||
common_name: '',
|
||||
sp_author: '',
|
||||
strain: null,
|
||||
|
||||
@@ -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/taxonomyFunctions'
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import { generateKml } from '@/util/kml'
|
||||
import { generateSvg } from '../Map/generateSvg'
|
||||
import { usePageContext } from '../Page'
|
||||
import { LocalitySynonymsModal } from './LocalitySynonymsModal'
|
||||
import { currentDateAsString } from '@/shared/currentDateAsString'
|
||||
|
||||
const decimalCount = (num: number) => {
|
||||
const numAsString = num.toString()
|
||||
@@ -403,7 +404,8 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'localitiesWithSpecies.csv'
|
||||
|
||||
a.download = `localities-with-species-${currentDateAsString()}.csv`
|
||||
a.click()
|
||||
}
|
||||
|
||||
@@ -416,7 +418,7 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'localities.kml'
|
||||
a.download = `localities-${currentDateAsString()}.kml`
|
||||
a.click()
|
||||
}
|
||||
|
||||
@@ -427,7 +429,7 @@ export const LocalityTable = ({ selectorFn }: { selectorFn?: (newObject: Localit
|
||||
const url = URL.createObjectURL(blob)
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = 'map.svg'
|
||||
a.download = `localities-map-${currentDateAsString()}.svg`
|
||||
a.click()
|
||||
}
|
||||
|
||||
|
||||
@@ -192,6 +192,7 @@ export const LithologyTab = () => {
|
||||
isError={isError}
|
||||
columns={selectingTableColumns}
|
||||
data={sedimentaryStructuresData}
|
||||
title="Sedimentary structures"
|
||||
fieldName="now_ss"
|
||||
idFieldName="ss_value"
|
||||
editingAction={(newSed: SedimentaryStructureValues) => {
|
||||
|
||||
@@ -38,6 +38,7 @@ export const MuseumTab = () => {
|
||||
<SelectingTable<Museum, LocalityDetailsType>
|
||||
buttonText="Select Museum"
|
||||
data={museumData}
|
||||
title="Museums"
|
||||
isError={isError}
|
||||
columns={columns}
|
||||
fieldName="now_mus"
|
||||
|
||||
@@ -43,6 +43,7 @@ export const ProjectTab = () => {
|
||||
<SelectingTable<Project, LocalityDetailsType>
|
||||
buttonText="Select Project"
|
||||
data={projectData}
|
||||
title="Projects"
|
||||
columns={columns}
|
||||
isError={isError}
|
||||
idFieldName="pid"
|
||||
|
||||
@@ -1,17 +1,105 @@
|
||||
import { LocalityDetailsType, LocalitySpecies, Species, SpeciesDetailsType } from '@/shared/types'
|
||||
import {
|
||||
Editable,
|
||||
EditDataType,
|
||||
LocalityDetailsType,
|
||||
LocalitySpecies,
|
||||
Species,
|
||||
SpeciesDetailsType,
|
||||
} from '@/shared/types'
|
||||
import { EditableTable } from '@/components/DetailView/common/EditableTable'
|
||||
import { EditingForm } from '@/components/DetailView/common/EditingForm'
|
||||
import { SelectingTable } from '@/components/DetailView/common/SelectingTable'
|
||||
import { Grouped } from '@/components/DetailView/common/tabLayoutHelpers'
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
import { useGetAllSpeciesQuery } from '@/redux/speciesReducer'
|
||||
import { Box } from '@mui/material'
|
||||
import { Box, CircularProgress } from '@mui/material'
|
||||
import { skipToken } from '@reduxjs/toolkit/query'
|
||||
import { MRT_ColumnDef } from 'material-react-table'
|
||||
import {
|
||||
checkSpeciesTaxonomy,
|
||||
convertSpeciesTaxonomyFields,
|
||||
fixNullValuesInTaxonomyFields,
|
||||
} from '@/util/taxonomyUtilities'
|
||||
import { useNotify } from '@/hooks/notification'
|
||||
import { validateSpecies } from '@/shared/validators/species'
|
||||
import { smallSpeciesTableColumns } from '@/common'
|
||||
import { useState } from 'react'
|
||||
import { SynonymsModal } from '@/components/Species/SynonymsModal'
|
||||
|
||||
export const SpeciesTab = () => {
|
||||
const { mode, editData, setEditData } = useDetailContext<LocalityDetailsType>()
|
||||
const { data: speciesData, isError } = useGetAllSpeciesQuery(mode.read ? skipToken : undefined)
|
||||
const { notify } = useNotify()
|
||||
const [replacedValues, setReplacedValues] = useState<EditDataType<Species> | undefined>()
|
||||
const [selectedSpecies, setSelectedSpecies] = useState<string | undefined>()
|
||||
const [modalOpen, setModalOpen] = useState<boolean>(false)
|
||||
|
||||
const handleRowActionClick = (row: Species) => {
|
||||
setSelectedSpecies(row.species_id.toString())
|
||||
setModalOpen(true)
|
||||
}
|
||||
|
||||
const copyTaxonomyButton = (
|
||||
<Box key="copy_existing_taxonomy_button">
|
||||
<SelectingTable<Species, Species>
|
||||
dataCy="copy_existing_taxonomy_button"
|
||||
buttonText="Copy existing taxonomy"
|
||||
title="Copy existing taxonomy"
|
||||
data={speciesData}
|
||||
isError={isError}
|
||||
columns={smallSpeciesTableColumns}
|
||||
fieldName="order_name" // this doesn't do anything here but is required
|
||||
idFieldName="species_id"
|
||||
useObject={true}
|
||||
tableRowAction={handleRowActionClick}
|
||||
editingAction={(selectedSpecies: Species) => {
|
||||
const fixedSpecies = fixNullValuesInTaxonomyFields(selectedSpecies)
|
||||
setReplacedValues({
|
||||
subclass_or_superorder_name: fixedSpecies.subclass_or_superorder_name,
|
||||
order_name: fixedSpecies.order_name!,
|
||||
suborder_or_superfamily_name: fixedSpecies.suborder_or_superfamily_name,
|
||||
family_name: fixedSpecies.family_name!,
|
||||
subfamily_name: fixedSpecies.subfamily_name,
|
||||
genus_name: fixedSpecies.genus_name!,
|
||||
species_name: fixedSpecies.species_name!,
|
||||
unique_identifier: fixedSpecies.unique_identifier!,
|
||||
taxonomic_status: '',
|
||||
sp_comment: '',
|
||||
sp_author: '',
|
||||
})
|
||||
}}
|
||||
/>
|
||||
<SynonymsModal open={modalOpen} onClose={() => setModalOpen(false)} selectedSpecies={selectedSpecies} />
|
||||
</Box>
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
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 taxonomyErrors = checkSpeciesTaxonomy(convertedSpecies, speciesData!.concat(filteredLs), [])
|
||||
if (taxonomyErrors.size > 0) {
|
||||
const errorMessage = [...taxonomyErrors].reduce((acc, currentError) => acc + `\n${currentError}`)
|
||||
notify(errorMessage, 'error', null)
|
||||
return false
|
||||
}
|
||||
return convertedSpecies
|
||||
}
|
||||
|
||||
const speciesColumns: MRT_ColumnDef<Species>[] = [
|
||||
{
|
||||
@@ -80,6 +168,14 @@ export const SpeciesTab = () => {
|
||||
accessorKey: 'com_species.taxonomic_status',
|
||||
header: 'Taxon status',
|
||||
},
|
||||
{
|
||||
accessorKey: 'com_species.sp_comment',
|
||||
header: 'Comment',
|
||||
},
|
||||
{
|
||||
accessorKey: 'com_species.sp_author',
|
||||
header: 'Author',
|
||||
},
|
||||
]
|
||||
const formFields: { name: string; label: string; required?: boolean }[] = [
|
||||
{ name: 'order_name', label: 'Order', required: true },
|
||||
@@ -88,26 +184,36 @@ export const SpeciesTab = () => {
|
||||
{ name: 'species_name', label: 'Species', required: true },
|
||||
{ name: 'subclass_or_superorder_name', label: 'Subclass or Superorder' },
|
||||
{ name: 'suborder_or_superfamily_name', label: 'Suborder or Superfamily' },
|
||||
{ name: 'subfamily_name', label: 'Subfamily or Tribe' },
|
||||
{ name: 'unique_identifier', label: 'Unique Identifier', required: true },
|
||||
{ name: 'taxonomic_status', label: 'Taxon status' },
|
||||
{ name: 'sp_comment', label: 'Comment' },
|
||||
{ name: 'sp_author', label: 'Author' },
|
||||
]
|
||||
|
||||
if (!mode.read && !speciesData) return <CircularProgress />
|
||||
|
||||
return (
|
||||
<Grouped title="Species">
|
||||
{!mode.read && (
|
||||
<Box display="flex" gap={1}>
|
||||
<EditingForm<Species, LocalityDetailsType>
|
||||
<EditingForm<EditDataType<Species>, LocalityDetailsType>
|
||||
buttonText="Add new Species"
|
||||
formFields={formFields}
|
||||
editAction={(newSpecies: Species) => {
|
||||
existingObject={{ unique_identifier: '-' }}
|
||||
replacedValues={replacedValues}
|
||||
copyTaxonomyButton={copyTaxonomyButton}
|
||||
editAction={(newSpecies: EditDataType<Species>) => {
|
||||
const convertedSpecies = convertAndCheckNewSpeciesTaxonomy(newSpecies)
|
||||
if (!convertedSpecies) return
|
||||
setEditData({
|
||||
...editData,
|
||||
now_ls: [
|
||||
...editData.now_ls,
|
||||
{
|
||||
lid: editData.lid,
|
||||
species_id: newSpecies.species_id,
|
||||
com_species: { ...(newSpecies as unknown as SpeciesDetailsType) },
|
||||
species_id: undefined,
|
||||
com_species: { ...(convertedSpecies as unknown as SpeciesDetailsType), class_name: 'Mammalia' },
|
||||
rowState: 'new',
|
||||
},
|
||||
],
|
||||
@@ -117,6 +223,7 @@ export const SpeciesTab = () => {
|
||||
<SelectingTable<Species, LocalityDetailsType>
|
||||
buttonText="Select Species"
|
||||
data={speciesData}
|
||||
title="Species"
|
||||
isError={isError}
|
||||
columns={speciesColumns}
|
||||
fieldName="now_ls"
|
||||
@@ -129,7 +236,9 @@ export const SpeciesTab = () => {
|
||||
{
|
||||
lid: editData.lid,
|
||||
species_id: newSpecies.species_id,
|
||||
com_species: { ...(newSpecies as unknown as SpeciesDetailsType) },
|
||||
com_species: {
|
||||
...(fixNullValuesInTaxonomyFields(newSpecies) as SpeciesDetailsType),
|
||||
},
|
||||
rowState: 'new',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -64,7 +64,7 @@ export const MuseumTable = ({ selectorFn }: { selectorFn?: (museum: Museum) => v
|
||||
|
||||
return (
|
||||
<TableView<Museum>
|
||||
title="Museum"
|
||||
title="Museums"
|
||||
selectorFn={selectorFn}
|
||||
idFieldName="museum"
|
||||
columns={columns}
|
||||
|
||||
@@ -124,6 +124,7 @@ export const AuthorTab: React.FC<AuthorTabProps> = ({ field_num_param, tab_name
|
||||
<SelectingTable<ReferenceAuthorType, ReferenceDetailsType>
|
||||
buttonText={`Select ${buttonText}`}
|
||||
data={authorData}
|
||||
title="Authors"
|
||||
isError={isError}
|
||||
columns={authorColumns}
|
||||
fieldName="ref_authors"
|
||||
|
||||
@@ -88,6 +88,7 @@ export const JournalTab: React.FC<JournalTabProps> = ({ tab_name = 'Journal' })
|
||||
<SelectingTable<ReferenceJournalType, ReferenceDetailsType>
|
||||
buttonText="Select Journal"
|
||||
data={journalData}
|
||||
title="Journals"
|
||||
isError={isError}
|
||||
columns={journalColumns}
|
||||
fieldName="ref_journal"
|
||||
|
||||
@@ -106,6 +106,7 @@ export const CoordinatorTab = () => {
|
||||
<SelectingTable<PersonDetailsType, RegionDetails>
|
||||
buttonText="Select Coordinator"
|
||||
data={personsData}
|
||||
title="Coordinators"
|
||||
isError={isError}
|
||||
columns={personTableColumns}
|
||||
fieldName="now_reg_coord_people"
|
||||
|
||||
@@ -31,6 +31,7 @@ export const SequenceTable = ({ selectorFn }: { selectorFn?: (id: Sequence) => v
|
||||
selectorFn={selectorFn}
|
||||
idFieldName="sequence"
|
||||
columns={columns}
|
||||
title="Sequences"
|
||||
isFetching={isFetching}
|
||||
visibleColumns={visibleColumns}
|
||||
clickableRows={false}
|
||||
|
||||
@@ -16,6 +16,7 @@ import { validateSpecies } from '@/shared/validators/species'
|
||||
import { emptySpecies } from '../DetailView/common/defaultValues'
|
||||
import { useNotify } from '@/hooks/notification'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { fixNullValuesInTaxonomyFields } from '@/util/taxonomyUtilities'
|
||||
|
||||
export const SpeciesDetails = () => {
|
||||
const { id } = useParams()
|
||||
@@ -24,7 +25,7 @@ export const SpeciesDetails = () => {
|
||||
document.title = 'New species'
|
||||
}
|
||||
const [editSpeciesRequest, { isLoading: mutationLoading }] = useEditSpeciesMutation()
|
||||
const { isError, isFetching, data } = useGetSpeciesDetailsQuery(id!, { skip: isNew })
|
||||
const { isError, isFetching, data } = useGetSpeciesDetailsQuery(encodeURIComponent(id!), { skip: isNew })
|
||||
const { notify } = useNotify()
|
||||
const navigate = useNavigate()
|
||||
const [deleteMutation, { isSuccess: deleteSuccess, isError: deleteError }] = useDeleteSpeciesMutation()
|
||||
@@ -40,15 +41,11 @@ export const SpeciesDetails = () => {
|
||||
}, [deleteSuccess, deleteError, notify, navigate])
|
||||
|
||||
useEffect(() => {
|
||||
// changes subclass, suborder, and subfamily from null to empty string to make validators work
|
||||
// changes subclass, suborder, subfamily and taxonomic_status from null to empty string to make validators work
|
||||
// this should be removed if validators are improved in the future
|
||||
if (data) {
|
||||
setModifiedData({
|
||||
...data,
|
||||
subclass_or_superorder_name: data.subclass_or_superorder_name ?? '',
|
||||
suborder_or_superfamily_name: data.suborder_or_superfamily_name ?? '',
|
||||
subfamily_name: data.subfamily_name ?? '',
|
||||
})
|
||||
const fixedSpeciesData = fixNullValuesInTaxonomyFields(data)
|
||||
setModifiedData(fixedSpeciesData as SpeciesDetailsType)
|
||||
}
|
||||
}, [data])
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@ export const LocalityTab = () => {
|
||||
<SelectingTable<Editable<Locality>, SpeciesDetailsType>
|
||||
buttonText="Select Locality"
|
||||
columns={columns}
|
||||
title="Localities"
|
||||
isError={isError}
|
||||
data={localitiesData}
|
||||
fieldName="now_ls"
|
||||
|
||||
@@ -4,9 +4,66 @@ 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'
|
||||
import { useGetAllSpeciesQuery } from '@/redux/speciesReducer'
|
||||
import { skipToken } from '@reduxjs/toolkit/query'
|
||||
import { CircularProgress } from '@mui/material'
|
||||
|
||||
export const SynonymTab = () => {
|
||||
const { mode, setEditData, editData } = useDetailContext<SpeciesDetailsType>()
|
||||
const { data: speciesQueryData, isFetching } = useGetAllSpeciesQuery(mode.read ? skipToken : undefined)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
for (const existingSpecies of speciesQueryData!) {
|
||||
if (
|
||||
convertedSynonym.syn_genus_name === existingSpecies.genus_name &&
|
||||
convertedSynonym.syn_species_name === existingSpecies.species_name
|
||||
) {
|
||||
notify(
|
||||
`${convertedSynonym.syn_genus_name} ${convertedSynonym.syn_species_name} \
|
||||
has already been created as a separate species, adding it as a synonym is currently not allowed.`,
|
||||
'error',
|
||||
null
|
||||
)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return convertedSynonym
|
||||
}
|
||||
|
||||
const columns: MRT_ColumnDef<SpeciesSynonym>[] = [
|
||||
{
|
||||
@@ -31,15 +88,17 @@ export const SynonymTab = () => {
|
||||
|
||||
const editingForm = (
|
||||
<EditingForm<Species, SpeciesDetailsType>
|
||||
buttonText="Add new Species"
|
||||
buttonText="Add synonym"
|
||||
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',
|
||||
},
|
||||
],
|
||||
@@ -48,6 +107,8 @@ export const SynonymTab = () => {
|
||||
/>
|
||||
)
|
||||
|
||||
if (isFetching) return <CircularProgress />
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grouped title="Synonyms">
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { SpeciesDetailsType, Species } from '@/shared/types'
|
||||
import { ArrayFrame, HalfFrames } from '@/components/DetailView/common/tabLayoutHelpers'
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
import { emptyOption } from '@/components/DetailView/common/misc'
|
||||
import { fixNullValuesInTaxonomyFields } from '@/util/taxonomyUtilities'
|
||||
import { Box } from '@mui/material'
|
||||
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 { taxonStatusOptions } from '@/shared/taxonStatusOptions'
|
||||
|
||||
export const TaxonomyTab = () => {
|
||||
const { textField, dropdown, bigTextField, editData, setEditData, mode } = useDetailContext<SpeciesDetailsType>()
|
||||
@@ -21,10 +22,12 @@ export const TaxonomyTab = () => {
|
||||
}
|
||||
|
||||
const copyTaxonomyButton = (
|
||||
<Box key="copy_existing_taxonomy_button" id="copy_existing_taxonomy_button">
|
||||
<Box key="copy_existing_taxonomy_button">
|
||||
<SelectingTable<Species, Species>
|
||||
dataCy="copy_existing_taxonomy_button"
|
||||
buttonText="Copy existing taxonomy"
|
||||
data={speciesQueryData}
|
||||
title="Species"
|
||||
isError={isError}
|
||||
columns={smallSpeciesTableColumns}
|
||||
fieldName="order_name" // this doesn't do anything here but is required
|
||||
@@ -32,16 +35,17 @@ export const TaxonomyTab = () => {
|
||||
useObject={true}
|
||||
tableRowAction={handleRowActionClick}
|
||||
editingAction={(selectedSpecies: Species) => {
|
||||
const fixedSpecies = fixNullValuesInTaxonomyFields(selectedSpecies)
|
||||
setEditData({
|
||||
...editData,
|
||||
subclass_or_superorder_name: selectedSpecies.subclass_or_superorder_name ?? '',
|
||||
order_name: selectedSpecies.order_name ?? '',
|
||||
suborder_or_superfamily_name: selectedSpecies.suborder_or_superfamily_name ?? '',
|
||||
family_name: selectedSpecies.family_name ?? '',
|
||||
subfamily_name: selectedSpecies.subfamily_name ?? '',
|
||||
genus_name: selectedSpecies.genus_name ?? '',
|
||||
species_name: selectedSpecies.species_name ?? '',
|
||||
unique_identifier: selectedSpecies.unique_identifier ?? '',
|
||||
subclass_or_superorder_name: fixedSpecies.subclass_or_superorder_name,
|
||||
order_name: fixedSpecies.order_name!,
|
||||
suborder_or_superfamily_name: fixedSpecies.suborder_or_superfamily_name,
|
||||
family_name: fixedSpecies.family_name!,
|
||||
subfamily_name: fixedSpecies.subfamily_name,
|
||||
genus_name: fixedSpecies.genus_name!,
|
||||
species_name: fixedSpecies.species_name!,
|
||||
unique_identifier: fixedSpecies.unique_identifier!,
|
||||
})
|
||||
}}
|
||||
/>
|
||||
@@ -49,16 +53,6 @@ export const TaxonomyTab = () => {
|
||||
</Box>
|
||||
)
|
||||
|
||||
const taxonStatusOptions = [
|
||||
emptyOption,
|
||||
'family attrib of genus uncertain',
|
||||
'genus attrib of species uncertain',
|
||||
'informal species',
|
||||
'species validity uncertain',
|
||||
'taxonomic validity uncertain',
|
||||
'NOW synonym',
|
||||
]
|
||||
|
||||
const classification = [
|
||||
[
|
||||
'Class',
|
||||
|
||||
@@ -12,6 +12,7 @@ import { usePageContext } from '../Page'
|
||||
|
||||
export const TableToolBar = <T extends MRT_RowData>({
|
||||
table,
|
||||
tableName,
|
||||
combinedExport,
|
||||
kmlExport,
|
||||
svgExport,
|
||||
@@ -21,6 +22,7 @@ export const TableToolBar = <T extends MRT_RowData>({
|
||||
showNewButton,
|
||||
}: {
|
||||
table: MRT_TableInstance<T>
|
||||
tableName: string
|
||||
combinedExport?: (lids: number[]) => Promise<void>
|
||||
kmlExport?: (table: MRT_TableInstance<T>) => void
|
||||
svgExport?: (table: MRT_TableInstance<T>) => void
|
||||
@@ -87,7 +89,7 @@ export const TableToolBar = <T extends MRT_RowData>({
|
||||
) : (
|
||||
<MenuItem
|
||||
onClick={() => {
|
||||
exportRows(table)
|
||||
exportRows(table, tableName)
|
||||
handleClose()
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -57,10 +57,9 @@ export const TableView = <T extends MRT_RowData>({
|
||||
idFieldName: keyof T
|
||||
checkRowRestriction?: (row: T) => boolean
|
||||
selectorFn?: (id: T) => void
|
||||
// here
|
||||
tableRowAction?: (row: T) => void
|
||||
url?: string
|
||||
title?: string
|
||||
title: string
|
||||
combinedExport?: (lids: number[]) => Promise<void>
|
||||
kmlExport?: (table: MRT_TableInstance<T>) => void
|
||||
svgExport?: (table: MRT_TableInstance<T>) => void
|
||||
@@ -259,6 +258,7 @@ export const TableView = <T extends MRT_RowData>({
|
||||
{title && <Box className="title">{title}</Box>}
|
||||
<TableToolBar<T>
|
||||
table={table}
|
||||
tableName={title}
|
||||
combinedExport={combinedExport}
|
||||
kmlExport={kmlExport}
|
||||
svgExport={svgExport}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { MRT_RowData, MRT_TableInstance } from 'material-react-table'
|
||||
import { mkConfig, generateCsv, download } from 'export-to-csv'
|
||||
import { AcceptedData } from 'node_modules/export-to-csv/output/lib/types'
|
||||
import { currentDateAsString } from '@/shared/currentDateAsString'
|
||||
|
||||
export type ExportFn<T> = (data: T) => { [k: string]: AcceptedData }
|
||||
|
||||
export const exportRows = <T extends MRT_RowData>(table: MRT_TableInstance<T>) => {
|
||||
export const exportRows = <T extends MRT_RowData>(table: MRT_TableInstance<T>, tableName: string) => {
|
||||
const csvConfig = mkConfig({
|
||||
fieldSeparator: ',',
|
||||
decimalSeparator: '.',
|
||||
useKeysAsHeaders: true,
|
||||
quoteStrings: true, // If true, will wrap all values in double quotes
|
||||
filename: `${tableName.toLowerCase()}-${currentDateAsString()}`,
|
||||
})
|
||||
const rowData = table.getPrePaginationRowModel().rows.map(row => {
|
||||
const r = {} as Record<string, AcceptedData>
|
||||
|
||||
@@ -11,6 +11,15 @@ export const LocalityTab = () => {
|
||||
if (isError) return 'Error loading Localities.'
|
||||
if (!localitiesData) return <CircularProgress />
|
||||
|
||||
// changes min and max age values to checkmarks or X symbols, to show whether this time unit is set as the basis for min/max age or not
|
||||
const modifiedData = localitiesData.map(locality => {
|
||||
return {
|
||||
...locality,
|
||||
min_age: locality.bfa_min === data.tu_name ? '\u2713' : '\u0058',
|
||||
max_age: locality.bfa_max === data.tu_name ? '\u2713' : '\u0058',
|
||||
}
|
||||
})
|
||||
|
||||
const columns = [
|
||||
{
|
||||
accessorKey: 'loc_name',
|
||||
@@ -30,5 +39,5 @@ export const LocalityTab = () => {
|
||||
},
|
||||
]
|
||||
|
||||
return <SimpleTable columns={columns} data={localitiesData} idFieldName="lid" url="locality" />
|
||||
return <SimpleTable columns={columns} data={modifiedData} idFieldName="lid" url="locality" />
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ export const api = createApi({
|
||||
'localities',
|
||||
'species',
|
||||
'specieslist',
|
||||
'speciessynonyms',
|
||||
'reference',
|
||||
'references',
|
||||
'timeunits',
|
||||
|
||||
@@ -28,7 +28,8 @@ const localitiesApi = api.injectEndpoints({
|
||||
method: 'PUT',
|
||||
body: { locality },
|
||||
}),
|
||||
invalidatesTags: (result, _error, { lid }) => (result ? [{ type: 'locality', id: lid }, 'localities'] : []),
|
||||
invalidatesTags: (result, _error, { lid }) =>
|
||||
result ? [{ type: 'locality', id: lid }, 'localities', 'specieslist'] : [],
|
||||
}),
|
||||
deleteLocality: builder.mutation<void, number>({
|
||||
query: id => ({
|
||||
|
||||
@@ -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,16 @@
|
||||
// cannot be imported from DetailView -> misc since files in shared folder cannot import from elsewhere in frontend
|
||||
const emptyOption = {
|
||||
optionDisplay: 'No value',
|
||||
display: '',
|
||||
value: '',
|
||||
}
|
||||
|
||||
export const taxonStatusOptions = [
|
||||
emptyOption,
|
||||
'family attrib of genus uncertain',
|
||||
'genus attrib of species uncertain',
|
||||
'informal species',
|
||||
'species validity uncertain',
|
||||
'taxonomic validity uncertain',
|
||||
'NOW synonym',
|
||||
]
|
||||
@@ -332,6 +332,7 @@ export type Species = {
|
||||
locomo2: string | null
|
||||
locomo3: string | null
|
||||
sp_comment: string | null
|
||||
sp_author: string | null
|
||||
}
|
||||
|
||||
export type RegionDetails = Prisma.now_reg_coord & { now_reg_coord_people: Array<RegionCoordinator> } & {
|
||||
@@ -443,8 +444,12 @@ export type ReferenceOfUpdate = Prisma.ref_ref & {
|
||||
ref_journal: Prisma.ref_journal
|
||||
}
|
||||
|
||||
export type LocalityReference = Prisma.now_lr & ReferenceOfUpdate
|
||||
export type SpeciesReference = Prisma.now_sr & ReferenceOfUpdate
|
||||
export type LocalityReference = Prisma.now_lr & { ref_ref: ReferenceOfUpdate }
|
||||
export type SpeciesReference = Prisma.now_sr & { ref_ref: ReferenceOfUpdate }
|
||||
export type TimeUnitReference = Prisma.now_tr & { ref_ref: ReferenceOfUpdate }
|
||||
export type TimeBoundReference = Prisma.now_br & { ref_ref: ReferenceOfUpdate }
|
||||
|
||||
export type AnyReference = LocalityReference | SpeciesReference | TimeUnitReference | TimeBoundReference
|
||||
|
||||
export type ReferenceJournalType = {
|
||||
journal_id?: number
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { taxonStatusOptions } from '../taxonStatusOptions'
|
||||
import { EditDataType, SpeciesDetailsType } from '../types'
|
||||
import { Validators, validator } from './validator'
|
||||
|
||||
@@ -80,6 +81,7 @@ export const validateSpecies = (
|
||||
name: 'Species',
|
||||
required: true,
|
||||
asString: (speciesName: string) => {
|
||||
if (speciesName.indexOf(' ') !== -1) return 'Species name must not contain any spaces.'
|
||||
if (speciesName !== 'indet.' && editData.genus_name === 'indet.')
|
||||
return 'when the Genus is indet., Species must also be indet.'
|
||||
if (speciesName !== 'sp.' && editData.genus_name === 'gen.')
|
||||
@@ -89,6 +91,14 @@ export const validateSpecies = (
|
||||
return
|
||||
},
|
||||
},
|
||||
taxonomic_status: {
|
||||
name: 'Taxonomic Status',
|
||||
asString: (taxonomicStatus: string) => {
|
||||
if (!taxonStatusOptions.slice(1).includes(taxonomicStatus) && taxonomicStatus !== '')
|
||||
return `Taxonomic Status must be one of the following (or left empty): ${taxonStatusOptions.slice(1).join(', ')}.`
|
||||
return
|
||||
},
|
||||
},
|
||||
unique_identifier: {
|
||||
name: 'Unique Identifier',
|
||||
required: true,
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EditDataType, Species, SpeciesSynonym } from '@/shared/types'
|
||||
import { EditDataType, Species, SpeciesDetailsType, SpeciesSynonym } from '@/shared/types'
|
||||
|
||||
const generateMultipleParentsError = (
|
||||
invalidField: string,
|
||||
@@ -11,7 +11,10 @@ Please contact the NOW administration to fix this taxonomy.`
|
||||
|
||||
const isDuplicateTaxon = (newSpecies: EditDataType<Species>, existingSpecies: Species) => {
|
||||
if (
|
||||
newSpecies.species_id !== existingSpecies.species_id &&
|
||||
// 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)
|
||||
((newSpecies.species_id === undefined && existingSpecies.species_id === undefined) ||
|
||||
newSpecies.species_id !== existingSpecies.species_id) &&
|
||||
newSpecies.genus_name === existingSpecies.genus_name &&
|
||||
newSpecies.species_name === existingSpecies.species_name &&
|
||||
newSpecies.unique_identifier === existingSpecies.unique_identifier
|
||||
@@ -133,7 +136,7 @@ const checkSubFamilyGenus = (newSpecies: EditDataType<Species>, existingSpecies:
|
||||
)
|
||||
}
|
||||
|
||||
export const checkTaxonomy = (
|
||||
export const checkSpeciesTaxonomy = (
|
||||
newSpecies: EditDataType<Species>,
|
||||
existingSpeciesArray: Species[],
|
||||
synonyms: SpeciesSynonym[]
|
||||
@@ -314,7 +317,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) {
|
||||
@@ -336,3 +339,31 @@ export const convertTaxonomyFields = (species: EditDataType<Species>) => {
|
||||
}
|
||||
return convertedSpecies as EditDataType<Species>
|
||||
}
|
||||
|
||||
export const fixNullValuesInTaxonomyFields = (species: Species | SpeciesDetailsType) => {
|
||||
return {
|
||||
...species,
|
||||
subclass_or_superorder_name: species.subclass_or_superorder_name ?? '',
|
||||
suborder_or_superfamily_name: species.suborder_or_superfamily_name ?? '',
|
||||
subfamily_name: species.subfamily_name ?? '',
|
||||
taxonomic_status: species.taxonomic_status ?? '',
|
||||
}
|
||||
}
|
||||
|
||||
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