mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Implement occurrence detail tabs with updates placeholder
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { describe, expect, it, jest } from '@jest/globals'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { UpdateTab } from './UpdateTab'
|
||||
|
||||
jest.mock('@/components/DetailView/Context/DetailContext', () => ({
|
||||
useDetailContext: () => ({ data: {} }),
|
||||
}))
|
||||
|
||||
jest.mock('./EditingModal', () => ({
|
||||
EditingModal: ({ children }: { children: ReactNode }) => <div>{children}</div>,
|
||||
}))
|
||||
|
||||
jest.mock('./SimpleTable', () => ({
|
||||
SimpleTable: () => <div data-testid="simple-table" />,
|
||||
}))
|
||||
|
||||
jest.mock('./ReferenceList', () => ({
|
||||
ReferenceList: () => <div data-testid="reference-list" />,
|
||||
}))
|
||||
|
||||
describe('UpdateTab', () => {
|
||||
it('renders placeholder message when placeholder mode is enabled', () => {
|
||||
render(<UpdateTab prefix="occ" placeholderMessage="Updates tab placeholder. Implementation is pending." />)
|
||||
|
||||
expect(screen.getByText('Updates tab placeholder. Implementation is pending.')).toBeTruthy()
|
||||
})
|
||||
})
|
||||
@@ -16,13 +16,25 @@ export const UpdateTab = <T, UpdateType extends MRT_RowData & { updates: UpdateL
|
||||
prefix,
|
||||
refFieldName,
|
||||
updatesFieldName,
|
||||
placeholderMessage,
|
||||
}: {
|
||||
prefix: string
|
||||
refFieldName: keyof UpdateType
|
||||
updatesFieldName: keyof T
|
||||
refFieldName?: keyof UpdateType
|
||||
updatesFieldName?: keyof T
|
||||
placeholderMessage?: string
|
||||
}) => {
|
||||
const { data } = useDetailContext<T>()
|
||||
|
||||
if (placeholderMessage) {
|
||||
return (
|
||||
<Grouped title="Updates">
|
||||
<Box>{placeholderMessage}</Box>
|
||||
</Grouped>
|
||||
)
|
||||
}
|
||||
|
||||
if (!refFieldName || !updatesFieldName) return null
|
||||
|
||||
const columns: MRT_ColumnDef<UpdateType>[] = [
|
||||
{
|
||||
accessorKey: `${prefix}_date`,
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { CircularProgress } from '@mui/material'
|
||||
import { useParams } 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 { useOccurrenceDetails } from '@/hooks/useOccurrenceDetails'
|
||||
import { OccurrenceDetailsType } from '@/shared/types'
|
||||
import { ValidationObject } from '@/shared/validators/validator'
|
||||
|
||||
const validateOccurrence = (): ValidationObject => ({ error: null, name: '' })
|
||||
|
||||
const emptyOccurrence: OccurrenceDetailsType = {
|
||||
lid: 0,
|
||||
species_id: 0,
|
||||
loc_status: null,
|
||||
loc_name: '',
|
||||
country: '',
|
||||
genus_name: '',
|
||||
species_name: '',
|
||||
nis: null,
|
||||
pct: null,
|
||||
quad: null,
|
||||
mni: null,
|
||||
qua: null,
|
||||
id_status: null,
|
||||
orig_entry: null,
|
||||
source_name: null,
|
||||
body_mass: null,
|
||||
mesowear: null,
|
||||
mw_or_high: null,
|
||||
mw_or_low: null,
|
||||
mw_cs_sharp: null,
|
||||
mw_cs_round: null,
|
||||
mw_cs_blunt: null,
|
||||
mw_scale_min: null,
|
||||
mw_scale_max: null,
|
||||
mw_value: null,
|
||||
microwear: null,
|
||||
dc13_mean: null,
|
||||
dc13_n: null,
|
||||
dc13_max: null,
|
||||
dc13_min: null,
|
||||
dc13_stdev: null,
|
||||
do18_mean: null,
|
||||
do18_n: null,
|
||||
do18_max: null,
|
||||
do18_min: null,
|
||||
do18_stdev: null,
|
||||
}
|
||||
|
||||
export const OccurrenceDetails = () => {
|
||||
const { lid, speciesId } = useParams()
|
||||
const parsedLid = lid ? parseInt(lid, 10) : null
|
||||
const parsedSpeciesId = speciesId ? parseInt(speciesId, 10) : null
|
||||
const { occurrence, isLoading, isError } = useOccurrenceDetails(parsedLid, parsedSpeciesId)
|
||||
|
||||
if (isError) return <div>Error loading occurrence data</div>
|
||||
if (isLoading || !occurrence) return <CircularProgress />
|
||||
|
||||
document.title = `Occurrence - ${occurrence.lid}/${occurrence.species_id}`
|
||||
|
||||
const tabs: TabType[] = [
|
||||
{ title: 'Core', content: <OccurrenceCoreTab /> },
|
||||
{ title: 'Wear', content: <OccurrenceWearTab /> },
|
||||
{ title: 'Isotopes', content: <OccurrenceIsotopeTab /> },
|
||||
{
|
||||
title: 'Updates',
|
||||
content: <UpdateTab prefix="occ" placeholderMessage="Updates tab placeholder. Implementation is pending." />,
|
||||
},
|
||||
]
|
||||
|
||||
return (
|
||||
<DetailView<OccurrenceDetailsType>
|
||||
tabs={tabs}
|
||||
data={occurrence ?? emptyOccurrence}
|
||||
validator={validateOccurrence}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
import { ArrayFrame, HalfFrames } from '@/components/DetailView/common/tabLayoutHelpers'
|
||||
import { OccurrenceDetailsType } from '@/shared/types'
|
||||
|
||||
const toText = (value: string | number | null) => (value === null || value === '' ? '-' : String(value))
|
||||
|
||||
export const OccurrenceCoreTab = () => {
|
||||
const { data } = useDetailContext<OccurrenceDetailsType>()
|
||||
|
||||
return (
|
||||
<HalfFrames>
|
||||
{[
|
||||
<ArrayFrame
|
||||
key="identification"
|
||||
title="Identification"
|
||||
array={[
|
||||
['Locality ID', toText(data.lid)],
|
||||
['Species ID', toText(data.species_id)],
|
||||
['Genus', toText(data.genus_name)],
|
||||
['Species', toText(data.species_name)],
|
||||
['ID status', toText(data.id_status)],
|
||||
['Quality', toText(data.qua)],
|
||||
]}
|
||||
/>,
|
||||
<ArrayFrame
|
||||
key="counts"
|
||||
title="Occurrence counts"
|
||||
array={[
|
||||
['NISP', toText(data.nis)],
|
||||
['Percent', toText(data.pct)],
|
||||
['Quadrate', toText(data.quad)],
|
||||
['MNI', toText(data.mni)],
|
||||
['Original entry', toText(data.orig_entry)],
|
||||
['Source name', toText(data.source_name)],
|
||||
]}
|
||||
/>,
|
||||
]}
|
||||
</HalfFrames>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
import { ArrayFrame, HalfFrames } from '@/components/DetailView/common/tabLayoutHelpers'
|
||||
import { OccurrenceDetailsType } from '@/shared/types'
|
||||
|
||||
const toText = (value: number | null) => (value === null ? '-' : String(value))
|
||||
|
||||
export const OccurrenceIsotopeTab = () => {
|
||||
const { data } = useDetailContext<OccurrenceDetailsType>()
|
||||
|
||||
return (
|
||||
<HalfFrames>
|
||||
{[
|
||||
<ArrayFrame
|
||||
key="dc13"
|
||||
title="δ13C"
|
||||
array={[
|
||||
['Mean', toText(data.dc13_mean)],
|
||||
['N', toText(data.dc13_n)],
|
||||
['Max', toText(data.dc13_max)],
|
||||
['Min', toText(data.dc13_min)],
|
||||
['Stdev', toText(data.dc13_stdev)],
|
||||
]}
|
||||
/>,
|
||||
<ArrayFrame
|
||||
key="do18"
|
||||
title="δ18O"
|
||||
array={[
|
||||
['Mean', toText(data.do18_mean)],
|
||||
['N', toText(data.do18_n)],
|
||||
['Max', toText(data.do18_max)],
|
||||
['Min', toText(data.do18_min)],
|
||||
['Stdev', toText(data.do18_stdev)],
|
||||
]}
|
||||
/>,
|
||||
]}
|
||||
</HalfFrames>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
import { useDetailContext } from '@/components/DetailView/Context/DetailContext'
|
||||
import { ArrayFrame, HalfFrames } from '@/components/DetailView/common/tabLayoutHelpers'
|
||||
import { OccurrenceDetailsType } from '@/shared/types'
|
||||
|
||||
const toText = (value: string | number | null) => (value === null || value === '' ? '-' : String(value))
|
||||
|
||||
export const OccurrenceWearTab = () => {
|
||||
const { data } = useDetailContext<OccurrenceDetailsType>()
|
||||
|
||||
return (
|
||||
<HalfFrames>
|
||||
{[
|
||||
<ArrayFrame
|
||||
key="mesowear"
|
||||
title="Mesowear"
|
||||
array={[
|
||||
['Mesowear', toText(data.mesowear)],
|
||||
['MW OR High', toText(data.mw_or_high)],
|
||||
['MW OR Low', toText(data.mw_or_low)],
|
||||
['MW CS Sharp', toText(data.mw_cs_sharp)],
|
||||
['MW CS Round', toText(data.mw_cs_round)],
|
||||
['MW CS Blunt', toText(data.mw_cs_blunt)],
|
||||
]}
|
||||
/>,
|
||||
<ArrayFrame
|
||||
key="microwear"
|
||||
title="Microwear"
|
||||
array={[
|
||||
['Microwear', toText(data.microwear)],
|
||||
['MW scale min', toText(data.mw_scale_min)],
|
||||
['MW scale max', toText(data.mw_scale_max)],
|
||||
['MW value', toText(data.mw_value)],
|
||||
['Body mass', toText(data.body_mass)],
|
||||
]}
|
||||
/>,
|
||||
]}
|
||||
</HalfFrames>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { describe, expect, it, jest, beforeEach } from '@jest/globals'
|
||||
import { render, screen } from '@testing-library/react'
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom'
|
||||
import { OccurrenceDetails } from '../OccurrenceDetails'
|
||||
import { useOccurrenceDetails } from '@/hooks/useOccurrenceDetails'
|
||||
|
||||
jest.mock('@/hooks/useOccurrenceDetails', () => ({
|
||||
useOccurrenceDetails: jest.fn(),
|
||||
}))
|
||||
|
||||
jest.mock('../Tabs/OccurrenceCoreTab', () => ({ OccurrenceCoreTab: () => <div>Core tab</div> }))
|
||||
jest.mock('../Tabs/OccurrenceWearTab', () => ({ OccurrenceWearTab: () => <div>Wear tab</div> }))
|
||||
jest.mock('../Tabs/OccurrenceIsotopeTab', () => ({ OccurrenceIsotopeTab: () => <div>Isotope tab</div> }))
|
||||
jest.mock('@/components/DetailView/common/UpdateTab', () => ({ UpdateTab: () => <div>Updates placeholder</div> }))
|
||||
|
||||
jest.mock('@/components/DetailView/DetailView', () => ({
|
||||
DetailView: ({ tabs }: { tabs: Array<{ title: string }> }) => (
|
||||
<div data-testid="occurrence-detail-view">{tabs.map(tab => tab.title).join('|')}</div>
|
||||
),
|
||||
}))
|
||||
|
||||
const mockUseOccurrenceDetails = useOccurrenceDetails as jest.MockedFunction<typeof useOccurrenceDetails>
|
||||
|
||||
describe('OccurrenceDetails', () => {
|
||||
beforeEach(() => {
|
||||
mockUseOccurrenceDetails.mockReset()
|
||||
})
|
||||
|
||||
it('renders occurrence tabs including Updates placeholder tab', () => {
|
||||
mockUseOccurrenceDetails.mockReturnValue({
|
||||
occurrence: {
|
||||
lid: 1,
|
||||
species_id: 2,
|
||||
loc_status: false,
|
||||
loc_name: 'Loc',
|
||||
country: 'Country',
|
||||
genus_name: 'Genus',
|
||||
species_name: 'species',
|
||||
nis: null,
|
||||
pct: null,
|
||||
quad: null,
|
||||
mni: null,
|
||||
qua: null,
|
||||
id_status: null,
|
||||
orig_entry: null,
|
||||
source_name: null,
|
||||
body_mass: null,
|
||||
mesowear: null,
|
||||
mw_or_high: null,
|
||||
mw_or_low: null,
|
||||
mw_cs_sharp: null,
|
||||
mw_cs_round: null,
|
||||
mw_cs_blunt: null,
|
||||
mw_scale_min: null,
|
||||
mw_scale_max: null,
|
||||
mw_value: null,
|
||||
microwear: null,
|
||||
dc13_mean: null,
|
||||
dc13_n: null,
|
||||
dc13_max: null,
|
||||
dc13_min: null,
|
||||
dc13_stdev: null,
|
||||
do18_mean: null,
|
||||
do18_n: null,
|
||||
do18_max: null,
|
||||
do18_min: null,
|
||||
do18_stdev: null,
|
||||
},
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
refetch: jest.fn(() => Promise.resolve(undefined)),
|
||||
})
|
||||
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/occurrence/1/2?tab=3']}>
|
||||
<Routes>
|
||||
<Route path="/occurrence/:lid/:speciesId" element={<OccurrenceDetails />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('occurrence-detail-view').textContent).toBe('Core|Wear|Isotopes|Updates')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user