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
|
||||
}
|
||||
|
||||
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_mus', locality.now_mus, ['lid', 'museum'])
|
||||
await writeHandler.applyListChanges('now_ss', locality.now_ss, ['lid', 'sed_struct'])
|
||||
|
||||
@@ -248,6 +248,7 @@ export const OccurrencesTab = () => {
|
||||
|
||||
return (
|
||||
<Grouped title={occurrenceLabels.informationSectionTitle}>
|
||||
{!mode.read && (
|
||||
<Button
|
||||
disabled={mode.new}
|
||||
variant="contained"
|
||||
@@ -257,7 +258,7 @@ export const OccurrencesTab = () => {
|
||||
>
|
||||
Create new occurrence
|
||||
</Button>
|
||||
{!mode.read && editingModal}
|
||||
)}
|
||||
<EditableTable<Editable<LocalitySpecies>, LocalityDetailsType>
|
||||
columns={columns}
|
||||
field="now_ls"
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
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 { UpdateTab } from '@/components/DetailView/common/UpdateTab'
|
||||
import { OccurrenceCoreTab } from './Tabs/OccurrenceCoreTab'
|
||||
import { OccurrenceWearTab } from './Tabs/OccurrenceWearTab'
|
||||
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 { getErrorMessage, useNotify } from '@/hooks/notification'
|
||||
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 = (
|
||||
editData: EditDataType<OccurrenceDetailsType>,
|
||||
@@ -66,10 +73,41 @@ const emptyOccurrence: OccurrenceDetailsType = {
|
||||
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 = () => {
|
||||
const { id, lid, speciesId } = useParams()
|
||||
const [searchParams] = useSearchParams()
|
||||
console.log(searchParams)
|
||||
const isNew = id === 'new'
|
||||
if (isNew) {
|
||||
document.title = 'New locality'
|
||||
@@ -86,11 +124,15 @@ export const OccurrenceDetails = () => {
|
||||
skip: isNew,
|
||||
}
|
||||
)
|
||||
|
||||
const { notify } = useNotify()
|
||||
const [editOccurrenceRequest, { isLoading: mutationLoading }] = useEditOccurrenceMutation()
|
||||
const navigate = useNavigate()
|
||||
const [editLocalityRequest, { isLoading: mutationLoading }] = useEditLocalityMutation()
|
||||
|
||||
const lidFromSearchParams = searchParams.get('lid')
|
||||
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 (isLoading || (!occurrenceData && !isNew) || mutationLoading) return <CircularProgress />
|
||||
@@ -98,19 +140,58 @@ export const OccurrenceDetails = () => {
|
||||
const initialOccurrence = emptyOccurrence
|
||||
|
||||
if (isNew) {
|
||||
initialOccurrence.lid = parseInt(lidFromSearchParams!, 10)
|
||||
initialOccurrence.loc_name = locNameFromSearchParams!
|
||||
initialOccurrence.lid = parseInt(localityId, 10)
|
||||
initialOccurrence.loc_name = locNameFromSearchParams ?? ''
|
||||
}
|
||||
|
||||
if (occurrenceData) {
|
||||
document.title = `Occurrence - ${occurrenceData.lid}/${occurrenceData.species_id}`
|
||||
}
|
||||
|
||||
const onWrite = async (editData: EditDataType<OccurrenceDetailsType>) => {
|
||||
const onWrite = async (editData: EditDataType<OccurrenceDetailsType> & EditMetaData) => {
|
||||
try {
|
||||
console.log(editData)
|
||||
await editOccurrenceRequest(editData).unwrap()
|
||||
if (!localityData) throw new Error('Could not load the linked locality.')
|
||||
|
||||
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.')
|
||||
setTimeout(() => navigate(`/occurrence/${editData.lid}/${editData.species_id}`), 15)
|
||||
} catch (error) {
|
||||
notify(getErrorMessage(error, 'Could not finalize occurrence entry.'), 'error')
|
||||
throw error
|
||||
|
||||
@@ -1,37 +1,51 @@
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
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 { idStatusOptions, quantityOptions } from '../constants'
|
||||
import { useGetAllSpeciesQuery } from '@/redux/speciesReducer'
|
||||
import { buildTaxonomySuggestionOptions, TaxonomySuggestionFieldName } from '@/components/Species/taxonomySuggestions'
|
||||
import { useMemo } from 'react'
|
||||
import { TaxonomySuggestionField } from '@/components/Species/TaxonomySuggestionField'
|
||||
import { SelectingTable } from '@/components/DetailView/common/SelectingTable'
|
||||
import { MRT_ColumnDef } from 'material-react-table'
|
||||
|
||||
const toText = (value: string | number | null | undefined) =>
|
||||
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 = () => {
|
||||
const { data: speciesQueryData, isError } = useGetAllSpeciesQuery()
|
||||
const { data, editData, 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]} />
|
||||
)
|
||||
const { data, editData, setEditData, mode, textField, dropdown } = useDetailContext<OccurrenceDetailsType>()
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -47,9 +61,35 @@ export const OccurrenceCoreTab = () => {
|
||||
{toText(data.loc_name)}
|
||||
</Link>,
|
||||
],
|
||||
['Family', taxonomySuggestionField('family_name')],
|
||||
['Genus', taxonomySuggestionField('genus_name')],
|
||||
['Species', taxonomySuggestionField('species_name')],
|
||||
!mode.read
|
||||
? [
|
||||
'',
|
||||
<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')],
|
||||
['Additional Information', textField('orig_entry', { type: 'text' })],
|
||||
['Source name', textField('source_name', { type: 'text' })],
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { api } from './api'
|
||||
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>) => {
|
||||
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 }] : []),
|
||||
}),
|
||||
editLocality: builder.mutation<{ id: number }, EditDataType<LocalityDetailsType>>({
|
||||
editLocality: builder.mutation<{ id: number }, EditDataType<LocalityDetailsType> & EditMetaData>({
|
||||
query: locality => ({
|
||||
url: `/locality`,
|
||||
method: 'PUT',
|
||||
body: { locality },
|
||||
}),
|
||||
invalidatesTags: (result, _error, { lid }) =>
|
||||
result ? [{ type: 'locality', id: lid }, 'localities', 'specieslist'] : [],
|
||||
result ? [{ type: 'locality', id: lid }, 'localities', 'specieslist', 'occurrence'] : [],
|
||||
async onQueryStarted(locality, { dispatch, queryFulfilled }) {
|
||||
if (!locality.lid) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user