mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Occurrence details page now edits or creates occurrences through the locality route in backend, also add button to select species for occurrence
This commit is contained in:
@@ -82,6 +82,11 @@ export const writeLocality = async (
|
|||||||
localitySpecies.species_id = species_id as number
|
localitySpecies.species_id = species_id as number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const localitySpecies of locality.now_ls) {
|
||||||
|
if (localitySpecies.rowState === 'new' || localitySpecies.rowState === 'removed') continue
|
||||||
|
await writeHandler.updateObject('now_ls', localitySpecies, ['lid', 'species_id'])
|
||||||
|
}
|
||||||
|
|
||||||
await writeHandler.applyListChanges('now_ls', locality.now_ls, ['lid', 'species_id'])
|
await writeHandler.applyListChanges('now_ls', locality.now_ls, ['lid', 'species_id'])
|
||||||
await writeHandler.applyListChanges('now_mus', locality.now_mus, ['lid', 'museum'])
|
await writeHandler.applyListChanges('now_mus', locality.now_mus, ['lid', 'museum'])
|
||||||
await writeHandler.applyListChanges('now_ss', locality.now_ss, ['lid', 'sed_struct'])
|
await writeHandler.applyListChanges('now_ss', locality.now_ss, ['lid', 'sed_struct'])
|
||||||
|
|||||||
@@ -248,16 +248,17 @@ export const OccurrencesTab = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Grouped title={occurrenceLabels.informationSectionTitle}>
|
<Grouped title={occurrenceLabels.informationSectionTitle}>
|
||||||
<Button
|
{!mode.read && (
|
||||||
disabled={mode.new}
|
<Button
|
||||||
variant="contained"
|
disabled={mode.new}
|
||||||
onClick={() =>
|
variant="contained"
|
||||||
window.open(`${window.location.origin}/occurrence/new?lid=${data.lid}&loc_name=${data.loc_name}`)
|
onClick={() =>
|
||||||
}
|
window.open(`${window.location.origin}/occurrence/new?lid=${data.lid}&loc_name=${data.loc_name}`)
|
||||||
>
|
}
|
||||||
Create new occurrence
|
>
|
||||||
</Button>
|
Create new occurrence
|
||||||
{!mode.read && editingModal}
|
</Button>
|
||||||
|
)}
|
||||||
<EditableTable<Editable<LocalitySpecies>, LocalityDetailsType>
|
<EditableTable<Editable<LocalitySpecies>, LocalityDetailsType>
|
||||||
columns={columns}
|
columns={columns}
|
||||||
field="now_ls"
|
field="now_ls"
|
||||||
|
|||||||
@@ -1,15 +1,22 @@
|
|||||||
import { CircularProgress } from '@mui/material'
|
import { CircularProgress } from '@mui/material'
|
||||||
import { useParams, useSearchParams } from 'react-router-dom'
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom'
|
||||||
import { DetailView, TabType } from '@/components/DetailView/DetailView'
|
import { DetailView, TabType } from '@/components/DetailView/DetailView'
|
||||||
import { UpdateTab } from '@/components/DetailView/common/UpdateTab'
|
import { UpdateTab } from '@/components/DetailView/common/UpdateTab'
|
||||||
import { OccurrenceCoreTab } from './Tabs/OccurrenceCoreTab'
|
import { OccurrenceCoreTab } from './Tabs/OccurrenceCoreTab'
|
||||||
import { OccurrenceWearTab } from './Tabs/OccurrenceWearTab'
|
import { OccurrenceWearTab } from './Tabs/OccurrenceWearTab'
|
||||||
import { OccurrenceIsotopeTab } from './Tabs/OccurrenceIsotopeTab'
|
import { OccurrenceIsotopeTab } from './Tabs/OccurrenceIsotopeTab'
|
||||||
import { EditDataType, EditableOccurrenceData, OccurrenceDetailsType } from '@/shared/types'
|
import {
|
||||||
|
EditDataType,
|
||||||
|
EditableOccurrenceData,
|
||||||
|
EditMetaData,
|
||||||
|
LocalityDetailsType,
|
||||||
|
OccurrenceDetailsType,
|
||||||
|
} from '@/shared/types'
|
||||||
import { validateOccurrence } from '@/shared/validators/occurrence'
|
import { validateOccurrence } from '@/shared/validators/occurrence'
|
||||||
import { getErrorMessage, useNotify } from '@/hooks/notification'
|
import { getErrorMessage, useNotify } from '@/hooks/notification'
|
||||||
import { ValidationObject } from '@/shared/validators/validator'
|
import { ValidationObject } from '@/shared/validators/validator'
|
||||||
import { useEditOccurrenceMutation, useGetOccurrenceDetailsQuery } from '@/redux/api'
|
import { useGetOccurrenceDetailsQuery } from '@/redux/api'
|
||||||
|
import { useEditLocalityMutation, useGetLocalityDetailsQuery } from '@/redux/localityReducer'
|
||||||
|
|
||||||
const validateOccurrenceDetail = (
|
const validateOccurrenceDetail = (
|
||||||
editData: EditDataType<OccurrenceDetailsType>,
|
editData: EditDataType<OccurrenceDetailsType>,
|
||||||
@@ -66,10 +73,41 @@ const emptyOccurrence: OccurrenceDetailsType = {
|
|||||||
now_oau: [],
|
now_oau: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const occurrenceFields: Array<keyof EditableOccurrenceData> = [
|
||||||
|
'nis',
|
||||||
|
'pct',
|
||||||
|
'quad',
|
||||||
|
'mni',
|
||||||
|
'qua',
|
||||||
|
'id_status',
|
||||||
|
'orig_entry',
|
||||||
|
'source_name',
|
||||||
|
'body_mass',
|
||||||
|
'mesowear',
|
||||||
|
'mw_or_high',
|
||||||
|
'mw_or_low',
|
||||||
|
'mw_cs_sharp',
|
||||||
|
'mw_cs_round',
|
||||||
|
'mw_cs_blunt',
|
||||||
|
'mw_scale_min',
|
||||||
|
'mw_scale_max',
|
||||||
|
'mw_value',
|
||||||
|
'microwear',
|
||||||
|
'dc13_mean',
|
||||||
|
'dc13_n',
|
||||||
|
'dc13_max',
|
||||||
|
'dc13_min',
|
||||||
|
'dc13_stdev',
|
||||||
|
'do18_mean',
|
||||||
|
'do18_n',
|
||||||
|
'do18_max',
|
||||||
|
'do18_min',
|
||||||
|
'do18_stdev',
|
||||||
|
]
|
||||||
|
|
||||||
export const OccurrenceDetails = () => {
|
export const OccurrenceDetails = () => {
|
||||||
const { id, lid, speciesId } = useParams()
|
const { id, lid, speciesId } = useParams()
|
||||||
const [searchParams] = useSearchParams()
|
const [searchParams] = useSearchParams()
|
||||||
console.log(searchParams)
|
|
||||||
const isNew = id === 'new'
|
const isNew = id === 'new'
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
document.title = 'New locality'
|
document.title = 'New locality'
|
||||||
@@ -86,11 +124,15 @@ export const OccurrenceDetails = () => {
|
|||||||
skip: isNew,
|
skip: isNew,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const { notify } = useNotify()
|
const { notify } = useNotify()
|
||||||
const [editOccurrenceRequest, { isLoading: mutationLoading }] = useEditOccurrenceMutation()
|
const navigate = useNavigate()
|
||||||
|
const [editLocalityRequest, { isLoading: mutationLoading }] = useEditLocalityMutation()
|
||||||
|
|
||||||
const lidFromSearchParams = searchParams.get('lid')
|
const lidFromSearchParams = searchParams.get('lid')
|
||||||
const locNameFromSearchParams = searchParams.get('loc_name')
|
const locNameFromSearchParams = searchParams.get('loc_name')
|
||||||
|
const localityId = lidFromSearchParams ?? lid ?? ''
|
||||||
|
const { data: localityData } = useGetLocalityDetailsQuery(localityId)
|
||||||
|
|
||||||
if (isError) return <div>Error loading occurrence data</div>
|
if (isError) return <div>Error loading occurrence data</div>
|
||||||
if (isLoading || (!occurrenceData && !isNew) || mutationLoading) return <CircularProgress />
|
if (isLoading || (!occurrenceData && !isNew) || mutationLoading) return <CircularProgress />
|
||||||
@@ -98,19 +140,58 @@ export const OccurrenceDetails = () => {
|
|||||||
const initialOccurrence = emptyOccurrence
|
const initialOccurrence = emptyOccurrence
|
||||||
|
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
initialOccurrence.lid = parseInt(lidFromSearchParams!, 10)
|
initialOccurrence.lid = parseInt(localityId, 10)
|
||||||
initialOccurrence.loc_name = locNameFromSearchParams!
|
initialOccurrence.loc_name = locNameFromSearchParams ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
if (occurrenceData) {
|
if (occurrenceData) {
|
||||||
document.title = `Occurrence - ${occurrenceData.lid}/${occurrenceData.species_id}`
|
document.title = `Occurrence - ${occurrenceData.lid}/${occurrenceData.species_id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
const onWrite = async (editData: EditDataType<OccurrenceDetailsType>) => {
|
const onWrite = async (editData: EditDataType<OccurrenceDetailsType> & EditMetaData) => {
|
||||||
try {
|
try {
|
||||||
console.log(editData)
|
if (!localityData) throw new Error('Could not load the linked locality.')
|
||||||
await editOccurrenceRequest(editData).unwrap()
|
|
||||||
|
const occurrenceSpeciesId = isNew ? editData.species_id : parsedSpeciesId
|
||||||
|
const existingOccurrence = localityData.now_ls.find(row => row.species_id === occurrenceSpeciesId)
|
||||||
|
const occurrenceData = occurrenceFields.reduce<Record<string, unknown>>((data, field) => {
|
||||||
|
if (field in editData) data[field] = editData[field]
|
||||||
|
return data
|
||||||
|
}, {})
|
||||||
|
const occurrence = {
|
||||||
|
...(existingOccurrence ?? {}),
|
||||||
|
lid: localityData.lid,
|
||||||
|
species_id: occurrenceSpeciesId ?? existingOccurrence?.species_id,
|
||||||
|
...occurrenceData,
|
||||||
|
...(isNew
|
||||||
|
? {
|
||||||
|
rowState: 'new' as const,
|
||||||
|
com_species: {
|
||||||
|
com_taxa_synonym: [],
|
||||||
|
now_sau: [],
|
||||||
|
species_id: editData.species_id,
|
||||||
|
family_name: editData.family_name,
|
||||||
|
genus_name: editData.genus_name,
|
||||||
|
species_name: editData.species_name,
|
||||||
|
unique_identifier: editData.unique_identifier,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
|
}
|
||||||
|
|
||||||
|
const nowLs = (
|
||||||
|
isNew
|
||||||
|
? [...localityData.now_ls, occurrence]
|
||||||
|
: localityData.now_ls.map(row => (row.species_id === occurrenceSpeciesId ? occurrence : row))
|
||||||
|
) as EditDataType<LocalityDetailsType>['now_ls']
|
||||||
|
await editLocalityRequest({
|
||||||
|
...localityData,
|
||||||
|
now_ls: nowLs,
|
||||||
|
comment: editData.comment,
|
||||||
|
references: editData.references ?? [],
|
||||||
|
}).unwrap()
|
||||||
notify('Occurrence entry finalized successfully.')
|
notify('Occurrence entry finalized successfully.')
|
||||||
|
setTimeout(() => navigate(`/occurrence/${editData.lid}/${editData.species_id}`), 15)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notify(getErrorMessage(error, 'Could not finalize occurrence entry.'), 'error')
|
notify(getErrorMessage(error, 'Could not finalize occurrence entry.'), 'error')
|
||||||
throw error
|
throw error
|
||||||
|
|||||||
@@ -1,37 +1,51 @@
|
|||||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||||
import { ArrayFrame, HalfFrames } from '@/components/DetailView/common/tabLayoutHelpers'
|
import { ArrayFrame, HalfFrames } from '@/components/DetailView/common/tabLayoutHelpers'
|
||||||
import { OccurrenceDetailsType } from '@/shared/types'
|
import { EditDataType, LocalityDetailsType, OccurrenceDetailsType, Species, SpeciesDetailsType } from '@/shared/types'
|
||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
import { idStatusOptions, quantityOptions } from '../constants'
|
import { idStatusOptions, quantityOptions } from '../constants'
|
||||||
import { useGetAllSpeciesQuery } from '@/redux/speciesReducer'
|
import { useGetAllSpeciesQuery } from '@/redux/speciesReducer'
|
||||||
import { buildTaxonomySuggestionOptions, TaxonomySuggestionFieldName } from '@/components/Species/taxonomySuggestions'
|
import { SelectingTable } from '@/components/DetailView/common/SelectingTable'
|
||||||
import { useMemo } from 'react'
|
import { MRT_ColumnDef } from 'material-react-table'
|
||||||
import { TaxonomySuggestionField } from '@/components/Species/TaxonomySuggestionField'
|
|
||||||
|
|
||||||
const toText = (value: string | number | null | undefined) =>
|
const toText = (value: string | number | null | undefined) =>
|
||||||
value === null || value === undefined || value === '' ? '-' : String(value)
|
value === null || value === undefined || value === '' ? '-' : String(value)
|
||||||
|
const speciesColumns: MRT_ColumnDef<Species>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: 'order_name',
|
||||||
|
header: 'Order',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'family_name',
|
||||||
|
header: 'Family',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'genus_name',
|
||||||
|
header: 'Genus',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'species_name',
|
||||||
|
header: 'Species',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'subclass_or_superorder_name',
|
||||||
|
header: 'Subclass or Superorder',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'suborder_or_superfamily_name',
|
||||||
|
header: 'Suborder or Superfamily',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'unique_identifier',
|
||||||
|
header: 'Unique Identifier',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'taxonomic_status',
|
||||||
|
header: 'Taxon status',
|
||||||
|
},
|
||||||
|
]
|
||||||
export const OccurrenceCoreTab = () => {
|
export const OccurrenceCoreTab = () => {
|
||||||
const { data: speciesQueryData, isError } = useGetAllSpeciesQuery()
|
const { data: speciesQueryData, isError } = useGetAllSpeciesQuery()
|
||||||
const { data, editData, mode, textField, dropdown } = useDetailContext<OccurrenceDetailsType>()
|
const { data, editData, setEditData, mode, textField, dropdown } = useDetailContext<OccurrenceDetailsType>()
|
||||||
|
|
||||||
console.log(editData)
|
|
||||||
const taxonomySuggestionOptions = useMemo(
|
|
||||||
() => ({
|
|
||||||
subclass_or_superorder_name: buildTaxonomySuggestionOptions(speciesQueryData, 'subclass_or_superorder_name'),
|
|
||||||
order_name: buildTaxonomySuggestionOptions(speciesQueryData, 'order_name'),
|
|
||||||
suborder_or_superfamily_name: buildTaxonomySuggestionOptions(speciesQueryData, 'suborder_or_superfamily_name'),
|
|
||||||
family_name: buildTaxonomySuggestionOptions(speciesQueryData, 'family_name'),
|
|
||||||
subfamily_name: buildTaxonomySuggestionOptions(speciesQueryData, 'subfamily_name'),
|
|
||||||
genus_name: buildTaxonomySuggestionOptions(speciesQueryData, 'genus_name'),
|
|
||||||
species_name: buildTaxonomySuggestionOptions(speciesQueryData, 'species_name'),
|
|
||||||
}),
|
|
||||||
[speciesQueryData]
|
|
||||||
)
|
|
||||||
|
|
||||||
const taxonomySuggestionField = (field: TaxonomySuggestionFieldName) => (
|
|
||||||
<TaxonomySuggestionField field={field} options={taxonomySuggestionOptions[field]} />
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -47,9 +61,35 @@ export const OccurrenceCoreTab = () => {
|
|||||||
{toText(data.loc_name)}
|
{toText(data.loc_name)}
|
||||||
</Link>,
|
</Link>,
|
||||||
],
|
],
|
||||||
['Family', taxonomySuggestionField('family_name')],
|
!mode.read
|
||||||
['Genus', taxonomySuggestionField('genus_name')],
|
? [
|
||||||
['Species', taxonomySuggestionField('species_name')],
|
'',
|
||||||
|
<SelectingTable
|
||||||
|
key={'selecting-table'}
|
||||||
|
buttonText="Select Species"
|
||||||
|
data={speciesQueryData}
|
||||||
|
title="Species"
|
||||||
|
isError={isError}
|
||||||
|
columns={speciesColumns}
|
||||||
|
fieldName="species_id"
|
||||||
|
idFieldName="species_id"
|
||||||
|
useObject
|
||||||
|
editingAction={(newSpecies: Species) => {
|
||||||
|
setEditData({
|
||||||
|
...editData,
|
||||||
|
family_name: newSpecies.family_name,
|
||||||
|
genus_name: newSpecies.genus_name!,
|
||||||
|
species_name: newSpecies.species_name!,
|
||||||
|
species_id: newSpecies.species_id,
|
||||||
|
unique_identifier: newSpecies.unique_identifier,
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
['Family', editData.family_name],
|
||||||
|
['Genus', editData.genus_name],
|
||||||
|
['Species', editData.species_name],
|
||||||
['ID status', dropdown('id_status', idStatusOptions, 'ID status')],
|
['ID status', dropdown('id_status', idStatusOptions, 'ID status')],
|
||||||
['Additional Information', textField('orig_entry', { type: 'text' })],
|
['Additional Information', textField('orig_entry', { type: 'text' })],
|
||||||
['Source name', textField('source_name', { type: 'text' })],
|
['Source name', textField('source_name', { type: 'text' })],
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { api } from './api'
|
import { api } from './api'
|
||||||
import { addLocality } from '@/redux/userReducer'
|
import { addLocality } from '@/redux/userReducer'
|
||||||
import { EditDataType, Locality, LocalityDetailsType } from '@/shared/types'
|
import { EditDataType, EditMetaData, Locality, LocalityDetailsType } from '@/shared/types'
|
||||||
|
|
||||||
const sanitizeLocalityProjects = (locality?: EditDataType<LocalityDetailsType>) => {
|
const sanitizeLocalityProjects = (locality?: EditDataType<LocalityDetailsType>) => {
|
||||||
if (!locality || !locality.now_plr) return [] as LocalityDetailsType['now_plr']
|
if (!locality || !locality.now_plr) return [] as LocalityDetailsType['now_plr']
|
||||||
@@ -23,14 +23,14 @@ const localitiesApi = api.injectEndpoints({
|
|||||||
}),
|
}),
|
||||||
providesTags: result => (result ? [{ type: 'locality', id: result.lid }] : []),
|
providesTags: result => (result ? [{ type: 'locality', id: result.lid }] : []),
|
||||||
}),
|
}),
|
||||||
editLocality: builder.mutation<{ id: number }, EditDataType<LocalityDetailsType>>({
|
editLocality: builder.mutation<{ id: number }, EditDataType<LocalityDetailsType> & EditMetaData>({
|
||||||
query: locality => ({
|
query: locality => ({
|
||||||
url: `/locality`,
|
url: `/locality`,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: { locality },
|
body: { locality },
|
||||||
}),
|
}),
|
||||||
invalidatesTags: (result, _error, { lid }) =>
|
invalidatesTags: (result, _error, { lid }) =>
|
||||||
result ? [{ type: 'locality', id: lid }, 'localities', 'specieslist'] : [],
|
result ? [{ type: 'locality', id: lid }, 'localities', 'specieslist', 'occurrence'] : [],
|
||||||
async onQueryStarted(locality, { dispatch, queryFulfilled }) {
|
async onQueryStarted(locality, { dispatch, queryFulfilled }) {
|
||||||
if (!locality.lid) {
|
if (!locality.lid) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user